blob: f06d18e49a1bff67569af76341a6573e75ef1683 [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,
Adam Snaider1c095c92023-07-08 02:09:58 -040046 compile_data_targets = crate_info.compile_data_targets,
Brian Silvermancc09f182022-03-09 15:40:20 -080047 )
48
49def rustdoc_compile_action(
50 ctx,
51 toolchain,
52 crate_info,
53 output = None,
54 rustdoc_flags = [],
55 is_test = False):
56 """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule.
57
58 Args:
59 ctx (ctx): The rule's context object.
60 toolchain (rust_toolchain): The currently configured `rust_toolchain`.
61 crate_info (CrateInfo): The provider of the crate passed to a rustdoc rule.
62 output (File, optional): An optional output a `rustdoc` action is intended to produce.
63 rustdoc_flags (list, optional): A list of `rustdoc` specific flags.
64 is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets
65
66 Returns:
67 struct: A struct of some `ctx.actions.run` arguments.
68 """
69
70 # If an output was provided, ensure it's used in rustdoc arguments
71 if output:
72 rustdoc_flags = [
73 "--output",
74 output.path,
75 ] + rustdoc_flags
76
77 cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
78
79 dep_info, build_info, linkstamps = collect_deps(
80 deps = crate_info.deps,
81 proc_macro_deps = crate_info.proc_macro_deps,
82 aliases = crate_info.aliases,
83 )
84
85 compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
86 ctx = ctx,
87 file = ctx.file,
88 files = ctx.files,
89 linkstamps = linkstamps,
90 toolchain = toolchain,
91 cc_toolchain = cc_toolchain,
92 feature_configuration = feature_configuration,
93 crate_info = crate_info,
94 dep_info = dep_info,
95 build_info = build_info,
Brian Silverman5f6f2762022-08-13 19:30:05 -070096 # If this is a rustdoc test, we need to depend on rlibs rather than .rmeta.
97 force_depend_on_objects = is_test,
Brian Silvermancc09f182022-03-09 15:40:20 -080098 )
99
100 # Since this crate is not actually producing the output described by the
101 # given CrateInfo, this attribute needs to be stripped to allow the rest
102 # of the rustc functionality in `construct_arguments` to avoid generating
103 # arguments expecting to do so.
104 rustdoc_crate_info = _strip_crate_info_output(crate_info)
105
106 args, env = construct_arguments(
107 ctx = ctx,
108 attr = ctx.attr,
109 file = ctx.file,
110 toolchain = toolchain,
111 tool_path = toolchain.rust_doc.short_path if is_test else toolchain.rust_doc.path,
112 cc_toolchain = cc_toolchain,
113 feature_configuration = feature_configuration,
114 crate_info = rustdoc_crate_info,
115 dep_info = dep_info,
116 linkstamp_outs = linkstamp_outs,
117 ambiguous_libs = ambiguous_libs,
118 output_hash = None,
119 rust_flags = rustdoc_flags,
120 out_dir = out_dir,
121 build_env_files = build_env_files,
122 build_flags_files = build_flags_files,
123 emit = [],
124 remap_path_prefix = None,
Adam Snaider1c095c92023-07-08 02:09:58 -0400125 rustdoc = True,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700126 force_depend_on_objects = is_test,
Brian Silvermancc09f182022-03-09 15:40:20 -0800127 )
128
129 # Because rustdoc tests compile tests outside of the sandbox, the sysroot
130 # must be updated to the `short_path` equivilant as it will now be
131 # a part of runfiles.
132 if is_test:
133 if "SYSROOT" in env:
134 env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)})
Adam Snaider1c095c92023-07-08 02:09:58 -0400135 if "OUT_DIR" in env:
136 env.update({"OUT_DIR": "${{pwd}}/{}".format(build_info.out_dir.short_path)})
Brian Silvermancc09f182022-03-09 15:40:20 -0800137
138 # `rustdoc` does not support the SYSROOT environment variable. To account
139 # for this, the flag must be explicitly passed to the `rustdoc` binary.
140 args.rustc_flags.add("--sysroot=${{pwd}}/{}".format(toolchain.sysroot_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
Adam Snaider1c095c92023-07-08 02:09:58 -0400181 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
Brian Silvermancc09f182022-03-09 15:40:20 -0800187 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
Adam Snaider1c095c92023-07-08 02:09:58 -0400198 rustdoc_flags.extend(ctx.attr.rustdoc_flags)
199
Brian Silvermancc09f182022-03-09 15:40:20 -0800200 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(
Adam Snaider1c095c92023-07-08 02:09:58 -0400224 files = depset([output_dir]),
Brian Silvermancc09f182022-03-09 15:40:20 -0800225 ),
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 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700300 "rustc_flags": attr.string_list(
Adam Snaider1c095c92023-07-08 02:09:58 -0400301 doc = "**Deprecated**: use `rustdoc_flags` instead",
302 ),
303 "rustdoc_flags": attr.string_list(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700304 doc = dedent("""\
Adam Snaider1c095c92023-07-08 02:09:58 -0400305 List of flags passed to `rustdoc`.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700306
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 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800313 "_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.",
Brian Silverman5f6f2762022-08-13 19:30:05 -0700315 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800316 ),
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 "_process_wrapper": attr.label(
324 doc = "A process wrapper for running rustdoc on all platforms",
325 default = Label("@rules_rust//util/process_wrapper"),
326 executable = True,
327 allow_single_file = True,
328 cfg = "exec",
329 ),
330 "_zipper": attr.label(
331 doc = "A Bazel provided tool for creating archives",
332 default = Label("@bazel_tools//tools/zip:zipper"),
333 cfg = "exec",
334 executable = True,
335 ),
336 },
337 fragments = ["cpp"],
338 host_fragments = ["cpp"],
339 outputs = {
340 "rust_doc_zip": "%{name}.zip",
341 },
342 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700343 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800344 "@bazel_tools//tools/cpp:toolchain_type",
345 ],
346 incompatible_use_toolchain_transition = True,
347)