authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2022-04-12 00:25:47+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-04-12 06:12:44-04:00
log38d6e1d8a85ff77bc98dd80f604525e0804dec11
tree7244813c9400376f8efc71c8011f89b4683a08a0
parentb9d86c6bc8e5d475ed8613bb241d1520377e629c

std.build: Fix transitive linkSystemLibraryName() dependencies

Currently transitive system library dependencies are always linked using linkSystemLibrary() and therefore pkg-config even if they were originally specified with linkSystemLibraryName() instead. This causes problems in practice for projects needing total control over exactly what library is linked, such as the mach game engine. This is fixed by keeping track of whether libraries are to be linked with pkg-config or not and holding off on actually running pkg-config until after transitive dependency resolution in LibExeObjStep.make(). This also fixes a separate issue with the pkg-config handling that could cause partial application of pkg-config flags if the first part of the pkg-config output parses correctly but there is an error later on. This error isn't always fatal as we fall back to a plain -lfoo in the case of linkSystemLibrary().

1 files changed, 119 insertions(+), 49 deletions(-)

lib/std/build.zig+119-49
...@@ -1600,12 +1600,26 @@ pub const LibExeObjStep = struct {...@@ -1600,12 +1600,26 @@ pub const LibExeObjStep = struct {
1600 pub const LinkObject = union(enum) {1600 pub const LinkObject = union(enum) {
1601 static_path: FileSource,1601 static_path: FileSource,
1602 other_step: *LibExeObjStep,1602 other_step: *LibExeObjStep,
1603 system_lib: []const u8,1603 system_lib: SystemLib,
1604 assembly_file: FileSource,1604 assembly_file: FileSource,
1605 c_source_file: *CSourceFile,1605 c_source_file: *CSourceFile,
1606 c_source_files: *CSourceFiles,1606 c_source_files: *CSourceFiles,
1607 };1607 };
16081608
1609 pub const SystemLib = struct {
1610 name: []const u8,
1611 use_pkg_config: enum {
1612 /// Don't use pkg-config, just pass -lfoo where foo is name.
1613 no,
1614 /// Try to get information on how to link the library from pkg-config.
1615 /// If that fails, fall back to passing -lfoo where foo is name.
1616 yes,
1617 /// Try to get information on how to link the library from pkg-config.
1618 /// If that fails, error out.
1619 force,
1620 },
1621 };
1622
1609 pub const IncludeDir = union(enum) {1623 pub const IncludeDir = union(enum) {
1610 raw_path: []const u8,1624 raw_path: []const u8,
1611 raw_path_system: []const u8,1625 raw_path_system: []const u8,
...@@ -1854,7 +1868,7 @@ pub const LibExeObjStep = struct {...@@ -1854,7 +1868,7 @@ pub const LibExeObjStep = struct {
1854 }1868 }
1855 for (self.link_objects.items) |link_object| {1869 for (self.link_objects.items) |link_object| {
1856 switch (link_object) {1870 switch (link_object) {
1857 .system_lib => |n| if (mem.eql(u8, n, name)) return true,1871 .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true,
1858 else => continue,1872 else => continue,
1859 }1873 }
1860 }1874 }
...@@ -1879,14 +1893,24 @@ pub const LibExeObjStep = struct {...@@ -1879,14 +1893,24 @@ pub const LibExeObjStep = struct {
1879 pub fn linkLibC(self: *LibExeObjStep) void {1893 pub fn linkLibC(self: *LibExeObjStep) void {
1880 if (!self.is_linking_libc) {1894 if (!self.is_linking_libc) {
1881 self.is_linking_libc = true;1895 self.is_linking_libc = true;
1882 self.link_objects.append(.{ .system_lib = "c" }) catch unreachable;1896 self.link_objects.append(.{
1897 .system_lib = .{
1898 .name = "c",
1899 .use_pkg_config = .no,
1900 },
1901 }) catch unreachable;
1883 }1902 }
1884 }1903 }
18851904
1886 pub fn linkLibCpp(self: *LibExeObjStep) void {1905 pub fn linkLibCpp(self: *LibExeObjStep) void {
1887 if (!self.is_linking_libcpp) {1906 if (!self.is_linking_libcpp) {
1888 self.is_linking_libcpp = true;1907 self.is_linking_libcpp = true;
1889 self.link_objects.append(.{ .system_lib = "c++" }) catch unreachable;1908 self.link_objects.append(.{
1909 .system_lib = .{
1910 .name = "c++",
1911 .use_pkg_config = .no,
1912 },
1913 }) catch unreachable;
1890 }1914 }
1891 }1915 }
18921916
...@@ -1905,12 +1929,28 @@ pub const LibExeObjStep = struct {...@@ -1905,12 +1929,28 @@ pub const LibExeObjStep = struct {
1905 /// This one has no integration with anything, it just puts -lname on the command line.1929 /// This one has no integration with anything, it just puts -lname on the command line.
1906 /// Prefer to use `linkSystemLibrary` instead.1930 /// Prefer to use `linkSystemLibrary` instead.
1907 pub fn linkSystemLibraryName(self: *LibExeObjStep, name: []const u8) void {1931 pub fn linkSystemLibraryName(self: *LibExeObjStep, name: []const u8) void {
1908 self.link_objects.append(.{ .system_lib = self.builder.dupe(name) }) catch unreachable;1932 self.link_objects.append(.{
1933 .system_lib = .{
1934 .name = self.builder.dupe(name),
1935 .use_pkg_config = .no,
1936 },
1937 }) catch unreachable;
1909 }1938 }
19101939
1911 /// This links against a system library, exclusively using pkg-config to find the library.1940 /// This links against a system library, exclusively using pkg-config to find the library.
1912 /// Prefer to use `linkSystemLibrary` instead.1941 /// Prefer to use `linkSystemLibrary` instead.
1913 pub fn linkSystemLibraryPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) !void {1942 pub fn linkSystemLibraryPkgConfigOnly(self: *LibExeObjStep, lib_name: []const u8) void {
1943 self.link_objects.append(.{
1944 .system_lib = .{
1945 .name = self.builder.dupe(lib_name),
1946 .use_pkg_config = .force,
1947 },
1948 }) catch unreachable;
1949 }
1950
1951 /// Run pkg-config for the given library name and parse the output, returning the arguments
1952 /// that should be passed to zig to link the given library.
1953 fn runPkgConfig(self: *LibExeObjStep, lib_name: []const u8) ![]const []const u8 {
1914 const pkg_name = match: {1954 const pkg_name = match: {
1915 // First we have to map the library name to pkg config name. Unfortunately,1955 // First we have to map the library name to pkg config name. Unfortunately,
1916 // there are several examples where this is not straightforward:1956 // there are several examples where this is not straightforward:
...@@ -1970,34 +2010,38 @@ pub const LibExeObjStep = struct {...@@ -1970,34 +2010,38 @@ pub const LibExeObjStep = struct {
1970 error.ChildExecFailed => return error.PkgConfigFailed,2010 error.ChildExecFailed => return error.PkgConfigFailed,
1971 else => return err,2011 else => return err,
1972 };2012 };
2013
2014 var zig_args = std.ArrayList([]const u8).init(self.builder.allocator);
2015 defer zig_args.deinit();
2016
1973 var it = mem.tokenize(u8, stdout, " \r\n\t");2017 var it = mem.tokenize(u8, stdout, " \r\n\t");
1974 while (it.next()) |tok| {2018 while (it.next()) |tok| {
1975 if (mem.eql(u8, tok, "-I")) {2019 if (mem.eql(u8, tok, "-I")) {
1976 const dir = it.next() orelse return error.PkgConfigInvalidOutput;2020 const dir = it.next() orelse return error.PkgConfigInvalidOutput;
1977 self.addIncludePath(dir);2021 try zig_args.appendSlice(&[_][]const u8{ "-I", dir });
1978 } else if (mem.startsWith(u8, tok, "-I")) {2022 } else if (mem.startsWith(u8, tok, "-I")) {
1979 self.addIncludePath(tok["-I".len..]);2023 try zig_args.append(tok);
1980 } else if (mem.eql(u8, tok, "-L")) {2024 } else if (mem.eql(u8, tok, "-L")) {
1981 const dir = it.next() orelse return error.PkgConfigInvalidOutput;2025 const dir = it.next() orelse return error.PkgConfigInvalidOutput;
1982 self.addLibraryPath(dir);2026 try zig_args.appendSlice(&[_][]const u8{ "-L", dir });
1983 } else if (mem.startsWith(u8, tok, "-L")) {2027 } else if (mem.startsWith(u8, tok, "-L")) {
1984 self.addLibraryPath(tok["-L".len..]);2028 try zig_args.append(tok);
1985 } else if (mem.eql(u8, tok, "-l")) {2029 } else if (mem.eql(u8, tok, "-l")) {
1986 const lib = it.next() orelse return error.PkgConfigInvalidOutput;2030 const lib = it.next() orelse return error.PkgConfigInvalidOutput;
1987 self.linkSystemLibraryName(lib);2031 try zig_args.appendSlice(&[_][]const u8{ "-l", lib });
1988 } else if (mem.startsWith(u8, tok, "-l")) {2032 } else if (mem.startsWith(u8, tok, "-l")) {
1989 self.linkSystemLibraryName(tok["-l".len..]);2033 try zig_args.append(tok);
1990 } else if (mem.eql(u8, tok, "-D")) {2034 } else if (mem.eql(u8, tok, "-D")) {
1991 const macro = it.next() orelse return error.PkgConfigInvalidOutput;2035 const macro = it.next() orelse return error.PkgConfigInvalidOutput;
1992 self.defineCMacroRaw(macro);2036 try zig_args.appendSlice(&[_][]const u8{ "-D", macro });
1993 } else if (mem.startsWith(u8, tok, "-D")) {2037 } else if (mem.startsWith(u8, tok, "-D")) {
1994 self.defineCMacroRaw(tok["-D".len..]);2038 try zig_args.append(tok);
1995 } else if (mem.eql(u8, tok, "-pthread")) {
1996 self.linkLibC();
1997 } else if (self.builder.verbose) {2039 } else if (self.builder.verbose) {
1998 warn("Ignoring pkg-config flag '{s}'\n", .{tok});2040 warn("Ignoring pkg-config flag '{s}'\n", .{tok});
1999 }2041 }
2000 }2042 }
2043
2044 return zig_args.toOwnedSlice();
2001 }2045 }
20022046
2003 pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {2047 pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void {
...@@ -2009,21 +2053,13 @@ pub const LibExeObjStep = struct {...@@ -2009,21 +2053,13 @@ pub const LibExeObjStep = struct {
2009 self.linkLibCpp();2053 self.linkLibCpp();
2010 return;2054 return;
2011 }2055 }
2012 if (self.linkSystemLibraryPkgConfigOnly(name)) |_| {
2013 // pkg-config worked, so nothing further needed to do.
2014 return;
2015 } else |err| switch (err) {
2016 error.PkgConfigInvalidOutput,
2017 error.PkgConfigCrashed,
2018 error.PkgConfigFailed,
2019 error.PkgConfigNotInstalled,
2020 error.PackageNotFound,
2021 => {},
2022
2023 else => unreachable,
2024 }
20252056
2026 self.linkSystemLibraryName(name);2057 self.link_objects.append(.{
2058 .system_lib = .{
2059 .name = self.builder.dupe(name),
2060 .use_pkg_config = .yes,
2061 },
2062 }) catch unreachable;
2027 }2063 }
20282064
2029 pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void {2065 pub fn setNamePrefix(self: *LibExeObjStep, text: []const u8) void {
...@@ -2317,27 +2353,34 @@ pub const LibExeObjStep = struct {...@@ -2317,27 +2353,34 @@ pub const LibExeObjStep = struct {
2317 var prev_has_extra_flags = false;2353 var prev_has_extra_flags = false;
23182354
2319 // Resolve transitive dependencies2355 // Resolve transitive dependencies
2320 for (self.link_objects.items) |link_object| {2356 {
2321 switch (link_object) {2357 var transitive_dependencies = std.ArrayList(LinkObject).init(builder.allocator);
2322 .other_step => |other| {2358 defer transitive_dependencies.deinit();
2323 // Inherit dependency on system libraries2359
2324 for (other.link_objects.items) |other_link_object| {2360 for (self.link_objects.items) |link_object| {
2325 switch (other_link_object) {2361 switch (link_object) {
2326 .system_lib => |name| self.linkSystemLibrary(name),2362 .other_step => |other| {
2327 else => continue,2363 // Inherit dependency on system libraries
2364 for (other.link_objects.items) |other_link_object| {
2365 switch (other_link_object) {
2366 .system_lib => try transitive_dependencies.append(other_link_object),
2367 else => continue,
2368 }
2328 }2369 }
2329 }
23302370
2331 // Inherit dependencies on darwin frameworks2371 // Inherit dependencies on darwin frameworks
2332 if (!other.isDynamicLibrary()) {2372 if (!other.isDynamicLibrary()) {
2333 var it = other.frameworks.iterator();2373 var it = other.frameworks.iterator();
2334 while (it.next()) |framework| {2374 while (it.next()) |framework| {
2335 self.frameworks.insert(framework.*) catch unreachable;2375 self.frameworks.insert(framework.*) catch unreachable;
2376 }
2336 }2377 }
2337 }2378 },
2338 },2379 else => continue,
2339 else => continue,2380 }
2340 }2381 }
2382
2383 try self.link_objects.appendSlice(transitive_dependencies.items);
2341 }2384 }
23422385
2343 for (self.link_objects.items) |link_object| {2386 for (self.link_objects.items) |link_object| {
...@@ -2363,8 +2406,35 @@ pub const LibExeObjStep = struct {...@@ -2363,8 +2406,35 @@ pub const LibExeObjStep = struct {
2363 }2406 }
2364 },2407 },
2365 },2408 },
2366 .system_lib => |name| {2409
2367 try zig_args.append(builder.fmt("-l{s}", .{name}));2410 .system_lib => |system_lib| {
2411 switch (system_lib.use_pkg_config) {
2412 .no => try zig_args.append(builder.fmt("-l{s}", .{system_lib.name})),
2413 .yes, .force => {
2414 if (self.runPkgConfig(system_lib.name)) |args| {
2415 try zig_args.appendSlice(args);
2416 } else |err| switch (err) {
2417 error.PkgConfigInvalidOutput,
2418 error.PkgConfigCrashed,
2419 error.PkgConfigFailed,
2420 error.PkgConfigNotInstalled,
2421 error.PackageNotFound,
2422 => switch (system_lib.use_pkg_config) {
2423 .yes => {
2424 // pkg-config failed, so fall back to linking the library
2425 // by name directly.
2426 try zig_args.append(builder.fmt("-l{s}", .{system_lib.name}));
2427 },
2428 .force => {
2429 panic("pkg-config failed for library {s}", .{system_lib.name});
2430 },
2431 .no => unreachable,
2432 },
2433
2434 else => |e| return e,
2435 }
2436 },
2437 }
2368 },2438 },
23692439
2370 .assembly_file => |asm_file| {2440 .assembly_file => |asm_file| {