Skip to content

build: fix the build system #212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
180 changes: 107 additions & 73 deletions build.zig
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -53,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{
Expand Down Expand Up @@ -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;
if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
Expand Down Expand Up @@ -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 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 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("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;
Expand All @@ -627,10 +631,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 }),
Expand All @@ -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}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);
};
}
Expand Down Expand Up @@ -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 changes: 65 additions & 0 deletions src/compat.zig
Original file line number Diff line number Diff line change
@@ -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;