Skip to content

Instantly share code, notes, and snippets.

@mitchellh
Created November 7, 2022 14:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mitchellh/0d25e47ed2c4731f076b637904410b87 to your computer and use it in GitHub Desktop.
Save mitchellh/0d25e47ed2c4731f076b637904410b87 to your computer and use it in GitHub Desktop.
//! API for Zig build scripts to build Playdate artifacts.
//!
//! Thanks to:
//! - @MeTheFlea (GitHub) - this build script is based on it.
//! - @DanB91 (GitHub) - provided my initial understanding of how to build
//! for Playdate via Zig.
//!
const std = @import("std");
/// The primary Zig package to add to your project so you can import "playdate".
pub const pkg = std.build.Pkg{
.name = "playdate",
.source = .{ .path = thisDir() ++ "/main.zig" },
};
/// Options for all the exported build APIs.
pub const Options = struct {
/// The path to the Playdate SDK root.
playdate_sdk_path: []const u8,
/// The path to the ARM GNU toolchain root. This is usually installed
/// via a package named 'gcc-arm-embedded' or something like that.
arm_toolchain_path: []const u8,
/// The version is the version of the toolchain such as "11.2.1". This
/// path can be looked up by downloading the toolchain and seeing what
/// version exists in "lib/gcc/arm-none-eabi/<VERSION>".
arm_toolchain_version: []const u8,
/// Path to the libc configuration file. This normally doesn't need
/// to be changed but may need to be modified if you change the
/// ARM toolchain versions.
libc_txt_path: []const u8 = thisDir() ++ "/libc.txt",
};
const target = std.zig.CrossTarget{
.cpu_arch = .thumb,
.cpu_model = .{
.explicit = &std.Target.arm.cpu.cortex_m7,
},
.cpu_features_add = std.Target.arm.featureSet(&.{.v7em}),
.os_tag = .freestanding,
.abi = .eabihf,
};
const eabi_features = "v7e-m+fp/hard/";
/// Create a step to build the Playdate shared library object. This
/// should be provided as input to the createElf function to turn
/// this into a final elf binary for the Playdate.
pub fn createLib(
b: *std.build.Builder,
name: []const u8,
root_src: ?[]const u8,
opts: Options,
) *std.build.LibExeObjStep {
const lib = b.addSharedLibrary(name, root_src, .unversioned);
lib.setOutputDir("zig-out/lib");
setupStep(b, lib, opts);
return lib;
}
/// Create a step to build the executable ELF binary from your lib.
pub fn createElf(
b: *std.build.Builder,
lib: *std.build.LibExeObjStep,
opts: Options,
) *std.build.LibExeObjStep {
const game_elf = b.addExecutable("pdex.elf", null);
game_elf.addObjectFile(b.pathJoin(&.{
lib.output_dir.?,
b.fmt("{s}{s}", .{ lib.name, target.getObjectFormat().fileExt(target.cpu_arch.?) }),
}));
game_elf.step.dependOn(&lib.step);
const c_args = [_][]const u8{
"-DTARGET_PLAYDATE=1",
"-DTARGET_EXTENSION=1",
};
game_elf.addCSourceFile(
thisDir() ++ "/setup.c",
//re-enable when SDK fixes this
//b.pathJoin(&.{ opts.playdate_sdk_path, "/C_API/buildsupport/setup.c" }),
&c_args,
);
game_elf.want_lto = false; // otherwise release build does not work
setupStep(b, game_elf, opts);
return game_elf;
}
fn setupStep(
b: *std.build.Builder,
step: *std.build.LibExeObjStep,
opts: Options,
) void {
step.setLinkerScriptPath(.{ .path = b.pathJoin(&.{
opts.playdate_sdk_path,
"/C_API/buildsupport/link_map.ld",
}) });
step.addIncludePath(b.pathJoin(&.{
opts.arm_toolchain_path,
"/arm-none-eabi/include",
}));
step.addLibraryPath(b.pathJoin(&.{
opts.arm_toolchain_path,
"/lib/gcc/arm-none-eabi/",
opts.arm_toolchain_version,
"/thumb/",
eabi_features,
}));
step.addLibraryPath(b.pathJoin(&.{
opts.arm_toolchain_path,
"/arm-none-eabi/lib/thumb/",
eabi_features,
}));
step.addIncludePath(b.pathJoin(&.{ opts.playdate_sdk_path, "C_API" }));
step.setLibCFile(std.build.FileSource{ .path = opts.libc_txt_path });
step.setTarget(target);
if (b.is_release) {
step.omit_frame_pointer = true;
}
step.strip = true;
step.link_function_sections = true;
step.link_z_notext = true; // needed for @cos func
step.stack_size = 61800;
}
pub const Paths = struct {
assets_to_process: []const u8 = "assets",
assets_to_copy_raw: []const u8 = "assets-raw",
pdc_inputs_path: []const u8 = "pdc-input",
};
pub fn setupPDC(
b: *std.build.Builder,
game_name: []const u8,
game_elf: *std.build.LibExeObjStep,
lib: *std.build.LibExeObjStep,
opts: Options,
paths: Paths,
simulator_target_: ?std.zig.CrossTarget,
) ?*std.build.LibExeObjStep {
const pdc_input = b.pathJoin(&.{ b.install_path, paths.pdc_inputs_path });
const clear_pdc_inputs_path_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt("rm -rf \"{s}\"", .{pdc_input}),
});
clear_pdc_inputs_path_step.expected_exit_code = null;
const pdc_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt(
"{s}/bin/pdc -sdkpath {0s} --skip-unknown {1s} zig-out/{2s}.pdx",
.{ opts.playdate_sdk_path, pdc_input, game_name },
),
});
pdc_step.step.dependOn(&game_elf.step);
pdc_step.step.dependOn(&clear_pdc_inputs_path_step.step);
const playdate_copy_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt(
"mkdir -p {0s} && cp zig-out/lib/lib{1s}{2s} {0s}/pdex{2s} && {3s}/arm-none-eabi/bin/objcopy -O binary zig-out/bin/pdex.elf {0s}/pdex.bin",
.{ pdc_input, lib.name, target.dynamicLibSuffix(), opts.arm_toolchain_path },
),
});
playdate_copy_step.step.dependOn(&clear_pdc_inputs_path_step.step);
pdc_step.step.dependOn(&playdate_copy_step.step);
const copy_assets_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt("cp -r {0s}/* {1s}", .{ paths.assets_to_process, pdc_input }),
});
copy_assets_step.step.dependOn(&clear_pdc_inputs_path_step.step);
copy_assets_step.expected_exit_code = null;
pdc_step.step.dependOn(&copy_assets_step.step);
const copy_assets_raw_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt("cp -r {0s}/* zig-out/{1s}.pdx", .{ paths.assets_to_copy_raw, game_name }),
});
copy_assets_raw_step.step.dependOn(&clear_pdc_inputs_path_step.step);
copy_assets_raw_step.step.dependOn(&pdc_step.step);
copy_assets_raw_step.expected_exit_code = null;
b.getInstallStep().dependOn(&copy_assets_raw_step.step);
if (simulator_target_) |simulator_target| {
const simulator_lib = b.addSharedLibrary(
"pdex",
if (lib.root_src) |src| src.path else null,
.unversioned,
);
simulator_lib.setOutputDir("zig-out/lib");
simulator_lib.setTarget(simulator_target);
simulator_lib.addIncludePath(b.pathJoin(&.{ opts.playdate_sdk_path, "C_API" }));
simulator_lib.linkLibC();
simulator_lib.install();
pdc_step.step.dependOn(&simulator_lib.step);
// On non-windows, the lib is prefixed with "lib" and we have to clean
// that up or else pdc ignores it.
if (simulator_target.getOsTag() != .windows) {
const simulator_lib_copy_step = b.addSystemCommand(&.{
"bash",
"-c",
b.fmt(
"mkdir -p {0s} && cp zig-out/lib/lib{1s}{2s} {0s}/pdex{2s}",
.{
pdc_input,
simulator_lib.name,
simulator_target.dynamicLibSuffix(),
},
),
});
simulator_lib_copy_step.step.dependOn(&simulator_lib.step);
pdc_step.step.dependOn(&simulator_lib_copy_step.step);
}
return simulator_lib;
}
return null;
}
fn thisDir() []const u8 {
return std.fs.path.dirname(@src().file) orelse ".";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment