From dd15cb94fd522bd7c6dd48ff096e4b059a69dbd3 Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Sun, 2 Apr 2023 14:59:22 +0200 Subject: [PATCH 1/7] build: make the logo a build step Currently, the logo is always printed when the build script is executed, resulting in the logo being printed twice with `zig build -h` and `zig build -l`. Make the logo a build step, so that the logo is printed to stderr only when necessary. Closes #211 --- build.zig | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 5ade0c4..035f0bb 100644 --- a/build.zig +++ b/build.zig @@ -572,7 +572,9 @@ pub fn build(b: *Builder) !void { \\ ; const header_step = b.step("info", logo); - print("{s}\n", .{logo}); + + const logo_step = PrintStep.create(b, logo, std.io.getStdErr()); + logo_step.step.dependOn(header_step); const verify_all = b.step("ziglings", "Check all ziglings"); verify_all.dependOn(header_step); @@ -804,3 +806,33 @@ const ZiglingStep = struct { }); } }; + +// Print a message to a file. +const PrintStep = struct { + step: Step, + message: []const u8, + file: std.fs.File, + + pub fn create(owner: *std.Build, message: []const u8, file: std.fs.File) *PrintStep { + const self = owner.allocator.create(PrintStep) catch @panic("OOM"); + self.* = .{ + .step = Step.init(.{ + .id = .custom, + .name = "Print", + .owner = owner, + .makeFn = make, + }), + .message = message, + .file = file, + }; + + return self; + } + + fn make(step: *Step, prog_node: *std.Progress.Node) !void { + _ = prog_node; + const p = @fieldParentPtr(PrintStep, "step", step); + + try p.file.writeAll(p.message); + } +}; From 45713ec8ab21f81291bc14e2725730dac26d5137 Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Mon, 3 Apr 2023 11:06:39 +0200 Subject: [PATCH 2/7] build: restore support for Zig 0.6.0 The version check for Zig 0.6.0 was incorrect since commit 971ab7f (Use a zig build script to run ziglings). Move compatibility support to a separate file, in order to simplify build.zig. In case of incompatible version, exit with code 3 instead of 0, in order to detect the case of failure in a test (to be implemented). Remove the use of comptime when checking compatibility at the start of the build function, since it is not necessary. Closes #210. --- build.zig | 58 ++++++++------------------------------------ src/compat.zig | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 48 deletions(-) create mode 100644 src/compat.zig diff --git a/build.zig b/build.zig index 035f0bb..a315a51 100644 --- a/build.zig +++ b/build.zig @@ -1,15 +1,13 @@ const std = @import("std"); const builtin = @import("builtin"); -const Builder = std.build.Builder; -const Step = std.build.Step; +const compat = @import("src/compat.zig"); + +const Build = compat.Build; +const Step = compat.build.Step; + const assert = std.debug.assert; const print = std.debug.print; -// When changing this version, be sure to also update README.md in two places: -// 1) Getting Started -// 2) Version Changes -const needed_version = std.SemanticVersion.parse("0.11.0-dev.2157") catch unreachable; - const Exercise = struct { /// main_file must have the format key_name.zig. /// The key will be used as a shorthand to build @@ -493,44 +491,8 @@ const exercises = [_]Exercise{ }, }; -/// Check the zig version to make sure it can compile the examples properly. -/// This will compile with Zig 0.6.0 and later. -fn checkVersion() bool { - if (!@hasDecl(builtin, "zig_version")) { - return false; - } - - const version = builtin.zig_version; - const order = version.order(needed_version); - return order != .lt; -} - -pub fn build(b: *Builder) !void { - // Use a comptime branch for the version check. - // If this fails, code after this block is not compiled. - // It is parsed though, so versions of zig from before 0.6.0 - // cannot do the version check and will just fail to compile. - // We could fix this by moving the ziglings code to a separate file, - // but 0.5.0 was a long time ago, it is unlikely that anyone who - // attempts these exercises is still using it. - if (comptime !checkVersion()) { - // very old versions of Zig used warn instead of print. - const stderrPrintFn = if (@hasDecl(std.debug, "print")) std.debug.print else std.debug.warn; - stderrPrintFn( - \\ERROR: Sorry, it looks like your version of zig is too old. :-( - \\ - \\Ziglings requires development build - \\ - \\ {} - \\ - \\or higher. Please download a development ("master") build from - \\ - \\ https://ziglang.org/download/ - \\ - \\ - , .{needed_version}); - std.os.exit(0); - } +pub fn build(b: *Build) !void { + if (!compat.is_compatible) compat.die(); use_color_escapes = false; if (std.io.getStdErr().supportsAnsiEscapeCodes()) { @@ -629,10 +591,10 @@ var reset_text: []const u8 = ""; const ZiglingStep = struct { step: Step, exercise: Exercise, - builder: *Builder, + builder: *Build, use_healed: bool, - pub fn create(builder: *Builder, exercise: Exercise, use_healed: bool) *@This() { + pub fn create(builder: *Build, exercise: Exercise, use_healed: bool) *@This() { const self = builder.allocator.create(@This()) catch unreachable; self.* = .{ .step = Step.init(Step.Options{ .id = .custom, .name = exercise.main_file, .owner = builder, .makeFn = make }), @@ -813,7 +775,7 @@ const PrintStep = struct { message: []const u8, file: std.fs.File, - pub fn create(owner: *std.Build, message: []const u8, file: std.fs.File) *PrintStep { + pub fn create(owner: *Build, message: []const u8, file: std.fs.File) *PrintStep { const self = owner.allocator.create(PrintStep) catch @panic("OOM"); self.* = .{ .step = Step.init(.{ diff --git a/src/compat.zig b/src/compat.zig new file mode 100644 index 0000000..34dfc76 --- /dev/null +++ b/src/compat.zig @@ -0,0 +1,65 @@ +/// Compatibility support for very old versions of Zig and recent versions before +/// commit efa25e7d5 (Merge pull request #14498 from ziglang/zig-build-api). +/// +/// Versions of Zig from before 0.6.0 cannot do the version check and will just +/// fail to compile, but 0.5.0 was a long time ago, it is unlikely that anyone +/// who attempts these exercises is still using it. +const std = @import("std"); +const builtin = @import("builtin"); + +const debug = std.debug; + +// Very old versions of Zig used warn instead of print. +const print = if (@hasDecl(debug, "print")) debug.print else debug.warn; + +// When changing this version, be sure to also update README.md in two places: +// 1) Getting Started +// 2) Version Changes +const needed_version_str = "0.11.0-dev.2157"; + +fn isCompatible() bool { + if (!@hasDecl(builtin, "zig_version") or !@hasDecl(std, "SemanticVersion")) { + return false; + } + + const needed_version = std.SemanticVersion.parse(needed_version_str) catch unreachable; + const version = builtin.zig_version; + const order = version.order(needed_version); + + return order != .lt; +} + +pub fn die() noreturn { + const error_message = + \\ERROR: Sorry, it looks like your version of zig is too old. :-( + \\ + \\Ziglings requires development build + \\ + \\ {s} + \\ + \\or higher. Please download a development ("master") build from + \\ + \\ https://ziglang.org/download/ + \\ + \\ + ; + + print(error_message, .{needed_version_str}); + + // Use exit code 2, to differentiate from a normal Zig compiler error. + std.os.exit(2); +} + +// A separate function is required because very old versions of Zig doesn't +// support labeled block expressions. +pub const is_compatible: bool = isCompatible(); + +/// This is the type to be used only for the build function definition, since +/// the type must be compatible with the build runner. +/// +/// Don't use std.Build.Builder, since it is deprecated and may be removed in +/// future. +pub const Build = if (is_compatible) std.Build else std.build.Builder; + +/// This is the type to be used for accessing the build namespace. +pub const build = if (is_compatible) std.Build else std.build; From 36e3f577ca9d4ae0eca74a31e5a9da8b248111ce Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Tue, 4 Apr 2023 18:32:30 +0200 Subject: [PATCH 3/7] build: replace the logo step with the header step Remove the logo step, and use PrintStep for the header step. The logo step was added as a quick fix after the Builder.addLog function was removed. Now the logo is no longer shown when running `zig build -l` or `zig build -h`. --- build.zig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build.zig b/build.zig index a315a51..ed5950d 100644 --- a/build.zig +++ b/build.zig @@ -533,13 +533,11 @@ pub fn build(b: *Build) !void { \\ \\ ; - const header_step = b.step("info", logo); - const logo_step = PrintStep.create(b, logo, std.io.getStdErr()); - logo_step.step.dependOn(header_step); + const header_step = PrintStep.create(b, logo, std.io.getStdErr()); const verify_all = b.step("ziglings", "Check all ziglings"); - verify_all.dependOn(header_step); + verify_all.dependOn(&header_step.step); b.default_step = verify_all; var prev_chain_verify = verify_all; @@ -574,7 +572,7 @@ pub fn build(b: *Build) !void { chain_verify.dependOn(&verify_step.step); const named_chain = b.step(b.fmt("{s}_start", .{key}), b.fmt("Check all solutions starting at {s}", .{ex.main_file})); - named_chain.dependOn(header_step); + named_chain.dependOn(&header_step.step); named_chain.dependOn(chain_verify); prev_chain_verify.dependOn(chain_verify); From 75a1600626b3352b516c0dc3b42380f5ab0db025 Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Wed, 5 Apr 2023 11:16:23 +0200 Subject: [PATCH 4/7] build: fix the description of the named_install step Replace the description of the named_install step from "Install {s} to zig-cache/bin" to "Copy {s} to prefix path". The latter has been adapded from the description of the builtin install step. Ad an empty line before the build_step variable, in order to improve readability. Closes #213 --- build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index ed5950d..e3533aa 100644 --- a/build.zig +++ b/build.zig @@ -549,8 +549,8 @@ pub fn build(b: *Build) !void { const file_path = std.fs.path.join(b.allocator, &[_][]const u8{ if (use_healed) "patches/healed" else "exercises", ex.main_file, }) catch unreachable; - const build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } }); + const build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } }); build_step.install(); const verify_step = ZiglingStep.create(b, ex, use_healed); @@ -561,7 +561,7 @@ pub fn build(b: *Build) !void { const run_step = build_step.run(); named_test.dependOn(&run_step.step); - const named_install = b.step(b.fmt("{s}_install", .{key}), b.fmt("Install {s} to zig-cache/bin", .{ex.main_file})); + const named_install = b.step(b.fmt("{s}_install", .{key}), b.fmt("Copy {s} to prefix path", .{ex.main_file})); named_install.dependOn(&build_step.install_step.?.step); const named_verify = b.step(key, b.fmt("Check {s} only", .{ex.main_file})); From 59e28987dab53acfb268d30e5f19f02aa20bd70d Mon Sep 17 00:00:00 2001 From: Manlio Perillo Date: Thu, 6 Apr 2023 12:47:08 +0200 Subject: [PATCH 5/7] build: restore the exercise chain The new parallel build support in Zig broke the exercise chain, so that each esercise check is no longer strictly serialized. 1. Add the Dexno option, in order to isolate the chain starting from a named exercise from the normal chain, thus simplify the code. The current code have an additional issue: it added 4 x n steps, making reading the help message or the list of steps very hard. Add only the `install`, `uninstall`, `zigling`, `test` and `start` steps. The last three steps match the old steps `n`, `n_test` and `n_start`. The default step is zigling (note the singular form). The `install` step override the builtin install step, showing a custom description and matches the old `n_install` step. The uninstall step was added for consistency, so that the description is consistent. Setup a new chain starting at `zig build -Dexno=n start` so that it is stricly serialized. The behavior should be the same as the old one. 2. Handle the code for all the exercises separately. Add only the `ziglings step`, making it the default step, in addition to the install and uninstall steps. Setup a new chain starting at the first exercise, to that it is strictly serialized. The behavior should be the same as the old one. The current code has a know issue: the messages from the ZiglingStep and the ones from the compiler compilation progress are interleaved, but each message is written atomically, due to the use of `std.debug.getStderrMutex()`. Update the README.md file. Closes #202 --- README.md | 11 ++----- build.zig | 88 ++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 600790b..f23f536 100644 --- a/README.md +++ b/README.md @@ -50,18 +50,11 @@ $ git clone https://github.com/ratfactor/ziglings $ cd ziglings ``` -Then run `zig build 1` and follow the instructions to begin! +Then run `zig build` and follow the instructions to begin! ```bash -$ zig build 1 +$ zig build ``` -## :warning: Attention -Due to Zig's new build system, exercises can currently only be run manually with their number! - -```bash -$ zig build xy -``` -We hope to be able to offer this again soon in the automatic way. ## A Note About Versions diff --git a/build.zig b/build.zig index e3533aa..391c260 100644 --- a/build.zig +++ b/build.zig @@ -51,6 +51,11 @@ const Exercise = struct { while (self.main_file[start_index] == '0') start_index += 1; return self.main_file[start_index..end_index.?]; } + + /// Returns the exercise key as an integer. + pub fn number(self: Exercise) usize { + return std.fmt.parseInt(usize, self.key(), 10) catch unreachable; + } }; const exercises = [_]Exercise{ @@ -534,17 +539,21 @@ pub fn build(b: *Build) !void { \\ ; - const header_step = PrintStep.create(b, logo, std.io.getStdErr()); + const use_healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false; + const exno: ?usize = b.option(usize, "n", "Select exercise"); - const verify_all = b.step("ziglings", "Check all ziglings"); - verify_all.dependOn(&header_step.step); - b.default_step = verify_all; + const header_step = PrintStep.create(b, logo, std.io.getStdErr()); - var prev_chain_verify = verify_all; + if (exno) |i| { + const ex = blk: { + for (exercises) |ex| { + if (ex.number() == i) break :blk ex; + } - const use_healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false; + print("unknown exercise number: {}\n", .{i}); + std.os.exit(1); + }; - for (exercises) |ex| { const base_name = ex.baseName(); const file_path = std.fs.path.join(b.allocator, &[_][]const u8{ if (use_healed) "patches/healed" else "exercises", ex.main_file, @@ -553,31 +562,64 @@ pub fn build(b: *Build) !void { const build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } }); build_step.install(); + const run_step = build_step.run(); + + const test_step = b.step("test", b.fmt("Run {s} without checking output", .{ex.main_file})); + test_step.dependOn(&run_step.step); + + const install_step = b.step("install", b.fmt("Install {s} to prefix path", .{ex.main_file})); + install_step.dependOn(b.getInstallStep()); + + const uninstall_step = b.step("uninstall", b.fmt("Uninstall {s} from prefix path", .{ex.main_file})); + uninstall_step.dependOn(b.getUninstallStep()); + const verify_step = ZiglingStep.create(b, ex, use_healed); - const key = ex.key(); + const zigling_step = b.step("zigling", b.fmt("Check the solution of {s}", .{ex.main_file})); + zigling_step.dependOn(&verify_step.step); + b.default_step = zigling_step; - const named_test = b.step(b.fmt("{s}_test", .{key}), b.fmt("Run {s} without checking output", .{ex.main_file})); - const run_step = build_step.run(); - named_test.dependOn(&run_step.step); + const start_step = b.step("start", b.fmt("Check all solutions starting at {s}", .{ex.main_file})); - const named_install = b.step(b.fmt("{s}_install", .{key}), b.fmt("Copy {s} to prefix path", .{ex.main_file})); - named_install.dependOn(&build_step.install_step.?.step); + var prev_step = verify_step; + for (exercises) |exn| { + const n = exn.number(); + if (n > i) { + const verify_stepn = ZiglingStep.create(b, exn, use_healed); + verify_stepn.step.dependOn(&prev_step.step); - const named_verify = b.step(key, b.fmt("Check {s} only", .{ex.main_file})); - named_verify.dependOn(&verify_step.step); + prev_step = verify_stepn; + } + } + start_step.dependOn(&prev_step.step); + + return; + } + + const ziglings_step = b.step("ziglings", "Check all ziglings"); + ziglings_step.dependOn(&header_step.step); + b.default_step = ziglings_step; + + var prev_step: *Step = undefined; + for (exercises, 0..) |ex, i| { + const base_name = ex.baseName(); + const file_path = std.fs.path.join(b.allocator, &[_][]const u8{ + if (use_healed) "patches/healed" else "exercises", ex.main_file, + }) catch unreachable; - const chain_verify = b.allocator.create(Step) catch unreachable; - chain_verify.* = Step.init(Step.Options{ .id = .custom, .name = b.fmt("chain {s}", .{key}), .owner = b }); - chain_verify.dependOn(&verify_step.step); + const build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } }); + build_step.install(); - const named_chain = b.step(b.fmt("{s}_start", .{key}), b.fmt("Check all solutions starting at {s}", .{ex.main_file})); - named_chain.dependOn(&header_step.step); - named_chain.dependOn(chain_verify); + const verify_stepn = ZiglingStep.create(b, ex, use_healed); + if (i == 0) { + prev_step = &verify_stepn.step; + } else { + verify_stepn.step.dependOn(prev_step); - prev_chain_verify.dependOn(chain_verify); - prev_chain_verify = chain_verify; + prev_step = &verify_stepn.step; + } } + ziglings_step.dependOn(prev_step); } var use_color_escapes = false; From 0fd80d6cba83783e10e8a63761afec268f3febd6 Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Fri, 7 Apr 2023 18:06:13 +0200 Subject: [PATCH 6/7] help text for parameters adapted --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 391c260..d2702d0 100644 --- a/build.zig +++ b/build.zig @@ -654,7 +654,7 @@ const ZiglingStep = struct { } print("\n{s}Edit exercises/{s} and run this again.{s}", .{ red_text, self.exercise.main_file, reset_text }); - print("\n{s}To continue from this zigling, use this command:{s}\n {s}zig build {s}{s}\n", .{ red_text, reset_text, bold_text, self.exercise.key(), reset_text }); + print("\n{s}To continue from this zigling, use this command:{s}\n {s}zig build -Dn={s}{s}\n", .{ red_text, reset_text, bold_text, self.exercise.key(), reset_text }); std.os.exit(1); }; } From ddc835762c1276c781295164a2bb3e42b35670df Mon Sep 17 00:00:00 2001 From: Chris Boesch Date: Fri, 7 Apr 2023 18:18:15 +0200 Subject: [PATCH 7/7] Insert current zig version for compatiblity! This is primarily to make users aware that there has been a change in the call for individual exercises. --- src/compat.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compat.zig b/src/compat.zig index 34dfc76..1adf8c0 100644 --- a/src/compat.zig +++ b/src/compat.zig @@ -15,7 +15,7 @@ const print = if (@hasDecl(debug, "print")) debug.print else debug.warn; // When changing this version, be sure to also update README.md in two places: // 1) Getting Started // 2) Version Changes -const needed_version_str = "0.11.0-dev.2157"; +const needed_version_str = "0.11.0-dev.2401"; fn isCompatible() bool { if (!@hasDecl(builtin, "zig_version") or !@hasDecl(std, "SemanticVersion")) {