diff --git a/mesonbuild/cargo/interpreter.py b/mesonbuild/cargo/interpreter.py index e26298641c75..db7c0a1edaed 100644 --- a/mesonbuild/cargo/interpreter.py +++ b/mesonbuild/cargo/interpreter.py @@ -12,6 +12,7 @@ from __future__ import annotations import dataclasses import functools +import itertools import os import pathlib import collections @@ -144,6 +145,8 @@ def get_lint_args(self, rustc: RustCompiler) -> T.List[str]: args.extend(lint.to_arguments(has_check_cfg)) if has_check_cfg: + args.append('--check-cfg') + args.append('cfg(test)') for feature in self.manifest.features: if feature != 'default': args.append('--check-cfg') @@ -426,6 +429,20 @@ def _prepare_package(self, pkg: PackageState) -> None: for condition, dependencies in pkg.manifest.target.items(): if eval_cfg(condition, cfgs): pkg.manifest.dependencies.update(dependencies) + + # If you specify the optional dependency with the dep: prefix anywhere in the [features] + # table, that disables the implicit feature. + deps = set(feature[4:] + for feature in itertools.chain.from_iterable(pkg.manifest.features.values()) + if feature.startswith('dep:')) + for name, dep in itertools.chain(pkg.manifest.dependencies.items(), + pkg.manifest.dev_dependencies.items(), + pkg.manifest.build_dependencies.items()): + if dep.optional and name not in deps: + pkg.manifest.features.setdefault(name, []) + pkg.manifest.features[name].append(f'dep:{name}') + deps.add(name) + # Fetch required dependencies recursively. for depname, dep in pkg.manifest.dependencies.items(): if not dep.optional: @@ -497,9 +514,6 @@ def _enable_feature(self, pkg: PackageState, feature: str) -> None: if feature in cfg.features: return cfg.features.add(feature) - # A feature can also be a dependency. - if feature in pkg.manifest.dependencies: - self._add_dependency(pkg, feature) # Recurse on extra features and dependencies this feature pulls. # https://doc.rust-lang.org/cargo/reference/features.html#the-features-section for f in pkg.manifest.features.get(feature, []): @@ -825,7 +839,7 @@ def load_cargo_lock(filename: str, subproject_dir: str) -> T.Optional[CargoLock] meson_depname = _dependency_name(package.name, version.api(package.version)) if package.source is None: # This is project's package, or one of its workspace members. - pass + continue elif package.source == 'registry+https://github.com/rust-lang/crates.io-index': checksum = package.checksum if checksum is None: diff --git a/mesonbuild/cargo/manifest.py b/mesonbuild/cargo/manifest.py index ec84e4b16cf2..2c3eeb3e0cf4 100644 --- a/mesonbuild/cargo/manifest.py +++ b/mesonbuild/cargo/manifest.py @@ -442,8 +442,7 @@ def from_raw(cls, r: T.Union[raw.FromWorkspace, T.Dict[str, T.Dict[str, raw.Lint settings = T.cast('raw.Lint', {'level': settings}) check_cfg = None if name == 'unexpected_cfgs': - # 'cfg(test)' is added automatically by cargo - check_cfg = ['cfg(test)'] + settings.get('check-cfg', []) + check_cfg = settings.get('check-cfg', []) lints[name] = Lint(name=name, level=settings['level'], priority=settings.get('priority', 0), diff --git a/mesonbuild/cargo/toml.py b/mesonbuild/cargo/toml.py index 601510e4ed8b..dda7cfda8f55 100644 --- a/mesonbuild/cargo/toml.py +++ b/mesonbuild/cargo/toml.py @@ -31,17 +31,28 @@ class TomlImplementationMissing(MesonException): pass +class CargoTomlError(MesonException): + """Exception for TOML parsing errors, keeping proper location info.""" + + def load_toml(filename: str) -> T.Dict[str, object]: if tomllib: - with open(filename, 'rb') as f: - raw = tomllib.load(f) + try: + with open(filename, 'rb') as f: + raw = tomllib.load(f) + except tomllib.TOMLDecodeError as e: + if hasattr(e, 'msg'): + raise CargoTomlError(e.msg, file=filename, lineno=e.lineno, colno=e.colno) from e + else: + raise CargoTomlError(str(e), file=filename) from e else: if toml2json is None: raise TomlImplementationMissing('Could not find an implementation of tomllib, nor toml2json') p, out, err = Popen_safe([toml2json, filename]) if p.returncode != 0: - raise MesonException('toml2json failed to decode output\n', err) + error_msg = err.strip() or 'toml2json failed to decode TOML' + raise CargoTomlError(error_msg, file=filename) raw = json.loads(out) diff --git a/test cases/failing/136 cargo toml error/meson.build b/test cases/failing/136 cargo toml error/meson.build new file mode 100644 index 000000000000..6efaab2c5525 --- /dev/null +++ b/test cases/failing/136 cargo toml error/meson.build @@ -0,0 +1,19 @@ +project('cargo-toml-error', 'c') + +if not add_languages('rust', required: false) + error('MESON_SKIP_TEST Rust not present, required for Cargo subprojets') +endif + +# Check if we have tomllib/tomli (not toml2json) +python = find_program('python3', 'python') +result = run_command(python, '-c', 'import tomllib', check: false) +if result.returncode() != 0 + result = run_command(python, '-c', 'import tomli', check: false) + if result.returncode() != 0 + # Skip test if using toml2json - error format will be different + error('MESON_SKIP_TEST toml2json in use, skipping test') + endif +endif + +# This should trigger a CargoTomlError with proper location info +foo_dep = dependency('foo-0') diff --git a/test cases/failing/136 cargo toml error/subprojects/foo-0-rs.wrap b/test cases/failing/136 cargo toml error/subprojects/foo-0-rs.wrap new file mode 100644 index 000000000000..c399701880c8 --- /dev/null +++ b/test cases/failing/136 cargo toml error/subprojects/foo-0-rs.wrap @@ -0,0 +1,5 @@ +[wrap-file] +method = cargo + +[provide] +dependency_names = foo-0 diff --git a/test cases/failing/136 cargo toml error/subprojects/foo-0-rs/Cargo.toml b/test cases/failing/136 cargo toml error/subprojects/foo-0-rs/Cargo.toml new file mode 100644 index 000000000000..2f2d7a6811cd --- /dev/null +++ b/test cases/failing/136 cargo toml error/subprojects/foo-0-rs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "foo" +version = "0.0.1" +edition = "2021" +# This creates a TOML decode error: duplicate key +name = "bar" \ No newline at end of file diff --git a/test cases/failing/136 cargo toml error/test.json b/test cases/failing/136 cargo toml error/test.json new file mode 100644 index 000000000000..480cf64d5d27 --- /dev/null +++ b/test cases/failing/136 cargo toml error/test.json @@ -0,0 +1,8 @@ +{ + "stdout": [ + { + "match": "re", + "line": "test cases/failing/136 cargo toml error/(subprojects/foo-0-rs/Cargo\\.toml:6:13|meson\\.build:19:10): ERROR: Cannot overwrite a value( \\(at.*)?" + } + ] +} diff --git a/unittests/cargotests.py b/unittests/cargotests.py index 643ceceb49a6..e8eace74fa2e 100644 --- a/unittests/cargotests.py +++ b/unittests/cargotests.py @@ -428,7 +428,7 @@ def test_cargo_toml_lints(self) -> None: self.assertEqual(manifest.lints[2].name, 'unexpected_cfgs') self.assertEqual(manifest.lints[2].level, 'deny') self.assertEqual(manifest.lints[2].priority, 0) - self.assertEqual(manifest.lints[2].check_cfg, ['cfg(test)', 'cfg(MESON)']) + self.assertEqual(manifest.lints[2].check_cfg, ['cfg(MESON)']) def test_cargo_toml_lints_to_args(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -444,8 +444,7 @@ def test_cargo_toml_lints_to_args(self) -> None: self.assertEqual(manifest.lints[1].to_arguments(True), ['-A', 'unknown_lints']) self.assertEqual(manifest.lints[2].to_arguments(False), ['-D', 'unexpected_cfgs']) self.assertEqual(manifest.lints[2].to_arguments(True), - ['-D', 'unexpected_cfgs', '--check-cfg', 'cfg(test)', - '--check-cfg', 'cfg(MESON)']) + ['-D', 'unexpected_cfgs', '--check-cfg', 'cfg(MESON)']) def test_cargo_toml_dependencies(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: