-
-
Save mitchellh/0d25e47ed2c4731f076b637904410b87 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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(©_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(©_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