🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Cross-Compilation

Cross-compilation is building a program for a target different from the machine doing the build. This lesson builds the same program for Windows and for Arm Linux.

Building for Windows

One flag builds the program for Windows, and the result lands in zig-out/bin as hello.exe.

const std = @import("std");
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Building for Arm Linux

The same script with a different triple builds for Arm Linux instead.

const std = @import("std");
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Target plus size setting

A target flag combines with the usual size and speed setting in one command.

const std = @import("std");
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Example

const std = @import("std");
pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "hello",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });
    b.installArtifact(exe);
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

Key points

  • Target: a target is the machine and system a program is built for.
  • Target triple: a target triple is a string naming the processor, the system, and the binary interface, like x86_64-windows-gnu.
  • Cross-compilation: cross-compilation is building a program for a target different from the machine doing the build.
  • standardTargetOptions reads the -Dtarget flag, so one build script serves every target.
  • A foreign binary is not run on the build machine, so only the build itself is checked here.
  • The built file lands in zig-out/bin, with a .exe ending for Windows targets.

The build script is the same one used for a native build. The -Dtarget flag points it at Arm Linux instead of the build machine. The build checks out and the binary lands in zig-out/bin. Nothing here runs the foreign binary, since it belongs to another machine.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which command builds for Windows from this machine?