| ... | ... | @@ -7,13 +7,8 @@ const Maker = @import("../Maker.zig"); |
| 7 | 7 | const Step = @import("Step.zig"); |
| 8 | 8 | const Graph = @import("Graph.zig"); |
| 9 | 9 | |
| 10 | | pub const Pkg = struct { |
| 11 | | name: []const u8, |
| 12 | | desc: []const u8, |
| 13 | | }; |
| 14 | | |
| 15 | 10 | mutex: Io.Mutex = .init, |
| 16 | | list: ?[]const Pkg = null, |
| 11 | pkgs: ?std.zig.PkgConfig = null, |
| 17 | 12 | debug: bool = false, |
| 18 | 13 | |
| 19 | 14 | pub const RunError = error{ |
| ... | ... | @@ -21,10 +16,7 @@ pub const RunError = error{ |
| 21 | 16 | PkgConfigUnavailable, |
| 22 | 17 | } || Step.ExtendedMakeError; |
| 23 | 18 | |
| 24 | | pub const Result = struct { |
| 25 | | cflags: []const []const u8, |
| 26 | | libs: []const []const u8, |
| 27 | | }; |
| 19 | pub const Result = std.zig.PkgConfig.Parsed; |
| 28 | 20 | |
| 29 | 21 | /// Run pkg-config for the given library name and parse the output, returning the arguments |
| 30 | 22 | /// that should be passed to zig to link the given library. |
| ... | ... | @@ -34,131 +26,50 @@ pub fn run( |
| 34 | 26 | progress_node: std.Progress.Node, |
| 35 | 27 | lib_name: []const u8, |
| 36 | 28 | /// If true, reports failure error messages on step rather than returning |
| 37 | | /// error.PackageNotFound or error.PkgConfigInvalidOutput, |
| 29 | /// error.PackageNotFound or error.PkgConfigUnavailable, |
| 38 | 30 | force: bool, |
| 39 | 31 | ) RunError!Result { |
| 40 | 32 | const pc = &maker.pkg_config; |
| 41 | 33 | const graph = maker.graph; |
| 42 | 34 | const arena = graph.arena; // TODO don't leak into process arena |
| 43 | 35 | |
| 44 | | const pkg_name = match: { |
| 45 | | // First we have to map the library name to pkg config name. Unfortunately, |
| 46 | | // there are several examples where this is not straightforward: |
| 47 | | // -lSDL2 -> pkg-config sdl2 |
| 48 | | // -lgdk-3 -> pkg-config gdk-3.0 |
| 49 | | // -latk-1.0 -> pkg-config atk |
| 50 | | // -lpulse -> pkg-config libpulse |
| 51 | | const pkgs = try getList(maker, step, progress_node, force); |
| 52 | | |
| 53 | | // Exact match means instant winner. |
| 54 | | for (pkgs) |pkg| { |
| 55 | | if (mem.eql(u8, pkg.name, lib_name)) { |
| 56 | | break :match pkg.name; |
| 57 | | } |
| 58 | | } |
| 59 | | |
| 60 | | // Next we'll try ignoring case. |
| 61 | | for (pkgs) |pkg| { |
| 62 | | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { |
| 63 | | break :match pkg.name; |
| 64 | | } |
| 65 | | } |
| 66 | | |
| 67 | | // Prefixed "lib" or suffixed ".0". |
| 68 | | for (pkgs) |pkg| { |
| 69 | | if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| { |
| 70 | | const prefix = pkg.name[0..pos]; |
| 71 | | const suffix = pkg.name[pos + lib_name.len ..]; |
| 72 | | if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue; |
| 73 | | if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue; |
| 74 | | break :match pkg.name; |
| 75 | | } |
| 76 | | } |
| 77 | | |
| 78 | | // Trimming "-1.0". |
| 79 | | if (mem.endsWith(u8, lib_name, "-1.0")) { |
| 80 | | const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; |
| 81 | | for (pkgs) |pkg| { |
| 82 | | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { |
| 83 | | break :match pkg.name; |
| 84 | | } |
| 85 | | } |
| 86 | | } |
| 87 | | |
| 88 | | if (force) return step.fail(maker, "{s}: package not found: {s}", .{ |
| 89 | | getExe(graph), lib_name, |
| 90 | | }); |
| 91 | | |
| 36 | const pkg_config_exe = getExe(graph); |
| 37 | const pkgs = try getPkgs(maker, step, progress_node, force); |
| 38 | const found_index = pkgs.find(lib_name) orelse { |
| 39 | if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name }); |
| 92 | 40 | return error.PackageNotFound; |
| 93 | 41 | }; |
| 42 | const pkg = pkgs.all[found_index]; |
| 94 | 43 | |
| 95 | | const pkg_config_exe = getExe(graph); |
| 96 | 44 | const stdout = try captureChildProcess(maker, step, .{ |
| 97 | | .argv = &.{ pkg_config_exe, pkg_name, "--cflags", "--libs" }, |
| 45 | .argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" }, |
| 98 | 46 | .progress_node = progress_node, |
| 99 | 47 | .allow_failure = !force, |
| 100 | 48 | }); |
| 101 | 49 | |
| 102 | | var zig_cflags: std.ArrayList([]const u8) = .empty; |
| 103 | | var zig_libs: std.ArrayList([]const u8) = .empty; |
| 104 | | var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t"); |
| 105 | | |
| 106 | | while (arg_it.next()) |arg| { |
| 107 | | if (mem.eql(u8, arg, "-I")) { |
| 108 | | const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); |
| 109 | | try zig_cflags.appendSlice(arena, &.{ "-I", dir }); |
| 110 | | } else if (mem.startsWith(u8, arg, "-I")) { |
| 111 | | try zig_cflags.append(arena, arg); |
| 112 | | } else if (mem.eql(u8, arg, "-L")) { |
| 113 | | const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); |
| 114 | | try zig_libs.appendSlice(arena, &.{ "-L", dir }); |
| 115 | | } else if (mem.startsWith(u8, arg, "-L")) { |
| 116 | | try zig_libs.append(arena, arg); |
| 117 | | } else if (mem.eql(u8, arg, "-l")) { |
| 118 | | const lib = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); |
| 119 | | try zig_libs.appendSlice(arena, &.{ "-l", lib }); |
| 120 | | } else if (mem.startsWith(u8, arg, "-l")) { |
| 121 | | try zig_libs.append(arena, arg); |
| 122 | | } else if (mem.eql(u8, arg, "-D")) { |
| 123 | | const macro = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); |
| 124 | | try zig_cflags.appendSlice(arena, &.{ "-D", macro }); |
| 125 | | } else if (mem.startsWith(u8, arg, "-D")) { |
| 126 | | try zig_cflags.append(arena, arg); |
| 127 | | } else if (mem.cutPrefix(u8, arg, "-Wl,-rpath,")) |rest| { |
| 128 | | try zig_cflags.appendSlice(arena, &.{ "-rpath", rest }); |
| 129 | | } else if (force or pc.debug) { |
| 130 | | return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, arg }); |
| 50 | const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) { |
| 51 | error.InvalidPkgConfigOutput => { |
| 52 | if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{ |
| 53 | pkg_config_exe, lib_name, stdout, |
| 54 | }); |
| 55 | return error.PkgConfigUnavailable; |
| 56 | }, |
| 57 | else => |e| return e, |
| 58 | }; |
| 59 | if (force or pc.debug) { |
| 60 | for (parsed.unknown_flags) |unknown_flag| { |
| 61 | return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag }); |
| 131 | 62 | } |
| 132 | 63 | } |
| 133 | 64 | |
| 134 | | try zig_cflags.shrinkToLen(arena); |
| 135 | | try zig_libs.shrinkToLen(arena); |
| 136 | | |
| 137 | | return .{ |
| 138 | | .cflags = zig_cflags.toOwnedSliceAssert(), |
| 139 | | .libs = zig_libs.toOwnedSliceAssert(), |
| 140 | | }; |
| 141 | | } |
| 142 | | |
| 143 | | fn missingArg( |
| 144 | | maker: *Maker, |
| 145 | | step: *Step, |
| 146 | | pkg_config_exe: []const u8, |
| 147 | | lib_name: []const u8, |
| 148 | | arg: []const u8, |
| 149 | | force: bool, |
| 150 | | ) RunError { |
| 151 | | if (force) return step.fail(maker, "{s} package {s} missing arg after flag: {s}", .{ |
| 152 | | pkg_config_exe, lib_name, arg, |
| 153 | | }); |
| 154 | | return error.PkgConfigUnavailable; |
| 65 | return parsed; |
| 155 | 66 | } |
| 156 | 67 | |
| 157 | 68 | fn getExe(graph: *const Graph) []const u8 { |
| 158 | | return std.zig.EnvVar.PKG_CONFIG.get(&graph.environ_map) orelse "pkg-config"; |
| 69 | return std.zig.PkgConfig.exe(&graph.environ_map); |
| 159 | 70 | } |
| 160 | 71 | |
| 161 | | fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError![]const Pkg { |
| 72 | fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError!std.zig.PkgConfig { |
| 162 | 73 | const graph = maker.graph; |
| 163 | 74 | const arena = graph.arena; // TODO don't leak into process arena |
| 164 | 75 | const io = graph.io; |
| ... | ... | @@ -167,7 +78,7 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: |
| 167 | 78 | try pc.mutex.lock(io); |
| 168 | 79 | defer pc.mutex.unlock(io); |
| 169 | 80 | |
| 170 | | if (pc.list) |list| return list; |
| 81 | if (pc.pkgs) |pkgs| return pkgs; |
| 171 | 82 | |
| 172 | 83 | const pkg_config_exe = getExe(graph); |
| 173 | 84 | const stdout = try captureChildProcess(maker, step, .{ |
| ... | ... | @@ -176,25 +87,18 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: |
| 176 | 87 | .allow_failure = !force, |
| 177 | 88 | }); |
| 178 | 89 | |
| 179 | | var list: std.ArrayList(Pkg) = .empty; |
| 180 | | var line_it = mem.tokenizeAny(u8, stdout, "\r\n"); |
| 181 | | while (line_it.next()) |line| { |
| 182 | | if (mem.trim(u8, line, " \t").len == 0) continue; |
| 183 | | var tok_it = mem.tokenizeAny(u8, line, " \t"); |
| 184 | | try list.append(arena, .{ |
| 185 | | .name = tok_it.next() orelse { |
| 186 | | if (force) return step.fail(maker, "{s}: invalid line: {s}", .{ |
| 187 | | pkg_config_exe, line, |
| 188 | | }); |
| 189 | | return error.PkgConfigUnavailable; |
| 190 | | }, |
| 191 | | .desc = tok_it.rest(), |
| 192 | | }); |
| 193 | | } |
| 194 | | try list.shrinkToLen(arena); |
| 90 | var diagnostic: std.zig.PkgConfig.Diagnostic = undefined; |
| 91 | const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) { |
| 92 | error.InvalidPkgConfigOutput => { |
| 93 | if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{ |
| 94 | pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line, |
| 95 | }); |
| 96 | return error.PkgConfigUnavailable; |
| 97 | }, |
| 98 | else => |e| return e, |
| 99 | }; |
| 195 | 100 | |
| 196 | | const result = list.toOwnedSliceAssert(); |
| 197 | | pc.list = result; |
| 101 | pc.pkgs = result; |
| 198 | 102 | return result; |
| 199 | 103 | } |
| 200 | 104 | |