Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1068,11 +1068,9 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {

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

// The filename hit alone does not carry the namespace: a bare spec
// like "llmapi" finds pkgs/l/llmapi.lua even though the descriptor
// declares `namespace = "mcpplibs"`, and xlings resolves install
// targets by the descriptor's qualified name. Derive the structured
// (namespace, shortName) from the descriptor fields.
auto coords = mcpp::pm::compat::descriptor_coordinates(
spec.pkg,
mcpp::manifest::extract_xpkg_namespace(*lua),
mcpp::manifest::extract_xpkg_name(*lua));
const std::string& ns = coords.namespace_;
const std::string& shortName = coords.shortName;

std::string version = spec.version;
if (version.empty()) {
auto v = mcpp::pm::resolve_semver(ns, spec.pkg, "*", fetcher);
auto v = mcpp::pm::resolve_semver(ns, shortName, "*", fetcher);
if (!v) return std::unexpected(v.error());
version = *v;
}

auto installed = fetcher.install_path(ns, spec.pkg, version);
auto installed = fetcher.install_path(ns, shortName, version);
if (!installed) {
auto fq = ns.empty() ? spec.pkg : std::format("{}.{}", ns, spec.pkg);
auto fq = ns.empty() ? shortName : std::format("{}.{}", ns, shortName);
mcpp::ui::info("Downloading", std::format("{} v{}", fq, version));
CliInstallProgress progress;
std::vector<std::string> targets{ std::format("{}@{}", fq, version) };
Expand All @@ -1101,7 +1111,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
"fetch '{}@{}': {}", fq, version, r.error().message));
if (r->exitCode != 0) return std::unexpected(std::format(
"fetch '{}@{}' failed (exit {})", fq, version, r->exitCode));
installed = fetcher.install_path(ns, spec.pkg, version);
installed = fetcher.install_path(ns, shortName, version);
if (!installed) return std::unexpected(std::format(
"package '{}@{}' install path missing after fetch", fq, version));
}
Expand All @@ -1123,7 +1133,7 @@ fetch_template_package(const mcpp::scaffold::TemplateSpec& spec) {
return std::unexpected(std::format(
"package '{}@{}' has no mcpp.toml", spec.pkg, version));
}
return FetchedTemplatePackage{root, spec.pkg, version};
return FetchedTemplatePackage{root, shortName, version};
}

void print_template_listing(const FetchedTemplatePackage& pkg,
Expand Down
41 changes: 41 additions & 0 deletions src/pm/compat.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,47 @@ inline ResolvedName resolve_package_name(std::string_view name,
return r;
}

// ─── Descriptor-derived coordinates ──────────────────────────────────
//
// Derive the structured (namespace, shortName) for a package whose index
// descriptor was located by filename candidates from an unqualified spec
// (`mcpp new --template <pkg>`). The filename hit alone is not enough:
// pkgs/l/llmapi.lua is found by the bare name "llmapi" while the
// descriptor declares `namespace = "mcpplibs"` (and possibly a legacy
// dotted `name = "mcpplibs.llmapi"`), and xlings resolves install targets
// by the descriptor's qualified name.
//
// Unlike resolve_package_name(), a bare name with no namespace field
// stays in the index root (namespace "") — root packages such as "imgui"
// install by their bare name.

inline ResolvedName descriptor_coordinates(std::string_view specPkg,
std::string_view luaNs,
std::string_view luaName)
{
ResolvedName r;
std::string name(luaName.empty() ? specPkg : luaName);
r.namespace_ = std::string(luaNs);

if (!r.namespace_.empty()) {
// Legacy descriptors embed the namespace in the name too
// (namespace = "mcpplibs", name = "mcpplibs.llmapi").
auto prefix = r.namespace_ + ".";
if (name.starts_with(prefix)) {
name = name.substr(prefix.size());
r.usedLegacySplit = true;
}
} else if (auto dot = name.find('.'); dot != std::string::npos) {
// Legacy dotted name without a namespace field.
r.namespace_ = name.substr(0, dot);
name = name.substr(dot + 1);
r.usedLegacySplit = true;
}

r.shortName = std::move(name);
return r;
}

// Reconstruct the fully-qualified name from (namespace, shortName).
// Default-namespace packages use the bare short name; others use
// "ns.short".
Expand Down
55 changes: 55 additions & 0 deletions tests/unit/test_pm_compat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,58 @@ TEST(DependencySelector, ExplicitRootSelectorHasOnlyThatRoot) {
EXPECT_EQ(selector.candidates[0].namespace_, "compat");
EXPECT_EQ(selector.candidates[0].shortName, "gtest");
}

// ─── descriptor_coordinates (package-template fetch) ────────────────

TEST(PmCompat, DescriptorCoordinatesLegacyEmbeddedNamespace) {
// pkgs/l/llmapi.lua: namespace = "mcpplibs", name = "mcpplibs.llmapi"
auto r = mcpp::pm::compat::descriptor_coordinates(
"llmapi", "mcpplibs", "mcpplibs.llmapi");

EXPECT_EQ(r.namespace_, "mcpplibs");
EXPECT_EQ(r.shortName, "llmapi");
EXPECT_TRUE(r.usedLegacySplit);
}

TEST(PmCompat, DescriptorCoordinatesCanonicalNamespaceField) {
auto r = mcpp::pm::compat::descriptor_coordinates(
"llmapi", "mcpplibs", "llmapi");

EXPECT_EQ(r.namespace_, "mcpplibs");
EXPECT_EQ(r.shortName, "llmapi");
EXPECT_FALSE(r.usedLegacySplit);
}

TEST(PmCompat, DescriptorCoordinatesRootPackageStaysInRoot) {
// pkgs/i/imgui.lua: namespace = "", name = "imgui" — must NOT be
// promoted to the default namespace (it installs by its bare name).
auto r = mcpp::pm::compat::descriptor_coordinates("imgui", "", "imgui");

EXPECT_EQ(r.namespace_, "");
EXPECT_EQ(r.shortName, "imgui");
EXPECT_FALSE(r.usedLegacySplit);
}

TEST(PmCompat, DescriptorCoordinatesLegacyDottedNameWithoutNamespace) {
auto r = mcpp::pm::compat::descriptor_coordinates(
"tinyhttps", "", "mcpplibs.tinyhttps");

EXPECT_EQ(r.namespace_, "mcpplibs");
EXPECT_EQ(r.shortName, "tinyhttps");
EXPECT_TRUE(r.usedLegacySplit);
}

TEST(PmCompat, DescriptorCoordinatesFallsBackToSpecWhenNameMissing) {
auto r = mcpp::pm::compat::descriptor_coordinates("imgui", "", "");

EXPECT_EQ(r.namespace_, "");
EXPECT_EQ(r.shortName, "imgui");
}

TEST(PmCompat, DescriptorCoordinatesCompatNamespace) {
auto r = mcpp::pm::compat::descriptor_coordinates(
"mbedtls", "compat", "compat.mbedtls");

EXPECT_EQ(r.namespace_, "compat");
EXPECT_EQ(r.shortName, "mbedtls");
}
Loading