-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlock_io.cppm
More file actions
162 lines (136 loc) · 5.96 KB
/
lock_io.cppm
File metadata and controls
162 lines (136 loc) · 5.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// 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.
//
// v2 schema (0.0.14+): adds [indices.<name>] sections with url + rev,
// and `namespace` field to [package.*] entries. v1 files are migrated
// on load: all packages default to namespace="mcpplibs".
export module mcpp.pm.lock_io;
import std;
import mcpp.libs.toml;
export namespace mcpp::pm {
struct LockedIndex {
std::string name; // index name (key in [indices])
std::string url; // git URL
std::string rev; // locked commit sha (40 chars)
};
struct LockedPackage {
std::string name;
std::string namespace_; // package namespace (v2+); empty = independent root
std::string version;
std::string source; // e.g. "index+mcpplibs@abc123def..."
std::string hash; // "sha256:..." or "fnv1a:..."
};
struct Lockfile {
int schemaVersion = 2;
std::vector<LockedIndex> indices;
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;
int fileVersion = 1;
if (auto v = doc->get_int("version")) fileVersion = static_cast<int>(*v);
// Always upgrade to v2 in memory.
lock.schemaVersion = 2;
// Parse [indices.<name>] sections (v2+).
auto* idxTbl = doc->get_table("indices");
if (idxTbl) {
for (auto& [k, v] : *idxTbl) {
if (!v.is_table()) continue;
auto& tt = v.as_table();
LockedIndex li;
li.name = k;
if (auto it = tt.find("url"); it != tt.end() && it->second.is_string()) li.url = it->second.as_string();
if (auto it = tt.find("rev"); it != tt.end() && it->second.is_string()) li.rev = it->second.as_string();
lock.indices.push_back(std::move(li));
}
}
// [[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("namespace"); it != tt.end() && it->second.is_string()) lp.namespace_ = it->second.as_string();
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();
// v1 → v2 migration: default namespace to "mcpplibs".
if (fileVersion < 2 && lp.namespace_.empty()) {
lp.namespace_ = "mcpplibs";
}
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", lock.schemaVersion);
// Write [indices.<name>] sections.
for (auto& idx : lock.indices) {
out += std::format("\n[indices.\"{}\"]\n", idx.name);
out += std::format("url = {}\n", t::escape_string(idx.url));
out += std::format("rev = {}\n", t::escape_string(idx.rev));
}
// Blank line before packages if we had indices or just after version.
if (!lock.packages.empty()) out += "\n";
for (auto& p : lock.packages) {
out += std::format("[package.\"{}\"]\n", p.name);
if (!p.namespace_.empty()) {
out += std::format("namespace = {}\n", t::escape_string(p.namespace_));
}
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