Zig Cross-Compilation Deep
Deep cross-compilation is building a program for a different machine and system than the one running the build. The target query names that machine and system exactly.
Build for Windows
This build file names a Windows target with a target query. The plain build command makes the program file without running it.
const std = @import("std");
pub fn build(b: *std.Build) void {
const query: std.Target.Query = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu };
const optimize = b.standardOptimizeOption(.{});
const app_target = b.resolveTargetQuery(query);
const app = b.addExecutable(.{ .name = "app", .root_source_file = b.path("src/main.zig"), .target = app_target, .optimize = optimize });
b.installArtifact(app);
}
Build for Arm Linux
This build file names an Arm Linux target with a target query. The same plain build command makes the program file without running it.
const std = @import("std");
pub fn build(b: *std.Build) void {
const query: std.Target.Query = .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu };
const optimize = b.standardOptimizeOption(.{});
const app_target = b.resolveTargetQuery(query);
const app = b.addExecutable(.{ .name = "app", .root_source_file = b.path("src/main.zig"), .target = app_target, .optimize = optimize });
b.installArtifact(app);
}
Build for two targets
One build file can hold two target queries at once. Each query gets its own program name, so both files are made by one build command.
const std = @import("std");
pub fn build(b: *std.Build) void {
const win_query: std.Target.Query = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu };
const arm_query: std.Target.Query = .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu };
const optimize = b.standardOptimizeOption(.{});
const app_win_target = b.resolveTargetQuery(win_query);
const app_win = b.addExecutable(.{ .name = "app_win", .root_source_file = b.path("src/main.zig"), .target = app_win_target, .optimize = optimize });
b.installArtifact(app_win);
const app_arm_target = b.resolveTargetQuery(arm_query);
const app_arm = b.addExecutable(.{ .name = "app_arm", .root_source_file = b.path("src/main.zig"), .target = app_arm_target, .optimize = optimize });
b.installArtifact(app_arm);
}
Example
const std = @import("std");
pub fn build(b: *std.Build) void {
const query: std.Target.Query = .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu };
const optimize = b.standardOptimizeOption(.{});
const app_target = b.resolveTargetQuery(query);
const app = b.addExecutable(.{ .name = "app", .root_source_file = b.path("src/main.zig"), .target = app_target, .optimize = optimize });
b.installArtifact(app);
}
Key points
- Target query: a target query is a value that names the CPU, the operating system, and the binary interface for a build.
- CPU architecture: a CPU architecture is the processor family a program is built for.
- Operating system tag: an operating system tag is the name of the system a program is built for.
- ABI: an ABI is the low-level calling and linking agreement for a system.
- Foreign program: a foreign program is a program built for a different machine than the build machine.
- Build-only step: a build-only step is a build command that makes files without running them.
The target query names a Windows machine, a Windows system, and the gnu binary interface. The plain build command compiles the program for that target and installs it under zig-out. The foreign program is not run here, because running it needs a Windows machine.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does a target query name?