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