Skip to content

Commit abcdfce

Browse files
committed
CLI: explicit integration with cgo
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.
1 parent cb896a6 commit abcdfce

File tree

2 files changed

+187
-44
lines changed

2 files changed

+187
-44
lines changed

src/main.zig

Lines changed: 113 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,6 @@ pub fn main() anyerror!void {
118118
const os_can_execve = std.builtin.os.tag != .windows;
119119

120120
pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !void {
121-
if (args.len <= 1) {
122-
std.log.info("{}", .{usage});
123-
fatal("expected command argument", .{});
124-
}
125-
126121
if (os_can_execve and std.os.getenvZ("ZIG_IS_DETECTING_LIBC_PATHS") != null) {
127122
// In this case we have accidentally invoked ourselves as "the system C compiler"
128123
// to figure out where libc is installed. This is essentially infinite recursion
@@ -154,6 +149,51 @@ pub fn mainArgs(gpa: *Allocator, arena: *Allocator, args: []const []const u8) !v
154149
}
155150
}
156151

152+
const is_cgo = std.os.getenvZ("ZIG_CGO") != null;
153+
if (is_cgo) cgo: {
154+
// Workaround for https://github.com/golang/go/issues/43078, here we fix the order of
155+
// command line arguments. Dear Go developers please let the Zig project know when
156+
// we can remove this!
157+
var fixed_args = std.ArrayList([]const u8).init(arena);
158+
try fixed_args.ensureCapacity(args.len);
159+
fixed_args.appendAssumeCapacity(args[0]);
160+
fixed_args.appendAssumeCapacity("cc");
161+
var arg_mode: BuildArgMode = .cc;
162+
var found_cc_arg = false;
163+
for (args[1..]) |arg| {
164+
if (!found_cc_arg) {
165+
if (mem.eql(u8, arg, "cc")) {
166+
arg_mode = .cc;
167+
fixed_args.items[1] = arg;
168+
found_cc_arg = true;
169+
} else if (mem.eql(u8, arg, "c++")) {
170+
arg_mode = .cpp;
171+
fixed_args.items[1] = arg;
172+
found_cc_arg = true;
173+
} else if (mem.eql(u8, arg, "clang") or
174+
mem.eql(u8, arg, "-cc1") or
175+
mem.eql(u8, arg, "-cc1as") or
176+
mem.eql(u8, arg, "ld.lld") or
177+
mem.eql(u8, arg, "ld64.lld") or
178+
mem.eql(u8, arg, "lld-link") or
179+
mem.eql(u8, arg, "wasm-ld"))
180+
{
181+
break :cgo; // fall back to regular arg parsing
182+
} else {
183+
fixed_args.appendAssumeCapacity(arg);
184+
}
185+
} else {
186+
fixed_args.appendAssumeCapacity(arg);
187+
}
188+
}
189+
return buildOutputType(gpa, arena, fixed_args.items, arg_mode);
190+
}
191+
192+
if (args.len <= 1) {
193+
std.log.info("{}", .{usage});
194+
fatal("expected command argument", .{});
195+
}
196+
157197
const cmd = args[1];
158198
const cmd_args = args[2..];
159199
if (mem.eql(u8, cmd, "build-exe")) {
@@ -435,18 +475,20 @@ fn optionalStringEnvVar(arena: *Allocator, name: []const u8) !?[]const u8 {
435475
}
436476
}
437477

478+
const BuildArgMode = union(enum) {
479+
build: std.builtin.OutputMode,
480+
cc,
481+
cpp,
482+
translate_c,
483+
zig_test,
484+
run,
485+
};
486+
438487
fn buildOutputType(
439488
gpa: *Allocator,
440489
arena: *Allocator,
441490
all_args: []const []const u8,
442-
arg_mode: union(enum) {
443-
build: std.builtin.OutputMode,
444-
cc,
445-
cpp,
446-
translate_c,
447-
zig_test,
448-
run,
449-
},
491+
arg_mode: BuildArgMode,
450492
) !void {
451493
var color: Color = .auto;
452494
var optimize_mode: std.builtin.Mode = .Debug;
@@ -477,7 +519,7 @@ fn buildOutputType(
477519
var emit_zir: Emit = .no;
478520
var emit_docs: Emit = .no;
479521
var emit_analysis: Emit = .no;
480-
var target_arch_os_abi: []const u8 = "native";
522+
var target_arch_os_abi: ?[]const u8 = null;
481523
var target_mcpu: ?[]const u8 = null;
482524
var target_dynamic_linker: ?[]const u8 = null;
483525
var target_ofmt: ?[]const u8 = null;
@@ -1361,38 +1403,65 @@ fn buildOutputType(
13611403
}
13621404
};
13631405

1364-
var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
1365-
const cross_target = std.zig.CrossTarget.parse(.{
1366-
.arch_os_abi = target_arch_os_abi,
1367-
.cpu_features = target_mcpu,
1368-
.dynamic_linker = target_dynamic_linker,
1369-
.diagnostics = &diags,
1370-
}) catch |err| switch (err) {
1371-
error.UnknownCpuModel => {
1372-
help: {
1373-
var help_text = std.ArrayList(u8).init(arena);
1374-
for (diags.arch.?.allCpuModels()) |cpu| {
1375-
help_text.writer().print(" {}\n", .{cpu.name}) catch break :help;
1376-
}
1377-
std.log.info("Available CPUs for architecture '{}': {}", .{
1378-
@tagName(diags.arch.?), help_text.items,
1406+
const is_cgo = std.os.getenvZ("ZIG_CGO") != null;
1407+
1408+
const cross_target: std.zig.CrossTarget = t: {
1409+
if (is_cgo and target_arch_os_abi == null) {
1410+
const go_os = std.os.getenvZ("GOOS") orelse "native";
1411+
const go_arch = std.os.getenvZ("GOARCH") orelse "native";
1412+
const cgo_ct = target_util.fromGoTarget(go_arch, go_os) catch |err| {
1413+
fatal("unable to determine target from GOOS={s} GOARCH={s}: {s}", .{
1414+
go_os, go_arch, @errorName(err),
13791415
});
1416+
};
1417+
// We render the CGO target to a string so that we can call CrossTarget.parse below
1418+
// and which takes into account the CPU features and dynamic linker CLI parameters.
1419+
const cpu_arch = if (cgo_ct.cpu_arch) |arch| @tagName(arch) else "native";
1420+
const os_tag = if (cgo_ct.os_tag) |os_tag| @tagName(os_tag) else "native";
1421+
if (cgo_ct.abi) |abi| {
1422+
target_arch_os_abi = try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ cpu_arch, os_tag, abi });
1423+
} else {
1424+
target_arch_os_abi = try std.fmt.allocPrint(arena, "{s}-{s}", .{ cpu_arch, os_tag });
13801425
}
1381-
fatal("Unknown CPU: '{}'", .{diags.cpu_name.?});
1382-
},
1383-
error.UnknownCpuFeature => {
1384-
help: {
1385-
var help_text = std.ArrayList(u8).init(arena);
1386-
for (diags.arch.?.allFeaturesList()) |feature| {
1387-
help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help;
1426+
}
1427+
1428+
var diags: std.zig.CrossTarget.ParseOptions.Diagnostics = .{};
1429+
const cross_target = std.zig.CrossTarget.parse(.{
1430+
.arch_os_abi = target_arch_os_abi orelse "native",
1431+
.cpu_features = target_mcpu,
1432+
.dynamic_linker = target_dynamic_linker,
1433+
.diagnostics = &diags,
1434+
}) catch |err| switch (err) {
1435+
error.UnknownCpuModel => {
1436+
help: {
1437+
var help_text = std.ArrayList(u8).init(arena);
1438+
for (diags.arch.?.allCpuModels()) |cpu| {
1439+
help_text.writer().print(" {}\n", .{cpu.name}) catch break :help;
1440+
}
1441+
std.log.info("Available CPUs for architecture '{}': {}", .{
1442+
@tagName(diags.arch.?), help_text.items,
1443+
});
13881444
}
1389-
std.log.info("Available CPU features for architecture '{}': {}", .{
1390-
@tagName(diags.arch.?), help_text.items,
1391-
});
1392-
}
1393-
fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name});
1394-
},
1395-
else => |e| return e,
1445+
fatal("Unknown CPU: '{}'", .{diags.cpu_name.?});
1446+
},
1447+
error.UnknownCpuFeature => {
1448+
help: {
1449+
var help_text = std.ArrayList(u8).init(arena);
1450+
for (diags.arch.?.allFeaturesList()) |feature| {
1451+
help_text.writer().print(" {}: {}\n", .{ feature.name, feature.description }) catch break :help;
1452+
}
1453+
std.log.info("Available CPU features for architecture '{}': {}", .{
1454+
@tagName(diags.arch.?), help_text.items,
1455+
});
1456+
}
1457+
fatal("Unknown CPU feature: '{}'", .{diags.unknown_feature_name});
1458+
},
1459+
error.UnknownOperatingSystem => {
1460+
fatal("Unknown Operating System: '{s}'", .{diags.os_name});
1461+
},
1462+
else => |e| return e,
1463+
};
1464+
break :t cross_target;
13961465
};
13971466

13981467
const target_info = try detectNativeTargetInfo(gpa, cross_target);
@@ -1653,7 +1722,7 @@ fn buildOutputType(
16531722
.path = local_cache_dir_path,
16541723
};
16551724
}
1656-
if (arg_mode == .run) {
1725+
if (arg_mode == .run or is_cgo) {
16571726
break :l global_cache_directory;
16581727
}
16591728
const cache_dir_path = blk: {

src/target.zig

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,77 @@ pub fn is_libcpp_lib_name(target: std.Target, name: []const u8) bool {
343343
pub fn hasDebugInfo(target: std.Target) bool {
344344
return !target.cpu.arch.isWasm();
345345
}
346+
347+
pub fn fromGoTarget(go_arch: []const u8, go_os: []const u8) !std.zig.CrossTarget {
348+
var os_tag: ?std.Target.Os.Tag = undefined;
349+
var abi: ?std.Target.Abi = null;
350+
if (std.mem.eql(u8, go_os, "native")) {
351+
os_tag = null;
352+
} else if (std.mem.eql(u8, go_os, "aix")) {
353+
os_tag = .aix;
354+
} else if (std.mem.eql(u8, go_os, "android")) {
355+
os_tag = .linux;
356+
abi = .android;
357+
} else if (std.mem.eql(u8, go_os, "darwin")) {
358+
os_tag = .macos;
359+
} else if (std.mem.eql(u8, go_os, "dragonfly")) {
360+
os_tag = .dragonfly;
361+
} else if (std.mem.eql(u8, go_os, "freebsd")) {
362+
os_tag = .freebsd;
363+
} else if (std.mem.eql(u8, go_os, "illumos")) {
364+
return error.UnsupportedOperatingSystem;
365+
} else if (std.mem.eql(u8, go_os, "js")) {
366+
return error.UnsupportedOperatingSystem;
367+
} else if (std.mem.eql(u8, go_os, "linux")) {
368+
os_tag = .linux;
369+
} else if (std.mem.eql(u8, go_os, "netbsd")) {
370+
os_tag = .netbsd;
371+
} else if (std.mem.eql(u8, go_os, "openbsd")) {
372+
os_tag = .openbsd;
373+
} else if (std.mem.eql(u8, go_os, "plan9")) {
374+
return error.UnsupportedOperatingSystem;
375+
} else if (std.mem.eql(u8, go_os, "solaris")) {
376+
os_tag = .solaris;
377+
} else if (std.mem.eql(u8, go_os, "windows")) {
378+
os_tag = .windows;
379+
} else {
380+
return error.UnrecognizedOperatingSystem;
381+
}
382+
383+
var cpu_arch: ?std.Target.Cpu.Arch = undefined;
384+
if (std.mem.eql(u8, go_arch, "386")) {
385+
cpu_arch = null;
386+
} else if (std.mem.eql(u8, go_arch, "386")) {
387+
cpu_arch = .i386;
388+
} else if (std.mem.eql(u8, go_arch, "amd64")) {
389+
cpu_arch = .x86_64;
390+
} else if (std.mem.eql(u8, go_arch, "arm")) {
391+
cpu_arch = .arm;
392+
} else if (std.mem.eql(u8, go_arch, "arm64")) {
393+
cpu_arch = .aarch64;
394+
} else if (std.mem.eql(u8, go_arch, "mips")) {
395+
cpu_arch = .mips;
396+
} else if (std.mem.eql(u8, go_arch, "mips64")) {
397+
cpu_arch = .mips64;
398+
} else if (std.mem.eql(u8, go_arch, "mips64le")) {
399+
cpu_arch = .mips64el;
400+
} else if (std.mem.eql(u8, go_arch, "mipsle")) {
401+
cpu_arch = .mipsel;
402+
} else if (std.mem.eql(u8, go_arch, "ppc64")) {
403+
cpu_arch = .powerpc64;
404+
} else if (std.mem.eql(u8, go_arch, "ppc64le")) {
405+
cpu_arch = .powerpc64le;
406+
} else if (std.mem.eql(u8, go_arch, "riscv64")) {
407+
cpu_arch = .riscv64;
408+
} else if (std.mem.eql(u8, go_arch, "s390x")) {
409+
cpu_arch = .s390x;
410+
} else {
411+
return error.UnrecognizedCPUArchitecture;
412+
}
413+
414+
return std.zig.CrossTarget{
415+
.cpu_arch = cpu_arch,
416+
.os_tag = os_tag,
417+
.abi = abi,
418+
};
419+
}

0 commit comments

Comments
 (0)