Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust: new target "ninja rustdoc" #14213

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions mesonbuild/backend/ninjabackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -3711,6 +3711,21 @@ def generate_clippy(self) -> None:
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
Expand Down Expand Up @@ -3779,6 +3794,7 @@ def generate_utils(self) -> None:
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')
Expand Down
101 changes: 101 additions & 0 deletions mesonbuild/scripts/rustdoc.py
Original file line number Diff line number Diff line change
@@ -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))
18 changes: 18 additions & 0 deletions unittests/allplatformstests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4884,6 +4884,24 @@ def output_name(name, type_):
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:
Expand Down
Loading