Brian Silverman | cc09f18 | 2022-03-09 15:40:20 -0800 | [diff] [blame^] | 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 | |
| 17 | load("//rust/private:common.bzl", "rust_common") |
| 18 | load("//rust/private:rustc.bzl", "collect_deps", "collect_inputs", "construct_arguments") |
| 19 | load("//rust/private:utils.bzl", "dedent", "find_cc_toolchain", "find_toolchain") |
| 20 | |
| 21 | def _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 | edition = crate_info.edition, |
| 41 | rustc_env = crate_info.rustc_env, |
| 42 | is_test = crate_info.is_test, |
| 43 | compile_data = crate_info.compile_data, |
| 44 | ) |
| 45 | |
| 46 | def rustdoc_compile_action( |
| 47 | ctx, |
| 48 | toolchain, |
| 49 | crate_info, |
| 50 | output = None, |
| 51 | rustdoc_flags = [], |
| 52 | is_test = False): |
| 53 | """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule. |
| 54 | |
| 55 | Args: |
| 56 | ctx (ctx): The rule's context object. |
| 57 | toolchain (rust_toolchain): The currently configured `rust_toolchain`. |
| 58 | crate_info (CrateInfo): The provider of the crate passed to a rustdoc rule. |
| 59 | output (File, optional): An optional output a `rustdoc` action is intended to produce. |
| 60 | rustdoc_flags (list, optional): A list of `rustdoc` specific flags. |
| 61 | is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets |
| 62 | |
| 63 | Returns: |
| 64 | struct: A struct of some `ctx.actions.run` arguments. |
| 65 | """ |
| 66 | |
| 67 | # If an output was provided, ensure it's used in rustdoc arguments |
| 68 | if output: |
| 69 | rustdoc_flags = [ |
| 70 | "--output", |
| 71 | output.path, |
| 72 | ] + rustdoc_flags |
| 73 | |
| 74 | cc_toolchain, feature_configuration = find_cc_toolchain(ctx) |
| 75 | |
| 76 | dep_info, build_info, linkstamps = collect_deps( |
| 77 | deps = crate_info.deps, |
| 78 | proc_macro_deps = crate_info.proc_macro_deps, |
| 79 | aliases = crate_info.aliases, |
| 80 | ) |
| 81 | |
| 82 | compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs( |
| 83 | ctx = ctx, |
| 84 | file = ctx.file, |
| 85 | files = ctx.files, |
| 86 | linkstamps = linkstamps, |
| 87 | toolchain = toolchain, |
| 88 | cc_toolchain = cc_toolchain, |
| 89 | feature_configuration = feature_configuration, |
| 90 | crate_info = crate_info, |
| 91 | dep_info = dep_info, |
| 92 | build_info = build_info, |
| 93 | ) |
| 94 | |
| 95 | # Since this crate is not actually producing the output described by the |
| 96 | # given CrateInfo, this attribute needs to be stripped to allow the rest |
| 97 | # of the rustc functionality in `construct_arguments` to avoid generating |
| 98 | # arguments expecting to do so. |
| 99 | rustdoc_crate_info = _strip_crate_info_output(crate_info) |
| 100 | |
| 101 | args, env = construct_arguments( |
| 102 | ctx = ctx, |
| 103 | attr = ctx.attr, |
| 104 | file = ctx.file, |
| 105 | toolchain = toolchain, |
| 106 | tool_path = toolchain.rust_doc.short_path if is_test else toolchain.rust_doc.path, |
| 107 | cc_toolchain = cc_toolchain, |
| 108 | feature_configuration = feature_configuration, |
| 109 | crate_info = rustdoc_crate_info, |
| 110 | dep_info = dep_info, |
| 111 | linkstamp_outs = linkstamp_outs, |
| 112 | ambiguous_libs = ambiguous_libs, |
| 113 | output_hash = None, |
| 114 | rust_flags = rustdoc_flags, |
| 115 | out_dir = out_dir, |
| 116 | build_env_files = build_env_files, |
| 117 | build_flags_files = build_flags_files, |
| 118 | emit = [], |
| 119 | remap_path_prefix = None, |
| 120 | force_link = True, |
| 121 | ) |
| 122 | |
| 123 | # Because rustdoc tests compile tests outside of the sandbox, the sysroot |
| 124 | # must be updated to the `short_path` equivilant as it will now be |
| 125 | # a part of runfiles. |
| 126 | if is_test: |
| 127 | if "SYSROOT" in env: |
| 128 | env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)}) |
| 129 | |
| 130 | # `rustdoc` does not support the SYSROOT environment variable. To account |
| 131 | # for this, the flag must be explicitly passed to the `rustdoc` binary. |
| 132 | args.rustc_flags.add("--sysroot=${{pwd}}/{}".format(toolchain.sysroot_short_path)) |
| 133 | |
| 134 | return struct( |
| 135 | executable = ctx.executable._process_wrapper, |
| 136 | inputs = depset([crate_info.output], transitive = [compile_inputs]), |
| 137 | env = env, |
| 138 | arguments = args.all, |
| 139 | tools = [toolchain.rust_doc], |
| 140 | ) |
| 141 | |
| 142 | def _zip_action(ctx, input_dir, output_zip, crate_label): |
| 143 | """Creates an archive of the generated documentation from `rustdoc` |
| 144 | |
| 145 | Args: |
| 146 | ctx (ctx): The `rust_doc` rule's context object |
| 147 | input_dir (File): A directory containing the outputs from rustdoc |
| 148 | output_zip (File): The location of the output archive containing generated documentation |
| 149 | crate_label (Label): The label of the crate docs are being generated for. |
| 150 | """ |
| 151 | args = ctx.actions.args() |
| 152 | args.add(ctx.executable._zipper) |
| 153 | args.add(output_zip) |
| 154 | args.add(ctx.bin_dir.path) |
| 155 | args.add_all([input_dir], expand_directories = True) |
| 156 | ctx.actions.run( |
| 157 | executable = ctx.executable._dir_zipper, |
| 158 | inputs = [input_dir], |
| 159 | outputs = [output_zip], |
| 160 | arguments = [args], |
| 161 | mnemonic = "RustdocZip", |
| 162 | progress_message = "Creating RustdocZip for {}".format(crate_label), |
| 163 | tools = [ctx.executable._zipper], |
| 164 | ) |
| 165 | |
| 166 | def _rust_doc_impl(ctx): |
| 167 | """The implementation of the `rust_doc` rule |
| 168 | |
| 169 | Args: |
| 170 | ctx (ctx): The rule's context object |
| 171 | """ |
| 172 | |
| 173 | crate = ctx.attr.crate |
| 174 | crate_info = crate[rust_common.crate_info] |
| 175 | |
| 176 | output_dir = ctx.actions.declare_directory("{}.rustdoc".format(ctx.label.name)) |
| 177 | |
| 178 | # Add the current crate as an extern for the compile action |
| 179 | rustdoc_flags = [ |
| 180 | "--extern", |
| 181 | "{}={}".format(crate_info.name, crate_info.output.path), |
| 182 | ] |
| 183 | |
| 184 | action = rustdoc_compile_action( |
| 185 | ctx = ctx, |
| 186 | toolchain = find_toolchain(ctx), |
| 187 | crate_info = crate_info, |
| 188 | output = output_dir, |
| 189 | rustdoc_flags = rustdoc_flags, |
| 190 | ) |
| 191 | |
| 192 | ctx.actions.run( |
| 193 | mnemonic = "Rustdoc", |
| 194 | progress_message = "Generating Rustdoc for {}".format(crate.label), |
| 195 | outputs = [output_dir], |
| 196 | executable = action.executable, |
| 197 | inputs = action.inputs, |
| 198 | env = action.env, |
| 199 | arguments = action.arguments, |
| 200 | tools = action.tools, |
| 201 | ) |
| 202 | |
| 203 | # This rule does nothing without a single-file output, though the directory should've sufficed. |
| 204 | _zip_action(ctx, output_dir, ctx.outputs.rust_doc_zip, crate.label) |
| 205 | |
| 206 | return [ |
| 207 | DefaultInfo( |
| 208 | files = depset([ctx.outputs.rust_doc_zip]), |
| 209 | ), |
| 210 | OutputGroupInfo( |
| 211 | rustdoc_dir = depset([output_dir]), |
| 212 | rustdoc_zip = depset([ctx.outputs.rust_doc_zip]), |
| 213 | ), |
| 214 | ] |
| 215 | |
| 216 | rust_doc = rule( |
| 217 | doc = dedent("""\ |
| 218 | Generates code documentation. |
| 219 | |
| 220 | Example: |
| 221 | Suppose you have the following directory structure for a Rust library crate: |
| 222 | |
| 223 | ``` |
| 224 | [workspace]/ |
| 225 | WORKSPACE |
| 226 | hello_lib/ |
| 227 | BUILD |
| 228 | src/ |
| 229 | lib.rs |
| 230 | ``` |
| 231 | |
| 232 | To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define \ |
| 233 | a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target: |
| 234 | |
| 235 | [rustdoc]: https://doc.rust-lang.org/book/documentation.html |
| 236 | |
| 237 | ```python |
| 238 | package(default_visibility = ["//visibility:public"]) |
| 239 | |
| 240 | load("@rules_rust//rust:defs.bzl", "rust_library", "rust_doc") |
| 241 | |
| 242 | rust_library( |
| 243 | name = "hello_lib", |
| 244 | srcs = ["src/lib.rs"], |
| 245 | ) |
| 246 | |
| 247 | rust_doc( |
| 248 | name = "hello_lib_doc", |
| 249 | crate = ":hello_lib", |
| 250 | ) |
| 251 | ``` |
| 252 | |
| 253 | Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing \ |
| 254 | the documentation for the `hello_lib` library crate generated by `rustdoc`. |
| 255 | """), |
| 256 | implementation = _rust_doc_impl, |
| 257 | attrs = { |
| 258 | "crate": attr.label( |
| 259 | doc = ( |
| 260 | "The label of the target to generate code documentation for.\n" + |
| 261 | "\n" + |
| 262 | "`rust_doc` can generate HTML code documentation for the source files of " + |
| 263 | "`rust_library` or `rust_binary` targets." |
| 264 | ), |
| 265 | providers = [rust_common.crate_info], |
| 266 | mandatory = True, |
| 267 | ), |
| 268 | "html_after_content": attr.label( |
| 269 | doc = "File to add in `<body>`, after content.", |
| 270 | allow_single_file = [".html", ".md"], |
| 271 | ), |
| 272 | "html_before_content": attr.label( |
| 273 | doc = "File to add in `<body>`, before content.", |
| 274 | allow_single_file = [".html", ".md"], |
| 275 | ), |
| 276 | "html_in_header": attr.label( |
| 277 | doc = "File to add to `<head>`.", |
| 278 | allow_single_file = [".html", ".md"], |
| 279 | ), |
| 280 | "markdown_css": attr.label_list( |
| 281 | doc = "CSS files to include via `<link>` in a rendered Markdown file.", |
| 282 | allow_files = [".css"], |
| 283 | ), |
| 284 | "_cc_toolchain": attr.label( |
| 285 | doc = "In order to use find_cpp_toolchain, you must define the '_cc_toolchain' attribute on your rule or aspect.", |
| 286 | default = "@bazel_tools//tools/cpp:current_cc_toolchain", |
| 287 | ), |
| 288 | "_dir_zipper": attr.label( |
| 289 | doc = "A tool that orchestrates the creation of zip archives for rustdoc outputs.", |
| 290 | default = Label("//util/dir_zipper"), |
| 291 | cfg = "exec", |
| 292 | executable = True, |
| 293 | ), |
| 294 | "_process_wrapper": attr.label( |
| 295 | doc = "A process wrapper for running rustdoc on all platforms", |
| 296 | default = Label("@rules_rust//util/process_wrapper"), |
| 297 | executable = True, |
| 298 | allow_single_file = True, |
| 299 | cfg = "exec", |
| 300 | ), |
| 301 | "_zipper": attr.label( |
| 302 | doc = "A Bazel provided tool for creating archives", |
| 303 | default = Label("@bazel_tools//tools/zip:zipper"), |
| 304 | cfg = "exec", |
| 305 | executable = True, |
| 306 | ), |
| 307 | }, |
| 308 | fragments = ["cpp"], |
| 309 | host_fragments = ["cpp"], |
| 310 | outputs = { |
| 311 | "rust_doc_zip": "%{name}.zip", |
| 312 | }, |
| 313 | toolchains = [ |
| 314 | str(Label("//rust:toolchain")), |
| 315 | "@bazel_tools//tools/cpp:toolchain_type", |
| 316 | ], |
| 317 | incompatible_use_toolchain_transition = True, |
| 318 | ) |