Skip to content

Commit d30f9c1

Browse files
committed
support building static self hosted compiler on macos
* add a --system-linker-hack command line parameter to work around poor LLD macho code. See #1535 * build.zig correctly handles static as well as dynamic dependencies when building the self hosted compiler. - no more unnecessary libxml2 dependency - a static build on macos produces a completely static self-hosted compiler for macos (except for libSystem as intended).
1 parent 9464edc commit d30f9c1

File tree

6 files changed

+122
-41
lines changed

6 files changed

+122
-41
lines changed

build.zig

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,23 @@ pub fn build(b: *Builder) !void {
121121
test_step.dependOn(docs_step);
122122
}
123123

124-
fn dependOnLib(lib_exe_obj: var, dep: LibraryDep) void {
124+
fn dependOnLib(b: *Builder, lib_exe_obj: var, dep: LibraryDep) void {
125125
for (dep.libdirs.toSliceConst()) |lib_dir| {
126126
lib_exe_obj.addLibPath(lib_dir);
127127
}
128+
const lib_dir = os.path.join(b.allocator, dep.prefix, "lib") catch unreachable;
128129
for (dep.system_libs.toSliceConst()) |lib| {
129-
lib_exe_obj.linkSystemLibrary(lib);
130+
const static_bare_name = if (mem.eql(u8, lib, "curses"))
131+
([]const u8)("libncurses.a")
132+
else
133+
b.fmt("lib{}.a", lib);
134+
const static_lib_name = os.path.join(b.allocator, lib_dir, static_bare_name) catch unreachable;
135+
const have_static = fileExists(static_lib_name) catch unreachable;
136+
if (have_static) {
137+
lib_exe_obj.addObjectFile(static_lib_name);
138+
} else {
139+
lib_exe_obj.linkSystemLibrary(lib);
140+
}
130141
}
131142
for (dep.libs.toSliceConst()) |lib| {
132143
lib_exe_obj.addObjectFile(lib);
@@ -136,34 +147,49 @@ fn dependOnLib(lib_exe_obj: var, dep: LibraryDep) void {
136147
}
137148
}
138149

150+
fn fileExists(filename: []const u8) !bool {
151+
os.File.access(filename) catch |err| switch (err) {
152+
error.PermissionDenied,
153+
error.FileNotFound,
154+
=> return false,
155+
else => return err,
156+
};
157+
return true;
158+
}
159+
139160
fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void {
140161
const lib_prefix = if (lib_exe_obj.target.isWindows()) "" else "lib";
141162
lib_exe_obj.addObjectFile(os.path.join(b.allocator, cmake_binary_dir, "zig_cpp", b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt())) catch unreachable);
142163
}
143164

144165
const LibraryDep = struct.{
166+
prefix: []const u8,
145167
libdirs: ArrayList([]const u8),
146168
libs: ArrayList([]const u8),
147169
system_libs: ArrayList([]const u8),
148170
includes: ArrayList([]const u8),
149171
};
150172

151173
fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep {
152-
const libs_output = try b.exec([][]const u8.{
153-
llvm_config_exe,
154-
"--libs",
155-
"--system-libs",
156-
});
157-
const includes_output = try b.exec([][]const u8.{
158-
llvm_config_exe,
159-
"--includedir",
160-
});
161-
const libdir_output = try b.exec([][]const u8.{
162-
llvm_config_exe,
163-
"--libdir",
164-
});
174+
const shared_mode = try b.exec([][]const u8.{ llvm_config_exe, "--shared-mode" });
175+
const is_static = mem.startsWith(u8, shared_mode, "static");
176+
const libs_output = if (is_static)
177+
try b.exec([][]const u8.{
178+
llvm_config_exe,
179+
"--libfiles",
180+
"--system-libs",
181+
})
182+
else
183+
try b.exec([][]const u8.{
184+
llvm_config_exe,
185+
"--libs",
186+
});
187+
const includes_output = try b.exec([][]const u8.{ llvm_config_exe, "--includedir" });
188+
const libdir_output = try b.exec([][]const u8.{ llvm_config_exe, "--libdir" });
189+
const prefix_output = try b.exec([][]const u8.{ llvm_config_exe, "--prefix" });
165190

166191
var result = LibraryDep.{
192+
.prefix = mem.split(prefix_output, " \r\n").next().?,
167193
.libs = ArrayList([]const u8).init(b.allocator),
168194
.system_libs = ArrayList([]const u8).init(b.allocator),
169195
.includes = ArrayList([]const u8).init(b.allocator),
@@ -244,10 +270,6 @@ fn nextValue(index: *usize, build_info: []const u8) []const u8 {
244270
}
245271

246272
fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
247-
// This is for finding /lib/libz.a on alpine linux.
248-
// TODO turn this into -Dextra-lib-path=/lib option
249-
exe.addLibPath("/lib");
250-
251273
exe.setNoRoSegment(ctx.no_rosegment);
252274

253275
exe.addIncludeDir("src");
@@ -265,39 +287,63 @@ fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
265287
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_coff");
266288
addCppLib(b, exe, ctx.cmake_binary_dir, "embedded_lld_lib");
267289
}
268-
dependOnLib(exe, ctx.llvm);
290+
dependOnLib(b, exe, ctx.llvm);
269291

270292
if (exe.target.getOs() == builtin.Os.linux) {
271-
const libstdcxx_path_padded = try b.exec([][]const u8.{
272-
ctx.cxx_compiler,
273-
"-print-file-name=libstdc++.a",
274-
});
275-
const libstdcxx_path = mem.split(libstdcxx_path_padded, "\r\n").next().?;
276-
if (mem.eql(u8, libstdcxx_path, "libstdc++.a")) {
277-
warn(
278-
\\Unable to determine path to libstdc++.a
279-
\\On Fedora, install libstdc++-static and try again.
280-
\\
281-
);
282-
return error.RequiredLibraryNotFound;
283-
}
284-
exe.addObjectFile(libstdcxx_path);
293+
try addCxxKnownPath(b, ctx, exe, "libstdc++.a",
294+
\\Unable to determine path to libstdc++.a
295+
\\On Fedora, install libstdc++-static and try again.
296+
\\
297+
);
285298

286299
exe.linkSystemLibrary("pthread");
287300
} else if (exe.target.isDarwin()) {
288-
exe.linkSystemLibrary("c++");
301+
if (addCxxKnownPath(b, ctx, exe, "libgcc_eh.a", "")) {
302+
// Compiler is GCC.
303+
try addCxxKnownPath(b, ctx, exe, "libstdc++.a", null);
304+
exe.linkSystemLibrary("pthread");
305+
// TODO LLD cannot perform this link.
306+
// See https://github.com/ziglang/zig/issues/1535
307+
exe.enableSystemLinkerHack();
308+
} else |err| switch (err) {
309+
error.RequiredLibraryNotFound => {
310+
// System compiler, not gcc.
311+
exe.linkSystemLibrary("c++");
312+
},
313+
else => return err,
314+
}
289315
}
290316

291317
if (ctx.dia_guids_lib.len != 0) {
292318
exe.addObjectFile(ctx.dia_guids_lib);
293319
}
294320

295-
if (exe.target.getOs() != builtin.Os.windows) {
296-
exe.linkSystemLibrary("xml2");
297-
}
298321
exe.linkSystemLibrary("c");
299322
}
300323

324+
fn addCxxKnownPath(
325+
b: *Builder,
326+
ctx: Context,
327+
exe: var,
328+
objname: []const u8,
329+
errtxt: ?[]const u8,
330+
) !void {
331+
const path_padded = try b.exec([][]const u8.{
332+
ctx.cxx_compiler,
333+
b.fmt("-print-file-name={}", objname),
334+
});
335+
const path_unpadded = mem.split(path_padded, "\r\n").next().?;
336+
if (mem.eql(u8, path_unpadded, objname)) {
337+
if (errtxt) |msg| {
338+
warn("{}", msg);
339+
} else {
340+
warn("Unable to determine path to {}\n", objname);
341+
}
342+
return error.RequiredLibraryNotFound;
343+
}
344+
exe.addObjectFile(path_unpadded);
345+
}
346+
301347
const Context = struct.{
302348
cmake_binary_dir: []const u8,
303349
cxx_compiler: []const u8,

src/all_types.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,7 @@ struct CodeGen {
17311731
bool generate_error_name_table;
17321732
bool enable_cache;
17331733
bool enable_time_report;
1734+
bool system_linker_hack;
17341735

17351736
//////////////////////////// Participates in Input Parameter Cache Hash
17361737
ZigList<LinkLib *> link_libs_list;

src/link.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -778,7 +778,8 @@ static bool darwin_version_lt(DarwinPlatform *platform, int major, int minor) {
778778
static void construct_linker_job_macho(LinkJob *lj) {
779779
CodeGen *g = lj->codegen;
780780

781-
lj->args.append("-error-limit=0");
781+
// LLD MACH-O has no error limit option.
782+
//lj->args.append("-error-limit=0");
782783
lj->args.append("-demangle");
783784

784785
if (g->linker_rdynamic) {
@@ -1007,7 +1008,17 @@ void codegen_link(CodeGen *g) {
10071008
Buf diag = BUF_INIT;
10081009

10091010
codegen_add_time_event(g, "LLVM Link");
1010-
if (!zig_lld_link(g->zig_target.oformat, lj.args.items, lj.args.length, &diag)) {
1011+
if (g->system_linker_hack && g->zig_target.os == OsMacOSX) {
1012+
Termination term;
1013+
ZigList<const char *> args = {};
1014+
for (size_t i = 1; i < lj.args.length; i += 1) {
1015+
args.append(lj.args.at(i));
1016+
}
1017+
os_spawn_process("ld", args, &term);
1018+
if (term.how != TerminationIdClean || term.code != 0) {
1019+
exit(1);
1020+
}
1021+
} else if (!zig_lld_link(g->zig_target.oformat, lj.args.items, lj.args.length, &diag)) {
10111022
fprintf(stderr, "%s\n", buf_ptr(&diag));
10121023
exit(1);
10131024
}

src/main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ int main(int argc, char **argv) {
394394
ZigList<const char *> test_exec_args = {0};
395395
int runtime_args_start = -1;
396396
bool no_rosegment_workaround = false;
397+
bool system_linker_hack = false;
397398

398399
if (argc >= 2 && strcmp(argv[1], "build") == 0) {
399400
Buf zig_exe_path_buf = BUF_INIT;
@@ -560,6 +561,8 @@ int main(int argc, char **argv) {
560561
timing_info = true;
561562
} else if (strcmp(arg, "--disable-pic") == 0) {
562563
disable_pic = true;
564+
} else if (strcmp(arg, "--system-linker-hack") == 0) {
565+
system_linker_hack = true;
563566
} else if (strcmp(arg, "--test-cmd-bin") == 0) {
564567
test_exec_args.append(nullptr);
565568
} else if (arg[1] == 'L' && arg[2] != 0) {
@@ -893,6 +896,7 @@ int main(int argc, char **argv) {
893896
g->verbose_llvm_ir = verbose_llvm_ir;
894897
g->verbose_cimport = verbose_cimport;
895898
codegen_set_errmsg_color(g, color);
899+
g->system_linker_hack = system_linker_hack;
896900

897901
for (size_t i = 0; i < lib_dirs.length; i += 1) {
898902
codegen_add_lib_dir(g, lib_dirs.at(i));

src/os.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ static void os_spawn_process_posix(const char *exe, ZigList<const char *> &args,
103103
}
104104

105105
pid_t pid;
106-
int rc = posix_spawn(&pid, exe, nullptr, nullptr, const_cast<char *const*>(argv), environ);
106+
int rc = posix_spawnp(&pid, exe, nullptr, nullptr, const_cast<char *const*>(argv), environ);
107107
if (rc != 0) {
108108
zig_panic("posix_spawn failed: %s", strerror(rc));
109109
}

std/build.zig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ pub const LibExeObjStep = struct.{
836836
assembly_files: ArrayList([]const u8),
837837
packages: ArrayList(Pkg),
838838
build_options_contents: std.Buffer,
839+
system_linker_hack: bool,
839840

840841
// C only stuff
841842
source_files: ArrayList([]const u8),
@@ -930,6 +931,7 @@ pub const LibExeObjStep = struct.{
930931
.disable_libc = true,
931932
.build_options_contents = std.Buffer.initSize(builder.allocator, 0) catch unreachable,
932933
.c_std = Builder.CStd.C99,
934+
.system_linker_hack = false,
933935
};
934936
self.computeOutFileNames();
935937
return self;
@@ -965,6 +967,7 @@ pub const LibExeObjStep = struct.{
965967
.is_zig = false,
966968
.linker_script = null,
967969
.c_std = Builder.CStd.C99,
970+
.system_linker_hack = false,
968971

969972
.root_src = undefined,
970973
.verbose_link = false,
@@ -1162,6 +1165,10 @@ pub const LibExeObjStep = struct.{
11621165
self.disable_libc = disable;
11631166
}
11641167

1168+
pub fn enableSystemLinkerHack(self: *LibExeObjStep) void {
1169+
self.system_linker_hack = true;
1170+
}
1171+
11651172
fn make(step: *Step) !void {
11661173
const self = @fieldParentPtr(LibExeObjStep, "step", step);
11671174
return if (self.is_zig) self.makeZig() else self.makeC();
@@ -1338,6 +1345,9 @@ pub const LibExeObjStep = struct.{
13381345
if (self.no_rosegment) {
13391346
try zig_args.append("--no-rosegment");
13401347
}
1348+
if (self.system_linker_hack) {
1349+
try zig_args.append("--system-linker-hack");
1350+
}
13411351

13421352
try builder.spawnChild(zig_args.toSliceConst());
13431353

@@ -1646,6 +1656,7 @@ pub const TestStep = struct.{
16461656
object_files: ArrayList([]const u8),
16471657
no_rosegment: bool,
16481658
output_path: ?[]const u8,
1659+
system_linker_hack: bool,
16491660

16501661
pub fn init(builder: *Builder, root_src: []const u8) TestStep {
16511662
const step_name = builder.fmt("test {}", root_src);
@@ -1665,6 +1676,7 @@ pub const TestStep = struct.{
16651676
.object_files = ArrayList([]const u8).init(builder.allocator),
16661677
.no_rosegment = false,
16671678
.output_path = null,
1679+
.system_linker_hack = false,
16681680
};
16691681
}
16701682

@@ -1747,6 +1759,10 @@ pub const TestStep = struct.{
17471759
self.exec_cmd_args = args;
17481760
}
17491761

1762+
pub fn enableSystemLinkerHack(self: *TestStep) void {
1763+
self.system_linker_hack = true;
1764+
}
1765+
17501766
fn make(step: *Step) !void {
17511767
const self = @fieldParentPtr(TestStep, "step", step);
17521768
const builder = self.builder;
@@ -1851,6 +1867,9 @@ pub const TestStep = struct.{
18511867
if (self.no_rosegment) {
18521868
try zig_args.append("--no-rosegment");
18531869
}
1870+
if (self.system_linker_hack) {
1871+
try zig_args.append("--system-linker-hack");
1872+
}
18541873

18551874
try builder.spawnChild(zig_args.toSliceConst());
18561875
}

0 commit comments

Comments
 (0)