# 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))