Skip to content

Commit a78cba8

Browse files
committed
fix(new): resolve namespaced packages in --template fetch
`mcpp new --template llmapi` failed with `fetch 'llmapi@0.2.6' failed`: the index lookup finds pkgs/l/llmapi.lua by bare filename, but the namespace stayed "" while the descriptor declares namespace="mcpplibs" (legacy name="mcpplibs.llmapi") — and xlings resolves install targets by the descriptor's qualified name. fetch_template_package now derives the structured (namespace, shortName) from the descriptor fields via the new pm::compat::descriptor_coordinates helper instead of trusting the filename hit. Root-index packages (imgui) keep namespace "" and still install by bare name; legacy embedded/dotted names and canonical namespace fields all normalize to the same coordinates. Covered by unit tests in test_pm_compat.
1 parent 84dd1b5 commit a78cba8

3 files changed

Lines changed: 113 additions & 7 deletions

File tree

src/cli.cppm

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,11 +1068,9 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
10681068

10691069
// Namespace candidates mirror dependency lookup: index root first,
10701070
// then the compat namespace.
1071-
std::string ns;
10721071
std::optional<std::string> lua;
10731072
for (std::string cand : {std::string{}, std::string{"compat"}}) {
10741073
if (auto l = fetcher.read_xpkg_lua(cand, spec.pkg)) {
1075-
ns = cand;
10761074
lua = std::move(*l);
10771075
break;
10781076
}
@@ -1083,16 +1081,28 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
10831081
"(check the name, or run `mcpp index update`)", spec.pkg));
10841082
}
10851083

1084+
// The filename hit alone does not carry the namespace: a bare spec
1085+
// like "llmapi" finds pkgs/l/llmapi.lua even though the descriptor
1086+
// declares `namespace = "mcpplibs"`, and xlings resolves install
1087+
// targets by the descriptor's qualified name. Derive the structured
1088+
// (namespace, shortName) from the descriptor fields.
1089+
auto coords = mcpp::pm::compat::descriptor_coordinates(
1090+
spec.pkg,
1091+
mcpp::manifest::extract_xpkg_namespace(*lua),
1092+
mcpp::manifest::extract_xpkg_name(*lua));
1093+
const std::string& ns = coords.namespace_;
1094+
const std::string& shortName = coords.shortName;
1095+
10861096
std::string version = spec.version;
10871097
if (version.empty()) {
1088-
auto v = mcpp::pm::resolve_semver(ns, spec.pkg, "*", fetcher);
1098+
auto v = mcpp::pm::resolve_semver(ns, shortName, "*", fetcher);
10891099
if (!v) return std::unexpected(v.error());
10901100
version = *v;
10911101
}
10921102

1093-
auto installed = fetcher.install_path(ns, spec.pkg, version);
1103+
auto installed = fetcher.install_path(ns, shortName, version);
10941104
if (!installed) {
1095-
auto fq = ns.empty() ? spec.pkg : std::format("{}.{}", ns, spec.pkg);
1105+
auto fq = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
10961106
mcpp::ui::info("Downloading", std::format("{} v{}", fq, version));
10971107
CliInstallProgress progress;
10981108
std::vector<std::string> targets{ std::format("{}@{}", fq, version) };
@@ -1101,7 +1111,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
11011111
"fetch '{}@{}': {}", fq, version, r.error().message));
11021112
if (r->exitCode != 0) return std::unexpected(std::format(
11031113
"fetch '{}@{}' failed (exit {})", fq, version, r->exitCode));
1104-
installed = fetcher.install_path(ns, spec.pkg, version);
1114+
installed = fetcher.install_path(ns, shortName, version);
11051115
if (!installed) return std::unexpected(std::format(
11061116
"package '{}@{}' install path missing after fetch", fq, version));
11071117
}
@@ -1123,7 +1133,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
11231133
return std::unexpected(std::format(
11241134
"package '{}@{}' has no mcpp.toml", spec.pkg, version));
11251135
}
1126-
return FetchedTemplatePackage{root, spec.pkg, version};
1136+
return FetchedTemplatePackage{root, shortName, version};
11271137
}
11281138

