diff --git a/CompileCheck.zig b/CompileCheck.zig index 25e553f..c4f6ef1 100644 --- a/CompileCheck.zig +++ b/CompileCheck.zig @@ -4,7 +4,6 @@ const std = @import("std"); step: std.Build.Step, target: std.Build.ResolvedTarget, -kind: Kind, source_content: []const u8, source_path: std.Build.LazyPath, result: ?Result = null, @@ -22,40 +21,22 @@ const Result = union(enum) { }, }; -pub const Kind = union(enum) { - exe: []const u8, - header: []const u8, -}; - -pub fn create(b: *std.Build, target: std.Build.ResolvedTarget, kind: Kind) *CompileCheck { +pub fn create(b: *std.Build, target: std.Build.ResolvedTarget, source: []const u8) *CompileCheck { const write_files = b.addWriteFiles(); - const source_duped = switch (kind) { - .exe => |src| b.dupe(src), - .header => |h| b.fmt("#include <{s}>", .{h}), - }; + const source_duped = b.dupe(source); const source_path = write_files.add( - switch (kind) { - .exe => "compilecheck-exe.c", - .header => "compilecheck-header.c", - }, + "compilecheck.c", source_duped, ); const check = b.allocator.create(CompileCheck) catch @panic("OOM"); check.* = .{ .step = std.Build.Step.init(.{ .id = .custom, - .name = switch (kind) { - .exe => "compile check exe", - .header => |h| b.fmt("compile check header '{s}'", .{h}), - }, + .name = "compile check exe", .owner = b, .makeFn = make, }), .target = target, - .kind = switch (kind) { - .exe => .{ .exe = source_duped }, - .header => |h| .{ .header = b.dupe(h) }, - }, .source_content = source_duped, .source_path = source_path, }; @@ -63,15 +44,6 @@ pub fn create(b: *std.Build, target: std.Build.ResolvedTarget, kind: Kind) *Comp return check; } -pub fn haveHeader(check: *CompileCheck, asking_step: *std.Build.Step) ?u1 { - std.debug.assert(check.kind == .header); - if (!dependsOn(asking_step, &check.step)) std.debug.panic("haveHeader called on CompileCheck without a dependency", .{}); - return switch (check.result.?) { - .pass => 1, - .fail => null, - }; -} - pub fn compiled( check: *CompileCheck, asking_step: *std.Build.Step, @@ -81,7 +53,6 @@ pub fn compiled( allow_undeclared_identifier: bool = true, }, ) !?u1 { - std.debug.assert(check.kind == .exe); if (!dependsOn(asking_step, &check.step)) std.debug.panic("compiled called on CompileCheck without a dependency", .{}); return switch (check.result.?) { .pass => 1, @@ -151,10 +122,7 @@ fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!voi var zig_args: std.array_list.Managed([]const u8) = .init(b.allocator); defer zig_args.deinit(); try zig_args.append(b.graph.zig_exe); - try zig_args.append(switch (check.kind) { - .exe => "build-exe", - .header => "build-obj", - }); + try zig_args.append("build-exe"); try zig_args.append("-lc"); try zig_args.append("-target"); try zig_args.append(try check.target.query.zigTriple(b.allocator)); @@ -162,61 +130,47 @@ fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!voi for (check.include_dirs.items) |include_dir| { try include_dir.appendZigProcessFlags(b, &zig_args, step); } - const links = switch (check.kind) { - .exe => true, - .header => false, - }; - if (links) { - for (check.link_objects.items) |link_object| { - switch (link_object) { - .other_step => |other| { - switch (other.kind) { - .exe => return step.fail("cannot link with an executable build artifact", .{}), - .@"test", .test_obj => return step.fail("cannot link with a test", .{}), - .obj => { - try zig_args.append(other.getEmittedBin().getPath2(b, step)); - }, - .lib => { - const other_produces_implib = other.producesImplib(); - // For DLLs, we must link against the implib. - // For everything else, we directly link - // against the library file. - const full_path_lib = if (other_produces_implib) - getGeneratedFilePath(other, "generated_implib", step) - else - getGeneratedFilePath(other, "generated_bin", step); - try zig_args.append(full_path_lib); - }, - } - }, - else => |o| std.debug.panic("todo: handle link object {t}", .{o}), - } + + for (check.link_objects.items) |link_object| { + switch (link_object) { + .other_step => |other| { + switch (other.kind) { + .exe => return step.fail("cannot link with an executable build artifact", .{}), + .@"test", .test_obj => return step.fail("cannot link with a test", .{}), + .obj => { + try zig_args.append(other.getEmittedBin().getPath2(b, step)); + }, + .lib => { + const other_produces_implib = other.producesImplib(); + // For DLLs, we must link against the implib. + // For everything else, we directly link + // against the library file. + const full_path_lib = if (other_produces_implib) + getGeneratedFilePath(other, "generated_implib", step) + else + getGeneratedFilePath(other, "generated_bin", step); + try zig_args.append(full_path_lib); + }, + } + }, + else => |o| std.debug.panic("todo: handle link object {t}", .{o}), } } - switch (check.kind) { - .exe => { - var rand_int: u64 = undefined; - b.graph.io.random(std.mem.asBytes(&rand_int)); - const path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(rand_int); - b.cache_root.handle.createDirPath(b.graph.io, path) catch |err| return step.fail( - "create dir '{f}{s}' failed with {t}", - .{ b.cache_root, path, err }, - ); - const emit_bin_path = try b.cache_root.join(b.allocator, &.{ path, "compilecheck-exe" }); - try zig_args.append(b.fmt("-femit-bin={s}", .{emit_bin_path})); - const result = try std.process.run(b.allocator, b.graph.io, .{ - .argv = zig_args.items, - }); - try b.cache_root.handle.deleteTree(b.graph.io, path); - break :blk result; - }, - .header => { - try zig_args.append("-fno-emit-bin"); - break :blk try std.process.run(b.allocator, b.graph.io, .{ - .argv = zig_args.items, - }); - }, - } + + var rand_int: u64 = undefined; + b.graph.io.random(std.mem.asBytes(&rand_int)); + const path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(rand_int); + b.cache_root.handle.createDirPath(b.graph.io, path) catch |err| return step.fail( + "create dir '{f}{s}' failed with {t}", + .{ b.cache_root, path, err }, + ); + const emit_bin_path = try b.cache_root.join(b.allocator, &.{ path, "compilecheck-exe" }); + try zig_args.append(b.fmt("-femit-bin={s}", .{emit_bin_path})); + const result = try std.process.run(b.allocator, b.graph.io, .{ + .argv = zig_args.items, + }); + try b.cache_root.handle.deleteTree(b.graph.io, path); + break :blk result; }; std.debug.assert(result.stdout.len == 0); @@ -228,7 +182,6 @@ fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!voi var files_not_found_count: u32 = 0; var undeclared_function_count: u32 = 0; var undeclared_identifier_count: u32 = 0; - var found_header = false; var line_it = std.mem.splitScalar(u8, result.stderr, '\n'); while (line_it.next()) |line_untrimmed| { const line = std.mem.trimEnd(u8, line_untrimmed, "\r"); @@ -240,22 +193,10 @@ fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!voi }; const err = line[error_start + error_prefix.len ..]; if (std.mem.endsWith(u8, err, "file not found")) { - switch (check.kind) { - .exe => {}, - .header => |h| found_header = found_header or (std.mem.indexOf(u8, err, h) != null), - } files_not_found_count += 1; } else if (std.mem.startsWith(u8, err, "call to undeclared function ")) { - switch (check.kind) { - .exe => {}, - .header => return check.notAllowed(step, "undeclared function", result.stderr), - } undeclared_function_count += 1; } else if (std.mem.startsWith(u8, err, "use of undeclared identifier")) { - switch (check.kind) { - .exe => {}, - .header => return check.notAllowed(step, "undeclared identifier", result.stderr), - } undeclared_identifier_count += 1; } else { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! diff --git a/build.zig b/build.zig index 7cd0a2e..3670e0d 100644 --- a/build.zig +++ b/build.zig @@ -1412,13 +1412,34 @@ fn addPyconfig( .@"3.12.11" => &exe_config_set.@"3.12.11", }; + const libc_dirs = try std.zig.LibCDirs.detect( + b.allocator, + b.graph.io, + b.graph.zig_lib_directory.path.?, + &target.result, + target.query.isNativeAbi(), + true, // link_libc + null, // libc_installation + &b.graph.environ_map, + ); + switch (version) { + inline else => |v| { + const Enum = @field(header_enum, @tagName(v)); + const headers = try systemquery.HeaderSet(Enum).scanDirs(b.graph.io, libc_dirs.libc_include_dir_list); + for (header_configs) |config| { + const header = std.meta.stringToEnum(Enum, config.string).?; + const assume = if (header == .@"zlib.h") (libs.zlib != null) else false; + const found = headers.contains(header); + config_header.addValue(config.name, ?u1, if (found or assume) 1 else null); + } + }, + } + { const AddValues = struct { step: std.Build.Step, version: Version, config_header: *std.Build.Step.ConfigHeader, - header_configs: []const Config, - header_checks: []*CompileCheck, exe_configs: []const Config, exe_checks: []*CompileCheck, }; @@ -1426,9 +1447,6 @@ fn addPyconfig( fn make(step: *std.Build.Step, options: std.Build.Step.MakeOptions) anyerror!void { _ = options; const self: *AddValues = @fieldParentPtr("step", step); - for (self.header_configs, self.header_checks) |config, check| { - self.config_header.addValue(config.name, ?u1, check.haveHeader(step)); - } for (self.exe_configs, self.exe_checks) |config, check| { self.config_header.addValue(config.name, ?u1, try check.compiled(step, .{})); } @@ -1444,19 +1462,11 @@ fn addPyconfig( }), .version = version, .config_header = config_header, - .header_configs = header_configs, - .header_checks = b.allocator.alloc(*CompileCheck, header_configs.len) catch @panic("OOM"), .exe_configs = exe_configs, .exe_checks = b.allocator.alloc(*CompileCheck, exe_configs.len) catch @panic("OOM"), }; - for (header_configs, add_values.header_checks) |config, *check| { - check.* = CompileCheck.create(b, target, .{ .header = config.string }); - if (libs.zlib) |zlib| check.*.linkLibrary(zlib); - if (libs.openssl) |openssl| check.*.linkLibrary(openssl); - add_values.step.dependOn(&check.*.step); - } for (exe_configs, add_values.exe_checks) |config, *check| { - check.* = CompileCheck.create(b, target, .{ .exe = config.string }); + check.* = CompileCheck.create(b, target, config.string); if (libs.zlib) |zlib| check.*.linkLibrary(zlib); if (libs.openssl) |openssl| check.*.linkLibrary(openssl); add_values.step.dependOn(&check.*.step); @@ -1476,6 +1486,23 @@ fn have(x: bool) ?u1 { } const Config = struct { name: []const u8, string: []const u8 }; + +const header_enum = struct { + pub const @"3.11.13" = HeaderEnum(header_config_set.@"3.11.13"); + pub const @"3.12.11" = HeaderEnum(header_config_set.@"3.12.11"); +}; + +fn HeaderEnum(comptime configs: anytype) type { + const TagInt = std.math.IntFittingRange(0, configs.len - 1); + var names: [configs.len][]const u8 = undefined; + var values: [configs.len]TagInt = undefined; + for (configs, 0..) |config, i| { + names[i] = config.string; + values[i] = i; + } + return @Enum(TagInt, .exhaustive, &names, &values); +} + fn concatConfigs(comptime first: anytype, comptime second: anytype) [std.meta.fields(@TypeOf(first)).len + std.meta.fields(@TypeOf(second)).len]Config { const first_len = std.meta.fields(@TypeOf(first)).len; var result: [first_len + std.meta.fields(@TypeOf(second)).len]Config = undefined; @@ -1804,4 +1831,5 @@ fn concat(allocator: std.mem.Allocator, lists: []const []const []const u8) []con } const std = @import("std"); +const systemquery = @import("systemquery.zig"); const CompileCheck = @import("CompileCheck.zig"); diff --git a/build.zig.zon b/build.zig.zon index e56e7d9..51cdb5f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -29,6 +29,7 @@ "build.zig.zon", "makesetup.zig", "replace.zig", + "systemquery.zig", "CompileCheck.zig", }, } diff --git a/systemquery.zig b/systemquery.zig new file mode 100644 index 0000000..61b6cca --- /dev/null +++ b/systemquery.zig @@ -0,0 +1,112 @@ +pub fn HeaderSet(comptime HeaderEnum: type) type { + return struct { + const Self = @This(); + bits: std.StaticBitSet(std.meta.fields(HeaderEnum).len), + + pub fn scanDirs(io: std.Io, include_dirs: []const []const u8) !Self { + var set = Self{ .bits = std.StaticBitSet(std.meta.fields(HeaderEnum).len).initEmpty() }; + for (include_dirs) |include_dir| { + try set.scanDir(io, include_dir, ""); + } + return set; + } + + pub fn contains(self: Self, header: HeaderEnum) bool { + return self.bits.isSet(@intFromEnum(header)); + } + + const max_name_len = blk: { + var max: usize = 0; + for (std.meta.fields(HeaderEnum)) |field| { + if (field.name.len > max) max = field.name.len; + } + break :blk max; + }; + + fn scanDir(self: *Self, io: std.Io, base_dir: []const u8, prefix: []const u8) !void { + const sep: []const u8 = if (prefix.len == 0) "" else std.fs.path.sep_str; + var dir = blk: { + var buf: [std.fs.max_path_bytes]u8 = undefined; + const dir_path = std.fmt.bufPrint(&buf, "{s}{s}{s}", .{ base_dir, sep, prefix }) catch return; + break :blk std.Io.Dir.cwd().openDir(io, dir_path, .{ .iterate = true }) catch |err| switch (err) { + error.FileNotFound => return, + else => |e| return e, + }; + }; + defer dir.close(io); + var it = dir.iterate(); + while (try it.next(io)) |entry| { + var name_buf: [max_name_len]u8 = undefined; + const name = std.fmt.bufPrint(&name_buf, "{s}{s}{s}", .{ prefix, sep, entry.name }) catch continue; + if (entry.kind == .directory) { + if (headerPrefixExists(name)) try self.scanDir(io, base_dir, name); + } else { + if (enumFromName(name)) |header| self.bits.set(@intFromEnum(header)); + } + } + } + + const known_prefixes = blk: { + @setEvalBranchQuota(30 * std.meta.fields(HeaderEnum).len); + var prefixes: []const []const u8 = &.{}; + for (std.meta.fields(HeaderEnum)) |field| { + var start: usize = 0; + while (std.mem.indexOfPos(u8, field.name, start, "/")) |sep_idx| { + const dir = field.name[0..sep_idx]; + for (prefixes) |p| { + if (std.mem.eql(u8, p, dir)) break; + } else { + prefixes = prefixes ++ .{dir}; + } + start = sep_idx + 1; + } + } + break :blk prefixes; + }; + + fn eqlName(a: []const u8, b: []const u8) bool { + return switch (builtin.os.tag) { + .windows => std.ascii.eqlIgnoreCase(a, b), + else => std.mem.eql(u8, a, b), + }; + } + + fn enumFromName(name: []const u8) ?HeaderEnum { + if (builtin.os.tag == .windows) { + for (std.meta.fields(HeaderEnum), 0..) |field, i| { + if (std.ascii.eqlIgnoreCase(name, field.name)) + return @enumFromInt(i); + } + return null; + } else return std.meta.stringToEnum(HeaderEnum, name); + } + + fn headerPrefixExists(prefix: []const u8) bool { + for (known_prefixes) |p| { + if (eqlName(prefix, p)) return true; + } + return false; + } + }; +} + +test "known_prefixes" { + const S = HeaderSet(enum { + @"alloca.h", + @"sys/stat.h", + @"sys/types.h", + @"net/if.h", + @"sys/sys/domain.h", + @"foo/bar/baz.h", + }); + try std.testing.expectEqualDeep(@as([]const []const u8, &.{ + "sys", + "net", + "sys/sys", + "foo", + "foo/bar", + }), S.known_prefixes); +} + +const builtin = @import("builtin"); +const std = @import("std");