diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 0fa8a9a6234f..b318e554c4a8 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -18,6 +18,7 @@ frameworks: std.StringArrayHashMapUnmanaged(LinkFrameworkOptions), link_objects: std.ArrayListUnmanaged(LinkObject), strip: ?bool, +soname: ?std.zig.SoName, unwind_tables: ?std.builtin.UnwindTables, single_threaded: ?bool, stack_protector: ?bool, @@ -238,6 +239,7 @@ pub const CreateOptions = struct { link_libcpp: ?bool = null, single_threaded: ?bool = null, strip: ?bool = null, + soname: ?std.zig.SoName = null, unwind_tables: ?std.builtin.UnwindTables = null, dwarf_format: ?std.dwarf.Format = null, code_model: std.builtin.CodeModel = .default, @@ -288,6 +290,7 @@ pub fn init( .frameworks = .{}, .link_objects = .{}, .strip = options.strip, + .soname = options.soname, .unwind_tables = options.unwind_tables, .single_threaded = options.single_threaded, .stack_protector = options.stack_protector, @@ -561,6 +564,14 @@ pub fn appendZigProcessFlags( .full => try zig_args.append("-fsanitize-c=full"), }; + if (m.soname) |soname| { + try zig_args.append(switch (soname) { + .no => "-fno-soname", + .yes_default_value => "-fsoname", + .yes => |value| b.fmt("-fsoname={s}", .{value}), + }); + } + if (m.dwarf_format) |dwarf_format| { try zig_args.append(switch (dwarf_format) { .@"32" => "-gdwarf32", diff --git a/lib/std/zig.zig b/lib/std/zig.zig index fd22da91db1a..aaea481d0fe2 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -319,6 +319,12 @@ pub const BuildId = union(enum) { pub const LtoMode = enum { none, full, thin }; +pub const SoName = union(enum) { + no, + yes_default_value, + yes: []const u8, +}; + /// Renders a `std.Target.Cpu` value into a textual representation that can be parsed /// via the `-mcpu` flag passed to the Zig compiler. /// Appends the result to `buffer`. diff --git a/src/link.zig b/src/link.zig index 0fbd4b28cfdb..a34d0434f85a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1609,6 +1609,8 @@ pub const Input = union(enum) { needed: bool, weak: bool, reexport: bool, + name: ?[]const u8, + lib_directory: Directory, }; pub const DsoExact = struct { @@ -1661,6 +1663,8 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { man.hash.add(dso.needed); man.hash.add(dso.weak); man.hash.add(dso.reexport); + man.hash.addOptionalBytes(dso.name); + man.hash.addOptionalBytes(dso.lib_directory.path); }, .dso_exact => |dso_exact| { man.hash.addBytes(dso_exact.name); @@ -1936,7 +1940,7 @@ fn resolveLibInput( else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }), }; errdefer file.close(); - return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query, name_query.name, lib_directory); } { @@ -1950,10 +1954,10 @@ fn resolveLibInput( }), }; try checked_paths.writer(gpa).print("\n {f}", .{test_path}); - switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{ + switch (try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, lib_directory, target, .{ .path = test_path, .query = name_query.query, - }, link_mode, color)) { + }, link_mode, color, name_query.name)) { .no_match => {}, .ok => return .ok, } @@ -1974,7 +1978,7 @@ fn resolveLibInput( }), }; errdefer file.close(); - return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query, name_query.name, lib_directory); } // In the case of MinGW, the main check will be .lib but we also need to @@ -1990,7 +1994,7 @@ fn resolveLibInput( else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }), }; errdefer file.close(); - return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query, name_query.name, lib_directory); } return .no_match; @@ -2002,6 +2006,8 @@ fn finishResolveLibInput( file: std.fs.File, link_mode: std.builtin.LinkMode, query: UnresolvedInput.Query, + name: ?[]const u8, + lib_directory: Directory, ) ResolveLibInputResult { switch (link_mode) { .static => resolved_inputs.appendAssumeCapacity(.{ .archive = .{ @@ -2016,6 +2022,8 @@ fn finishResolveLibInput( .needed = query.needed, .weak = query.weak, .reexport = query.reexport, + .name = name, + .lib_directory = lib_directory, } }), } return .ok; @@ -2035,8 +2043,8 @@ fn resolvePathInput( color: std.zig.Color, ) Allocator.Error!?ResolveLibInputResult { switch (Compilation.classifyFileExt(pq.path.sub_path)) { - .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .static, color), - .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, target, pq, .dynamic, color), + .static_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, Directory.cwd(), target, pq, .static, color, null), + .shared_library => return try resolvePathInputLib(gpa, arena, unresolved_inputs, resolved_inputs, ld_script_bytes, Directory.cwd(), target, pq, .dynamic, color, null), .object => { var file = pq.path.root_dir.handle.openFile(pq.path.sub_path, .{}) catch |err| fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) }); @@ -2072,10 +2080,12 @@ fn resolvePathInputLib( resolved_inputs: *std.ArrayListUnmanaged(Input), /// Allocated via `gpa`. ld_script_bytes: *std.ArrayListUnmanaged(u8), + lib_directory: Directory, target: *const std.Target, pq: UnresolvedInput.PathQuery, link_mode: std.builtin.LinkMode, color: std.zig.Color, + name: ?[]const u8, ) Allocator.Error!ResolveLibInputResult { try resolved_inputs.ensureUnusedCapacity(gpa, 1); @@ -2100,7 +2110,7 @@ fn resolvePathInputLib( const buf = ld_script_bytes.items[0..n]; if (mem.startsWith(u8, buf, std.elf.MAGIC) or mem.startsWith(u8, buf, std.elf.ARMAG)) { // Appears to be an ELF or archive file. - return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query, name, lib_directory); } const stat = file.stat() catch |err| fatal("failed to stat {f}: {s}", .{ test_path, @errorName(err) }); @@ -2166,7 +2176,7 @@ fn resolvePathInputLib( }), }; errdefer file.close(); - return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query); + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, pq.query, name, lib_directory); } pub fn openObject(path: Path, must_link: bool, hidden: bool) !Input.Object { @@ -2189,6 +2199,8 @@ pub fn openDso(path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso .needed = needed, .weak = weak, .reexport = reexport, + .name = null, + .lib_directory = Directory.cwd(), }; } diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 1aeeb5d2145e..ea33013f82ca 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1179,10 +1179,17 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { } // By this time, we depend on these libs being dynamically linked - // libraries and not static libraries (the check for that needs to be earlier), - // but they could be full paths to .so files, in which case we - // want to avoid prepending "-l". - argv.appendAssumeCapacity(try dso.path.toString(arena)); + // libraries and not static libraries (the check for that needs to be earlier). + if (dso.name) |name| { + if (dso.lib_directory.path) |path| { + argv.appendAssumeCapacity("-L"); + argv.appendAssumeCapacity(path); + } + argv.appendAssumeCapacity(if (dso.weak) "-weak-l" else "-l"); + argv.appendAssumeCapacity(name); + } else { + argv.appendAssumeCapacity(try dso.path.toString(arena)); + } }, }; diff --git a/src/main.zig b/src/main.zig index 1d178730b054..be014a48bc0f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,6 +16,7 @@ const native_os = builtin.os.tag; const Cache = std.Build.Cache; const Path = std.Build.Cache.Path; const Directory = std.Build.Cache.Directory; +const SoName = std.zig.SoName; const EnvVar = std.zig.EnvVar; const LibCInstallation = std.zig.LibCInstallation; const AstGen = std.zig.AstGen; @@ -685,12 +686,6 @@ const usage_build_generic = \\ ; -const SOName = union(enum) { - no, - yes_default_value, - yes: []const u8, -}; - const EmitBin = union(enum) { no, yes_default_path, @@ -837,7 +832,7 @@ fn buildOutputType( var target_arch_os_abi: ?[]const u8 = null; var target_mcpu: ?[]const u8 = null; var emit_h: Emit = .no; - var soname: SOName = undefined; + var soname: SoName = undefined; var want_compiler_rt: ?bool = null; var want_ubsan_rt: ?bool = null; var linker_script: ?[]const u8 = null; diff --git a/test/link/elf.zig b/test/link/elf.zig index f6dfbbea86fc..5042f96749f1 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -82,6 +82,8 @@ pub fn testAll(b: *Build, build_opts: BuildOptions) *Step { // glibc tests elf_step.dependOn(testAsNeeded(b, .{ .target = gnu_target })); + elf_step.dependOn(testLibraryPathsCompatibility(b, .{ .target = gnu_target, .use_lld = true })); + elf_step.dependOn(testLibraryPathsCompatibility(b, .{ .target = gnu_target, .use_lld = false })); // https://github.com/ziglang/zig/issues/17430 // elf_step.dependOn(testCanonicalPlt(b, .{ .target = gnu_target })); elf_step.dependOn(testCommentString(b, .{ .target = gnu_target })); @@ -309,6 +311,53 @@ fn testAsNeeded(b: *Build, opts: Options) *Step { return test_step; } +fn testLibraryPathsCompatibility(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "library-paths-compatibility", opts); + + const main_o = addObject(b, opts, .{ + .name = "main", + .c_source_bytes = + \\#include + \\int foo(); + \\int main() { + \\ printf("%d\n", foo()); + \\ return 0; + \\} + \\ + , + }); + main_o.linkLibC(); + + const libfoo = addSharedLibrary(b, opts, .{ .name = "foo", .soname = .no }); + addCSourceBytes(libfoo, "int foo() { return 42; }", &.{}); + + { + const scripts = WriteFile.create(b); + const path = scripts.addCopyFile(libfoo.getEmittedBin(), "foo/libfoo.so"); + + const exe = addExecutable(b, opts, .{ .name = "test" }); + exe.addObject(main_o); + + exe.addLibraryPath(.{ .generated = .{ .file = &scripts.generated_directory, .sub_path = "foo" } }); + exe.addRPath(path.dirname()); + + exe.linkSystemLibrary2("foo", .{ .needed = false }); + exe.linkLibC(); + + const run = addRunArtifact(exe); + run.expectStdOutEqual("42\n"); + test_step.dependOn(&run.step); + + const check = exe.checkObject(); + check.checkInDynamicSection(); + check.checkExact("NEEDED libfoo.so"); + check.checkNotPresent("NEEDED foo/libfoo.so"); + test_step.dependOn(&check.step); + } + + return test_step; +} + fn testCanonicalPlt(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "canonical-plt", opts); diff --git a/test/link/link.zig b/test/link/link.zig index 5d1a02f23e69..a4ebfcbfc05d 100644 --- a/test/link/link.zig +++ b/test/link/link.zig @@ -44,6 +44,7 @@ const OverlayOptions = struct { zig_source_bytes: ?[]const u8 = null, pic: ?bool = null, strip: ?bool = null, + soname: ?std.zig.SoName = null, }; 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 }, .pic = overlay.pic, .strip = if (base.strip) |s| s else overlay.strip, + .soname = overlay.soname, }); if (overlay.objcpp_source_bytes) |bytes| {