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