diff --git a/src/lockfile.cppm b/src/lockfile.cppm index 578f639..323fe75 100644 --- a/src/lockfile.cppm +++ b/src/lockfile.cppm @@ -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 packages; -}; - -struct LockError { - std::string message; -}; - -std::expected load(const std::filesystem::path& path); -std::expected 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 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(*v); - - // [[package]] arrays are not in our minimal parser. We use [package.] 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 +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 +write(const Lockfile& lock, const std::filesystem::path& path) { + return mcpp::pm::write(lock, path); } -std::expected 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 diff --git a/src/pm/lock_io.cppm b/src/pm/lock_io.cppm new file mode 100644 index 0000000..23c4e53 --- /dev/null +++ b/src/pm/lock_io.cppm @@ -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 packages; +}; + +struct LockError { + std::string message; +}; + +std::expected load(const std::filesystem::path& path); +std::expected 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 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(*v); + + // [[package]] arrays are not in our minimal parser. We use [package.] 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 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