diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 8de9e31..d22cc4b 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -23,10 +23,7 @@ jobs: strategy: fail-fast: false matrix: - # macos-latest is macOS 26, whose SDK Zig 0.15.2 cannot link against — - # it fails while building its own build runner, before this package is - # reached. Unpin once we are on a Zig that supports it. - os: [ubuntu-latest, windows-latest, macos-15] + os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v7 @@ -34,11 +31,12 @@ jobs: # -Dinstall-vpk=true, but it needs an SDK to install it with. - uses: actions/setup-dotnet@v6 with: - dotnet-version: '9.0.x' + # vpk 1.2 targets .NET 10. + dotnet-version: '10.0.x' - uses: mlugg/setup-zig@v2 with: - version: 0.15.2 + version: 0.17.0 cache-key: ${{ matrix.os }} - run: ./test/package.sh diff --git a/README.md b/README.md index 3403630..2476e2c 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ rewritten yet so may be useless to read.*** ## Zig version -**`main` targets Zig 0.15.2** (`build.zig.zon` `minimum_zig_version`). +**`main` targets Zig 0.17.0** (`build.zig.zon` `minimum_zig_version`). ## Usage diff --git a/build.zig b/build.zig index 9d1837a..4373f4d 100644 --- a/build.zig +++ b/build.zig @@ -7,7 +7,7 @@ const builtin = @import("builtin"); /// Velopack release whose prebuilt runtime archives this package bundles. /// Keep in sync with the `velopack` dependency URL in build.zig.zon — the /// `vpk` CLI and the linked runtime should come from the same release. -pub const velopack_version = "0.0.1589-ga2c5a97"; +pub const velopack_version = "1.2.161"; /// No-op `pub fn build` — velopack-zig produces no install artifacts of its /// own. The exposed helpers below are invoked from the consumer's build.zig. @@ -27,10 +27,11 @@ fn ownBuilder(b: *std.Build) *std.Build { // Per-build cache of the host tools we compile out of tools/. var trim_velopack_tool: ?*std.Build.Step.Compile = null; var vpk_setup_tool: ?*std.Build.Step.Compile = null; +var run_env_tool: ?*std.Build.Step.Compile = null; // Steps that must not be duplicated when several call sites ask for them. // Keyed by consumer builder plus whatever else makes them distinct. -var step_cache: std.StringHashMapUnmanaged(*std.Build.Step.Run) = .{}; +var step_cache: std.StringHashMapUnmanaged(*std.Build.Step.Run) = .empty; fn cachedRun( b: *std.Build, @@ -51,7 +52,7 @@ fn hostTool(own: *std.Build, cache: *?*std.Build.Step.Compile, name: []const u8, .root_module = own.createModule(.{ .root_source_file = own.path(src), .target = own.graph.host, - .optimize = .Debug, + .optimize = .debug, }), }); cache.* = t; @@ -89,6 +90,23 @@ pub const LinkVelopackOptions = struct { msvc_dir: []const u8 = ".velopack-msvc", }; +/// `Velopack.h` as a Zig module, through translate-c: Zig 0.17 removed `@cImport`. +/// `linkVelopack` adds it to the compile's root module as `velopack_c`; add it to any other +/// module that calls the C API (`mod.addImport("velopack_c", velopackCModule(…))`). +pub fn velopackCModule( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) *std.Build.Module { + const velopack_dep = ownBuilder(b).dependency("velopack", .{}); + const tc = b.addTranslateC(.{ + .root_source_file = velopack_dep.path("include/Velopack.h"), + .target = target, + .optimize = optimize, + }); + return tc.createModule(); +} + pub fn linkVelopack( b: *std.Build, compile: *std.Build.Step.Compile, @@ -99,6 +117,7 @@ pub fn linkVelopack( const target = opts.target; compile.root_module.addIncludePath(velopack_dep.path("include")); + compile.root_module.addImport("velopack_c", velopackCModule(b, target, opts.optimize)); if (target.result.os.tag == .windows and target.result.abi != .msvc) @panic("velopack-zig: Velopack's Windows runtime is MSVC-only; build for *-windows-msvc"); @@ -178,23 +197,21 @@ fn handleWindowsMsvcLibc(b: *std.Build, compile: *std.Build.Step.Compile, opts: var needs_setup = false; const ini: []const u8 = blk: { - if (opts.windows_msvc_libc orelse b.libc_file) |p| { - break :blk if (std.fs.path.isAbsolute(p)) b.dupePath(p) else b.pathFromRoot(p); + if (opts.windows_msvc_libc) |p| { + break :blk if (std.fs.path.isAbsolute(p)) b.dupePath(p) else fromRoot(b, p); } const rel = msvcLibcIniPath(b, target, opts.msvc_dir) orelse return; - if (fileExists(b, rel)) break :blk b.pathFromRoot(rel); + if (fileExists(b, rel)) break :blk fromRoot(b, rel); // A Windows host with Visual Studio installed already has everything // Zig needs; don't download a second toolchain behind the user's back. if (b.graph.host.result.os.tag == .windows) return; needs_setup = true; - break :blk b.pathFromRoot(rel); + break :blk fromRoot(b, rel); }; if (needs_setup) compile.step.dependOn(&addMsvcupSetupStep(b, opts.msvc_dir).step); - // `b.libc_file` is inherited by *every* compile, host-side build tools - // included, which breaks them. Scope the manifest to the msvc compiles. - b.libc_file = null; + // Scoped to the msvc compiles: host-side build tools must not get it. applyWindowsMsvcLibcRecursive(b, &.{compile}, .{ .cwd_relative = ini }); } @@ -207,11 +224,31 @@ fn msvcLibcIniPath(b: *std.Build, target: std.Build.ResolvedTarget, msvc_dir: [] return b.fmt("{s}/zig-libc-{s}.ini", .{ msvc_dir, arch_suffix }); } +/// Whether `rel` (under the build root) exists, read while configuring and declared to the +/// configure cache, so the build reconfigures when it appears or goes. fn fileExists(b: *std.Build, rel: []const u8) bool { - b.build_root.handle.access(rel, .{}) catch return false; + b.root.access(b.graph.io, rel, .{}) catch { + // Declared on the nearest directory that exists: Zig 0.17's maker cannot load a + // configuration that depends on a path inside a missing directory (FileSystemFailure). + // Making the file changes that directory, so the build still reconfigures. + var dir = std.fs.path.dirname(rel); + while (dir) |d| : (dir = std.fs.path.dirname(d)) { + b.root.access(b.graph.io, d, .{}) catch continue; + b.dependOnDirectoryMetadata(b.path(d)); + return false; + } + b.dependOnDirectoryMetadata(b.path(".")); + return false; + }; + b.dependOnFileMetadata(b.path(rel)); return true; } +/// `rel` under the build root, as an absolute path. +fn fromRoot(b: *std.Build, rel: []const u8) []const u8 { + return b.root.joinString(b.allocator, rel) catch @panic("OOM"); +} + // --------------------------------------------------------------------------- // addVelopackStep / addVelopackedAppDir — run `vpk pack` over a staged app // directory. velopack-zig owns the whole invocation: provisioning the `vpk` @@ -347,12 +384,19 @@ pub fn addVelopackStep(b: *std.Build, opts: VelopackOptions) *std.Build.Step.Run const target = opts.target; const windows = target.result.os.tag == .windows; - const run = if (opts.vpk_argv) |argv| b.addSystemCommand(argv) else blk: { - const setup = cachedVpkSetup(b, opts); - const r = b.addSystemCommand(&.{vpkExePath(b, opts.vpk_dir, opts.vpk_version)}); - r.step.dependOn(&setup.step); - break :blk r; + const vpk_argv: []const []const u8 = opts.vpk_argv orelse &.{vpkExePath(b, opts.vpk_dir, opts.vpk_version)}; + // vpk runs through `run_env`, which sets its environment when it runs (see the tool for why + // not `setEnvironmentVariable`). vpk targets an older .NET than is typically installed, and a + // Linux AppImage needs `mksquashfs` on its PATH. + const run = b.addRunArtifact(hostTool(ownBuilder(b), &run_env_tool, "run-env", "tools/run_env.zig")); + run.addArgs(&.{ "--env", "DOTNET_ROLL_FORWARD=Major" }); + if (target.result.os.tag == .linux) if (buildMksquashfs(b)) |m| { + run.addArg("--path"); + run.addDirectoryArg(m.bin_dir); }; + run.addArg("--"); + run.addArgs(vpk_argv); + if (opts.vpk_argv == null) run.step.dependOn(&cachedVpkSetup(b, opts).step); // `vpk` infers the platform from the host, so packaging for another OS // needs an explicit selector. @@ -386,10 +430,6 @@ pub fn addVelopackStep(b: *std.Build, opts: VelopackOptions) *std.Build.Step.Run } for (opts.depends_on) |dep| run.step.dependOn(dep); - // vpk targets an older .NET runtime than what is typically installed. - run.setEnvironmentVariable("DOTNET_ROLL_FORWARD", "Major"); - - attachMksquashfsToVpkRun(b, run, target); return run; } @@ -397,7 +437,7 @@ pub fn addVelopackStep(b: *std.Build, opts: VelopackOptions) *std.Build.Step.Run /// to. pub fn outputDir(run: *std.Build.Step.Run) std.Build.LazyPath { for (run.argv.items) |arg| switch (arg) { - .output_directory => |out| return .{ .generated = .{ .file = &out.generated_file } }, + .output_directory => |out| return .{ .generated = .{ .index = out.generated_file } }, else => {}, }; @panic("velopack-zig: Run has no output directory; was it made by addVelopackStep?"); @@ -409,7 +449,7 @@ pub fn outputDir(run: *std.Build.Step.Run) std.Build.LazyPath { /// binary under a fixed path would leave the key untouched and silently reuse /// the previous version's bundle. fn vpkInstallDir(b: *std.Build, vpk_dir: []const u8, version: VpkVersion) []const u8 { - return b.pathFromRoot(b.pathJoin(&.{ vpk_dir, version.string() orelse "latest" })); + return fromRoot(b, b.pathJoin(&.{ vpk_dir, version.string() orelse "latest" })); } fn vpkExePath(b: *std.Build, vpk_dir: []const u8, version: VpkVersion) []const u8 { @@ -449,10 +489,9 @@ fn cachedVpkSetup(b: *std.Build, opts: VelopackOptions) *std.Build.Step.Run { // --------------------------------------------------------------------------- pub const MksquashfsBuild = struct { - /// Wire as a dependency of the consumer's vpk pack run step. - step: *std.Build.Step, - /// Pass to `Run.addPathDir(...)` so vpk finds mksquashfs. - bin_dir: []const u8, + /// The directory holding `mksquashfs`, resolved when the build runs; put it on vpk's PATH + /// (`addVelopackStep` runs vpk through `tools/run_env.zig` to do that). + bin_dir: std.Build.LazyPath, }; /// Build the bundled mksquashfs for Linux AppImage packaging. @@ -471,36 +510,11 @@ pub fn buildMksquashfs(b: *std.Build) ?MksquashfsBuild { const dep = own.lazyDependency("squashfs", .{ .target = own.graph.host, - .optimize = .ReleaseFast, + .optimize = .fast, }) orelse return null; const mksquashfs_art = dep.artifact("mksquashfs"); - const install_mksquashfs = b.addInstallArtifact(mksquashfs_art, .{ - .dest_dir = .{ .override = .{ .custom = "velopack-mksquashfs" } }, - }); - - return MksquashfsBuild{ - .step = &install_mksquashfs.step, - .bin_dir = b.getInstallPath(.{ .custom = "velopack-mksquashfs" }, ""), - }; -} - -/// Wire the bundled mksquashfs into a `vpk pack` Run for Linux targets. -/// -/// No-op for non-Linux targets: vpk only needs squashfs when packaging a Linux -/// AppImage. On Linux targets this builds mksquashfs (lazily — first build -/// prompts the user to `--fetch`) and prepends its bin dir to the Run's PATH. -/// -/// `addVelopackStep` already calls this; consumers rarely need it directly. -pub fn attachMksquashfsToVpkRun( - b: *std.Build, - run: *std.Build.Step.Run, - target: std.Build.ResolvedTarget, -) void { - if (target.result.os.tag != .linux) return; - const built = buildMksquashfs(b) orelse return; - run.addPathDir(built.bin_dir); - run.step.dependOn(built.step); + return MksquashfsBuild{ .bin_dir = mksquashfs_art.getEmittedBin().dirname() }; } // --------------------------------------------------------------------------- @@ -527,31 +541,36 @@ fn createMsvcupSetupStep(b: *std.Build, install_dir: []const u8) *std.Build.Step const resolved_install_dir: []const u8 = if (std.fs.path.isAbsolute(install_dir)) b.dupePath(install_dir) else - b.pathFromRoot(install_dir); + fromRoot(b, install_dir); - const env_path = own.path("tools/msvcup.env").getPath3(b, null).toString(b.allocator) catch |e| + const env_path = own.root.joinString(b.allocator, "tools/msvcup.env") catch |e| std.debug.panic("velopack-zig: resolve msvcup.env: {}", .{e}); - const gen_path = own.path("tools/gen_zig_libc_msvc.zig").getPath3(b, null).toString(b.allocator) catch |e| + const gen_path = own.root.joinString(b.allocator, "tools/gen_zig_libc_msvc.zig") catch |e| std.debug.panic("velopack-zig: resolve gen_zig_libc_msvc.zig: {}", .{e}); - const run: *std.Build.Step.Run = switch (builtin.os.tag) { - .windows => blk: { - const script_path = own.path("tools/setup-msvc.ps1").getPath3(b, null).toString(b.allocator) catch |e| + // Through `run_env`, not `setEnvironmentVariable`: see `tools/run_env.zig`. + const run = b.addRunArtifact(hostTool(own, &run_env_tool, "run-env", "tools/run_env.zig")); + run.addArgs(&.{ + "--env", b.fmt("VELOPACK_ZIG_ENV_FILE={s}", .{env_path}), + "--env", b.fmt("VELOPACK_ZIG_GEN_SCRIPT={s}", .{gen_path}), + "--env", b.fmt("VELOPACK_ZIG_ZIG={s}", .{b.graph.zig_exe}), + "--", + }); + switch (builtin.target.os.tag) { + .windows => { + const script_path = own.root.joinString(b.allocator, "tools/setup-msvc.ps1") catch |e| std.debug.panic("velopack-zig: resolve setup-msvc.ps1: {}", .{e}); - break :blk b.addSystemCommand(&.{ + run.addArgs(&.{ "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script_path, resolved_install_dir, }); }, - else => blk: { - const script_path = own.path("tools/setup-msvc.sh").getPath3(b, null).toString(b.allocator) catch |e| + else => { + const script_path = own.root.joinString(b.allocator, "tools/setup-msvc.sh") catch |e| std.debug.panic("velopack-zig: resolve setup-msvc.sh: {}", .{e}); - break :blk b.addSystemCommand(&.{ "bash", script_path, resolved_install_dir }); + run.addArgs(&.{ "bash", script_path, resolved_install_dir }); }, - }; - run.setEnvironmentVariable("VELOPACK_ZIG_ENV_FILE", env_path); - run.setEnvironmentVariable("VELOPACK_ZIG_GEN_SCRIPT", gen_path); - run.setEnvironmentVariable("VELOPACK_ZIG_ZIG", b.graph.zig_exe); + } return run; } @@ -580,7 +599,7 @@ pub fn resolveWindowsMsvcLibc( return .{ .libc_path = null, .needs_setup = false }; if (opts.explicit_path) |p| { - const abs = if (std.fs.path.isAbsolute(p)) b.dupePath(p) else b.pathFromRoot(p); + const abs = if (std.fs.path.isAbsolute(p)) b.dupePath(p) else fromRoot(b, p); return .{ .libc_path = abs, .needs_setup = false }; } @@ -588,9 +607,9 @@ pub fn resolveWindowsMsvcLibc( return .{ .libc_path = null, .needs_setup = false }; if (fileExists(b, rel)) - return .{ .libc_path = b.pathFromRoot(rel), .needs_setup = false }; + return .{ .libc_path = fromRoot(b, rel), .needs_setup = false }; if (opts.fetch_if_missing) - return .{ .libc_path = b.pathFromRoot(rel), .needs_setup = true }; + return .{ .libc_path = fromRoot(b, rel), .needs_setup = true }; return .{ .libc_path = null, .needs_setup = false }; } diff --git a/build.zig.zon b/build.zig.zon index 7fdd8ed..c701534 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -5,22 +5,23 @@ // https://github.com/velopack/velopack/releases plus Zig-built mksquashfs // (squashfs-tools + zlib) for `vpk pack` on Linux. .version = "0.1.0", - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.17.0", .fingerprint = 0x95d249841170b06d, .dependencies = .{ - // Prebuilt C-ABI archives for every supported triple. Same release - // pixi currently uses; bump in lockstep so both consumers see the - // same Velopack runtime version. + // Prebuilt C-ABI archives for every supported triple. The release + // `velopack_version` in build.zig names: `vpk` installs at the same + // version, so bump both together. .velopack = .{ - .url = "https://github.com/velopack/velopack/releases/download/0.0.1589-ga2c5a97/velopack_libc_0.0.1589-ga2c5a97.zip", - .hash = "N-V-__8AAGLSaQX1RqiiyJ0wx1tREvLoiEyH-NHb0YTvjMzW", + .url = "https://github.com/velopack/velopack/releases/download/1.2.161/velopack_libc_1.2.161.zip", + .hash = "N-V-__8AAK4wKAbP2L62TlmTlu6t9PQ1qMVF5Gt_SaI9TwGd", }, // Zig build of mksquashfs (+ unsquashfs) from upstream squashfs-tools. - // Lazy — only fetched when buildMksquashfs() is called on a Linux host. + // Vendored from github.com/allyourcodebase/squashfs-tools@4ed6b23 with + // its build.zig updated for Zig 0.17; switch back to the url once + // upstream supports it. .squashfs = .{ - .url = "git+https://github.com/allyourcodebase/squashfs-tools?ref=main#4ed6b23f0ccd0c108ec88745e10a506f6030128f", - .hash = "squashfs-4.7.5-oYR59XkYAABk2d0xgAhBoMmnidQCk0wSoTK9OFrRZ9V9", + .path = "squashfs", .lazy = true, }, }, diff --git a/squashfs/build.zig b/squashfs/build.zig new file mode 100644 index 0000000..81bb0e7 --- /dev/null +++ b/squashfs/build.zig @@ -0,0 +1,150 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const small_reader_threads = b.option(u32, "small-reader-threads", "Default small reader threads (Makefile default: 4)") orelse 4; + const block_reader_threads = b.option(u32, "block-reader-threads", "Default block reader threads (Makefile default: 4)") orelse 4; + + const upstream = b.dependency("squashfs_tools", .{}); + const zlib_dep = b.dependency("zlib", .{ + .target = target, + .optimize = optimize, + }); + + const max_reader_threads: u32 = 1024; + + // NOTE: squashfs-tools uses various posix features, does not support windows or MacOS (afaict), + // and we've only tested on Linux + + var common_flags = std.ArrayList([]const u8).initCapacity(b.allocator, 32) catch @panic("OOM"); + common_flags.appendSlice(b.allocator, &.{ + "-D_FILE_OFFSET_BITS=64", + "-D_LARGEFILE_SOURCE", + "-D_GNU_SOURCE", + "-DCOMP_DEFAULT=\"gzip\"", + "-DGZIP_SUPPORT", + "-DXATTR_SUPPORT", + "-DXATTR_OS_SUPPORT", + "-DXATTR_DEFAULT", + "-DVERSION=\"4.7.5\"", + "-DDATE=\"2026/03/01\"", + "-DYEAR=\"2026\"", + b.fmt("-DMAX_READER_THREADS={d}", .{max_reader_threads}), + b.fmt("-DSMALL_READER_THREADS={d}", .{small_reader_threads}), + b.fmt("-DBLOCK_READER_THREADS={d}", .{block_reader_threads}), + "-Wall", + "-Wno-unused-result", + }) catch @panic("OOM"); + + const mksquashfs_sources = [_][]const u8{ + "squashfs-tools/mksquashfs.c", + "squashfs-tools/read_fs.c", + "squashfs-tools/action.c", + "squashfs-tools/swap.c", + "squashfs-tools/pseudo.c", + "squashfs-tools/compressor.c", + "squashfs-tools/sort.c", + "squashfs-tools/progressbar.c", + "squashfs-tools/info.c", + "squashfs-tools/restore.c", + "squashfs-tools/process_fragments.c", + "squashfs-tools/caches-queues-lists.c", + "squashfs-tools/reader.c", + "squashfs-tools/tar.c", + "squashfs-tools/date.c", + "squashfs-tools/memory.c", + "squashfs-tools/print_pager.c", + "squashfs-tools/symbolic_mode.c", + "squashfs-tools/thread.c", + "squashfs-tools/nprocessors_compat.c", + "squashfs-tools/limit.c", + "squashfs-tools/virt_disk_pos.c", + "squashfs-tools/gzip_wrapper.c", + "squashfs-tools/xattr.c", + "squashfs-tools/read_xattrs.c", + "squashfs-tools/tar_xattr.c", + "squashfs-tools/pseudo_xattr.c", + "squashfs-tools/xattr_system.c", + }; + + const unsquashfs_sources = [_][]const u8{ + "squashfs-tools/unsquashfs.c", + "squashfs-tools/unsquash-1.c", + "squashfs-tools/unsquash-2.c", + "squashfs-tools/unsquash-3.c", + "squashfs-tools/unsquash-4.c", + "squashfs-tools/unsquash-123.c", + "squashfs-tools/unsquash-34.c", + "squashfs-tools/unsquash-1234.c", + "squashfs-tools/unsquash-12.c", + "squashfs-tools/swap.c", + "squashfs-tools/compressor.c", + "squashfs-tools/unsquashfs_info.c", + "squashfs-tools/date.c", + "squashfs-tools/memory.c", + "squashfs-tools/print_pager.c", + "squashfs-tools/nprocessors_compat.c", + "squashfs-tools/limit.c", + "squashfs-tools/gzip_wrapper.c", + "squashfs-tools/read_xattrs.c", + "squashfs-tools/unsquashfs_xattr.c", + "squashfs-tools/unsquashfs_xattr_system.c", + }; + + const mksquashfs = b.addExecutable(.{ + .name = "mksquashfs", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + mksquashfs.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &mksquashfs_sources, + .flags = common_flags.items, + }); + { + var help_flags = common_flags.clone(b.allocator) catch @panic("OOM"); + help_flags.append(b.allocator, "-DCOMPRESSORS=\"gzip (default)\"") catch @panic("OOM"); + mksquashfs.root_module.addCSourceFile(.{ + .file = upstream.path("squashfs-tools/mksquashfs_help.c"), + .flags = help_flags.items, + }); + } + mksquashfs.root_module.addIncludePath(upstream.path("squashfs-tools")); + mksquashfs.root_module.linkLibrary(zlib_dep.artifact("z")); + // TODO: explore using musl libm and disabling threading? + mksquashfs.root_module.linkSystemLibrary("pthread", .{}); + mksquashfs.root_module.linkSystemLibrary("m", .{}); + b.installArtifact(mksquashfs); + + const unsquashfs = b.addExecutable(.{ + .name = "unsquashfs", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + }), + }); + unsquashfs.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &unsquashfs_sources, + .flags = common_flags.items, + }); + { + var help_flags = common_flags.clone(b.allocator) catch @panic("OOM"); + help_flags.append(b.allocator, "-DDECOMPRESSORS=\"gzip\"") catch @panic("OOM"); + unsquashfs.root_module.addCSourceFile(.{ + .file = upstream.path("squashfs-tools/unsquashfs_help.c"), + .flags = help_flags.items, + }); + } + unsquashfs.root_module.addIncludePath(upstream.path("squashfs-tools")); + unsquashfs.root_module.linkLibrary(zlib_dep.artifact("z")); + unsquashfs.root_module.linkSystemLibrary("pthread", .{}); + unsquashfs.root_module.linkSystemLibrary("m", .{}); + b.installArtifact(unsquashfs); +} diff --git a/squashfs/build.zig.zon b/squashfs/build.zig.zon new file mode 100644 index 0000000..dd5bcf5 --- /dev/null +++ b/squashfs/build.zig.zon @@ -0,0 +1,20 @@ +.{ + .name = .squashfs, + .version = "4.7.5", + .fingerprint = 0x37ba2947f57984a1, + .minimum_zig_version = "0.17.0", + .dependencies = .{ + .squashfs_tools = .{ + .url = "git+https://github.com/plougher/squashfs-tools?ref=master#55381b3c75812d7856518a5843502185db22e98e", + .hash = "N-V-__8AADLmQgBO-gn4-alEvrUeC4MdMNbAuvZypPfnh3Ao", + }, + .zlib = .{ + .url = "https://github.com/allyourcodebase/zlib/archive/refs/tags/1.3.2.tar.gz", + .hash = "zlib-1.3.2-ZZQ7lc8NAAAHm9MDfplvwoesXvk4tVm6VCsiI8KnIbT0", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/test/sample/build.zig b/test/sample/build.zig index 11d72df..7d0cae6 100644 --- a/test/sample/build.zig +++ b/test/sample/build.zig @@ -19,9 +19,9 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }), }); - exe.linkLibC(); velopack.linkVelopack(b, exe, .{ .target = target, .optimize = optimize }); diff --git a/test/sample/build.zig.zon b/test/sample/build.zig.zon index c12f9a6..a8b0ae6 100644 --- a/test/sample/build.zig.zon +++ b/test/sample/build.zig.zon @@ -2,7 +2,7 @@ .name = .velopack_zig_sample, .version = "1.0.0", .fingerprint = 0x259d39aa5313eb7e, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.17.0", .dependencies = .{ .velopack_zig = .{ .path = "../.." }, }, diff --git a/test/sample/src/main.zig b/test/sample/src/main.zig index 8b5d1fa..ebc929e 100644 --- a/test/sample/src/main.zig +++ b/test/sample/src/main.zig @@ -1,7 +1,5 @@ const std = @import("std"); -const c = @cImport({ - @cInclude("Velopack.h"); -}); +const c = @import("velopack_c"); pub fn main() void { // Handles Velopack's install/update hooks. A no-op outside an install, so diff --git a/tools/gen_zig_libc_msvc.zig b/tools/gen_zig_libc_msvc.zig index c4ecbc6..ddb8ddb 100644 --- a/tools/gen_zig_libc_msvc.zig +++ b/tools/gen_zig_libc_msvc.zig @@ -8,7 +8,7 @@ //! //! Usage: zig run gen_zig_libc_msvc.zig -- //! -//! Pinned to Zig 0.15.2 std APIs. +//! Pinned to Zig 0.16.0 std APIs. const std = @import("std"); const case_aliases = @import("symlink-bad-cased-include-tree.zig"); @@ -21,10 +21,10 @@ fn die(comptime fmt: []const u8, args: anytype) noreturn { std.process.exit(1); } -fn pickLatestSubdirName(alloc: std.mem.Allocator, parent: std.fs.Dir) ![]const u8 { +fn pickLatestSubdirName(alloc: std.mem.Allocator, io: std.Io, parent: std.Io.Dir) ![]const u8 { var best: ?[]const u8 = null; var it = parent.iterate(); - while (try it.next()) |e| { + while (try it.next(io)) |e| { if (e.kind != .directory) continue; const n = e.name; if (std.mem.eql(u8, n, ".") or std.mem.eql(u8, n, "..")) continue; @@ -40,45 +40,44 @@ fn pickLatestSubdirName(alloc: std.mem.Allocator, parent: std.fs.Dir) ![]const u return best orelse error.Empty; } -fn exists(dir: std.fs.Dir, sub_path: []const u8) bool { - dir.access(sub_path, .{}) catch return false; +fn exists(io: std.Io, dir: std.Io.Dir, sub_path: []const u8) bool { + dir.access(io, sub_path, .{}) catch return false; return true; } -pub fn main() !void { - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - defer arena.deinit(); - const alloc = arena.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.arena.allocator(); + const io = init.io; - var args = try std.process.argsWithAllocator(alloc); + var args = try init.minimal.args.iterateAllocator(alloc); defer args.deinit(); _ = args.next(); // exe const install_root = args.next() orelse die("usage: gen_zig_libc_msvc ", .{}); const arch = args.next() orelse die("usage: gen_zig_libc_msvc ", .{}); const out_path = args.next() orelse die("usage: gen_zig_libc_msvc ", .{}); - var root = try std.fs.openDirAbsolute(install_root, .{}); - defer root.close(); + var root = try std.Io.Dir.openDirAbsolute(io, install_root, .{}); + defer root.close(io); - var inc_dir = try root.openDir("Windows Kits/10/Include", .{ .iterate = true }); - defer inc_dir.close(); - const sdk_inc_ver = try pickLatestSubdirName(alloc, inc_dir); + var inc_dir = try root.openDir(io, "Windows Kits/10/Include", .{ .iterate = true }); + defer inc_dir.close(io); + const sdk_inc_ver = try pickLatestSubdirName(alloc, io, inc_dir); - var lib_dir = try root.openDir("Windows Kits/10/Lib", .{ .iterate = true }); - defer lib_dir.close(); - const sdk_lib_ver = try pickLatestSubdirName(alloc, lib_dir); + var lib_dir = try root.openDir(io, "Windows Kits/10/Lib", .{ .iterate = true }); + defer lib_dir.close(io); + const sdk_lib_ver = try pickLatestSubdirName(alloc, io, lib_dir); - var msvc_root = try root.openDir("VC/Tools/MSVC", .{ .iterate = true }); - defer msvc_root.close(); - const msvc_ver = try pickLatestSubdirName(alloc, msvc_root); + var msvc_root = try root.openDir(io, "VC/Tools/MSVC", .{ .iterate = true }); + defer msvc_root.close(io); + const msvc_ver = try pickLatestSubdirName(alloc, io, msvc_root); - if (case_aliases.host_needs_aliases and !exists(root, case_alias_marker)) { + if (case_aliases.host_needs_aliases and !exists(io, root, case_alias_marker)) { std.debug.print("Normalizing SDK filename case (once per install)...\n", .{}); const sdk_include = try std.fs.path.join(alloc, &.{ "Windows Kits/10/Include", sdk_inc_ver }); const sdk_lib = try std.fs.path.join(alloc, &.{ "Windows Kits/10/Lib", sdk_lib_ver }); const msvc_include = try std.fs.path.join(alloc, &.{ "VC/Tools/MSVC", msvc_ver, "include" }); const msvc_lib = try std.fs.path.join(alloc, &.{ "VC/Tools/MSVC", msvc_ver, "lib" }); - try case_aliases.apply(alloc, root, .{ + try case_aliases.apply(alloc, io, root, .{ .lowercase_trees = &.{ sdk_include, sdk_lib, msvc_include, msvc_lib }, .scan_trees = &.{ sdk_include, msvc_include }, .include_roots = &.{ @@ -95,7 +94,7 @@ pub fn main() !void { msvc_lib, }, }); - (try root.createFile(case_alias_marker, .{})).close(); + (try root.createFile(io, case_alias_marker, .{})).close(io); } const include_dir = try std.fs.path.join(alloc, &.{ install_root, "Windows Kits/10/Include", sdk_inc_ver, "ucrt" }); @@ -114,14 +113,14 @@ pub fn main() !void { const dir_path = pair.@"0"; const base = pair.@"1"; const full = try std.fs.path.join(alloc, &.{ dir_path, base }); - std.fs.accessAbsolute(full, .{}) catch |e| die("missing {s}: {}", .{ full, e }); + std.Io.Dir.accessAbsolute(io, full, .{}) catch |e| die("missing {s}: {}", .{ full, e }); } if (std.fs.path.dirname(out_path)) |dir| { - try std.fs.cwd().makePath(dir); + try std.Io.Dir.cwd().createDirPath(io, dir); } - const out_file = try std.fs.createFileAbsolute(out_path, .{}); - defer out_file.close(); + const out_file = try std.Io.Dir.createFileAbsolute(io, out_path, .{}); + defer out_file.close(io); const contents = try std.fmt.allocPrint(alloc, \\include_dir={s} @@ -129,7 +128,8 @@ pub fn main() !void { \\crt_dir={s} \\msvc_lib_dir={s} \\kernel32_lib_dir={s} - \\gcc_dir= + \\cc_dir= + \\darwin_sdk_dir= \\ , .{ include_dir, @@ -138,5 +138,5 @@ pub fn main() !void { msvc_lib_dir, kernel32_lib_dir, }); - try out_file.writeAll(contents); + try out_file.writeStreamingAll(io, contents); } diff --git a/tools/run_env.zig b/tools/run_env.zig new file mode 100644 index 0000000..314cf1a --- /dev/null +++ b/tools/run_env.zig @@ -0,0 +1,49 @@ +//! Run a command with environment changes applied when it runs, on top of the environment the +//! build is running in. `vpk` needs `DOTNET_ROLL_FORWARD=Major`, and `mksquashfs` on its `PATH` +//! for a Linux AppImage. +//! +//! Why a program: in Zig 0.17, `Run.setEnvironmentVariable` copies the whole environment while +//! the build is configured, and that copy is cached with the configuration, so the command keeps +//! getting the environment the first configure saw (a `DOTNET_ROOT` set later never arrives). +//! `Run.addPathDir` is gone too, and the directory is only known once the build runs. +//! +//! Usage: `run_env [--path ]... [--env =]... -- [args...]`. +//! Exits with the command's exit code. +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(arena); + const env = init.environ_map; + const sep = if (builtin.target.os.tag == .windows) ";" else ":"; + + var i: usize = 1; + while (i < args.len) : (i += 1) { + const arg = args[i]; + if (std.mem.eql(u8, arg, "--")) break; + if (i + 1 >= args.len) usage(); + i += 1; + if (std.mem.eql(u8, arg, "--path")) { + const dir = try std.fs.path.resolve(arena, &.{args[i]}); + const path = if (env.get("PATH")) |p| try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ dir, sep, p }) else dir; + try env.put("PATH", path); + } else if (std.mem.eql(u8, arg, "--env")) { + const eq = std.mem.indexOfScalar(u8, args[i], '=') orelse usage(); + try env.put(args[i][0..eq], args[i][eq + 1 ..]); + } else usage(); + } + if (i + 1 >= args.len) usage(); + + var child = try std.process.spawn(io, .{ .argv = args[i + 1 ..], .environ_map = env }); + switch (try child.wait(io)) { + .exited => |code| std.process.exit(code), + else => std.process.exit(1), + } +} + +fn usage() noreturn { + std.debug.print("usage: run_env [--path ]... [--env =]... -- [args...]\n", .{}); + std.process.exit(2); +} diff --git a/tools/symlink-bad-cased-include-tree.zig b/tools/symlink-bad-cased-include-tree.zig index a12b40f..1667d3d 100644 --- a/tools/symlink-bad-cased-include-tree.zig +++ b/tools/symlink-bad-cased-include-tree.zig @@ -39,14 +39,14 @@ //! against the tree above them, so `DriverSpecs.h` has to be matched inside //! `shared/` rather than as `shared/driverspecs.h` one level up. //! -//! Pinned to Zig 0.15.2 std APIs. +//! Pinned to Zig 0.16.0 std APIs. const std = @import("std"); const builtin = @import("builtin"); /// Whether this pass is needed at all. Windows and (by default) macOS fold case /// in the filesystem, so the aliases would be pure noise there. -pub const host_needs_aliases = builtin.os.tag != .windows and builtin.os.tag != .macos; +pub const host_needs_aliases = builtin.target.os.tag != .windows and builtin.target.os.tag != .macos; /// Where the interesting directories live, relative to the tree root. Every /// path is optional: missing ones are skipped, so one layout can describe @@ -66,56 +66,56 @@ pub const Layout = struct { /// Symlink every spelling the tree uses onto the file that really exists. /// Allocations live for the whole pass; hand it an arena. -pub fn apply(alloc: std.mem.Allocator, root: std.fs.Dir, layout: Layout) !void { +pub fn apply(alloc: std.mem.Allocator, io: std.Io, root: std.Io.Dir, layout: Layout) !void { for (layout.lowercase_trees) |sub| { - var dir = openIter(root, sub) orelse continue; - defer dir.close(); - try aliasLowercaseTree(alloc, dir); + var dir = openIter(io, root, sub) orelse continue; + defer dir.close(io); + try aliasLowercaseTree(alloc, io, dir); } - var includes: std.StringHashMapUnmanaged(void) = .{}; - var libs: std.StringHashMapUnmanaged(void) = .{}; + var includes: std.StringHashMapUnmanaged(void) = .empty; + var libs: std.StringHashMapUnmanaged(void) = .empty; for (layout.scan_trees) |sub| { - var dir = openIter(root, sub) orelse continue; - defer dir.close(); - try scanTree(alloc, dir, &includes, &libs); + var dir = openIter(io, root, sub) orelse continue; + defer dir.close(io); + try scanTree(alloc, io, dir, &includes, &libs); } for (layout.include_roots) |sub| { - var dir = openIter(root, sub) orelse continue; - defer dir.close(); - try aliasSpellingsIn(alloc, dir, includes); + var dir = openIter(io, root, sub) orelse continue; + defer dir.close(io); + try aliasSpellingsIn(alloc, io, dir, includes); } for (layout.lib_root_parents) |sub| { - var parent = openIter(root, sub) orelse continue; - defer parent.close(); + var parent = openIter(io, root, sub) orelse continue; + defer parent.close(io); var it = parent.iterate(); - while (try it.next()) |entry| { + while (try it.next(io)) |entry| { if (entry.kind != .directory) continue; - var dir = openIter(parent, entry.name) orelse continue; - defer dir.close(); - try aliasSpellingsIn(alloc, dir, libs); - try aliasLibCaseVariants(alloc, dir); + var dir = openIter(io, parent, entry.name) orelse continue; + defer dir.close(io); + try aliasSpellingsIn(alloc, io, dir, libs); + try aliasLibCaseVariants(alloc, io, dir); } } } -fn openIter(root: std.fs.Dir, sub_path: []const u8) ?std.fs.Dir { - return root.openDir(sub_path, .{ .iterate = true }) catch null; +fn openIter(io: std.Io, root: std.Io.Dir, sub_path: []const u8) ?std.Io.Dir { + return root.openDir(io, sub_path, .{ .iterate = true }) catch null; } /// Maps the lowercased form of every real path under a directory to the real /// one, so a differently-cased spelling can be resolved back to its file. const CaseMap = struct { alloc: std.mem.Allocator, - entries: std.StringHashMapUnmanaged([]const u8) = .{}, + entries: std.StringHashMapUnmanaged([]const u8) = .empty, - fn build(alloc: std.mem.Allocator, dir: std.fs.Dir) !CaseMap { + fn build(alloc: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) !CaseMap { var self: CaseMap = .{ .alloc = alloc }; var walker = try dir.walk(alloc); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { // Skip symlinks: aliases from an earlier run, and following them // would walk the same subtree twice. if (entry.kind == .sym_link) continue; @@ -136,7 +136,7 @@ const CaseMap = struct { /// Make `spelling` resolve to `real_rel` under `dir` by symlinking each path /// component that is spelled differently. Idempotent. -fn aliasSpelling(alloc: std.mem.Allocator, dir: std.fs.Dir, real_rel: []const u8, spelling: []const u8) !void { +fn aliasSpelling(alloc: std.mem.Allocator, io: std.Io, dir: std.Io.Dir, real_rel: []const u8, spelling: []const u8) !void { var real_parts = std.mem.tokenizeScalar(u8, real_rel, '/'); var spell_parts = std.mem.tokenizeScalar(u8, spelling, '/'); var prefix: []const u8 = ""; @@ -147,7 +147,7 @@ fn aliasSpelling(alloc: std.mem.Allocator, dir: std.fs.Dir, real_rel: []const u8 spell_part else try std.fs.path.join(alloc, &.{ prefix, spell_part }); - dir.symLink(real_part, link, .{}) catch |err| switch (err) { + dir.symLink(io, real_part, link, .{}) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; @@ -163,10 +163,11 @@ fn aliasSpelling(alloc: std.mem.Allocator, dir: std.fs.Dir, real_rel: []const u8 /// header / library search root. fn aliasSpellingsIn( alloc: std.mem.Allocator, - dir: std.fs.Dir, + io: std.Io, + dir: std.Io.Dir, spellings: std.StringHashMapUnmanaged(void), ) !void { - const map = try CaseMap.build(alloc, dir); + const map = try CaseMap.build(alloc, io, dir); var it = spellings.keyIterator(); while (it.next()) |key| { const spelling = key.*; @@ -176,18 +177,18 @@ fn aliasSpellingsIn( if (std.mem.indexOf(u8, normalized, "..") != null) continue; const real = (try map.realPath(normalized)) orelse continue; if (std.mem.eql(u8, real, normalized)) continue; - try aliasSpelling(alloc, dir, real, normalized); + try aliasSpelling(alloc, io, dir, real, normalized); } } /// Give every mixed-case entry under `dir` an all-lowercase alias — the /// spelling clang and lld-link reach for by default. -fn aliasLowercaseTree(alloc: std.mem.Allocator, dir: std.fs.Dir) !void { +fn aliasLowercaseTree(alloc: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) !void { const Entry = struct { name: []const u8, is_dir: bool }; var entries: std.ArrayList(Entry) = .empty; var it = dir.iterate(); - while (try it.next()) |e| { + while (try it.next(io)) |e| { if (e.kind == .sym_link) continue; try entries.append(alloc, .{ .name = try alloc.dupe(u8, e.name), @@ -198,15 +199,15 @@ fn aliasLowercaseTree(alloc: std.mem.Allocator, dir: std.fs.Dir) !void { for (entries.items) |e| { const lower = try std.ascii.allocLowerString(alloc, e.name); if (!std.mem.eql(u8, lower, e.name)) { - dir.symLink(e.name, lower, .{}) catch |err| switch (err) { + dir.symLink(io, e.name, lower, .{}) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; } if (e.is_dir) { - var sub = try dir.openDir(e.name, .{ .iterate = true }); - defer sub.close(); - try aliasLowercaseTree(alloc, sub); + var sub = try dir.openDir(io, e.name, .{ .iterate = true }); + defer sub.close(io); + try aliasLowercaseTree(alloc, io, sub); } } } @@ -214,10 +215,10 @@ fn aliasLowercaseTree(alloc: std.mem.Allocator, dir: std.fs.Dir) !void { /// The guessed half: `/DEFAULTLIB:` directives live in binary object members, /// so no header scan can see them. Alias the spellings such a directive can /// plausibly hold. -fn aliasLibCaseVariants(alloc: std.mem.Allocator, dir: std.fs.Dir) !void { +fn aliasLibCaseVariants(alloc: std.mem.Allocator, io: std.Io, dir: std.Io.Dir) !void { var names: std.ArrayList([]const u8) = .empty; var it = dir.iterate(); - while (try it.next()) |e| { + while (try it.next(io)) |e| { if (e.kind != .file) continue; try names.append(alloc, try alloc.dupe(u8, e.name)); } @@ -233,7 +234,7 @@ fn aliasLibCaseVariants(alloc: std.mem.Allocator, dir: std.fs.Dir) !void { }; for (variants) |variant| { if (std.mem.eql(u8, variant, name)) continue; - dir.symLink(name, variant, .{}) catch |err| switch (err) { + dir.symLink(io, name, variant, .{}) catch |err| switch (err) { error.PathAlreadyExists => {}, else => return err, }; @@ -251,7 +252,8 @@ fn isHeader(name: []const u8) bool { /// Read every header under `dir` and gather the spellings it references. fn scanTree( alloc: std.mem.Allocator, - dir: std.fs.Dir, + io: std.Io, + dir: std.Io.Dir, includes: *std.StringHashMapUnmanaged(void), libs: *std.StringHashMapUnmanaged(void), ) !void { @@ -262,9 +264,9 @@ fn scanTree( var walker = try dir.walk(alloc); defer walker.deinit(); - while (try walker.next()) |entry| { + while (try walker.next(io)) |entry| { if (entry.kind != .file or !isHeader(entry.basename)) continue; - const text = dir.readFileAlloc(scratch.allocator(), entry.path, 8 << 20) catch continue; + const text = dir.readFileAlloc(io, entry.path, scratch.allocator(), .limited(8 << 20)) catch continue; try scanQuoted(alloc, text, "#include", includes); try scanQuoted(alloc, text, "comment(lib", libs); _ = scratch.reset(.retain_capacity); diff --git a/tools/trim_velopack_lib.zig b/tools/trim_velopack_lib.zig index 18f9f34..0759ed3 100644 --- a/tools/trim_velopack_lib.zig +++ b/tools/trim_velopack_lib.zig @@ -7,15 +7,14 @@ //! Uses `zig ar` (LLVM's archiver, bundled with the Zig toolchain) so no //! separate LLVM install is required. //! -//! Pinned to Zig 0.15.2 std APIs. +//! Pinned to Zig 0.16.0 std APIs. const std = @import("std"); -pub fn main() !void { - var gpa_state: std.heap.GeneralPurposeAllocator(.{}) = .{}; - defer _ = gpa_state.deinit(); - const gpa = gpa_state.allocator(); +pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; + const io = init.io; - var args = try std.process.argsWithAllocator(gpa); + var args = try init.minimal.args.iterateAllocator(gpa); defer args.deinit(); _ = args.next(); // skip exe name @@ -25,17 +24,17 @@ pub fn main() !void { // List archive members via `zig ar t`. const list_argv = [_][]const u8{ zig_exe, "ar", "t", lib_path }; - const listed = try std.process.Child.run(.{ - .allocator = gpa, + const listed = try std.process.run(gpa, io, .{ .argv = &list_argv, - .max_output_bytes = 32 * 1024 * 1024, + .stdout_limit = .limited(32 * 1024 * 1024), + .stderr_limit = .limited(32 * 1024 * 1024), }); defer { gpa.free(listed.stdout); gpa.free(listed.stderr); } switch (listed.term) { - .Exited => |c| if (c != 0) { + .exited => |c| if (c != 0) { std.debug.print("trim-velopack-lib: `zig ar t` failed (code {d})\nstderr: {s}\n", .{ c, listed.stderr }); return error.ListFailed; }, @@ -56,17 +55,17 @@ pub fn main() !void { // Nothing to trim. if (del_argv.items.len == 5) return; - const del = try std.process.Child.run(.{ - .allocator = gpa, + const del = try std.process.run(gpa, io, .{ .argv = del_argv.items, - .max_output_bytes = 1024 * 1024, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), }); defer { gpa.free(del.stdout); gpa.free(del.stderr); } switch (del.term) { - .Exited => |c| if (c != 0) { + .exited => |c| if (c != 0) { std.debug.print("trim-velopack-lib: `zig ar d` failed (code {d})\nstderr: {s}\n", .{ c, del.stderr }); return error.DeleteFailed; }, diff --git a/tools/vpk_setup.zig b/tools/vpk_setup.zig index 4635aac..83eca70 100644 --- a/tools/vpk_setup.zig +++ b/tools/vpk_setup.zig @@ -13,19 +13,18 @@ const std = @import("std"); -pub fn main() !void { - var arena_state: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer arena_state.deinit(); - const arena = arena_state.allocator(); +pub fn main(init: std.process.Init) !void { + const arena = init.arena.allocator(); + const io = init.io; - const args = try std.process.argsAlloc(arena); + const args = try init.minimal.args.toSlice(arena); if (args.len < 3) fatal("usage: vpk_setup [version]", .{}); const tool_dir = args[1]; const may_install = std.mem.eql(u8, args[2], "1"); const wanted: ?[]const u8 = if (args.len > 3 and args[3].len > 0) args[3] else null; - const installed = try installedVersion(arena, tool_dir); + const installed = try installedVersion(arena, io, tool_dir); if (installed) |have| { if (wanted == null or std.mem.eql(u8, have, wanted.?)) return; @@ -36,7 +35,7 @@ pub fn main() !void { \\with the recommended build option that is `zig build -Dinstall-vpk=true`), \\or delete {s} and install vpk there yourself. , .{ tool_dir, have, wanted.?, tool_dir }); - try run(arena, &.{ "dotnet", "tool", "uninstall", "--tool-path", tool_dir, "vpk" }); + try run(arena, io, &.{ "dotnet", "tool", "uninstall", "--tool-path", tool_dir, "vpk" }); } else if (!may_install) { fatal( \\velopack-zig: no vpk found under {s}. @@ -50,20 +49,20 @@ pub fn main() !void { } if (wanted) |v| { - try run(arena, &.{ "dotnet", "tool", "install", "--tool-path", tool_dir, "vpk", "--version", v }); + try run(arena, io, &.{ "dotnet", "tool", "install", "--tool-path", tool_dir, "vpk", "--version", v }); } else { - try run(arena, &.{ "dotnet", "tool", "install", "--tool-path", tool_dir, "vpk" }); + try run(arena, io, &.{ "dotnet", "tool", "install", "--tool-path", tool_dir, "vpk" }); } } /// `dotnet tool list --tool-path ` prints a header, a dashed rule, then /// one ` ` row per tool. A missing directory /// makes it exit non-zero, which reads as "nothing installed". -fn installedVersion(arena: std.mem.Allocator, tool_dir: []const u8) !?[]const u8 { - const res = std.process.Child.run(.{ - .allocator = arena, +fn installedVersion(arena: std.mem.Allocator, io: std.Io, tool_dir: []const u8) !?[]const u8 { + const res = std.process.run(arena, io, .{ .argv = &.{ "dotnet", "tool", "list", "--tool-path", tool_dir }, - .max_output_bytes = 1 << 20, + .stdout_limit = .limited(1 << 20), + .stderr_limit = .limited(1 << 20), }) catch |err| switch (err) { error.FileNotFound => fatal( \\velopack-zig: `dotnet` not found on PATH. @@ -74,7 +73,7 @@ fn installedVersion(arena: std.mem.Allocator, tool_dir: []const u8) !?[]const u8 else => return err, }; switch (res.term) { - .Exited => |code| if (code != 0) return null, + .exited => |code| if (code != 0) return null, else => return null, } @@ -88,13 +87,12 @@ fn installedVersion(arena: std.mem.Allocator, tool_dir: []const u8) !?[]const u8 return null; } -fn run(arena: std.mem.Allocator, argv: []const []const u8) !void { - var child: std.process.Child = .init(argv, arena); - child.stdin_behavior = .Ignore; - const term = try child.spawnAndWait(); +fn run(arena: std.mem.Allocator, io: std.Io, argv: []const []const u8) !void { + var child = try std.process.spawn(io, .{ .argv = argv, .stdin = .ignore }); + const term = try child.wait(io); const cmd = try std.mem.join(arena, " ", argv); switch (term) { - .Exited => |code| if (code != 0) fatal("velopack-zig: `{s}` failed with exit code {d}", .{ cmd, code }), + .exited => |code| if (code != 0) fatal("velopack-zig: `{s}` failed with exit code {d}", .{ cmd, code }), else => fatal("velopack-zig: `{s}` terminated abnormally", .{cmd}), } }