14
14
import os
15
15
import collections
16
16
import urllib .parse
17
- import itertools
18
17
import typing as T
18
+ from functools import lru_cache
19
19
20
20
from . import builder , cfg , version
21
21
from .toml import load_toml , TomlImplementationMissing
22
- from .manifest import Manifest , CargoLock , fixup_meson_varname
22
+ from .manifest import Manifest , CargoLock , Workspace , fixup_meson_varname
23
23
from ..mesonlib import MesonException , MachineChoice
24
24
from .. import coredata , mlog
25
25
from ..wrap .wrap import PackageDefinition
@@ -56,6 +56,9 @@ class PackageState:
56
56
features : T .Set [str ] = dataclasses .field (default_factory = set )
57
57
required_deps : T .Set [str ] = dataclasses .field (default_factory = set )
58
58
optional_deps_features : T .Dict [str , T .Set [str ]] = dataclasses .field (default_factory = lambda : collections .defaultdict (set ))
59
+ # If this package is member of a workspace.
60
+ ws_subdir : T .Optional [str ] = None
61
+ ws_member : T .Optional [str ] = None
59
62
60
63
61
64
@dataclasses .dataclass (frozen = True )
@@ -64,31 +67,61 @@ class PackageKey:
64
67
api : str
65
68
66
69
70
+ @dataclasses .dataclass
71
+ class WorkspaceState :
72
+ workspace : Workspace
73
+ # member path -> PackageState, for all members of this workspace
74
+ packages : T .Dict [str , PackageState ] = dataclasses .field (default_factory = dict )
75
+ # package name to member path, for all members of this workspace
76
+ packages_to_member : T .Dict [str , str ] = dataclasses .field (default_factory = dict )
77
+ # member paths that are required to be built
78
+ required_members : T .List [str ] = dataclasses .field (default_factory = list )
79
+
80
+
67
81
class Interpreter :
68
82
def __init__ (self , env : Environment ) -> None :
69
83
self .environment = env
70
84
self .host_rustc = T .cast ('RustCompiler' , self .environment .coredata .compilers [MachineChoice .HOST ]['rust' ])
71
85
# Map Cargo.toml's subdir to loaded manifest.
72
- self .manifests : T .Dict [str , Manifest ] = {}
86
+ self .manifests : T .Dict [str , T . Union [ Manifest , Workspace ] ] = {}
73
87
# Map of cargo package (name + api) to its state
74
88
self .packages : T .Dict [PackageKey , PackageState ] = {}
89
+ # Map subdir to workspace
90
+ self .workspaces : T .Dict [str , WorkspaceState ] = {}
75
91
# Rustc's config
76
92
self .cfgs = self ._get_cfgs ()
77
93
78
- def interpret (self , subdir : str ) -> mparser .CodeBlockNode :
94
+ def interpret (self , subdir : str , project_root : T . Optional [ str ] = None ) -> mparser .CodeBlockNode :
79
95
manifest = self ._load_manifest (subdir )
80
- pkg , cached = self ._fetch_package (manifest .package .name , manifest .package .api )
81
- if not cached :
82
- # This is an entry point, always enable the 'default' feature.
83
- # FIXME: We should have a Meson option similar to `cargo build --no-default-features`
84
- self ._enable_feature (pkg , 'default' )
85
-
86
- # Build an AST for this package
87
96
filename = os .path .join (self .environment .source_dir , subdir , 'Cargo.toml' )
88
97
build = builder .Builder (filename )
89
- ast = self ._create_project (pkg , build )
90
- ast += [
91
- build .assign (build .function ('import' , [build .string ('rust' )]), 'rust' ),
98
+ if isinstance (manifest , Workspace ):
99
+ return self .interpret_workspace (manifest , build , subdir , project_root )
100
+ return self .interpret_package (manifest , build , subdir , project_root )
101
+
102
+ def interpret_package (self , manifest : Manifest , build : builder .Builder , subdir : str , project_root : T .Optional [str ]) -> mparser .CodeBlockNode :
103
+ if project_root :
104
+ ws = self .workspaces [project_root ]
105
+ member = ws .packages_to_member [manifest .package .name ]
106
+ pkg = ws .packages [member ]
107
+ else :
108
+ pkg , cached = self ._fetch_package (manifest .package .name , manifest .package .api )
109
+ assert isinstance (pkg , PackageState )
110
+ if not cached :
111
+ # This is an entry point, always enable the 'default' feature.
112
+ # FIXME: We should have a Meson option similar to `cargo build --no-default-features`
113
+ self ._enable_feature (pkg , 'default' )
114
+
115
+ # Build an AST for this package
116
+ ast : T .List [mparser .BaseNode ] = []
117
+ if not project_root :
118
+ ast += self ._create_project (pkg , build )
119
+ ast .append (build .assign (build .function ('import' , [build .string ('rust' )]), 'rust' ))
120
+ ast += self ._create_package (pkg , build , subdir )
121
+ return build .block (ast )
122
+
123
+ def _create_package (self , pkg : PackageState , build : builder .Builder , subdir : str ) -> T .List [mparser .BaseNode ]:
124
+ ast : T .List [mparser .BaseNode ] = [
92
125
build .function ('message' , [
93
126
build .string ('Enabled features:' ),
94
127
build .array ([build .string (f ) for f in pkg .features ]),
@@ -97,48 +130,155 @@ def interpret(self, subdir: str) -> mparser.CodeBlockNode:
97
130
ast += self ._create_dependencies (pkg , build )
98
131
ast += self ._create_meson_subdir (build )
99
132
100
- # Libs are always auto-discovered and there's no other way to handle them,
101
- # which is unfortunate for reproducability
102
133
if os .path .exists (os .path .join (self .environment .source_dir , subdir , pkg .manifest .lib .path )):
103
134
for crate_type in pkg .manifest .lib .crate_type :
104
- ast .extend (self ._create_lib (pkg , build , crate_type ))
135
+ ast += self ._create_lib (pkg , build , crate_type )
136
+
137
+ return ast
138
+
139
+ def interpret_workspace (self , workspace : Workspace , build : builder .Builder , subdir : str , project_root : T .Optional [str ]) -> mparser .CodeBlockNode :
140
+ ws = self ._get_workspace (workspace , subdir )
141
+ name = os .path .dirname (subdir )
142
+ subprojects_dir = os .path .join (subdir , 'subprojects' )
143
+ self .environment .wrap_resolver .load_and_merge (subprojects_dir , T .cast ('SubProject' , name ))
144
+ ast : T .List [mparser .BaseNode ] = []
145
+ if not project_root :
146
+ args : T .List [mparser .BaseNode ] = [
147
+ build .string (name ),
148
+ build .string ('rust' ),
149
+ ]
150
+ kwargs : T .Dict [str , mparser .BaseNode ] = {
151
+ 'meson_version' : build .string (f'>= { coredata .stable_version } ' ),
152
+ }
153
+ ast += [
154
+ build .function ('project' , args , kwargs ),
155
+ build .assign (build .function ('import' , [build .string ('rust' )]), 'rust' ),
156
+ ]
157
+ if not ws .required_members :
158
+ for member in ws .workspace .default_members :
159
+ self ._add_workspace_member (ws , member )
160
+
161
+ # Call subdir() for each required member of the workspace. The order is
162
+ # important, if a member depends on another member, that member must be
163
+ # processed first.
164
+ processed_members : T .Set [str ] = set ()
165
+
166
+ def _process_member (member : str ) -> None :
167
+ if member in processed_members :
168
+ return
169
+ pkg = ws .packages [member ]
170
+ for depname in pkg .required_deps :
171
+ dep = pkg .manifest .dependencies [depname ]
172
+ if dep .path :
173
+ dep_member = os .path .normpath (os .path .join (pkg .ws_member , dep .path ))
174
+ _process_member (dep_member )
175
+ if member == '.' :
176
+ ast .extend (self ._create_package (pkg , build , subdir ))
177
+ else :
178
+ ast .append (build .function ('subdir' , [build .string (member )]))
179
+ processed_members .add (member )
180
+
181
+ for member in ws .required_members :
182
+ _process_member (member )
105
183
106
184
return build .block (ast )
107
185
186
+ def _get_workspace (self , workspace : Workspace , subdir : str ) -> WorkspaceState :
187
+ ws = self .workspaces .get (subdir )
188
+ if ws :
189
+ return ws
190
+ ws = WorkspaceState (workspace )
191
+ for m in workspace .members :
192
+ m = os .path .normpath (m )
193
+ # Load member's manifest
194
+ m_subdir = os .path .join (subdir , m )
195
+ manifest_ = self ._load_manifest (m_subdir , ws .workspace , m )
196
+ assert isinstance (manifest_ , Manifest )
197
+ pkg = PackageState (manifest_ , ws_subdir = subdir , ws_member = m )
198
+ ws .packages [m ] = pkg
199
+ ws .packages_to_member [manifest_ .package .name ] = m
200
+ if workspace .root_package :
201
+ m = '.'
202
+ pkg = PackageState (workspace .root_package , ws_subdir = subdir , ws_member = m )
203
+ ws .packages [m ] = pkg
204
+ ws .packages_to_member [workspace .root_package .package .name ] = m
205
+ self .workspaces [subdir ] = ws
206
+ return ws
207
+
208
+ def _add_workspace_member (self , ws : WorkspaceState , member : str ) -> PackageState :
209
+ pkg = ws .packages [member ]
210
+ if member not in ws .required_members :
211
+ self ._prepare_package (pkg )
212
+ ws .required_members .append (member )
213
+ return pkg
214
+
108
215
def _fetch_package (self , package_name : str , api : str ) -> T .Tuple [PackageState , bool ]:
109
216
key = PackageKey (package_name , api )
110
217
pkg = self .packages .get (key )
111
218
if pkg :
112
219
return pkg , True
113
220
meson_depname = _dependency_name (package_name , api )
114
- subdir , _ = self .environment .wrap_resolver .resolve (meson_depname )
221
+ return self ._fetch_package_from_subproject (package_name , meson_depname )
222
+
223
+ @lru_cache (maxsize = None )
224
+ def _fetch_package_from_subproject (self , package_name : str , meson_depname : str ) -> T .Tuple [PackageState , bool ]:
225
+ subp_name , _ = self .environment .wrap_resolver .find_dep_provider (meson_depname )
226
+ subdir , _ = self .environment .wrap_resolver .resolve (subp_name )
115
227
subprojects_dir = os .path .join (subdir , 'subprojects' )
116
- self .environment .wrap_resolver .load_and_merge (subprojects_dir , T .cast ('SubProject' , meson_depname ))
228
+ self .environment .wrap_resolver .load_and_merge (subprojects_dir , T .cast ('SubProject' , subp_name ))
117
229
manifest = self ._load_manifest (subdir )
118
230
downloaded = \
119
- meson_depname in self .environment .wrap_resolver .wraps and \
120
- self .environment .wrap_resolver .wraps [meson_depname ].type is not None
231
+ subp_name in self .environment .wrap_resolver .wraps and \
232
+ self .environment .wrap_resolver .wraps [subp_name ].type is not None
233
+ if isinstance (manifest , Workspace ):
234
+ ws = self ._get_workspace (manifest , subdir )
235
+ member = ws .packages_to_member [package_name ]
236
+ pkg = self ._add_workspace_member (ws , member )
237
+ return pkg , False
238
+ key = PackageKey (package_name , version .api (manifest .package .version ))
239
+ pkg = self .packages .get (key )
240
+ if pkg :
241
+ return pkg , True
121
242
pkg = PackageState (manifest , downloaded )
122
243
self .packages [key ] = pkg
244
+ self ._prepare_package (pkg )
245
+ return pkg , False
246
+
247
+ def _prepare_package (self , pkg : PackageState ) -> None :
123
248
# Merge target specific dependencies that are enabled
124
- for condition , dependencies in manifest .target .items ():
249
+ for condition , dependencies in pkg . manifest .target .items ():
125
250
if cfg .eval_cfg (condition , self .cfgs ):
126
- manifest .dependencies .update (dependencies )
251
+ pkg . manifest .dependencies .update (dependencies )
127
252
# Fetch required dependencies recursively.
128
- for depname , dep in manifest .dependencies .items ():
253
+ for depname , dep in pkg . manifest .dependencies .items ():
129
254
if not dep .optional :
130
255
self ._add_dependency (pkg , depname )
131
- return pkg , False
132
256
133
- def _dep_package (self , dep : Dependency ) -> PackageState :
134
- return self .packages [PackageKey (dep .package , dep .api )]
257
+ def _dep_package (self , pkg : PackageState , dep : Dependency ) -> PackageState :
258
+ if dep .path :
259
+ ws = self .workspaces [pkg .ws_subdir ]
260
+ dep_member = os .path .normpath (os .path .join (pkg .ws_member , dep .path ))
261
+ dep_pkg = self ._add_workspace_member (ws , dep_member )
262
+ elif dep .git :
263
+ _ , _ , directory = _parse_git_url (dep .git , dep .branch )
264
+ dep_pkg , _ = self ._fetch_package_from_subproject (dep .package , directory )
265
+ elif dep .version :
266
+ dep_pkg , _ = self ._fetch_package (dep .package , dep .api )
267
+ else :
268
+ raise MesonException (f'Cannot determine version of cargo package { dep .package } ' )
269
+ return dep_pkg
135
270
136
- def _load_manifest (self , subdir : str ) -> Manifest :
271
+ def _load_manifest (self , subdir : str , workspace : T . Optional [ Workspace ] = None , member_path : str = '' ) -> T . Union [ Manifest , Workspace ] :
137
272
manifest_ = self .manifests .get (subdir )
138
273
if not manifest_ :
139
274
filename = os .path .join (self .environment .source_dir , subdir , 'Cargo.toml' )
140
275
raw = load_toml (filename )
141
- manifest_ = Manifest .from_raw (raw )
276
+ if 'workspace' in raw :
277
+ manifest_ = Workspace .from_raw (raw )
278
+ elif 'package' in raw :
279
+ manifest_ = Manifest .from_raw (raw , workspace , member_path )
280
+ else :
281
+ raise MesonException (f'{ subdir } /Cargo.toml does not have [package] or [workspace] section' )
142
282
self .manifests [subdir ] = manifest_
143
283
return manifest_
144
284
@@ -147,12 +287,10 @@ def _add_dependency(self, pkg: PackageState, depname: str) -> None:
147
287
return
148
288
dep = pkg .manifest .dependencies .get (depname )
149
289
if not dep :
150
- if depname in itertools .chain (pkg .manifest .dev_dependencies , pkg .manifest .build_dependencies ):
151
- # FIXME: Not supported yet
152
- return
153
- raise MesonException (f'Dependency { depname } not defined in { pkg .manifest .package .name } manifest' )
290
+ # It could be build/dev/target dependency. Just ignore it.
291
+ return
292
+ dep_pkg = self ._dep_package (pkg , dep )
154
293
pkg .required_deps .add (depname )
155
- dep_pkg , _ = self ._fetch_package (dep .package , dep .api )
156
294
if dep .default_features :
157
295
self ._enable_feature (dep_pkg , 'default' )
158
296
for f in dep .features :
@@ -176,7 +314,7 @@ def _enable_feature(self, pkg: PackageState, feature: str) -> None:
176
314
depname = depname [:- 1 ]
177
315
if depname in pkg .required_deps :
178
316
dep = pkg .manifest .dependencies [depname ]
179
- dep_pkg = self ._dep_package (dep )
317
+ dep_pkg = self ._dep_package (pkg , dep )
180
318
self ._enable_feature (dep_pkg , dep_f )
181
319
else :
182
320
# This feature will be enabled only if that dependency
@@ -186,7 +324,7 @@ def _enable_feature(self, pkg: PackageState, feature: str) -> None:
186
324
self ._add_dependency (pkg , depname )
187
325
dep = pkg .manifest .dependencies .get (depname )
188
326
if dep :
189
- dep_pkg = self ._dep_package (dep )
327
+ dep_pkg = self ._dep_package (pkg , dep )
190
328
self ._enable_feature (dep_pkg , dep_f )
191
329
elif f .startswith ('dep:' ):
192
330
self ._add_dependency (pkg , f [4 :])
@@ -247,7 +385,8 @@ def _create_dependencies(self, pkg: PackageState, build: builder.Builder) -> T.L
247
385
ast : T .List [mparser .BaseNode ] = []
248
386
for depname in pkg .required_deps :
249
387
dep = pkg .manifest .dependencies [depname ]
250
- ast += self ._create_dependency (dep , build )
388
+ dep_pkg = self ._dep_package (pkg , dep )
389
+ ast += self ._create_dependency (dep_pkg , dep , build )
251
390
ast .append (build .assign (build .array ([]), 'system_deps_args' ))
252
391
for name , sys_dep in pkg .manifest .system_dependencies .items ():
253
392
if sys_dep .enabled (pkg .features ):
@@ -280,10 +419,11 @@ def _create_system_dependency(self, name: str, dep: SystemDependency, build: bui
280
419
),
281
420
]
282
421
283
- def _create_dependency (self , dep : Dependency , build : builder .Builder ) -> T .List [mparser .BaseNode ]:
284
- pkg = self ._dep_package (dep )
422
+ def _create_dependency (self , pkg : PackageState , dep : Dependency , build : builder .Builder ) -> T .List [mparser .BaseNode ]:
423
+ version_ = dep .meson_version or [pkg .manifest .package .version ]
424
+ api = dep .api or pkg .manifest .package .api
285
425
kw = {
286
- 'version' : build .array ([build .string (s ) for s in dep . meson_version ]),
426
+ 'version' : build .array ([build .string (s ) for s in version_ ]),
287
427
}
288
428
# Lookup for this dependency with the features we want in default_options kwarg.
289
429
#
@@ -301,7 +441,7 @@ def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[
301
441
build .assign (
302
442
build .function (
303
443
'dependency' ,
304
- [build .string (_dependency_name (dep .package , dep . api ))],
444
+ [build .string (_dependency_name (dep .package , api ))],
305
445
kw ,
306
446
),
307
447
_dependency_varname (dep .package ),
@@ -331,7 +471,7 @@ def _create_dependency(self, dep: Dependency, build: builder.Builder) -> T.List[
331
471
build .if_ (build .not_in (build .identifier ('f' ), build .identifier ('actual_features' )), build .block ([
332
472
build .function ('error' , [
333
473
build .string ('Dependency' ),
334
- build .string (_dependency_name (dep .package , dep . api )),
474
+ build .string (_dependency_name (dep .package , api )),
335
475
build .string ('previously configured with features' ),
336
476
build .identifier ('actual_features' ),
337
477
build .string ('but need' ),
@@ -366,7 +506,7 @@ def _create_lib(self, pkg: PackageState, build: builder.Builder, crate_type: CRA
366
506
dep = pkg .manifest .dependencies [name ]
367
507
dependencies .append (build .identifier (_dependency_varname (dep .package )))
368
508
if name != dep .package :
369
- dep_pkg = self ._dep_package (dep )
509
+ dep_pkg = self ._dep_package (pkg , dep )
370
510
dep_lib_name = dep_pkg .manifest .lib .name
371
511
dependency_map [build .string (fixup_meson_varname (dep_lib_name ))] = build .string (name )
372
512
for name , sys_dep in pkg .manifest .system_dependencies .items ():
0 commit comments