| 1 | const std = @import("std"); |
| 2 | const Io = std.Io; |
| 3 | const mem = std.mem; |
| 4 | const assert = std.debug.assert; |
| 5 | const Allocator = std.mem.Allocator; |
| 6 | |
| 7 | const Maker = @import("../Maker.zig"); |
| 8 | const Step = @import("Step.zig"); |
| 9 | const Graph = @import("Graph.zig"); |
| 10 | |
| 11 | mutex: Io.Mutex = .init, |
| 12 | pkgs: ?std.zig.PkgConfig = null, |
| 13 | debug: bool = false, |
| 14 | |
| 15 | pub const RunError = error{ |
| 16 | PackageNotFound, |
| 17 | PkgConfigUnavailable, |
| 18 | } || Step.ExtendedMakeError; |
| 19 | |
| 20 | pub const Result = std.zig.PkgConfig.Parsed; |
| 21 | |
| 22 | /// Run pkg-config for the given library name and parse the output, returning the arguments |
| 23 | /// that should be passed to zig to link the given library. |
| 24 | pub fn run( |
| 25 | maker: *Maker, |
| 26 | step: *Step, |
| 27 | arena: Allocator, |
| 28 | progress_node: std.Progress.Node, |
| 29 | lib_name: []const u8, |
| 30 | /// If true, reports failure error messages on step rather than returning |
| 31 | /// error.PackageNotFound or error.PkgConfigUnavailable, |
| 32 | force: bool, |
| 33 | ) RunError!Result { |
| 34 | const pc = &maker.pkg_config; |
| 35 | const graph = maker.graph; |
| 36 | |
| 37 | const pkg_config_exe = getExe(graph); |
| 38 | const pkgs = try getPkgs(maker, step, progress_node, force); |
| 39 | const found_index = pkgs.find(lib_name) orelse { |
| 40 | if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name }); |
| 41 | return error.PackageNotFound; |
| 42 | }; |
| 43 | const pkg = pkgs.all[found_index]; |
| 44 | |
| 45 | const stdout = try captureChildProcess(maker, step, arena, .{ |
| 46 | .argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" }, |
| 47 | .progress_node = progress_node, |
| 48 | .allow_failure = !force, |
| 49 | }); |
| 50 | |
| 51 | const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) { |
| 52 | error.InvalidPkgConfigOutput => { |
| 53 | if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{ |
| 54 | pkg_config_exe, lib_name, stdout, |
| 55 | }); |
| 56 | return error.PkgConfigUnavailable; |
| 57 | }, |
| 58 | else => |e| return e, |
| 59 | }; |
| 60 | if (force or pc.debug) { |
| 61 | for (parsed.unknown_flags) |unknown_flag| { |
| 62 | return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag }); |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | step.clearFailedCommand(maker.gpa); |
| 67 | |
| 68 | return parsed; |
| 69 | } |
| 70 | |
| 71 | fn getExe(graph: *const Graph) []const u8 { |
| 72 | return std.zig.PkgConfig.exe(&graph.environ_map); |
| 73 | } |
| 74 | |
| 75 | fn getPkgs( |
| 76 | maker: *Maker, |
| 77 | step: *Step, |
| 78 | progress_node: std.Progress.Node, |
| 79 | force: bool, |
| 80 | ) RunError!std.zig.PkgConfig { |
| 81 | const graph = maker.graph; |
| 82 | const io = graph.io; |
| 83 | const pc = &maker.pkg_config; |
| 84 | const arena = graph.arena; |
| 85 | |
| 86 | try pc.mutex.lock(io); |
| 87 | defer pc.mutex.unlock(io); |
| 88 | |
| 89 | if (pc.pkgs) |pkgs| return pkgs; |
| 90 | |
| 91 | const pkg_config_exe = getExe(graph); |
| 92 | const stdout = try captureChildProcess(maker, step, arena, .{ |
| 93 | .argv = &.{ pkg_config_exe, "--list-all" }, |
| 94 | .progress_node = progress_node, |
| 95 | .allow_failure = !force, |
| 96 | }); |
| 97 | |
| 98 | var diagnostic: std.zig.PkgConfig.Diagnostic = undefined; |
| 99 | const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) { |
| 100 | error.InvalidPkgConfigOutput => { |
| 101 | if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{ |
| 102 | pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line, |
| 103 | }); |
| 104 | return error.PkgConfigUnavailable; |
| 105 | }, |
| 106 | else => |e| return e, |
| 107 | }; |
| 108 | |
| 109 | step.clearFailedCommand(maker.gpa); |
| 110 | pc.pkgs = result; |
| 111 | return result; |
| 112 | } |
| 113 | |
| 114 | fn captureChildProcess(maker: *Maker, step: *Step, arena: Allocator, options: Step.CaptureChildProcessOptions) ![]const u8 { |
| 115 | const captured = step.captureChildProcess(maker, arena, options) catch |err| switch (err) { |
| 116 | error.FileNotFound => return error.PkgConfigUnavailable, |
| 117 | else => |e| return e, |
| 118 | }; |
| 119 | if (captured.stderr.len != 0) try step.setResultStderr(maker.gpa, captured.stderr); |
| 120 | assert(step.result_failed_command != null); |
| 121 | if (captured.term.success()) return captured.stdout; |
| 122 | if (!options.allow_failure) return step.fail(maker, "{s} {f}", .{ options.argv[0], captured.term }); |
| 123 | return error.PkgConfigUnavailable; |
| 124 | } |