Skip to content

Commit fe25a14

Browse files
bonzinidcbaker
authored andcommitted
cargo: move dataclasses out of interpreter module
Extracted from a patch by Xavier Classens. Signed-off-by: Paolo Bonzini <[email protected]>
1 parent 164c128 commit fe25a14

File tree

2 files changed

+277
-332
lines changed

2 files changed

+277
-332
lines changed

mesonbuild/cargo/interpreter.py

Lines changed: 4 additions & 331 deletions
Original file line numberDiff line numberDiff line change
@@ -19,346 +19,19 @@
1919

2020
from . import builder, version, cfg
2121
from .toml import load_toml, TomlImplementationMissing
22-
from .manifest import fixup_meson_varname, CargoLock
22+
from .manifest import Manifest, CargoLock, fixup_meson_varname
2323
from ..mesonlib import MesonException, MachineChoice
2424
from .. import coredata, mlog
2525
from ..wrap.wrap import PackageDefinition
2626

2727
if T.TYPE_CHECKING:
28-
from typing_extensions import Protocol, Self
29-
30-
from . import manifest, raw
28+
from . import raw
3129
from .. import mparser
30+
from .manifest import Dependency, SystemDependency
3231
from ..environment import Environment
3332
from ..interpreterbase import SubProject
3433
from ..compilers.rust import RustCompiler
3534

