Skip to content

Commit 7a1020e

Browse files
committed
feat(index): project-level [indices] artifact/source with pin-forces-git rule (#269)
IndexSpec gains artifact/source, parsed from both mcpp.toml and config.toml [indices] long forms; ensure_project_index_dir passes them through to the project seed. artifact_applicable() encodes the design rule: rev/tag/branch pins (and local path) force git because the artifact channel only tracks the latest pointer — a declared-but-inapplicable artifact warns instead of silently syncing the wrong revision.
1 parent d7570b7 commit 7a1020e

4 files changed

Lines changed: 52 additions & 1 deletion

File tree

‎src/config.cppm‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ std::expected<GlobalConfig, ConfigError> load_or_init(
596596
if (auto it = sub.find("tag"); it != sub.end() && it->second.is_string()) spec.tag = it->second.as_string();
597597
if (auto it = sub.find("branch"); it != sub.end() && it->second.is_string()) spec.branch = it->second.as_string();
598598
if (auto it = sub.find("path"); it != sub.end() && it->second.is_string()) spec.path = it->second.as_string();
599+
if (auto it = sub.find("artifact"); it != sub.end() && it->second.is_string()) spec.artifact = it->second.as_string();
600+
if (auto it = sub.find("source"); it != sub.end() && it->second.is_string()) spec.source = it->second.as_string();
599601
}
600602
if (!spec.url.empty() || !spec.path.empty())
601603
cfg.indices[k] = std::move(spec);
@@ -731,7 +733,18 @@ bool ensure_project_index_dir(
731733
customRepos.push_back({ name, source.generic_string(), "", "" });
732734
continue;
733735
}
734-
customRepos.push_back({ name, spec.url, "", "" });
736+
// #269: pass a declared artifact source through to xlings unless a
737+
// rev/tag/branch pin forces git (the artifact channel only tracks
738+
// the latest published pointer).
739+
mcpp::xlings::SeedRepo repo{ name, spec.url, "", "" };
740+
if (spec.artifact_applicable()) {
741+
repo.artifact = spec.artifact;
742+
repo.source = spec.source;
743+
} else if (!spec.artifact.empty()) {
744+
std::println("warning: [indices].{}: artifact source ignored "
745+
"(rev/tag/branch/path pins force git)", name);
746+
}
747+
customRepos.push_back(std::move(repo));
735748
}
736749

737750
// NOTE: the official global `xim` index is deliberately NOT injected into

‎src/manifest/toml.cppm‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,8 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
11461146
if (auto it = sub.find("tag"); it != sub.end() && it->second.is_string()) spec.tag = it->second.as_string();
11471147
if (auto it = sub.find("branch"); it != sub.end() && it->second.is_string()) spec.branch = it->second.as_string();
11481148
if (auto it = sub.find("path"); it != sub.end() && it->second.is_string()) spec.path = it->second.as_string();
1149+
if (auto it = sub.find("artifact"); it != sub.end() && it->second.is_string()) spec.artifact = it->second.as_string();
1150+
if (auto it = sub.find("source"); it != sub.end() && it->second.is_string()) spec.source = it->second.as_string();
11491151
if (spec.url.empty() && spec.path.empty()) {
11501152
return std::unexpected(error(origin, std::format(
11511153
"[indices].{} must specify 'url' or 'path'", k)));

‎src/pm/index_spec.cppm‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@ struct IndexSpec {
1818
std::string tag; // git tag
1919
std::string branch; // git branch
2020
std::filesystem::path path; // local path (takes priority over url)
21+
std::string artifact; // optional artifact source base (xlings >= 0.4.68, #269)
22+
std::string source; // optional "auto" | "artifact" | "git"
2123

2224
bool is_local() const { return !path.empty(); }
2325
bool is_pinned() const { return !rev.empty(); }
26+
// The artifact channel only tracks the latest published pointer; any
27+
// rev/tag/branch pin (and local path) therefore forces git and the
28+
// artifact declaration is ignored (with a warning at seed time).
29+
bool artifact_applicable() const {
30+
return !artifact.empty() && rev.empty() && tag.empty()
31+
&& branch.empty() && path.empty();
32+
}
2433
// R6: `name == "mcpplibs"` alone used to mean "builtin" unconditionally,
2534
// which was correct while the only way to reach that name was the
2635
// literal `[indices] mcpplibs = { url = ..., rev = ... }` pin form (still

‎tests/unit/test_manifest.cpp‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,3 +2601,30 @@ mcpp = {
26012601
EXPECT_EQ(host->modules.sources, nativeTarget->modules.sources);
26022602
EXPECT_EQ(host->buildConfig.cxxflags, nativeTarget->buildConfig.cxxflags);
26032603
}
2604+
2605+
TEST(Manifest, IndicesArtifactAndSourceParsedAndPinSuppresses) {
2606+
// #269: [indices] entries may declare an xlings >= 0.4.68 artifact
2607+
// source. rev/tag/branch pins force git (the artifact channel only
2608+
// tracks the latest pointer), which artifact_applicable() encodes.
2609+
constexpr auto src = R"(
2610+
[package]
2611+
name = "hello"
2612+
version = "0.1.0"
2613+
[language]
2614+
standard = "c++23"
2615+
[modules]
2616+
sources = ["src/**/*.cppm"]
2617+
[indices]
2618+
fast = { url = "https://example.com/i.git", artifact = "https://example.com/res/i", source = "auto" }
2619+
pinned = { url = "https://example.com/i.git", artifact = "https://example.com/res/i", rev = "abc123" }
2620+
)";
2621+
auto m = mcpp::manifest::parse_string(src);
2622+
ASSERT_TRUE(m.has_value()) << m.error().format();
2623+
auto& fast = m->indices.at("fast");
2624+
EXPECT_EQ(fast.artifact, "https://example.com/res/i");
2625+
EXPECT_EQ(fast.source, "auto");
2626+
EXPECT_TRUE(fast.artifact_applicable());
2627+
auto& pinned = m->indices.at("pinned");
2628+
EXPECT_EQ(pinned.artifact, "https://example.com/res/i");
2629+
EXPECT_FALSE(pinned.artifact_applicable());
2630+
}

0 commit comments

Comments
 (0)