Skip to content

Commit 46752f5

Browse files
committed
fix: make zig cc pass -l/-L like Clang/GCC for ELF
This commit makes the way `zig cc` passes `-l`/`-L` flags for ELF linking consistent with Clang and GCC. The fix itself is in `src/link/Elf.zig`. The new test is in `test/link/elf.zig`. It follows the example from #19699. The rest of the changes are new functionality needed for the test: * `omit_soname` option for `Build.Module`, `OverlayOptions`. This passes `-fno-soname` to the linker when enabled. * `addLibraryPathSpecial` for `Build.Module`, `Build.Step.Compile`. Like `addLibraryPath` but for `[]const u8`. `addRPathSpecial` is also added for completeness. * `setCwd` for `Build.Step.Compile`. Allows setting current directory for compile steps. * `opt_cwd` for `evalZigProcess` in `Build.Step`. Allows running `evalZigProcess` in a specific directory. Needed for setting current directory for compile steps. * `--zig-lib-dir` is now appended differently in `Build.Step.Compile`. This is needed for compatibility with `setCwd`.
1 parent 05d8b56 commit 46752f5

File tree

9 files changed

+123
-22
lines changed

9 files changed

+123
-22
lines changed

lib/compiler/build_runner.zig

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1496,7 +1496,10 @@ fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void {
14961496

14971497
.config_header_step => |other| step.dependOn(&other.step),
14981498
};
1499-
for (mod.lib_paths.items) |lp| lp.addStepDependencies(step);
1499+
for (mod.lib_paths.items) |lib_path| switch (lib_path) {
1500+
.lazy_path => |lp| lp.addStepDependencies(step),
1501+
.special => {},
1502+
};
15001503
for (mod.rpaths.items) |rpath| switch (rpath) {
15011504
.lazy_path => |lp| lp.addStepDependencies(step),
15021505
.special => {},

lib/std/Build/Module.zig

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ dwarf_format: ?std.dwarf.Format,
1212

1313
c_macros: std.ArrayListUnmanaged([]const u8),
1414
include_dirs: std.ArrayListUnmanaged(IncludeDir),
15-
lib_paths: std.ArrayListUnmanaged(LazyPath),
15+
lib_paths: std.ArrayListUnmanaged(LibraryPath),
1616
rpaths: std.ArrayListUnmanaged(RPath),
1717
frameworks: std.StringArrayHashMapUnmanaged(LinkFrameworkOptions),
1818
link_objects: std.ArrayListUnmanaged(LinkObject),
1919

2020
strip: ?bool,
21+
omit_soname: ?bool,
2122
unwind_tables: ?std.builtin.UnwindTables,
2223
single_threaded: ?bool,
2324
stack_protector: ?bool,
@@ -41,6 +42,11 @@ export_symbol_names: []const []const u8 = &.{},
4142
/// Use `getGraph` instead of accessing this field directly.
4243
cached_graph: Graph = .{ .modules = &.{}, .names = &.{} },
4344

45+
pub const LibraryPath = union(enum) {
46+
lazy_path: LazyPath,
47+
special: []const u8,
48+
};
49+
4450
pub const RPath = union(enum) {
4551
lazy_path: LazyPath,
4652
special: []const u8,
@@ -247,6 +253,7 @@ pub const CreateOptions = struct {
247253
link_libcpp: ?bool = null,
248254
single_threaded: ?bool = null,
249255
strip: ?bool = null,
256+
omit_soname: ?bool = null,
250257
unwind_tables: ?std.builtin.UnwindTables = null,
251258
dwarf_format: ?std.dwarf.Format = null,
252259
code_model: std.builtin.CodeModel = .default,
@@ -296,6 +303,7 @@ pub fn init(
296303
.frameworks = .{},
297304
.link_objects = .{},
298305
.strip = options.strip,
306+
.omit_soname = options.omit_soname,
299307
.unwind_tables = options.unwind_tables,
300308
.single_threaded = options.single_threaded,
301309
.stack_protector = options.stack_protector,
@@ -513,7 +521,12 @@ pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void {
513521

514522
pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void {
515523
const b = m.owner;
516-
m.lib_paths.append(b.allocator, directory_path.dupe(b)) catch @panic("OOM");
524+
m.lib_paths.append(b.allocator, .{ .lazy_path = directory_path.dupe(b) }) catch @panic("OOM");
525+
}
526+
527+
pub fn addLibraryPathSpecial(m: *Module, bytes: []const u8) void {
528+
const b = m.owner;
529+
m.lib_paths.append(b.allocator, .{ .special = b.dupe(bytes) }) catch @panic("OOM");
517530
}
518531

519532
pub fn addRPath(m: *Module, directory_path: LazyPath) void {
@@ -545,6 +558,7 @@ pub fn appendZigProcessFlags(
545558
const b = m.owner;
546559

547560
try addFlag(zig_args, m.strip, "-fstrip", "-fno-strip");
561+
try addFlag(zig_args, m.omit_soname, "-fno-soname", "-fsoname");
548562
try addFlag(zig_args, m.single_threaded, "-fsingle-threaded", "-fno-single-threaded");
549563
try addFlag(zig_args, m.stack_check, "-fstack-check", "-fno-stack-check");
550564
try addFlag(zig_args, m.stack_protector, "-fstack-protector", "-fno-stack-protector");
@@ -611,10 +625,16 @@ pub fn appendZigProcessFlags(
611625
try zig_args.appendSlice(m.c_macros.items);
612626

613627
try zig_args.ensureUnusedCapacity(2 * m.lib_paths.items.len);
614-
for (m.lib_paths.items) |lib_path| {
615-
zig_args.appendAssumeCapacity("-L");
616-
zig_args.appendAssumeCapacity(lib_path.getPath2(b, asking_step));
617-
}
628+
for (m.lib_paths.items) |lib_path| switch (lib_path) {
629+
.lazy_path => |lp| {
630+
zig_args.appendAssumeCapacity("-L");
631+
zig_args.appendAssumeCapacity(lp.getPath2(b, asking_step));
632+
},
633+
.special => |bytes| {
634+
zig_args.appendAssumeCapacity("-L");
635+
zig_args.appendAssumeCapacity(bytes);
636+
},
637+
};
618638

619639
try zig_args.ensureUnusedCapacity(2 * m.rpaths.items.len);
620640
for (m.rpaths.items) |rpath| switch (rpath) {

lib/std/Build/Step.zig

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ pub const ZigProcess = struct {
371371
/// is the zig compiler - the same version that compiled the build runner.
372372
pub fn evalZigProcess(
373373
s: *Step,
374+
opt_cwd: ?[]const u8,
374375
argv: []const []const u8,
375376
prog_node: std.Progress.Node,
376377
watch: bool,
@@ -401,7 +402,7 @@ pub fn evalZigProcess(
401402
};
402403
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
403404
s.clearZigProcess();
404-
try handleChildProcessTerm(s, term, null, argv);
405+
try handleChildProcessTerm(s, term, opt_cwd, argv);
405406
return error.MakeFailed;
406407
}
407408

@@ -412,8 +413,8 @@ pub fn evalZigProcess(
412413
const arena = b.allocator;
413414
const gpa = arena;
414415

415-
try handleChildProcUnsupported(s, null, argv);
416-
try handleVerbose(s.owner, null, argv);
416+
try handleChildProcUnsupported(s, opt_cwd, argv);
417+
try handleVerbose(s.owner, opt_cwd, argv);
417418

418419
var child = std.process.Child.init(argv, arena);
419420
child.env_map = &b.graph.env_map;
@@ -422,6 +423,7 @@ pub fn evalZigProcess(
422423
child.stderr_behavior = .Pipe;
423424
child.request_resource_usage_statistics = true;
424425
child.progress_node = prog_node;
426+
child.cwd = opt_cwd;
425427

426428
child.spawn() catch |err| return s.fail("failed to spawn zig compiler {s}: {s}", .{
427429
argv[0], @errorName(err),
@@ -463,15 +465,15 @@ pub fn evalZigProcess(
463465
else => {},
464466
};
465467

466-
try handleChildProcessTerm(s, term, null, argv);
468+
try handleChildProcessTerm(s, term, opt_cwd, argv);
467469
}
468470

469471
// This is intentionally printed for failure on the first build but not for
470472
// subsequent rebuilds.
471473
if (s.result_error_bundle.errorMessageCount() > 0) {
472474
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
473475
s.result_error_bundle.errorMessageCount(),
474-
try allocPrintCmd(arena, null, argv),
476+
try allocPrintCmd(arena, opt_cwd, argv),
475477
});
476478
}
477479

lib/std/Build/Step/Compile.zig

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const fs = std.fs;
55
const assert = std.debug.assert;
66
const panic = std.debug.panic;
77
const ArrayList = std.ArrayList;
8+
const Build = std.Build;
89
const StringHashMap = std.StringHashMap;
910
const Sha256 = std.crypto.hash.sha2.Sha256;
1011
const Allocator = mem.Allocator;
@@ -240,6 +241,9 @@ zig_process: ?*Step.ZigProcess,
240241
/// builtin fuzzer, see the `fuzz` flag in `Module`.
241242
sanitize_coverage_trace_pc_guard: ?bool = null,
242243

244+
/// Use `setCwd` to set the initial current working directory
245+
cwd: ?Build.LazyPath = null,
246+
243247
pub const ExpectedCompileErrors = union(enum) {
244248
contains: []const u8,
245249
exact: []const []const u8,
@@ -939,10 +943,18 @@ pub fn addLibraryPath(compile: *Compile, directory_path: LazyPath) void {
939943
compile.root_module.addLibraryPath(directory_path);
940944
}
941945

946+
pub fn addLibraryPathSpecial(compile: *Compile, bytes: []const u8) void {
947+
compile.root_module.addLibraryPathSpecial(bytes);
948+
}
949+
942950
pub fn addRPath(compile: *Compile, directory_path: LazyPath) void {
943951
compile.root_module.addRPath(directory_path);
944952
}
945953

954+
pub fn addRPathSpecial(compile: *Compile, bytes: []const u8) void {
955+
compile.root_module.addRPathSpecial(bytes);
956+
}
957+
946958
pub fn addSystemFrameworkPath(compile: *Compile, directory_path: LazyPath) void {
947959
compile.root_module.addSystemFrameworkPath(directory_path);
948960
}
@@ -1691,7 +1703,7 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
16911703

16921704
if (opt_zig_lib_dir) |zig_lib_dir| {
16931705
try zig_args.append("--zig-lib-dir");
1694-
try zig_args.append(zig_lib_dir);
1706+
try zig_args.append(try fs.cwd().realpathAlloc(arena, zig_lib_dir));
16951707
}
16961708

16971709
try addFlag(&zig_args, "PIE", compile.pie);
@@ -1789,9 +1801,11 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
17891801
const b = step.owner;
17901802
const compile: *Compile = @fieldParentPtr("step", step);
17911803

1804+
const cwd: ?[]const u8 = if (compile.cwd) |lazy_cwd| lazy_cwd.getPath(b) else null;
17921805
const zig_args = try getZigArgs(compile, false);
17931806

17941807
const maybe_output_dir = step.evalZigProcess(
1808+
cwd,
17951809
zig_args,
17961810
options.progress_node,
17971811
(b.graph.incremental == true) and options.watch,
@@ -1874,8 +1888,9 @@ pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path {
18741888
c.step.result_error_bundle.deinit(gpa);
18751889
c.step.result_error_bundle = std.zig.ErrorBundle.empty;
18761890

1891+
const cwd: ?[]const u8 = if (c.cwd) |lazy_cwd| lazy_cwd.getPath(c.step.owner) else null;
18771892
const zig_args = try getZigArgs(c, true);
1878-
const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false);
1893+
const maybe_output_bin_path = try c.step.evalZigProcess(cwd, zig_args, progress_node, false);
18791894
return maybe_output_bin_path.?;
18801895
}
18811896

@@ -2116,3 +2131,8 @@ pub fn getCompileDependencies(start: *Compile, chase_dynamic: bool) []const *Com
21162131

21172132
return compiles.keys();
21182133
}
2134+
2135+
pub fn setCwd(self: *Compile, cwd: Build.LazyPath) void {
2136+
cwd.addStepDependencies(&self.step);
2137+
self.cwd = cwd;
2138+
}

lib/std/Build/Step/ObjCopy.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
236236
try argv.appendSlice(&.{ full_src_path, full_dest_path });
237237

238238
try argv.append("--listen=-");
239-
_ = try step.evalZigProcess(argv.items, prog_node, false);
239+
_ = try step.evalZigProcess(null, argv.items, prog_node, false);
240240

241241
objcopy.output_file.path = full_dest_path;
242242
if (objcopy.output_file_debug) |*file| file.path = full_dest_path_debug;

lib/std/Build/Step/TranslateC.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
200200
const c_source_path = translate_c.source.getPath2(b, step);
201201
try argv_list.append(c_source_path);
202202

203-
const output_dir = try step.evalZigProcess(argv_list.items, prog_node, false);
203+
const output_dir = try step.evalZigProcess(null, argv_list.items, prog_node, false);
204204

205205
const basename = std.fs.path.stem(std.fs.path.basename(c_source_path));
206206
translate_c.out_basename = b.fmt("{s}.zig", .{basename});

src/link/Elf.zig

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,9 +1986,9 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
19861986

19871987
// Shared libraries.
19881988
if (is_exe_or_dyn_lib) {
1989-
// Worst-case, we need an --as-needed argument for every lib, as well
1989+
// Worst-case, we need --as-needed, -l, -L arguments for every lib, as well
19901990
// as one before and one after.
1991-
try argv.ensureUnusedCapacity(2 * self.base.comp.link_inputs.len + 2);
1991+
try argv.ensureUnusedCapacity(5 * self.base.comp.link_inputs.len + 2);
19921992
argv.appendAssumeCapacity("--as-needed");
19931993
var as_needed = true;
19941994

@@ -2010,10 +2010,15 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
20102010
}
20112011

20122012
// By this time, we depend on these libs being dynamically linked
2013-
// libraries and not static libraries (the check for that needs to be earlier),
2014-
// but they could be full paths to .so files, in which case we
2015-
// want to avoid prepending "-l".
2016-
argv.appendAssumeCapacity(try dso.path.toString(arena));
2013+
// libraries and not static libraries (the check for that needs to be earlier).
2014+
if (fs.path.dirname(try dso.path.toString(arena))) |dir| {
2015+
argv.appendAssumeCapacity("-L");
2016+
argv.appendAssumeCapacity(dir);
2017+
}
2018+
const stem = dso.path.stem();
2019+
assert(mem.startsWith(u8, stem, "lib"));
2020+
argv.appendAssumeCapacity("-l");
2021+
argv.appendAssumeCapacity(stem[3..]);
20172022
},
20182023
};
20192024

test/link/elf.zig

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
8282

8383
// glibc tests
8484
elf_step.dependOn(testAsNeeded(b, .{ .target = gnu_target }));
85+
elf_step.dependOn(testLibraryPathsCompatibility(b, .{ .target = gnu_target, .use_lld = true }));
8586
// https://github.com/ziglang/zig/issues/17430
8687
// elf_step.dependOn(testCanonicalPlt(b, .{ .target = gnu_target }));
8788
elf_step.dependOn(testCommentString(b, .{ .target = gnu_target }));
@@ -308,6 +309,54 @@ fn testAsNeeded(b: *Build, opts: Options) *Step {
308309
return test_step;
309310
}
310311

312+
fn testLibraryPathsCompatibility(b: *Build, opts: Options) *Step {
313+
const test_step = addTestStep(b, "library-paths-compatibility", opts);
314+
315+
const main_o = addObject(b, opts, .{
316+
.name = "main",
317+
.c_source_bytes =
318+
\\#include <stdio.h>
319+
\\int foo();
320+
\\int main() {
321+
\\ printf("%d\n", foo());
322+
\\ return 0;
323+
\\}
324+
\\
325+
,
326+
});
327+
main_o.linkLibC();
328+
329+
const libfoo = addSharedLibrary(b, opts, .{ .name = "foo", .omit_soname = true });
330+
addCSourceBytes(libfoo, "int foo() { return 42; }", &.{});
331+
332+
{
333+
const scripts = WriteFile.create(b);
334+
const path = scripts.addCopyFile(libfoo.getEmittedBin(), "foo/libfoo.so");
335+
336+
const exe = addExecutable(b, opts, .{ .name = "test" });
337+
exe.addObject(main_o);
338+
339+
exe.setCwd(scripts.getDirectory());
340+
exe.addLibraryPathSpecial("foo");
341+
exe.addRPath(path.dirname());
342+
343+
exe.linkSystemLibrary2("foo", .{ .needed = false });
344+
exe.linkLibC();
345+
346+
const run = addRunArtifact(exe);
347+
run.expectStdOutEqual("42\n");
348+
test_step.dependOn(&run.step);
349+
350+
const check = exe.checkObject();
351+
check.checkInDynamicSection();
352+
check.checkExact("NEEDED libfoo.so");
353+
check.checkNotPresent("NEEDED foo/libfoo.so");
354+
test_step.dependOn(&check.step);
355+
}
356+
357+
return test_step;
358+
}
359+
311360
fn testCanonicalPlt(b: *Build, opts: Options) *Step {
312361
const test_step = addTestStep(b, "canonical-plt", opts);
313362

test/link/link.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const OverlayOptions = struct {
4444
zig_source_bytes: ?[]const u8 = null,
4545
pic: ?bool = null,
4646
strip: ?bool = null,
47+
omit_soname: ?bool = null,
4748
};
4849

4950
pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile {
@@ -97,6 +98,7 @@ fn createModule(b: *Build, base: Options, overlay: OverlayOptions) *Build.Module
9798
},
9899
.pic = overlay.pic,
99100
.strip = if (base.strip) |s| s else overlay.strip,
101+
.omit_soname = overlay.omit_soname,
100102
});
101103

102104
if (overlay.objcpp_source_bytes) |bytes| {

0 commit comments

Comments
 (0)