1919
2020from . import builder , version , cfg
2121from .toml import load_toml
22- from .manifest import Manifest , CargoLock , fixup_meson_varname
22+ from .manifest import Manifest , CargoLock , Workspace , fixup_meson_varname
2323from ..mesonlib import MesonException , MachineChoice , version_compare
2424from .. import coredata , mlog
2525from ..wrap .wrap import PackageDefinition
@@ -56,6 +56,9 @@ class PackageState:
5656 features : T .Set [str ] = dataclasses .field (default_factory = set )
5757 required_deps : T .Set [str ] = dataclasses .field (default_factory = set )
5858 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
5962
6063
6164@dataclasses .dataclass (frozen = True )
@@ -64,13 +67,27 @@ class PackageKey:
6467 api : str
6568
6669
70+ @dataclasses .dataclass
71+ class WorkspaceState :
72+ workspace : Workspace
73+ subdir : str
74+ # member path -> PackageState, for all members of this workspace
75+ packages : T .Dict [str , PackageState ] = dataclasses .field (default_factory = dict )
76+ # package name to member path, for all members of this workspace
77+ packages_to_member : T .Dict [str , str ] = dataclasses .field (default_factory = dict )
78+ # member paths that are required to be built
79+ required_members : T .List [str ] = dataclasses .field (default_factory = list )
80+
81+
6782class Interpreter :
6883 def __init__ (self , env : Environment , subdir : str , subprojects_dir : str ) -> None :
6984 self .environment = env
7085 # Map Cargo.toml's subdir to loaded manifest.
71- self .manifests : T .Dict [str , Manifest ] = {}
86+ self .manifests : T .Dict [str , T . Union [ Manifest , Workspace ] ] = {}
7287 # Map of cargo package (name + api) to its state
7388 self .packages : T .Dict [PackageKey , PackageState ] = {}
89+ # Map subdir to workspace
90+ self .workspaces : T .Dict [str , WorkspaceState ] = {}
7491 # Cargo packages
7592 filename = os .path .join (self .environment .get_source_dir (), subdir , 'Cargo.lock' )
7693 subprojects_dir = os .path .join (self .environment .get_source_dir (), subprojects_dir )
@@ -90,17 +107,23 @@ def interpret(self, subdir: str, project_root: T.Optional[str] = None) -> mparse
90107 manifest = self ._load_manifest (subdir )
91108 filename = os .path .join (self .environment .source_dir , subdir , 'Cargo.toml' )
92109 build = builder .Builder (filename )
110+ if isinstance (manifest , Workspace ):
111+ return self .interpret_workspace (manifest , build , subdir , project_root )
93112 return self .interpret_package (manifest , build , subdir , project_root )
94113
95114 def interpret_package (self , manifest : Manifest , build : builder .Builder , subdir : str , project_root : T .Optional [str ]) -> mparser .CodeBlockNode :
96115 # Build an AST for this package
97116 ast : T .List [mparser .BaseNode ] = []
98- pkg , cached = self ._fetch_package (manifest .package .name , manifest .package .api )
99- if not cached :
100- # This is an entry point, always enable the 'default' feature.
101- # FIXME: We should have a Meson option similar to `cargo build --no-default-features`
102- self ._enable_feature (pkg , 'default' )
103- if not project_root :
117+ if project_root :
118+ ws = self .workspaces [project_root ]
119+ member = ws .packages_to_member [manifest .package .name ]
120+ pkg = ws .packages [member ]
121+ else :
122+ pkg , cached = self ._fetch_package (manifest .package .name , manifest .package .api )
123+ if not cached :
124+ # This is an entry point, always enable the 'default' feature.
125+ # FIXME: We should have a Meson option similar to `cargo build --no-default-features`
126+ self ._enable_feature (pkg , 'default' )
104127 ast += self ._create_project (pkg .manifest .package .name , pkg , build )
105128 ast .append (build .assign (build .function ('import' , [build .string ('rust' )]), 'rust' ))
106129 ast += self ._create_package (pkg , build , subdir )
@@ -122,6 +145,74 @@ def _create_package(self, pkg: PackageState, build: builder.Builder, subdir: str
122145
123146 return ast
124147
148+ def interpret_workspace (self , workspace : Workspace , build : builder .Builder , subdir : str , project_root : T .Optional [str ]) -> mparser .CodeBlockNode :
149+ ws = self ._get_workspace (workspace , subdir )
150+ name = os .path .dirname (subdir )
151+ subprojects_dir = os .path .join (subdir , 'subprojects' )
152+ self .environment .wrap_resolver .load_and_merge (subprojects_dir , T .cast ('SubProject' , name ))
153+ ast : T .List [mparser .BaseNode ] = []
154+ if not ws .required_members :
155+ for member in ws .workspace .default_members :
156+ self ._require_workspace_member (ws , member )
157+
158+ # Call subdir() for each required member of the workspace. The order is
159+ # important, if a member depends on another member, that member must be
160+ # processed first.
161+ processed_members : T .Dict [str , PackageState ] = {}
162+
163+ def _process_member (member : str ) -> None :
164+ if member in processed_members :
165+ return
166+ pkg = ws .packages [member ]
167+ for depname in pkg .required_deps :
168+ dep = pkg .manifest .dependencies [depname ]
169+ if dep .path :
170+ dep_member = os .path .normpath (os .path .join (pkg .ws_member , dep .path ))
171+ _process_member (dep_member )
172+ ast .append (build .function ('subdir' , [build .string (member )]))
173+ processed_members [member ] = pkg
174+
175+ ast .append (build .assign (build .function ('import' , [build .string ('rust' )]), 'rust' ))
176+ for member in ws .required_members :
177+ _process_member (member )
178+ if not project_root :
179+ ast = self ._create_project (name , None , build ) + ast
180+
181+ return build .block (ast )
182+
183+ def _load_workspace_member (self , ws : WorkspaceState , m : str ) -> None :
184+ m = os .path .normpath (m )
185+ # Load member's manifest
186+ m_subdir = os .path .join (ws .subdir , m )
187+ manifest_ = self ._load_manifest (m_subdir , ws .workspace , m )
188+ assert isinstance (manifest_ , Manifest )
189+ self ._add_workspace_member (manifest_ , ws , m )
190+
191+ def _add_workspace_member (self , manifest_ : Manifest , ws : WorkspaceState , m : str ) -> None :
192+ if m in ws .packages :
193+ return
194+ pkg = PackageState (manifest_ , ws_subdir = ws .subdir , ws_member = m )
195+ ws .packages [m ] = pkg
196+ ws .packages_to_member [manifest_ .package .name ] = m
197+
198+ def _get_workspace (self , workspace : Workspace , subdir : str ) -> WorkspaceState :
199+ ws = self .workspaces .get (subdir )
200+ if ws :
201+ return ws
202+ ws = WorkspaceState (workspace , subdir )
203+ for m in workspace .members :
204+ self ._load_workspace_member (ws , m )
205+ self .workspaces [subdir ] = ws
206+ return ws
207+
208+ def _require_workspace_member (self , ws : WorkspaceState , member : str ) -> PackageState :
209+ member = os .path .normpath (member )
210+ pkg = ws .packages [member ]
211+ if member not in ws .required_members :
212+ self ._prepare_package (pkg )
213+ ws .required_members .append (member )
214+ return pkg
215+
125216 def _fetch_package (self , package_name : str , api : str ) -> T .Tuple [PackageState , bool ]:
126217 key = PackageKey (package_name , api )
127218 pkg = self .packages .get (key )
@@ -150,6 +241,11 @@ def _fetch_package_from_subproject(self, package_name: str, meson_depname: str)
150241 downloaded = \
151242 subp_name in self .environment .wrap_resolver .wraps and \
152243 self .environment .wrap_resolver .wraps [subp_name ].type is not None
244+ if isinstance (manifest , Workspace ):
245+ ws = self ._get_workspace (manifest , subdir )
246+ member = ws .packages_to_member [package_name ]
247+ pkg = self ._require_workspace_member (ws , member )
248+ return pkg , False
153249 key = PackageKey (package_name , version .api (manifest .package .version ))
154250
155251 pkg = self .packages .get (key )
@@ -172,7 +268,14 @@ def _prepare_package(self, pkg: PackageState) -> None:
172268 self ._add_dependency (pkg , depname )
173269
174270 def _dep_package (self , pkg : PackageState , dep : Dependency ) -> PackageState :
175- if dep .git :
271+ if dep .path :
272+ if not pkg .ws_subdir :
273+ raise MesonException ("path dependencies only supported inside workspaces" )
274+ ws = self .workspaces [pkg .ws_subdir ]
275+ dep_member = os .path .normpath (os .path .join (pkg .ws_member , dep .path ))
276+ self ._load_workspace_member (ws , dep_member )
277+ dep_pkg = self ._require_workspace_member (ws , dep_member )
278+ elif dep .git :
176279 _ , _ , directory = _parse_git_url (dep .git , dep .branch )
177280 dep_pkg , _ = self ._fetch_package_from_subproject (dep .package , directory )
178281 else :
@@ -192,18 +295,30 @@ def _dep_package(self, pkg: PackageState, dep: Dependency) -> PackageState:
192295 dep_pkg , _ = self ._fetch_package (dep .package , dep .api )
193296 return dep_pkg
194297
195- def _load_manifest (self , subdir : str ) -> Manifest :
298+ def _load_manifest (self , subdir : str , workspace : T . Optional [ Workspace ] = None , member_path : str = '' ) -> T . Union [ Manifest , Workspace ] :
196299 manifest_ = self .manifests .get (subdir )
197300 if not manifest_ :
198301 path = os .path .join (self .environment .source_dir , subdir )
199302 filename = os .path .join (path , 'Cargo.toml' )
200303 toml = load_toml (filename )
304+ workspace_ = None
305+ if 'workspace' in toml :
306+ raw_workspace = T .cast ('raw.VirtualManifest' , toml )
307+ workspace_ = Workspace .from_raw (raw_workspace )
308+ manifest_ = None
201309 if 'package' in toml :
202310 raw_manifest = T .cast ('raw.Manifest' , toml )
203- manifest_ = Manifest .from_raw (raw_manifest , path )
204- self .manifests [subdir ] = manifest_
205- else :
206- raise MesonException (f'{ subdir } /Cargo.toml does not have [package] section' )
311+ manifest_ = Manifest .from_raw (raw_manifest , path , workspace , member_path )
312+ if not manifest_ and not workspace_ :
313+ raise MesonException (f'{ subdir } /Cargo.toml does not have [package] or [workspace] section' )
314+
315+ if workspace_ :
316+ if manifest_ :
317+ raise NotImplementedError
318+ self .manifests [subdir ] = workspace_
319+ return workspace_
320+
321+ self .manifests [subdir ] = manifest_
207322 return manifest_
208323
209324 def _add_dependency (self , pkg : PackageState , depname : str ) -> None :
0 commit comments