-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathplan.cppm
More file actions
246 lines (217 loc) · 10.3 KB
/
Copy pathplan.cppm
File metadata and controls
246 lines (217 loc) · 10.3 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// mcpp.build.plan — backend-agnostic representation of "what to build".
//
// The pipeline is:
// manifest + modgraph + toolchain + fingerprint → BuildPlan → Backend.build()
export module mcpp.build.plan;
import std;
import mcpp.manifest;
import mcpp.modgraph.graph;
import mcpp.toolchain.detect;
import mcpp.toolchain.fingerprint;
export namespace mcpp::build {
struct CompileUnit {
std::filesystem::path source;
std::filesystem::path object; // relative to plan.outputDir
std::optional<std::string> providesModule; // logical name, if .cppm export
std::vector<std::string> imports; // logical names imported
};
struct LinkUnit {
std::string targetName;
enum Kind { Binary, StaticLibrary, SharedLibrary, TestBinary } kind = Binary;
std::vector<std::filesystem::path> objects; // relative to plan.outputDir
std::filesystem::path output; // relative to plan.outputDir
std::optional<std::filesystem::path> entryMain; // src path of main.cpp for bin
};
struct BuildPlan {
mcpp::manifest::Manifest manifest;
mcpp::toolchain::Toolchain toolchain;
mcpp::toolchain::Fingerprint fingerprint;
std::filesystem::path projectRoot; // where mcpp.toml lives
std::filesystem::path outputDir; // target/<triple>/<fp>/
std::filesystem::path stdBmiPath; // absolute path to prebuilt std.gcm
std::filesystem::path stdObjectPath; // absolute path to prebuilt std.o
std::vector<CompileUnit> compileUnits; // topologically sorted
std::vector<LinkUnit> linkUnits;
};
// Build a BuildPlan from already-validated inputs.
BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
const mcpp::toolchain::Toolchain& tc,
const mcpp::toolchain::Fingerprint& fp,
const mcpp::modgraph::Graph& graph,
const std::vector<std::size_t>& topoOrder,
const std::filesystem::path& projectRoot,
const std::filesystem::path& outputDir,
const std::filesystem::path& stdBmiPath,
const std::filesystem::path& stdObjectPath);
} // namespace mcpp::build
namespace mcpp::build {
namespace {
std::string sanitize_for_path(std::string_view module_name) {
std::string s;
s.reserve(module_name.size());
for (char c : module_name) {
if (c == ':') s.push_back('-');
else s.push_back(c);
}
return s;
}
std::string object_filename_for(const std::filesystem::path& src) {
auto stem = src.stem().string();
// distinguish .cppm vs .cpp by extension prefix to avoid collisions
return stem + (src.extension() == ".cppm" ? ".m.o" : ".o");
}
} // namespace
BuildPlan make_plan(const mcpp::manifest::Manifest& manifest,
const mcpp::toolchain::Toolchain& tc,
const mcpp::toolchain::Fingerprint& fp,
const mcpp::modgraph::Graph& graph,
const std::vector<std::size_t>& topoOrder,
const std::filesystem::path& projectRoot,
const std::filesystem::path& outputDir,
const std::filesystem::path& stdBmiPath,
const std::filesystem::path& stdObjectPath)
{
BuildPlan plan;
plan.manifest = manifest;
plan.toolchain = tc;
plan.fingerprint = fp;
plan.projectRoot = projectRoot;
plan.outputDir = outputDir;
plan.stdBmiPath = stdBmiPath;
plan.stdObjectPath = stdObjectPath;
// 1a. Detect basename collisions across packages (multi-version mangling
// stages a second copy of the same dep, so `parse.cppm` and friends
// can show up twice). For colliding files we namespace the object
// path by the unit's owning package so `obj/<file>.o` doesn't get
// two `build` rules.
std::map<std::string, int> basenameCount;
for (auto idx : topoOrder) {
basenameCount[object_filename_for(graph.units[idx].path)]++;
}
auto sanitize_pkg = [](const std::string& s) {
std::string out; out.reserve(s.size());
for (char c : s) out += (c == '.' ? '_' : c);
return out;
};
// 1. Compile units in topological order
for (auto idx : topoOrder) {
auto& u = graph.units[idx];
CompileUnit cu;
cu.source = u.path;
const auto fname = object_filename_for(u.path);
if (basenameCount[fname] > 1 && !u.packageName.empty()) {
cu.object = std::filesystem::path("obj")
/ sanitize_pkg(u.packageName) / fname;
} else {
cu.object = std::filesystem::path("obj") / fname;
}
if (u.provides) {
cu.providesModule = u.provides->logicalName;
}
for (auto& req : u.requires_) cu.imports.push_back(req.logicalName);
plan.compileUnits.push_back(std::move(cu));
}
// 2. Build map of module-name → compile unit (for inter-unit dep resolution)
std::map<std::string, std::size_t> producerOf;
for (std::size_t i = 0; i < plan.compileUnits.size(); ++i) {
if (plan.compileUnits[i].providesModule) {
producerOf[*plan.compileUnits[i].providesModule] = i;
}
}
// 3. Compute the set of all targets' entry .cpp files. Each entry is
// exclusive to its target — when assembling another target's link
// image we must NOT pull in foreign entries (they each define
// `int main(...)`, causing multiple-definition link errors).
std::set<std::filesystem::path> entryFilesAcrossTargets;
for (auto& t : manifest.targets) {
if (!t.main.empty()) {
entryFilesAcrossTargets.insert(projectRoot / t.main);
}
}
// 4. Link units (one per [targets.X])
// When any TestBinary target exists, skip Binary/Library/SharedLibrary
// targets — `mcpp test` only cares about the test binaries, and pulling
// dev-deps' .o (e.g. gtest_main.cc with its own main()) into the
// project's regular bin would cause `multiple definition of 'main'`.
bool inTestMode = false;
for (auto& t : manifest.targets) {
if (t.kind == mcpp::manifest::Target::TestBinary) { inTestMode = true; break; }
}
for (auto& t : manifest.targets) {
if (inTestMode && t.kind != mcpp::manifest::Target::TestBinary) continue;
LinkUnit lu;
lu.targetName = t.name;
if (t.kind == mcpp::manifest::Target::Library) {
lu.kind = LinkUnit::StaticLibrary;
lu.output = std::filesystem::path("bin") / std::format("lib{}.a", t.name);
} else if (t.kind == mcpp::manifest::Target::SharedLibrary) {
lu.kind = LinkUnit::SharedLibrary;
lu.output = std::filesystem::path("bin") / std::format("lib{}.so", t.name);
} else if (t.kind == mcpp::manifest::Target::TestBinary) {
lu.kind = LinkUnit::TestBinary;
lu.output = std::filesystem::path("bin") / t.name;
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
} else {
lu.kind = LinkUnit::Binary;
lu.output = std::filesystem::path("bin") / t.name;
if (!t.main.empty()) lu.entryMain = projectRoot / t.main;
}
// Include all module units' objects (they may be needed at runtime via global init).
// For binary target, also include main.cpp's object if main is present.
for (auto& cu : plan.compileUnits) {
if (cu.source.extension() == ".cppm") {
lu.objects.push_back(cu.object);
}
}
if ((lu.kind == LinkUnit::Binary || lu.kind == LinkUnit::TestBinary) && lu.entryMain) {
// Add main.cpp -> obj/main.o
CompileUnit main_cu;
main_cu.source = *lu.entryMain;
main_cu.object = std::filesystem::path("obj") / object_filename_for(*lu.entryMain);
// We didn't scan main.cpp earlier (it's not in scanner output unless globbed in).
// Best-effort: scan its imports here.
std::ifstream is(*lu.entryMain);
std::string line;
while (std::getline(is, line)) {
auto trim = [](std::string s) {
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.front()))) s.erase(0, 1);
while (!s.empty() && std::isspace(static_cast<unsigned char>(s.back()))) s.pop_back();
return s;
};
line = trim(line);
if (line.starts_with("import ")) {
std::string name;
std::size_t i = 7;
while (i < line.size() && (std::isalnum(static_cast<unsigned char>(line[i]))
|| line[i] == '_' || line[i] == '.')) {
name.push_back(line[i]);
++i;
}
if (!name.empty()) main_cu.imports.push_back(name);
}
}
// Avoid duplicate insert if main was already scanned
bool already = false;
for (auto& cu : plan.compileUnits) {
if (cu.source == main_cu.source) { already = true; break; }
}
if (!already) {
plan.compileUnits.push_back(main_cu);
}
lu.objects.push_back(main_cu.object);
}
// Also include implementation .cpp/.cc/.cxx/.c units, but EXCLUDE any
// file registered as another target's entryMain (each binary's main()
// is exclusive to that binary).
for (auto& cu : plan.compileUnits) {
auto ext = cu.source.extension();
if (ext != ".cpp" && ext != ".cc" && ext != ".cxx" && ext != ".c") continue;
if (lu.entryMain && cu.source == *lu.entryMain) continue; // own entry: already added above
if (entryFilesAcrossTargets.contains(cu.source)) continue; // foreign entry: skip
lu.objects.push_back(cu.object);
}
plan.linkUnits.push_back(std::move(lu));
}
return plan;
}
} // namespace mcpp::build