diff options
Diffstat (limited to 'mesonbuild/scripts/rustdoc.py')
| -rw-r--r-- | mesonbuild/scripts/rustdoc.py | 101 |
1 files changed, 101 insertions, 0 deletions
diff --git a/mesonbuild/scripts/rustdoc.py b/mesonbuild/scripts/rustdoc.py new file mode 100644 index 000000000..f5f74c4e3 --- /dev/null +++ b/mesonbuild/scripts/rustdoc.py @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2024 The Meson development team + +from __future__ import annotations +from collections import defaultdict +import os +import tempfile +import typing as T + +from .run_tool import run_tool_on_targets, run_with_buffered_output +from .. import build, mlog +from ..mesonlib import MachineChoice, PerMachine +from ..wrap import WrapMode, wrap + +if T.TYPE_CHECKING: + from ..compilers.rust import RustCompiler + +async def run_and_confirm_success(cmdlist: T.List[str], crate: str) -> int: + returncode = await run_with_buffered_output(cmdlist) + if returncode == 0: + print(mlog.green('Generated'), os.path.join('doc', crate)) + return returncode + +class Rustdoc: + def __init__(self, build: build.Build, tempdir: str, subprojects: T.Set[str]) -> None: + self.tools: PerMachine[T.List[str]] = PerMachine([], []) + self.warned: T.DefaultDict[str, bool] = defaultdict(lambda: False) + self.tempdir = tempdir + self.subprojects = subprojects + for machine in MachineChoice: + compilers = build.environment.coredata.compilers[machine] + if 'rust' in compilers: + compiler = T.cast('RustCompiler', compilers['rust']) + self.tools[machine] = compiler.get_rust_tool('rustdoc', build.environment) + + def warn_missing_rustdoc(self, machine: str) -> None: + if self.warned[machine]: + return + mlog.warning(f'rustdoc not found for {machine} machine') + self.warned[machine] = True + + def __call__(self, target: T.Dict[str, T.Any]) -> T.Iterable[T.Coroutine[None, None, int]]: + if target['subproject'] is not None and target['subproject'] not in self.subprojects: + return + + for src_block in target['target_sources']: + if 'compiler' in src_block and src_block['language'] == 'rust': + rustdoc = getattr(self.tools, src_block['machine']) + if not rustdoc: + self.warn_missing_rustdoc(src_block['machine']) + continue + + cmdlist = list(rustdoc) + prev = None + crate_name = None + is_test = False + for arg in src_block['parameters']: + if prev: + if prev == '--crate-name': + cmdlist.extend((prev, arg)) + crate_name = arg + prev = None + continue + + if arg == '--test': + is_test = True + break + elif arg in {'--crate-name', '--emit', '--out-dir', '-l'}: + prev = arg + elif arg != '-g' and not arg.startswith('-l'): + cmdlist.append(arg) + + if is_test: + # --test has a completely different meaning for rustc and rustdoc; + # when using rust.test(), only the non-test target is documented + continue + if crate_name: + cmdlist.extend(src_block['sources']) + # Assume documentation is generated for the developer's use + cmdlist.append('--document-private-items') + cmdlist.append('-o') + cmdlist.append('doc') + yield run_and_confirm_success(cmdlist, crate_name) + else: + print(mlog.yellow('Skipping'), target['name'], '(no crate name)') + +def get_nonwrap_subprojects(build_data: build.Build) -> T.Set[str]: + wrap_resolver = wrap.Resolver( + build_data.environment.get_source_dir(), + build_data.subproject_dir, + wrap_mode=WrapMode.nodownload) + return set(sp + for sp in build_data.environment.coredata.initialized_subprojects + if sp and (sp not in wrap_resolver.wraps or wrap_resolver.wraps[sp].type is None)) + +def run(args: T.List[str]) -> int: + os.chdir(args[0]) + build_data = build.load(os.getcwd()) + subproject_list = get_nonwrap_subprojects(build_data) + with tempfile.TemporaryDirectory() as d: + return run_tool_on_targets(Rustdoc(build_data, d, subproject_list)) |
