@@ -113,6 +113,54 @@ std::optional<std::filesystem::path> find_manifest_root(std::filesystem::path st
113113 }
114114}
115115
116+ // Find the workspace root by walking upward from a member directory.
117+ // Returns empty if no workspace root found.
118+ std::filesystem::path find_workspace_root (const std::filesystem::path& memberRoot) {
119+ auto p = memberRoot.parent_path ();
120+ while (true ) {
121+ if (std::filesystem::exists (p / " mcpp.toml" )) {
122+ auto m = mcpp::manifest::load (p / " mcpp.toml" );
123+ if (m && m->workspace .present ) {
124+ // Verify memberRoot is in members list
125+ auto rel = std::filesystem::relative (memberRoot, p);
126+ for (auto & member : m->workspace .members ) {
127+ if (rel == std::filesystem::path (member)) return p;
128+ }
129+ }
130+ }
131+ auto parent = p.parent_path ();
132+ if (parent == p) break ;
133+ p = parent;
134+ }
135+ return {};
136+ }
137+
138+ // Merge workspace.dependencies versions into a member's deps.
139+ void merge_workspace_deps (mcpp::manifest::Manifest& member,
140+ const mcpp::manifest::Manifest& workspace) {
141+ auto merge_map = [&](std::map<std::string, mcpp::manifest::DependencySpec>& deps) {
142+ for (auto & [name, spec] : deps) {
143+ if (!spec.inheritWorkspace ) continue ;
144+ // Try exact key match first
145+ auto it = workspace.workspace .dependencies .find (name);
146+ if (it != workspace.workspace .dependencies .end ()) {
147+ spec.version = it->second .version ;
148+ spec.inheritWorkspace = false ;
149+ continue ;
150+ }
151+ // Try short name for default-ns deps
152+ auto shortIt = workspace.workspace .dependencies .find (spec.shortName );
153+ if (shortIt != workspace.workspace .dependencies .end ()) {
154+ spec.version = shortIt->second .version ;
155+ spec.inheritWorkspace = false ;
156+ }
157+ }
158+ };
159+ merge_map (member.dependencies );
160+ merge_map (member.devDependencies );
161+ merge_map (member.buildDependencies );
162+ }
163+
116164std::filesystem::path target_dir (const mcpp::toolchain::Toolchain& tc,
117165 const mcpp::toolchain::Fingerprint& fp,
118166 const std::filesystem::path& root)
@@ -772,8 +820,9 @@ struct BuildContext {
772820// Command-level overrides (--target / --static).
773821// Empty defaults preserve pre-existing behaviour exactly.
774822struct BuildOverrides {
775- std::string target_triple; // empty = host triple, fall through to [toolchain]
776- bool force_static = false ; // --static (or implied by musl target)
823+ std::string target_triple; // empty = host triple, fall through to [toolchain]
824+ bool force_static = false ; // --static (or implied by musl target)
825+ std::string package_filter; // -p <name>: only build this workspace member
777826};
778827
779828// `prepare_build` builds the BuildContext for any verb that compiles.
@@ -795,6 +844,94 @@ prepare_build(bool print_fingerprint,
795844 auto m = mcpp::manifest::load (*root / " mcpp.toml" );
796845 if (!m) return std::unexpected (m.error ().format ());
797846
847+ // ─── Workspace handling ────────────────────────────────────────────
848+ // If the manifest has [workspace] and is a virtual workspace (no [package]),
849+ // or if -p filter is set, switch to the target member's manifest.
850+ std::optional<mcpp::manifest::Manifest> wsManifest; // keep workspace manifest alive
851+ if (m->workspace .present ) {
852+ std::string targetMember;
853+
854+ if (!overrides.package_filter .empty ()) {
855+ // -p <name>: find matching member by directory basename or path
856+ for (auto & mp : m->workspace .members ) {
857+ auto basename = std::filesystem::path (mp).filename ().string ();
858+ if (basename == overrides.package_filter || mp == overrides.package_filter ) {
859+ targetMember = mp;
860+ break ;
861+ }
862+ }
863+ if (targetMember.empty ()) {
864+ return std::unexpected (std::format (
865+ " workspace member '{}' not found in [workspace].members" ,
866+ overrides.package_filter ));
867+ }
868+ } else if (m->package .name .empty ()) {
869+ // Virtual workspace: find a member with a binary target, or use last member.
870+ for (auto & mp : m->workspace .members ) {
871+ auto memberDir = *root / mp;
872+ auto mm = mcpp::manifest::load (memberDir / " mcpp.toml" );
873+ if (!mm) continue ;
874+ for (auto & t : mm->targets ) {
875+ if (t.kind == mcpp::manifest::Target::Binary) {
876+ targetMember = mp;
877+ break ;
878+ }
879+ }
880+ if (!targetMember.empty ()) break ;
881+ }
882+ if (targetMember.empty () && !m->workspace .members .empty ()) {
883+ targetMember = m->workspace .members .back ();
884+ }
885+ }
886+ // else: rooted workspace with [package] — build root normally.
887+
888+ if (!targetMember.empty ()) {
889+ auto memberDir = *root / targetMember;
890+ if (!std::filesystem::exists (memberDir / " mcpp.toml" )) {
891+ return std::unexpected (std::format (
892+ " workspace member '{}' has no mcpp.toml" , targetMember));
893+ }
894+ wsManifest = std::move (*m); // preserve workspace manifest
895+ m = mcpp::manifest::load (memberDir / " mcpp.toml" );
896+ if (!m) return std::unexpected (std::format (
897+ " workspace member '{}': {}" , targetMember, m.error ().format ()));
898+
899+ // Merge workspace dependency versions
900+ merge_workspace_deps (*m, *wsManifest);
901+
902+ // Inherit workspace toolchain if member doesn't define one
903+ if (m->toolchain .byPlatform .empty ()) {
904+ m->toolchain = wsManifest->toolchain ;
905+ }
906+ // Inherit workspace target overrides
907+ for (auto & [triple, entry] : wsManifest->targetOverrides ) {
908+ if (!m->targetOverrides .contains (triple)) {
909+ m->targetOverrides [triple] = entry;
910+ }
911+ }
912+
913+ mcpp::ui::status (" Workspace" , std::format (" building member '{}'" , targetMember));
914+ root = memberDir;
915+ }
916+ } else {
917+ // Not at workspace root — check if we're inside a workspace
918+ auto wsRoot = find_workspace_root (*root);
919+ if (!wsRoot.empty ()) {
920+ auto wsm = mcpp::manifest::load (wsRoot / " mcpp.toml" );
921+ if (wsm && wsm->workspace .present ) {
922+ merge_workspace_deps (*m, *wsm);
923+ if (m->toolchain .byPlatform .empty ()) {
924+ m->toolchain = wsm->toolchain ;
925+ }
926+ for (auto & [triple, entry] : wsm->targetOverrides ) {
927+ if (!m->targetOverrides .contains (triple)) {
928+ m->targetOverrides [triple] = entry;
929+ }
930+ }
931+ }
932+ }
933+ }
934+
798935 // Inject synthetic targets (e.g. test binaries from `mcpp test`).
799936 for (auto & t : extraTargets) m->targets .push_back (t);
800937
@@ -2053,6 +2190,7 @@ int cmd_build(const mcpplibs::cmdline::ParsedArgs& parsed) {
20532190
20542191 BuildOverrides ov;
20552192 if (auto t = parsed.value (" target" )) ov.target_triple = *t;
2193+ if (auto p = parsed.value (" package" )) ov.package_filter = *p;
20562194 ov.force_static = parsed.is_flag_set (" static" );
20572195
20582196 // P0: try fast-path if inputs haven't changed.
@@ -3533,6 +3671,8 @@ int run(int argc, char** argv) {
35333671 " Build for <triple> (e.g. x86_64-linux-musl); looks up [target.<triple>] in mcpp.toml" ))
35343672 .option (cl::Option (" static" ).help (
35353673 " Force static linking (-static). On Linux, prefer pairing with --target <arch>-linux-musl" ))
3674+ .option (cl::Option (" package" ).short_name (' p' ).takes_value ().value_name (" NAME" )
3675+ .help (" Build only the named workspace member" ))
35363676 .action (wrap_rc (cmd_build)))
35373677 .subcommand (cl::App (" run" )
35383678 .description (" Build + run a binary target (after `--`, args are passed to it)" )
0 commit comments