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
4 changes: 2 additions & 2 deletions .github/workflows/xlings-ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ jobs:
echo "XDEV=$xdev" >> "$GITHUB_ENV"
export XDEV_BIN="$xdev"
mkdir -p target/xdev/linux-xdev
printf '%s\n' '{"name":"linux-xdev","platform":"linux","probes":{}}' > target/xdev/linux-xdev/lane.json
printf '{"name":"linux-xdev","platform":"linux","run":"%s:%s","probes":{}}\n' "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" > target/xdev/linux-xdev/lane.json
XTEST_META_OUT="$GITHUB_WORKSPACE/target/xdev/linux-xdev/meta.ndjson" \
XTEST_RESULTS_OUT="$GITHUB_WORKSPACE/target/xdev/linux-xdev/cases.ndjson" \
mcpp test -p xdev
Expand Down Expand Up @@ -826,7 +826,7 @@ jobs:
$dir = "target/xdev/windows-wsl"
New-Item -ItemType Directory -Force $dir | Out-Null
$name = "wsl: subos export --tar imports with wsl --import (WSL1)"
'{"name":"windows-wsl","platform":"windows","probes":{}}' | Set-Content "$dir/lane.json"
('{"name":"windows-wsl","platform":"windows","run":"' + $env:GITHUB_RUN_ID + ':' + $env:GITHUB_RUN_ATTEMPT + '","probes":{}}') | Set-Content "$dir/lane.json"
('{"test":"' + $name + '","area":"scenario","covers":["EXPORT-WSL"],"requires":[],"proves":"flow"}') | Set-Content "$dir/meta.ndjson"
if ("${{ steps.run.outcome }}" -eq "success") { $st = "pass"; $msg = "" }
elseif ("${{ steps.setup.outcome }}" -ne "success") { $st = "skip"; $msg = "WSL1 could not be set up on this runner" }
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/xlings-ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ jobs:
xdev="$GITHUB_WORKSPACE/$xdev"
export XDEV_BIN="$xdev"
mkdir -p target/xdev/macos-xdev
printf '%s\n' '{"name":"macos-xdev","platform":"macosx","probes":{}}' > target/xdev/macos-xdev/lane.json
printf '{"name":"macos-xdev","platform":"macos","run":"%s:%s","probes":{}}\n' "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" > target/xdev/macos-xdev/lane.json
XTEST_META_OUT="$GITHUB_WORKSPACE/target/xdev/macos-xdev/meta.ndjson" \
XTEST_RESULTS_OUT="$GITHUB_WORKSPACE/target/xdev/macos-xdev/cases.ndjson" \
mcpp test -p xdev
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/xlings-ci-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ jobs:
if (-not $xdev) { Write-Host "::error::mcpp build -p xdev produced no xdev.exe under target/"; exit 1 }
$env:XDEV_BIN = $xdev.FullName
New-Item -ItemType Directory -Force target/xdev/windows-xdev | Out-Null
'{"name":"windows-xdev","platform":"windows","probes":{}}' | Set-Content target/xdev/windows-xdev/lane.json
('{"name":"windows-xdev","platform":"windows","run":"' + $env:GITHUB_RUN_ID + ':' + $env:GITHUB_RUN_ATTEMPT + '","probes":{}}') | Set-Content target/xdev/windows-xdev/lane.json
$env:XTEST_META_OUT = "$env:GITHUB_WORKSPACE/target/xdev/windows-xdev/meta.ndjson"
$env:XTEST_RESULTS_OUT = "$env:GITHUB_WORKSPACE/target/xdev/windows-xdev/cases.ndjson"
mcpp test -p xdev
Expand Down
10 changes: 7 additions & 3 deletions apps/xdev/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -696,11 +696,15 @@ int cmd_report_dirs(const std::vector<fs::path>& dirs, bool summary,
lanes.push_back(l);
}
const auto first = records.size();
std::string execution = fs::absolute(dir).lexically_normal().string();
// A trend sample needs the execution it came from. A lane that does
// not say (a hand-written lane.json) is reported but never sampled:
// its directory names the same place in every CI run, so it would read
// as one execution with two results.
std::string execution;
std::string platform;
if (!lanes.empty() && lanes.back().value("name", "") == lane_name) {
platform = lanes.back().value("platform", "");
execution = lanes.back().value("run", execution);
execution = lanes.back().value("run", "");
}
for (auto& j : read_ndjson(dir / "mcpp.ndjson")) {
if (!j.contains("test")) continue;
Expand Down Expand Up @@ -735,7 +739,7 @@ int cmd_report_dirs(const std::vector<fs::path>& dirs, bool summary,
if (!program.empty()) j["test"] = program + ":" + name;
meta[{lane_name, j.value("test", "")}] = j;
}
if (!platform.empty())
if (!platform.empty() && !execution.empty())
for (auto i = first; i < records.size(); ++i) {
const auto& r = records[i];
observations.push_back({r.kind, r.name, platform, lane_name, execution, r.status, r.ms});
Expand Down
15 changes: 15 additions & 0 deletions apps/xdev/tests/test_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ XTEST(XdevCli, TrendArtifactsPreserveMemberAndPlatformAndFeedFutureShardPlans,
"--trend", (failure / "trend.json").string(), "--write", (fixture.home.root() / "repeat").string()});
EXPECT_EQ(duplicate.exit_code, 1);
EXPECT_EQ(json::parse(tk::read_file(fixture.home.root() / "repeat/trend.json"))["observations"].size(), 3);
// A lane that does not name its execution is reported, never sampled:
// its directory is the same in every CI run.
const auto unnamed = fixture.home.root() / "input-unnamed";
tk::write_file(unnamed / "lane.json", json{{"name", "linux-xdev"}, {"platform", "linux"}}.dump());
tk::write_file(unnamed / "cases.ndjson", json{{"program", "xdev"}, {"test", "Loopback.Case"},
{"status", "pass"}, {"ms", 7}}.dump() + "\n");
for (int again = 0; again != 2; ++again) {
const auto out = fixture.home.root() / std::format("unnamed-{}", again);
const auto rendered = fixture.run({"report", "--in", unnamed.string(), "--trend",
(failure / "trend.json").string(), "--write", out.string()});
EXPECT_EQ(rendered.exit_code, 0) << rendered.transcript();
EXPECT_EQ(json::parse(tk::read_file(out / "trend.json"))["observations"].size(), 3);
tk::write_file(unnamed / "cases.ndjson", json{{"program", "xdev"}, {"test", "Loopback.Case"},
{"status", "pass"}, {"ms", 9}}.dump() + "\n");
}
tk::write_file(fixture.home.root() / "tests/unit/slow.cpp", "import std;");
const auto inventory = fixture.discovery({json{{"member", "xdev"}, {"test", "unit/slow"},
{"main", "tests/unit/slow.cpp"}, {"cases", json::array({test_case("Slow.Check")})}}});
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ members = [

[package]
name = "xlings"
version = "2026.10.9.1"
version = "2026.10.9.2"
description = "Universal package management infrastructure tool with SubOS isolation"
license = "Apache-2.0"
repo = "https://github.com/openxlings/xlings"
Expand Down
2 changes: 1 addition & 1 deletion src/core/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import xlings.core.home;
namespace xlings {

export struct Info {
static constexpr std::string_view VERSION = "2026.10.9.1";
static constexpr std::string_view VERSION = "2026.10.9.2";
static constexpr std::string_view REPO = "https://github.com/openxlings/xlings";
};

Expand Down
20 changes: 18 additions & 2 deletions src/core/xvm/materialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,29 @@ std::expected<Prepared, std::string> preflight_materialization(
if (!expanded) return std::unexpected(expanded.error());
}
}
// An asset a recipe declares that this payload does not have is not
// placed -- as before this materializer: recipes declare their assets
// across versions, and a version without one (gcc 15.1.0 ships no
// libasan.so) is not a broken install. What exists and cannot be read
// still refuses: that is not "absent".
for (auto it = operations.begin(); it != operations.end();) {
if (it->second.change.remove) { ++it; continue; }
std::error_code ec;
const auto status = fs::status(it->second.change.source, ec);
if (status.type() == fs::file_type::not_found
&& (!ec || ec == std::errc::no_such_file_or_directory)) {
log::debug("[xvm] asset source missing, not placed: {}", it->second.change.source.string());
it = operations.erase(it);
continue;
}
if (ec) return std::unexpected(it->second.change.source.string() + ": asset source is unreadable: " + ec.message());
++it;
}
// A declared directory and another package's descendant must merge into
// a real directory, never write through the existing payload link.
for (auto& [destination, operation] : operations) {
if (operation.change.remove) continue;
std::error_code ec;
if (!fs::exists(operation.change.source, ec) || ec)
return std::unexpected(operation.change.source.string() + ": asset source is missing or unreadable");
if (fs::is_directory(operation.change.source, ec)) {
for (const auto& [child, nested] : operations)
if (!nested.change.remove && below_(destination, child)) operation.unwrap = true;
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/test_xvm_materialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,27 @@ XTEST(XvmMaterialize, UseConflictDoesNotPublishMetadataOrDeleteUserHeaders, .are
EXPECT_EQ(tk::read_file(scope / "usr/include/shared.h.xlings-new/sentinel"), "user staging");
EXPECT_EQ(nlohmann::json::parse(tk::read_file(scope / ".xlings.json")), scopeConfig);
}

XTEST(XvmMaterialize, AnAssetThePayloadDoesNotShipIsNotPlacedAndDoesNotRefuseTheRest,
.area = "xvm", .covers = {"HOME-LAYER-RESOLVE"}) {
// gcc 15.1.0's recipe declares lib64/libasan.so, which that build does
// not ship. The 2026.10.9.1 materializer refused the whole update over it
// (fresh-install, gcc suite); an absent source is skipped, as it was
// before -- the others are placed.
auto home = tk::Home::isolated("asset-absent-source");
const auto scope = home.dir() / "subos/default";
const auto payload = home.dir() / "data/xpkgs/xim-x-gcc/15.1.0/lib64";
tk::write_file(payload / "libstdc++.so.6", "present");
const std::vector<m::AssetChange> changes{
{payload / "libstdc++.so.6", scope / "lib/libstdc++.so.6", false},
{payload / "libasan.so", scope / "lib/libasan.so", false},
};
auto prepared = m::preflight_materialization(changes, {}, scope);
ASSERT_TRUE(prepared) << prepared.error();
auto applied = prepared->execute();
ASSERT_TRUE(applied) << applied.error();
ASSERT_TRUE(applied->commit());
EXPECT_TRUE(fs::exists(scope / "lib/libstdc++.so.6"));
std::error_code ec;
EXPECT_FALSE(fs::exists(fs::symlink_status(scope / "lib/libasan.so", ec))) << "absent: not placed";
}
Loading