blob: ba3a12cec729ee9de850cc49f1c9cc1098d3bab3 [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# 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,
Brian Silverman5f6f2762022-08-13 19:30:05 -070040 metadata = None,
Brian Silvermancc09f182022-03-09 15:40:20 -080041 edition = crate_info.edition,
42 rustc_env = crate_info.rustc_env,
Brian Silverman5f6f2762022-08-13 19:30:05 -070043 rustc_env_files = crate_info.rustc_env_files,
Brian Silvermancc09f182022-03-09 15:40:20 -080044 is_test = crate_info.is_test,
45 compile_data = crate_info.compile_data,
46 )
47
48def 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 Silverman5f6f2762022-08-13 19:30:05 -070095 # If this is a rustdoc test, we need to depend on rlibs rather than .rmeta.
96 force_depend_on_objects = is_test,
Brian Silvermancc09f182022-03-09 15:40:20 -080097 )
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 Silverman5f6f2762022-08-13 19:30:05 -0700125 force_depend_on_objects = is_test,
Brian Silvermancc09f182022-03-09 15:40:20 -0800126 )
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
147def _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
171def _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
221rust_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 Silverman5f6f2762022-08-13 19:30:05 -0700289 "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 Silvermancc09f182022-03-09 15:40:20 -0800299 "_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 Silverman5f6f2762022-08-13 19:30:05 -0700301 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800302 ),
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 Silverman5f6f2762022-08-13 19:30:05 -0700329 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800330 "@bazel_tools//tools/cpp:toolchain_type",
331 ],
332 incompatible_use_toolchain_transition = True,
333)