Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
136 changes: 114 additions & 22 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,45 +1,58 @@
const std = @import("std");

pub fn addExecutable(b: *std.Build, options: BuildStep.Options) *BuildStep {
return BuildStep.create(b, options);
pub fn addExecutable(b: *std.Build, options: GoBuildStep.Options) *GoBuildStep {
return GoBuildStep.create(b, options);
}

/// Convenience method to create a library.
pub fn addLibrary(b: *std.Build, options: GoBuildStep.Options) *GoBuildStep {
var library_options = options;
library_options.make_library = true;
// A library build requires CGO.
library_options.cgo_enabled = true;
return GoBuildStep.create(b, library_options);
}

pub fn build(b: *std.Build) void {
_ = b;
}

/// Runs `go build` with relevant flags
pub const BuildStep = struct {
pub const GoBuildStep = struct {
step: std.Build.Step,
generated_bin: ?*std.Build.GeneratedFile,
opts: Options,

pub const Options = struct {
// The name of generated target.
name: []const u8,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
package_path: std.Build.LazyPath,
// Why is this true by default? Should be false by default.
cgo_enabled: bool = true,
make_library: bool = false,
// TODO(rjk): Add Mac target sysroot support.
};

/// Create a GoBuildStep
pub fn create(b: *std.Build, options: Options) *BuildStep {
const self = b.allocator.create(BuildStep) catch unreachable;
pub fn create(b: *std.Build, options: Options) *GoBuildStep {
const self = b.allocator.create(GoBuildStep) catch unreachable;
self.* = .{
.opts = options,
.generated_bin = null,
.step = std.Build.Step.init(.{
.id = .custom,
.name = "go build",
.owner = b,
.makeFn = BuildStep.make,
.makeFn = GoBuildStep.make,
}),
};
return self;
}

pub fn make(step: *std.Build.Step, progress: std.Progress.Node) !void {
const self: *BuildStep = @fieldParentPtr("step", step);
pub fn make(step: *std.Build.Step, mkoptions: std.Build.Step.MakeOptions) !void {
const self: *GoBuildStep = @fieldParentPtr("step", step);
const b = step.owner;
var go_args = std.ArrayList([]const u8).init(b.allocator);
defer go_args.deinit();
Expand All @@ -59,37 +72,54 @@ pub const BuildStep = struct {

var env = try std.process.getEnvMap(b.allocator);

// GOOS and GOARCH fields are not the same as triple fields on some
// platforms. Adjust them appropriately.
const target = self.opts.target;
const goarch = try isa_to_goarch(@tagName(target.result.cpu.arch));
try env.put("GOARCH", goarch);
const goos = try ostag_to_goos(@tagName(target.result.os.tag));
try env.put("GOOS", goos);

// Cross compilling Go to MacOS requires providing a sysroot. The default
// sysroot exists when the target is macos, a MacOS SDK is installed and
// the Zig target is native. Otherwise, a sysroot *might* be present in
// some fashion. Add support for specifying the system root.

// CGO
if (self.opts.cgo_enabled) {
try env.put("CGO_ENABLED", "1");
// Set zig as the CGO compiler
const target = self.opts.target;
const ts = try self.mktargetstring(b);
const cc = b.fmt(
"zig cc -target {s}-{s}-{s}",
.{ @tagName(target.result.cpu.arch), @tagName(target.result.os.tag), @tagName(target.result.abi) },
"zig cc -target {s}",
.{ts},
);
try env.put("CC", cc);
const cxx = b.fmt(
"zig c++ -target {s}-{s}-{s}",
.{ @tagName(target.result.cpu.arch), @tagName(target.result.os.tag), @tagName(target.result.abi) },
"zig c++ -target {s}",
.{ts},
);
try env.put("CXX", cxx);
try env.put("GOOS", @tagName(target.result.os.tag));

// Tell the linker we are statically linking
go_args.appendSlice(&.{ "--ldflags", "-linkmode=external -extldflags=-static" }) catch @panic("OOM");
} else {
try env.put("CGO_ENABLED", "0");
}

// Output file always needs to be added last
if (self.opts.make_library) {
// Where does the .h file go? (next to it)
try go_args.appendSlice(&.{"--buildmode=c-archive"});
}

// Package path always needs to be added last
try go_args.append(self.opts.package_path.getPath(b));

const cmd = std.mem.join(b.allocator, " ", go_args.items) catch @panic("OOM");
const node = progress.start(cmd, 1);
const node = mkoptions.progress_node.start(cmd, 1);
defer node.end();

// run the command
// Run the command
try self.evalChildProcess(go_args.items, &env);

if (self.generated_bin == null) {
Expand All @@ -100,8 +130,13 @@ pub const BuildStep = struct {
self.generated_bin.?.path = output_file;
}

/// Return the LazyPath of the directory containing the Go-generated include file.
pub fn getIncludePath(self: *GoBuildStep) std.Build.LazyPath {
return self.getEmittedBin().dirname();
}

/// Return the LazyPath of the generated binary
pub fn getEmittedBin(self: *BuildStep) std.Build.LazyPath {
pub fn getEmittedBin(self: *GoBuildStep) std.Build.LazyPath {
if (self.generated_bin) |generated_bin|
return .{ .generated = .{ .file = generated_bin } };

Expand All @@ -113,7 +148,7 @@ pub const BuildStep = struct {
}

/// Add a run step which depends on the GoBuildStep
pub fn addRunStep(self: *BuildStep) *std.Build.Step.Run {
pub fn addRunStep(self: *GoBuildStep) *std.Build.Step.Run {
const b = self.step.owner;
const run_step = std.Build.Step.Run.create(b, b.fmt("run {s}", .{self.opts.name}));
run_step.step.dependOn(&self.step);
Expand All @@ -123,16 +158,16 @@ pub const BuildStep = struct {
return run_step;
}

// Add an install step which depends on the GoBuildStep
pub fn addInstallStep(self: *BuildStep) void {
/// Add an install step which depends on the GoBuildStep
pub fn addInstallStep(self: *GoBuildStep) void {
const b = self.step.owner;
const bin_file = self.getEmittedBin();
const install_step = b.addInstallBinFile(bin_file, self.opts.name);
install_step.step.dependOn(&self.step);
b.getInstallStep().dependOn(&install_step.step);
}

fn evalChildProcess(self: *BuildStep, argv: []const []const u8, env: *const std.process.EnvMap) !void {
fn evalChildProcess(self: *GoBuildStep, argv: []const []const u8, env: *const std.process.EnvMap) !void {
const s = &self.step;
const arena = s.owner.allocator;

Expand All @@ -151,4 +186,61 @@ pub const BuildStep = struct {

try std.Build.Step.handleChildProcessTerm(s, result.term, null, argv);
}

/// Create a target that can build the Go code. Uses "native" on MacOS
/// so that it has a sysroot.
// TODO(rjk): Add some kind of completed sysroot support.
fn mktargetstring(self: *GoBuildStep, b: *std.Build) ![]const u8 {
const target = self.opts.target;
if (target.result.os.tag == .ios or target.result.os.tag == .macos) {
return "native";
} else {
return b.fmt(
"{s}-{s}-{s}",
.{
@tagName(target.result.cpu.arch),
@tagName(target.result.os.tag),
@tagName(target.result.abi),
},
);
}
return error.NOTIMPL_CUSTOM_MACOS_SYSROOT;
}
};

const GoEnvError = error{
UNSUPPORTED_GOARCH,
UNSUPPORTED_GOOS,
};

/// Convert a Zig arch triple member to the corresponding GOARCH value.
fn isa_to_goarch(isa: [:0]const u8) ![:0]const u8 {
if (std.mem.eql(u8, isa, "x86_64")) {
return "amd64";
} else if (std.mem.eql(u8, isa, "arm")) {
return "arm";
} else if (std.mem.eql(u8, isa, "aarch64")) {
return "arm64";
}
// TODO(rjk): Consider adding some less popular processors that might
// work.

return error.UNSUPPORTED_GOARCH;
}

/// Convert a Zig os triple member to the corresponding GOOS value.
fn ostag_to_goos(os: [:0]const u8) ![:0]const u8 {
if (std.mem.startsWith(u8, os, "macos")) {
return "darwin";
} else if (std.mem.startsWith(u8, os, "linux")) {
return "linux";
} else if (std.mem.startsWith(u8, os, "windows")) {
return "windows";
}
// TODO(rjk): Add less common OS. In particular, support Plan9.
return error.UNSUPPORTED_GOOS;
}

const MacEnvError = error{
NOTIMPL_CUSTOM_MACOS_SYSROOT,
};
10 changes: 8 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
.{
.name = "go",
.name = .go,
.version = "0.0.0",
.dependencies = .{},
.dependencies = .{
.go = .{
.url = "/Users/rjkroege/tools/_builds/zig4go",
.hash = "go-0.0.0-Pc-5hm6ztgQ9f0bk8a1X8qYupArIzWASlP6St1ZBWWnf",
},
},
.paths = .{""},
.fingerprint = 0xb668935686b9cf3d,
}