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
110 changes: 21 additions & 89 deletions src/lockfile.cppm
Original file line number Diff line number Diff line change
@@ -1,103 +1,35 @@
// mcpp.lockfile — read & write mcpp.lock (TOML).
// mcpp.lockfile — backward-compat shim. The implementation has moved
// to `mcpp.pm.lock_io` as part of the package-management subsystem
// refactor (PR-R2 in
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`).
//
// All existing callers continue to use `mcpp::lockfile::Lockfile`,
// `mcpp::lockfile::load`, etc. — the aliases below resolve those to
// the new `mcpp::pm` types. Once `cli.cppm` migrates to `mcpp::pm::`
// directly this shim can be deleted.

export module mcpp.lockfile;

import std;
import mcpp.libs.toml;
export import mcpp.pm.lock_io;

export namespace mcpp::lockfile {

struct LockedPackage {
std::string name;
std::string version;
std::string source; // e.g. "mcpp-index+https://..." (M2 placeholder)
std::string hash; // "sha256:..." or "fnv1a:..."
};
using LockedPackage = mcpp::pm::LockedPackage;
using Lockfile = mcpp::pm::Lockfile;
using LockError = mcpp::pm::LockError;

struct Lockfile {
int schemaVersion = 1;
std::vector<LockedPackage> packages;
};

struct LockError {
std::string message;
};

std::expected<Lockfile, LockError> load(const std::filesystem::path& path);
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path);

std::string serialize(const Lockfile& lock);
std::string compute_hash(const Lockfile& lock);

} // namespace mcpp::lockfile

namespace mcpp::lockfile {

namespace t = mcpp::libs::toml;

std::expected<Lockfile, LockError> load(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
return Lockfile{}; // no lock yet
}
auto doc = t::parse_file(path);
if (!doc) return std::unexpected(LockError{
std::format("{}:{}:{}: {}", path.string(), doc.error().where.line,
doc.error().where.column, doc.error().message)});

Lockfile lock;
if (auto v = doc->get_int("version")) lock.schemaVersion = static_cast<int>(*v);

// [[package]] arrays are not in our minimal parser. We use [package.<name>] instead.
// Or just iterate the root looking for top-level "package" table that contains a list.
// For simplicity in M2: accept either format with top-level array of tables described
// as [package.X] sections.
auto* pkgs = doc->get_table("package");
if (pkgs) {
for (auto& [k, v] : *pkgs) {
if (!v.is_table()) continue;
auto& tt = v.as_table();
LockedPackage lp;
lp.name = k;
if (auto it = tt.find("version"); it != tt.end() && it->second.is_string()) lp.version = it->second.as_string();
if (auto it = tt.find("source"); it != tt.end() && it->second.is_string()) lp.source = it->second.as_string();
if (auto it = tt.find("hash"); it != tt.end() && it->second.is_string()) lp.hash = it->second.as_string();
lock.packages.push_back(std::move(lp));
}
}
return lock;
inline std::expected<Lockfile, LockError>
load(const std::filesystem::path& path) {
return mcpp::pm::load(path);
}

std::string serialize(const Lockfile& lock) {
std::string out;
out += "# Auto-generated by mcpp. Do not edit by hand.\n";
out += std::format("version = {}\n\n", lock.schemaVersion);
for (auto& p : lock.packages) {
out += std::format("[package.\"{}\"]\n", p.name);
out += std::format("version = {}\n", t::escape_string(p.version));
out += std::format("source = {}\n", t::escape_string(p.source));
out += std::format("hash = {}\n\n", t::escape_string(p.hash));
}
return out;
inline std::expected<void, LockError>
write(const Lockfile& lock, const std::filesystem::path& path) {
return mcpp::pm::write(lock, path);
}

std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path) {
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
std::ofstream os(path);
if (!os) return std::unexpected(LockError{std::format("cannot write '{}'", path.string())});
os << serialize(lock);
return {};
}

std::string compute_hash(const Lockfile& lock) {
// FNV-1a over the canonical serialized form.
auto s = serialize(lock);
std::uint64_t h = 0xcbf29ce484222325ull;
for (unsigned char c : s) {
h ^= c;
h *= 0x100000001b3ull;
}
return std::format("{:016x}", h);
}
inline std::string serialize(const Lockfile& lock) { return mcpp::pm::serialize(lock); }
inline std::string compute_hash(const Lockfile& lock) { return mcpp::pm::compute_hash(lock); }

} // namespace mcpp::lockfile
111 changes: 111 additions & 0 deletions src/pm/lock_io.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// mcpp.pm.lock_io — read & write mcpp.lock (TOML).
//
// Part of the package-management subsystem refactor; see
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`.
// Body unchanged from the previous `mcpp.lockfile` module — only the
// namespace + module name moved. The old `mcpp.lockfile` module is
// kept as a thin shim that re-exports the same names so existing
// callers compile unchanged. A later PR will migrate call sites to
// `mcpp::pm::` directly and the shim will be removed.

