diff --git a/.github/workflows/artifact.yml b/.github/workflows/artifact.yml index a937e26..0d29928 100644 --- a/.github/workflows/artifact.yml +++ b/.github/workflows/artifact.yml @@ -12,19 +12,19 @@ jobs: - uses: actions/checkout@v2 - uses: goto-bus-stop/setup-zig@v1 with: - version: 0.11.0 + version: 0.12.0 - run: | - zig build test -Dfetch -Dci_target=${{matrix.os}}-${{matrix.arch}} + zig build test -Dci_target=${{matrix.os}}-${{matrix.arch}} - run: | - zig build -Dfetch -Dci_target=ubuntu-latest-x86_64 -p zig-out-ubuntu-latest-x86_64 + zig build -Dci_target=ubuntu-latest-x86_64 -p zig-out-ubuntu-latest-x86_64 - run: | - zig build -Dfetch -Dci_target=ubuntu-latest-aarch64 -p zig-out-ubuntu-latest-aarch64 + zig build -Dci_target=ubuntu-latest-aarch64 -p zig-out-ubuntu-latest-aarch64 - run: | - zig build -Dfetch -Dci_target=macos-latest-x86_64 -p zig-out-macos-latest-x86_64 + zig build -Dci_target=macos-latest-x86_64 -p zig-out-macos-latest-x86_64 - run: | - zig build -Dfetch -Dci_target=macos-latest-aarch64 -p zig-out-macos-latest-aarch64 + zig build -Dci_target=macos-latest-aarch64 -p zig-out-macos-latest-aarch64 - run: | - zig build -Dfetch -Dci_target=windows-latest-x86_64 -p zig-out-windows-latest-x86_64 + zig build -Dci_target=windows-latest-x86_64 -p zig-out-windows-latest-x86_64 - uses: actions/upload-artifact@v2 with: name: zigup ${{ matrix.os }}-${{ matrix.arch }} diff --git a/GitRepoStep.zig b/GitRepoStep.zig deleted file mode 100644 index 838405b..0000000 --- a/GitRepoStep.zig +++ /dev/null @@ -1,212 +0,0 @@ -//! Publish Date: 2023_03_19 -//! This file is hosted at github.com/marler8997/zig-build-repos and is meant to be copied -//! to projects that use it. -const std = @import("std"); -const GitRepoStep = @This(); - -pub const ShaCheck = enum { - none, - warn, - err, - - pub fn reportFail(self: ShaCheck, comptime fmt: []const u8, args: anytype) void { - switch (self) { - .none => unreachable, - .warn => std.log.warn(fmt, args), - .err => { - std.log.err(fmt, args); - std.os.exit(0xff); - }, - } - } -}; - -step: std.build.Step, -url: []const u8, -name: []const u8, -branch: ?[]const u8 = null, -sha: []const u8, -path: []const u8, -sha_check: ShaCheck = .warn, -fetch_enabled: bool, - -var cached_default_fetch_option: ?bool = null; -pub fn defaultFetchOption(b: *std.build.Builder) bool { - if (cached_default_fetch_option) |_| {} else { - cached_default_fetch_option = if (b.option(bool, "fetch", "automatically fetch network resources")) |o| o else false; - } - return cached_default_fetch_option.?; -} - -pub fn create(b: *std.build.Builder, opt: struct { - url: []const u8, - branch: ?[]const u8 = null, - sha: []const u8, - path: ?[]const u8 = null, - sha_check: ShaCheck = .warn, - fetch_enabled: ?bool = null, - first_ret_addr: ?usize = null, -}) *GitRepoStep { - var result = b.allocator.create(GitRepoStep) catch @panic("memory"); - const name = std.fs.path.basename(opt.url); - result.* = GitRepoStep{ - .step = std.build.Step.init(.{ - .id = .custom, - .name = b.fmt("clone git repository '{s}'", .{name}), - .owner = b, - .makeFn = make, - .first_ret_addr = opt.first_ret_addr orelse @returnAddress(), - .max_rss = 0, - }), - .url = opt.url, - .name = name, - .branch = opt.branch, - .sha = opt.sha, - .path = if (opt.path) |p| b.allocator.dupe(u8, p) catch @panic("OOM") else b.pathFromRoot(b.pathJoin(&.{ "dep", name })), - .sha_check = opt.sha_check, - .fetch_enabled = if (opt.fetch_enabled) |fe| fe else defaultFetchOption(b), - }; - return result; -} - -// TODO: this should be included in std.build, it helps find bugs in build files -fn hasDependency(step: *const std.build.Step, dep_candidate: *const std.build.Step) bool { - for (step.dependencies.items) |dep| { - // TODO: should probably use step.loop_flag to prevent infinite recursion - // when a circular reference is encountered, or maybe keep track of - // the steps encounterd with a hash set - if (dep == dep_candidate or hasDependency(dep, dep_candidate)) - return true; - } - return false; -} - -fn make(step: *std.Build.Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(GitRepoStep, "step", step); - - std.fs.accessAbsolute(self.path, .{}) catch { - const branch_args = if (self.branch) |b| &[2][]const u8{ " -b ", b } else &[2][]const u8{ "", "" }; - if (!self.fetch_enabled) { - std.debug.print("Error: git repository '{s}' does not exist\n", .{self.path}); - std.debug.print(" Use -Dfetch to download it automatically, or run the following to clone it:\n", .{}); - std.debug.print(" git clone {s}{s}{s} {s} && git -C {3s} checkout {s} -b fordep\n", .{ - self.url, - branch_args[0], - branch_args[1], - self.path, - self.sha, - }); - std.os.exit(1); - } - - { - var args = std.ArrayList([]const u8).init(self.step.owner.allocator); - defer args.deinit(); - try args.append("git"); - try args.append("clone"); - try args.append(self.url); - // TODO: clone it to a temporary location in case of failure - // also, remove that temporary location before running - try args.append(self.path); - if (self.branch) |branch| { - try args.append("-b"); - try args.append(branch); - } - try run(self.step.owner, args.items); - } - try run(self.step.owner, &[_][]const u8{ - "git", - "-C", - self.path, - "checkout", - self.sha, - "-b", - "fordep", - }); - }; - - try self.checkSha(); -} - -fn checkSha(self: GitRepoStep) !void { - if (self.sha_check == .none) - return; - - const result: union(enum) { failed: anyerror, output: []const u8 } = blk: { - const result = std.ChildProcess.exec(.{ - .allocator = self.step.owner.allocator, - .argv = &[_][]const u8{ - "git", - "-C", - self.path, - "rev-parse", - "HEAD", - }, - .cwd = self.step.owner.build_root.path, - .env_map = self.step.owner.env_map, - }) catch |e| break :blk .{ .failed = e }; - try std.io.getStdErr().writer().writeAll(result.stderr); - switch (result.term) { - .Exited => |code| { - if (code == 0) break :blk .{ .output = result.stdout }; - break :blk .{ .failed = error.GitProcessNonZeroExit }; - }, - .Signal => break :blk .{ .failed = error.GitProcessFailedWithSignal }, - .Stopped => break :blk .{ .failed = error.GitProcessWasStopped }, - .Unknown => break :blk .{ .failed = error.GitProcessFailed }, - } - }; - switch (result) { - .failed => |err| { - return self.sha_check.reportFail("failed to retreive sha for repository '{s}': {s}", .{ self.name, @errorName(err) }); - }, - .output => |output| { - if (!std.mem.eql(u8, std.mem.trimRight(u8, output, "\n\r"), self.sha)) { - return self.sha_check.reportFail("repository '{s}' sha does not match\nexpected: {s}\nactual : {s}\n", .{ self.name, self.sha, output }); - } - }, - } -} - -fn run(builder: *std.build.Builder, argv: []const []const u8) !void { - { - var msg = std.ArrayList(u8).init(builder.allocator); - defer msg.deinit(); - const writer = msg.writer(); - var prefix: []const u8 = ""; - for (argv) |arg| { - try writer.print("{s}\"{s}\"", .{ prefix, arg }); - prefix = " "; - } - std.log.info("[RUN] {s}", .{msg.items}); - } - - var child = std.ChildProcess.init(argv, builder.allocator); - child.stdin_behavior = .Ignore; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; - child.cwd = builder.build_root.path; - child.env_map = builder.env_map; - - try child.spawn(); - const result = try child.wait(); - switch (result) { - .Exited => |code| if (code != 0) { - std.log.err("git clone failed with exit code {}", .{code}); - std.os.exit(0xff); - }, - else => { - std.log.err("git clone failed with: {}", .{result}); - std.os.exit(0xff); - }, - } -} - -// Get's the repository path and also verifies that the step requesting the path -// is dependent on this step. -pub fn getPath(self: *const GitRepoStep, who_wants_to_know: *const std.build.Step) []const u8 { - if (!hasDependency(who_wants_to_know, &self.step)) - @panic("a step called GitRepoStep.getPath but has not added it as a dependency"); - return self.path; -} diff --git a/README.md b/README.md index 60ce67d..cb11610 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,6 @@ Zigup is currently built/tested using zig 0.11.0. # Dependencies -zigup depends on https://github.com/marler8997/ziget which in turn depends on other projects depending on which SSL backend is selected. You can provide `-Dfetch` to `zig build` to automatically clone all repository dependencies, otherwise, the build will report a missing dependency error with an explanation of how to clone it. - The windows target depends on https://github.com/SuperAuguste/zarc to extract zip files. This repo might point to my fork if there are needed changes pending the acceptance of a PR: https://github.com/marler8997/zarc. On linux and macos, zigup depends on `tar` to extract the compiler archive files (this may change in the future). diff --git a/build.zig b/build.zig index 48a4c3d..6c0c1f8 100644 --- a/build.zig +++ b/build.zig @@ -1,66 +1,128 @@ -//! This build.zig file boostraps the real build in build2.zig - -// NOTE: need to wait on https://github.com/ziglang/zig/pull/9989 before doing this -// to make build errors reasonable const std = @import("std"); -const Builder = std.build.Builder; - -const GitRepoStep = @import("GitRepoStep.zig"); +const builtin = @import("builtin"); +const Pkg = std.build.Pkg; -pub fn build(b: *Builder) !void { - buildNoreturn(b); +fn unwrapOptionalBool(optionalBool: ?bool) bool { + if (optionalBool) |b| return b; + return false; } -fn buildNoreturn(b: *Builder) noreturn { - const err = buildOrFail(b); - std.log.err("{s}", .{@errorName(err)}); - if (@errorReturnTrace()) |trace| { - std.debug.dumpStackTrace(trace.*); + +pub fn build(b: *std.Build) !void { + //var github_release_step = b.step("github-release", "Build the github-release binaries"); + //try addGithubReleaseExe(b, github_release_step, ziget_repo, "x86_64-linux", .std); + + const target = if (b.option([]const u8, "ci_target", "the CI target being built")) |ci_target| + b.resolveTargetQuery(try std.zig.CrossTarget.parse(.{ .arch_os_abi = ci_target_map.get(ci_target) orelse { + std.log.err("unknown ci_target '{s}'", .{ci_target}); + std.process.exit(1); + } })) + else + b.standardTargetOptions(.{}); + + const optimize = b.standardOptimizeOption(.{}); + + const win32exelink_mod: ?*std.Build.Module = blk: { + if (target.result.os.tag == .windows) { + const exe = b.addExecutable(.{ + .name = "win32exelink", + .root_source_file = .{ .path = "win32exelink.zig" }, + .target = target, + .optimize = optimize, + }); + break :blk b.createModule(.{ + .root_source_file = exe.getEmittedBin(), + }); + } + break :blk null; + }; + + const exe = try addZigupExe( + b, + target, + optimize, + win32exelink_mod, + ); + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + if (b.args) |args| { + run_cmd.addArgs(args); } - std.os.exit(0xff); + + addTest(b, exe, target, optimize); } -fn buildOrFail(b: *Builder) anyerror { - const ziget_repo = GitRepoStep.create(b, .{ - .url = "https://github.com/marler8997/ziget", - .branch = null, - .sha = @embedFile("zigetsha"), - .fetch_enabled = true, + +fn addTest( + b: *std.Build, + exe: *std.Build.Step.Compile, + target: std.Build.ResolvedTarget, + optimize: std.builtin.Mode, +) void { + const test_exe = b.addExecutable(.{ + .name = "test", + .root_source_file = .{ .path = "test.zig" }, + .target = target, + .optimize = optimize, }); - const build2 = addBuild(b, .{ .path = "build2.zig" }, .{}); - build2.addArgs(try getBuildArgs(b)); - - var progress = std.Progress{}; - { - var prog_node = progress.start("clone ziget", 1); - ziget_repo.step.make(prog_node) catch |e| return e; - prog_node.end(); - } - { - var prog_node = progress.start("run build2.zig", 1); - build2.step.make(prog_node) catch |err| switch (err) { - error.MakeFailed => std.os.exit(0xff), // error already printed by subprocess, hopefully? - error.MakeSkipped => @panic("impossible?"), - }; - prog_node.end(); - } - std.os.exit(0); + const run_cmd = b.addRunArtifact(test_exe); + + // TODO: make this work, add exe install path as argument to test + //run_cmd.addArg(exe.getInstallPath()); + _ = exe; + run_cmd.step.dependOn(b.getInstallStep()); + + const test_step = b.step("test", "test the executable"); + test_step.dependOn(&run_cmd.step); } -// TODO: remove the following if https://github.com/ziglang/zig/pull/9987 is integrated -fn getBuildArgs(self: *Builder) ![]const [:0]const u8 { - const args = try std.process.argsAlloc(self.allocator); - return args[5..]; +fn addZigupExe( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.Mode, + win32exelink_mod: ?*std.Build.Module, +) !*std.Build.Step.Compile { + const exe = b.addExecutable(.{ + .name = "zigup", + .root_source_file = .{ .path = "zigup.zig" }, + .target = target, + .optimize = optimize, + }); + + if (target.result.os.tag == .windows) { + exe.root_module.addImport("win32exelink", win32exelink_mod.?); + if (b.lazyDependency("zarc", .{})) |zarc_dep| { + exe.root_module.addImport("zarc", zarc_dep.module("zarc")); + } + } + + return exe; } -pub fn addBuild(self: *Builder, build_file: std.build.FileSource, _: struct {}) *std.build.RunStep { - const run_step = std.build.RunStep.create( - self, - self.fmt("zig build {s}", .{build_file.getDisplayName()}), - ); - run_step.addArg(self.zig_exe); - run_step.addArg("build"); - run_step.addArg("--build-file"); - run_step.addFileSourceArg(build_file); - run_step.addArg("--cache-dir"); - const cache_root_path = self.cache_root.path orelse @panic("todo"); - run_step.addArg(self.pathFromRoot(cache_root_path)); - return run_step; + +fn addGithubReleaseExe( + b: *std.Build, + github_release_step: *std.build.Step, + comptime target_triple: []const u8, +) !void { + const small_release = true; + + const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = target_triple }); + const mode = if (small_release) .ReleaseSafe else .Debug; + const exe = try addZigupExe(b, target, mode); + if (small_release) { + exe.strip = true; + } + exe.setOutputDir("github-release" ++ std.fs.path.sep_str ++ target_triple); + github_release_step.dependOn(&exe.step); } + +const ci_target_map = std.ComptimeStringMap([]const u8, .{ + .{ "ubuntu-latest-x86_64", "x86_64-linux" }, + .{ "macos-latest-x86_64", "x86_64-macos" }, + .{ "windows-latest-x86_64", "x86_64-windows" }, + .{ "ubuntu-latest-aarch64", "aarch64-linux" }, + .{ "macos-latest-aarch64", "aarch64-macos" }, +}); diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..800c983 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,23 @@ +.{ + .name = "zigup", + .version = "0.0.1", + + .dependencies = .{ + .zarc = .{ + .url = "https://github.com/marler8997/zarc/archive/6ed370681d0e0c5d8ef32ad63e440d0b3effc8a8.tar.gz", + .hash = "1220a69ceaac42e5a3593161c3b4ab1ce7dbc574b1a80398a7cb3100fbfa07baf372", + .lazy = true, + }, + }, + + .paths = .{ + "LICENSE", + "README.md", + "build.zig", + "build.zig.zon", + "fixdeletetree.zig", + "test.zig", + "win32exelink.zig", + "zigup.zig", + }, +} diff --git a/build2.zig b/build2.zig deleted file mode 100644 index ec15db9..0000000 --- a/build2.zig +++ /dev/null @@ -1,213 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const Builder = std.build.Builder; -const Pkg = std.build.Pkg; - -// TODO: make this work with "GitRepoStep.zig", there is a -// problem with the -Dfetch option -const GitRepoStep = @import("dep/ziget/GitRepoStep.zig"); - -const zigetbuild = @import("dep/ziget/build.zig"); -const SslBackend = zigetbuild.SslBackend; - -fn unwrapOptionalBool(optionalBool: ?bool) bool { - if (optionalBool) |b| return b; - return false; -} - -pub fn build(b: *Builder) !void { - const ziget_repo = GitRepoStep.create(b, .{ - .url = "https://github.com/marler8997/ziget", - .branch = null, - .sha = @embedFile("zigetsha"), - }); - - // TODO: implement this if/when we get @tryImport - //if (zigetbuild) |_| { } else { - // std.log.err("TODO: add zigetbuild package and recompile/reinvoke build.d", .{}); - // return; - //} - - //var github_release_step = b.step("github-release", "Build the github-release binaries"); - //try addGithubReleaseExe(b, github_release_step, ziget_repo, "x86_64-linux", .std); - - const target = if (b.option([]const u8, "ci_target", "the CI target being built")) |ci_target| - try std.zig.CrossTarget.parse(.{ .arch_os_abi = ci_target_map.get(ci_target) orelse { - std.log.err("unknown ci_target '{s}'", .{ci_target}); - std.os.exit(1); - } }) - else - b.standardTargetOptions(.{}); - - const optimize = b.standardOptimizeOption(.{}); - - const win32exelink_mod: ?*std.Build.Module = blk: { - if (target.getOs().tag == .windows) { - const exe = b.addExecutable(.{ - .name = "win32exelink", - .root_source_file = .{ .path = "win32exelink.zig" }, - .target = target, - .optimize = optimize, - }); - break :blk b.createModule(.{ - .source_file = exe.getEmittedBin(), - }); - } - break :blk null; - }; - - // TODO: Maybe add more executables with different ssl backends - const exe = try addZigupExe( - b, - ziget_repo, - target, - optimize, - win32exelink_mod, - .iguana, - ); - b.installArtifact(exe); - - const run_cmd = b.addRunArtifact(exe); - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - if (b.args) |args| { - run_cmd.addArgs(args); - } - - addTest(b, exe, target, optimize); -} - -fn addTest(b: *Builder, exe: *std.build.LibExeObjStep, target: std.zig.CrossTarget, optimize: std.builtin.Mode) void { - const test_exe = b.addExecutable(.{ - .name = "test", - .root_source_file = .{ .path = "test.zig" }, - .target = target, - .optimize = optimize, - }); - const run_cmd = b.addRunArtifact(test_exe); - - // TODO: make this work, add exe install path as argument to test - //run_cmd.addArg(exe.getInstallPath()); - _ = exe; - run_cmd.step.dependOn(b.getInstallStep()); - - const test_step = b.step("test", "test the executable"); - test_step.dependOn(&run_cmd.step); -} - -fn addZigupExe( - b: *Builder, - ziget_repo: *GitRepoStep, - target: std.zig.CrossTarget, - optimize: std.builtin.Mode, - win32exelink_mod: ?*std.build.Module, - ssl_backend: ?SslBackend, -) !*std.build.LibExeObjStep { - const require_ssl_backend = b.allocator.create(RequireSslBackendStep) catch unreachable; - require_ssl_backend.* = RequireSslBackendStep.init(b, "the zigup exe", ssl_backend); - - const exe = b.addExecutable(.{ - .name = "zigup", - .root_source_file = .{ .path = "zigup.zig" }, - .target = target, - .optimize = optimize, - }); - - exe.step.dependOn(&ziget_repo.step); - zigetbuild.addZigetModule(exe, ssl_backend, ziget_repo.getPath(&exe.step)); - - if (targetIsWindows(target)) { - exe.addModule("win32exelink", win32exelink_mod.?); - const zarc_repo = GitRepoStep.create(b, .{ - .url = "https://github.com/marler8997/zarc", - .branch = "protected", - .sha = "2e5256624d7871180badc9784b96dd66d927d604", - .fetch_enabled = true, - }); - exe.step.dependOn(&zarc_repo.step); - const zarc_repo_path = zarc_repo.getPath(&exe.step); - const zarc_mod = b.addModule("zarc", .{ - .source_file = .{ .path = b.pathJoin(&.{ zarc_repo_path, "src", "main.zig" }) }, - }); - exe.addModule("zarc", zarc_mod); - } - - exe.step.dependOn(&require_ssl_backend.step); - return exe; -} - -fn targetIsWindows(target: std.zig.CrossTarget) bool { - if (target.os_tag) |tag| - return tag == .windows; - return builtin.target.os.tag == .windows; -} - -const SslBackendFailedStep = struct { - step: std.build.Step, - context: []const u8, - backend: SslBackend, - pub fn init(b: *Builder, context: []const u8, backend: SslBackend) SslBackendFailedStep { - return .{ - .step = std.build.Step.init(.custom, "SslBackendFailedStep", b.allocator, make), - .context = context, - .backend = backend, - }; - } - fn make(step: *std.build.Step) !void { - const self = @fieldParentPtr(RequireSslBackendStep, "step", step); - std.debug.print("error: the {s} failed to add the {s} SSL backend\n", .{ self.context, self.backend }); - std.os.exit(1); - } -}; - -const RequireSslBackendStep = struct { - step: std.build.Step, - context: []const u8, - backend: ?SslBackend, - pub fn init(b: *Builder, context: []const u8, backend: ?SslBackend) RequireSslBackendStep { - return .{ - .step = std.build.Step.init(.{ - .id = .custom, - .name = "RequireSslBackend", - .owner = b, - .makeFn = make, - }), - .context = context, - .backend = backend, - }; - } - fn make(step: *std.build.Step, prog_node: *std.Progress.Node) !void { - _ = prog_node; - const self = @fieldParentPtr(RequireSslBackendStep, "step", step); - if (self.backend) |_| {} else { - std.debug.print("error: {s} requires an SSL backend:\n", .{self.context}); - inline for (zigetbuild.ssl_backends) |field| { - std.debug.print(" -D{s}\n", .{field.name}); - } - std.os.exit(1); - } - } -}; - -fn addGithubReleaseExe(b: *Builder, github_release_step: *std.build.Step, ziget_repo: []const u8, comptime target_triple: []const u8, comptime ssl_backend: SslBackend) !void { - const small_release = true; - - const target = try std.zig.CrossTarget.parse(.{ .arch_os_abi = target_triple }); - const mode = if (small_release) .ReleaseSafe else .Debug; - const exe = try addZigupExe(b, ziget_repo, target, mode, ssl_backend); - if (small_release) { - exe.strip = true; - } - exe.setOutputDir("github-release" ++ std.fs.path.sep_str ++ target_triple ++ std.fs.path.sep_str ++ @tagName(ssl_backend)); - github_release_step.dependOn(&exe.step); -} - -const ci_target_map = std.ComptimeStringMap([]const u8, .{ - .{ "ubuntu-latest-x86_64", "x86_64-linux" }, - .{ "macos-latest-x86_64", "x86_64-macos" }, - .{ "windows-latest-x86_64", "x86_64-windows" }, - .{ "ubuntu-latest-aarch64", "aarch64-linux" }, - .{ "macos-latest-aarch64", "aarch64-macos" }, -}); diff --git a/test.zig b/test.zig index 2e9110e..dc4eba4 100644 --- a/test.zig +++ b/test.zig @@ -265,7 +265,8 @@ pub fn main() !u8 { allocator.free(result.stdout); allocator.free(result.stderr); } - try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "HTTP request failed")); + try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "download")); + try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "failed")); } try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir)); @@ -310,7 +311,7 @@ pub fn main() !u8 { // NOTE: this test will eventually break when these builds are cleaned up, // we should support downloading from bazel and use that instead since // it should be more permanent - try runNoCapture(zigup_args ++ &[_][]const u8{"0.11.0-dev.4263+f821543e4"}); + try runNoCapture(zigup_args ++ &[_][]const u8{ "0.12.0-dev.3639+9cfac4718" }); std.log.info("Success", .{}); return 0; @@ -338,7 +339,7 @@ fn checkZigVersion(allocator: std.mem.Allocator, zig: []const u8, compare: []con } fn getCompilerCount(install_dir: []const u8) !u32 { - var dir = try std.fs.cwd().openIterableDir(install_dir, .{}); + var dir = try std.fs.cwd().openDir(install_dir, .{ .iterate = true }); defer dir.close(); var it = dir.iterate(); var count: u32 = 0; @@ -360,7 +361,7 @@ fn trailNl(s: []const u8) []const u8 { return if (s.len == 0 or s[s.len - 1] != '\n') "\n" else ""; } -fn dumpExecResult(result: std.ChildProcess.ExecResult) void { +fn dumpExecResult(result: std.ChildProcess.RunResult) void { if (result.stdout.len > 0) { std.debug.print("--- STDOUT ---\n{s}{s}--------------\n", .{ result.stdout, trailNl(result.stdout) }); } @@ -376,13 +377,13 @@ fn runNoCapture(argv: []const []const u8) !void { dumpExecResult(result); try passOrThrow(result.term); } -fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.ChildProcess.ExecResult { +fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.ChildProcess.RunResult { { const cmd = try std.mem.join(allocator, " ", argv); defer allocator.free(cmd); std.log.info("RUN: {s}", .{cmd}); } - return try std.ChildProcess.exec(.{ .allocator = allocator, .argv = argv, .env_map = &child_env_map }); + return try std.ChildProcess.run(.{ .allocator = allocator, .argv = argv, .env_map = &child_env_map }); } fn passOrThrow(term: std.ChildProcess.Term) error{ChildProcessFailed}!void { if (!execResultPassed(term)) { @@ -390,7 +391,7 @@ fn passOrThrow(term: std.ChildProcess.Term) error{ChildProcessFailed}!void { return error.ChildProcessFailed; } } -fn passOrDumpAndThrow(result: std.ChildProcess.ExecResult) error{ChildProcessFailed}!void { +fn passOrDumpAndThrow(result: std.ChildProcess.RunResult) error{ChildProcessFailed}!void { if (!execResultPassed(result.term)) { dumpExecResult(result); std.log.err("child process failed with {}", .{result.term}); diff --git a/win32exelink.zig b/win32exelink.zig index 862e258..e960c22 100644 --- a/win32exelink.zig +++ b/win32exelink.zig @@ -75,14 +75,14 @@ fn consoleCtrlHandler(ctrl_type: u32) callconv(@import("std").os.windows.WINAPI) log.info("caught ctrl signal {d} ({s}), stopping process...", .{ ctrl_type, name }); const exit_code = switch (global.child.kill() catch |err| { log.err("failed to kill process, error={s}", .{@errorName(err)}); - std.os.exit(0xff); + std.process.exit(0xff); }) { .Exited => |e| e, .Signal => 0xff, .Stopped => 0xff, .Unknown => 0xff, }; - std.os.exit(exit_code); + std.process.exit(exit_code); unreachable; } diff --git a/zigetsha b/zigetsha deleted file mode 100644 index 9a861fa..0000000 --- a/zigetsha +++ /dev/null @@ -1 +0,0 @@ -9020437522f092860c513b1e96dcc250c7dea0aa \ No newline at end of file diff --git a/zigup.zig b/zigup.zig index c863182..0dcf360 100644 --- a/zigup.zig +++ b/zigup.zig @@ -5,7 +5,6 @@ const mem = std.mem; const ArrayList = std.ArrayList; const Allocator = mem.Allocator; -const ziget = @import("ziget"); const zarc = @import("zarc"); const fixdeletetree = @import("fixdeletetree.zig"); @@ -36,36 +35,82 @@ fn loginfo(comptime fmt: []const u8, args: anytype) void { } } -fn download(allocator: Allocator, url: []const u8, writer: anytype) !void { - var download_options = ziget.request.DownloadOptions{ - .flags = 0, - .allocator = allocator, - .maxRedirects = 10, - .forwardBufferSize = 4096, - .maxHttpResponseHeaders = 8192, - .onHttpRequest = ignoreHttpCallback, - .onHttpResponse = ignoreHttpCallback, - }; - var dowload_state = ziget.request.DownloadState.init(); - try ziget.request.download( - ziget.url.parseUrl(url) catch unreachable, - writer, - download_options, - &dowload_state, - ); +pub fn oom(e: error{OutOfMemory}) noreturn { + @panic(@errorName(e)); } -fn downloadToFileAbsolute(allocator: Allocator, url: []const u8, file_absolute: []const u8) !void { - const file = try std.fs.createFileAbsolute(file_absolute, .{}); - defer file.close(); - try download(allocator, url, file.writer()); +const DownloadResult = union(enum) { + ok: void, + err: []u8, + pub fn deinit(self: DownloadResult, allocator: Allocator) void { + switch (self) { + .ok => {}, + .err => |e| allocator.free(e), + } + } +}; +fn download(allocator: Allocator, url: []const u8, writer: anytype) DownloadResult { + const uri = std.Uri.parse(url) catch |err| std.debug.panic( + "failed to parse url '{s}' with {s}", .{url, @errorName(err)} + ); + + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + client.initDefaultProxies(allocator) catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to query the HTTP proxy settings with {s}", .{ @errorName(err) } + ) catch |e| oom(e) }; + + var header_buffer: [4096]u8 = undefined; + var request = client.open(.GET, uri, .{ + .server_header_buffer = &header_buffer, + .keep_alive = false, + }) catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to connect to the HTTP server with {s}", .{ @errorName(err) } + ) catch |e| oom(e) }; + + defer request.deinit(); + + request.send() catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to send the HTTP request with {s}", .{ @errorName(err) } + ) catch |e| oom(e) }; + request.wait() catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to read the HTTP response headers with {s}", .{ @errorName(err) } + ) catch |e| oom(e) }; + + if (request.response.status != .ok) return .{ .err = std.fmt.allocPrint( + allocator, + "HTTP server replied with unsuccessful response '{d} {s}'", + .{ @intFromEnum(request.response.status), request.response.status.phrase() orelse "" }, + ) catch |e| oom(e) }; + + // TODO: we take advantage of request.response.content_length + + var buf: [std.mem.page_size]u8 = undefined; + while (true) { + const len = request.reader().read(&buf) catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to read the HTTP response body with {s}'", .{ @errorName(err) } + ) catch |e| oom(e) }; + if (len == 0) + return .ok; + writer.writeAll(buf[0..len]) catch |err| return .{ .err = std.fmt.allocPrint( + allocator, "failed to write the HTTP response body with {s}'", .{ @errorName(err) } + ) catch |e| oom(e) }; + } } -fn downloadToString(allocator: Allocator, url: []const u8) ![]u8 { - var response_array_list = try ArrayList(u8).initCapacity(allocator, 20 * 1024); // 20 KB (modify if response is expected to be bigger) - errdefer response_array_list.deinit(); - try download(allocator, url, response_array_list.writer()); - return response_array_list.toOwnedSlice(); +const DownloadStringResult = union(enum) { + ok: []u8, + err: []u8, + +}; +fn downloadToString(allocator: Allocator, url: []const u8) DownloadStringResult { + var response_array_list = ArrayList(u8).initCapacity(allocator, 20 * 1024) catch |e| oom(e); // 20 KB (modify if response is expected to be bigger) + defer response_array_list.deinit(); + switch (download(allocator, url, response_array_list.writer())) { + .ok => return .{ .ok = response_array_list.toOwnedSlice() catch |e| oom(e) }, + .err => |e| return .{ .err = e }, + } } fn ignoreHttpCallback(request: []const u8) void { @@ -73,7 +118,7 @@ fn ignoreHttpCallback(request: []const u8) void { } fn getHomeDir() ![]const u8 { - return std.os.getenv("HOME") orelse { + return std.posix.getenv("HOME") orelse { std.log.err("cannot find install directory, $HOME environment variable is not set", .{}); return error.MissingHomeEnvironmentVariable; }; @@ -282,13 +327,13 @@ pub fn main2() !u8 { if (!std.mem.eql(u8, version_string, "master")) break :init_resolved version_string; - var optional_master_dir: ?[]const u8 = blk: { - var install_dir = std.fs.openIterableDirAbsolute(install_dir_string, .{}) catch |e| switch (e) { + const optional_master_dir: ?[]const u8 = blk: { + var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) { error.FileNotFound => break :blk null, else => return e, }; defer install_dir.close(); - break :blk try getMasterDir(allocator, &install_dir.dir); + break :blk try getMasterDir(allocator, &install_dir); }; // no need to free master_dir, this is a short lived program break :init_resolved optional_master_dir orelse { @@ -408,10 +453,11 @@ const DownloadIndex = struct { }; fn fetchDownloadIndex(allocator: Allocator) !DownloadIndex { - const text = downloadToString(allocator, download_index_url) catch |e| switch (e) { - else => { - std.log.err("failed to download '{s}': {}", .{ download_index_url, e }); - return e; + const text = switch (downloadToString(allocator, download_index_url)) { + .ok => |text| text, + .err => |err| { + std.log.err("download '{s}' failed: {s}", .{download_index_url, err}); + return error.AlreadyReported; }, }; errdefer allocator.free(text); @@ -443,24 +489,24 @@ pub fn loggyRenameAbsolute(old_path: []const u8, new_path: []const u8) !void { try std.fs.renameAbsolute(old_path, new_path); } -pub fn loggySymlinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.SymLinkFlags) !void { +pub fn loggySymlinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.Dir.SymLinkFlags) !void { loginfo("ln -s '{s}' '{s}'", .{ target_path, sym_link_path }); // NOTE: can't use symLinkAbsolute because it requires target_path to be absolute but we don't want that // not sure if it is a bug in the standard lib or not //try std.fs.symLinkAbsolute(target_path, sym_link_path, flags); _ = flags; - try std.os.symlink(target_path, sym_link_path); + try std.posix.symlink(target_path, sym_link_path); } /// returns: true if the symlink was updated, false if it was already set to the given `target_path` -pub fn loggyUpdateSymlink(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.SymLinkFlags) !bool { +pub fn loggyUpdateSymlink(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.Dir.SymLinkFlags) !bool { var current_target_path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; if (std.fs.readLinkAbsolute(sym_link_path, ¤t_target_path_buffer)) |current_target_path| { if (std.mem.eql(u8, target_path, current_target_path)) { loginfo("symlink '{s}' already points to '{s}'", .{ sym_link_path, target_path }); return false; // already up-to-date } - try std.os.unlink(sym_link_path); + try std.posix.unlink(sym_link_path); } else |e| switch (e) { error.FileNotFound => {}, error.NotLink => { @@ -468,7 +514,7 @@ pub fn loggyUpdateSymlink(target_path: []const u8, sym_link_path: []const u8, fl "unable to update/overwrite the 'zig' PATH symlink, the file '{s}' already exists and is not a symlink\n", .{ sym_link_path}, ); - std.os.exit(1); + std.process.exit(1); }, else => return e, } @@ -486,7 +532,8 @@ fn existsAbsolute(absolutePath: []const u8) !bool { error.SymLinkLoop => return e, error.FileBusy => return e, error.Unexpected => unreachable, - error.InvalidUtf8 => unreachable, + error.InvalidUtf8 => return e, + error.InvalidWtf8 => return e, error.ReadOnlyFileSystem => unreachable, error.NameTooLong => unreachable, error.BadPathName => unreachable, @@ -498,7 +545,7 @@ fn listCompilers(allocator: Allocator) !void { const install_dir_string = try getInstallDir(allocator, .{ .create = false }); defer allocator.free(install_dir_string); - var install_dir = std.fs.openIterableDirAbsolute(install_dir_string, .{}) catch |e| switch (e) { + var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) { error.FileNotFound => return, else => return e, }; @@ -521,10 +568,10 @@ fn keepCompiler(allocator: Allocator, compiler_version: []const u8) !void { const install_dir_string = try getInstallDir(allocator, .{ .create = true }); defer allocator.free(install_dir_string); - var install_dir = try std.fs.openIterableDirAbsolute(install_dir_string, .{}); + var install_dir = try std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }); defer install_dir.close(); - var compiler_dir = install_dir.dir.openDir(compiler_version, .{}) catch |e| switch (e) { + var compiler_dir = install_dir.openDir(compiler_version, .{}) catch |e| switch (e) { error.FileNotFound => { std.log.err("compiler not found: {s}", .{compiler_version}); return error.AlreadyReported; @@ -543,12 +590,12 @@ fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void { const default_comp_opt = try getDefaultCompiler(allocator); defer if (default_comp_opt) |default_compiler| allocator.free(default_compiler); - var install_dir = std.fs.openIterableDirAbsolute(install_dir_string, .{}) catch |e| switch (e) { + var install_dir = std.fs.openDirAbsolute(install_dir_string, .{ .iterate = true }) catch |e| switch (e) { error.FileNotFound => return, else => return e, }; defer install_dir.close(); - const master_points_to_opt = try getMasterDir(allocator, &install_dir.dir); + const master_points_to_opt = try getMasterDir(allocator, &install_dir); defer if (master_points_to_opt) |master_points_to| allocator.free(master_points_to); if (compiler_name_opt) |compiler_name| { if (getKeepReason(master_points_to_opt, default_comp_opt, compiler_name)) |reason| { @@ -556,7 +603,7 @@ fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void { return error.AlreadyReported; } loginfo("deleting '{s}{c}{s}'", .{ install_dir_string, std.fs.path.sep, compiler_name }); - try fixdeletetree.deleteTree(install_dir.dir, compiler_name); + try fixdeletetree.deleteTree(install_dir, compiler_name); } else { var it = install_dir.iterate(); while (try it.next()) |entry| { @@ -568,7 +615,7 @@ fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void { } { - var compiler_dir = try install_dir.dir.openDir(entry.name, .{}); + var compiler_dir = try install_dir.openDir(entry.name, .{}); defer compiler_dir.close(); if (compiler_dir.access("keep", .{})) |_| { loginfo("keeping '{s}' (has keep file)", .{entry.name}); @@ -579,7 +626,7 @@ fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void { } } loginfo("deleting '{s}{c}{s}'", .{ install_dir_string, std.fs.path.sep, entry.name }); - try fixdeletetree.deleteTree(install_dir.dir, entry.name); + try fixdeletetree.deleteTree(install_dir, entry.name); } } } @@ -632,16 +679,16 @@ fn readMasterDir(buffer: *[std.fs.MAX_PATH_BYTES]u8, install_dir: *std.fs.Dir) ! fn getDefaultCompiler(allocator: Allocator) !?[]const u8 { var buffer: [std.fs.MAX_PATH_BYTES + 1]u8 = undefined; const slice_path = (try readDefaultCompiler(allocator, &buffer)) orelse return null; - var path_to_return = try allocator.alloc(u8, slice_path.len); - std.mem.copy(u8, path_to_return, slice_path); + const path_to_return = try allocator.alloc(u8, slice_path.len); + @memcpy(path_to_return, slice_path); return path_to_return; } fn getMasterDir(allocator: Allocator, install_dir: *std.fs.Dir) !?[]const u8 { var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; const slice_path = (try readMasterDir(&buffer, install_dir)) orelse return null; - var path_to_return = try allocator.alloc(u8, slice_path.len); - std.mem.copy(u8, path_to_return, slice_path); + const path_to_return = try allocator.alloc(u8, slice_path.len); + @memcpy(path_to_return, slice_path); return path_to_return; } @@ -754,7 +801,7 @@ fn verifyPathLink(allocator: Allocator, path_link: []const u8) !void { } } } else { - var path_it = std.mem.tokenize(u8, std.os.getenv("PATH") orelse "", ":"); + var path_it = std.mem.tokenize(u8, std.posix.getenv("PATH") orelse "", ":"); while (path_it.next()) |path| { switch (try compareDir(path_link_dir_id, path)) { .missing => continue, @@ -798,11 +845,11 @@ fn enforceNoZig(path_link: []const u8, exe: []const u8) !void { const FileId = struct { dev: if (builtin.os.tag == .windows) u32 else blk: { - var st: std.os.Stat = undefined; + const st: std.posix.Stat = undefined; break :blk @TypeOf(st.dev); }, ino: if (builtin.os.tag == .windows) u64 else blk: { - var st: std.os.Stat = undefined; + const st: std.posix.Stat = undefined; break :blk @TypeOf(st.ino); }, @@ -818,7 +865,7 @@ const FileId = struct { .ino = (@as(u64, @intCast(info.nFileIndexHigh)) << 32) | @as(u64, @intCast(info.nFileIndexLow)), }; } - const st = try std.os.fstat(file.handle); + const st = try std.posix.fstat(file.handle); return FileId{ .dev = st.dev, .ino = st.ino, @@ -886,7 +933,7 @@ fn createExeLink(link_target: []const u8, path_link: []const u8) !void { "unable to create the exe link, the path '{s}' is a directory\n", .{ path_link}, ); - std.os.exit(1); + std.process.exit(1); }, else => |e| return e, }; @@ -927,16 +974,19 @@ fn installCompiler(allocator: Allocator, compiler_dir: []const u8, url: []const const archive_absolute = try std.fs.path.join(allocator, &[_][]const u8{ installing_dir, archive_basename }); defer allocator.free(archive_absolute); loginfo("downloading '{s}' to '{s}'", .{ url, archive_absolute }); - downloadToFileAbsolute(allocator, url, archive_absolute) catch |e| switch (e) { - error.HttpNon200StatusCode => { - // TODO: more information would be good - std.log.err("HTTP request failed (TODO: improve ziget library to get better error)", .{}); - // this removes the installing dir if the http request fails so we dont have random directories - try loggyDeleteTreeAbsolute(installing_dir); - return error.AlreadyReported; - }, - else => return e, - }; + { + const file = try std.fs.createFileAbsolute(archive_absolute, .{}); + defer file.close(); + switch (download(allocator, url, file.writer())) { + .ok => {}, + .err => |err| { + std.log.err("download '{s}' failed: {s}", .{url, err}); + // this removes the installing dir if the http request fails so we dont have random directories + try loggyDeleteTreeAbsolute(installing_dir); + return error.AlreadyReported; + }, + } + } if (std.mem.endsWith(u8, archive_basename, ".tar.xz")) { archive_root_dir = archive_basename[0 .. archive_basename.len - ".tar.xz".len]; @@ -1004,7 +1054,7 @@ fn logRun(allocator: Allocator, argv: []const []const u8) !void { } else { prefix = true; } - std.mem.copy(u8, buffer[offset .. offset + arg.len], arg); + @memcpy(buffer[offset .. offset + arg.len], arg); offset += arg.len; } std.debug.assert(offset == buffer.len);