Skip to content

Commit 20d49c7

Browse files
committed
refactor(cli): split cli.cppm into focused modules (dispatcher-only cli)
cli.cppm (6192 lines) -> thin dispatcher (481 lines). All command implementations move into dedicated modules, byte-identical bodies: mcpp.cli.common project/workspace discovery + fs utils mcpp.cli.install_ui xlings NDJSON -> ui download-progress adapters mcpp.toolchain.post_install patchelf/specs/cfg payload fixups mcpp.cli.build BuildContext + prepare_build (build core) mcpp.cli.cmd_build build/run/test/clean/dyndep + fast-path cache mcpp.cli.cmd_new new + package templates mcpp.cli.cmd_registry search + index management mcpp.cli.cmd_cache cache list/info/prune/clean mcpp.cli.cmd_toolchain toolchain install/list/default/remove mcpp.cli.cmd_publish publish/pack/emit-xpkg mcpp.cli.cmd_self self */doctor/why/env/explain Zero behavior change (same statement order, messages, exit codes). Architecture + plan: .agents/docs/2026-06-10-cli-modularization.md
1 parent ee3e909 commit 20d49c7

12 files changed

Lines changed: 5993 additions & 5729 deletions

src/cli.cppm

Lines changed: 18 additions & 5729 deletions
Large diffs are not rendered by default.

src/cli/build.cppm

Lines changed: 2492 additions & 0 deletions
Large diffs are not rendered by default.

src/cli/cmd_build.cppm

Lines changed: 686 additions & 0 deletions
Large diffs are not rendered by default.

