Browse Source

Merge pull request #212 from perillo/improve-build

build: make the logo a build step
Chris Boesch 2 years ago
parent
commit
e242d821ba
3 changed files with 174 additions and 82 deletions
  1. 2 9
      README.md
  2. 107 73
      build.zig
  3. 65 0
      src/compat.zig

+ 2 - 9
README.md

@@ -50,18 +50,11 @@ $ git clone https://github.com/ratfactor/ziglings
 $ cd 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
 ```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
 ## A Note About Versions
 
 

+ 107 - 73
build.zig

@@ -1,15 +1,13 @@
 const std = @import("std");
 const std = @import("std");
 const builtin = @import("builtin");
 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 assert = std.debug.assert;
 const print = std.debug.print;
 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 {
 const Exercise = struct {
     /// main_file must have the format key_name.zig.
     /// main_file must have the format key_name.zig.
     /// The key will be used as a shorthand to build
     /// The key will be used as a shorthand to build
@@ -53,6 +51,11 @@ const Exercise = struct {
         while (self.main_file[start_index] == '0') start_index += 1;
         while (self.main_file[start_index] == '0') start_index += 1;
         return self.main_file[start_index..end_index.?];
         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{
 const exercises = [_]Exercise{
@@ -493,44 +496,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;
     use_color_escapes = false;
     if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
     if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
@@ -571,51 +538,88 @@ pub fn build(b: *Builder) !void {
         \\
         \\
         \\
         \\
     ;
     ;
-    const header_step = b.step("info", logo);
-    print("{s}\n", .{logo});
 
 
-    const verify_all = b.step("ziglings", "Check all ziglings");
-    verify_all.dependOn(header_step);
-    b.default_step = verify_all;
+    const use_healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false;
+    const exno: ?usize = b.option(usize, "n", "Select exercise");
 
 
-    var prev_chain_verify = verify_all;
+    const header_step = PrintStep.create(b, logo, std.io.getStdErr());
 
 
-    const use_healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false;
+    if (exno) |i| {
+        const ex = blk: {
+            for (exercises) |ex| {
+                if (ex.number() == i) break :blk ex;
+            }
+
+            print("unknown exercise number: {}\n", .{i});
+            std.os.exit(1);
+        };
 
 
-    for (exercises) |ex| {
         const base_name = ex.baseName();
         const base_name = ex.baseName();
         const file_path = std.fs.path.join(b.allocator, &[_][]const u8{
         const file_path = std.fs.path.join(b.allocator, &[_][]const u8{
             if (use_healed) "patches/healed" else "exercises", ex.main_file,
             if (use_healed) "patches/healed" else "exercises", ex.main_file,
         }) catch unreachable;
         }) 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();
         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 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("Install {s} to zig-cache/bin", .{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);
 
 
-        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);
+        return;
+    }
 
 
-        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(chain_verify);
+    const ziglings_step = b.step("ziglings", "Check all ziglings");
+    ziglings_step.dependOn(&header_step.step);
+    b.default_step = ziglings_step;
 
 
-        prev_chain_verify.dependOn(chain_verify);
-        prev_chain_verify = chain_verify;
+    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 build_step = b.addExecutable(.{ .name = base_name, .root_source_file = .{ .path = file_path } });
+        build_step.install();
+
+        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_step = &verify_stepn.step;
+        }
     }
     }
+    ziglings_step.dependOn(prev_step);
 }
 }
 
 
 var use_color_escapes = false;
 var use_color_escapes = false;
@@ -627,10 +631,10 @@ var reset_text: []const u8 = "";
 const ZiglingStep = struct {
 const ZiglingStep = struct {
     step: Step,
     step: Step,
     exercise: Exercise,
     exercise: Exercise,
-    builder: *Builder,
+    builder: *Build,
     use_healed: bool,
     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;
         const self = builder.allocator.create(@This()) catch unreachable;
         self.* = .{
         self.* = .{
             .step = Step.init(Step.Options{ .id = .custom, .name = exercise.main_file, .owner = builder, .makeFn = make }),
             .step = Step.init(Step.Options{ .id = .custom, .name = exercise.main_file, .owner = builder, .makeFn = make }),
@@ -650,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}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);
             std.os.exit(1);
         };
         };
     }
     }
@@ -804,3 +808,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: *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);
+    }
+};

+ 65 - 0
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.2401";
+
+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;