36-
# Copied from typeshed. Blarg that they don't expose this
37-
class DataclassInstance(Protocol):
38-
__dataclass_fields__: T.ClassVar[dict[str, dataclasses.Field[T.Any]]]
39-
40-
_R = T.TypeVar('_R', bound='raw._BaseBuildTarget')
41-
42-
43-
_EXTRA_KEYS_WARNING = (
44-
"This may (unlikely) be an error in the cargo manifest, or may be a missing "
45-
"implementation in Meson. If this issue can be reproduced with the latest "
46-
"version of Meson, please help us by opening an issue at "
47-
"https://github.com/mesonbuild/meson/issues. Please include the crate and "
48-
"version that is generating this warning if possible."
49-
)
50-
51-
52-
def _fixup_raw_mappings(d: T.Mapping[str, T.Any], convert_version: bool = True) -> T.MutableMapping[str, T.Any]:
53-
"""Fixup raw cargo mappings to ones more suitable for python to consume.
54-
55-
This does the following:
56-
* replaces any `-` with `_`, cargo likes the former, but python dicts make
57-
keys with `-` in them awkward to work with
58-
* Convert Dependency versions from the cargo format to something meson
59-
understands
60-
61-
:param d: The mapping to fix
62-
:return: the fixed string
63-
"""
64-
raw = {fixup_meson_varname(k): v for k, v in d.items()}
65-
if convert_version and 'version' in raw:
66-
assert isinstance(raw['version'], str), 'for mypy'
67-
raw['version'] = version.convert(raw['version'])
68-
return raw
69-
70-
71-
def _handle_unknown_keys(data: T.MutableMapping[str, T.Any], cls: T.Union[DataclassInstance, T.Type[DataclassInstance]],
72-
msg: str) -> T.MutableMapping[str, T.Any]:
73-
"""Remove and warn on keys that are coming from cargo, but are unknown to
74-
our representations.
75-
76-
This is intended to give users the possibility of things proceeding when a
77-
new key is added to Cargo.toml that we don't yet handle, but to still warn
78-
them that things might not work.
79-
80-
:param data: The raw data to look at
81-
:param cls: The Dataclass derived type that will be created
82-
:param msg: the header for the error message. Usually something like "In N structure".
83-
:return: The original data structure, but with all unknown keys removed.
84-
"""
85-
unexpected = set(data) - {x.name for x in dataclasses.fields(cls)}
86-
if unexpected:
87-
mlog.warning(msg, 'has unexpected keys', '"{}".'.format(', '.join(sorted(unexpected))),
88-
_EXTRA_KEYS_WARNING)
89-
for k in unexpected:
90-
del data[k]
91-
return data
92-
93-
94-
@dataclasses.dataclass
95-
class Package:
96-
97-
"""Representation of a Cargo Package entry, with defaults filled in."""
98-
99-
name: str
100-
version: str
101-
description: T.Optional[str] = None
102-
resolver: T.Optional[str] = None
103-
authors: T.List[str] = dataclasses.field(default_factory=list)
104-
edition: manifest.EDITION = '2015'
105-
rust_version: T.Optional[str] = None
106-
documentation: T.Optional[str] = None
107-
readme: T.Optional[str] = None
108-
homepage: T.Optional[str] = None
109-
repository: T.Optional[str] = None
110-
license: T.Optional[str] = None
111-
license_file: T.Optional[str] = None
112-
keywords: T.List[str] = dataclasses.field(default_factory=list)
113-
categories: T.List[str] = dataclasses.field(default_factory=list)
114-
workspace: T.Optional[str] = None
115-
build: T.Optional[str] = None
116-
links: T.Optional[str] = None
117-
exclude: T.List[str] = dataclasses.field(default_factory=list)
118-
include: T.List[str] = dataclasses.field(default_factory=list)
119-
publish: bool = True
120-
metadata: T.Dict[str, T.Any] = dataclasses.field(default_factory=dict)
121-
default_run: T.Optional[str] = None
122-
autolib: bool = True
123-
autobins: bool = True
124-
autoexamples: bool = True
125-
autotests: bool = True
126-
autobenches: bool = True
127-
api: str = dataclasses.field(init=False)
128-
129-
def __post_init__(self) -> None:
130-
self.api = version.api(self.version)
131-
132-
@classmethod
133-
def from_raw(cls, raw: raw.Package) -> Self:
134-
pkg = _fixup_raw_mappings(raw, convert_version=False)
135-
pkg = _handle_unknown_keys(pkg, cls, f'Package entry {pkg["name"]}')
136-
return cls(**pkg)
137-
138-
@dataclasses.dataclass
139-
class SystemDependency:
140-
141-
""" Representation of a Cargo system-deps entry
142-
https://docs.rs/system-deps/latest/system_deps
143-
"""
144-
145-
name: str
146-
version: T.List[str]
147-
optional: bool = False
148-
feature: T.Optional[str] = None
149-
feature_overrides: T.Dict[str, T.Dict[str, str]] = dataclasses.field(default_factory=dict)
150-
151-
@classmethod
152-
def from_raw(cls, name: str, raw: T.Any) -> SystemDependency:
153-
if isinstance(raw, str):
154-
return cls(name, SystemDependency.convert_version(raw))
155-
name = raw.get('name', name)
156-
version = SystemDependency.convert_version(raw.get('version'))
157-
optional = raw.get('optional', False)
158-
feature = raw.get('feature')
159-
# Everything else are overrides when certain features are enabled.
160-
feature_overrides = {k: v for k, v in raw.items() if k not in {'name', 'version', 'optional', 'feature'}}
161-
return cls(name, version, optional, feature, feature_overrides)
162-
163-
@staticmethod
164-
def convert_version(version: T.Optional[str]) -> T.List[str]:
165-
vers = version.split(',') if version is not None else []
166-
result: T.List[str] = []
167-
for v in vers:
168-
v = v.strip()
169-
if v[0] not in '><=':
170-
v = f'>={v}'
171-
result.append(v)
172-
return result
173-
174-
def enabled(self, features: T.Set[str]) -> bool:
175-
return self.feature is None or self.feature in features
176-
177-
@dataclasses.dataclass
178-
class Dependency:
179-
180-
"""Representation of a Cargo Dependency Entry."""
181-
182-
name: dataclasses.InitVar[str]
183-
version: T.List[str]
184-
registry: T.Optional[str] = None
185-
git: T.Optional[str] = None
186-
branch: T.Optional[str] = None
187-
rev: T.Optional[str] = None
188-
path: T.Optional[str] = None
189-
optional: bool = False
190-
package: str = ''
191-
default_features: bool = True
192-
features: T.List[str] = dataclasses.field(default_factory=list)
193-
api: str = dataclasses.field(init=False)
194-
195-
def __post_init__(self, name: str) -> None:
196-
self.package = self.package or name
197-
# Extract wanted API version from version constraints.
198-
api = set()
199-
for v in self.version:
200-
if v.startswith(('>=', '==')):
201-
api.add(version.api(v[2:].strip()))
202-
elif v.startswith('='):
203-
api.add(version.api(v[1:].strip()))
204-
if not api:
205-
self.api = '0'
206-
elif len(api) == 1:
207-
self.api = api.pop()
208-
else:
209-
raise MesonException(f'Cannot determine minimum API version from {self.version}.')
210-
211-
@classmethod
212-
def from_raw(cls, name: str, raw: raw.DependencyV) -> Dependency:
213-
"""Create a dependency from a raw cargo dictionary"""
214-
if isinstance(raw, str):
215-
return cls(name, version.convert(raw))
216-
fixed = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Dependency entry {name}')
217-
return cls(name, **fixed)
218-
219-
220-
@dataclasses.dataclass
221-
class BuildTarget(T.Generic[_R]):
222-
223-
name: str
224-
crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib'])
225-
path: dataclasses.InitVar[T.Optional[str]] = None
226-
227-
# https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-test-field
228-
# True for lib, bin, test
229-
test: bool = True
230-
231-
# https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doctest-field
232-
# True for lib
233-
doctest: bool = False
234-
235-
# https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-bench-field
236-
# True for lib, bin, benchmark
237-
bench: bool = True
238-
239-
# https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-doc-field
240-
# True for libraries and binaries
241-
doc: bool = False
242-
243-
harness: bool = True
244-
edition: manifest.EDITION = '2015'
245-
required_features: T.List[str] = dataclasses.field(default_factory=list)
246-
plugin: bool = False
247-
248-
@classmethod
249-
def from_raw(cls, raw: raw.BuildTarget) -> Self:
250-
name = raw.get('name', '<anonymous>')
251-
build = _handle_unknown_keys(_fixup_raw_mappings(raw), cls, f'Binary entry {name}')
252-
return cls(**build)
253-
254-
@dataclasses.dataclass
255-
class Library(BuildTarget['raw.LibTarget']):
256-
257-
"""Representation of a Cargo Library Entry."""
258-
259-
doctest: bool = True
260-
doc: bool = True
261-
path: str = os.path.join('src', 'lib.rs')
262-
proc_macro: bool = False
263-
crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['lib'])
264-
doc_scrape_examples: bool = True
265-
266-
@classmethod
267-
def from_raw(cls, raw: raw.LibTarget, fallback_name: str) -> Self: # type: ignore[override]
268-
if 'name' not in raw:
269-
raw['name'] = fallback_name
270-
fixed = _fixup_raw_mappings(raw)
271-
272-
# We need to set the name field if it's not set manually, including if
273-
# other fields are set in the lib section
274-
fixed = _handle_unknown_keys(fixed, cls, f'Library entry {fixed["name"]}')
275-
276-
return cls(**fixed)
277-
278-
279-
@dataclasses.dataclass
280-
class Binary(BuildTarget['raw.BuildTarget']):
281-
282-
"""Representation of a Cargo Bin Entry."""
283-
284-
doc: bool = True
285-
286-
287-
@dataclasses.dataclass
288-
class Test(BuildTarget['raw.BuildTarget']):
289-
290-
"""Representation of a Cargo Test Entry."""
291-
292-
bench: bool = True
293-
294-
295-
@dataclasses.dataclass
296-
class Benchmark(BuildTarget['raw.BuildTarget']):
297-
298-
"""Representation of a Cargo Benchmark Entry."""
299-
300-
test: bool = True
301-
302-
303-
@dataclasses.dataclass
304-
class Example(BuildTarget['raw.BuildTarget']):
305-
306-
"""Representation of a Cargo Example Entry."""
307-
308-
crate_type: T.List[manifest.CRATE_TYPE] = dataclasses.field(default_factory=lambda: ['bin'])
309-
310-
311-
@dataclasses.dataclass
312-
class Manifest:
313-
314-
"""Cargo Manifest definition.
315-
316-
Most of these values map up to the Cargo Manifest, but with default values
317-
if not provided.
318-
319-
Cargo subprojects can contain what Meson wants to treat as multiple,
320-
interdependent, subprojects.
321-
322-
:param path: the path within the cargo subproject.
323-
"""
324-
325-
package: Package
326-
dependencies: T.Dict[str, Dependency]
327-
dev_dependencies: T.Dict[str, Dependency]
328-
build_dependencies: T.Dict[str, Dependency]
329-
system_dependencies: T.Dict[str, SystemDependency] = dataclasses.field(init=False)
330-
lib: Library
331-
bin: T.List[Binary]
332-
test: T.List[Test]
333-
bench: T.List[Benchmark]
334-
example: T.List[Example]
335-
features: T.Dict[str, T.List[str]]
336-
target: T.Dict[str, T.Dict[str, Dependency]]
337-
path: str = ''
338-
339-
def __post_init__(self) -> None:
340-
self.features.setdefault('default', [])
341-
self.system_dependencies = {k: SystemDependency.from_raw(k, v) for k, v in self.package.metadata.get('system-deps', {}).items()}
342-
343-
@classmethod
344-
def from_raw(cls, raw: raw.Manifest, path: str = '') -> Self:
345-
return cls(
346-
package=Package.from_raw(raw['package']),
347-
dependencies={k: Dependency.from_raw(k, v) for k, v in raw.get('dependencies', {}).items()},
348-
dev_dependencies={k: Dependency.from_raw(k, v) for k, v in raw.get('dev-dependencies', {}).items()},
349-
build_dependencies={k: Dependency.from_raw(k, v) for k, v in raw.get('build-dependencies', {}).items()},
350-
lib=Library.from_raw(raw.get('lib', {}), raw['package']['name']),
351-
bin=[Binary.from_raw(b) for b in raw.get('bin', {})],
352-
test=[Test.from_raw(b) for b in raw.get('test', {})],
353-
bench=[Benchmark.from_raw(b) for b in raw.get('bench', {})],
354-
example=[Example.from_raw(b) for b in raw.get('example', {})],
355-
features=raw.get('features', {}),
356-
target={k: {k2: Dependency.from_raw(k2, v2) for k2, v2 in v.get('dependencies', {}).items()}
357-
for k, v in raw.get('target', {}).items()},
358-
path=path,
359-
)
360-
361-
36235
def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str:
36336
basename = package_name[:-len(suffix)] if package_name.endswith(suffix) else package_name
36437
return f'{basename}-{api}{suffix}'
@@ -693,7 +366,7 @@ def _create_meson_subdir(self, build: builder.Builder) -> T.List[mparser.BaseNod
693366
build.block([build.function('subdir', [build.string('meson')])]))
694367
]
695368

696-
def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: manifest.CRATE_TYPE) -> T.List[mparser.BaseNode]:
369+
def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: raw.CRATE_TYPE) -> T.List[mparser.BaseNode]:
697370
dependencies: T.List[mparser.BaseNode] = []
698371
dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {}
699372
for name in pkg.required_deps:

0 commit comments

Comments
 (0)