bc10382ec1
Mostly picking the same paths as FreeBSD. We need a little special handling for crt files, as netbsd uses its own (and not GCC's) for those, with slightly different names.
469 lines
18 KiB
Zig
469 lines
18 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const event = std.event;
|
|
const Target = @import("target.zig").Target;
|
|
const c = @import("c.zig");
|
|
|
|
/// See the render function implementation for documentation of the fields.
|
|
pub const LibCInstallation = struct {
|
|
include_dir: []const u8,
|
|
lib_dir: ?[]const u8,
|
|
static_lib_dir: ?[]const u8,
|
|
msvc_lib_dir: ?[]const u8,
|
|
kernel32_lib_dir: ?[]const u8,
|
|
dynamic_linker_path: ?[]const u8,
|
|
|
|
pub const FindError = error{
|
|
OutOfMemory,
|
|
FileSystem,
|
|
UnableToSpawnCCompiler,
|
|
CCompilerExitCode,
|
|
CCompilerCrashed,
|
|
CCompilerCannotFindHeaders,
|
|
LibCRuntimeNotFound,
|
|
LibCStdLibHeaderNotFound,
|
|
LibCKernel32LibNotFound,
|
|
UnsupportedArchitecture,
|
|
};
|
|
|
|
pub fn parse(
|
|
self: *LibCInstallation,
|
|
allocator: *std.mem.Allocator,
|
|
libc_file: []const u8,
|
|
stderr: *std.io.OutStream(std.os.File.WriteError),
|
|
) !void {
|
|
self.initEmpty();
|
|
|
|
const keys = []const []const u8{
|
|
"include_dir",
|
|
"lib_dir",
|
|
"static_lib_dir",
|
|
"msvc_lib_dir",
|
|
"kernel32_lib_dir",
|
|
"dynamic_linker_path",
|
|
};
|
|
const FoundKey = struct {
|
|
found: bool,
|
|
allocated: ?[]u8,
|
|
};
|
|
var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len;
|
|
errdefer {
|
|
self.initEmpty();
|
|
for (found_keys) |found_key| {
|
|
if (found_key.allocated) |s| allocator.free(s);
|
|
}
|
|
}
|
|
|
|
const contents = try std.io.readFileAlloc(allocator, libc_file);
|
|
defer allocator.free(contents);
|
|
|
|
var it = std.mem.tokenize(contents, "\n");
|
|
while (it.next()) |line| {
|
|
if (line.len == 0 or line[0] == '#') continue;
|
|
var line_it = std.mem.separate(line, "=");
|
|
const name = line_it.next() orelse {
|
|
try stderr.print("missing equal sign after field name\n");
|
|
return error.ParseError;
|
|
};
|
|
const value = line_it.rest();
|
|
inline for (keys) |key, i| {
|
|
if (std.mem.eql(u8, name, key)) {
|
|
found_keys[i].found = true;
|
|
switch (@typeInfo(@typeOf(@field(self, key)))) {
|
|
builtin.TypeId.Optional => {
|
|
if (value.len == 0) {
|
|
@field(self, key) = null;
|
|
} else {
|
|
found_keys[i].allocated = try std.mem.dupe(allocator, u8, value);
|
|
@field(self, key) = found_keys[i].allocated;
|
|
}
|
|
},
|
|
else => {
|
|
if (value.len == 0) {
|
|
try stderr.print("field cannot be empty: {}\n", key);
|
|
return error.ParseError;
|
|
}
|
|
const dupe = try std.mem.dupe(allocator, u8, value);
|
|
found_keys[i].allocated = dupe;
|
|
@field(self, key) = dupe;
|
|
},
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (found_keys) |found_key, i| {
|
|
if (!found_key.found) {
|
|
try stderr.print("missing field: {}\n", keys[i]);
|
|
return error.ParseError;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(std.os.File.WriteError)) !void {
|
|
@setEvalBranchQuota(4000);
|
|
try out.print(
|
|
\\# The directory that contains `stdlib.h`.
|
|
\\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`
|
|
\\include_dir={}
|
|
\\
|
|
\\# The directory that contains `crt1.o`.
|
|
\\# On Linux, can be found with `cc -print-file-name=crt1.o`.
|
|
\\# Not needed when targeting MacOS.
|
|
\\lib_dir={}
|
|
\\
|
|
\\# The directory that contains `crtbegin.o`.
|
|
\\# On Linux, can be found with `cc -print-file-name=crtbegin.o`.
|
|
\\# Not needed when targeting MacOS or Windows.
|
|
\\static_lib_dir={}
|
|
\\
|
|
\\# The directory that contains `vcruntime.lib`.
|
|
\\# Only needed when targeting Windows.
|
|
\\msvc_lib_dir={}
|
|
\\
|
|
\\# The directory that contains `kernel32.lib`.
|
|
\\# Only needed when targeting Windows.
|
|
\\kernel32_lib_dir={}
|
|
\\
|
|
\\# The full path to the dynamic linker, on the target system.
|
|
\\# Only needed when targeting Linux.
|
|
\\dynamic_linker_path={}
|
|
\\
|
|
,
|
|
self.include_dir,
|
|
self.lib_dir orelse "",
|
|
self.static_lib_dir orelse "",
|
|
self.msvc_lib_dir orelse "",
|
|
self.kernel32_lib_dir orelse "",
|
|
self.dynamic_linker_path orelse Target(Target.Native).getDynamicLinkerPath(),
|
|
);
|
|
}
|
|
|
|
/// Finds the default, native libc.
|
|
pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void {
|
|
self.initEmpty();
|
|
var group = event.Group(FindError!void).init(loop);
|
|
errdefer group.deinit();
|
|
var windows_sdk: ?*c.ZigWindowsSDK = null;
|
|
errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk));
|
|
|
|
switch (builtin.os) {
|
|
builtin.Os.windows => {
|
|
var sdk: *c.ZigWindowsSDK = undefined;
|
|
switch (c.zig_find_windows_sdk(@ptrCast(?[*]?[*]c.ZigWindowsSDK, &sdk))) {
|
|
c.ZigFindWindowsSdkError.None => {
|
|
windows_sdk = sdk;
|
|
|
|
if (sdk.msvc_lib_dir_ptr != 0) {
|
|
self.msvc_lib_dir = try std.mem.dupe(loop.allocator, u8, sdk.msvc_lib_dir_ptr[0..sdk.msvc_lib_dir_len]);
|
|
}
|
|
try group.call(findNativeKernel32LibDir, self, loop, sdk);
|
|
try group.call(findNativeIncludeDirWindows, self, loop, sdk);
|
|
try group.call(findNativeLibDirWindows, self, loop, sdk);
|
|
},
|
|
c.ZigFindWindowsSdkError.OutOfMemory => return error.OutOfMemory,
|
|
c.ZigFindWindowsSdkError.NotFound => return error.NotFound,
|
|
c.ZigFindWindowsSdkError.PathTooLong => return error.NotFound,
|
|
}
|
|
},
|
|
builtin.Os.linux => {
|
|
try group.call(findNativeIncludeDirLinux, self, loop);
|
|
try group.call(findNativeLibDirLinux, self, loop);
|
|
try group.call(findNativeStaticLibDir, self, loop);
|
|
try group.call(findNativeDynamicLinker, self, loop);
|
|
},
|
|
builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => {
|
|
self.include_dir = try std.mem.dupe(loop.allocator, u8, "/usr/include");
|
|
},
|
|
else => @compileError("unimplemented: find libc for this OS"),
|
|
}
|
|
return await (async group.wait() catch unreachable);
|
|
}
|
|
|
|
async fn findNativeIncludeDirLinux(self: *LibCInstallation, loop: *event.Loop) !void {
|
|
const cc_exe = std.os.getEnvPosix("CC") orelse "cc";
|
|
const argv = []const []const u8{
|
|
cc_exe,
|
|
"-E",
|
|
"-Wp,-v",
|
|
"-xc",
|
|
"/dev/null",
|
|
};
|
|
// TODO make this use event loop
|
|
const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024);
|
|
const exec_result = if (std.debug.runtime_safety) blk: {
|
|
break :blk errorable_result catch unreachable;
|
|
} else blk: {
|
|
break :blk errorable_result catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => return error.UnableToSpawnCCompiler,
|
|
};
|
|
};
|
|
defer {
|
|
loop.allocator.free(exec_result.stdout);
|
|
loop.allocator.free(exec_result.stderr);
|
|
}
|
|
|
|
switch (exec_result.term) {
|
|
std.os.ChildProcess.Term.Exited => |code| {
|
|
if (code != 0) return error.CCompilerExitCode;
|
|
},
|
|
else => {
|
|
return error.CCompilerCrashed;
|
|
},
|
|
}
|
|
|
|
var it = std.mem.tokenize(exec_result.stderr, "\n\r");
|
|
var search_paths = std.ArrayList([]const u8).init(loop.allocator);
|
|
defer search_paths.deinit();
|
|
while (it.next()) |line| {
|
|
if (line.len != 0 and line[0] == ' ') {
|
|
try search_paths.append(line);
|
|
}
|
|
}
|
|
if (search_paths.len == 0) {
|
|
return error.CCompilerCannotFindHeaders;
|
|
}
|
|
|
|
// search in reverse order
|
|
var path_i: usize = 0;
|
|
while (path_i < search_paths.len) : (path_i += 1) {
|
|
const search_path_untrimmed = search_paths.at(search_paths.len - path_i - 1);
|
|
const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " ");
|
|
const stdlib_path = try std.os.path.join(loop.allocator, [][]const u8{ search_path, "stdlib.h" });
|
|
defer loop.allocator.free(stdlib_path);
|
|
|
|
if (try fileExists(stdlib_path)) {
|
|
self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
|
|
return;
|
|
}
|
|
}
|
|
|
|
return error.LibCStdLibHeaderNotFound;
|
|
}
|
|
|
|
async fn findNativeIncludeDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) !void {
|
|
var search_buf: [2]Search = undefined;
|
|
const searches = fillSearch(&search_buf, sdk);
|
|
|
|
var result_buf = try std.Buffer.initSize(loop.allocator, 0);
|
|
defer result_buf.deinit();
|
|
|
|
for (searches) |search| {
|
|
result_buf.shrink(0);
|
|
const stream = &std.io.BufferOutStream.init(&result_buf).stream;
|
|
try stream.print("{}\\Include\\{}\\ucrt", search.path, search.version);
|
|
|
|
const stdlib_path = try std.os.path.join(
|
|
loop.allocator,
|
|
[][]const u8{ result_buf.toSliceConst(), "stdlib.h" },
|
|
);
|
|
defer loop.allocator.free(stdlib_path);
|
|
|
|
if (try fileExists(stdlib_path)) {
|
|
self.include_dir = result_buf.toOwnedSlice();
|
|
return;
|
|
}
|
|
}
|
|
|
|
return error.LibCStdLibHeaderNotFound;
|
|
}
|
|
|
|
async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void {
|
|
var search_buf: [2]Search = undefined;
|
|
const searches = fillSearch(&search_buf, sdk);
|
|
|
|
var result_buf = try std.Buffer.initSize(loop.allocator, 0);
|
|
defer result_buf.deinit();
|
|
|
|
for (searches) |search| {
|
|
result_buf.shrink(0);
|
|
const stream = &std.io.BufferOutStream.init(&result_buf).stream;
|
|
try stream.print("{}\\Lib\\{}\\ucrt\\", search.path, search.version);
|
|
switch (builtin.arch) {
|
|
builtin.Arch.i386 => try stream.write("x86"),
|
|
builtin.Arch.x86_64 => try stream.write("x64"),
|
|
builtin.Arch.aarch64v8 => try stream.write("arm"),
|
|
else => return error.UnsupportedArchitecture,
|
|
}
|
|
const ucrt_lib_path = try std.os.path.join(
|
|
loop.allocator,
|
|
[][]const u8{ result_buf.toSliceConst(), "ucrt.lib" },
|
|
);
|
|
defer loop.allocator.free(ucrt_lib_path);
|
|
if (try fileExists(ucrt_lib_path)) {
|
|
self.lib_dir = result_buf.toOwnedSlice();
|
|
return;
|
|
}
|
|
}
|
|
return error.LibCRuntimeNotFound;
|
|
}
|
|
|
|
async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) FindError!void {
|
|
self.lib_dir = try await (async ccPrintFileName(loop, "crt1.o", true) catch unreachable);
|
|
}
|
|
|
|
async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void {
|
|
self.static_lib_dir = try await (async ccPrintFileName(loop, "crtbegin.o", true) catch unreachable);
|
|
}
|
|
|
|
async fn findNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop) FindError!void {
|
|
var dyn_tests = []DynTest{
|
|
DynTest{
|
|
.name = "ld-linux-x86-64.so.2",
|
|
.result = null,
|
|
},
|
|
DynTest{
|
|
.name = "ld-musl-x86_64.so.1",
|
|
.result = null,
|
|
},
|
|
};
|
|
var group = event.Group(FindError!void).init(loop);
|
|
errdefer group.deinit();
|
|
for (dyn_tests) |*dyn_test| {
|
|
try group.call(testNativeDynamicLinker, self, loop, dyn_test);
|
|
}
|
|
try await (async group.wait() catch unreachable);
|
|
for (dyn_tests) |*dyn_test| {
|
|
if (dyn_test.result) |result| {
|
|
self.dynamic_linker_path = result;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
const DynTest = struct {
|
|
name: []const u8,
|
|
result: ?[]const u8,
|
|
};
|
|
|
|
async fn testNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop, dyn_test: *DynTest) FindError!void {
|
|
if (await (async ccPrintFileName(loop, dyn_test.name, false) catch unreachable)) |result| {
|
|
dyn_test.result = result;
|
|
return;
|
|
} else |err| switch (err) {
|
|
error.LibCRuntimeNotFound => return,
|
|
else => return err,
|
|
}
|
|
}
|
|
|
|
async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void {
|
|
var search_buf: [2]Search = undefined;
|
|
const searches = fillSearch(&search_buf, sdk);
|
|
|
|
var result_buf = try std.Buffer.initSize(loop.allocator, 0);
|
|
defer result_buf.deinit();
|
|
|
|
for (searches) |search| {
|
|
result_buf.shrink(0);
|
|
const stream = &std.io.BufferOutStream.init(&result_buf).stream;
|
|
try stream.print("{}\\Lib\\{}\\um\\", search.path, search.version);
|
|
switch (builtin.arch) {
|
|
builtin.Arch.i386 => try stream.write("x86\\"),
|
|
builtin.Arch.x86_64 => try stream.write("x64\\"),
|
|
builtin.Arch.aarch64v8 => try stream.write("arm\\"),
|
|
else => return error.UnsupportedArchitecture,
|
|
}
|
|
const kernel32_path = try std.os.path.join(
|
|
loop.allocator,
|
|
[][]const u8{ result_buf.toSliceConst(), "kernel32.lib" },
|
|
);
|
|
defer loop.allocator.free(kernel32_path);
|
|
if (try fileExists(kernel32_path)) {
|
|
self.kernel32_lib_dir = result_buf.toOwnedSlice();
|
|
return;
|
|
}
|
|
}
|
|
return error.LibCKernel32LibNotFound;
|
|
}
|
|
|
|
fn initEmpty(self: *LibCInstallation) void {
|
|
self.* = LibCInstallation{
|
|
.include_dir = ([*]const u8)(undefined)[0..0],
|
|
.lib_dir = null,
|
|
.static_lib_dir = null,
|
|
.msvc_lib_dir = null,
|
|
.kernel32_lib_dir = null,
|
|
.dynamic_linker_path = null,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// caller owns returned memory
|
|
async fn ccPrintFileName(loop: *event.Loop, o_file: []const u8, want_dirname: bool) ![]u8 {
|
|
const cc_exe = std.os.getEnvPosix("CC") orelse "cc";
|
|
const arg1 = try std.fmt.allocPrint(loop.allocator, "-print-file-name={}", o_file);
|
|
defer loop.allocator.free(arg1);
|
|
const argv = []const []const u8{ cc_exe, arg1 };
|
|
|
|
// TODO This simulates evented I/O for the child process exec
|
|
await (async loop.yield() catch unreachable);
|
|
const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024);
|
|
const exec_result = if (std.debug.runtime_safety) blk: {
|
|
break :blk errorable_result catch unreachable;
|
|
} else blk: {
|
|
break :blk errorable_result catch |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => return error.UnableToSpawnCCompiler,
|
|
};
|
|
};
|
|
defer {
|
|
loop.allocator.free(exec_result.stdout);
|
|
loop.allocator.free(exec_result.stderr);
|
|
}
|
|
switch (exec_result.term) {
|
|
std.os.ChildProcess.Term.Exited => |code| {
|
|
if (code != 0) return error.CCompilerExitCode;
|
|
},
|
|
else => {
|
|
return error.CCompilerCrashed;
|
|
},
|
|
}
|
|
var it = std.mem.tokenize(exec_result.stdout, "\n\r");
|
|
const line = it.next() orelse return error.LibCRuntimeNotFound;
|
|
const dirname = std.os.path.dirname(line) orelse return error.LibCRuntimeNotFound;
|
|
|
|
if (want_dirname) {
|
|
return std.mem.dupe(loop.allocator, u8, dirname);
|
|
} else {
|
|
return std.mem.dupe(loop.allocator, u8, line);
|
|
}
|
|
}
|
|
|
|
const Search = struct {
|
|
path: []const u8,
|
|
version: []const u8,
|
|
};
|
|
|
|
fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
|
|
var search_end: usize = 0;
|
|
if (sdk.path10_ptr != 0) {
|
|
if (sdk.version10_ptr != 0) {
|
|
search_buf[search_end] = Search{
|
|
.path = sdk.path10_ptr[0..sdk.path10_len],
|
|
.version = sdk.version10_ptr[0..sdk.version10_len],
|
|
};
|
|
search_end += 1;
|
|
}
|
|
}
|
|
if (sdk.path81_ptr != 0) {
|
|
if (sdk.version81_ptr != 0) {
|
|
search_buf[search_end] = Search{
|
|
.path = sdk.path81_ptr[0..sdk.path81_len],
|
|
.version = sdk.version81_ptr[0..sdk.version81_len],
|
|
};
|
|
search_end += 1;
|
|
}
|
|
}
|
|
return search_buf[0..search_end];
|
|
}
|
|
|
|
fn fileExists(path: []const u8) !bool {
|
|
if (std.os.File.access(path)) |_| {
|
|
return true;
|
|
} else |err| switch (err) {
|
|
error.FileNotFound, error.PermissionDenied => return false,
|
|
else => return error.FileSystem,
|
|
}
|
|
}
|