Skip to content

CLI: explicit integration with cgo #7377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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!
Comment on lines +154 to +156
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://go-review.googlesource.com/c/go/+/276412/ which is under review may fix 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an ok default behaviour, but this should be configurable via a flag or env var.

} 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;
Comment on lines +386 to +387
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (std.mem.eql(u8, go_arch, "386")) {
cpu_arch = null;
if (std.mem.eql(u8, go_arch, "native")) {
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,
};
}