Skip to content

Commit b42000d

Browse files
committed
refactor(pm): move lockfile.cppm into pm/lock_io.cppm (PR-R2)
Step two of the package-management subsystem refactor described in `.agents/docs/2026-05-08-pm-subsystem-architecture.md`. Strictly zero behavior change. * New module `mcpp.pm.lock_io` (`src/pm/lock_io.cppm`) carries the full lockfile read/write/serialize/hash implementation under the `mcpp::pm` namespace. * The existing `mcpp.lockfile` module (`src/lockfile.cppm`) is now a thin shim: re-exports `mcpp.pm.lock_io` and provides `using` aliases + inline forwarders so `mcpp::lockfile::Lockfile`, `::load`, ::write`, ... continue to resolve unchanged. The shim disappears once `cli.cppm` migrates to the `mcpp::pm::` qualified names directly. Verification: * `mcpp build` compiles unchanged. * `mcpp test` — 9/9 unit binaries pass. * e2e subset (02 / 09 / 12 / 23 / 27) all pass.
1 parent b0154f0 commit b42000d

2 files changed

Lines changed: 132 additions & 89 deletions

File tree

src/lockfile.cppm

Lines changed: 21 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,35 @@
1-
// mcpp.lockfile — read & write mcpp.lock (TOML).
1+
// mcpp.lockfile — backward-compat shim. The implementation has moved
2+
// to `mcpp.pm.lock_io` as part of the package-management subsystem
3+
// refactor (PR-R2 in
4+
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`).
5+
//
6+
// All existing callers continue to use `mcpp::lockfile::Lockfile`,
7+
// `mcpp::lockfile::load`, etc. — the aliases below resolve those to
8+
// the new `mcpp::pm` types. Once `cli.cppm` migrates to `mcpp::pm::`
9+
// directly this shim can be deleted.
210

311
export module mcpp.lockfile;
412

513
import std;
6-
import mcpp.libs.toml;
14+
export import mcpp.pm.lock_io;
715

