| # Copyright 2018 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Rules for generating documentation with `rustdoc` for Bazel built crates""" |
| |
| load("//rust/private:common.bzl", "rust_common") |
| load("//rust/private:rustc.bzl", "collect_deps", "collect_inputs", "construct_arguments") |
| load("//rust/private:utils.bzl", "dedent", "find_cc_toolchain", "find_toolchain") |
| |
| def _strip_crate_info_output(crate_info): |
| """Set the CrateInfo.output to None for a given CrateInfo provider. |
| |
| Args: |
| crate_info (CrateInfo): A provider |
| |
| Returns: |
| CrateInfo: A modified CrateInfo provider |
| """ |
| return rust_common.create_crate_info( |
| name = crate_info.name, |
| type = crate_info.type, |
| root = crate_info.root, |
| srcs = crate_info.srcs, |
| deps = crate_info.deps, |
| proc_macro_deps = crate_info.proc_macro_deps, |
| aliases = crate_info.aliases, |
| # This crate info should have no output |
| output = None, |
| metadata = None, |
| edition = crate_info.edition, |
| rustc_env = crate_info.rustc_env, |
| rustc_env_files = crate_info.rustc_env_files, |
| is_test = crate_info.is_test, |
| compile_data = crate_info.compile_data, |
| compile_data_targets = crate_info.compile_data_targets, |
| data = crate_info.data, |
| ) |
| |
| def rustdoc_compile_action( |
| ctx, |
| toolchain, |
| crate_info, |
| output = None, |
| rustdoc_flags = [], |
| is_test = False): |
| """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule. |
| |
| Args: |
| ctx (ctx): The rule's context object. |
| toolchain (rust_toolchain): The currently configured `rust_toolchain`. |
| crate_info (CrateInfo): The provider of the crate passed to a rustdoc rule. |
| output (File, optional): An optional output a `rustdoc` action is intended to produce. |
| rustdoc_flags (list, optional): A list of `rustdoc` specific flags. |
| is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets |
| |
| Returns: |
| struct: A struct of some `ctx.actions.run` arguments. |
| """ |
| |
| # If an output was provided, ensure it's used in rustdoc arguments |
| if output: |
| rustdoc_flags = [ |
| "--output", |
| output.path, |
| ] + rustdoc_flags |
| |
| cc_toolchain, feature_configuration = find_cc_toolchain(ctx) |
| |
| dep_info, build_info, _ = collect_deps( |
| deps = crate_info.deps, |
| proc_macro_deps = crate_info.proc_macro_deps, |
| aliases = crate_info.aliases, |
| ) |
| |
| compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( |
| ctx = ctx, |
| file = ctx.file, |
| files = ctx.files, |
| linkstamps = depset([]), |
| toolchain = toolchain, |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| crate_info = crate_info, |
| dep_info = dep_info, |
| build_info = build_info, |
| # If this is a rustdoc test, we need to depend on rlibs rather than .rmeta. |
| force_depend_on_objects = is_test, |
| include_link_flags = False, |
| ) |
| |
| # Since this crate is not actually producing the output described by the |
| # given CrateInfo, this attribute needs to be stripped to allow the rest |
| # of the rustc functionality in `construct_arguments` to avoid generating |
| # arguments expecting to do so. |
| rustdoc_crate_info = _strip_crate_info_output(crate_info) |
| |
| args, env = construct_arguments( |
| ctx = ctx, |
| attr = ctx.attr, |
| file = ctx.file, |
| toolchain = toolchain, |
| tool_path = toolchain.rust_doc.short_path if is_test else toolchain.rust_doc.path, |
| cc_toolchain = cc_toolchain, |
| feature_configuration = feature_configuration, |
| crate_info = rustdoc_crate_info, |
| dep_info = dep_info, |
| linkstamp_outs = linkstamp_outs, |
| ambiguous_libs = ambiguous_libs, |
| output_hash = None, |
| rust_flags = rustdoc_flags, |
| out_dir = out_dir, |
| build_env_files = build_env_files, |
| build_flags_files = build_flags_files, |
| emit = [], |
| remap_path_prefix = None, |
| add_flags_for_binary = True, |
| include_link_flags = False, |
| force_depend_on_objects = is_test, |
| skip_expanding_rustc_env = True, |
| ) |
| |
| # Because rustdoc tests compile tests outside of the sandbox, the sysroot |
| # must be updated to the `short_path` equivilant as it will now be |
| # a part of runfiles. |
| if is_test: |
| if "SYSROOT" in env: |
| env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)}) |
| if "OUT_DIR" in env: |
| env.update({"OUT_DIR": "${{pwd}}/{}".format(build_info.out_dir.short_path)}) |
| |
| return struct( |
| executable = ctx.executable._process_wrapper, |
| inputs = depset([crate_info.output], transitive = [compile_inputs]), |
| env = env, |
| arguments = args.all, |
| tools = [toolchain.rust_doc], |
| ) |
| |
| def _zip_action(ctx, input_dir, output_zip, crate_label): |
| """Creates an archive of the generated documentation from `rustdoc` |
| |
| Args: |
| ctx (ctx): The `rust_doc` rule's context object |
| input_dir (File): A directory containing the outputs from rustdoc |
| output_zip (File): The location of the output archive containing generated documentation |
| crate_label (Label): The label of the crate docs are being generated for. |
| """ |
| args = ctx.actions.args() |
| args.add(ctx.executable._zipper) |
| args.add(output_zip) |
| args.add(ctx.bin_dir.path) |
| args.add_all([input_dir], expand_directories = True) |
| ctx.actions.run( |
| executable = ctx.executable._dir_zipper, |
| inputs = [input_dir], |
| outputs = [output_zip], |
| arguments = [args], |
| mnemonic = "RustdocZip", |
| progress_message = "Creating RustdocZip for {}".format(crate_label), |
| tools = [ctx.executable._zipper], |
| ) |
| |
| def _rust_doc_impl(ctx): |
| """The implementation of the `rust_doc` rule |
| |
| Args: |
| ctx (ctx): The rule's context object |
| """ |
| |
| if ctx.attr.rustc_flags: |
| # buildifier: disable=print |
| print("rustc_flags is deprecated in favor of `rustdoc_flags` for rustdoc targets. Please update {}".format( |
| ctx.label, |
| )) |
| |
| crate = ctx.attr.crate |
| crate_info = crate[rust_common.crate_info] |
| |
| output_dir = ctx.actions.declare_directory("{}.rustdoc".format(ctx.label.name)) |
| |
| # Add the current crate as an extern for the compile action |
| rustdoc_flags = [ |
| "--extern", |
| "{}={}".format(crate_info.name, crate_info.output.path), |
| ] |
| |
| rustdoc_flags.extend(ctx.attr.rustdoc_flags) |
| |
| action = rustdoc_compile_action( |
| ctx = ctx, |
| toolchain = find_toolchain(ctx), |
| crate_info = crate_info, |
| output = output_dir, |
| rustdoc_flags = rustdoc_flags, |
| ) |
| |
| ctx.actions.run( |
| mnemonic = "Rustdoc", |
| progress_message = "Generating Rustdoc for {}".format(crate.label), |
| outputs = [output_dir], |
| executable = action.executable, |
| inputs = action.inputs, |
| env = action.env, |
| arguments = action.arguments, |
| tools = action.tools, |
| ) |
| |
| # This rule does nothing without a single-file output, though the directory should've sufficed. |
| _zip_action(ctx, output_dir, ctx.outputs.rust_doc_zip, crate.label) |
| |
| return [ |
| DefaultInfo( |
| files = depset([output_dir]), |
| ), |
| OutputGroupInfo( |
| rustdoc_dir = depset([output_dir]), |
| rustdoc_zip = depset([ctx.outputs.rust_doc_zip]), |
| ), |
| ] |
| |
| rust_doc = rule( |
| doc = dedent("""\ |
| Generates code documentation. |
| |
| Example: |
| Suppose you have the following directory structure for a Rust library crate: |
| |
| ``` |
| [workspace]/ |
| WORKSPACE |
| hello_lib/ |
| BUILD |
| src/ |
| lib.rs |
| ``` |
| |
| To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define \ |
| a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target: |
| |
| [rustdoc]: https://doc.rust-lang.org/book/documentation.html |
| |
| ```python |
| package(default_visibility = ["//visibility:public"]) |
| |
| load("@rules_rust//rust:defs.bzl", "rust_library", "rust_doc") |
| |
| rust_library( |
| name = "hello_lib", |
| srcs = ["src/lib.rs"], |
| ) |
| |
| rust_doc( |
| name = "hello_lib_doc", |
| crate = ":hello_lib", |
| ) |
| ``` |
| |
| Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing \ |
| the documentation for the `hello_lib` library crate generated by `rustdoc`. |
| """), |
| implementation = _rust_doc_impl, |
| attrs = { |
| "crate": attr.label( |
| doc = ( |
| "The label of the target to generate code documentation for.\n" + |
| "\n" + |
| "`rust_doc` can generate HTML code documentation for the source files of " + |
| "`rust_library` or `rust_binary` targets." |
| ), |
| providers = [rust_common.crate_info], |
| mandatory = True, |
| ), |
| "html_after_content": attr.label( |
| doc = "File to add in `<body>`, after content.", |
| allow_single_file = [".html", ".md"], |
| ), |
| "html_before_content": attr.label( |
| doc = "File to add in `<body>`, before content.", |
| allow_single_file = [".html", ".md"], |
| ), |
| "html_in_header": attr.label( |
| doc = "File to add to `<head>`.", |
| allow_single_file = [".html", ".md"], |
| ), |
| "markdown_css": attr.label_list( |
| doc = "CSS files to include via `<link>` in a rendered Markdown file.", |
| allow_files = [".css"], |
| ), |
| "rustc_flags": attr.string_list( |
| doc = "**Deprecated**: use `rustdoc_flags` instead", |
| ), |
| "rustdoc_flags": attr.string_list( |
| doc = dedent("""\ |
| List of flags passed to `rustdoc`. |
| |
| These strings are subject to Make variable expansion for predefined |
| source/output path variables like `$location`, `$execpath`, and |
| `$rootpath`. This expansion is useful if you wish to pass a generated |
| file of arguments to rustc: `@$(location //package:target)`. |
| """), |
| ), |
| "_cc_toolchain": attr.label( |
| doc = "In order to use find_cpp_toolchain, you must define the '_cc_toolchain' attribute on your rule or aspect.", |
| default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), |
| ), |
| "_dir_zipper": attr.label( |
| doc = "A tool that orchestrates the creation of zip archives for rustdoc outputs.", |
| default = Label("//util/dir_zipper"), |
| cfg = "exec", |
| executable = True, |
| ), |
| "_error_format": attr.label( |
| default = Label("//:error_format"), |
| ), |
| "_process_wrapper": attr.label( |
| doc = "A process wrapper for running rustdoc on all platforms", |
| default = Label("@rules_rust//util/process_wrapper"), |
| executable = True, |
| allow_single_file = True, |
| cfg = "exec", |
| ), |
| "_zipper": attr.label( |
| doc = "A Bazel provided tool for creating archives", |
| default = Label("@bazel_tools//tools/zip:zipper"), |
| cfg = "exec", |
| executable = True, |
| ), |
| }, |
| fragments = ["cpp"], |
| outputs = { |
| "rust_doc_zip": "%{name}.zip", |
| }, |
| toolchains = [ |
| str(Label("//rust:toolchain_type")), |
| "@bazel_tools//tools/cpp:toolchain_type", |
| ], |
| ) |