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