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
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
15 changes: 15 additions & 0 deletions src/xpkg-compat.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,27 @@ std::expected<ResolvedResource, std::string> resolve_resource(
arch_alias = alias->second;

std::string source;
std::unordered_map<std::string, std::string> 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)) {
Expand Down
20 changes: 19 additions & 1 deletion src/xpkg-loader.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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:
//
Expand Down
5 changes: 5 additions & 0 deletions src/xpkg.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string> source_mirrors;
std::unordered_map<std::string, std::string> platform_sources;
std::unordered_map<std::string,
std::unordered_map<std::string, std::string>> platform_source_mirrors;
// platform -> version -> resource
std::unordered_map<std::string,
std::unordered_map<std::string, PlatformResource>> entries;
Expand Down
16 changes: 16 additions & 0 deletions tests/fixtures/pkgindex/pkgs/s/source-map.lua
Original file line number Diff line number Diff line change
@@ -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" },
},
},
},
}
35 changes: 35 additions & 0 deletions tests/test_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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";
Expand Down
Loading