rust: new target rustdoc

Another rust tool, another rough copy of the code to run clippy.
Apart from the slightly different command lines, the output is in a
directory and test targets are skipped.

Knowing the output directory can be useful, so print that on successful
execution of rustdoc.

Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
pull/14213/head
Paolo Bonzini 3 months ago
parent ea678ed829
commit 41d1e78f15
  1. 16
      mesonbuild/backend/ninjabackend.py
  2. 101
      mesonbuild/scripts/rustdoc.py
  3. 18
      unittests/allplatformstests.py

@ -3711,6 +3711,21 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
elem.add_dep(list(self.all_structured_sources))
self.add_build(elem)
def generate_rustdoc(self) -> None:
if 'rustdoc' in self.all_outputs or not self.have_language('rust'):
return
cmd = self.environment.get_build_command() + \
['--internal', 'rustdoc', self.environment.build_dir]
elem = self.create_phony_target('rustdoc', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', cmd)
elem.add_item('pool', 'console')
for crate in self.rust_crates.values():
if crate.crate_type in {'rlib', 'dylib', 'proc-macro'}:
elem.add_dep(crate.target_name)
elem.add_dep(list(self.all_structured_sources))
self.add_build(elem)
def generate_scanbuild(self) -> None:
if not environment.detect_scanbuild():
return
@ -3779,6 +3794,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.generate_clangformat()
self.generate_clangtidy()
self.generate_clippy()
self.generate_rustdoc()
self.generate_tags('etags', 'TAGS')
self.generate_tags('ctags', 'ctags')
self.generate_tags('cscope', 'cscope')

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

@ -4884,6 +4884,24 @@ class AllPlatformTests(BasePlatformTests):
with self.subTest(key='{}.{}'.format(data_type, file)):
self.assertEqual(res[data_type][file], details)
@skip_if_not_language('rust')
@unittest.skipIf(not shutil.which('rustdoc'), 'Test requires rustdoc')
def test_rustdoc(self) -> None:
if self.backend is not Backend.ninja:
raise unittest.SkipTest('Rust is only supported with ninja currently')
try:
with tempfile.TemporaryDirectory() as tmpdir:
testdir = os.path.join(tmpdir, 'a')
shutil.copytree(os.path.join(self.rust_test_dir, '9 unit tests'),
testdir)
self.init(testdir)
self.build('rustdoc')
except PermissionError:
# When run under Windows CI, something (virus scanner?)
# holds on to the git files so cleaning up the dir
# fails sometimes.
pass
@skip_if_not_language('rust')
@unittest.skipIf(not shutil.which('clippy-driver'), 'Test requires clippy-driver')
def test_rust_clippy(self) -> None:

Loading…
Cancel
Save