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