816
export namespace mcpp::lockfile {
917

10-
struct LockedPackage {
11-
std::string name;
12-
std::string version;
13-
std::string source; // e.g. "mcpp-index+https://..." (M2 placeholder)
14-
std::string hash; // "sha256:..." or "fnv1a:..."
15-
};
18+
using LockedPackage = mcpp::pm::LockedPackage;
19+
using Lockfile = mcpp::pm::Lockfile;
20+
using LockError = mcpp::pm::LockError;
1621

17-
struct Lockfile {
18-
int schemaVersion = 1;
19-
std::vector<LockedPackage> packages;
20-
};
21-
22-
struct LockError {
23-
std::string message;
24-
};
25-
26-
std::expected<Lockfile, LockError> load(const std::filesystem::path& path);
27-
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path);
28-
29-
std::string serialize(const Lockfile& lock);
30-
std::string compute_hash(const Lockfile& lock);
31-
32-
} // namespace mcpp::lockfile
33-
34-
namespace mcpp::lockfile {
35-
36-
namespace t = mcpp::libs::toml;
37-
38-
std::expected<Lockfile, LockError> load(const std::filesystem::path& path) {
39-
if (!std::filesystem::exists(path)) {
40-
return Lockfile{}; // no lock yet
41-
}
42-
auto doc = t::parse_file(path);
43-
if (!doc) return std::unexpected(LockError{
44-
std::format("{}:{}:{}: {}", path.string(), doc.error().where.line,
45-
doc.error().where.column, doc.error().message)});
46-
47-
Lockfile lock;
48-
if (auto v = doc->get_int("version")) lock.schemaVersion = static_cast<int>(*v);
49-
50-
// [[package]] arrays are not in our minimal parser. We use [package.<name>] instead.
51-
// Or just iterate the root looking for top-level "package" table that contains a list.
52-
// For simplicity in M2: accept either format with top-level array of tables described
53-
// as [package.X] sections.
54-
auto* pkgs = doc->get_table("package");
55-
if (pkgs) {
56-
for (auto& [k, v] : *pkgs) {
57-
if (!v.is_table()) continue;
58-
auto& tt = v.as_table();
59-
LockedPackage lp;
60-
lp.name = k;
61-
if (auto it = tt.find("version"); it != tt.end() && it->second.is_string()) lp.version = it->second.as_string();
62-
if (auto it = tt.find("source"); it != tt.end() && it->second.is_string()) lp.source = it->second.as_string();
63-
if (auto it = tt.find("hash"); it != tt.end() && it->second.is_string()) lp.hash = it->second.as_string();
64-
lock.packages.push_back(std::move(lp));
65-
}
66-
}
67-
return lock;
22+
inline std::expected<Lockfile, LockError>
23+
load(const std::filesystem::path& path) {
24+
return mcpp::pm::load(path);
6825
}
6926

70-
std::string serialize(const Lockfile& lock) {
71-
std::string out;
72-
out += "# Auto-generated by mcpp. Do not edit by hand.\n";
73-
out += std::format("version = {}\n\n", lock.schemaVersion);
74-
for (auto& p : lock.packages) {
75-
out += std::format("[package.\"{}\"]\n", p.name);
76-
out += std::format("version = {}\n", t::escape_string(p.version));
77-
out += std::format("source = {}\n", t::escape_string(p.source));
78-
out += std::format("hash = {}\n\n", t::escape_string(p.hash));
79-
}
80-
return out;
27+
inline std::expected<void, LockError>
28+
write(const Lockfile& lock, const std::filesystem::path& path) {
29+
return mcpp::pm::write(lock, path);
8130
}
8231

83-
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path) {
84-
std::error_code ec;
85-
std::filesystem::create_directories(path.parent_path(), ec);
86-
std::ofstream os(path);
87-
if (!os) return std::unexpected(LockError{std::format("cannot write '{}'", path.string())});
88-
os << serialize(lock);
89-
return {};
90-
}
91-
92-
std::string compute_hash(const Lockfile& lock) {
93-
// FNV-1a over the canonical serialized form.
94-
auto s = serialize(lock);
95-
std::uint64_t h = 0xcbf29ce484222325ull;
96-
for (unsigned char c : s) {
97-
h ^= c;
98-
h *= 0x100000001b3ull;
99-
}
100-
return std::format("{:016x}", h);
101-
}
32+
inline std::string serialize(const Lockfile& lock) { return mcpp::pm::serialize(lock); }
33+
inline std::string compute_hash(const Lockfile& lock) { return mcpp::pm::compute_hash(lock); }
10234

10335
} // namespace mcpp::lockfile

src/pm/lock_io.cppm

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// mcpp.pm.lock_io — read & write mcpp.lock (TOML).
2+
//
3+
// Part of the package-management subsystem refactor; see
4+
// `.agents/docs/2026-05-08-pm-subsystem-architecture.md`.
5+
// Body unchanged from the previous `mcpp.lockfile` module — only the
6+
// namespace + module name moved. The old `mcpp.lockfile` module is
7+
// kept as a thin shim that re-exports the same names so existing
8+
// callers compile unchanged. A later PR will migrate call sites to
9+
// `mcpp::pm::` directly and the shim will be removed.
10+
11+
export module mcpp.pm.lock_io;
12+
13+
import std;
14+
import mcpp.libs.toml;
15+
16+
export namespace mcpp::pm {
17+
18+
struct LockedPackage {
19+
std::string name;
20+
std::string version;
21+
std::string source; // e.g. "mcpp-index+https://..." (M2 placeholder)
22+
std::string hash; // "sha256:..." or "fnv1a:..."
23+
};
24+
25+
struct Lockfile {
26+
int schemaVersion = 1;
27+
std::vector<LockedPackage> packages;
28+
};
29+
30+
struct LockError {
31+
std::string message;
32+
};
33+
34+
std::expected<Lockfile, LockError> load(const std::filesystem::path& path);
35+
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path);
36+
37+
std::string serialize(const Lockfile& lock);
38+
std::string compute_hash(const Lockfile& lock);
39+
40+
} // namespace mcpp::pm
41+
42+
namespace mcpp::pm {
43+
44+
namespace t = mcpp::libs::toml;
45+
46+
std::expected<Lockfile, LockError> load(const std::filesystem::path& path) {
47+
if (!std::filesystem::exists(path)) {
48+
return Lockfile{}; // no lock yet
49+
}
50+
auto doc = t::parse_file(path);
51+
if (!doc) return std::unexpected(LockError{
52+
std::format("{}:{}:{}: {}", path.string(), doc.error().where.line,
53+
doc.error().where.column, doc.error().message)});
54+
55+
Lockfile lock;
56+
if (auto v = doc->get_int("version")) lock.schemaVersion = static_cast<int>(*v);
57+
58+
// [[package]] arrays are not in our minimal parser. We use [package.<name>] instead.
59+
// Or just iterate the root looking for top-level "package" table that contains a list.
60+
// For simplicity in M2: accept either format with top-level array of tables described
61+
// as [package.X] sections.
62+
auto* pkgs = doc->get_table("package");
63+
if (pkgs) {
64+
for (auto& [k, v] : *pkgs) {
65+
if (!v.is_table()) continue;
66+
auto& tt = v.as_table();
67+
LockedPackage lp;
68+
lp.name = k;
69+
if (auto it = tt.find("version"); it != tt.end() && it->second.is_string()) lp.version = it->second.as_string();
70+
if (auto it = tt.find("source"); it != tt.end() && it->second.is_string()) lp.source = it->second.as_string();
71+
if (auto it = tt.find("hash"); it != tt.end() && it->second.is_string()) lp.hash = it->second.as_string();
72+
lock.packages.push_back(std::move(lp));
73+
}
74+
}
75+
return lock;
76+
}
77+
78+
std::string serialize(const Lockfile& lock) {
79+
std::string out;
80+
out += "# Auto-generated by mcpp. Do not edit by hand.\n";
81+
out += std::format("version = {}\n\n", lock.schemaVersion);
82+
for (auto& p : lock.packages) {
83+
out += std::format("[package.\"{}\"]\n", p.name);
84+
out += std::format("version = {}\n", t::escape_string(p.version));
85+
out += std::format("source = {}\n", t::escape_string(p.source));
86+
out += std::format("hash = {}\n\n", t::escape_string(p.hash));
87+
}
88+
return out;
89+
}
90+
91+
std::expected<void, LockError> write(const Lockfile& lock, const std::filesystem::path& path) {
92+
std::error_code ec;
93+
std::filesystem::create_directories(path.parent_path(), ec);
94+
std::ofstream os(path);
95+
if (!os) return std::unexpected(LockError{std::format("cannot write '{}'", path.string())});
96+
os << serialize(lock);
97+
return {};
98+
}
99+
100+
std::string compute_hash(const Lockfile& lock) {
101+
// FNV-1a over the canonical serialized form.
102+
auto s = serialize(lock);
103+
std::uint64_t h = 0xcbf29ce484222325ull;
104+
for (unsigned char c : s) {
105+
h ^= c;
106+
h *= 0x100000001b3ull;
107+
}
108+
return std::format("{:016x}", h);
109+
}
110+
111+
} // namespace mcpp::pm

0 commit comments

Comments
 (0)