diff --git a/mcpp.toml b/mcpp.toml index cfb78d5..a1c3522 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "xpkg" -version = "0.0.44" +version = "0.0.45" description = "C++23 reference implementation of the xpkg V2 spec (multi-arch)" license = "Apache-2.0" repo = "https://github.com/openxlings/libxpkg" diff --git a/src/xpkg-compat.cppm b/src/xpkg-compat.cppm index 970f7c1..25b1ca3 100644 --- a/src/xpkg-compat.cppm +++ b/src/xpkg-compat.cppm @@ -133,12 +133,27 @@ std::expected resolve_resource( arch_alias = alias->second; std::string source; + std::unordered_map source_mirrors; if (auto source_it = matrix.platform_sources.find(context.platform); source_it != matrix.platform_sources.end()) source = source_it->second; else source = matrix.source; + const bool has_platform_source = + matrix.platform_sources.contains(context.platform); + + if (has_platform_source) { + if (auto mirrors_it = matrix.platform_source_mirrors.find(context.platform); + mirrors_it != matrix.platform_source_mirrors.end()) + source_mirrors = mirrors_it->second; + } else { + source_mirrors = matrix.source_mirrors; + } + + if (result.mirrors.empty() && !source_mirrors.empty()) + result.mirrors = source_mirrors; + if (result.url.empty() && (resource->is_res || is_xlings_res(source))) { result.kind = SourceKind::XlingsRes; } else if (is_xlings_res(result.url)) { diff --git a/src/xpkg-loader.cppm b/src/xpkg-loader.cppm index 7c84f28..62e4870 100644 --- a/src/xpkg-loader.cppm +++ b/src/xpkg-loader.cppm @@ -199,6 +199,14 @@ PlatformMatrix parse_xpm(lua::State* L, int pkg_idx) { // `source` is additive metadata: it supplies a default resource origin // without changing the existing platform/version matrix. xpm.source = get_str(L, xpm_idx, "source"); + if (xpm.source.empty()) { + xpm.source_mirrors = get_str_map(L, xpm_idx, "source"); + if (auto it = xpm.source_mirrors.find("GLOBAL"); + it != xpm.source_mirrors.end()) + xpm.source = it->second; + else if (!xpm.source_mirrors.empty()) + xpm.source = xpm.source_mirrors.begin()->second; + } lua::pushnil(L); while (lua::next(L, xpm_idx)) { @@ -212,8 +220,18 @@ PlatformMatrix parse_xpm(lua::State* L, int pkg_idx) { int plat_idx = lua::gettop(L); auto platform_source = get_str(L, plat_idx, "source"); - if (!platform_source.empty()) + if (!platform_source.empty()) { xpm.platform_sources[platform] = std::move(platform_source); + } else { + auto mirrors = get_str_map(L, plat_idx, "source"); + if (!mirrors.empty()) { + if (auto it = mirrors.find("GLOBAL"); it != mirrors.end()) + xpm.platform_sources[platform] = it->second; + else + xpm.platform_sources[platform] = mirrors.begin()->second; + xpm.platform_source_mirrors[platform] = std::move(mirrors); + } + } // Parse deps. Two accepted shapes: // diff --git a/src/xpkg.cppm b/src/xpkg.cppm index e9bfe34..848e4d4 100644 --- a/src/xpkg.cppm +++ b/src/xpkg.cppm @@ -79,8 +79,13 @@ struct PlatformMatrix { // Optional resource defaults. `source` applies to every platform while a // platform-specific value overrides it. Values are either "xlings-res" // or a URL template; version entries keep the existing model unchanged. + // String form remains the canonical/default source for V1 and V2. + // `source_mirrors` is populated when source is a GLOBAL/CN map. std::string source; + std::unordered_map source_mirrors; std::unordered_map platform_sources; + std::unordered_map> platform_source_mirrors; // platform -> version -> resource std::unordered_map> entries; diff --git a/tests/fixtures/pkgindex/pkgs/s/source-map.lua b/tests/fixtures/pkgindex/pkgs/s/source-map.lua new file mode 100644 index 0000000..e64ef25 --- /dev/null +++ b/tests/fixtures/pkgindex/pkgs/s/source-map.lua @@ -0,0 +1,16 @@ +package = { + spec = "2", + name = "source-map", + xpm = { + source = { + GLOBAL = "https://github.com/example/tool/releases/download/v${version}/tool-${arch_alias}.tar.gz", + CN = "https://gitcode.com/xlings-res/tool/releases/download/${version}/tool-${arch_alias}.tar.gz", + }, + linux = { + ["1.0.0"] = { + sha256 = "same-bytes", + arch_alias = { x86_64 = "amd64" }, + }, + }, + }, +} diff --git a/tests/test_loader.cpp b/tests/test_loader.cpp index f3067df..bd6e3cf 100644 --- a/tests/test_loader.cpp +++ b/tests/test_loader.cpp @@ -64,6 +64,17 @@ TEST(LoaderTest, LoadPackage_HasLinuxPlatform) { EXPECT_GT(result->xpm.entries.count("linux"), 0u); } +TEST(LoaderTest, LoadPackage_SourceMapPreservesGlobalAndRegionalMirrors) { + auto result = load_package(PKGINDEX / "pkgs/s/source-map.lua"); + ASSERT_TRUE(result.has_value()) << result.error(); + EXPECT_EQ(result->xpm.source, + "https://github.com/example/tool/releases/download/v${version}/tool-${arch_alias}.tar.gz"); + ASSERT_EQ(result->xpm.source_mirrors.size(), 2u); + EXPECT_EQ(result->xpm.source_mirrors.at("GLOBAL"), result->xpm.source); + EXPECT_EQ(result->xpm.source_mirrors.at("CN"), + "https://gitcode.com/xlings-res/tool/releases/download/${version}/tool-${arch_alias}.tar.gz"); +} + TEST(LoaderTest, BuildIndex_ReturnsEntries) { auto result = build_index(PKGINDEX); ASSERT_TRUE(result.has_value()) << result.error(); @@ -419,6 +430,30 @@ TEST(CompatTest, TemplateMirrorsAreExpandedAndPreserved) { EXPECT_EQ(resolved->mirrors.at("CN"), "https://mirror.test/2.0.0/amd64"); } +TEST(CompatTest, SourceMapUsesGlobalAsPrimaryAndPreservesCnFallback) { + PlatformMatrix matrix; + matrix.source = "https://github.com/example/tool/${version}/tool-${arch_alias}.tar.gz"; + matrix.source_mirrors = { + {"GLOBAL", matrix.source}, + {"CN", "https://gitcode.com/xlings-res/tool/${version}/tool-${arch_alias}.tar.gz"}, + }; + matrix.entries["linux"]["1.0.0"].sha256 = "same-bytes"; + matrix.entries["linux"]["1.0.0"].arch_alias["x86_64"] = "amd64"; + + auto resolved = resolve_resource(matrix, { + .name = "tool", + .version = "1.0.0", + .platform = "linux", + .arch = "x86_64", + }); + ASSERT_TRUE(resolved.has_value()) << resolved.error(); + EXPECT_EQ(resolved->url, + "https://github.com/example/tool/1.0.0/tool-amd64.tar.gz"); + EXPECT_EQ(resolved->mirrors.at("GLOBAL"), resolved->url); + EXPECT_EQ(resolved->mirrors.at("CN"), + "https://gitcode.com/xlings-res/tool/1.0.0/tool-amd64.tar.gz"); +} + TEST(CompatTest, RefCyclesAreRejected) { PlatformMatrix matrix; matrix.entries["linux"]["a"].ref = "b";