mirror of
https://github.com/ziglang/zig.git
synced 2026-02-11 12:01:18 +00:00
This is f5fb720a5399ee98e45f36337b2f68a4d23a783c plus ehaas's nonnull attribute pull request currently at 4b26cb3ac610a0a070fc43e43da8b4cdf0e9101b with zig patches intact.
495 lines
16 KiB
Zig
Vendored
495 lines
16 KiB
Zig
Vendored
const std = @import("std");
|
|
const mem = std.mem;
|
|
|
|
const system_defaults = @import("system_defaults");
|
|
|
|
const Compilation = @import("Compilation.zig");
|
|
const Driver = @import("Driver.zig");
|
|
const Filesystem = @import("Driver/Filesystem.zig").Filesystem;
|
|
const Multilib = @import("Driver/Multilib.zig");
|
|
const target_util = @import("target.zig");
|
|
|
|
pub const PathList = std.ArrayList([]const u8);
|
|
|
|
pub const RuntimeLibKind = enum {
|
|
compiler_rt,
|
|
libgcc,
|
|
};
|
|
|
|
pub const FileKind = enum {
|
|
object,
|
|
static,
|
|
shared,
|
|
};
|
|
|
|
pub const LibGCCKind = enum {
|
|
unspecified,
|
|
static,
|
|
shared,
|
|
};
|
|
|
|
pub const UnwindLibKind = enum {
|
|
none,
|
|
compiler_rt,
|
|
libgcc,
|
|
};
|
|
|
|
const Inner = union(enum) {
|
|
uninitialized,
|
|
unknown: void,
|
|
};
|
|
|
|
const Toolchain = @This();
|
|
|
|
filesystem: Filesystem,
|
|
driver: *Driver,
|
|
|
|
/// The list of toolchain specific path prefixes to search for libraries.
|
|
library_paths: PathList = .{},
|
|
|
|
/// The list of toolchain specific path prefixes to search for files.
|
|
file_paths: PathList = .{},
|
|
|
|
/// The list of toolchain specific path prefixes to search for programs.
|
|
program_paths: PathList = .{},
|
|
|
|
selected_multilib: Multilib = .{},
|
|
|
|
inner: Inner = .{ .uninitialized = {} },
|
|
|
|
pub fn getTarget(tc: *const Toolchain) std.Target {
|
|
return tc.driver.comp.target;
|
|
}
|
|
|
|
fn getDefaultLinker(tc: *const Toolchain) []const u8 {
|
|
return switch (tc.inner) {
|
|
.uninitialized => unreachable,
|
|
.unknown => "ld",
|
|
};
|
|
}
|
|
|
|
/// Call this after driver has finished parsing command line arguments to find the toolchain
|
|
pub fn discover(tc: *Toolchain) !void {
|
|
if (tc.inner != .uninitialized) return;
|
|
|
|
tc.inner = .unknown;
|
|
return switch (tc.inner) {
|
|
.uninitialized => unreachable,
|
|
.unknown => {},
|
|
};
|
|
}
|
|
|
|
pub fn deinit(tc: *Toolchain) void {
|
|
const gpa = tc.driver.comp.gpa;
|
|
|
|
tc.library_paths.deinit(gpa);
|
|
tc.file_paths.deinit(gpa);
|
|
tc.program_paths.deinit(gpa);
|
|
}
|
|
|
|
/// Write assembler path to `buf` and return a slice of it
|
|
pub fn getAssemblerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
|
|
return tc.getProgramPath("as", buf);
|
|
}
|
|
|
|
/// Write linker path to `buf` and return a slice of it
|
|
pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
|
|
// --ld-path= takes precedence over -fuse-ld= and specifies the executable
|
|
// name. -B, COMPILER_PATH and PATH are consulted if the value does not
|
|
// contain a path component separator.
|
|
// -fuse-ld=lld can be used with --ld-path= to indicate that the binary
|
|
// that --ld-path= points to is lld.
|
|
const use_linker = tc.driver.use_linker orelse system_defaults.linker;
|
|
|
|
if (tc.driver.linker_path) |ld_path| {
|
|
var path = ld_path;
|
|
if (path.len > 0) {
|
|
if (std.fs.path.dirname(path) == null) {
|
|
path = tc.getProgramPath(path, buf);
|
|
}
|
|
if (tc.filesystem.canExecute(path)) {
|
|
return path;
|
|
}
|
|
}
|
|
return tc.driver.fatal(
|
|
"invalid linker name in argument '--ld-path={s}'",
|
|
.{path},
|
|
);
|
|
}
|
|
|
|
// If we're passed -fuse-ld= with no argument, or with the argument ld,
|
|
// then use whatever the default system linker is.
|
|
if (use_linker.len == 0 or mem.eql(u8, use_linker, "ld")) {
|
|
const default = tc.getDefaultLinker();
|
|
if (std.fs.path.isAbsolute(default)) return default;
|
|
return tc.getProgramPath(default, buf);
|
|
}
|
|
|
|
// Extending -fuse-ld= to an absolute or relative path is unexpected. Checking
|
|
// for the linker flavor is brittle. In addition, prepending "ld." or "ld64."
|
|
// to a relative path is surprising. This is more complex due to priorities
|
|
// among -B, COMPILER_PATH and PATH. --ld-path= should be used instead.
|
|
if (mem.indexOfScalar(u8, use_linker, '/') != null) {
|
|
try tc.driver.comp.diagnostics.add(.{
|
|
.text = "'-fuse-ld=' taking a path is deprecated; use '--ld-path=' instead",
|
|
.kind = .off,
|
|
.opt = .@"fuse-ld-path",
|
|
.location = null,
|
|
});
|
|
}
|
|
|
|
if (std.fs.path.isAbsolute(use_linker)) {
|
|
if (tc.filesystem.canExecute(use_linker)) {
|
|
return use_linker;
|
|
}
|
|
} else {
|
|
const gpa = tc.driver.comp.gpa;
|
|
var linker_name: std.ArrayList(u8) = .empty;
|
|
defer linker_name.deinit(gpa);
|
|
try linker_name.ensureUnusedCapacity(tc.driver.comp.gpa, 5 + use_linker.len); // "ld64." ++ use_linker
|
|
|
|
if (tc.getTarget().os.tag.isDarwin()) {
|
|
linker_name.appendSliceAssumeCapacity("ld64.");
|
|
} else {
|
|
linker_name.appendSliceAssumeCapacity("ld.");
|
|
}
|
|
linker_name.appendSliceAssumeCapacity(use_linker);
|
|
const linker_path = tc.getProgramPath(linker_name.items, buf);
|
|
if (tc.filesystem.canExecute(linker_path)) {
|
|
return linker_path;
|
|
}
|
|
}
|
|
|
|
if (tc.driver.use_linker) |linker| {
|
|
return tc.driver.fatal(
|
|
"invalid linker name in argument '-fuse-ld={s}'",
|
|
.{linker},
|
|
);
|
|
}
|
|
const default_linker = tc.getDefaultLinker();
|
|
return tc.getProgramPath(default_linker, buf);
|
|
}
|
|
|
|
/// If an explicit target is provided, also check the prefixed tool-specific name
|
|
/// TODO: this isn't exactly right since our target names don't necessarily match up
|
|
/// with GCC's.
|
|
/// For example the Zig target `arm-freestanding-eabi` would need the `arm-none-eabi` tools
|
|
fn possibleProgramNames(
|
|
raw_triple: ?[]const u8,
|
|
name: []const u8,
|
|
buf: *[64]u8,
|
|
possible_name_buf: *[2][]const u8,
|
|
) []const []const u8 {
|
|
var i: u32 = 0;
|
|
if (raw_triple) |triple| {
|
|
if (std.fmt.bufPrint(buf, "{s}-{s}", .{ triple, name })) |res| {
|
|
possible_name_buf[i] = res;
|
|
i += 1;
|
|
} else |_| {}
|
|
}
|
|
possible_name_buf[i] = name;
|
|
|
|
return possible_name_buf[0..i];
|
|
}
|
|
|
|
/// Add toolchain `file_paths` to argv as `-L` arguments
|
|
pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
|
|
try argv.ensureUnusedCapacity(tc.driver.comp.gpa, tc.file_paths.items.len);
|
|
|
|
var bytes_needed: usize = 0;
|
|
for (tc.file_paths.items) |path| {
|
|
bytes_needed += path.len + 2; // +2 for `-L`
|
|
}
|
|
var bytes = try tc.driver.comp.arena.alloc(u8, bytes_needed);
|
|
var index: usize = 0;
|
|
for (tc.file_paths.items) |path| {
|
|
@memcpy(bytes[index..][0..2], "-L");
|
|
@memcpy(bytes[index + 2 ..][0..path.len], path);
|
|
argv.appendAssumeCapacity(bytes[index..][0 .. path.len + 2]);
|
|
index += path.len + 2;
|
|
}
|
|
}
|
|
|
|
/// Search for an executable called `name` or `{triple}-{name} in program_paths and the $PATH environment variable
|
|
/// If not found there, just use `name`
|
|
/// Writes the result to `buf` and returns a slice of it
|
|
fn getProgramPath(tc: *const Toolchain, name: []const u8, buf: []u8) []const u8 {
|
|
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
|
|
|
|
var tool_specific_buf: [64]u8 = undefined;
|
|
var possible_name_buf: [2][]const u8 = undefined;
|
|
const possible_names = possibleProgramNames(tc.driver.raw_target_triple, name, &tool_specific_buf, &possible_name_buf);
|
|
|
|
for (possible_names) |tool_name| {
|
|
for (tc.program_paths.items) |program_path| {
|
|
defer fib.reset();
|
|
|
|
const candidate = std.fs.path.join(fib.allocator(), &.{ program_path, tool_name }) catch continue;
|
|
|
|
if (tc.filesystem.canExecute(candidate) and candidate.len <= buf.len) {
|
|
@memcpy(buf[0..candidate.len], candidate);
|
|
return buf[0..candidate.len];
|
|
}
|
|
}
|
|
return tc.filesystem.findProgramByName(tc.driver.comp.gpa, name, tc.driver.comp.environment.path, buf) orelse continue;
|
|
}
|
|
@memcpy(buf[0..name.len], name);
|
|
return buf[0..name.len];
|
|
}
|
|
|
|
pub fn getSysroot(tc: *const Toolchain) []const u8 {
|
|
return tc.driver.sysroot orelse system_defaults.sysroot;
|
|
}
|
|
|
|
/// Search for `name` in a variety of places
|
|
/// TODO: cache results based on `name` so we're not repeatedly allocating the same strings?
|
|
pub fn getFilePath(tc: *const Toolchain, name: []const u8) ![]const u8 {
|
|
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
|
|
const allocator = fib.allocator();
|
|
const arena = tc.driver.comp.arena;
|
|
|
|
const sysroot = tc.getSysroot();
|
|
|
|
// todo check resource dir
|
|
// todo check compiler RT path
|
|
const aro_dir = std.fs.path.dirname(tc.driver.aro_name) orelse "";
|
|
const candidate = try std.fs.path.join(allocator, &.{ aro_dir, "..", name });
|
|
if (tc.filesystem.exists(candidate)) {
|
|
return arena.dupe(u8, candidate);
|
|
}
|
|
|
|
if (tc.searchPaths(&fib, sysroot, tc.library_paths.items, name)) |path| {
|
|
return arena.dupe(u8, path);
|
|
}
|
|
|
|
if (tc.searchPaths(&fib, sysroot, tc.file_paths.items, name)) |path| {
|
|
return try arena.dupe(u8, path);
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/// Search a list of `path_prefixes` for the existence `name`
|
|
/// Assumes that `fba` is a fixed-buffer allocator, so does not free joined path candidates
|
|
fn searchPaths(tc: *const Toolchain, fib: *std.heap.FixedBufferAllocator, sysroot: []const u8, path_prefixes: []const []const u8, name: []const u8) ?[]const u8 {
|
|
for (path_prefixes) |path| {
|
|
fib.reset();
|
|
if (path.len == 0) continue;
|
|
|
|
const candidate = if (path[0] == '=')
|
|
std.fs.path.join(fib.allocator(), &.{ sysroot, path[1..], name }) catch continue
|
|
else
|
|
std.fs.path.join(fib.allocator(), &.{ path, name }) catch continue;
|
|
|
|
if (tc.filesystem.exists(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const PathKind = enum {
|
|
library,
|
|
file,
|
|
program,
|
|
};
|
|
|
|
/// Join `components` into a path. If the path exists, dupe it into the Compilation arena and
|
|
/// add it to the specified path list.
|
|
pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void {
|
|
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
|
|
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
|
|
|
|
const candidate = try std.fs.path.join(fib.allocator(), components);
|
|
|
|
if (tc.filesystem.exists(candidate)) {
|
|
const duped = try tc.driver.comp.arena.dupe(u8, candidate);
|
|
const dest = switch (dest_kind) {
|
|
.library => &tc.library_paths,
|
|
.file => &tc.file_paths,
|
|
.program => &tc.program_paths,
|
|
};
|
|
try dest.append(tc.driver.comp.gpa, duped);
|
|
}
|
|
}
|
|
|
|
/// Join `components` using the Compilation arena and add the resulting path to `dest_kind`. Does not check
|
|
/// whether the path actually exists
|
|
pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void {
|
|
const full_path = try std.fs.path.join(tc.driver.comp.arena, components);
|
|
const dest = switch (dest_kind) {
|
|
.library => &tc.library_paths,
|
|
.file => &tc.file_paths,
|
|
.program => &tc.program_paths,
|
|
};
|
|
try dest.append(tc.driver.comp.gpa, full_path);
|
|
}
|
|
|
|
fn getDefaultRuntimeLibKind(tc: *const Toolchain) RuntimeLibKind {
|
|
if (tc.getTarget().abi.isAndroid()) {
|
|
return .compiler_rt;
|
|
}
|
|
return .libgcc;
|
|
}
|
|
|
|
pub fn getRuntimeLibKind(tc: *const Toolchain) RuntimeLibKind {
|
|
const libname = tc.driver.rtlib orelse system_defaults.rtlib;
|
|
if (mem.eql(u8, libname, "compiler-rt"))
|
|
return .compiler_rt
|
|
else if (mem.eql(u8, libname, "libgcc"))
|
|
return .libgcc
|
|
else
|
|
return tc.getDefaultRuntimeLibKind();
|
|
}
|
|
|
|
/// TODO
|
|
pub fn getCompilerRt(tc: *const Toolchain, component: []const u8, file_kind: FileKind) ![]const u8 {
|
|
_ = file_kind;
|
|
_ = component;
|
|
_ = tc;
|
|
return "";
|
|
}
|
|
|
|
fn getLibGCCKind(tc: *const Toolchain) LibGCCKind {
|
|
const target = tc.getTarget();
|
|
if (tc.driver.static_libgcc or tc.driver.static or tc.driver.static_pie or target.abi.isAndroid()) {
|
|
return .static;
|
|
}
|
|
if (tc.driver.shared_libgcc) {
|
|
return .shared;
|
|
}
|
|
return .unspecified;
|
|
}
|
|
|
|
fn getUnwindLibKind(tc: *const Toolchain) !UnwindLibKind {
|
|
const libname = tc.driver.unwindlib orelse system_defaults.unwindlib;
|
|
if (libname.len == 0 or mem.eql(u8, libname, "platform")) {
|
|
switch (tc.getRuntimeLibKind()) {
|
|
.compiler_rt => {
|
|
const target = tc.getTarget();
|
|
if (target.abi.isAndroid() or target.os.tag == .aix) {
|
|
return .compiler_rt;
|
|
} else {
|
|
return .none;
|
|
}
|
|
},
|
|
.libgcc => return .libgcc,
|
|
}
|
|
} else if (mem.eql(u8, libname, "none")) {
|
|
return .none;
|
|
} else if (mem.eql(u8, libname, "libgcc")) {
|
|
return .libgcc;
|
|
} else if (mem.eql(u8, libname, "libunwind")) {
|
|
if (tc.getRuntimeLibKind() == .libgcc) {
|
|
try tc.driver.err("--rtlib=libgcc requires --unwindlib=libgcc", .{});
|
|
}
|
|
return .compiler_rt;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
|
|
fn getAsNeededOption(is_solaris: bool, needed: bool) []const u8 {
|
|
if (is_solaris) {
|
|
return if (needed) "-zignore" else "-zrecord";
|
|
} else {
|
|
return if (needed) "--as-needed" else "--no-as-needed";
|
|
}
|
|
}
|
|
|
|
fn addUnwindLibrary(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
|
|
const unw = try tc.getUnwindLibKind();
|
|
const target = tc.getTarget();
|
|
if ((target.abi.isAndroid() and unw == .libgcc) or
|
|
target.ofmt == .wasm or
|
|
target_util.isWindowsMSVCEnvironment(target) or
|
|
unw == .none) return;
|
|
|
|
const lgk = tc.getLibGCCKind();
|
|
const as_needed = lgk == .unspecified and !target.abi.isAndroid() and !target_util.isCygwinMinGW(target) and target.os.tag != .aix;
|
|
|
|
try argv.ensureUnusedCapacity(tc.driver.comp.gpa, 3);
|
|
if (as_needed) {
|
|
argv.appendAssumeCapacity(getAsNeededOption(target.os.tag == .solaris, true));
|
|
}
|
|
switch (unw) {
|
|
.none => return,
|
|
.libgcc => argv.appendAssumeCapacity(if (lgk == .static) "-lgcc_eh" else "-lgcc_s"),
|
|
.compiler_rt => if (target.os.tag == .aix) {
|
|
if (lgk != .static) {
|
|
argv.appendAssumeCapacity("-lunwind");
|
|
}
|
|
} else if (lgk == .static) {
|
|
argv.appendAssumeCapacity("-l:libunwind.a");
|
|
} else if (lgk == .shared) {
|
|
if (target_util.isCygwinMinGW(target)) {
|
|
argv.appendAssumeCapacity("-l:libunwind.dll.a");
|
|
} else {
|
|
argv.appendAssumeCapacity("-l:libunwind.so");
|
|
}
|
|
} else {
|
|
argv.appendAssumeCapacity("-lunwind");
|
|
},
|
|
}
|
|
|
|
if (as_needed) {
|
|
argv.appendAssumeCapacity(getAsNeededOption(target.os.tag == .solaris, false));
|
|
}
|
|
}
|
|
|
|
fn addLibGCC(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
|
|
const gpa = tc.driver.comp.gpa;
|
|
const libgcc_kind = tc.getLibGCCKind();
|
|
if (libgcc_kind == .static or libgcc_kind == .unspecified) {
|
|
try argv.append(gpa, "-lgcc");
|
|
}
|
|
try tc.addUnwindLibrary(argv);
|
|
if (libgcc_kind == .shared) {
|
|
try argv.append(gpa, "-lgcc");
|
|
}
|
|
}
|
|
|
|
pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
|
|
const target = tc.getTarget();
|
|
const rlt = tc.getRuntimeLibKind();
|
|
switch (rlt) {
|
|
.compiler_rt => {
|
|
// TODO
|
|
},
|
|
.libgcc => {
|
|
if (target_util.isKnownWindowsMSVCEnvironment(target)) {
|
|
const rtlib_str = tc.driver.rtlib orelse system_defaults.rtlib;
|
|
if (!mem.eql(u8, rtlib_str, "platform")) {
|
|
try tc.driver.err("unsupported runtime library 'libgcc' for platform 'MSVC'", .{});
|
|
}
|
|
} else {
|
|
try tc.addLibGCC(argv);
|
|
}
|
|
},
|
|
}
|
|
|
|
if (target.abi.isAndroid() and !tc.driver.static and !tc.driver.static_pie) {
|
|
try argv.append(tc.driver.comp.gpa, "-ldl");
|
|
}
|
|
}
|
|
|
|
pub fn defineSystemIncludes(tc: *Toolchain) !void {
|
|
return switch (tc.inner) {
|
|
.uninitialized => unreachable,
|
|
.unknown => {
|
|
if (tc.driver.nostdinc) return;
|
|
|
|
const comp = tc.driver.comp;
|
|
if (!tc.driver.nobuiltininc) {
|
|
try comp.addBuiltinIncludeDir(tc.driver.aro_name, tc.driver.resource_dir);
|
|
}
|
|
|
|
if (!tc.driver.nostdlibinc) {
|
|
try comp.addSystemIncludeDir("/usr/include");
|
|
}
|
|
},
|
|
};
|
|
}
|