aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaolo Bonzini <pbonzini@redhat.com>2024-12-20 18:00:58 +0100
committerJussi Pakkanen <jpakkane@gmail.com>2025-04-02 15:43:15 +0300
commit6cc848dafdf5696430a0fa68b5717fb788e70820 (patch)
tree75edc31bb2c8837275843668facc2bfcd7ac5df9
parent19482e4553775d4612f5fcc1c7c124774e9a0f79 (diff)
downloadmeson-6cc848dafdf5696430a0fa68b5717fb788e70820.zip
meson-6cc848dafdf5696430a0fa68b5717fb788e70820.tar.gz
meson-6cc848dafdf5696430a0fa68b5717fb788e70820.tar.bz2
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>
-rw-r--r--mesonbuild/backend/ninjabackend.py16
-rw-r--r--mesonbuild/scripts/rustdoc.py101
-rw-r--r--unittests/allplatformstests.py18
3 files changed, 135 insertions, 0 deletions
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index f698a2b..9ca092c 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -3716,6 +3716,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
@@ -3789,6 +3804,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')
diff --git a/mesonbuild/scripts/rustdoc.py b/mesonbuild/scripts/rustdoc.py
new file mode 100644
index 0000000..f5f74c4
--- /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))
diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py
index fbb1c91..a189065 100644
--- a/unittests/allplatformstests.py
+++ b/unittests/allplatformstests.py
@@ -4917,6 +4917,24 @@ class AllPlatformTests(BasePlatformTests):
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:
if self.backend is not Backend.ninja: