Repository navigation
Expand file tree
/
Copy pathbuild.mcpp
More file actions
167 lines (157 loc) · 7.24 KB
/
Copy pathbuild.mcpp
File metadata and controls
167 lines (157 loc) · 7.24 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
163
164
165
166
167
// boost-module (M8): dynamic aggregate module `import boost;`.
//
// Replaces the static src/boost.cppm (deleted). Runs BEFORE the modgraph scan
// (prepare G2) and after feature activation, so it can see the active feature
// set (MCPP_FEATURE_<NAME>) and emit a boost.cppm that re-exports exactly the
// currently active libraries — `import boost;` then matches any features
// combination. See .agents/docs/2026-08-15-m8-mcpp-features-infra.md.
//
// Reads scripts/features.lst (generated by scripts/gen_features.py): the full
// library list, one module name per line. For each lib whose feature is active
// it emits `export import boost.<lib>;`. Zero active libs → a valid empty shell
// (`export module boost;` with no imports).
//
// The file is written into MCPP_OUT_DIR (mcpp::out_dir(), always absolute) and
// selected with mcpp::source() using that ABSOLUTE path — the pattern docs/30
// §"A library that is also built standalone: emit an absolute path" prescribes
// for a package that is built both as a root and as a dependency. The package
// root is shared across consumers (and may be read-only), so a generated file
// must NOT live there: a fixed location like <root>/generated/boost.cppm is
// written concurrently by every build with a different feature set — a race.
// MCPP_OUT_DIR is per build tree (target/.build-mcpp/out for the root, a
// per-consumer deps/ dir for a dependency), so concurrent builds cannot
// collide. mcpp.toml declares `[lib].path = "target/.build-mcpp/out/boost.cppm"`
// to satisfy validate's lib-root check in the root role (build.mcpp runs before
// the scan/validate; a dependency's manifest is not lib-root-validated).
#include <cstdio>
#include <fstream>
#include <map>
#include <string>
#include <string_view>
#include <vector>
#include <filesystem>
import mcpp;
int main()
{
namespace fs = std::filesystem;
const std::string manifestDir = mcpp::manifest_dir();
if (manifestDir.empty()) {
std::fputs("build.mcpp: MCPP_MANIFEST_DIR not set\n", stderr);
return 1;
}
// Library list — gen_features.py regenerates; rerun build.mcpp when it changes.
mcpp::rerun_if_changed("scripts/features.lst");
std::ifstream lst("scripts/features.lst");
std::vector<std::string> libs;
std::string line;
while (std::getline(lst, line)) {
if (line.empty() || line[0] == '#') continue;
libs.push_back(line);
}
// Profile features (mcpp `backend = "<axis>-<impl>"` sugar; scripts/
// profiles.lst, generated by scripts/gen_features.py): each line is
// `<axis>\t<feature>\t<arch>`. Two profiles on one axis set contradictory
// macros (e.g. BOOST_LOG_USE_AVX2 vs BOOST_LOG_USE_SSSE3) and an arch-gated
// profile (e.g. -mavx2) is invalid off that arch. Refuse before the module
// graph is scanned so the diagnostic names the features (M13, 2026-09-21).
mcpp::rerun_if_changed("scripts/profiles.lst");
std::ifstream plst("scripts/profiles.lst");
std::map<std::string, std::vector<std::string>> active_profiles;
std::string pline;
while (std::getline(plst, pline)) {
if (pline.empty() || pline[0] == '#') continue;
const auto t1 = pline.find('\t');
if (t1 == std::string::npos) continue;
const auto t2 = pline.find('\t', t1 + 1);
const std::string axis = pline.substr(0, t1);
const std::string feat = pline.substr(
t1 + 1, t2 == std::string::npos ? std::string::npos : t2 - t1 - 1);
const std::string arch = t2 == std::string::npos
? std::string()
: pline.substr(t2 + 1);
if (!mcpp::has_feature(feat.c_str())) continue;
if (!arch.empty() && arch != mcpp::target_arch()) {
std::fprintf(stderr,
"build.mcpp: profile feature '%s' is only valid on "
"arch '%s' (target is '%s')\n",
feat.c_str(), arch.c_str(), mcpp::target_arch());
return 1;
}
active_profiles[axis].push_back(feat);
}
for (auto const& [axis, feats] : active_profiles) {
if (feats.size() <= 1) continue;
std::fprintf(stderr,
"build.mcpp: conflicting profile features on axis '%s':",
axis.c_str());
for (auto const& f : feats) std::fprintf(stderr, " %s", f.c_str());
std::fputs("\n activate at most one profile per axis.\n", stderr);
return 1;
}
std::string body = "// generated by build.mcpp (M8) — import boost; re-exports the active feature set\n";
body += "export module boost;\n\n";
// M9: gcc 16.1.0 segfaults (ICE in cc1plus) while writing the BMI of an
// aggregate module that re-exports boost.sort (export import boost.sort; —
// only in the aggregate; direct `import boost.sort;` consumers are fine).
// Skip it on gcc (clang/MSVC unaffected); boost.sort stays importable
// directly. Verified: all 85 features except sort build, adding sort ICEs.
#if defined(__GNUC__) && !defined(__clang__)
static const char* const kGccAggregateSkip[] = {"sort"};
#endif
// C1 (2026-09-06): module-less features — compiled include-only libraries
// (boost_common.LIBS_COMPILED_INCLUDE_ONLY). They ship library TUs under
// their feature but have no `export module boost.<name>;` interface, so
// there is no CMI to re-export; emitting `export import boost.log;` would
// fail to build. Consumers #include the upstream headers instead.
static const char* const kModuleLessFeatures[] = {"log",
"unit_test_framework"};
for (auto const& lib : libs) {
bool module_less = false;
for (char const* s : kModuleLessFeatures) {
if (lib == s) {
module_less = true;
break;
}
}
if (module_less) continue;
if (mcpp::has_feature(lib.c_str())) {
#if defined(__GNUC__) && !defined(__clang__)
bool skip = false;
for (char const* s : kGccAggregateSkip) {
if (lib == s) {
skip = true;
break;
}
}
if (skip) continue;
#endif
body += "export import boost." + lib + ";\n";
}
}
const fs::path out = fs::path(mcpp::out_dir()) / "boost.cppm";
std::error_code ec;
fs::create_directories(out.parent_path(), ec);
std::string existing;
{
std::ifstream in(out);
if (in) {
std::string s((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
existing = std::move(s);
}
}
// mtime-stable write: touch only when content actually changed, so an
// unchanged feature set does not trigger a rebuild of the module.
if (existing != body) {
std::ofstream os(out, std::ios::trunc);
os << body;
if (!os) {
std::fprintf(stderr, "build.mcpp: cannot write %s\n", out.string().c_str());
return 1;
}
std::fprintf(stderr, "build.mcpp: wrote %s (%zu import%s)\n",
out.string().c_str(), libs.size(), libs.size() == 1 ? "" : "s");
}
mcpp::source(out.string().c_str());
return 0;
}