-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.mcpp
More file actions
190 lines (180 loc) · 9.91 KB
/
Copy pathbuild.mcpp
File metadata and controls
190 lines (180 loc) · 9.91 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
import mcpp;
import std;
// riscv-virt-rt — the BOARD, and only the board.
//
// WHAT IS *NOT* HERE ANY MORE, AND WHY THAT MATTERS
//
// This file used to locate picolibc and hand out its include and library
// paths. It no longer does: the target's C library is a property of the
// TARGET, and mcpp resolves it the same way it resolves the compiler — the
// row for `riscv*-none-elf` in its target table names both. A hosted project
// never wrote down `xim:glibc`, and a bare-metal one should not have to write
// down picolibc.
//
// ⚠️ AND WHAT LEFT IN 0.4.0: TWO NAMES THAT WERE NOT THIS BOARD'S TO KNOW.
//
// Until 0.3.0 this file also said `clang_rt.builtins-riscv64` and
// `rv64gc/lp64d`. Neither is a board fact:
//
// * which builtins library exists is decided by the COMPILER, and no board
// chooses to go without one — on rv64 the trigger is picolibc's printf
// doing 128-bit shifts, which the ISA has no instruction for;
// * where a profile's libraries sit is the C LIBRARY's multilib convention.
//
// The coupling was invisible: this package declares no dependency on LLVM and
// none on picolibc, and could still serve neither a second toolchain nor a
// second C library, because the names were written into the source. A declared
// dependency is reviewable; a hardcoded name fails only when something is
// swapped, which is when nobody is looking for it.
//
// What remains is the part that is genuinely the board's, and it is a short
// list: WHICH startup object and libraries to select out of that C library,
// WHICH linker script describes this machine's memory, and HOW to run an
// image. Location is a target fact; selection is a board fact.
int main() {
const std::string arch = mcpp::target_arch() ? mcpp::target_arch() : "";
const bool rv32 = (arch == "riscv32");
// ⚠️ Selected out of the C library, not located in it. `-L` for the target
// sysroot is already on the link line, so these are bare names — which is
// also what makes the same two lines work for both widths.
//
// The crt0 IS a board choice: `crt0-semihost` reaches the host through the
// debugger channel, and a board without one selects a UART crt0 instead.
// Its NAME, however, is the C library's. `mcpp::target_libc()` is asked
// rather than assumed so that a target on newlib can be served from this
// same file by extending the branch instead of forking it.
const std::string libc = mcpp::target_libc() ? mcpp::target_libc() : "";
// How to run an image. ⚠️ Emitted on BOTH paths below, because which
// emulator boots this machine is a board fact and has nothing to do with
// which C library compiled the image — a zero-libc kernel for `virt` is
// still a `virt` image. An earlier version returned before this point and
// left such a project with no runner at all.
//
// ⚠️ Absolute path: a bare name resolves through PATH to a shim that
// dispatches against its own owner home, which is not necessarily the home
// this build uses.
auto emit_runner = [&] {
if (const char* qemu = mcpp::xpkg_dir("xim", "qemu-riscv"); qemu && *qemu) {
mcpp::runner(std::format("{}/bin/qemu-system-{}", qemu,
rv32 ? "riscv32" : "riscv64").c_str());
for (auto a : {"-machine", "virt", "-nographic", "-no-reboot",
"-semihosting", "-bios", "none", "-kernel"})
mcpp::runner(a);
}
};
// ⚠️ NO C LIBRARY IS A SUPPORTED WAY TO USE THIS BOARD, AND ARRIVING AT
// THAT TOOK THREE ANSWERS. THE HISTORY IS THE ARGUMENT.
//
// 0.4.0 treated it as an error: this board is written against picolibc, so
// a project that declines the C library and still depends on it was making
// two statements that cannot both hold.
//
// That broke `mcpp new k --template riscv-virt-rt:nolibc`, because the
// scaffolder wrote this package into the generated manifest and the
// template's whole point is to depend on nothing. 0.4.1 made this branch
// degrade silently instead — emit the runner, contribute nothing else — and
// separately gave the template `[template.inject] self = false` so that the
// dependency was not written at all. Two fixes for one problem; the second
// removed the reason for the first, which then only served to delay the
// failure until the board's own module was compiled, where it surfaced as
// `fatal error: 'stdio.h' file not found` inside a package the reader did
// not write.
//
// Both answers shared a premise, and the premise was wrong. A RISC-V
// developer working without a C library does not need LESS of a board
// package — they need more of one. Which emulator boots this machine, where
// its UART is, where RAM begins: none of that is a C library fact, and
// refusing merely moved it, hardcoded, into every kernel project. That is
// the coupling a BSP exists to remove.
//
// So the C library is a feature of how this board is CONSUMED. With
// `features = ["nolibc"]` the board contributes what it knows that does not
// depend on one; without it, the request really is contradictory and is
// still refused, now naming the feature that resolves it.
// ⚠️ THE ZERO-LIBC ARRANGEMENT IS SELECTED BY A DECLARATION, NOT INFERRED
// FROM AN ABSENT C LIBRARY. THE DIFFERENCE IS NOT PEDANTRY; INFERRING IT
// WAS A BUG THAT ONLY A COLD MACHINE COULD SHOW.
//
// A first version of this branch refused when `mcpp::target_libc()` came
// back empty, on the reading that an empty answer means "this project
// declined the C library". It does not only mean that. mcpp installs the
// target's C library LAZILY, and the query is answered from the payload on
// disk — so on a machine that has never built for this target, the first
// pass reports no C library for a project that never declined one. The
// engine says as much where it fills the value in: "the install ... may
// legitimately not have run yet on a first pass".
//
// Measured: every local test passed, because picolibc was already
// installed here; CI failed on the package's own self-test, on a runner
// that deliberately does not pre-install it.
//
// The feature is unambiguous — a project either asked for this arrangement
// or it did not — so it is what selects, and it is tested before the C
// library is consulted at all.
if (mcpp::has_feature("nolibc")) {
// The memory map. ⚠️ A package-relative path, which `link-script`
// resolves against the PACKAGE root — so this names the board's own
// file rather than something in the consumer's directory.
mcpp::link_script("virt.ld");
emit_runner();
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");
mcpp::rerun_if_env_changed("MCPP_FEATURE_NOLIBC");
return 0;
}
// No C library YET, which is not the same as none. Contribute the runner —
// which emulator boots this machine does not depend on a C library — and
// nothing that would need one. The `rerun_if_env_changed` on
// MCPP_TARGET_LIBC at the end of the picolibc path below is what brings
// this program back once the install has happened and the name is known.
//
// A project that genuinely declined the C library and did NOT ask for the
// `nolibc` feature also lands here, and its link then fails naming the
// missing C library. That message is worse than a named refusal, and a
// named refusal is not available: from inside a build program these two
// situations are the same observation.
if (libc.empty()) {
emit_runner();
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");
mcpp::rerun_if_env_changed("MCPP_TARGET_LIBC");
mcpp::rerun_if_env_changed("MCPP_FEATURE_NOLIBC");
return 0;
}
// A C library that is present but is not the one this board is written
// against IS an error: the startup object and the linker script are named
// by the C library, and guessing at another one's names would fail later
// and less clearly.
if (!libc.starts_with("picolibc")) {
std::cerr << "riscv-virt-rt: this board is written against picolibc, and "
"the target resolved '" << libc
<< "'.\n The startup object and the linker script are named by "
"the C library, so\n serving this one needs a branch here "
"rather than a different board.\n";
return 1;
}
mcpp::link_lib("crt0-semihost"); // pulled from the archive by ENTRY(_start)
mcpp::link_lib("c");
mcpp::link_lib("semihost");
// ⚠️ Not optional, and not a board fact either. Without the builtins the
// link fails on __ashlti3/__lshrti3. Note that a "does 64-bit division
// work" check does NOT reach this: rv64gc has a hardware divu.
if (const char* b = mcpp::target_builtins_lib(); b && *b)
mcpp::link_lib(b);
// The memory layout — the one thing a consumer cannot write for itself.
//
// ⚠️ Asked for, not declared. The board knows WHICH script describes this
// machine; where the target's C library put it, and under which profile
// directory, are the engine's answers.
// (A bare name would not do: link-script resolves relative paths against
// the PACKAGE root, so `picolibcpp.ld` would point inside this package.)
const char* sysroot = mcpp::sysroot_dir();
const char* profile = mcpp::target_libc_profile();
if (sysroot && *sysroot && profile && *profile)
mcpp::link_script(
std::format("{}/lib/{}/picolibcpp.ld", sysroot, profile).c_str());
emit_runner();
mcpp::rerun_if_env_changed("MCPP_TARGET_ARCH");
mcpp::rerun_if_env_changed("MCPP_TARGET_LIBC");
mcpp::rerun_if_env_changed("MCPP_TARGET_LIBC_PROFILE");
mcpp::rerun_if_env_changed("MCPP_TARGET_BUILTINS_LIB");
return 0;
}