-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathtest_link_model_runtime_dirs.cpp
More file actions
157 lines (140 loc) · 6.44 KB
/
Copy pathtest_link_model_runtime_dirs.cpp
File metadata and controls
157 lines (140 loc) · 6.44 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
// An artifact must find every library it needs inside the sandbox — in BOTH
// link modes.
//
// RUNPATH is assembled by two separate paths, CLibMode::Sysroot and
// CLibMode::PayloadFirst, and any given machine only ever takes one of them.
// The compiler's own runtime (libgcc_s.so.1) lives beside the compiler, not in
// the C library, and a produced binary links it whether or not the build ever
// mentions it. gcc's patched specs used to supply that directory; removing the
// specs rewrite therefore has to supply it explicitly — and at first only the
// Sysroot path did.
//
// The consequence was invisible where it was written: on a machine with a host
// toolchain the artifact simply resolved libgcc_s.so.1 from /lib and ran. It
// failed only in a throw-away home on CI, as `error while loading shared
// libraries` with nothing said about what was missing.
//
// Asserted here rather than in e2e on purpose. Which mode an e2e reaches
// depends on whether the machine happens to have a usable sysroot — this one
// does, so an e2e leg written for PayloadFirst quietly ran Sysroot twice and
// passed with the defect in place. The link model takes its mode from its
// input, so a test can name the mode instead of hoping for it.
#include <gtest/gtest.h>
import std;
import mcpp.build.flags;
import mcpp.manifest.types;
import mcpp.toolchain.linkmodel;
import mcpp.toolchain.model;
namespace tc = mcpp::toolchain;
namespace {
void touch(const std::filesystem::path& p) {
std::filesystem::create_directories(p.parent_path());
std::ofstream(p) << "x";
}
// A payload tree with a glibc beside a gcc, as xlings lays them out.
struct Payload {
std::filesystem::path root, compiler, glibcLib, gccLib, sysroot;
Payload() {
root = std::filesystem::temp_directory_path()
/ std::format("mcpp_linkmodel_{}", std::random_device{}());
auto xpkgs = root / "data" / "xpkgs";
compiler = xpkgs / "xim-x-gcc" / "16.1.0" / "bin" / "g++";
touch(compiler);
gccLib = xpkgs / "xim-x-gcc" / "16.1.0" / "lib64";
touch(gccLib / "libgcc_s.so.1");
glibcLib = xpkgs / "xim-x-glibc" / "2.39" / "lib64";
touch(glibcLib / "libc.so.6");
touch(glibcLib / "ld-linux-x86-64.so.2");
touch(xpkgs / "xim-x-glibc" / "2.39" / "include" / "features.h");
sysroot = root / "subos" / "default";
touch(sysroot / "usr" / "include" / "stdlib.h");
}
~Payload() { std::error_code ec; std::filesystem::remove_all(root, ec); }
tc::Toolchain toolchain(bool withSysroot) const {
tc::Toolchain t;
t.compiler = tc::CompilerId::GCC;
t.version = "16.1.0";
t.binaryPath = compiler;
t.targetTriple = "x86_64-linux-gnu";
t.runtimeBinding = "glibc@2.39";
tc::PayloadPaths pp;
pp.glibcLib = glibcLib;
pp.glibcInclude = glibcLib.parent_path() / "include";
t.payloadPaths = pp;
if (withSysroot) t.sysroot = sysroot;
return t;
}
};
std::string joined(const tc::ToolchainLinkModel& lm) {
auto id = [](const std::filesystem::path& p) { return p.string(); };
std::string s;
for (auto& tok : lm.link_tokens(id)) { s += tok; s += ' '; }
return s;
}
TEST(LinkModelRuntimeDirs, PayloadFirstCarriesTheCompilerRuntime) {
Payload p;
auto lm = tc::resolve_link_model(p.toolchain(/*withSysroot=*/false));
ASSERT_EQ(lm.mode, tc::CLibMode::PayloadFirst);
auto line = joined(lm);
EXPECT_NE(line.find("-Wl,-rpath," + p.gccLib.string()), std::string::npos)
<< "libgcc_s.so.1 lives here and nothing else will find it:\n" << line;
}
TEST(LinkModelRuntimeDirs, SysrootCarriesTheCompilerRuntime) {
Payload p;
auto lm = tc::resolve_link_model(p.toolchain(/*withSysroot=*/true));
ASSERT_EQ(lm.mode, tc::CLibMode::Sysroot);
auto line = joined(lm);
EXPECT_NE(line.find("-Wl,-rpath," + p.gccLib.string()), std::string::npos)
<< line;
}
// The C library's own directory, in both modes, for the same reason.
TEST(LinkModelRuntimeDirs, BothModesCarryTheCLibrary) {
Payload p;
for (bool withSysroot : {false, true}) {
auto line = joined(tc::resolve_link_model(p.toolchain(withSysroot)));
EXPECT_NE(line.find("-Wl,-rpath," + p.glibcLib.string()),
std::string::npos)
<< "withSysroot=" << withSysroot << "\n" << line;
}
}
// And the interpreter. A sysroot says where headers live; it says nothing
// about which loader runs the result, and gcc's `*link:` no longer answers.
TEST(LinkModelRuntimeDirs, BothModesNameTheInterpreter) {
Payload p;
for (bool withSysroot : {false, true}) {
auto line = joined(tc::resolve_link_model(p.toolchain(withSysroot)));
EXPECT_NE(line.find("--dynamic-linker=" + p.glibcLib.string()),
std::string::npos)
<< "withSysroot=" << withSysroot << "\n" << line;
}
}
TEST(LinkIntent, KeepsLinkSearchTransitiveSearchAndRuntimeSearchSeparate) {
mcpp::manifest::LinkIntent intent;
intent.libraries = {"widget"};
intent.linkLibraryDirs = {"/contract/link"};
intent.transitiveNeededDirs = {"/contract/needed"};
intent.runtimeSearchDirs = {"/contract/run"};
intent.frameworks = {"WindowKit"};
intent.deployFiles = {"/contract/bin/widget.dll"};
auto elf = mcpp::build::render_link_intent_flags(
intent, mcpp::build::LinkIntentFlavor::Elf);
EXPECT_NE(elf.find("-L/contract/link"), std::string::npos) << elf;
EXPECT_NE(elf.find("-Wl,-rpath-link,/contract/needed"), std::string::npos)
<< elf;
EXPECT_NE(elf.find("-Wl,-rpath,/contract/run"), std::string::npos) << elf;
EXPECT_EQ(elf.find("-L/contract/run"), std::string::npos) << elf;
EXPECT_EQ(elf.find("widget.dll"), std::string::npos) << elf;
auto macho = mcpp::build::render_link_intent_flags(
intent, mcpp::build::LinkIntentFlavor::MachO);
EXPECT_NE(macho.find("-Wl,-rpath,/contract/run"), std::string::npos)
<< macho;
EXPECT_NE(macho.find("-framework WindowKit"), std::string::npos) << macho;
EXPECT_EQ(macho.find("rpath-link"), std::string::npos) << macho;
auto pe = mcpp::build::render_link_intent_flags(
intent, mcpp::build::LinkIntentFlavor::PeMsvc);
EXPECT_NE(pe.find("/LIBPATH$:/contract/link"), std::string::npos) << pe;
EXPECT_EQ(pe.find("rpath"), std::string::npos) << pe;
EXPECT_EQ(pe.find("/contract/run"), std::string::npos) << pe;
EXPECT_EQ(pe.find("widget.dll"), std::string::npos) << pe;
}
} // namespace