@@ -139,6 +139,21 @@ struct PackConfig {
139139 std::vector<std::string> forceBundle; // libs to bundle even if PEP 600 says skip
140140};
141141
142+ // `[workspace]` — multi-package workspace support (0.0.11+).
143+ //
144+ // A workspace root mcpp.toml declares member packages. Members share
145+ // a unified lock file, target directory, and can inherit dependency
146+ // versions via `.workspace = true`.
147+ //
148+ // Virtual workspace (no [package]): pure management node.
149+ // Rooted workspace ([package] + [workspace]): root is also a package.
150+ struct WorkspaceConfig {
151+ std::vector<std::string> members; // relative paths to member dirs
152+ std::vector<std::string> exclude; // paths to exclude
153+ std::map<std::string, DependencySpec> dependencies; // [workspace.dependencies]
154+ bool present = false ;
155+ };
156+
142157struct Manifest {
143158 std::filesystem::path sourcePath; // mcpp.toml's filesystem path
144159
@@ -156,9 +171,6 @@ struct Manifest {
156171 BuildConfig buildConfig;
157172
158173 // [target.<triple>] tables — empty if user didn't declare any.
159- // Triple keys are accepted in either GCC form (x86_64-linux-musl)
160- // or Rust form (x86_64-unknown-linux-musl); both are normalised by
161- // stripping `-unknown-` on read.
162174 std::map<std::string, TargetEntry> targetOverrides;
163175
164176 // [pack] — `mcpp pack` config (see docs/35-pack-design.md).
@@ -167,6 +179,9 @@ struct Manifest {
167179 // [lib] — library root interface convention (M5.x+).
168180 LibConfig lib;
169181
182+ // [workspace] — multi-package workspace.
183+ WorkspaceConfig workspace;
184+
170185 // M5.0: post-parse computed/inferred state
171186 bool usesModules = true ; // refined by scanner
172187 bool usesImportStd = true ; // refined by scanner
@@ -267,22 +282,26 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
267282 Manifest m;
268283 m.sourcePath = origin;
269284
270- // [package]
285+ // [package] — required unless [workspace] is present (virtual workspace).
271286 auto * pkg_t = doc->get_table (" package" );
272- if (!pkg_t ) return std::unexpected (error (origin, " missing required [package] section" ));
287+ bool has_workspace = (doc->get_table (" workspace" ) != nullptr );
288+ if (!pkg_t && !has_workspace)
289+ return std::unexpected (error (origin, " missing required [package] section" ));
273290
274291 auto name = doc->get_string (" package.name" );
275- if (!name) return std::unexpected (error (origin, " missing required field 'package.name'" ));
276- m.package .name = *name;
292+ if (!name && !has_workspace)
293+ return std::unexpected (error (origin, " missing required field 'package.name'" ));
294+ if (name) m.package .name = *name;
277295
278296 // 0.0.6+: explicit namespace field (xpkg V1 style).
279297 // If present, [package].name is the short name.
280298 // If absent, compat.cppm::resolve_package_name infers from dotted name.
281299 if (auto v = doc->get_string (" package.namespace" )) m.package .namespace_ = *v;
282300
283301 auto version = doc->get_string (" package.version" );
284- if (!version) return std::unexpected (error (origin, " missing required field 'package.version'" ));
285- m.package .version = *version;
302+ if (!version && !has_workspace)
303+ return std::unexpected (error (origin, " missing required field 'package.version'" ));
304+ if (version) m.package .version = *version;
286305
287306 if (auto v = doc->get_string (" package.description" )) m.package .description = *v;
288307 if (auto v = doc->get_string (" package.license" )) m.package .license = *v;
@@ -395,7 +414,7 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
395414 auto is_dep_spec_key = [](std::string_view k) {
396415 return k == " path" || k == " version" || k == " git"
397416 || k == " rev" || k == " tag" || k == " branch"
398- || k == " features" ;
417+ || k == " features" || k == " workspace " ;
399418 };
400419 auto looks_like_inline_dep_spec = [&](const t::Table& sub) {
401420 if (sub.empty ()) return false ;
@@ -423,6 +442,10 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
423442 spec.gitRev = it->second .as_string ();
424443 spec.gitRefKind = " branch" ;
425444 }
445+ if (auto it = sub.find (" workspace" ); it != sub.end () && it->second .is_bool () && it->second .as_bool ()) {
446+ spec.inheritWorkspace = true ;
447+ return {}; // version will be filled in by workspace merge
448+ }
426449 if (spec.path .empty () && spec.version .empty () && spec.git .empty ()) {
427450 return std::unexpected (error (origin, std::format (
428451 " [{}.\" {}\" ] must specify 'path', 'version', or 'git'" , section, fqName)));
@@ -597,6 +620,46 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
597620 }
598621 }
599622
623+ // [workspace] — multi-package workspace support (0.0.11+).
624+ if (doc->get_table (" workspace" )) {
625+ m.workspace .present = true ;
626+ if (auto v = doc->get_string_array (" workspace.members" ))
627+ m.workspace .members = *v;
628+ if (auto v = doc->get_string_array (" workspace.exclude" ))
629+ m.workspace .exclude = *v;
630+
631+ // [workspace.dependencies] — versions that members inherit via .workspace = true.
632+ if (auto * wdeps = doc->get_table (" workspace.dependencies" )) {
633+ for (auto & [k, v] : *wdeps) {
634+ if (v.is_string ()) {
635+ DependencySpec spec;
636+ spec.version = v.as_string ();
637+ if (k.find (' .' ) != std::string::npos) {
638+ auto pos = k.find (' .' );
639+ spec.namespace_ = k.substr (0 , pos);
640+ spec.shortName = k.substr (pos + 1 );
641+ } else {
642+ spec.namespace_ = std::string{kDefaultNamespace };
643+ spec.shortName = k;
644+ }
645+ m.workspace .dependencies [k] = std::move (spec);
646+ continue ;
647+ }
648+ if (!v.is_table ()) continue ;
649+ // Namespaced subtable: [workspace.dependencies.<ns>]
650+ const std::string ns = k;
651+ for (auto & [sk, sv] : v.as_table ()) {
652+ if (!sv.is_string ()) continue ;
653+ DependencySpec spec;
654+ spec.namespace_ = ns;
655+ spec.shortName = sk;
656+ spec.version = sv.as_string ();
657+ m.workspace .dependencies [std::format (" {}.{}" , ns, sk)] = std::move (spec);
658+ }
659+ }
660+ }
661+ }
662+
600663 return m;
601664}
602665
0 commit comments