diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 81f7de0e..8a7ad32c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,12 +37,22 @@ jobs: run: zig build -Dtarget=x86_64-windows-gnu - name: launch xvfb run: Xvfb :99 -screen 0 1680x720x24 > /dev/null 2>&1 & - - name: test + # TODO: do we actually need this? + - name: install Linux dependencies run: | sudo add-apt-repository -y ppa:kisak/kisak-mesa sudo apt-get update sudo apt-get install mesa-utils mesa-utils-extra mesa-va-drivers mesa-vdpau-drivers mesa-vulkan-drivers xvfb pulseaudio jackd - zig build test + - name: test + run: zig build test + - name: test (with core deps only) + run: zig build -Dcore test + - name: test (with sysaudio deps only) + run: zig build -Dsysaudio test + - name: test (with sysgpu deps only) + run: zig build -Dsysgpu test + - name: test (specific deps only) + run: zig build -Dcore -Dsysaudio -Dsysgpu test x86_64-windows: runs-on: windows-latest # We want to run on external PRs, but not on our own internal PRs as they'll be run by the push diff --git a/build.zig b/build.zig index 7c7a2d60..0e5bdc4c 100644 --- a/build.zig +++ b/build.zig @@ -1,49 +1,173 @@ const std = @import("std"); const builtin = @import("builtin"); const glfw = @import("mach_glfw"); -const sysaudio = @import("mach_sysaudio"); const core = @import("mach_core"); -var _module: ?*std.Build.Module = null; - +/// Examples: +/// +/// `zig build` -> builds all of Mach +/// `zig build test` -> runs all tests +/// +/// ## (optional) minimal dependency fetching +/// +/// By default, all Mach dependencies will be added to the build. If you only depend on a specific +/// part of Mach, then you can opt to have only the dependencies you need fetched as part of the +/// build: +/// +/// ``` +/// b.dependency("mach", .{ +/// .target = target, +/// .optimize = optimize, +/// .core = true, +/// .sysaudio = true, +/// }); +/// ``` +/// +/// The presense of `.core = true` and `.sysaudio = true` indicate Mach should add the dependencies +/// required by `@import("mach").core` and `@import("mach").sysaudio` to the build. You can use this +/// option with the following: +/// +/// * core (also implies sysgpu) +/// * sysaudio +/// * sysgpu +/// +/// Note that Zig's dead code elimination and, more importantly, lazy code evaluation means that +/// you really only pay for the parts of `@import("mach")` that you use/reference. pub fn build(b: *std.Build) !void { const optimize = b.standardOptimizeOption(.{}); const target = b.standardTargetOptions(.{}); + const core_deps = b.option(bool, "core", "build core specifically"); + const sysaudio_deps = b.option(bool, "sysaudio", "build sysaudio specifically"); + const sysgpu_deps = b.option(bool, "sysgpu", "build sysgpu specifically"); - const mach_core_dep = b.dependency("mach_core", .{ - .target = target, - .optimize = optimize, - }); - const mach_sysaudio_dep = b.dependency("mach_sysaudio", .{ - .target = target, - .optimize = optimize, - }); - const mach_basisu_dep = b.dependency("mach_basisu", .{ - .target = target, - .optimize = optimize, - }); - const mach_freetype_dep = b.dependency("mach_freetype", .{ - .target = target, - .optimize = optimize, - }); - const mach_sysjs_dep = b.dependency("mach_sysjs", .{ - .target = target, - .optimize = optimize, - }); - const font_assets_dep = b.dependency("font_assets", .{}); + const want_mach = core_deps != null or sysaudio_deps != null or sysgpu_deps != null; + const want_core = want_mach or (core_deps orelse false); + const want_sysaudio = want_mach or (sysaudio_deps orelse false); + const want_sysgpu = want_mach or (sysgpu_deps orelse false); + + const build_options = b.addOptions(); + build_options.addOption(bool, "want_mach", want_mach); + build_options.addOption(bool, "want_core", want_core); + build_options.addOption(bool, "want_sysaudio", want_sysaudio); + build_options.addOption(bool, "want_sysgpu", want_sysgpu); const module = b.addModule("mach", .{ .root_source_file = .{ .path = sdkPath("/src/main.zig") }, - .imports = &.{ - .{ .name = "mach-core", .module = mach_core_dep.module("mach-core") }, - .{ .name = "mach-sysaudio", .module = mach_sysaudio_dep.module("mach-sysaudio") }, - .{ .name = "mach-basisu", .module = mach_basisu_dep.module("mach-basisu") }, - .{ .name = "mach-freetype", .module = mach_freetype_dep.module("mach-freetype") }, - .{ .name = "mach-harfbuzz", .module = mach_freetype_dep.module("mach-harfbuzz") }, - .{ .name = "mach-sysjs", .module = mach_sysjs_dep.module("mach-sysjs") }, - .{ .name = "font-assets", .module = font_assets_dep.module("font-assets") }, - }, + .optimize = optimize, + .target = target, }); + module.addImport("build-options", build_options.createModule()); + if (want_mach) { + // TODO(Zig 2024.03): use b.lazyDependency + const mach_core_dep = b.dependency("mach_core", .{ + .target = target, + .optimize = optimize, + }); + const mach_basisu_dep = b.dependency("mach_basisu", .{ + .target = target, + .optimize = optimize, + }); + const mach_freetype_dep = b.dependency("mach_freetype", .{ + .target = target, + .optimize = optimize, + }); + const mach_sysjs_dep = b.dependency("mach_sysjs", .{ + .target = target, + .optimize = optimize, + }); + const font_assets_dep = b.dependency("font_assets", .{}); + + module.addImport("mach-core", mach_core_dep.module("mach-core")); + module.addImport("mach-basisu", mach_basisu_dep.module("mach-basisu")); + module.addImport("mach-freetype", mach_freetype_dep.module("mach-freetype")); + module.addImport("mach-harfbuzz", mach_freetype_dep.module("mach-harfbuzz")); + module.addImport("mach-sysjs", mach_sysjs_dep.module("mach-sysjs")); + module.addImport("font-assets", font_assets_dep.module("font-assets")); + } + if (want_sysaudio) { + // Can build sysaudio examples if desired, then. + inline for ([_][]const u8{ + "sine", + "record", + }) |example| { + const example_exe = b.addExecutable(.{ + .name = "sysaudio-" ++ example, + .root_source_file = .{ .path = "src/sysaudio/examples/" ++ example ++ ".zig" }, + .target = target, + .optimize = optimize, + }); + example_exe.root_module.addImport("mach", module); + addPaths(&example_exe.root_module); + b.installArtifact(example_exe); + + const example_compile_step = b.step("sysaudio-" ++ example, "Compile 'sysaudio-" ++ example ++ "' example"); + example_compile_step.dependOn(b.getInstallStep()); + + const example_run_cmd = b.addRunArtifact(example_exe); + example_run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| example_run_cmd.addArgs(args); + + const example_run_step = b.step("run-sysaudio-" ++ example, "Run '" ++ example ++ "' example"); + example_run_step.dependOn(&example_run_cmd.step); + } + + // Add sysaudio dependencies to the module. + // TODO(Zig 2024.03): use b.lazyDependency + const mach_sysjs_dep = b.dependency("mach_sysjs", .{ + .target = target, + .optimize = optimize, + }); + const mach_objc_dep = b.dependency("mach_objc", .{ + .target = target, + .optimize = optimize, + }); + module.addImport("sysjs", mach_sysjs_dep.module("mach-sysjs")); + module.addImport("objc", mach_objc_dep.module("mach-objc")); + + if (target.result.isDarwin()) { + // Transitive dependencies, explicit linkage of these works around + // ziglang/zig#17130 + module.linkSystemLibrary("objc", .{}); + + // Direct dependencies + module.linkFramework("AudioToolbox", .{}); + module.linkFramework("CoreFoundation", .{}); + module.linkFramework("CoreAudio", .{}); + } + if (target.result.os.tag == .linux) { + // TODO(Zig 2024.03): use b.lazyDependency + const linux_audio_headers_dep = b.dependency("linux_audio_headers", .{ + .target = target, + .optimize = optimize, + }); + module.link_libc = true; + module.linkLibrary(linux_audio_headers_dep.artifact("linux-audio-headers")); + + // TODO: for some reason this is not functional, a Zig bug (only when using this Zig package + // externally): + // + // module.addCSourceFile(.{ + // .file = .{ .path = "src/pipewire/sysaudio.c" }, + // .flags = &.{"-std=gnu99"}, + // }); + // + // error: unable to check cache: stat file '/Volumes/data/hexops/mach-flac/zig-cache//Volumes/data/hexops/mach-flac/src/pipewire/sysaudio.c' failed: FileNotFound + // + // So instead we do this: + const lib = b.addStaticLibrary(.{ + .name = "sysaudio-pipewire", + .target = target, + .optimize = optimize, + }); + lib.linkLibC(); + lib.addCSourceFile(.{ + .file = .{ .path = "src/pipewire/sysaudio.c" }, + .flags = &.{"-std=gnu99"}, + }); + lib.linkLibrary(linux_audio_headers_dep.artifact("linux-audio-headers")); + module.linkLibrary(lib); + } + } if (target.result.cpu.arch != .wasm32) { // Creates a step for unit testing. This only builds the test executable @@ -57,6 +181,7 @@ pub fn build(b: *std.Build) !void { while (iter.next()) |e| { unit_tests.root_module.addImport(e.key_ptr.*, e.value_ptr.*); } + addPaths(&unit_tests.root_module); // Exposes a `test` step to the `zig build --help` menu, providing a way for the user to // request running the unit tests. @@ -114,11 +239,6 @@ pub const App = struct { var deps = std.ArrayList(std.Build.Module.Import).init(app_builder.allocator); if (options.deps) |v| try deps.appendSlice(v); try deps.append(.{ .name = "mach", .module = mach_mod }); - const mach_sysaudio_dep = mach_builder.dependency("mach_sysaudio", .{ - .target = options.target, - .optimize = options.optimize, - }); - try deps.append(.{ .name = "mach-sysaudio", .module = mach_sysaudio_dep.module("mach-sysaudio") }); const mach_core_dep = mach_builder.dependency("mach_core", .{ .target = options.target, @@ -154,10 +274,15 @@ pub const App = struct { .target = app.compile.root_module.resolved_target.?, .optimize = app.compile.root_module.optimize.?, }).artifact("mach-basisu")); + addPaths(app.compile); } } }; +pub fn addPaths(mod: *std.Build.Module) void { + if (mod.resolved_target.?.result.isDarwin()) @import("xcode_frameworks").addPaths(mod); +} + fn sdkPath(comptime suffix: []const u8) []const u8 { if (suffix[0] != '/') @compileError("suffix must be an absolute path"); return comptime blk: { diff --git a/build.zig.zon b/build.zig.zon index b9e1c4b9..212e1b63 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -19,10 +19,6 @@ .url = "https://pkg.machengine.org/mach-basisu/5a68105a5e503c7183aab481b7ec6ae16b996943.tar.gz", .hash = "12204098c8d8d15a687068a3c0b15fe95d20f8fe074531e34e78f9dfe518cf86a67c", }, - .mach_sysaudio = .{ - .url = "https://pkg.machengine.org/mach-sysaudio/ce8ab30dd300b822224d14997c58c06520b642c9.tar.gz", - .hash = "12203f0d4afc83bae8571434ff48a459732c15c9542df13e0a97baedc73212d355a1", - }, .mach_freetype = .{ .url = "https://pkg.machengine.org/mach-freetype/dc4a5d8ce14f8678f35bdaf197303091e22b1f27.tar.gz", .hash = "122070070dd2c402d94c279d64d4a4d154691ad49f46fa2c24ed7c6e4e4f5c531477", @@ -35,5 +31,17 @@ .url = "https://github.com/hexops/font-assets/archive/6b43c160451e8fa5c64620ffb614929feacf2f5d.tar.gz", .hash = "12202039304f0603a9706105788e450fd4f901c3e20eca28a52a2173879b14c606c7", }, + .linux_audio_headers = .{ + .url = "https://pkg.machengine.org/linux-audio-headers/302c41dc4ab5fa292c3062e9e0d26c7f62652740.tar.gz", + .hash = "1220dc84528a0e298467bec65ab158426306961f08e7555eab9eef684ef126893689", + }, + .xcode_frameworks = .{ + .url = "https://pkg.machengine.org/xcode-frameworks/e7c2abbe28efbb5ae7f04e6c1ac6578a6d75bb67.tar.gz", + .hash = "1220432df3c74dc28bd6cc299e5c8317bc65a95219e6ebf40450c157d8c7a3ac54c0", + }, + .mach_objc = .{ + .url = "https://pkg.machengine.org/mach-objc/2b2a698e7f019e1599edb3eda4a974fa1fb07483.tar.gz", + .hash = "1220d708af437c2076d1a0482ac745b3c4507f4f41cc9f248ee78a3c297c41ee7c33", + }, }, } diff --git a/src/main.zig b/src/main.zig index 5f649dc4..a608a7b5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,19 +1,20 @@ -// Core re-exports -pub const core = @import("mach-core"); -pub const Timer = core.Timer; +const build_options = @import("build-options"); -// Mach packages -pub const gpu = core.gpu; -pub const sysjs = @import("mach-sysjs"); -pub const sysaudio = @import("mach-sysaudio"); +// Core re-exports +pub const core = if (build_options.want_core) @import("mach-core") else struct {}; +pub const Timer = if (build_options.want_core) core.Timer else struct {}; +pub const gpu = if (build_options.want_core) core.gpu else struct {}; +pub const sysjs = if (build_options.want_core) @import("mach-sysjs") else struct {}; // Mach standard library pub const ecs = @import("ecs/main.zig"); pub const gamemode = @import("gamemode.zig"); -pub const gfx = @import("gfx/main.zig"); +pub const gfx = if (build_options.want_mach) @import("gfx/main.zig") else struct {}; pub const math = @import("math/main.zig"); pub const testing = @import("testing.zig"); +pub const sysaudio = if (build_options.want_sysaudio) @import("sysaudio/main.zig") else struct {}; + // Engine exports pub const App = @import("engine.zig").App; pub const Engine = @import("engine.zig").Engine;