blob: 487c4d9591dbd06b76e9c998426184358f82e2c0 [file] [log] [blame]
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# buildifier: disable=module-docstring
load("//rust/private:common.bzl", "rust_common")
load("//rust/private:rustc.bzl", "add_crate_link_flags", "add_edition_flags")
load("//rust/private:utils.bzl", "find_toolchain")
_rust_doc_doc = """Generates code documentation.
Example:
Suppose you have the following directory structure for a Rust library crate:
```
[workspace]/
WORKSPACE
hello_lib/
BUILD
src/
lib.rs
```
To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define \
a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target:
[rustdoc]: https://doc.rust-lang.org/book/documentation.html
```python
package(default_visibility = ["//visibility:public"])
load("@rules_rust//rust:rust.bzl", "rust_library", "rust_doc")
rust_library(
name = "hello_lib",
srcs = ["src/lib.rs"],
)
rust_doc(
name = "hello_lib_doc",
crate = ":hello_lib",
)
```
Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing \
the documentation for the `hello_lib` library crate generated by `rustdoc`.
"""
def _rust_doc_impl(ctx):
"""The implementation of the `rust_doc` rule
Args:
ctx (ctx): The rule's context object
"""
if ctx.attr.crate and ctx.attr.dep:
fail("{} should only use the `crate` attribute. `dep` is deprecated".format(
ctx.label,
))
crate = ctx.attr.crate or ctx.attr.dep
if not crate:
fail("{} is missing the `crate` attribute".format(ctx.label))
crate_info = crate[rust_common.crate_info]
dep_info = crate[rust_common.dep_info]
toolchain = find_toolchain(ctx)
rustdoc_inputs = depset(
[c.output for c in dep_info.transitive_crates.to_list()] +
[toolchain.rust_doc],
transitive = [
crate_info.srcs,
toolchain.rustc_lib.files,
toolchain.rust_lib.files,
],
)
output_dir = ctx.actions.declare_directory(ctx.label.name)
args = ctx.actions.args()
args.add(crate_info.root.path)
args.add("--crate-name", crate_info.name)
args.add("--crate-type", crate_info.type)
if crate_info.type == "proc-macro":
args.add("--extern")
args.add("proc_macro")
args.add("--output", output_dir.path)
add_edition_flags(args, crate_info)
# nb. rustdoc can't do anything with native link flags; we must omit them.
add_crate_link_flags(args, dep_info)
args.add_all(ctx.files.markdown_css, before_each = "--markdown-css")
if ctx.file.html_in_header:
args.add("--html-in-header", ctx.file.html_in_header)
if ctx.file.html_before_content:
args.add("--html-before-content", ctx.file.html_before_content)
if ctx.file.html_after_content:
args.add("--html-after-content", ctx.file.html_after_content)
ctx.actions.run(
executable = toolchain.rust_doc,
inputs = rustdoc_inputs,
outputs = [output_dir],
arguments = [args],
mnemonic = "Rustdoc",
progress_message = "Generating rustdoc for {} ({} files)".format(
crate_info.name,
len(crate_info.srcs.to_list()),
),
)
# This rule does nothing without a single-file output, though the directory should've sufficed.
_zip_action(ctx, output_dir, ctx.outputs.rust_doc_zip)
def _zip_action(ctx, input_dir, output_zip):
"""Creates an archive of the generated documentation from `rustdoc`
Args:
ctx (ctx): The `rust_doc` rule's context object
input_dir (File): A directory containing the outputs from rustdoc
output_zip (File): The location of the output archive containing generated documentation
"""
args = ctx.actions.args()
args.add(ctx.executable._zipper)
args.add(output_zip)
args.add(ctx.bin_dir.path)
args.add_all([input_dir], expand_directories = True)
ctx.actions.run(
executable = ctx.executable._dir_zipper,
inputs = [input_dir],
outputs = [output_zip],
arguments = [args],
tools = [ctx.executable._zipper],
)
rust_doc = rule(
doc = _rust_doc_doc,
implementation = _rust_doc_impl,
attrs = {
"crate": attr.label(
doc = (
"The label of the target to generate code documentation for.\n" +
"\n" +
"`rust_doc` can generate HTML code documentation for the source files of " +
"`rust_library` or `rust_binary` targets."
),
providers = [rust_common.crate_info],
# TODO: Make this attribute mandatory once `dep` is removed
),
"dep": attr.label(
doc = "__deprecated__: use `crate`",
providers = [rust_common.crate_info],
),
"html_after_content": attr.label(
doc = "File to add in `<body>`, after content.",
allow_single_file = [".html", ".md"],
),
"html_before_content": attr.label(
doc = "File to add in `<body>`, before content.",
allow_single_file = [".html", ".md"],
),
"html_in_header": attr.label(
doc = "File to add to `<head>`.",
allow_single_file = [".html", ".md"],
),
"markdown_css": attr.label_list(
doc = "CSS files to include via `<link>` in a rendered Markdown file.",
allow_files = [".css"],
),
"_dir_zipper": attr.label(
default = Label("//util/dir_zipper"),
cfg = "exec",
executable = True,
),
"_zipper": attr.label(
default = Label("@bazel_tools//tools/zip:zipper"),
cfg = "exec",
executable = True,
),
},
outputs = {
"rust_doc_zip": "%{name}.zip",
},
toolchains = [str(Label("//rust:toolchain"))],
incompatible_use_toolchain_transition = True,
)