1const std = @import("std");
2const Io = std.Io;
3const mem = std.mem;
4const assert = std.debug.assert;
5const Allocator = std.mem.Allocator;
6
7const Maker = @import("../Maker.zig");
8const Step = @import("Step.zig");
9const Graph = @import("Graph.zig");
10
11mutex: Io.Mutex = .init,
12pkgs: ?std.zig.PkgConfig = null,
13debug: bool = false,
14
15pub const RunError = error{
16 PackageNotFound,
17 PkgConfigUnavailable,
18} || Step.ExtendedMakeError;
19
20pub 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.
24pub 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
71fn getExe(graph: *const Graph) []const u8 {
72 return std.zig.PkgConfig.exe(&graph.environ_map);
73}
74
75fn 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
114fn 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}