export module mcpp.pm.lock_io;

import std;
import mcpp.libs.toml;

export namespace mcpp::pm {

struct LockedPackage {
std::string name;
std::string version;
std::string source; // e.g. "mcpp-index+https://..." (M2 placeholder)
std::string hash; // "sha256:..." or "fnv1a:..."
};

struct Lockfile {
int schemaVersion = 1;
std::vector<LockedPackage> packages;
};

struct LockError {
std::string message;
};

std::expected<Lockfile, LockError> load(const std::filesystem::path& path);
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path);

std::string serialize(const Lockfile& lock);
std::string compute_hash(const Lockfile& lock);

} // namespace mcpp::pm

namespace mcpp::pm {

namespace t = mcpp::libs::toml;

std::expected<Lockfile, LockError> load(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
return Lockfile{}; // no lock yet
}
auto doc = t::parse_file(path);
if (!doc) return std::unexpected(LockError{
std::format("{}:{}:{}: {}", path.string(), doc.error().where.line,
doc.error().where.column, doc.error().message)});

Lockfile lock;
if (auto v = doc->get_int("version")) lock.schemaVersion = static_cast<int>(*v);

// [[package]] arrays are not in our minimal parser. We use [package.<name>] instead.
// Or just iterate the root looking for top-level "package" table that contains a list.
// For simplicity in M2: accept either format with top-level array of tables described
// as [package.X] sections.
auto* pkgs = doc->get_table("package");
if (pkgs) {
for (auto& [k, v] : *pkgs) {
if (!v.is_table()) continue;
auto& tt = v.as_table();
LockedPackage lp;
lp.name = k;
if (auto it = tt.find("version"); it != tt.end() && it->second.is_string()) lp.version = it->second.as_string();
if (auto it = tt.find("source"); it != tt.end() && it->second.is_string()) lp.source = it->second.as_string();
if (auto it = tt.find("hash"); it != tt.end() && it->second.is_string()) lp.hash = it->second.as_string();
lock.packages.push_back(std::move(lp));
}
}
return lock;
}

std::string serialize(const Lockfile& lock) {
std::string out;
out += "# Auto-generated by mcpp. Do not edit by hand.\n";
out += std::format("version = {}\n\n", lock.schemaVersion);
for (auto& p : lock.packages) {
out += std::format("[package.\"{}\"]\n", p.name);
out += std::format("version = {}\n", t::escape_string(p.version));
out += std::format("source = {}\n", t::escape_string(p.source));
out += std::format("hash = {}\n\n", t::escape_string(p.hash));
}
return out;
}

std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path) {
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
std::ofstream os(path);
if (!os) return std::unexpected(LockError{std::format("cannot write '{}'", path.string())});
os << serialize(lock);
return {};
}

std::string compute_hash(const Lockfile& lock) {
// FNV-1a over the canonical serialized form.
auto s = serialize(lock);
std::uint64_t h = 0xcbf29ce484222325ull;
for (unsigned char c : s) {
h ^= c;
h *= 0x100000001b3ull;
}
return std::format("{:016x}", h);
}

} // namespace mcpp::pm
Loading