src/cli/cmd_cache.cppm

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// mcpp.cli.cmd_cache — BMI cache inspection / pruning commands
2+
//
3+
// Extracted verbatim from cli.cppm (cli modularization, see
4+
// .agents/docs/2026-06-10-cli-modularization.md). Zero behavior change:
5+
// bodies are byte-identical moves; only the surrounding module/namespace
6+
// changed (mcpp::cli::detail -> mcpp::cli).
7+
8+
module;
9+
#include <cstdio>
10+
#include <cstdlib>
11+
12+
export module mcpp.cli.cmd_cache;
13+
14+
import std;
15+
import mcpplibs.cmdline;
16+
import mcpp.cli.common;
17+
import mcpp.toolchain.stdmod;
18+
import mcpp.ui;
19+
20+
namespace mcpp::cli {
21+
22+
// ─── M4 #4: mcpp cache list / prune / clean / info ──────────────────────
23+
struct CacheEntry {
24+
std::filesystem::path dir;
25+
std::string fingerprint;
26+
std::string pkgAtVer; // "<idx>/<pkg>@<ver>"
27+
std::uintmax_t size = 0;
28+
std::filesystem::file_time_type lastWrite{};
29+
std::size_t fileCount = 0;
30+
};
31+
32+
static std::vector<CacheEntry> walk_cache_entries() {
33+
std::vector<CacheEntry> entries;
34+
auto bmi = mcpp::toolchain::default_cache_root();
35+
std::error_code ec;
36+
if (!std::filesystem::exists(bmi, ec)) return entries;
37+
38+
for (auto& fpEntry : std::filesystem::directory_iterator(bmi, ec)) {
39+
auto fpDir = fpEntry.path();
40+
auto depsDir = fpDir / "deps";
41+
if (!std::filesystem::exists(depsDir, ec)) continue;
42+
for (auto& idxEntry : std::filesystem::directory_iterator(depsDir, ec)) {
43+
for (auto& pkgEntry : std::filesystem::directory_iterator(idxEntry.path(), ec)) {
44+
CacheEntry e;
45+
e.dir = pkgEntry.path();
46+
e.fingerprint = fpDir.filename().string();
47+
e.pkgAtVer = idxEntry.path().filename().string()
48+
+ "/" + pkgEntry.path().filename().string();
49+
e.size = dir_size(e.dir);
50+
e.lastWrite = std::filesystem::last_write_time(e.dir, ec);
51+
for (auto& _ : std::filesystem::recursive_directory_iterator(e.dir, ec)) {
52+
if (!ec) ++e.fileCount;
53+
}
54+
entries.push_back(std::move(e));
55+
}
56+
}
57+
}
58+
return entries;
59+
}
60+
61+
static std::string format_age(std::filesystem::file_time_type t) {
62+
auto now = std::chrono::file_clock::now();
63+
auto diff = std::chrono::duration_cast<std::chrono::seconds>(now - t).count();
64+
if (diff < 60) return std::format("{}s ago", diff);
65+
if (diff < 3600) return std::format("{}m ago", diff / 60);
66+
if (diff < 86400) return std::format("{}h ago", diff / 3600);
67+
return std::format("{}d ago", diff / 86400);
68+
}
69+
70+
// `mcpp cache` is dispatched at the App level — list / info / prune / clean
71+
// each get their own action lambda invoking one of these helpers.
72+
73+
export int cmd_cache_list(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
74+
auto entries = walk_cache_entries();
75+
if (entries.empty()) {
76+
std::println("(BMI cache is empty)");
77+
return 0;
78+
}
79+
std::println("{:<18} {:>10} {:>14} {}",
80+
"fingerprint", "size", "last accessed", "package");
81+
for (auto& e : entries) {
82+
auto fp = e.fingerprint.size() > 16
83+
? e.fingerprint.substr(0, 16) : e.fingerprint;
84+
std::println("{:<18} {:>10} {:>14} {}",
85+
fp, human_bytes(e.size), format_age(e.lastWrite), e.pkgAtVer);
86+
}
87+
return 0;
88+
}
89+
90+
export int cmd_cache_info(const mcpplibs::cmdline::ParsedArgs& parsed) {
91+
std::string needle = parsed.positional(0);
92+
if (needle.empty()) {
93+
mcpp::ui::error("usage: mcpp cache info <pkg>@<ver>");
94+
return 2;
95+
}
96+
auto entries = walk_cache_entries();
97+
for (auto& e : entries) {
98+
if (e.pkgAtVer.ends_with(needle)) {
99+
std::println("dir = {}", e.dir.string());
100+
std::println("fingerprint = {}", e.fingerprint);
101+
std::println("package = {}", e.pkgAtVer);
102+
std::println("size = {}", human_bytes(e.size));
103+
std::println("file count = {}", e.fileCount);
104+
std::println("last write = {}", format_age(e.lastWrite));
105+
return 0;
106+
}
107+
}
108+
std::println("no cache entry matching '{}'", needle);
109+
return 1;
110+
}
111+
112+
export int cmd_cache_prune(const mcpplibs::cmdline::ParsedArgs& parsed) {
113+
std::string v = parsed.option_or_empty("older-than").value();
114+
if (v.empty()) {
115+
mcpp::ui::error("`mcpp cache prune` requires --older-than <N>{s,m,h,d}");
116+
return 2;
117+
}
118+
char unit = v.back();
119+
long long n = 0;
120+
try { n = std::stoll(v.substr(0, v.size() - 1)); }
121+
catch (...) { mcpp::ui::error(std::format("bad --older-than value '{}'", v)); return 2; }
122+
std::chrono::seconds threshold{0};
123+
if (unit == 's') threshold = std::chrono::seconds(n);
124+
else if (unit == 'm') threshold = std::chrono::seconds(n * 60);
125+
else if (unit == 'h') threshold = std::chrono::seconds(n * 3600);
126+
else if (unit == 'd') threshold = std::chrono::seconds(n * 86400);
127+
else { mcpp::ui::error(std::format("bad time unit '{}': use s/m/h/d", unit)); return 2; }
128+
auto cutoff = std::chrono::file_clock::now() - threshold;
129+
auto entries = walk_cache_entries();
130+
int removed = 0;
131+
std::uintmax_t freed = 0;
132+
for (auto& e : entries) {
133+
if (e.lastWrite < cutoff) {
134+
std::error_code ec;
135+
std::filesystem::remove_all(e.dir, ec);
136+
if (!ec) {
137+
++removed;
138+
freed += e.size;
139+
mcpp::ui::status("Pruned",
140+
std::format("{} ({})", e.pkgAtVer, human_bytes(e.size)));
141+
}
142+
}
143+
}
144+
std::println("");
145+
std::println("Pruned {} entries, freed {}", removed, human_bytes(freed));
146+
return 0;
147+
}
148+
149+
export int cmd_cache_clean(const mcpplibs::cmdline::ParsedArgs& /*parsed*/) {
150+
auto bmi = mcpp::toolchain::default_cache_root();
151+
std::error_code ec;
152+
std::filesystem::remove_all(bmi / "deps", ec); // deps only; preserve std.gcm
153+
if (std::filesystem::exists(bmi)) {
154+
for (auto& f : std::filesystem::directory_iterator(bmi, ec)) {
155+
auto deps = f.path() / "deps";
156+
std::filesystem::remove_all(deps, ec);
157+
}
158+
}
159+
std::println("Cleaned all dep BMI cache entries (std.gcm preserved)");
160+
return 0;
161+
}
162+
163+
} // namespace mcpp::cli

0 commit comments

Comments
 (0)