11291139
void print_template_listing(const FetchedTemplatePackage& pkg,

src/pm/compat.cppm

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,47 @@ inline ResolvedName resolve_package_name(std::string_view name,
7373
return r;
7474
}
7575

76+
// ─── Descriptor-derived coordinates ──────────────────────────────────
77+
//
78+
// Derive the structured (namespace, shortName) for a package whose index
79+
// descriptor was located by filename candidates from an unqualified spec
80+
// (`mcpp new --template <pkg>`). The filename hit alone is not enough:
81+
// pkgs/l/llmapi.lua is found by the bare name "llmapi" while the
82+
// descriptor declares `namespace = "mcpplibs"` (and possibly a legacy
83+
// dotted `name = "mcpplibs.llmapi"`), and xlings resolves install targets
84+
// by the descriptor's qualified name.
85+
//
86+
// Unlike resolve_package_name(), a bare name with no namespace field
87+
// stays in the index root (namespace "") — root packages such as "imgui"
88+
// install by their bare name.
89+
90+
inline ResolvedName descriptor_coordinates(std::string_view specPkg,
91+
std::string_view luaNs,
92+
std::string_view luaName)
93+
{
94+
ResolvedName r;
95+
std::string name(luaName.empty() ? specPkg : luaName);
96+
r.namespace_ = std::string(luaNs);
97+
98+
if (!r.namespace_.empty()) {
99+
// Legacy descriptors embed the namespace in the name too
100+
// (namespace = "mcpplibs", name = "mcpplibs.llmapi").
101+
auto prefix = r.namespace_ + ".";
102+
if (name.starts_with(prefix)) {
103+
name = name.substr(prefix.size());
104+
r.usedLegacySplit = true;
105+
}
106+
} else if (auto dot = name.find('.'); dot != std::string::npos) {
107+
// Legacy dotted name without a namespace field.
108+
r.namespace_ = name.substr(0, dot);
109+
name = name.substr(dot + 1);
110+
r.usedLegacySplit = true;
111+
}
112+
113+
r.shortName = std::move(name);
114+
return r;
115+
}
116+
76117
// Reconstruct the fully-qualified name from (namespace, shortName).
77118
// Default-namespace packages use the bare short name; others use
78119
// "ns.short".

tests/unit/test_pm_compat.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,58 @@ TEST(DependencySelector, ExplicitRootSelectorHasOnlyThatRoot) {
9393
EXPECT_EQ(selector.candidates[0].namespace_, "compat");
9494
EXPECT_EQ(selector.candidates[0].shortName, "gtest");
9595
}
96+
97+
// ─── descriptor_coordinates (package-template fetch) ────────────────
98+
99+
TEST(PmCompat, DescriptorCoordinatesLegacyEmbeddedNamespace) {
100+
// pkgs/l/llmapi.lua: namespace = "mcpplibs", name = "mcpplibs.llmapi"
101+
auto r = mcpp::pm::compat::descriptor_coordinates(
102+
"llmapi", "mcpplibs", "mcpplibs.llmapi");
103+
104+
EXPECT_EQ(r.namespace_, "mcpplibs");
105+
EXPECT_EQ(r.shortName, "llmapi");
106+
EXPECT_TRUE(r.usedLegacySplit);
107+
}
108+
109+
TEST(PmCompat, DescriptorCoordinatesCanonicalNamespaceField) {
110+
auto r = mcpp::pm::compat::descriptor_coordinates(
111+
"llmapi", "mcpplibs", "llmapi");
112+
113+
EXPECT_EQ(r.namespace_, "mcpplibs");
114+
EXPECT_EQ(r.shortName, "llmapi");
115+
EXPECT_FALSE(r.usedLegacySplit);
116+
}
117+
118+
TEST(PmCompat, DescriptorCoordinatesRootPackageStaysInRoot) {
119+
// pkgs/i/imgui.lua: namespace = "", name = "imgui" — must NOT be
120+
// promoted to the default namespace (it installs by its bare name).
121+
auto r = mcpp::pm::compat::descriptor_coordinates("imgui", "", "imgui");
122+
123+
EXPECT_EQ(r.namespace_, "");
124+
EXPECT_EQ(r.shortName, "imgui");
125+
EXPECT_FALSE(r.usedLegacySplit);
126+
}
127+
128+
TEST(PmCompat, DescriptorCoordinatesLegacyDottedNameWithoutNamespace) {
129+
auto r = mcpp::pm::compat::descriptor_coordinates(
130+
"tinyhttps", "", "mcpplibs.tinyhttps");
131+
132+
EXPECT_EQ(r.namespace_, "mcpplibs");
133+
EXPECT_EQ(r.shortName, "tinyhttps");
134+
EXPECT_TRUE(r.usedLegacySplit);
135+
}
136+
137+
TEST(PmCompat, DescriptorCoordinatesFallsBackToSpecWhenNameMissing) {
138+
auto r = mcpp::pm::compat::descriptor_coordinates("imgui", "", "");
139+
140+
EXPECT_EQ(r.namespace_, "");
141+
EXPECT_EQ(r.shortName, "imgui");
142+
}
143+
144+
TEST(PmCompat, DescriptorCoordinatesCompatNamespace) {
145+
auto r = mcpp::pm::compat::descriptor_coordinates(
146+
"mbedtls", "compat", "compat.mbedtls");
147+
148+
EXPECT_EQ(r.namespace_, "compat");
149+
EXPECT_EQ(r.shortName, "mbedtls");
150+
}

0 commit comments

Comments
 (0)