blob: b16e7565cfe99d8bcee18d5d8c12b8525d3553b3 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2024 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""rustdoc_wrapper
Runs rustdoc to document a crate; executed as part of the .rustdoc subtarget.
"""
import json
import re
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
from os import walk
from pathlib import Path
from subprocess import run
from sys import argv
from zipfile import ZipFile
def label_to_crate_name(label: str):
"""
This function is not robust, but in the context of
//third_party/rust_crates it is fine (ethanws@)
"""
match = re.match(r"^:(.+?)-v\d+_\d+_\d+\.actual$", label)
assert (
match is not None
), f"rustdoc_wrapper: the crate name could not be extacted from {label}"
return match.group(1).replace("-", "_")
def get_aliases(args: Namespace) -> dict[str, str]:
"""
Returns a map from the original crate name to the new crate name.
"""
if args.aliased_deps_map is None:
return dict()
aliases = json.loads(args.aliased_deps_map.read_text())
return {label_to_crate_name(v): k for k, v in aliases.items()}
def fixup_extern(extern: str, aliases: dict[str, str]) -> tuple[str, str]:
"""
Adjusts the crate name in an --extern or --extern-html-root-url
to respect the alias map.
"""
crate_name, path = extern.split("=")
if crate_name in aliases:
crate_name = aliases[crate_name]
return crate_name, path
def zip_dir(src: Path, dst: Path):
"""
Creates a zip archive called `dst`, containing the recursive contents of the `src` directory.
"""
with ZipFile(dst, mode="w") as dst:
for dirpath, dirnames, filenames in walk(src):
dst.mkdir(str(Path(dirpath).relative_to(src)))
for filename in filenames:
path = Path(dirpath, filename)
dst.write(
filename=str(path),
arcname=str(path.relative_to(src)),
)
def main(args: Namespace, rustdoc_invocation: list[str]) -> int:
"""Runs rustdoc executable."""
assert not (
(args.zip_from is None) ^ (args.zip_to is None)
), "must provide either neither or both of --zip-from and --zip-to"
aliases = get_aliases(args)
extern = (
f'--extern={"=".join(fixup_extern(e, aliases))}' for e in args.extern
)
extern_html_root_urls = (
f"--extern-html-root-url={fixup_extern(e, aliases)[0]}={args.extern_html_root_url}"
for e in args.extern
)
flags = [
*rustdoc_invocation,
*extern,
*extern_html_root_urls,
]
stdout = None if args.stdout_path is None else args.stdout_path.open("w")
stderr = None if args.stderr_path is None else args.stderr_path.open("w")
completed = run(flags, stdout=stdout, stderr=stderr)
if args.fail:
completed.check_returncode()
if args.zip_to is not None:
zip_dir(src=args.zip_from, dst=args.zip_to)
if args.touch is not None:
args.touch.touch()
def _get_ignore_parser() -> ArgumentParser:
parser = ArgumentParser(
description="ignore arguments in rustdoc invocation"
)
# ignored from //build/rbe/remote_action.py
parser.add_argument("--remote-disable", nargs="*", help="ignored")
parser.add_argument("--remote-inputs", nargs="*", help="ignored")
parser.add_argument("--remote-outputs", nargs="*", help="ignored")
parser.add_argument("--remote-output-dirs", nargs="*", help="ignored")
parser.add_argument("--remote-flag", nargs="*", help="ignored")
# ignored from //build/rust/rustc_link_attribute.gni
parser.add_argument("-l", help="ignored")
return parser
def _main_arg_parser() -> ArgumentParser:
parser = ArgumentParser(
description="runs rustdoc", fromfile_prefix_chars="@"
)
parser.add_argument(
"--fail",
default=True,
action=BooleanOptionalAction,
help="fail this wrapper if rustdoc fails",
)
parser.add_argument(
"--stdout-path",
type=Path,
help="path to write rustdoc stdout",
)
parser.add_argument(
"--stderr-path",
type=Path,
help="path to write rustdoc stderr",
)
parser.add_argument(
"--touch",
type=Path,
help="Touch this file upon script completion",
)
parser.add_argument(
"--extern",
action="append",
default=[],
help="extern crates",
)
parser.add_argument(
"--extern-html-root-url",
type=str,
required=True,
help="use this as the --extern-html-root-url",
)
parser.add_argument(
"--aliased-deps-map",
type=Path,
help="rename aliases",
)
parser.add_argument(
"--zip-from", type=Path, help="--out-dir of rustdoc invocation"
)
parser.add_argument(
"--zip-to", type=Path, help="path to .zip file to append to or create"
)
parser.add_argument(
"rustdoc_invocation",
nargs="*",
help="Provide full rustdoc invocation, including the rustdoc executable, and all intended flags. Will be forwarded to subprocess.run, along with generated externs",
)
parser.set_defaults(func=main)
return parser
def _strip_cmd_prefixes(arg: str) -> str:
for prefix in ("--remote-only=", "--local-only="):
arg = arg.removeprefix(prefix)
return arg
if __name__ == "__main__":
parser = _main_arg_parser()
args = parser.parse_args(argv[1:])
ignore_parser = _get_ignore_parser()
(_ignored, rustdoc_invocation) = ignore_parser.parse_known_args(
args.rustdoc_invocation
)
# Strip prefixes to make sure flags from Rust configs are correctly applied
# to rustdoc invocations. See https://fxbug.dev/407713438 for details.
rustdoc_invocation = [
_strip_cmd_prefixes(arg) for arg in rustdoc_invocation
]
args.func(args, rustdoc_invocation)