|
19 | 19 |
|
20 | 20 | from . import builder, version, cfg
|
21 | 21 | from .toml import load_toml, TomlImplementationMissing
|
22 |
| -from .manifest import fixup_meson_varname, CargoLock |
| 22 | +from .manifest import Manifest, CargoLock, fixup_meson_varname |
23 | 23 | from ..mesonlib import MesonException, MachineChoice
|
24 | 24 | from .. import coredata, mlog
|
25 | 25 | from ..wrap.wrap import PackageDefinition
|
26 | 26 |
|
27 | 27 | if T.TYPE_CHECKING:
|
28 |
| - from typing_extensions import Protocol, Self |
29 |
| - |
30 |
| - from . import manifest, raw |
| 28 | + from . import raw |
31 | 29 | from .. import mparser
|
| 30 | + from .manifest import Dependency, SystemDependency |
32 | 31 | from ..environment import Environment
|
33 | 32 | from ..interpreterbase import SubProject
|
34 | 33 | from ..compilers.rust import RustCompiler
|
35 | 34 |
|
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 |
| - |
362 | 35 | def _dependency_name(package_name: str, api: str, suffix: str = '-rs') -> str:
|
363 | 36 | basename = package_name[:-len(suffix)] if package_name.endswith(suffix) else package_name
|
364 | 37 | return f'{basename}-{api}{suffix}'
|
@@ -693,7 +366,7 @@ def _create_meson_subdir(self, build: builder.Builder) -> T.List[mparser.BaseNod
|
693 | 366 | build.block([build.function('subdir', [build.string('meson')])]))
|
694 | 367 | ]
|
695 | 368 |
|
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]: |
697 | 370 | dependencies: T.List[mparser.BaseNode] = []
|
698 | 371 | dependency_map: T.Dict[mparser.BaseNode, mparser.BaseNode] = {}
|
699 | 372 | for name in pkg.required_deps:
|
|
0 commit comments