From: Volker Schlecht Subject: lang/zig: Find versioned shared libraries on OpenBSD To: ports Date: Tue, 14 Jul 2026 22:26:06 +0200 Hi, as Robert Lillack remarked in his submission of wayland/river: The Zig build system currently does not correctly work for linking to system libraries in OpenBSD without specifying a version number, so a patch for River's build system is necessary. https://marc.info/?l=openbsd-ports&m=177944156665691&w=2 There have been prior attempts to make that work on OpenBSD, but it seems that interest in the feature was lost in early 2024: https://github.com/ziglang/zig/pull/18475 Attached is a second attempt. Both as a diff to lang/zig and as a PR on codeberg: https://codeberg.org/ziglang/zig/pulls/36135 What I can say is that this *works*. I can drop the patch in wayland/river and it builds and runs fine without. What I suspect however is that this looks like what it is: A hodgepodge of Robert's diff and the originally proposed PR that I came up with after reading a few tutorials. So if anyone here is a more experienced zig programmer, please review, fix, rewrite or weigh in on the PR :-) NB: I am looking for feedback from experienced *human* zig programmers (see zig's no-AI policy). cheers, Volker Index: Makefile =================================================================== RCS file: /cvs/ports/lang/zig/Makefile,v retrieving revision 1.32 diff -u -p -r1.32 Makefile --- Makefile 11 May 2026 21:18:06 -0000 1.32 +++ Makefile 14 Jul 2026 20:02:33 -0000 @@ -7,6 +7,7 @@ COMMENT = zig compiler and toolchain V = 0.16.0 DIST_TUPLE = codeberg ziglang zig ${V} . +REVISION = 0 # see https://codeberg.org/ziglang/zig/src/tag/0.16.0 ZIG_COMMIT = 24fdd5b7a4c1c8b5deb5b56756b9dbc8e08c86a8 Index: distinfo =================================================================== RCS file: /cvs/ports/lang/zig/distinfo,v retrieving revision 1.16 diff -u -p -r1.16 distinfo --- distinfo 11 May 2026 21:18:06 -0000 1.16 +++ distinfo 14 Jul 2026 20:02:33 -0000 @@ -1,2 +1,2 @@ -SHA256 (ziglang-zig-0.16.0.tar.gz) = CV10gwCOiF5l2Malf7l8VzRPmVQIpki41Q9hJ4rYXX8= -SIZE (ziglang-zig-0.16.0.tar.gz) = 35262315 +SHA256 (ziglang-zig-0.16.0.tar.gz) = /b3OcQUn7dIBhXCYf8Kb2dbJ6CxRyykHudG9cMmT+l8= +SIZE (ziglang-zig-0.16.0.tar.gz) = 35594555 Index: patches/patch-src_link_zig =================================================================== RCS file: patches/patch-src_link_zig diff -N patches/patch-src_link_zig --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ patches/patch-src_link_zig 14 Jul 2026 20:02:33 -0000 @@ -0,0 +1,67 @@ +Index: src/link.zig +--- src/link.zig.orig ++++ src/link.zig +@@ -2148,6 +2148,63 @@ fn resolveLibInput( + return finishResolveLibInput(resolved_inputs, test_path, file, link_mode, name_query.query); + } + ++ // In the case of OpenBSD, dynamic libraries are always versioned, without ++ // unversioned symlinks, so we need to look for the highest-versioned shared ++ // library. ++ if (target.isOpenBSDLibC() and link_mode == .dynamic) versioned: { ++ const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name}); ++ ++ var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) { ++ error.NotDir, error.FileNotFound => break :versioned, ++ else => |e| fatal("unable to search for shared library '{s}.*': {s}", .{ prefix, @errorName(e) }), ++ }; ++ defer dir.close(io); ++ ++ var best_match_version = std.SemanticVersion{ ++ .major = 0, ++ .minor = 0, ++ .patch = 0, ++ }; ++ var best_match: ?[]const u8 = null; ++ ++ var iter = dir.iterate(); ++ while (iter.next(io) catch |err| { ++ fatal("unable to scan library directory '{s}'", .{@errorName(err)}); ++ }) |entry| { ++ if (entry.kind != .file) continue; ++ if (!std.mem.startsWith(u8, entry.name, prefix)) continue; ++ ++ const rest = entry.name[prefix.len..]; ++ var sit = std.mem.splitScalar(u8, rest, '.'); ++ const major_str = sit.next() orelse continue; ++ const minor_str = sit.next() orelse continue; ++ if (sit.next() != null) continue; ++ const major = std.fmt.parseInt(usize, major_str, 10) catch continue; ++ const minor = std.fmt.parseInt(usize, minor_str, 10) catch continue; ++ ++ if (major > best_match_version.major or (major == best_match_version.major and minor >= best_match_version.minor)) { ++ best_match_version.major = major; ++ best_match_version.minor = minor; ++ best_match = try arena.dupe(u8, entry.name); ++ } ++ } ++ ++ if (best_match) |found| { ++ const test_path: Path = .{ ++ .root_dir = lib_directory, ++ .sub_path = found, ++ }; ++ try checked_paths.print(gpa, "\n {f}", .{test_path}); ++ switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, target, .{ ++ .path = test_path, ++ .query = name_query.query, ++ }, link_mode, color)) { ++ .no_match => {}, ++ .ok => return .ok, ++ } ++ } ++ } ++ + return .no_match; + } +