xref: /aosp_15_r20/external/bazelbuild-rules_rust/rust/private/rustdoc.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Rules for generating documentation with `rustdoc` for Bazel built crates"""
16
17load("//rust/private:common.bzl", "rust_common")
18load("//rust/private:rustc.bzl", "collect_deps", "collect_inputs", "construct_arguments")
19load("//rust/private:utils.bzl", "dedent", "find_cc_toolchain", "find_toolchain")
20
21def _strip_crate_info_output(crate_info):
22    """Set the CrateInfo.output to None for a given CrateInfo provider.
23
24    Args:
25        crate_info (CrateInfo): A provider
26
27    Returns:
28        CrateInfo: A modified CrateInfo provider
29    """
30    return rust_common.create_crate_info(
31        name = crate_info.name,
32        type = crate_info.type,
33        root = crate_info.root,
34        srcs = crate_info.srcs,
35        deps = crate_info.deps,
36        proc_macro_deps = crate_info.proc_macro_deps,
37        aliases = crate_info.aliases,
38        # This crate info should have no output
39        output = None,
40        metadata = None,
41        edition = crate_info.edition,
42        rustc_env = crate_info.rustc_env,
43        rustc_env_files = crate_info.rustc_env_files,
44        is_test = crate_info.is_test,
45        compile_data = crate_info.compile_data,
46        compile_data_targets = crate_info.compile_data_targets,
47        data = crate_info.data,
48    )
49
50def rustdoc_compile_action(
51        ctx,
52        toolchain,
53        crate_info,
54        output = None,
55        rustdoc_flags = [],
56        is_test = False):
57    """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule.
58
59    Args:
60        ctx (ctx): The rule's context object.
61        toolchain (rust_toolchain): The currently configured `rust_toolchain`.
62        crate_info (CrateInfo): The provider of the crate passed to a rustdoc rule.
63        output (File, optional): An optional output a `rustdoc` action is intended to produce.
64        rustdoc_flags (list, optional): A list of `rustdoc` specific flags.
65        is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets
66
67    Returns:
68        struct: A struct of some `ctx.actions.run` arguments.
69    """
70
71    # If an output was provided, ensure it's used in rustdoc arguments
72    if output:
73        rustdoc_flags = [
74            "--output",
75            output.path,
76        ] + rustdoc_flags
77
78    cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
79
80    dep_info, build_info, _ = collect_deps(
81        deps = crate_info.deps,
82        proc_macro_deps = crate_info.proc_macro_deps,
83        aliases = crate_info.aliases,
84    )
85
86    compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
87        ctx = ctx,
88        file = ctx.file,
89        files = ctx.files,
90        linkstamps = depset([]),
91        toolchain = toolchain,
92        cc_toolchain = cc_toolchain,
93        feature_configuration = feature_configuration,
94        crate_info = crate_info,
95        dep_info = dep_info,
96        build_info = build_info,
97        # If this is a rustdoc test, we need to depend on rlibs rather than .rmeta.
98        force_depend_on_objects = is_test,
99        include_link_flags = False,
100    )
101
102    # Since this crate is not actually producing the output described by the
103    # given CrateInfo, this attribute needs to be stripped to allow the rest
104    # of the rustc functionality in `construct_arguments` to avoid generating
105    # arguments expecting to do so.
106    rustdoc_crate_info = _strip_crate_info_output(crate_info)
107
108    args, env = construct_arguments(
109        ctx = ctx,
110        attr = ctx.attr,
111        file = ctx.file,
112        toolchain = toolchain,
113        tool_path = toolchain.rust_doc.short_path if is_test else toolchain.rust_doc.path,
114        cc_toolchain = cc_toolchain,
115        feature_configuration = feature_configuration,
116        crate_info = rustdoc_crate_info,
117        dep_info = dep_info,
118        linkstamp_outs = linkstamp_outs,
119        ambiguous_libs = ambiguous_libs,
120        output_hash = None,
121        rust_flags = rustdoc_flags,
122        out_dir = out_dir,
123        build_env_files = build_env_files,
124        build_flags_files = build_flags_files,
125        emit = [],
126        remap_path_prefix = None,
127        add_flags_for_binary = True,
128        include_link_flags = False,
129        force_depend_on_objects = is_test,
130        skip_expanding_rustc_env = True,
131    )
132
133    # Because rustdoc tests compile tests outside of the sandbox, the sysroot
134    # must be updated to the `short_path` equivilant as it will now be
135    # a part of runfiles.
136    if is_test:
137        if "SYSROOT" in env:
138            env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)})
139        if "OUT_DIR" in env:
140            env.update({"OUT_DIR": "${{pwd}}/{}".format(build_info.out_dir.short_path)})
141
142    return struct(
143        executable = ctx.executable._process_wrapper,
144        inputs = depset([crate_info.output], transitive = [compile_inputs]),
145        env = env,
146        arguments = args.all,
147        tools = [toolchain.rust_doc],
148    )
149
150def _zip_action(ctx, input_dir, output_zip, crate_label):
151    """Creates an archive of the generated documentation from `rustdoc`
152
153    Args:
154        ctx (ctx): The `rust_doc` rule's context object
155        input_dir (File): A directory containing the outputs from rustdoc
156        output_zip (File): The location of the output archive containing generated documentation
157        crate_label (Label): The label of the crate docs are being generated for.
158    """
159    args = ctx.actions.args()
160    args.add(ctx.executable._zipper)
161    args.add(output_zip)
162    args.add(ctx.bin_dir.path)
163    args.add_all([input_dir], expand_directories = True)
164    ctx.actions.run(
165        executable = ctx.executable._dir_zipper,
166        inputs = [input_dir],
167        outputs = [output_zip],
168        arguments = [args],
169        mnemonic = "RustdocZip",
170        progress_message = "Creating RustdocZip for {}".format(crate_label),
171        tools = [ctx.executable._zipper],
172    )
173
174def _rust_doc_impl(ctx):
175    """The implementation of the `rust_doc` rule
176
177    Args:
178        ctx (ctx): The rule's context object
179    """
180
181    if ctx.attr.rustc_flags:
182        # buildifier: disable=print
183        print("rustc_flags is deprecated in favor of `rustdoc_flags` for rustdoc targets. Please update {}".format(
184            ctx.label,
185        ))
186
187    crate = ctx.attr.crate
188    crate_info = crate[rust_common.crate_info]
189
190    output_dir = ctx.actions.declare_directory("{}.rustdoc".format(ctx.label.name))
191
192    # Add the current crate as an extern for the compile action
193    rustdoc_flags = [
194        "--extern",
195        "{}={}".format(crate_info.name, crate_info.output.path),
196    ]
197
198    rustdoc_flags.extend(ctx.attr.rustdoc_flags)
199
200    action = rustdoc_compile_action(
201        ctx = ctx,
202        toolchain = find_toolchain(ctx),
203        crate_info = crate_info,
204        output = output_dir,
205        rustdoc_flags = rustdoc_flags,
206    )
207
208    ctx.actions.run(
209        mnemonic = "Rustdoc",
210        progress_message = "Generating Rustdoc for {}".format(crate.label),
211        outputs = [output_dir],
212        executable = action.executable,
213        inputs = action.inputs,
214        env = action.env,
215        arguments = action.arguments,
216        tools = action.tools,
217    )
218
219    # This rule does nothing without a single-file output, though the directory should've sufficed.
220    _zip_action(ctx, output_dir, ctx.outputs.rust_doc_zip, crate.label)
221
222    return [
223        DefaultInfo(
224            files = depset([output_dir]),
225        ),
226        OutputGroupInfo(
227            rustdoc_dir = depset([output_dir]),
228            rustdoc_zip = depset([ctx.outputs.rust_doc_zip]),
229        ),
230    ]
231
232rust_doc = rule(
233    doc = dedent("""\
234    Generates code documentation.
235
236    Example:
237    Suppose you have the following directory structure for a Rust library crate:
238
239    ```
240    [workspace]/
241        WORKSPACE
242        hello_lib/
243            BUILD
244            src/
245                lib.rs
246    ```
247
248    To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define \
249    a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target:
250
251    [rustdoc]: https://doc.rust-lang.org/book/documentation.html
252
253    ```python
254    package(default_visibility = ["//visibility:public"])
255
256    load("@rules_rust//rust:defs.bzl", "rust_library", "rust_doc")
257
258    rust_library(
259        name = "hello_lib",
260        srcs = ["src/lib.rs"],
261    )
262
263    rust_doc(
264        name = "hello_lib_doc",
265        crate = ":hello_lib",
266    )
267    ```
268
269    Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing \
270    the documentation for the `hello_lib` library crate generated by `rustdoc`.
271    """),
272    implementation = _rust_doc_impl,
273    attrs = {
274        "crate": attr.label(
275            doc = (
276                "The label of the target to generate code documentation for.\n" +
277                "\n" +
278                "`rust_doc` can generate HTML code documentation for the source files of " +
279                "`rust_library` or `rust_binary` targets."
280            ),
281            providers = [rust_common.crate_info],
282            mandatory = True,
283        ),
284        "html_after_content": attr.label(
285            doc = "File to add in `<body>`, after content.",
286            allow_single_file = [".html", ".md"],
287        ),
288        "html_before_content": attr.label(
289            doc = "File to add in `<body>`, before content.",
290            allow_single_file = [".html", ".md"],
291        ),
292        "html_in_header": attr.label(
293            doc = "File to add to `<head>`.",
294            allow_single_file = [".html", ".md"],
295        ),
296        "markdown_css": attr.label_list(
297            doc = "CSS files to include via `<link>` in a rendered Markdown file.",
298            allow_files = [".css"],
299        ),
300        "rustc_flags": attr.string_list(
301            doc = "**Deprecated**: use `rustdoc_flags` instead",
302        ),
303        "rustdoc_flags": attr.string_list(
304            doc = dedent("""\
305                List of flags passed to `rustdoc`.
306
307                These strings are subject to Make variable expansion for predefined
308                source/output path variables like `$location`, `$execpath`, and
309                `$rootpath`. This expansion is useful if you wish to pass a generated
310                file of arguments to rustc: `@$(location //package:target)`.
311            """),
312        ),
313        "_cc_toolchain": attr.label(
314            doc = "In order to use find_cpp_toolchain, you must define the '_cc_toolchain' attribute on your rule or aspect.",
315            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
316        ),
317        "_dir_zipper": attr.label(
318            doc = "A tool that orchestrates the creation of zip archives for rustdoc outputs.",
319            default = Label("//util/dir_zipper"),
320            cfg = "exec",
321            executable = True,
322        ),
323        "_error_format": attr.label(
324            default = Label("//:error_format"),
325        ),
326        "_process_wrapper": attr.label(
327            doc = "A process wrapper for running rustdoc on all platforms",
328            default = Label("@rules_rust//util/process_wrapper"),
329            executable = True,
330            allow_single_file = True,
331            cfg = "exec",
332        ),
333        "_zipper": attr.label(
334            doc = "A Bazel provided tool for creating archives",
335            default = Label("@bazel_tools//tools/zip:zipper"),
336            cfg = "exec",
337            executable = True,
338        ),
339    },
340    fragments = ["cpp"],
341    outputs = {
342        "rust_doc_zip": "%{name}.zip",
343    },
344    toolchains = [
345        str(Label("//rust:toolchain_type")),
346        "@bazel_tools//tools/cpp:toolchain_type",
347    ],
348)
349