-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathtest_linkmodel.cpp
More file actions
336 lines (301 loc) · 13.6 KB
/
Copy pathtest_linkmodel.cpp
File metadata and controls
336 lines (301 loc) · 13.6 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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
#include <gtest/gtest.h>
import std;
import mcpp.toolchain.linkmodel;
import mcpp.toolchain.model;
// The toolchain link model is the single resolver for "how do we compile and
// link against this toolchain's C library" (issue #195 / the hermetic link
// model design doc). These tests pin its three contracts:
// 1. loader names come from data (triple map → glob), never a
// hardcoded x86_64 string;
// 2. the payload link flags include -B (CRT discovery — the driver never
// consults -L for Scrt1.o/crti.o/crtn.o);
// 3. mode selection mirrors the historical flags.cppm precedence.
namespace {
namespace tc = mcpp::toolchain;
struct Tmp {
std::filesystem::path path;
Tmp() {
path = std::filesystem::temp_directory_path()
/ std::format("mcpp_linkmodel_test_{}", std::random_device{}());
std::filesystem::create_directories(path);
}
~Tmp() {
std::error_code ec;
std::filesystem::remove_all(path, ec);
}
};
void touch(const std::filesystem::path& p) {
std::filesystem::create_directories(p.parent_path());
std::ofstream(p) << "x";
}
std::string ident(const std::filesystem::path& p) { return p.string(); }
TEST(LoaderFilename, TripleMap) {
EXPECT_EQ(tc::loader_filename("x86_64-unknown-linux-gnu"), "ld-linux-x86-64.so.2");
EXPECT_EQ(tc::loader_filename("aarch64-linux-gnu"), "ld-linux-aarch64.so.1");
EXPECT_EQ(tc::loader_filename("x86_64-linux-musl"), "ld-musl-x86_64.so.1");
EXPECT_EQ(tc::loader_filename("aarch64-linux-musl"), "ld-musl-aarch64.so.1");
EXPECT_EQ(tc::loader_filename("wasm32-unknown-unknown"), "");
}
TEST(DistroLoaderPath, LsbLayoutPerArch) {
EXPECT_EQ(tc::distro_loader_path("x86_64-linux-gnu"), "/lib64/ld-linux-x86-64.so.2");
EXPECT_EQ(tc::distro_loader_path("aarch64-linux-gnu"), "/lib/ld-linux-aarch64.so.1");
EXPECT_EQ(tc::distro_loader_path("mips64-unknown"), "");
}
TEST(ResolveLoader, TripleMapHit) {
Tmp dir;
auto lib = dir.path / "lib64";
touch(lib / "ld-linux-x86-64.so.2");
EXPECT_EQ(tc::resolve_loader(lib, "x86_64-unknown-linux-gnu"),
lib / "ld-linux-x86-64.so.2");
}
TEST(ResolveLoader, GlobFallbackForUnknownTriple) {
Tmp dir;
auto lib = dir.path / "lib";
touch(lib / "ld-linux-aarch64.so.1");
auto got = tc::resolve_loader(lib, ""); // no triple info at all
EXPECT_EQ(got, lib / "ld-linux-aarch64.so.1");
}
TEST(ResolveLoader, EmptyWhenNothingFound) {
Tmp dir;
auto lib = dir.path / "lib64";
std::filesystem::create_directories(lib);
EXPECT_TRUE(tc::resolve_loader(lib, "x86_64-linux-gnu").empty());
}
TEST(PayloadLibDir, PrefersLib64ThenLib) {
Tmp dir;
touch(dir.path / "lib" / "ld-linux-x86-64.so.2");
EXPECT_EQ(tc::payload_lib_dir_with_loader(dir.path, "x86_64-linux-gnu"),
dir.path / "lib");
touch(dir.path / "lib64" / "ld-linux-x86-64.so.2");
EXPECT_EQ(tc::payload_lib_dir_with_loader(dir.path, "x86_64-linux-gnu"),
dir.path / "lib64");
}
// Fabricate a bundled-LLVM-style toolchain: bin/clang++ + bin/clang++.cfg,
// libc++ headers, and a glibc payload.
struct FakeClangWithPayload {
Tmp dir;
tc::Toolchain t;
std::filesystem::path glibcLib;
FakeClangWithPayload() {
auto llvm = dir.path / "llvm";
touch(llvm / "bin" / "clang++");
touch(llvm / "bin" / "clang++.cfg");
touch(llvm / "include" / "c++" / "v1" / "version");
auto glibc = dir.path / "glibc";
glibcLib = glibc / "lib64";
touch(glibc / "include" / "features.h");
touch(glibcLib / "Scrt1.o");
touch(glibcLib / "ld-linux-x86-64.so.2");
t.compiler = tc::CompilerId::Clang;
t.binaryPath = llvm / "bin" / "clang++";
t.targetTriple = "x86_64-unknown-linux-gnu";
t.payloadPaths = tc::PayloadPaths{
.glibcInclude = glibc / "include",
.glibcLib = glibcLib,
.linuxInclude = {},
};
}
};
TEST(LinkModel, ClangCfgPayloadFirstCarriesCrtDiscovery) {
FakeClangWithPayload fx;
auto lm = tc::resolve_link_model(fx.t);
EXPECT_EQ(lm.mode, tc::CLibMode::PayloadFirst);
EXPECT_TRUE(lm.clangWithCfg);
EXPECT_EQ(lm.crtDir, fx.glibcLib);
EXPECT_EQ(lm.loader, fx.glibcLib / "ld-linux-x86-64.so.2");
auto link = lm.link_flags(ident);
// -B is the #195 fix: CRT objects resolve via -B, never -L.
EXPECT_NE(link.find(" -B" + fx.glibcLib.string()), std::string::npos);
EXPECT_NE(link.find(" -L" + fx.glibcLib.string()), std::string::npos);
EXPECT_NE(link.find("--dynamic-linker=" + lm.loader.string()), std::string::npos);
auto compile = lm.compile_flags(ident);
EXPECT_NE(compile.find("-isystem"), std::string::npos);
EXPECT_EQ(compile.find("-idirafter"), std::string::npos);
}
TEST(LinkModel, ClangDriverModelExposesCfgAndHeaders) {
FakeClangWithPayload fx;
auto dm = tc::resolve_clang_driver(fx.t);
EXPECT_TRUE(dm.hasCfg);
ASSERT_FALSE(dm.cxxIncludes.empty());
EXPECT_NE(dm.compile_flags(ident).find("--no-default-config"), std::string::npos);
}
TEST(LinkModel, GccSysrootWinsOverPayload) {
Tmp dir;
auto sysroot = dir.path / "sysroot";
touch(sysroot / "usr" / "include" / "stdlib.h");
touch(sysroot / "usr" / "include" / "linux" / "limits.h");
tc::Toolchain t;
t.compiler = tc::CompilerId::GCC;
t.binaryPath = dir.path / "bin" / "g++";
t.targetTriple = "x86_64-linux-gnu";
t.sysroot = sysroot;
t.payloadPaths = tc::PayloadPaths{
.glibcInclude = dir.path / "glibc" / "include",
.glibcLib = dir.path / "glibc" / "lib64",
.linuxInclude = dir.path / "linux" / "include",
};
auto lm = tc::resolve_link_model(t);
EXPECT_EQ(lm.mode, tc::CLibMode::Sysroot);
EXPECT_NE(lm.compile_flags(ident).find("--sysroot="), std::string::npos);
EXPECT_NE(lm.link_flags(ident).find("--sysroot="), std::string::npos);
// Kernel headers exist in the sysroot → no supplement.
EXPECT_TRUE(lm.systemIncludes.empty());
}
TEST(LinkModel, GccSysrootSupplementsMissingKernelHeaders) {
Tmp dir;
auto sysroot = dir.path / "sysroot";
touch(sysroot / "usr" / "include" / "stdlib.h"); // no linux/limits.h
tc::Toolchain t;
t.compiler = tc::CompilerId::GCC;
t.targetTriple = "x86_64-linux-gnu";
t.sysroot = sysroot;
t.payloadPaths = tc::PayloadPaths{
.glibcInclude = dir.path / "glibc" / "include",
.glibcLib = dir.path / "glibc" / "lib64",
.linuxInclude = dir.path / "linux" / "include",
};
auto lm = tc::resolve_link_model(t);
ASSERT_EQ(lm.systemIncludes.size(), 1u);
// Sysroot-mode supplement renders as -isystem even for GCC.
EXPECT_NE(lm.compile_flags(ident).find("-isystem"), std::string::npos);
}
TEST(LinkModel, GccPayloadEmitsIdirafterAndItsOwnAddressing) {
Tmp dir;
auto glibcLib = dir.path / "glibc" / "lib64";
touch(glibcLib / "ld-linux-x86-64.so.2");
tc::Toolchain t;
t.compiler = tc::CompilerId::GCC;
t.targetTriple = "x86_64-linux-gnu";
t.payloadPaths = tc::PayloadPaths{
.glibcInclude = dir.path / "glibc" / "include",
.glibcLib = glibcLib,
.linuxInclude = {},
};
auto lm = tc::resolve_link_model(t);
EXPECT_EQ(lm.mode, tc::CLibMode::PayloadFirst);
// Headers still differ by driver: libstdc++'s #include_next wrappers need
// -idirafter, and that has not changed.
EXPECT_NE(lm.compile_flags(ident).find("-idirafter"), std::string::npos);
// Addressing no longer differs. GCC used to be left to its install-time
// specs here, which made the RUN side a per-toolchain-install decision
// while the COMPILE side stayed per-build -- and the two named different
// glibc versions as soon as a second one was installed. mcpp already
// refuses to depend on clang's install-time cfg for exactly this reason.
auto link = lm.link_flags(ident);
EXPECT_NE(link.find(" -B"), std::string::npos);
EXPECT_NE(link.find("--dynamic-linker=" + lm.loader.string()),
std::string::npos);
EXPECT_NE(link.find("-Wl,-rpath," + glibcLib.string()), std::string::npos);
}
TEST(LinkModel, NothingUsableYieldsNoneAndEmptyFlags) {
tc::Toolchain t;
t.compiler = tc::CompilerId::Clang;
t.targetTriple = "x86_64-linux-gnu";
auto lm = tc::resolve_link_model(t);
EXPECT_EQ(lm.mode, tc::CLibMode::None);
EXPECT_TRUE(lm.compile_flags(ident).empty());
EXPECT_TRUE(lm.link_flags(ident).empty());
}
// 关于 `find_sibling_tool` 的顺序:**不在这里测**。
//
// 它的注释曾写着 "Return the first (highest) version dir",而代码只是取
// directory_iterator 的第一项 —— 没有排序。为它写一条断言的尝试失败了:
// 结果取决于 readdir 顺序,所以那条测试在本机通过、在别的机器上可能失败,
// **通过和失败都不说明问题**。
//
// 正确的做法不是把不确定性钉住,而是让它不再被用于选版本:版本由权威
// (`--runtime` / subos 的 `subos_info.runtime`)给出,`find_sibling_tool`
// 只负责定位包根。确定性的判据在 test_toolchain_probe.cpp —— 给定
// `[email protected]`,即使 home 里同时有 2.44,也必须返回 2.39。
//
// 注释本身已改为陈述事实(顺序未定义,调用方不得依赖)。
// 设计:.agents/docs/2026-08-08-payload-version-and-contract-drift-design.md §3.2
} // namespace
// ─── An SDK target gets CLibMode::None, and the gate is in the MODEL ───────
//
// The C-runtime group reaches the link line through TWO channels -- flags.cppm's
// `link_toolchain_flags` and its `payload_ld` -- both rendering
// `lm.link_flags()`. The comment at the second one records that a reader who
// fixed only the first "saw the identical error and could reasonably conclude
// the fix had not worked". So the gate belongs here, where both read it.
//
// Measured on `--target wasm32-emscripten` with the compile side already
// correct: `wasm-ld: error: unknown argument:
// --dynamic-linker=<glibc>/lib64/ld-linux-x86-64.so.2` -- this host's loader
// handed to a WebAssembly linker.
TEST(LinkModel, AnSdkTargetDescribesNoCLibrary) {
for (auto target : {"wasm32-emscripten", "aarch64-linux-android",
"x86_64-linux-android"}) {
auto tc = mcpp::toolchain::Toolchain{};
tc.compiler = mcpp::toolchain::CompilerId::Clang;
tc.targetTriple = target;
auto lm = mcpp::toolchain::resolve_link_model(tc);
EXPECT_EQ(lm.mode, mcpp::toolchain::CLibMode::None) << target;
EXPECT_TRUE(lm.libDirs.empty()) << target;
EXPECT_TRUE(lm.crtDir.empty()) << target;
// And the rendered flags are empty, which is what both channels emit.
EXPECT_TRUE(lm.link_flags([](const std::filesystem::path& p) {
return p.string(); }).empty()) << target;
}
}
// ─── Clang on the MSVC ABI: the toolset and SDK, said to the driver ───────
namespace {
tc::Toolchain clang_msvc() {
tc::Toolchain t;
t.compiler = tc::CompilerId::Clang;
t.targetTriple = "x86_64-pc-windows-msvc";
t.msvcToolsDir = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.44.35207";
t.windowsSdkRoot = "C:/Program Files (x86)/Windows Kits/10";
t.windowsSdkVersion = "10.0.26100.0";
return t;
}
} // namespace
TEST(LinkModel, ClangOnTheMsvcAbiNamesItsToolsetAndSdk) {
auto t = clang_msvc();
auto words = tc::resolve_link_model(t).msvc_driver_tokens(ident);
const std::vector<std::string> expected = {
"-Xmicrosoft-visualc-tools-root", t.msvcToolsDir.string(),
"-Xmicrosoft-windows-sdk-root", t.windowsSdkRoot.string(),
"-Xmicrosoft-windows-sdk-version", "10.0.26100.0",
};
EXPECT_EQ(words, expected) << "separate words: the driver rejects the `=` form";
// Not a C-library mode: the other token sets stay empty.
auto lm = tc::resolve_link_model(t);
EXPECT_EQ(lm.mode, tc::CLibMode::None);
EXPECT_TRUE(lm.compile_tokens(ident).empty());
}
// mcpp#746. The compiler version travels with the toolset: two runner images
// held different cl.exe builds under one toolset directory name, and a key
// that saw only the directory served one image's std module to the other.
TEST(LinkModel, TheCompilerVersionIsSaidWithTheToolset) {
auto t = clang_msvc();
t.msvcCompilerVersion = "19.44.35211";
auto words = tc::resolve_link_model(t).msvc_driver_tokens(ident);
ASSERT_FALSE(words.empty());
EXPECT_EQ(words.back(), "-fms-compatibility-version=19.44.35211");
// A second build of cl.exe under the same directory is a different word.
auto other = t;
other.msvcCompilerVersion = "19.44.35217";
EXPECT_NE(tc::resolve_link_model(other).msvc_driver_tokens(ident), words);
}
TEST(LinkModel, TheToolsetWithoutAnSdkOmitsTheSdkWords) {
auto t = clang_msvc();
t.windowsSdkRoot.clear();
t.windowsSdkVersion.clear();
auto words = tc::resolve_link_model(t).msvc_driver_tokens(ident);
ASSERT_EQ(words.size(), 2u);
EXPECT_EQ(words[0], "-Xmicrosoft-visualc-tools-root");
}
TEST(LinkModel, NoOtherRowReceivesMsvcDriverWords) {
// cl.exe carries the toolset in its own path and envOverrides; MinGW is
// not MSVC at all; a row with no toolset recorded says nothing.
auto cl = clang_msvc();
cl.compiler = tc::CompilerId::MSVC;
EXPECT_TRUE(tc::resolve_link_model(cl).msvc_driver_tokens(ident).empty());
auto mingw = clang_msvc();
mingw.targetTriple = "x86_64-w64-mingw32";
EXPECT_TRUE(tc::resolve_link_model(mingw).msvc_driver_tokens(ident).empty());
auto unbound = clang_msvc();
unbound.msvcToolsDir.clear();
EXPECT_TRUE(tc::resolve_link_model(unbound).msvc_driver_tokens(ident).empty());
}