Skip to content
Closed
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
12 changes: 11 additions & 1 deletion mcpp.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
# Auto-generated by mcpp. Do not edit by hand.
# Records what this build resolved. It does not yet pin future builds:
# index dependencies are re-resolved from their constraints each time.
# dev-dependencies are excluded: only `mcpp test` resolves them, and this
# file must not change depending on which command ran last.
version = 2

[package."compat.lua"]
namespace = "compat"
version = "5.4.7"
source = "index+compat@5.4.7"
hash = "fnv1a:62ff6fb178577847"

[package."mcpplibs.capi.lua"]
namespace = "mcpplibs.capi"
version = "0.0.3"
source = "index+mcpplibs.capi@0.0.3"
hash = "fnv1a:c8e7ef9d60a1a3a6"
hash = "fnv1a:f3031630d0b22d94"

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.57"
version = "0.0.58"
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-loader.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,20 @@ bool get_bool(lua::State* L, int idx, const char* key) {
return r;
}

// A non-negative integer field, or 0. Lenient like the readers above: a
// malformed value reads as absent. The index's own CI rejects it.
int get_nonneg_int(lua::State* L, int idx, const char* key) {
lua::getfield(L, idx, key);
int r = 0;
if (lua::isinteger(L, -1)) {
auto v = lua::tointeger(L, -1);
if (v >= 0 && v <= std::numeric_limits<int>::max())
r = static_cast<int>(v);
}
lua::pop(L, 1);
return r;
}

std::vector<std::string> get_str_array(lua::State* L, int idx, const char* key) {
std::vector<std::string> result;
lua::getfield(L, idx, key);
Expand Down Expand Up @@ -401,6 +415,7 @@ PlatformMatrix parse_xpm(lua::State* L, int pkg_idx) {
}
res.sha256 = get_str(L, res_idx, "sha256");
res.ref = get_str(L, res_idx, "ref");
res.revision = get_nonneg_int(L, res_idx, "revision");

// ---- V2 multi-arch shapes ----
// Scheme C / res: `sha256` is a per-arch TABLE rather
Expand Down
5 changes: 5 additions & 0 deletions src/xpkg.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ struct PlatformResource {
std::unordered_map<std::string, std::string> sha256_by_arch; // canonical-arch -> sha256 (Scheme C: template / res)
std::unordered_map<std::string, std::string> arch_alias; // canonical-arch -> upstream token for ${arch_alias}
bool is_res = false; // res=true: XLINGS_RES with per-arch checksums
// Packaging revision of this version's payload: a change to what the
// recipe installs that keeps the upstream version. 0 when the entry
// states none. A client records the revision it installed and treats a
// different recipe revision as not installed (xpkg V2 spec, "revision").
int revision = 0;
};

// What this package exposes to consumers/xlings at install/runtime.
Expand Down
38 changes: 38 additions & 0 deletions tests/fixtures/pkgindex/pkgs/v/v2revision.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package = {
spec = "2",
name = "v2revision",
description = "V2 fixture: packaging revision on version entries",
type = "package",
archs = {"x86_64", "aarch64"},
status = "stable",
categories = {"test"},
xpm = {
linux = {
["latest"] = { ref = "1.2.0" },
["1.0.0"] = {
url = "https://ex/v2revision-1.0.0.tar.gz",
sha256 = "aaaa",
},
["1.1.0"] = {
url = "https://ex/v2revision-1.1.0.tar.gz",
sha256 = "bbbb",
revision = 2,
},
["1.2.0"] = {
x86_64 = { url = "https://ex/v2revision-1.2.0-x86_64.tar.gz", sha256 = "cccc" },
aarch64 = { url = "https://ex/v2revision-1.2.0-aarch64.tar.gz", sha256 = "dddd" },
revision = 1,
},
["1.3.0"] = {
url = "https://ex/v2revision-1.3.0.tar.gz",
sha256 = "eeee",
revision = "3",
},
["1.4.0"] = {
url = "https://ex/v2revision-1.4.0.tar.gz",
sha256 = "ffff",
revision = -1,
},
},
},
}
24 changes: 24 additions & 0 deletions tests/test_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,30 @@ TEST(LoaderTest, V2_LegacySingleArch_Unchanged) {
EXPECT_TRUE(r.archs.empty());
EXPECT_TRUE(r.sha256_by_arch.empty());
EXPECT_FALSE(r.is_res);
EXPECT_EQ(r.revision, 0);
}

// A version entry may state a packaging revision. An entry that states none
// has revision 0, and so does an entry whose value is not a non-negative
// integer. The revision does not change how the rest of the entry is read:
// a per-arch map with a revision is still a per-arch map.
TEST(LoaderTest, V2_Revision_ParsedPerVersionEntry) {
auto result = load_package(PKGINDEX / "pkgs/v/v2revision.lua");
ASSERT_TRUE(result.has_value()) << result.error();
auto& plat = result->xpm.entries.at("linux");
EXPECT_EQ(plat.at("1.0.0").revision, 0);
EXPECT_EQ(plat.at("1.1.0").revision, 2);
EXPECT_EQ(plat.at("1.1.0").url, "https://ex/v2revision-1.1.0.tar.gz");

auto& map = plat.at("1.2.0");
EXPECT_EQ(map.revision, 1);
ASSERT_EQ(map.archs.size(), 2u);
EXPECT_EQ(map.archs.at("x86_64").sha256, "cccc");

EXPECT_EQ(plat.at("1.3.0").revision, 0); // a string is not a revision
EXPECT_EQ(plat.at("1.4.0").revision, 0); // nor is a negative number
EXPECT_EQ(plat.at("latest").revision, 0); // an alias carries none
EXPECT_EQ(plat.at("latest").ref, "1.2.0");
}

TEST(LoaderTest, SourceDefaultsAreParsedAsMetadataNotVersions) {
Expand Down