Skip to content

Commit e9ed0e9

Browse files
authored
refactor(arch): cli = parse + route; domain logic relocated to owning subsystems; 0.0.54 (#129)
* docs(agents): cli modularization architecture + implementation plan * 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 * docs(agents): mark cli modularization plan executed + verification results * refactor(arch): relocate domain logic out of the cli layer (cli = parse + route) Phase 2 of the cli modularization: every implementation now lives in its owning subsystem; cli/cmd_* modules contain only argument handling, CLI-shape validation and routing (no ParsedArgs below the cli layer). mcpp.project manifest/workspace discovery (pm.commands' private copy folded in) mcpp.fetcher.progress xlings NDJSON -> ui adapters (InstallProgressHandler) mcpp.build.prepare BuildContext + prepare_build mcpp.build.execute build cache/fast-path, run_build_plan, run/test/clean mcpp.toolchain.manager toolchain list/install/set-default/remove mcpp.pm.index_ops search + index list/add/remove/update/pin/unpin mcpp.bmi_cache.ops cache walk/list/info/prune/clean + fs size helpers mcpp.scaffold.ops package templates + builtin project creation mcpp.publish.ops publish pipeline + emit xpkg mcpp.pack.ops pack orchestration mcpp.doctor doctor/why/env/explain + self init/config mcpp.cli.{common,install_ui,build} are gone; the whole cli layer is now cli.cppm (481) + seven adapters (450 lines total). Bodies moved verbatim (zero behavior change). Doc: .agents/docs/2026-06-10-cli-modularization.md * refactor(naming): replace grab-bag ops/manager module names with responsibility names mcpp.bmi_cache.ops -> mcpp.bmi_cache.maintenance mcpp.scaffold.ops -> mcpp.scaffold.create mcpp.publish.ops -> mcpp.publish.pipeline mcpp.pack.ops -> mcpp.pack.pipeline mcpp.pm.index_ops -> mcpp.pm.index_management mcpp.toolchain.manager -> mcpp.toolchain.lifecycle Pure rename (module/file/import sites); namespaces, exported symbols and behavior unchanged. * release: bump version to 0.0.54 + changelog (cli architecture refactor, #130)
1 parent a62fb8e commit e9ed0e9

25 files changed

Lines changed: 6424 additions & 5763 deletions

.agents/docs/2026-06-10-cli-modularization.md

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

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.54] — 2026-06-10
7+
8+
### 修复
9+
10+
- `mcpp new <name> --template <pkg>`:对声明了命名空间的模板包(如
11+
`mcpplibs.llmapi` 以裸名 `llmapi` 引用)现在能从描述符派生出
12+
(namespace, shortName) 坐标,正确完成 semver 解析与安装(#130)。
13+
14+
### 其他
15+
16+
- 架构重构(零行为变更):`cli.cppm` 从 6192 行精简为约 480 行的纯命令
17+
分发层;`src/cli/cmd_*` 仅保留参数解析与路由,全部领域实现下沉到属主
18+
子系统 —— `mcpp.build.{prepare,execute}`、`mcpp.toolchain.{post_install,
19+
lifecycle}``mcpp.pm.index_management``mcpp.bmi_cache.maintenance`、
20+
`mcpp.scaffold.create``mcpp.publish.pipeline``mcpp.pack.pipeline`
21+
`mcpp.doctor``mcpp.project``mcpp.fetcher.progress`
22+
设计与迁移记录见 `.agents/docs/2026-06-10-cli-modularization.md`
23+
624
## [0.0.53] — 2026-06-09
725

826
### 新增

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.53"
3+
version = "0.0.54"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/bmi_cache/maintenance.cppm

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

0 commit comments

Comments
 (0)