Skip to content

Commit 588f662

Browse files
committed
Merge main: port #130 (namespaced --template fetch) into mcpp.scaffold.create
2 parents fc0af9f + a62fb8e commit 588f662

3 files changed

Lines changed: 114 additions & 7 deletions

File tree

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".

src/scaffold/create.cppm

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import mcpp.config;
1313
import mcpp.fetcher;
1414
import mcpp.fetcher.progress;
1515
import mcpp.manifest;
16+
import mcpp.pm.compat;
1617
import mcpp.pm.resolver;
1718
import mcpp.scaffold;
1819
import mcpp.ui;
@@ -39,11 +40,9 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
3940

4041
// Namespace candidates mirror dependency lookup: index root first,
4142
// then the compat namespace.
42-
std::string ns;
4343
std::optional<std::string> lua;
4444
for (std::string cand : {std::string{}, std::string{"compat"}}) {
4545
if (auto l = fetcher.read_xpkg_lua(cand, spec.pkg)) {
46-
ns = cand;
4746
lua = std::move(*l);
4847
break;
4948
}
@@ -54,16 +53,28 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
5453
"(check the name, or run `mcpp index update`)", spec.pkg));
5554
}
5655

56+
// The filename hit alone does not carry the namespace: a bare spec
57+
// like "llmapi" finds pkgs/l/llmapi.lua even though the descriptor
58+
// declares `namespace = "mcpplibs"`, and xlings resolves install
59+
// targets by the descriptor's qualified name. Derive the structured
60+
// (namespace, shortName) from the descriptor fields.
61+
auto coords = mcpp::pm::compat::descriptor_coordinates(
62+
spec.pkg,
63+
mcpp::manifest::extract_xpkg_namespace(*lua),
64+
mcpp::manifest::extract_xpkg_name(*lua));
65+
const std::string& ns = coords.namespace_;
66+
const std::string& shortName = coords.shortName;
67+
5768
std::string version = spec.version;
5869
if (version.empty()) {
59-
auto v = mcpp::pm::resolve_semver(ns, spec.pkg, "*", fetcher);
70+
auto v = mcpp::pm::resolve_semver(ns, shortName, "*", fetcher);
6071
if (!v) return std::unexpected(v.error());
6172
version = *v;
6273
}
6374

64-
auto installed = fetcher.install_path(ns, spec.pkg, version);
75+
auto installed = fetcher.install_path(ns, shortName, version);
6576
if (!installed) {
66-
auto fq = ns.empty() ? spec.pkg : std::format("{}.{}", ns, spec.pkg);
77+
auto fq = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
6778
mcpp::ui::info("Downloading", std::format("{} v{}", fq, version));
6879
mcpp::fetcher::InstallProgressHandler progress;
6980
std::vector<std::string> targets{ std::format("{}@{}", fq, version) };
@@ -72,7 +83,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
7283
"fetch '{}@{}': {}", fq, version, r.error().message));
7384
if (r->exitCode != 0) return std::unexpected(std::format(
7485
"fetch '{}@{}' failed (exit {})", fq, version, r->exitCode));
75-
installed = fetcher.install_path(ns, spec.pkg, version);
86+
installed = fetcher.install_path(ns, shortName, version);
7687
if (!installed) return std::unexpected(std::format(
7788
"package '{}@{}' install path missing after fetch", fq, version));
7889
}
@@ -94,7 +105,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
94105
return std::unexpected(std::format(
95106
"package '{}@{}' has no mcpp.toml", spec.pkg, version));
96107
}
97-
return FetchedTemplatePackage{root, spec.pkg, version};
108+
return FetchedTemplatePackage{root, shortName, version};
98109
}
99110

100111
void print_template_listing(const FetchedTemplatePackage& pkg,

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)