-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.mcpp
More file actions
153 lines (144 loc) · 7.36 KB
/
Copy pathbuild.mcpp
File metadata and controls
153 lines (144 loc) · 7.36 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
import mcpp;
import std;
// ⭐⭐ THE COMPILER'S OWN RUNTIME, NAMED ONLY WHEN IT HAS THAT NAME.
//
// A compiler emits calls to routines no C library defines — a 128-bit shift, a
// stack probe, a complex multiply on `long double`. They live in `libgcc.a`
// under GCC and in compiler-rt under clang, and the two are not
// interchangeable names for one file: `-lgcc` on a clang link is a library that
// does not exist.
//
// ⚠️ THE MANIFEST CANNOT SAY THIS, WHICH IS WHY THERE IS A PROGRAM. A
// `[target.'cfg(windows)']` block describes the TARGET, and this is a question
// about the toolchain that resolved. Both measurements arrived on 2026-08-22
// from the same missing distinction:
//
// naming it unconditionally lld: error: unable to find library -lgcc
// (a clean runner, clang, cross to PE)
// naming it never undefined reference to `___chkstk_ms'
// undefined reference to `__mulxc3'
// (a Windows runner, GCC, no C++ runtime in the
// graph to supply compiler-rt instead)
//
// Each was the correct behaviour for the other configuration.
//
// ⚠️ PE ONLY, AND `gcc_eh` WITH IT. On this format a thread-local variable is
// reached through a helper rather than through an address the processor
// supplies, and that helper is in the second archive rather than the first.
// Where the graph brings `openkal-llvm-runtime`, compiler-rt's `emutls.c` is
// the same answer and neither archive is consulted.
//
// ⚠️ NOT ON ELF OR Mach-O. There the driver still links the compiler's runtime
// even under `-nostdlib`, or the graph supplies it; adding a name here would be
// a second source for something that already has one.
// ⭐⭐ WHICH COMPILER FAMILY RESOLVED — AND WHAT AN ABSENT ANSWER MEANS.
//
// ⚠️ READ FROM THE ENVIRONMENT, NOT THROUGH A HELPER. A build program is
// compiled against the `mcpp` module of whichever tool RUNS it, so naming a
// helper newer than the released tool makes this package require an unreleased
// one. Measured 2026-08-22, on every row of three repositories at once:
//
// build.mcpp: error: no member named 'compiler' in namespace 'mcpp'
// build.mcpp: error: 'toolchain_dir' is not a member of 'mcpp'
//
// — and the second is the more useful of the two, because `toolchain_dir` was
// the FALLBACK written for the first. The environment variables are the
// contract and `std::getenv` reads them on every version.
//
// ⚠️ AND AN ABSENT VALUE IS NOT "NO COMPILER". Measured against mcpp 2026.8.19.1,
// which is what CI installs: `MCPP_TARGET_OS`, `MCPP_HOST`, `MCPP_OUT_DIR`,
// `MCPP_MANIFEST_DIR`, `MCPP_TARGET` and `MCPP_TARGET_ARCH` are all set, and
// `MCPP_COMPILER` and `MCPP_TOOLCHAIN_DIR` are absent.
//
// ⭐ So absent means "a build tool from before the question could be asked" —
// and such a tool cannot produce the configuration the answer would change. The
// clang-over-openkal cross to PE arrives on the same release as the variable.
// The one configuration a tool that predates it can produce is the GCC one, so
// that is what absent is answered with, and it is a deduction rather than a
// default.
std::string compiler_family() {
if (const char* v = std::getenv("MCPP_COMPILER"); v && *v) return v;
return "gcc";
}
std::string env_or_empty(const char* name) {
const char* v = std::getenv(name);
return v ? v : "";
}
// THE VERSION IS READ FROM THE MANIFEST RATHER THAN WRITTEN OUT A SECOND TIME.
//
// A program built on this library could not state which version of it it held.
// The only version-shaped thing it could read was `uname`'s release field,
// which was the string literal "0.5.0" and had never moved -- so a consumer who
// checked it was told a version, and the version was wrong. Two rounds of
// mcpplibs/openkal-linux#13 turned on a question the software gave no way to
// settle.
//
// A `defines` entry in the manifest would state the number a second time, four
// lines below where it is already stated, and the two would agree until one of
// them was edited. So it is read from the one place that has it. An unreadable
// manifest yields no definition at all rather than a wrong one, and the header
// that consumes this reports "unknown" -- which is a true statement, unlike
// the constant it replaces.
std::string package_version() {
const std::string dir = env_or_empty("MCPP_MANIFEST_DIR");
if (dir.empty()) return "";
std::ifstream in(dir + "/mcpp.toml");
if (!in) return "";
// The first `version = "..."` under [package]. Sections after it declare
// versions of other things -- dependencies name theirs the same way -- so
// reading past the first table would answer about one of those.
std::string line;
bool in_package = false;
while (std::getline(in, line)) {
const auto first = line.find_first_not_of(" \t");
if (first == std::string::npos || line[first] == '#') continue;
if (line[first] == '[') { in_package = line.compare(first, 9, "[package]") == 0; continue; }
if (!in_package) continue;
if (line.compare(first, 7, "version") != 0) continue;
const auto open = line.find('"');
if (open == std::string::npos) continue;
const auto close = line.find('"', open + 1);
if (close == std::string::npos) continue;
return line.substr(open + 1, close - open - 1);
}
return "";
}
int main() {
const std::string os = env_or_empty("MCPP_TARGET_OS");
const std::string fam = compiler_family();
// BEFORE THE EARLY RETURN BELOW, AND THAT IS THE WHOLE OF WHY IT IS HERE.
// The compiler family decides whether a runtime library is named; it does
// not decide whether this library knows its own version. Emitting this
// after that return would define the version under one compiler and leave
// it undefined under the other, and the sources would then disagree about
// what they report while compiling cleanly under both.
if (const std::string v = package_version(); !v.empty()) {
mcpp::rerun_if_changed((env_or_empty("MCPP_MANIFEST_DIR") + "/mcpp.toml").c_str());
// THE QUOTES HAVE TO SURVIVE A SHELL. The build file this reaches is
// executed by one, so `-DOKM_VERSION="0.9.0"` arrives at the compiler
// as `-DOKM_VERSION=0.9.0` and 0.9.0 is not an expression:
//
// <command-line>: error: too many decimal points in number
//
// naming a line in this file that is correct. Escaped here so that
// what the shell removes is the escape rather than the quote.
mcpp::cflag(("-DOKM_VERSION=\\\"" + v + "\\\"").c_str());
}
if (fam != "gcc") return 0;
// ⚠️ ELF NEEDS IT TOO, AND FOR THE SAME REASON THE PE BLOCK DID. The Linux
// block of the manifest named `-lgcc` unconditionally, and that resolved on
// a Linux host because a payload with it happens to be installed there.
// Measured 2026-08-23, a macOS host cross-building for `x86_64-linux-gnu`:
//
// ld.lld: error: unable to find library -lgcc
//
// ⇒ Under clang the answer is compiler-rt, which `openkal-llvm-runtime`
// builds for this target as it already did for the other three.
if (os == "linux") {
mcpp::link_lib("gcc");
} else if (os == "windows") {
mcpp::link_lib("gcc");
mcpp::link_lib("gcc_eh");
}
return 0;
}