@@ -78,9 +78,14 @@ namespace paths {
7878 // Find a sibling package across all index prefixes.
7979 // e.g. find_sibling_package(gcc_bin, "linux-headers") searches for
8080 // xim-x-linux-headers, scode-x-linux-headers, etc.
81+ // Metadata-only dirs (.xim-installed/.xpkg.lua husks left by delegating
82+ // index packages) never qualify; when requiredRelPath is given, only a
83+ // version dir containing it qualifies (the payload may live under a
84+ // different prefix than the husk — issue #120).
8185 std::optional<std::filesystem::path>
8286 find_sibling_package (const std::filesystem::path& compilerBin,
83- std::string_view packageName);
87+ std::string_view packageName,
88+ std::string_view requiredRelPath = {});
8489
8590 // xpkgs root of the ACTIVE mcpp home ($MCPP_HOME or ~/.mcpp). Payload
8691 // discovery consults this in addition to compiler siblings: an
@@ -89,7 +94,11 @@ namespace paths {
8994 std::optional<std::filesystem::path> active_home_xpkgs ();
9095
9196 // Like find_sibling_tool, but anchored at the active home's xpkgs.
92- std::optional<std::filesystem::path> find_home_tool (std::string_view tool);
97+ // Searches across index prefixes (xim-x-, scode-x-, …) with the same
98+ // husk/requiredRelPath rules as find_sibling_package.
99+ std::optional<std::filesystem::path>
100+ find_home_tool (std::string_view tool,
101+ std::string_view requiredRelPath = {});
93102
94103 // index data root: env.home / "data"
95104 std::filesystem::path index_data (const Env& env);
@@ -543,20 +552,60 @@ std::optional<std::filesystem::path> active_home_xpkgs() {
543552 return xpkgs;
544553}
545554
546- std::optional<std::filesystem::path> find_home_tool (std::string_view tool) {
547- auto xpkgs = active_home_xpkgs ();
548- if (!xpkgs) return std::nullopt ;
555+ namespace {
549556
550- auto root = *xpkgs / std::format (" xim-x-{}" , tool);
557+ // A version dir qualifies as a payload only if it has real content —
558+ // dot-prefixed entries (.xim-installed, .xpkg.lua) are install metadata,
559+ // and a dir holding nothing else is the husk a delegating index package
560+ // leaves behind (the payload lives under another prefix; issue #120).
561+ // When requiredRelPath is given, the dir must also contain that path.
562+ bool payload_dir_qualifies (const std::filesystem::path& versionDir,
563+ std::string_view requiredRelPath) {
551564 std::error_code ec;
552- if (!std::filesystem::exists (root, ec)) return std::nullopt ;
565+ bool hasContent = false ;
566+ for (auto & f : std::filesystem::directory_iterator (versionDir, ec)) {
567+ if (!f.path ().filename ().string ().starts_with (" ." )) {
568+ hasContent = true ;
569+ break ;
570+ }
571+ }
572+ if (!hasContent) return false ;
573+ if (!requiredRelPath.empty ()
574+ && !std::filesystem::exists (versionDir / requiredRelPath, ec))
575+ return false ;
576+ return true ;
577+ }
553578
554- for (auto & v : std::filesystem::directory_iterator (root, ec)) {
555- if (v.is_directory (ec)) return v.path ();
579+ // Scan an xpkgs root across index prefixes (xim-x-, scode-x-, compat-x-, …)
580+ // for the first qualifying version dir of `packageName`.
581+ std::optional<std::filesystem::path>
582+ find_package_in_xpkgs (const std::filesystem::path& xpkgs,
583+ std::string_view packageName,
584+ std::string_view requiredRelPath) {
585+ std::error_code ec;
586+ std::string suffix = std::format (" -x-{}" , packageName);
587+ for (auto & entry : std::filesystem::directory_iterator (xpkgs, ec)) {
588+ if (!entry.is_directory (ec)) continue ;
589+ auto name = entry.path ().filename ().string ();
590+ if (!name.ends_with (suffix)) continue ;
591+ for (auto & v : std::filesystem::directory_iterator (entry.path (), ec)) {
592+ if (!v.is_directory (ec)) continue ;
593+ if (payload_dir_qualifies (v.path (), requiredRelPath))
594+ return v.path ();
595+ }
556596 }
557597 return std::nullopt ;
558598}
559599
600+ } // namespace
601+
602+ std::optional<std::filesystem::path>
603+ find_home_tool (std::string_view tool, std::string_view requiredRelPath) {
604+ auto xpkgs = active_home_xpkgs ();
605+ if (!xpkgs) return std::nullopt ;
606+ return find_package_in_xpkgs (*xpkgs, tool, requiredRelPath);
607+ }
608+
560609std::optional<std::filesystem::path>
561610find_sibling_binary (const std::filesystem::path& compilerBin,
562611 std::string_view tool,
@@ -578,54 +627,22 @@ find_sibling_binary(const std::filesystem::path& compilerBin,
578627
579628std::optional<std::filesystem::path>
580629find_sibling_package (const std::filesystem::path& compilerBin,
581- std::string_view packageName) {
630+ std::string_view packageName,
631+ std::string_view requiredRelPath) {
582632 auto xpkgs = xpkgs_from_compiler (compilerBin);
583633 if (!xpkgs) return std::nullopt ;
584634
585635 // Search across index prefixes: xim-x-, scode-x-, compat-x-, etc.
586- std::error_code ec;
587- std::string suffix = std::format (" -x-{}" , packageName);
588- for (auto & entry : std::filesystem::directory_iterator (*xpkgs, ec)) {
589- if (!entry.is_directory (ec)) continue ;
590- auto name = entry.path ().filename ().string ();
591- if (!name.ends_with (suffix)) continue ;
592- // Return the first (highest) version dir that has actual content.
593- for (auto & v : std::filesystem::directory_iterator (entry.path (), ec)) {
594- if (!v.is_directory (ec)) continue ;
595- // Skip empty packages (only .xim-installed marker)
596- bool hasContent = false ;
597- for (auto & f : std::filesystem::directory_iterator (v.path (), ec)) {
598- if (f.path ().filename () != " .xim-installed" ) {
599- hasContent = true ;
600- break ;
601- }
602- }
603- if (hasContent) return v.path ();
604- }
605- }
636+ if (auto found = find_package_in_xpkgs (*xpkgs, packageName, requiredRelPath))
637+ return found;
606638
607639 // Also check ~/.xlings/data/xpkgs/ (xlings global home) as fallback.
640+ std::error_code ec;
608641 const char * home = std::getenv (" HOME" );
609642 if (home) {
610643 auto xlingsXpkgs = std::filesystem::path (home) / " .xlings" / " data" / " xpkgs" ;
611- if (xlingsXpkgs != *xpkgs && std::filesystem::exists (xlingsXpkgs, ec)) {
612- for (auto & entry : std::filesystem::directory_iterator (xlingsXpkgs, ec)) {
613- if (!entry.is_directory (ec)) continue ;
614- auto name = entry.path ().filename ().string ();
615- if (!name.ends_with (suffix)) continue ;
616- for (auto & v : std::filesystem::directory_iterator (entry.path (), ec)) {
617- if (!v.is_directory (ec)) continue ;
618- bool hasContent = false ;
619- for (auto & f : std::filesystem::directory_iterator (v.path (), ec)) {
620- if (f.path ().filename () != " .xim-installed" ) {
621- hasContent = true ;
622- break ;
623- }
624- }
625- if (hasContent) return v.path ();
626- }
627- }
628- }
644+ if (xlingsXpkgs != *xpkgs && std::filesystem::exists (xlingsXpkgs, ec))
645+ return find_package_in_xpkgs (xlingsXpkgs, packageName, requiredRelPath);
629646 }
630647
631648 return std::nullopt ;
0 commit comments