mirror of
https://github.com/mesonbuild/meson.git
synced 2026-11-06 05:16:42 +00:00
cargo: parse [patch] table
While at it, run patch validation after building the set of dependencies. Otherwise, it is not able to match patches against dependencies and will incorrectly warn. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
1 parent
521502c336
commit
de8aa318ec
3 files changed
+90
-55
No files matched your search
@@ -25,7 +25,6 @@ from .cfg import eval_cfg
|
||||
from .toml import load_toml
|
||||
from .manifest import (
|
||||
Manifest, CargoLock, CargoLockPackage, Workspace, fixup_meson_varname,
|
||||
validate_patch,
|
||||
)
|
||||
from ..mesonlib import (
|
||||
as_posix, is_parent_path, late_property, lazy_property, MesonException,
|
||||
@@ -324,12 +323,9 @@ class Interpreter:
|
||||
ws = self._get_workspace(manifest, subdir, extra_members, False)
|
||||
if is_root:
|
||||
self.root_workspace = ws
|
||||
# [patch] only takes effect in the top-level Cargo.toml
|
||||
for warning in validate_patch(ws.workspace.patch, ws.packages_to_member):
|
||||
mlog.warning(warning)
|
||||
|
||||
self.profiles = ws.workspace.profile
|
||||
self._prepare_entry_point(ws)
|
||||
ws.workspace.validate_patches(ws.packages_to_member)
|
||||
return ws
|
||||
|
||||
def _prepare_entry_point(self, ws: WorkspaceState) -> None:
|
||||
@@ -522,7 +518,9 @@ class Interpreter:
|
||||
if ws:
|
||||
return ws
|
||||
workspace = manifest if isinstance(manifest, Workspace) else \
|
||||
Workspace(root_package=manifest, members=['.'], default_members=['.'])
|
||||
Workspace(root_package=manifest, members=['.'], default_members=['.'],
|
||||
patches=manifest.patches,
|
||||
manifest_path=os.path.join(self.environment.source_dir, subdir))
|
||||
ws = WorkspaceState(workspace, self.environment.source_dir, subdir,
|
||||
downloaded=downloaded)
|
||||
if workspace.root_package:
|
||||
@@ -745,10 +743,14 @@ class Interpreter:
|
||||
raise MesonException(f'could not load {subdir}/Cargo.toml: {e}')
|
||||
|
||||
self.build_def_files.append(filename)
|
||||
# [patch] always comes from the top-level Cargo.toml
|
||||
patches = self.root_workspace.workspace.patches if self.workspaces else None
|
||||
if 'workspace' in raw_manifest:
|
||||
manifest_ = Workspace.from_raw(raw_manifest, path)
|
||||
manifest_ = Workspace.from_raw(raw_manifest, path, patches)
|
||||
elif 'package' in raw_manifest:
|
||||
manifest_ = Manifest.from_raw(raw_manifest, path, workspace, member_path)
|
||||
if workspace is not None:
|
||||
patches = workspace.patches
|
||||
manifest_ = Manifest.from_raw(raw_manifest, path, workspace, member_path, patches)
|
||||
else:
|
||||
raise MesonException(f'{subdir}/Cargo.toml does not have [package] or [workspace] section')
|
||||
self.manifests[subdir] = manifest_
|
||||
|
||||
@@ -615,9 +615,8 @@ class Manifest:
|
||||
lints: T.List[Lint] = dataclasses.field(default_factory=list)
|
||||
profile: T.Dict[str, Profile] = dataclasses.field(default_factory=dict)
|
||||
|
||||
# Kept in raw form: Meson does not implement [patch], it only validates it
|
||||
# for the entry-point crate (see validate_patch).
|
||||
patch: object = None
|
||||
# Only [patch.crates-io] is supported.
|
||||
patches: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.features.setdefault('default', [])
|
||||
@@ -663,7 +662,10 @@ class Manifest:
|
||||
return {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()}
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw: raw.Manifest, path: str, workspace: T.Optional[Workspace] = None, member_path: str = '') -> Self:
|
||||
def from_raw(cls, raw: raw.Manifest, path: str, workspace: T.Optional[Workspace] = None,
|
||||
member_path: str = '', patches: T.Optional[T.Dict[str, Dependency]] = None) -> Self:
|
||||
if patches is None:
|
||||
patches = _parse_patches(raw.get('patch'), path)
|
||||
pkg = Package.from_raw(raw['package'], workspace)
|
||||
|
||||
autolib = None
|
||||
@@ -696,7 +698,7 @@ class Manifest:
|
||||
|
||||
manifest = _raw_to_dataclass(raw, cls, f'Cargo.toml package {pkg.name}',
|
||||
raw_from_workspace=workspace.inheritable if workspace else None,
|
||||
ignored_fields=['badges', 'workspace', 'target'],
|
||||
ignored_fields=['badges', 'patch', 'workspace', 'target'],
|
||||
package=ConvertValue(lambda _: pkg),
|
||||
dependencies=ConvertValue(dependencies_from_raw),
|
||||
dev_dependencies=ConvertValue(dependencies_from_raw),
|
||||
@@ -723,6 +725,7 @@ class Manifest:
|
||||
dep = Dependency.from_raw(name, v, member_path, workspace, target=condition)
|
||||
deps.setdefault(name, []).append(dep)
|
||||
|
||||
manifest.patches = patches
|
||||
return manifest
|
||||
|
||||
|
||||
@@ -747,8 +750,21 @@ class Workspace:
|
||||
# A workspace can also have a root package.
|
||||
root_package: T.Optional[Manifest] = None
|
||||
|
||||
# Top-level [patch] table, kept in raw form (see Manifest.validate_patch).
|
||||
patch: object = None
|
||||
# Only [patch.crates-io] is supported.
|
||||
patches: T.Dict[str, Dependency] = dataclasses.field(default_factory=dict)
|
||||
manifest_path: str = ''
|
||||
|
||||
def iter_patch_paths(self) -> T.Iterator[T.Tuple[str, str]]:
|
||||
for name, dep in self.patches.items():
|
||||
if not dep.path:
|
||||
mlog.warning(f'[patch.crates-io] entry {name!r} is not a path dependency')
|
||||
continue
|
||||
yield name, as_posix(dep.path, relative_to=self.manifest_path)
|
||||
|
||||
def validate_patches(self, resolved: T.Mapping[str, str]) -> None:
|
||||
for name, path in self.iter_patch_paths():
|
||||
if resolved.get(name) != path:
|
||||
mlog.warning(f'[patch.crates-io] entry {name!r} is not for a dependency')
|
||||
|
||||
@lazy_property
|
||||
def inheritable(self) -> T.Dict[str, object]:
|
||||
@@ -771,14 +787,18 @@ class Workspace:
|
||||
any(is_parent_path(ex, path) for ex in self.exclude)
|
||||
|
||||
@classmethod
|
||||
def from_raw(cls, raw: raw.Manifest, path: str) -> Self:
|
||||
def from_raw(cls, raw: raw.Manifest, path: str,
|
||||
patches: T.Optional[T.Dict[str, Dependency]] = None) -> Self:
|
||||
if patches is None:
|
||||
patches = _parse_patches(raw.get('patch'), path)
|
||||
|
||||
ws = _raw_to_dataclass(raw['workspace'], cls, 'Workspace')
|
||||
ws.manifest_path = path
|
||||
ws.patches = patches
|
||||
if 'package' in raw:
|
||||
ws.root_package = Manifest.from_raw(raw, path, ws, '.')
|
||||
ws.patch = ws.root_package.patch
|
||||
ws.root_package = Manifest.from_raw(raw, path, ws, '.', ws.patches)
|
||||
ws.profile = ws.root_package.profile
|
||||
else:
|
||||
ws.patch = raw.get('patch')
|
||||
ws.profile = {k: Profile.from_raw(v) for k, v in raw.get('profile', {}).items()}
|
||||
|
||||
ws.members = list(as_posix(m) for m in ws.members)
|
||||
@@ -868,25 +888,27 @@ class CargoLock:
|
||||
package=ConvertValue(lambda x: [CargoLockPackage.from_raw(p) for p in x]))
|
||||
|
||||
|
||||
def validate_patch(patch: object, resolved: T.Mapping[str, str]) -> T.Iterator[str]:
|
||||
# Meson does not implement [patch]; recognize entries that simply point
|
||||
# at a package Meson already builds from the workspace ('resolved' maps
|
||||
# each such package name to its path), and warn for everything else.
|
||||
# This is only done for the top-level Cargo.toml, since Cargo ignores
|
||||
# [patch] in a dependency's manifest too.
|
||||
def _parse_patches(patch: object, path: str) -> T.Dict[str, Dependency]:
|
||||
"""Parse supported top-level [patch] entries and warn about the rest."""
|
||||
if not patch:
|
||||
return
|
||||
return {}
|
||||
if not isinstance(patch, dict):
|
||||
yield '[patch] format not recognized'
|
||||
elif list(patch.keys()) != ['crates-io']:
|
||||
yield 'Found [patch] for a registry other than crates.io'
|
||||
elif not isinstance(patch['crates-io'], dict):
|
||||
yield '[patch.crates-io] format not recognized'
|
||||
else:
|
||||
for name, value in patch['crates-io'].items():
|
||||
if not isinstance(value, dict) or not isinstance(value.get('path'), str):
|
||||
yield f'Unrecognized [patch.crates-io] entry {name!r}'
|
||||
elif resolved.get(name) == as_posix(value['path']):
|
||||
continue
|
||||
else:
|
||||
yield f'[patch.crates-io] entry {name!r} is not for a dependency'
|
||||
mlog.warning('[patch] format not recognized')
|
||||
return {}
|
||||
if any(registry != 'crates-io' for registry in patch):
|
||||
mlog.warning('Found [patch] for a registry other than crates.io')
|
||||
crates_io = patch.get('crates-io')
|
||||
if crates_io is None:
|
||||
return {}
|
||||
if not isinstance(crates_io, dict):
|
||||
mlog.warning('[patch.crates-io] format not recognized')
|
||||
return {}
|
||||
|
||||
result: T.Dict[str, Dependency] = {}
|
||||
for name, value in crates_io.items():
|
||||
dep = Dependency.from_raw(name, value)
|
||||
# Anchor to the directory of the Cargo.toml that has the [patch] table.
|
||||
if dep.path is not None:
|
||||
dep.path = as_posix(path, dep.path)
|
||||
result[name] = dep
|
||||
return result
|
||||
+27
-16
@@ -3,6 +3,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import unittest
|
||||
from unittest import mock
|
||||
import os
|
||||
import tempfile
|
||||
import textwrap
|
||||
@@ -11,7 +12,7 @@ import typing as T
|
||||
from mesonbuild.cargo import cfg
|
||||
from mesonbuild.cargo.cfg import TokenType
|
||||
from mesonbuild.cargo.interpreter import load_cargo_lock
|
||||
from mesonbuild.cargo.manifest import Dependency, Lint, Manifest, Package, Workspace, validate_patch
|
||||
from mesonbuild.cargo.manifest import Dependency, Lint, Manifest, Package, Workspace
|
||||
from mesonbuild.cargo.toml import load_toml
|
||||
from mesonbuild.cargo.version import api, cargo_parse, SemVer
|
||||
from mesonbuild.mesonlib import MachineChoice, MesonException
|
||||
@@ -830,22 +831,32 @@ class CargoTomlTest(unittest.TestCase):
|
||||
'member': {'version': '1', 'path': 'vendor/member'},
|
||||
}}
|
||||
|
||||
# Empty or absent [patch]
|
||||
self.assertFalse(list(validate_patch(None, resolved)))
|
||||
self.assertFalse(list(validate_patch({}, resolved)))
|
||||
with mock.patch('mesonbuild.cargo.manifest.mlog.warning') as warning:
|
||||
def validate_patch(raw_patch: object, packages: T.Dict[str, str]) -> T.List[object]:
|
||||
warning.reset_mock()
|
||||
workspace = Workspace.from_raw({'workspace': {}, 'patch': raw_patch}, '')
|
||||
workspace.validate_patches(packages)
|
||||
return warning.call_args_list
|
||||
|
||||
# Invalid [patch] tables
|
||||
self.assertTrue(list(validate_patch({'crates-io': {}, 'https://example.com': {}}, resolved)))
|
||||
self.assertTrue(list(validate_patch({'crates-io': 'nonsense'}, resolved)))
|
||||
self.assertTrue(list(validate_patch('nonsense', resolved)))
|
||||
# Empty or absent [patch]
|
||||
self.assertFalse(validate_patch(None, resolved))
|
||||
self.assertFalse(validate_patch({}, resolved))
|
||||
|
||||
# Invalid entries
|
||||
self.assertTrue(list(validate_patch({'crates-io': {'cxx': {'git': 'https://example.com'}}}, resolved)))
|
||||
self.assertTrue(list(validate_patch({'crates-io': {'cxx': '1.0'}}, resolved)))
|
||||
# Invalid [patch] tables
|
||||
self.assertTrue(validate_patch({'crates-io': {}, 'https://example.com': {}}, resolved))
|
||||
self.assertTrue(validate_patch({'crates-io': 'nonsense'}, resolved))
|
||||
self.assertTrue(validate_patch('nonsense', resolved))
|
||||
|
||||
# Unknown crate
|
||||
self.assertTrue(list(validate_patch({'crates-io': {'unknown': {'path': 'foo'}}}, resolved)))
|
||||
# Invalid entries
|
||||
self.assertTrue(validate_patch(
|
||||
{'crates-io': {'cxx': {'git': 'https://example.com'}}}, resolved))
|
||||
self.assertTrue(validate_patch({'crates-io': {'cxx': '1.0'}}, resolved))
|
||||
|
||||
# The only entries that don't warn point at a package that Meson already builds
|
||||
self.assertFalse(list(validate_patch(patch, resolved)))
|
||||
self.assertTrue(list(validate_patch({'crates-io': {'cxx-build': {'path': 'elsewhere'}}}, resolved)))
|
||||
# Unknown crate
|
||||
self.assertTrue(validate_patch(
|
||||
{'crates-io': {'unknown': {'path': 'foo'}}}, resolved))
|
||||
|
||||
# The only entries that don't warn point at a package that Meson already builds
|
||||
self.assertFalse(validate_patch(patch, resolved))
|
||||
self.assertTrue(validate_patch(
|
||||
{'crates-io': {'cxx-build': {'path': 'elsewhere'}}}, resolved))
|
||||
Reference in new issue
Block a user