mirror of
https://codeberg.org/andyscott/ziglings.git
synced 2024-11-13 21:40:46 -05:00
Merge pull request #293 from perillo/improve-build-even-more
More improvements to build.zig and test/tests.zig
This commit is contained in:
commit
22c3b10855
2 changed files with 89 additions and 93 deletions
140
build.zig
140
build.zig
|
@ -74,9 +74,22 @@ pub const Exercise = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const logo =
|
||||||
|
\\ _ _ _
|
||||||
|
\\ ___(_) __ _| (_)_ __ __ _ ___
|
||||||
|
\\ |_ | |/ _' | | | '_ \ / _' / __|
|
||||||
|
\\ / /| | (_| | | | | | | (_| \__ \
|
||||||
|
\\ /___|_|\__, |_|_|_| |_|\__, |___/
|
||||||
|
\\ |___/ |___/
|
||||||
|
\\
|
||||||
|
\\ "Look out! Broken programs below!"
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
|
||||||
pub fn build(b: *Build) !void {
|
pub fn build(b: *Build) !void {
|
||||||
if (!compat.is_compatible) compat.die();
|
if (!compat.is_compatible) compat.die();
|
||||||
if (!validate_exercises()) std.os.exit(1);
|
if (!validate_exercises()) std.os.exit(2);
|
||||||
|
|
||||||
use_color_escapes = false;
|
use_color_escapes = false;
|
||||||
if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
|
if (std.io.getStdErr().supportsAnsiEscapeCodes()) {
|
||||||
|
@ -106,24 +119,16 @@ pub fn build(b: *Build) !void {
|
||||||
reset_text = "\x1b[0m";
|
reset_text = "\x1b[0m";
|
||||||
}
|
}
|
||||||
|
|
||||||
const logo =
|
const healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse
|
||||||
\\ _ _ _
|
false;
|
||||||
\\ ___(_) __ _| (_)_ __ __ _ ___
|
|
||||||
\\ |_ | |/ _' | | | '_ \ / _' / __|
|
|
||||||
\\ / /| | (_| | | | | | | (_| \__ \
|
|
||||||
\\ /___|_|\__, |_|_|_| |_|\__, |___/
|
|
||||||
\\ |___/ |___/
|
|
||||||
\\
|
|
||||||
\\ "Look out! Broken programs below!"
|
|
||||||
\\
|
|
||||||
\\
|
|
||||||
;
|
|
||||||
|
|
||||||
const healed = b.option(bool, "healed", "Run exercises from patches/healed") orelse false;
|
|
||||||
const override_healed_path = b.option([]const u8, "healed-path", "Override healed path");
|
const override_healed_path = b.option([]const u8, "healed-path", "Override healed path");
|
||||||
const exno: ?usize = b.option(usize, "n", "Select exercise");
|
const exno: ?usize = b.option(usize, "n", "Select exercise");
|
||||||
|
|
||||||
const healed_path = if (override_healed_path) |path| path else "patches/healed";
|
const sep = std.fs.path.sep_str;
|
||||||
|
const healed_path = if (override_healed_path) |path|
|
||||||
|
path
|
||||||
|
else
|
||||||
|
"patches" ++ sep ++ "healed";
|
||||||
const work_path = if (healed) healed_path else "exercises";
|
const work_path = if (healed) healed_path else "exercises";
|
||||||
|
|
||||||
const header_step = PrintStep.create(b, logo);
|
const header_step = PrintStep.create(b, logo);
|
||||||
|
@ -131,19 +136,25 @@ pub fn build(b: *Build) !void {
|
||||||
if (exno) |n| {
|
if (exno) |n| {
|
||||||
if (n == 0 or n > exercises.len - 1) {
|
if (n == 0 or n > exercises.len - 1) {
|
||||||
print("unknown exercise number: {}\n", .{n});
|
print("unknown exercise number: {}\n", .{n});
|
||||||
std.os.exit(1);
|
std.os.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ex = exercises[n - 1];
|
const ex = exercises[n - 1];
|
||||||
|
|
||||||
const build_step = ex.addExecutable(b, work_path);
|
const build_step = ex.addExecutable(b, work_path);
|
||||||
b.installArtifact(build_step);
|
|
||||||
|
const skip_step = SkipStep.create(b, ex);
|
||||||
|
if (!ex.skip)
|
||||||
|
b.installArtifact(build_step)
|
||||||
|
else
|
||||||
|
b.getInstallStep().dependOn(&skip_step.step);
|
||||||
|
|
||||||
const run_step = b.addRunArtifact(build_step);
|
const run_step = b.addRunArtifact(build_step);
|
||||||
|
|
||||||
const test_step = b.step("test", b.fmt("Run {s} without checking output", .{ex.main_file}));
|
const test_step = b.step(
|
||||||
|
"test",
|
||||||
|
b.fmt("Run {s} without checking output", .{ex.main_file}),
|
||||||
|
);
|
||||||
if (ex.skip) {
|
if (ex.skip) {
|
||||||
const skip_step = SkipStep.create(b, ex);
|
|
||||||
test_step.dependOn(&skip_step.step);
|
test_step.dependOn(&skip_step.step);
|
||||||
} else {
|
} else {
|
||||||
test_step.dependOn(&run_step.step);
|
test_step.dependOn(&run_step.step);
|
||||||
|
@ -151,11 +162,17 @@ pub fn build(b: *Build) !void {
|
||||||
|
|
||||||
const verify_step = ZiglingStep.create(b, ex, work_path);
|
const verify_step = ZiglingStep.create(b, ex, work_path);
|
||||||
|
|
||||||
const zigling_step = b.step("zigling", b.fmt("Check the solution of {s}", .{ex.main_file}));
|
const zigling_step = b.step(
|
||||||
|
"zigling",
|
||||||
|
b.fmt("Check the solution of {s}", .{ex.main_file}),
|
||||||
|
);
|
||||||
zigling_step.dependOn(&verify_step.step);
|
zigling_step.dependOn(&verify_step.step);
|
||||||
b.default_step = zigling_step;
|
b.default_step = zigling_step;
|
||||||
|
|
||||||
const start_step = b.step("start", b.fmt("Check all solutions starting at {s}", .{ex.main_file}));
|
const start_step = b.step(
|
||||||
|
"start",
|
||||||
|
b.fmt("Check all solutions starting at {s}", .{ex.main_file}),
|
||||||
|
);
|
||||||
|
|
||||||
var prev_step = verify_step;
|
var prev_step = verify_step;
|
||||||
for (exercises) |exn| {
|
for (exercises) |exn| {
|
||||||
|
@ -198,12 +215,15 @@ pub fn build(b: *Build) !void {
|
||||||
const ziglings_step = b.step("ziglings", "Check all ziglings");
|
const ziglings_step = b.step("ziglings", "Check all ziglings");
|
||||||
b.default_step = ziglings_step;
|
b.default_step = ziglings_step;
|
||||||
|
|
||||||
// Don't use the "multi-object for loop" syntax, in order to avoid a syntax
|
|
||||||
// error with old Zig compilers.
|
|
||||||
var prev_step = &header_step.step;
|
var prev_step = &header_step.step;
|
||||||
for (exercises) |ex| {
|
for (exercises) |ex| {
|
||||||
const build_step = ex.addExecutable(b, "exercises");
|
const build_step = ex.addExecutable(b, work_path);
|
||||||
b.installArtifact(build_step);
|
|
||||||
|
const skip_step = SkipStep.create(b, ex);
|
||||||
|
if (!ex.skip)
|
||||||
|
b.installArtifact(build_step)
|
||||||
|
else
|
||||||
|
b.getInstallStep().dependOn(&skip_step.step);
|
||||||
|
|
||||||
const verify_stepn = ZiglingStep.create(b, ex, work_path);
|
const verify_stepn = ZiglingStep.create(b, ex, work_path);
|
||||||
verify_stepn.step.dependOn(prev_step);
|
verify_stepn.step.dependOn(prev_step);
|
||||||
|
@ -260,10 +280,10 @@ const ZiglingStep = struct {
|
||||||
|
|
||||||
self.help();
|
self.help();
|
||||||
|
|
||||||
// NOTE: Returning 0 'success' status because the *exercise* failed,
|
// NOTE: Using exit code 2 will prevent the Zig compiler to print
|
||||||
// but Ziglings did not. Otherwise the learner will see this message:
|
// the message:
|
||||||
// "error: the following build command failed with exit code 1:..."
|
// "error: the following build command failed with exit code 1:..."
|
||||||
std.os.exit(0);
|
std.os.exit(2);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.run(exe_path, prog_node) catch {
|
self.run(exe_path, prog_node) catch {
|
||||||
|
@ -273,7 +293,7 @@ const ZiglingStep = struct {
|
||||||
self.help();
|
self.help();
|
||||||
|
|
||||||
// NOTE: See note above!
|
// NOTE: See note above!
|
||||||
std.os.exit(0);
|
std.os.exit(2);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,37 +402,32 @@ const ZiglingStep = struct {
|
||||||
print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{
|
print("{s}{s}: Unable to spawn the following command: file not found{s}\n", .{
|
||||||
red_text, self.exercise.main_file, reset_text,
|
red_text, self.exercise.main_file, reset_text,
|
||||||
});
|
});
|
||||||
for (argv) |v| print("{s} ", .{v});
|
dumpArgs(argv);
|
||||||
print("\n", .{});
|
|
||||||
},
|
},
|
||||||
error.ExitCodeFailure => {
|
error.ExitCodeFailure => {
|
||||||
print("{s}{s}: The following command exited with error code {}:{s}\n", .{
|
print("{s}{s}: The following command exited with error code {}:{s}\n", .{
|
||||||
red_text, self.exercise.main_file, code, reset_text,
|
red_text, self.exercise.main_file, code, reset_text,
|
||||||
});
|
});
|
||||||
for (argv) |v| print("{s} ", .{v});
|
dumpArgs(argv);
|
||||||
print("\n", .{});
|
|
||||||
},
|
},
|
||||||
error.ProcessTerminated => {
|
error.ProcessTerminated => {
|
||||||
print("{s}{s}: The following command terminated unexpectedly:{s}\n", .{
|
print("{s}{s}: The following command terminated unexpectedly:{s}\n", .{
|
||||||
red_text, self.exercise.main_file, reset_text,
|
red_text, self.exercise.main_file, reset_text,
|
||||||
});
|
});
|
||||||
for (argv) |v| print("{s} ", .{v});
|
dumpArgs(argv);
|
||||||
print("\n", .{});
|
|
||||||
},
|
},
|
||||||
error.ZigIPCError => {
|
error.ZigIPCError => {
|
||||||
// Commenting this out for now. It always shows up when compilation fails.
|
// Commenting this out for now. It always shows up when compilation fails.
|
||||||
//print("{s}{s}: The following command failed to communicate the compilation result:{s}\n", .{
|
//print("{s}{s}: The following command failed to communicate the compilation result:{s}\n", .{
|
||||||
// red_text, self.exercise.main_file, reset_text,
|
// red_text, self.exercise.main_file, reset_text,
|
||||||
//});
|
//});
|
||||||
//for (argv) |v| print("{s} ", .{v});
|
//dumpArgs(argv);
|
||||||
//print("\n", .{});
|
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
print("{s}{s}: Unexpected error: {s}{s}\n", .{
|
print("{s}{s}: Unexpected error: {s}{s}\n", .{
|
||||||
red_text, self.exercise.main_file, @errorName(err), reset_text,
|
red_text, self.exercise.main_file, @errorName(err), reset_text,
|
||||||
});
|
});
|
||||||
for (argv) |v| print("{s} ", .{v});
|
dumpArgs(argv);
|
||||||
print("\n", .{});
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,13 +577,18 @@ const ZiglingStep = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear the entire line and move the cursor to column zero.
|
fn dumpArgs(args: []const []const u8) void {
|
||||||
// Used for clearing the compiler and build_runner progress messages.
|
for (args) |arg| print("{s} ", .{arg});
|
||||||
|
print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the entire line and move the cursor to column zero.
|
||||||
|
/// Used for clearing the compiler and build_runner progress messages.
|
||||||
fn resetLine() void {
|
fn resetLine() void {
|
||||||
if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
|
if (use_color_escapes) print("{s}", .{"\x1b[2K\r"});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove trailing whitespace for each line in buf, also ensuring that there
|
/// Removes trailing whitespace for each line in buf, also ensuring that there
|
||||||
/// are no trailing LF characters at the end.
|
/// are no trailing LF characters at the end.
|
||||||
pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
||||||
var list = try std.ArrayList(u8).initCapacity(allocator, buf.len);
|
var list = try std.ArrayList(u8).initCapacity(allocator, buf.len);
|
||||||
|
@ -588,7 +608,7 @@ pub fn trimLines(allocator: std.mem.Allocator, buf: []const u8) ![]const u8 {
|
||||||
return std.mem.trimRight(u8, result, "\n");
|
return std.mem.trimRight(u8, result, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a message to stderr.
|
/// Prints a message to stderr.
|
||||||
const PrintStep = struct {
|
const PrintStep = struct {
|
||||||
step: Step,
|
step: Step,
|
||||||
message: []const u8,
|
message: []const u8,
|
||||||
|
@ -608,15 +628,14 @@ const PrintStep = struct {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
|
fn make(step: *Step, _: *std.Progress.Node) !void {
|
||||||
_ = prog_node;
|
const self = @fieldParentPtr(PrintStep, "step", step);
|
||||||
const p = @fieldParentPtr(PrintStep, "step", step);
|
|
||||||
|
|
||||||
print("{s}", .{p.message});
|
print("{s}", .{self.message});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip an exercise.
|
/// Skips an exercise.
|
||||||
const SkipStep = struct {
|
const SkipStep = struct {
|
||||||
step: Step,
|
step: Step,
|
||||||
exercise: Exercise,
|
exercise: Exercise,
|
||||||
|
@ -636,20 +655,19 @@ const SkipStep = struct {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make(step: *Step, prog_node: *std.Progress.Node) !void {
|
fn make(step: *Step, _: *std.Progress.Node) !void {
|
||||||
_ = prog_node;
|
const self = @fieldParentPtr(SkipStep, "step", step);
|
||||||
const p = @fieldParentPtr(SkipStep, "step", step);
|
|
||||||
|
|
||||||
if (p.exercise.skip) {
|
if (self.exercise.skip) {
|
||||||
print("{s} skipped\n", .{p.exercise.main_file});
|
print("{s} skipped\n", .{self.exercise.main_file});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check that each exercise number, excluding the last, forms the sequence
|
/// Checks that each exercise number, excluding the last, forms the sequence
|
||||||
// `[1, exercise.len)`.
|
/// `[1, exercise.len)`.
|
||||||
//
|
///
|
||||||
// Additionally check that the output field lines doesn't have trailing whitespace.
|
/// Additionally check that the output field lines doesn't have trailing whitespace.
|
||||||
fn validate_exercises() bool {
|
fn validate_exercises() bool {
|
||||||
// Don't use the "multi-object for loop" syntax, in order to avoid a syntax
|
// Don't use the "multi-object for loop" syntax, in order to avoid a syntax
|
||||||
// error with old Zig compilers.
|
// error with old Zig compilers.
|
||||||
|
@ -661,9 +679,7 @@ fn validate_exercises() bool {
|
||||||
|
|
||||||
if (exno != i and exno != last) {
|
if (exno != i and exno != last) {
|
||||||
print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{
|
print("exercise {s} has an incorrect number: expected {}, got {s}\n", .{
|
||||||
ex.main_file,
|
ex.main_file, i, ex.key(),
|
||||||
i,
|
|
||||||
ex.key(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -84,10 +84,11 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
|
||||||
b.fmt("-Dn={}", .{n}),
|
b.fmt("-Dn={}", .{n}),
|
||||||
"test",
|
"test",
|
||||||
});
|
});
|
||||||
|
const expect = b.fmt("{s} skipped", .{ex.main_file});
|
||||||
cmd.setName(b.fmt("zig build -Dhealed -Dn={} test", .{n}));
|
cmd.setName(b.fmt("zig build -Dhealed -Dn={} test", .{n}));
|
||||||
cmd.expectExitCode(0);
|
cmd.expectExitCode(0);
|
||||||
cmd.expectStdOutEqual("");
|
cmd.addCheck(.{ .expect_stdout_exact = "" });
|
||||||
expectStdErrMatch(cmd, b.fmt("{s} skipped", .{ex.main_file}));
|
cmd.addCheck(.{ .expect_stderr_match = expect });
|
||||||
|
|
||||||
cmd.step.dependOn(&heal_step.step);
|
cmd.step.dependOn(&heal_step.step);
|
||||||
|
|
||||||
|
@ -172,9 +173,10 @@ pub fn addCliTests(b: *std.Build, exercises: []const Exercise) *Step {
|
||||||
const case_step = createCase(b, "case-5");
|
const case_step = createCase(b, "case-5");
|
||||||
|
|
||||||
const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dn=1" });
|
const cmd = b.addSystemCommand(&.{ b.zig_exe, "build", "-Dn=1" });
|
||||||
|
const expect = exercises[0].hint orelse "";
|
||||||
cmd.setName("zig build -Dn=1");
|
cmd.setName("zig build -Dn=1");
|
||||||
cmd.expectExitCode(1);
|
cmd.expectExitCode(2);
|
||||||
expectStdErrMatch(cmd, exercises[0].hint orelse "");
|
cmd.addCheck(.{ .expect_stderr_match = expect });
|
||||||
|
|
||||||
cmd.step.dependOn(case_step);
|
cmd.step.dependOn(case_step);
|
||||||
|
|
||||||
|
@ -280,10 +282,11 @@ const CheckStep = struct {
|
||||||
for (exercises) |ex| {
|
for (exercises) |ex| {
|
||||||
if (ex.number() == 1 and self.has_logo) {
|
if (ex.number() == 1 and self.has_logo) {
|
||||||
// Skip the logo.
|
// Skip the logo.
|
||||||
|
const nlines = mem.count(u8, root.logo, "\n");
|
||||||
var buf: [80]u8 = undefined;
|
var buf: [80]u8 = undefined;
|
||||||
|
|
||||||
var lineno: usize = 0;
|
var lineno: usize = 0;
|
||||||
while (lineno < 8) : (lineno += 1) {
|
while (lineno < nlines) : (lineno += 1) {
|
||||||
_ = try readLine(stderr, &buf);
|
_ = try readLine(stderr, &buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -433,10 +436,11 @@ const HealStep = struct {
|
||||||
|
|
||||||
/// Heals all the exercises.
|
/// Heals all the exercises.
|
||||||
fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
|
fn heal(allocator: Allocator, exercises: []const Exercise, work_path: []const u8) !void {
|
||||||
|
const sep = std.fs.path.sep_str;
|
||||||
const join = fs.path.join;
|
const join = fs.path.join;
|
||||||
|
|
||||||
const exercises_path = "exercises";
|
const exercises_path = "exercises";
|
||||||
const patches_path = "patches/patches";
|
const patches_path = "patches" ++ sep ++ "patches";
|
||||||
|
|
||||||
for (exercises) |ex| {
|
for (exercises) |ex| {
|
||||||
const name = ex.name();
|
const name = ex.name();
|
||||||
|
@ -466,27 +470,3 @@ pub fn makeTempPath(b: *Build) ![]const u8 {
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Missing functions from std.Build.RunStep
|
|
||||||
//
|
|
||||||
|
|
||||||
/// Adds a check for stderr match. Does not add any other checks.
|
|
||||||
pub fn expectStdErrMatch(self: *RunStep, bytes: []const u8) void {
|
|
||||||
const new_check: RunStep.StdIo.Check = .{
|
|
||||||
.expect_stderr_match = self.step.owner.dupe(bytes),
|
|
||||||
};
|
|
||||||
self.addCheck(new_check);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a check for stdout match as well as a check for exit code 0, if
|
|
||||||
/// there is not already an expected termination check.
|
|
||||||
pub fn expectStdOutMatch(self: *RunStep, bytes: []const u8) void {
|
|
||||||
const new_check: RunStep.StdIo.Check = .{
|
|
||||||
.expect_stdout_match = self.step.owner.dupe(bytes),
|
|
||||||
};
|
|
||||||
self.addCheck(new_check);
|
|
||||||
if (!self.hasTermCheck()) {
|
|
||||||
self.expectExitCode(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue