Commit 260c5aed86

Isaac Freund <ifreund@ifreund.xyz>
2021-06-21 22:45:43
stage2: add --sysroot link option
This feature is necessary for cross-compiling code that dynamically links system libraries, at least with the current feature set of lld.
1 parent 411f9c6
src/link/Elf.zig
@@ -1354,6 +1354,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
         man.hash.add(allow_shlib_undefined);
         man.hash.add(self.base.options.bind_global_refs_locally);
         man.hash.add(self.base.options.tsan);
+        man.hash.addOptionalBytes(self.base.options.sysroot);
 
         // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
         _ = try man.hit();
@@ -1423,6 +1424,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
 
         try argv.append("-error-limit=0");
 
+        if (self.base.options.sysroot) |sysroot| {
+            try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot}));
+        }
+
         if (self.base.options.lto) {
             switch (self.base.options.optimize_mode) {
                 .Debug => {},
src/link/MachO.zig
@@ -704,7 +704,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
         man.hash.add(allow_shlib_undefined);
         man.hash.add(self.base.options.bind_global_refs_locally);
         man.hash.add(self.base.options.system_linker_hack);
-        man.hash.addOptionalBytes(self.base.options.syslibroot);
+        man.hash.addOptionalBytes(self.base.options.sysroot);
 
         // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
         _ = try man.hit();
@@ -789,7 +789,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 zld.deinit();
             }
             zld.arch = target.cpu.arch;
-            zld.syslibroot = self.base.options.syslibroot;
+            zld.syslibroot = self.base.options.sysroot;
             zld.stack_size = stack_size;
 
             // Positional arguments to the linker such as object files and static archives.
@@ -833,7 +833,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             try resolvePaths(
                 arena,
                 &libs,
-                self.base.options.syslibroot,
+                self.base.options.sysroot,
                 self.base.options.lib_dirs,
                 search_lib_names.items,
                 .lib,
@@ -856,7 +856,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             try resolvePaths(
                 arena,
                 &libs,
-                self.base.options.syslibroot,
+                self.base.options.sysroot,
                 self.base.options.framework_dirs,
                 self.base.options.frameworks,
                 .framework,
@@ -868,7 +868,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 try argv.append("zig");
                 try argv.append("ld");
 
-                if (self.base.options.syslibroot) |syslibroot| {
+                if (self.base.options.sysroot) |syslibroot| {
                     try argv.append("-syslibroot");
                     try argv.append(syslibroot);
                 }
@@ -1017,7 +1017,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             }
         }
 
-        if (self.base.options.syslibroot) |dir| {
+        if (self.base.options.sysroot) |dir| {
             try argv.append("-syslibroot");
             try argv.append(dir);
         }
src/Compilation.zig
@@ -612,6 +612,7 @@ pub const InitOptions = struct {
     output_mode: std.builtin.OutputMode,
     thread_pool: *ThreadPool,
     dynamic_linker: ?[]const u8 = null,
+    sysroot: ?[]const u8 = null,
     /// `null` means to not emit a binary file.
     emit_bin: ?EmitLoc,
     /// `null` means to not emit a C header file.
@@ -879,25 +880,32 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             break :blk false;
         };
 
-        const DarwinOptions = struct {
-            syslibroot: ?[]const u8 = null,
-            system_linker_hack: bool = false,
+        const darwin_can_use_system_linker_and_sdk =
+            // comptime conditions
+            ((build_options.have_llvm and comptime std.Target.current.isDarwin()) and
+            // runtime conditions
+            (use_lld and std.builtin.os.tag == .macos and options.target.isDarwin()));
+
+        const darwin_system_linker_hack = blk: {
+            if (darwin_can_use_system_linker_and_sdk) {
+                break :blk std.os.getenv("ZIG_SYSTEM_LINKER_HACK") != null;
+            } else {
+                break :blk false;
+            }
         };
 
-        const darwin_options: DarwinOptions = if (build_options.have_llvm and comptime std.Target.current.isDarwin()) outer: {
-            const opts: DarwinOptions = if (use_lld and std.builtin.os.tag == .macos and options.target.isDarwin()) inner: {
+        const sysroot = blk: {
+            if (options.sysroot) |sysroot| {
+                break :blk sysroot;
+            } else if (darwin_can_use_system_linker_and_sdk) {
                 // TODO Revisit this targeting versions lower than macOS 11 when LLVM 12 is out.
                 // See https://github.com/ziglang/zig/issues/6996
                 const at_least_big_sur = options.target.os.getVersionRange().semver.min.major >= 11;
-                const syslibroot = if (at_least_big_sur) try std.zig.system.getSDKPath(arena) else null;
-                const system_linker_hack = std.os.getenv("ZIG_SYSTEM_LINKER_HACK") != null;
-                break :inner .{
-                    .syslibroot = syslibroot,
-                    .system_linker_hack = system_linker_hack,
-                };
-            } else .{};
-            break :outer opts;
-        } else .{};
+                break :blk if (at_least_big_sur) try std.zig.system.getSDKPath(arena) else null;
+            } else {
+                break :blk null;
+            }
+        };
 
         const lto = blk: {
             if (options.want_lto) |explicit| {
@@ -908,7 +916,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
                 break :blk false;
             } else if (options.c_source_files.len == 0) {
                 break :blk false;
-            } else if (darwin_options.system_linker_hack) {
+            } else if (darwin_system_linker_hack) {
                 break :blk false;
             } else switch (options.output_mode) {
                 .Lib, .Obj => break :blk false,
@@ -1281,13 +1289,14 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .module = module,
             .target = options.target,
             .dynamic_linker = options.dynamic_linker,
+            .sysroot = sysroot,
             .output_mode = options.output_mode,
             .link_mode = link_mode,
             .object_format = ofmt,
             .optimize_mode = options.optimize_mode,
             .use_lld = use_lld,
             .use_llvm = use_llvm,
-            .system_linker_hack = darwin_options.system_linker_hack,
+            .system_linker_hack = darwin_system_linker_hack,
             .link_libc = link_libc,
             .link_libcpp = link_libcpp,
             .link_libunwind = link_libunwind,
@@ -1295,7 +1304,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             .frameworks = options.frameworks,
             .framework_dirs = options.framework_dirs,
             .system_libs = system_libs,
-            .syslibroot = darwin_options.syslibroot,
             .wasi_emulated_libs = options.wasi_emulated_libs,
             .lib_dirs = options.lib_dirs,
             .rpath_list = options.rpath_list,
src/link.zig
@@ -38,6 +38,8 @@ pub const Options = struct {
     /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
     module: ?*Module,
     dynamic_linker: ?[]const u8,
+    /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin)
+    sysroot: ?[]const u8,
     /// Used for calculating how much space to reserve for symbols in case the binary file
     /// does not already have a symbol table.
     symbol_count_hint: u64 = 32,
@@ -104,8 +106,6 @@ pub const Options = struct {
     llvm_cpu_features: ?[*:0]const u8,
     /// Extra args passed directly to LLD. Ignored when not linking with LLD.
     extra_lld_args: []const []const u8,
-    /// Darwin-only. Set the root path to the system libraries and frameworks.
-    syslibroot: ?[]const u8,
 
     objects: []const []const u8,
     framework_dirs: []const []const u8,
src/main.zig
@@ -379,6 +379,7 @@ const usage_build_generic =
     \\  -T[script], --script [script]  Use a custom linker script
     \\  --version-script [path]        Provide a version .map file
     \\  --dynamic-linker [path]        Set the dynamic interpreter path (usually ld.so)
+    \\  --sysroot [path]               Set the system root directory (usually /)
     \\  --version [ver]                Dynamic library semver
     \\  -fsoname[=name]                (Linux) Override the default SONAME value
     \\  -fno-soname                    (Linux) Disable emitting a SONAME
@@ -604,6 +605,7 @@ fn buildOutputType(
     var link_eh_frame_hdr = false;
     var link_emit_relocs = false;
     var each_lib_rpath: ?bool = null;
+    var sysroot: ?[]const u8 = null;
     var libc_paths_file: ?[]const u8 = try optionalStringEnvVar(arena, "ZIG_LIBC");
     var machine_code_model: std.builtin.CodeModel = .default;
     var runtime_args_start: ?usize = null;
@@ -865,6 +867,10 @@ fn buildOutputType(
                         if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
                         i += 1;
                         target_dynamic_linker = args[i];
+                    } else if (mem.eql(u8, arg, "--sysroot")) {
+                        if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
+                        i += 1;
+                        sysroot = args[i];
                     } else if (mem.eql(u8, arg, "--libc")) {
                         if (i + 1 >= args.len) fatal("expected parameter after {s}", .{arg});
                         i += 1;
@@ -1641,7 +1647,9 @@ fn buildOutputType(
             want_native_include_dirs = true;
     }
 
-    if (cross_target.isNativeOs() and (system_libs.items.len != 0 or want_native_include_dirs)) {
+    if (sysroot == null and cross_target.isNativeOs() and
+        (system_libs.items.len != 0 or want_native_include_dirs))
+    {
         const paths = std.zig.system.NativePaths.detect(arena, target_info) catch |err| {
             fatal("unable to detect native system paths: {s}", .{@errorName(err)});
         };
@@ -1918,6 +1926,7 @@ fn buildOutputType(
         .is_native_os = cross_target.isNativeOs(),
         .is_native_abi = cross_target.isNativeAbi(),
         .dynamic_linker = target_info.dynamic_linker.get(),
+        .sysroot = sysroot,
         .output_mode = output_mode,
         .root_pkg = root_pkg,
         .emit_bin = emit_bin_loc,