| ... | @@ -7,13 +7,8 @@ const Maker = @import("../Maker.zig"); | ... | @@ -7,13 +7,8 @@ const Maker = @import("../Maker.zig"); |
| 7 | const Step = @import("Step.zig"); | 7 | const Step = @import("Step.zig"); |
| 8 | const Graph = @import("Graph.zig"); | 8 | const Graph = @import("Graph.zig"); |
| 9 | | 9 | |
| 10 | pub const Pkg = struct { | | |
| 11 | name: []const u8, | | |
| 12 | desc: []const u8, | | |
| 13 | }; | | |
| 14 | | | |
| 15 | mutex: Io.Mutex = .init, | 10 | mutex: Io.Mutex = .init, |
| 16 | list: ?[]const Pkg = null, | 11 | pkgs: ?std.zig.PkgConfig = null, |
| 17 | debug: bool = false, | 12 | debug: bool = false, |
| 18 | | 13 | |
| 19 | pub const RunError = error{ | 14 | pub const RunError = error{ |
| ... | @@ -21,10 +16,7 @@ pub const RunError = error{ | ... | @@ -21,10 +16,7 @@ pub const RunError = error{ |
| 21 | PkgConfigUnavailable, | 16 | PkgConfigUnavailable, |
| 22 | } || Step.ExtendedMakeError; | 17 | } || Step.ExtendedMakeError; |
| 23 | | 18 | |
| 24 | pub const Result = struct { | 19 | pub const Result = std.zig.PkgConfig.Parsed; |
| 25 | cflags: []const []const u8, | | |
| 26 | libs: []const []const u8, | | |
| 27 | }; | | |
| 28 | | 20 | |
| 29 | /// Run pkg-config for the given library name and parse the output, returning the arguments | 21 | /// Run pkg-config for the given library name and parse the output, returning the arguments |
| 30 | /// that should be passed to zig to link the given library. | 22 | /// that should be passed to zig to link the given library. |
| ... | @@ -34,131 +26,50 @@ pub fn run( | ... | @@ -34,131 +26,50 @@ pub fn run( |
| 34 | progress_node: std.Progress.Node, | 26 | progress_node: std.Progress.Node, |
| 35 | lib_name: []const u8, | 27 | lib_name: []const u8, |
| 36 | /// If true, reports failure error messages on step rather than returning | 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 | force: bool, | 30 | force: bool, |
| 39 | ) RunError!Result { | 31 | ) RunError!Result { |
| 40 | const pc = &maker.pkg_config; | 32 | const pc = &maker.pkg_config; |
| 41 | const graph = maker.graph; | 33 | const graph = maker.graph; |
| 42 | const arena = graph.arena; // TODO don't leak into process arena | 34 | const arena = graph.arena; // TODO don't leak into process arena |
| 43 | | 35 | |
| 44 | const pkg_name = match: { | 36 | const pkg_config_exe = getExe(graph); |
| 45 | // First we have to map the library name to pkg config name. Unfortunately, | 37 | const pkgs = try getPkgs(maker, step, progress_node, force); |
| 46 | // there are several examples where this is not straightforward: | 38 | const found_index = pkgs.find(lib_name) orelse { |
| 47 | // -lSDL2 -> pkg-config sdl2 | 39 | if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name }); |
| 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 | | | |
| 92 | return error.PackageNotFound; | 40 | return error.PackageNotFound; |
| 93 | }; | 41 | }; |
| | 42 | const pkg = pkgs.all[found_index]; |
| 94 | | 43 | |
| 95 | const pkg_config_exe = getExe(graph); | | |
| 96 | const stdout = try captureChildProcess(maker, step, .{ | 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 | .progress_node = progress_node, | 46 | .progress_node = progress_node, |
| 99 | .allow_failure = !force, | 47 | .allow_failure = !force, |
| 100 | }); | 48 | }); |
| 101 | | 49 | |
| 102 | var zig_cflags: std.ArrayList([]const u8) = .empty; | 50 | const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) { |
| 103 | var zig_libs: std.ArrayList([]const u8) = .empty; | 51 | error.InvalidPkgConfigOutput => { |
| 104 | var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t"); | 52 | if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{ |
| 105 | | 53 | pkg_config_exe, lib_name, stdout, |
| 106 | while (arg_it.next()) |arg| { | 54 | }); |
| 107 | if (mem.eql(u8, arg, "-I")) { | 55 | return error.PkgConfigUnavailable; |
| 108 | const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); | 56 | }, |
| 109 | try zig_cflags.appendSlice(arena, &.{ "-I", dir }); | 57 | else => |e| return e, |
| 110 | } else if (mem.startsWith(u8, arg, "-I")) { | 58 | }; |
| 111 | try zig_cflags.append(arena, arg); | 59 | if (force or pc.debug) { |
| 112 | } else if (mem.eql(u8, arg, "-L")) { | 60 | for (parsed.unknown_flags) |unknown_flag| { |
| 113 | const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force); | 61 | return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag }); |
| 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 }); | | |
| 131 | } | 62 | } |
| 132 | } | 63 | } |
| 133 | | 64 | |
| 134 | try zig_cflags.shrinkToLen(arena); | 65 | return parsed; |
| 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; | | |
| 155 | } | 66 | } |
| 156 | | 67 | |
| 157 | fn getExe(graph: *const Graph) []const u8 { | 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 | const graph = maker.graph; | 73 | const graph = maker.graph; |
| 163 | const arena = graph.arena; // TODO don't leak into process arena | 74 | const arena = graph.arena; // TODO don't leak into process arena |
| 164 | const io = graph.io; | 75 | const io = graph.io; |
| ... | @@ -167,7 +78,7 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: | ... | @@ -167,7 +78,7 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: |
| 167 | try pc.mutex.lock(io); | 78 | try pc.mutex.lock(io); |
| 168 | defer pc.mutex.unlock(io); | 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 | const pkg_config_exe = getExe(graph); | 83 | const pkg_config_exe = getExe(graph); |
| 173 | const stdout = try captureChildProcess(maker, step, .{ | 84 | const stdout = try captureChildProcess(maker, step, .{ |
| ... | @@ -176,25 +87,18 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: | ... | @@ -176,25 +87,18 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: |
| 176 | .allow_failure = !force, | 87 | .allow_failure = !force, |
| 177 | }); | 88 | }); |
| 178 | | 89 | |
| 179 | var list: std.ArrayList(Pkg) = .empty; | 90 | var diagnostic: std.zig.PkgConfig.Diagnostic = undefined; |
| 180 | var line_it = mem.tokenizeAny(u8, stdout, "\r\n"); | 91 | const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) { |
| 181 | while (line_it.next()) |line| { | 92 | error.InvalidPkgConfigOutput => { |
| 182 | if (mem.trim(u8, line, " \t").len == 0) continue; | 93 | if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{ |
| 183 | var tok_it = mem.tokenizeAny(u8, line, " \t"); | 94 | pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line, |
| 184 | try list.append(arena, .{ | 95 | }); |
| 185 | .name = tok_it.next() orelse { | 96 | return error.PkgConfigUnavailable; |
| 186 | if (force) return step.fail(maker, "{s}: invalid line: {s}", .{ | 97 | }, |
| 187 | pkg_config_exe, line, | 98 | else => |e| return e, |
| 188 | }); | 99 | }; |
| 189 | return error.PkgConfigUnavailable; | | |
| 190 | }, | | |
| 191 | .desc = tok_it.rest(), | | |
| 192 | }); | | |
| 193 | } | | |
| 194 | try list.shrinkToLen(arena); | | |
| 195 | | 100 | |
| 196 | const result = list.toOwnedSliceAssert(); | 101 | pc.pkgs = result; |
| 197 | pc.list = result; | | |
| 198 | return result; | 102 | return result; |
| 199 | } | 103 | } |
| 200 | | 104 | |