Skip to content

Commit

Permalink
CLI: explicit integration with cgo
Browse files Browse the repository at this point in the history
This is just a proof-of-concept. Maybe it could be nice for Go
developers, but also maybe we don't put additional complexity into the
zig codebase when CLI flags work just fine.
  • Loading branch information
andrewrk committed Dec 10, 2020
1 parent cb896a6 commit cb26f2e
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 44 deletions.
157 changes: 113 additions & 44 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,6 @@ pub fn main() anyerror!void {
const os_can_execve = std.builtin.os.tag != .windows;

pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void {
if (args.len <= 1) {
std.log.info("{}", .{usage});
fatal("expected command argument", .{});
}

if (os_can_execve and std.os.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) {
// In this case we have accidentally invoked ourselves as "the system C compiler"
// to figure out where libc is installed. This is essentially infinite recursion
Expand Down Expand Up @@ -154,6 +149,51 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
}
}

const is_cgo = std.os.getenvZ("ZIG_CGO") != null;
if (is_cgo) cgo: {
// Workaround for https://github.com/golang/go/issues/43078, here we fix the order of
// command line arguments. Dear Go developers please let the Zig project know when
// we can remove this!
var fixed_args = std.ArrayList([]const u8).init(arena);
try fixed_args.ensureCapacity(args.len);
fixed_args.appendAssumeCapacity(args[0]);
fixed_args.appendAssumeCapacity("cc");
var arg_mode: BuildArgMode = .cc;
var found_cc_arg = false;
for (args[1..]) |arg| {
if (!found_cc_arg) {
if (mem.eql(u8, arg, "cc")) {
arg_mode = .cc;
fixed_args.items[1] = arg;
found_cc_arg = true;
} else if (mem.eql(u8, arg, "c++")) {
arg_mode = .cpp;
fixed_args.items[1] = arg;
found_cc_arg = true;
} else if (mem.eql(u8, arg, "clang") or
mem.eql(u8, arg, "-cc1") or
mem.eql(u8, arg, "-cc1as") or
mem.eql(u8, arg, "ld.lld") or
mem.eql(u8, arg, "ld64.lld") or
mem.eql(u8, arg, "lld-link") or
mem.eql(u8, arg, "wasm-ld"))
{
break :cgo; // fall back to regular arg parsing
} else {
fixed_args.appendAssumeCapacity(arg);
}
} else {
fixed_args.appendAssumeCapacity(arg);
}
}
return buildOutputType(gpa, arena, fixed_args.items, arg_mode);
}

if (args.len <= 1) {
std.log.info("{}", .{usage});
fatal("expected command argument", .{});
}

const cmd = args[1];
const cmd_args = args[2..];
if (mem.eql(u8, cmd, "build-exe")) {
Expand Down Expand Up @@ -435,18 +475,20 @@ fn optionalStringEnvVar(arena: *Allocator, name: []const u8) !?[]const u8 {
}
}

const BuildArgMode = union(enum) {
build: std.builtin.OutputMode,
cc,
cpp,
translate_c,
zig_test,
run,
};

fn buildOutputType(
gpa: *Allocator,
arena: *Allocator,
all_args: []const []const u8,
arg_mode: union(enum) {
build: std.builtin.OutputMode,
cc,
cpp,
translate_c,
zig_test,
run,
},
arg_mode: BuildArgMode,
) !void {
var color: Color = .auto;
var optimize_mode: std.builtin.Mode = .Debug;
Expand Down Expand Up @@ -477,7 +519,7 @@ fn buildOutputType(
var emit_zir: Emit = .no;
var emit_docs: Emit = .no;
var emit_analysis: Emit = .no;
var target_arch_os_abi: []const u8 = "native";
var target_arch_os_abi: ?[]const u8 = null;
var target_mcpu: ?[]const u8 = null;
var target_dynamic_linker: ?[]const u8 = null;
var target_ofmt: ?[]const u8 = null;
Expand Down Expand Up @@ -1361,38 +1403,65 @@ fn buildOutputType(
}
};

var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
const cross_target = std.zig.CrossTarget.parse(.{
.arch_os_abi = target_arch_os_abi,
.cpu_features = target_mcpu,
.dynamic_linker = target_dynamic_linker,
.diagnostics = &diags,
}) catch |err| switch (err) {
error.UnknownCpuModel => {
help: {
var help_text = std.ArrayList(u8).init(arena);
for (diags.arch.?.allCpuModels()) |cpu| {
help_text.writer().print(" {}\n", .{cpu.name}) catch break :help;
}
std.log.info("Available CPUs for architecture '{}': {}", .{
@tagName(diags.arch.?), help_text.items,
const is_cgo = std.os.getenvZ("ZIG_CGO") != null;

const cross_target: std.zig.CrossTarget = t: {
if (is_cgo and target_arch_os_abi == null) {
const go_os = std.os.getenvZ("GOOS") orelse "native";
const go_arch = std.os.getenvZ("GOARCH") orelse "native";
const cgo_ct = target_util.fromGoTarget(go_arch, go_os) catch |err| {
fatal("unable to determine target from GOOS={s} GOARCH={s}: {s}", .{
go_os, go_arch, @errorName(err),
});
};
// We render the CGO target to a string so that we can call CrossTarget.parse below
// and which takes into account the CPU features and dynamic linker CLI parameters.
const cpu_arch = if (cgo_ct.cpu_arch) |arch| @tagName(arch) else "native";
const os_tag = if (cgo_ct.os_tag) |os_tag| @tagName(os_tag) else "native";
if (cgo_ct.abi) |abi| {
target_arch_os_abi = try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ cpu_arch, os_tag, abi });
} else {
target_arch_os_abi = try std.fmt.allocPrint(arena, "{s}-{s}", .{ cpu_arch, os_tag });
}
fatal("Unknown CPU: '{}'", .{diags.cpu_name.?});
},
error.UnknownCpuFeature => {
help: {
var help_text = std.ArrayList(u8).init(arena);
for (diags.arch.?.allFeaturesList()) |feature| {
help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help;
}

var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
const cross_target = std.zig.CrossTarget.parse(.{
.arch_os_abi = target_arch_os_abi orelse "native",
.cpu_features = target_mcpu,
.dynamic_linker = target_dynamic_linker,
.diagnostics = &diags,
}) catch |err| switch (err) {
error.UnknownCpuModel => {
help: {
var help_text = std.ArrayList(u8).init(arena);
for (diags.arch.?.allCpuModels()) |cpu| {
help_text.writer().print(" {}\n", .{cpu.name}) catch break :help;
}
std.log.info("Available CPUs for architecture '{}': {}", .{
@tagName(diags.arch.?), help_text.items,
});
}
std.log.info("Available CPU features for architecture '{}': {}", .{
@tagName(diags.arch.?), help_text.items,
});
}
fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name});
},
else => |e| return e,
fatal("Unknown CPU: '{}'", .{diags.cpu_name.?});
},
error.UnknownCpuFeature => {
help: {
var help_text = std.ArrayList(u8).init(arena);
for (diags.arch.?.allFeaturesList()) |feature| {
help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help;
}
std.log.info("Available CPU features for architecture '{}': {}", .{
@tagName(diags.arch.?), help_text.items,
});
}
fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name});
},
error.UnknownOperatingSystem => {
fatal("Unknown Operating System: '{s}'", .{diags.os_name});
},
else => |e| return e,
};
break :t cross_target;
};

const target_info = try detectNativeTargetInfo(gpa, cross_target);
Expand Down Expand Up @@ -1653,7 +1722,7 @@ fn buildOutputType(
.path = local_cache_dir_path,
};
}
if (arg_mode == .run) {
if (arg_mode == .run or is_cgo) {
break :l global_cache_directory;
}
const cache_dir_path = blk: {
Expand Down
76 changes: 76 additions & 0 deletions src/target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -343,3 +343,79 @@ pub fn is_libcpp_lib_name(target: std.Target, name: []const u8) bool {
pub fn hasDebugInfo(target: std.Target) bool {
return !target.cpu.arch.isWasm();
}

pub fn fromGoTarget(go_arch: []const u8, go_os: []const u8) !std.zig.CrossTarget {
var os_tag: ?std.Target.Os.Tag = undefined;
var abi: ?std.Target.Abi = null;
if (std.mem.eql(u8, go_os, "native")) {
os_tag = null;
} else if (std.mem.eql(u8, go_os, "aix")) {
os_tag = .aix;
} else if (std.mem.eql(u8, go_os, "android")) {
os_tag = .linux;
abi = .android;
} else if (std.mem.eql(u8, go_os, "darwin")) {
os_tag = .macos;
} else if (std.mem.eql(u8, go_os, "dragonfly")) {
os_tag = .dragonfly;
} else if (std.mem.eql(u8, go_os, "freebsd")) {
os_tag = .freebsd;
} else if (std.mem.eql(u8, go_os, "illumos")) {
return error.UnsupportedOperatingSystem;
} else if (std.mem.eql(u8, go_os, "js")) {
return error.UnsupportedOperatingSystem;
} else if (std.mem.eql(u8, go_os, "linux")) {
os_tag = .linux;
abi = .musl; // go wants static linking on linux
} else if (std.mem.eql(u8, go_os, "netbsd")) {
os_tag = .netbsd;
} else if (std.mem.eql(u8, go_os, "openbsd")) {
os_tag = .openbsd;
} else if (std.mem.eql(u8, go_os, "plan9")) {
return error.UnsupportedOperatingSystem;
} else if (std.mem.eql(u8, go_os, "solaris")) {
os_tag = .solaris;
} else if (std.mem.eql(u8, go_os, "windows")) {
os_tag = .windows;
abi = .gnu; // so we can provide libc
} else {
return error.UnrecognizedOperatingSystem;
}

var cpu_arch: ?std.Target.Cpu.Arch = undefined;
if (std.mem.eql(u8, go_arch, "386")) {
cpu_arch = null;
} else if (std.mem.eql(u8, go_arch, "386")) {
cpu_arch = .i386;
} else if (std.mem.eql(u8, go_arch, "amd64")) {
cpu_arch = .x86_64;
} else if (std.mem.eql(u8, go_arch, "arm")) {
cpu_arch = .arm;
} else if (std.mem.eql(u8, go_arch, "arm64")) {
cpu_arch = .aarch64;
} else if (std.mem.eql(u8, go_arch, "mips")) {
cpu_arch = .mips;
} else if (std.mem.eql(u8, go_arch, "mips64")) {
cpu_arch = .mips64;
} else if (std.mem.eql(u8, go_arch, "mips64le")) {
cpu_arch = .mips64el;
} else if (std.mem.eql(u8, go_arch, "mipsle")) {
cpu_arch = .mipsel;
} else if (std.mem.eql(u8, go_arch, "ppc64")) {
cpu_arch = .powerpc64;
} else if (std.mem.eql(u8, go_arch, "ppc64le")) {
cpu_arch = .powerpc64le;
} else if (std.mem.eql(u8, go_arch, "riscv64")) {
cpu_arch = .riscv64;
} else if (std.mem.eql(u8, go_arch, "s390x")) {
cpu_arch = .s390x;
} else {
return error.UnrecognizedCPUArchitecture;
}

return std.zig.CrossTarget{
.cpu_arch = cpu_arch,
.os_tag = os_tag,
.abi = abi,
};
}

0 comments on commit cb26f2e

Please sign in to comment.