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
84 changes: 7 additions & 77 deletions src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import mcpp.publish.xpkg_emit;
import mcpp.pack;
import mcpp.config;
import mcpp.fetcher;
import mcpp.pm.resolver; // PR-R4: extracted from cli.cppm
import mcpp.ui;
import mcpp.bmi_cache;
import mcpp.dyndep;
Expand Down Expand Up @@ -665,81 +666,10 @@ void fixup_gcc_specs(const std::filesystem::path& gccPkgRoot,
// it starts with one of `^~><=` or contains a comma (multi-part), or is `*`
// or empty. Bare `1.2.3` is treated as exact for back-compat with pre-SemVer
// pinning workflows; users opt into resolution by writing `^1.2.3` etc.
bool is_version_constraint(std::string_view v) {
if (v.empty()) return true;
if (v == "*") return true;
char c = v.front();
if (c == '^' || c == '~' || c == '>' || c == '<' || c == '=') return true;
if (v.find(',') != std::string_view::npos) return true;
return false;
}

// xpkg.lua's xpm.<key> uses these names. (Distinct from kCurrentPlatform
// which is the [toolchain] table key — "macos" vs "macosx".)
constexpr std::string_view kXpkgPlatform =
#if defined(__linux__)
"linux";
#elif defined(__APPLE__)
"macosx";
#elif defined(_WIN32)
"windows";
#else
"linux";
#endif

// Resolve a SemVer constraint against the index entry's available versions.
// Returns the chosen exact version string, or an error message.
std::expected<std::string, std::string>
resolve_semver(std::string_view name,
std::string_view constraint,
mcpp::fetcher::Fetcher& fetcher)
{
namespace vr = mcpp::version_req;

auto luaContent = fetcher.read_xpkg_lua(name);
if (!luaContent) {
return std::unexpected(std::format(
"dependency '{}' has SemVer constraint '{}' but the index entry "
"isn't cloned locally yet — run `mcpp index update` first",
name, constraint));
}

auto req = vr::parse_req(constraint);
if (!req) {
return std::unexpected(std::format(
"dependency '{}': invalid version constraint '{}': {}",
name, constraint, req.error()));
}

auto rawVersions = mcpp::manifest::list_xpkg_versions(*luaContent, kXpkgPlatform);
if (rawVersions.empty()) {
return std::unexpected(std::format(
"dependency '{}': index entry has no versions for platform '{}'",
name, kXpkgPlatform));
}

std::vector<vr::Version> parsed;
parsed.reserve(rawVersions.size());
for (auto& s : rawVersions) {
auto v = vr::parse_version(s);
if (!v) continue; // ignore unparseable entries
parsed.push_back(*v);
}
if (parsed.empty()) {
return std::unexpected(std::format(
"dependency '{}': no valid versions in index", name));
}

auto idx = vr::choose(*req, parsed);
if (!idx) {
std::string avail;
for (auto& s : rawVersions) { if (!avail.empty()) avail += ", "; avail += s; }
return std::unexpected(std::format(
"dependency '{}': constraint '{}' matches none of: [{}]",
name, constraint, avail));
}
return parsed[*idx].str();
}
// `is_version_constraint`, `kXpkgPlatform` and `resolve_semver` have moved
// to `mcpp.pm.resolver` (PR-R4 — see
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`). Call sites
// below reference the `mcpp::pm::` qualified names directly.

// --- Commands ---

Expand Down Expand Up @@ -1121,11 +1051,11 @@ prepare_build(bool print_fingerprint,
-> std::expected<void, std::string>
{
if (s.isPath() || s.isGit()) return {};
if (!is_version_constraint(s.version)) return {};
if (!mcpp::pm::is_version_constraint(s.version)) return {};
auto cfg = get_cfg();
if (!cfg) return std::unexpected(cfg.error());
mcpp::fetcher::Fetcher fetcher(**cfg);
auto resolved = resolve_semver(depName, s.version, fetcher);
auto resolved = mcpp::pm::resolve_semver(depName, s.version, fetcher);
if (!resolved) return std::unexpected(resolved.error());
mcpp::ui::info("Resolved",
std::format("{} {} → v{}", depName, s.version, *resolved));
Expand Down
102 changes: 102 additions & 0 deletions src/pm/resolver.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// mcpp.pm.resolver — turn a SemVer constraint into a concrete version,
// using the package's xpkg lua descriptor as the version inventory.
//
// Part of the package-management subsystem refactor (PR-R4 in
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`). Strictly
// pulled out of `cli.cppm` with no behavior change; the same
// signatures, the same error strings, the same platform key picking.

export module mcpp.pm.resolver;

import std;
import mcpp.manifest;
import mcpp.pm.package_fetcher;
import mcpp.version_req;

export namespace mcpp::pm {

// xpkg.lua's `xpm.<key>` uses these names. (Distinct from
// `kCurrentPlatform` in cli.cppm, which is the [toolchain] table key —
// "macos" vs "macosx".)
inline constexpr std::string_view kXpkgPlatform =
#if defined(__linux__)
"linux";
#elif defined(__APPLE__)
"macosx";
#elif defined(_WIN32)
"windows";
#else
"linux";
#endif

// Returns true if `v` is a SemVer constraint (caret, tilde, range, glob,
// `*`, or empty) rather than a literal exact version. Empty counts as
// "constraint" so callers re-resolve via the index — bare `1.2.3` is
// treated as exact for back-compat with pre-SemVer pinning workflows;
// users opt into resolution by writing `^1.2.3` etc.
inline bool is_version_constraint(std::string_view v) {
if (v.empty()) return true;
if (v == "*") return true;
char c = v.front();
if (c == '^' || c == '~' || c == '>' || c == '<' || c == '=') return true;
if (v.find(',') != std::string_view::npos) return true;
return false;
}

// Resolve a SemVer constraint against the index entry's available
// versions. Returns the chosen exact version string, or an error
// message. The fetcher is used to read the lua descriptor for the
// requested package name.
inline std::expected<std::string, std::string>
resolve_semver(std::string_view name,
std::string_view constraint,
mcpp::pm::Fetcher& fetcher)
{
namespace vr = mcpp::version_req;

auto luaContent = fetcher.read_xpkg_lua(name);
if (!luaContent) {
return std::unexpected(std::format(
"dependency '{}' has SemVer constraint '{}' but the index entry "
"isn't cloned locally yet — run `mcpp index update` first",
name, constraint));
}

auto req = vr::parse_req(constraint);
if (!req) {
return std::unexpected(std::format(
"dependency '{}': invalid version constraint '{}': {}",
name, constraint, req.error()));
}

auto rawVersions = mcpp::manifest::list_xpkg_versions(*luaContent, kXpkgPlatform);
if (rawVersions.empty()) {
return std::unexpected(std::format(
"dependency '{}': index entry has no versions for platform '{}'",
name, kXpkgPlatform));
}

std::vector<vr::Version> parsed;
parsed.reserve(rawVersions.size());
for (auto& s : rawVersions) {
auto v = vr::parse_version(s);
if (!v) continue; // ignore unparseable entries
parsed.push_back(*v);
}
if (parsed.empty()) {
return std::unexpected(std::format(
"dependency '{}': no valid versions in index", name));
}

auto idx = vr::choose(*req, parsed);
if (!idx) {
std::string avail;
for (auto& s : rawVersions) { if (!avail.empty()) avail += ", "; avail += s; }
return std::unexpected(std::format(
"dependency '{}': constraint '{}' matches none of: [{}]",
name, constraint, avail));
}
return parsed[*idx].str();
}

} // namespace mcpp::pm
Loading