mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-30 21:07:34 +03:00
maker: extract some pkg-config logic into reusable API
This commit is contained in:
@@ -532,11 +532,7 @@ pub fn main(init: process.Init.Minimal) !void {
|
||||
.web_server = undefined, // set after `prepare`
|
||||
.memory_blocked_steps = .empty,
|
||||
.step_stack = .empty,
|
||||
.pkg_config = .{
|
||||
.mutex = .init,
|
||||
.list = null,
|
||||
.debug = debug_pkg_config,
|
||||
},
|
||||
.pkg_config = .{ .debug = debug_pkg_config },
|
||||
|
||||
.error_style = error_style,
|
||||
.multiline_errors = multiline_errors,
|
||||
@@ -1882,7 +1878,6 @@ pub fn truncatePath(
|
||||
if (graph.verbose) try graph.handleVerbose(.inherit, null, &.{
|
||||
"truncate", try dest_path.toString(arena),
|
||||
});
|
||||
// https://codeberg.org/ziglang/zig/issues/35353
|
||||
const err = e: {
|
||||
var file = f: {
|
||||
break :f dest_path.root_dir.handle.createFile(io, dest_path.sub_path, .{}) catch |err| switch (err) {
|
||||
|
||||
@@ -7,13 +7,8 @@ const Maker = @import("../Maker.zig");
|
||||
const Step = @import("Step.zig");
|
||||
const Graph = @import("Graph.zig");
|
||||
|
||||
pub const Pkg = struct {
|
||||
name: []const u8,
|
||||
desc: []const u8,
|
||||
};
|
||||
|
||||
mutex: Io.Mutex = .init,
|
||||
list: ?[]const Pkg = null,
|
||||
pkgs: ?std.zig.PkgConfig = null,
|
||||
debug: bool = false,
|
||||
|
||||
pub const RunError = error{
|
||||
@@ -21,10 +16,7 @@ pub const RunError = error{
|
||||
PkgConfigUnavailable,
|
||||
} || Step.ExtendedMakeError;
|
||||
|
||||
pub const Result = struct {
|
||||
cflags: []const []const u8,
|
||||
libs: []const []const u8,
|
||||
};
|
||||
pub const Result = std.zig.PkgConfig.Parsed;
|
||||
|
||||
/// Run pkg-config for the given library name and parse the output, returning the arguments
|
||||
/// that should be passed to zig to link the given library.
|
||||
@@ -34,131 +26,50 @@ pub fn run(
|
||||
progress_node: std.Progress.Node,
|
||||
lib_name: []const u8,
|
||||
/// If true, reports failure error messages on step rather than returning
|
||||
/// error.PackageNotFound or error.PkgConfigInvalidOutput,
|
||||
/// error.PackageNotFound or error.PkgConfigUnavailable,
|
||||
force: bool,
|
||||
) RunError!Result {
|
||||
const pc = &maker.pkg_config;
|
||||
const graph = maker.graph;
|
||||
const arena = graph.arena; // TODO don't leak into process arena
|
||||
|
||||
const pkg_name = match: {
|
||||
// First we have to map the library name to pkg config name. Unfortunately,
|
||||
// there are several examples where this is not straightforward:
|
||||
// -lSDL2 -> pkg-config sdl2
|
||||
// -lgdk-3 -> pkg-config gdk-3.0
|
||||
// -latk-1.0 -> pkg-config atk
|
||||
// -lpulse -> pkg-config libpulse
|
||||
const pkgs = try getList(maker, step, progress_node, force);
|
||||
|
||||
// Exact match means instant winner.
|
||||
for (pkgs) |pkg| {
|
||||
if (mem.eql(u8, pkg.name, lib_name)) {
|
||||
break :match pkg.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Next we'll try ignoring case.
|
||||
for (pkgs) |pkg| {
|
||||
if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) {
|
||||
break :match pkg.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Prefixed "lib" or suffixed ".0".
|
||||
for (pkgs) |pkg| {
|
||||
if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| {
|
||||
const prefix = pkg.name[0..pos];
|
||||
const suffix = pkg.name[pos + lib_name.len ..];
|
||||
if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue;
|
||||
if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue;
|
||||
break :match pkg.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Trimming "-1.0".
|
||||
if (mem.endsWith(u8, lib_name, "-1.0")) {
|
||||
const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len];
|
||||
for (pkgs) |pkg| {
|
||||
if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) {
|
||||
break :match pkg.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (force) return step.fail(maker, "{s}: package not found: {s}", .{
|
||||
getExe(graph), lib_name,
|
||||
});
|
||||
|
||||
const pkg_config_exe = getExe(graph);
|
||||
const pkgs = try getPkgs(maker, step, progress_node, force);
|
||||
const found_index = pkgs.find(lib_name) orelse {
|
||||
if (force) return step.fail(maker, "{s}: package not found: {s}", .{ pkg_config_exe, lib_name });
|
||||
return error.PackageNotFound;
|
||||
};
|
||||
const pkg = pkgs.all[found_index];
|
||||
|
||||
const pkg_config_exe = getExe(graph);
|
||||
const stdout = try captureChildProcess(maker, step, .{
|
||||
.argv = &.{ pkg_config_exe, pkg_name, "--cflags", "--libs" },
|
||||
.argv = &.{ pkg_config_exe, pkg.name, "--cflags", "--libs" },
|
||||
.progress_node = progress_node,
|
||||
.allow_failure = !force,
|
||||
});
|
||||
|
||||
var zig_cflags: std.ArrayList([]const u8) = .empty;
|
||||
var zig_libs: std.ArrayList([]const u8) = .empty;
|
||||
var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t");
|
||||
|
||||
while (arg_it.next()) |arg| {
|
||||
if (mem.eql(u8, arg, "-I")) {
|
||||
const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force);
|
||||
try zig_cflags.appendSlice(arena, &.{ "-I", dir });
|
||||
} else if (mem.startsWith(u8, arg, "-I")) {
|
||||
try zig_cflags.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-L")) {
|
||||
const dir = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force);
|
||||
try zig_libs.appendSlice(arena, &.{ "-L", dir });
|
||||
} else if (mem.startsWith(u8, arg, "-L")) {
|
||||
try zig_libs.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-l")) {
|
||||
const lib = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force);
|
||||
try zig_libs.appendSlice(arena, &.{ "-l", lib });
|
||||
} else if (mem.startsWith(u8, arg, "-l")) {
|
||||
try zig_libs.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-D")) {
|
||||
const macro = arg_it.next() orelse return missingArg(maker, step, pkg_config_exe, lib_name, arg, force);
|
||||
try zig_cflags.appendSlice(arena, &.{ "-D", macro });
|
||||
} else if (mem.startsWith(u8, arg, "-D")) {
|
||||
try zig_cflags.append(arena, arg);
|
||||
} else if (mem.cutPrefix(u8, arg, "-Wl,-rpath,")) |rest| {
|
||||
try zig_cflags.appendSlice(arena, &.{ "-rpath", rest });
|
||||
} else if (force or pc.debug) {
|
||||
return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, arg });
|
||||
const parsed = std.zig.PkgConfig.parse(arena, stdout) catch |err| switch (err) {
|
||||
error.InvalidPkgConfigOutput => {
|
||||
if (force) return step.fail(maker, "{s} package {s} invalid output: {s}", .{
|
||||
pkg_config_exe, lib_name, stdout,
|
||||
});
|
||||
return error.PkgConfigUnavailable;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
if (force or pc.debug) {
|
||||
for (parsed.unknown_flags) |unknown_flag| {
|
||||
return step.fail(maker, "{s} package {s} unknown flag: {s}", .{ pkg_config_exe, lib_name, unknown_flag });
|
||||
}
|
||||
}
|
||||
|
||||
try zig_cflags.shrinkToLen(arena);
|
||||
try zig_libs.shrinkToLen(arena);
|
||||
|
||||
return .{
|
||||
.cflags = zig_cflags.toOwnedSliceAssert(),
|
||||
.libs = zig_libs.toOwnedSliceAssert(),
|
||||
};
|
||||
}
|
||||
|
||||
fn missingArg(
|
||||
maker: *Maker,
|
||||
step: *Step,
|
||||
pkg_config_exe: []const u8,
|
||||
lib_name: []const u8,
|
||||
arg: []const u8,
|
||||
force: bool,
|
||||
) RunError {
|
||||
if (force) return step.fail(maker, "{s} package {s} missing arg after flag: {s}", .{
|
||||
pkg_config_exe, lib_name, arg,
|
||||
});
|
||||
return error.PkgConfigUnavailable;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
fn getExe(graph: *const Graph) []const u8 {
|
||||
return std.zig.EnvVar.PKG_CONFIG.get(&graph.environ_map) orelse "pkg-config";
|
||||
return std.zig.PkgConfig.exe(&graph.environ_map);
|
||||
}
|
||||
|
||||
fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError![]const Pkg {
|
||||
fn getPkgs(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force: bool) RunError!std.zig.PkgConfig {
|
||||
const graph = maker.graph;
|
||||
const arena = graph.arena; // TODO don't leak into process arena
|
||||
const io = graph.io;
|
||||
@@ -167,7 +78,7 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force:
|
||||
try pc.mutex.lock(io);
|
||||
defer pc.mutex.unlock(io);
|
||||
|
||||
if (pc.list) |list| return list;
|
||||
if (pc.pkgs) |pkgs| return pkgs;
|
||||
|
||||
const pkg_config_exe = getExe(graph);
|
||||
const stdout = try captureChildProcess(maker, step, .{
|
||||
@@ -176,25 +87,18 @@ fn getList(maker: *Maker, step: *Step, progress_node: std.Progress.Node, force:
|
||||
.allow_failure = !force,
|
||||
});
|
||||
|
||||
var list: std.ArrayList(Pkg) = .empty;
|
||||
var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
|
||||
while (line_it.next()) |line| {
|
||||
if (mem.trim(u8, line, " \t").len == 0) continue;
|
||||
var tok_it = mem.tokenizeAny(u8, line, " \t");
|
||||
try list.append(arena, .{
|
||||
.name = tok_it.next() orelse {
|
||||
if (force) return step.fail(maker, "{s}: invalid line: {s}", .{
|
||||
pkg_config_exe, line,
|
||||
});
|
||||
return error.PkgConfigUnavailable;
|
||||
},
|
||||
.desc = tok_it.rest(),
|
||||
});
|
||||
}
|
||||
try list.shrinkToLen(arena);
|
||||
var diagnostic: std.zig.PkgConfig.Diagnostic = undefined;
|
||||
const result = std.zig.PkgConfig.init(arena, stdout, &diagnostic) catch |err| switch (err) {
|
||||
error.InvalidPkgConfigOutput => {
|
||||
if (force) return step.fail(maker, "{s}: invalid line({d}): {s}", .{
|
||||
pkg_config_exe, diagnostic.invalid_line_index + 1, diagnostic.invalid_line,
|
||||
});
|
||||
return error.PkgConfigUnavailable;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
const result = list.toOwnedSliceAssert();
|
||||
pc.list = result;
|
||||
pc.pkgs = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
|
||||
pub const LibCInstallation = @import("zig/LibCInstallation.zig");
|
||||
pub const WindowsSdk = @import("zig/WindowsSdk.zig");
|
||||
pub const LibCDirs = @import("zig/LibCDirs.zig");
|
||||
pub const PkgConfig = @import("zig/PkgConfig.zig");
|
||||
pub const target = @import("zig/target.zig");
|
||||
pub const llvm = @import("zig/llvm.zig");
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
//! The more reusable pieces of the build system's pkg-config integration logic.
|
||||
const PkgConfig = @This();
|
||||
|
||||
const std = @import("../std.zig");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
all: []const Pkg,
|
||||
|
||||
pub const Pkg = struct {
|
||||
name: []const u8,
|
||||
desc: []const u8,
|
||||
};
|
||||
|
||||
pub const InitError = Allocator.Error || error{InvalidPkgConfigOutput};
|
||||
|
||||
pub const Diagnostic = struct {
|
||||
invalid_line_index: usize,
|
||||
invalid_line: []const u8,
|
||||
};
|
||||
|
||||
/// Parses the output of `pkg-config --list-all`.
|
||||
pub fn init(arena: Allocator, stdout: []const u8, diagnostic: ?*Diagnostic) InitError!PkgConfig {
|
||||
var list: std.ArrayList(Pkg) = .empty;
|
||||
var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
|
||||
var line_index: usize = 0;
|
||||
while (line_it.next()) |line| : (line_index += 1) {
|
||||
if (mem.trim(u8, line, " \t").len == 0) continue;
|
||||
var tok_it = mem.tokenizeAny(u8, line, " \t");
|
||||
try list.append(arena, .{
|
||||
.name = tok_it.next() orelse {
|
||||
if (diagnostic) |d| d.* = .{
|
||||
.invalid_line_index = line_index,
|
||||
.invalid_line = line,
|
||||
};
|
||||
return error.InvalidPkgConfigOutput;
|
||||
},
|
||||
.desc = tok_it.rest(),
|
||||
});
|
||||
}
|
||||
try list.shrinkToLen(arena);
|
||||
return .{ .all = list.toOwnedSliceAssert() };
|
||||
}
|
||||
|
||||
// Maps the library name to pkg config name. Unfortunately, there are several
|
||||
// examples where this is not straightforward:
|
||||
// * -lSDL2 -> pkg-config sdl2
|
||||
// * -lgdk-3 -> pkg-config gdk-3.0
|
||||
// * -latk-1.0 -> pkg-config atk
|
||||
// * -lpulse -> pkg-config libpulse
|
||||
pub fn find(pc: *const PkgConfig, lib_name: []const u8) ?usize {
|
||||
const all = pc.all;
|
||||
|
||||
// Exact match means instant winner.
|
||||
for (all, 0..) |pkg, i| {
|
||||
if (mem.eql(u8, pkg.name, lib_name))
|
||||
return i;
|
||||
}
|
||||
|
||||
// Next we'll try ignoring case.
|
||||
for (all, 0..) |pkg, i| {
|
||||
if (std.ascii.eqlIgnoreCase(pkg.name, lib_name))
|
||||
return i;
|
||||
}
|
||||
|
||||
// Prefixed "lib" or suffixed ".0".
|
||||
for (all, 0..) |pkg, i| {
|
||||
if (std.ascii.findIgnoreCase(pkg.name, lib_name)) |pos| {
|
||||
const prefix = pkg.name[0..pos];
|
||||
const suffix = pkg.name[pos + lib_name.len ..];
|
||||
if (prefix.len > 0 and !mem.eql(u8, prefix, "lib")) continue;
|
||||
if (suffix.len > 0 and !mem.eql(u8, suffix, ".0")) continue;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Trimming "-1.0".
|
||||
if (mem.cutSuffix(u8, lib_name, "-1.0")) |trimmed| {
|
||||
for (all, 0..) |pkg, i| {
|
||||
if (std.ascii.eqlIgnoreCase(pkg.name, trimmed)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn exe(environ_map: *const std.process.Environ.Map) []const u8 {
|
||||
return std.zig.EnvVar.PKG_CONFIG.get(environ_map) orelse "pkg-config";
|
||||
}
|
||||
|
||||
pub const Parsed = struct {
|
||||
cflags: []const []const u8,
|
||||
libs: []const []const u8,
|
||||
unknown_flags: []const []const u8,
|
||||
};
|
||||
|
||||
pub const ParseError = Allocator.Error || error{InvalidPkgConfigOutput};
|
||||
|
||||
/// Parses the output of `pkg-config [name] --cflags --libs`.
|
||||
pub fn parse(arena: Allocator, stdout: []const u8) ParseError!Parsed {
|
||||
var zig_cflags: std.ArrayList([]const u8) = .empty;
|
||||
var zig_libs: std.ArrayList([]const u8) = .empty;
|
||||
var unknown_flags: std.ArrayList([]const u8) = .empty;
|
||||
var arg_it = mem.tokenizeAny(u8, stdout, " \r\n\t");
|
||||
|
||||
while (arg_it.next()) |arg| {
|
||||
if (mem.eql(u8, arg, "-I")) {
|
||||
const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput;
|
||||
try zig_cflags.appendSlice(arena, &.{ "-I", dir });
|
||||
} else if (mem.startsWith(u8, arg, "-I")) {
|
||||
try zig_cflags.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-L")) {
|
||||
const dir = arg_it.next() orelse return error.InvalidPkgConfigOutput;
|
||||
try zig_libs.appendSlice(arena, &.{ "-L", dir });
|
||||
} else if (mem.startsWith(u8, arg, "-L")) {
|
||||
try zig_libs.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-l")) {
|
||||
const lib = arg_it.next() orelse return error.InvalidPkgConfigOutput;
|
||||
try zig_libs.appendSlice(arena, &.{ "-l", lib });
|
||||
} else if (mem.startsWith(u8, arg, "-l")) {
|
||||
try zig_libs.append(arena, arg);
|
||||
} else if (mem.eql(u8, arg, "-D")) {
|
||||
const macro = arg_it.next() orelse return error.InvalidPkgConfigOutput;
|
||||
try zig_cflags.appendSlice(arena, &.{ "-D", macro });
|
||||
} else if (mem.startsWith(u8, arg, "-D")) {
|
||||
try zig_cflags.append(arena, arg);
|
||||
} else if (mem.cutPrefix(u8, arg, "-Wl,-rpath,")) |rest| {
|
||||
try zig_cflags.appendSlice(arena, &.{ "-rpath", rest });
|
||||
} else {
|
||||
try unknown_flags.append(arena, arg);
|
||||
}
|
||||
}
|
||||
|
||||
try zig_cflags.shrinkToLen(arena);
|
||||
try zig_libs.shrinkToLen(arena);
|
||||
try unknown_flags.shrinkToLen(arena);
|
||||
|
||||
return .{
|
||||
.cflags = zig_cflags.toOwnedSliceAssert(),
|
||||
.libs = zig_libs.toOwnedSliceAssert(),
|
||||
.unknown_flags = unknown_flags.toOwnedSliceAssert(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user