Commit 5fdefe58e4

Andrew Kelley <superjoe30@gmail.com>
2017-04-13 23:21:00
zig build system understands the concept of dependencies
See #204
1 parent a6bd3d8
Changed files (4)
src/main.cpp
@@ -218,6 +218,7 @@ int main(int argc, char **argv) {
                         "  --build-file [file]    Override path to build.zig.\n"
                         "  --verbose              Print commands before executing them.\n"
                         "  --debug-build-verbose  Print verbose debugging information for the build system itself.\n"
+                        "  --prefix [prefix]      Override default install prefix.\n"
                 , zig_exe_path);
                 return 0;
             }
std/special/build_runner.zig
@@ -22,6 +22,8 @@ pub fn main() -> %void {
     var maybe_zig_exe: ?[]const u8 = null;
     var targets = List([]const u8).init(allocator);
 
+    var prefix: ?[]const u8 = null;
+
     var arg_i: usize = 1;
     while (arg_i < os.args.count(); arg_i += 1) {
         const arg = os.args.at(arg_i);
@@ -45,6 +47,9 @@ pub fn main() -> %void {
                 builder.verbose = true;
             } else if (mem.eql(u8, arg, "--help")) {
                  return usage(&builder, maybe_zig_exe, false, &io.stdout);
+            } else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) {
+                 arg_i += 1;
+                 prefix = os.args.at(arg_i);
             } else {
                 %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
                 return usage(&builder, maybe_zig_exe, false, &io.stderr);
@@ -56,14 +61,15 @@ pub fn main() -> %void {
         }
     }
 
-    const zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
+    builder.zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
+    builder.setInstallPrefix(prefix);
 
     root.build(&builder);
 
     if (builder.validateUserInputDidItFail())
         return usage(&builder, maybe_zig_exe, true, &io.stderr);
 
-    %return builder.make(zig_exe, targets.toSliceConst());
+    %return builder.make(targets.toSliceConst());
 }
 
 fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, out_stream: &io.OutStream) -> %void {
@@ -79,22 +85,33 @@ fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool,
 
     // This usage text has to be synchronized with src/main.cpp
     %%out_stream.printf(
-        \\Usage: {} build [options]
+        \\Usage: {} build [steps] [options]
+        \\
+        \\Steps:
+        \\
+    , zig_exe);
+
+    const allocator = builder.allocator;
+    for (builder.top_level_steps.toSliceConst()) |top_level_step| {
+        %%out_stream.printf("  {s22} {}\n", top_level_step.step.name, top_level_step.description);
+    }
+
+    %%out_stream.write(
         \\
         \\General Options:
-        \\  --help                 Print this help and exit.
-        \\  --build-file [file]    Override path to build.zig.
-        \\  --verbose              Print commands before executing them.
-        \\  --debug-build-verbose  Print verbose debugging information for the build system itself.
+        \\  --help                 Print this help and exit
+        \\  --build-file [file]    Override path to build.zig
+        \\  --verbose              Print commands before executing them
+        \\  --debug-build-verbose  Print verbose debugging information for the build system itself
+        \\  --prefix [prefix]      Override default install prefix
         \\
         \\Project-Specific Options:
         \\
-    , zig_exe);
+    );
 
     if (builder.available_options_list.len == 0) {
         %%out_stream.printf("  (none)\n");
     } else {
-        const allocator = builder.allocator;
         for (builder.available_options_list.toSliceConst()) |option| {
             const name = %%fmt.allocPrint(allocator,
                 "  -D{}=({})", option.name, Builder.typeIdName(option.type_id));
std/build.zig
@@ -1,6 +1,7 @@
 const io = @import("io.zig");
 const mem = @import("mem.zig");
 const debug = @import("debug.zig");
+const assert = debug.assert;
 const List = @import("list.zig").List;
 const HashMap = @import("hash_map.zig").HashMap;
 const Allocator = @import("mem.zig").Allocator;
@@ -8,13 +9,16 @@ const os = @import("os/index.zig");
 const StdIo = os.ChildProcess.StdIo;
 const Term = os.ChildProcess.Term;
 const BufSet = @import("buf_set.zig").BufSet;
+const BufMap = @import("buf_map.zig").BufMap;
 
 error ExtraArg;
 error UncleanExit;
+error InvalidStepName;
+error DependencyLoopDetected;
+error NoCompilerFound;
 
 pub const Builder = struct {
     allocator: &Allocator,
-    exe_list: List(&Exe),
     lib_paths: List([]const u8),
     include_paths: List([]const u8),
     rpaths: List([]const u8),
@@ -23,6 +27,12 @@ pub const Builder = struct {
     available_options_list: List(AvailableOption),
     verbose: bool,
     invalid_user_input: bool,
+    zig_exe: []const u8,
+    default_step: &Step,
+    env_map: BufMap,
+    top_level_steps: List(&TopLevelStep),
+    prefix: []const u8,
+    out_dir: []const u8,
 
     const UserInputOptionsMap = HashMap([]const u8, UserInputOption, mem.hash_slice_u8, mem.eql_slice_u8);
     const AvailableOptionsMap = HashMap([]const u8, AvailableOption, mem.hash_slice_u8, mem.eql_slice_u8);
@@ -53,49 +63,91 @@ pub const Builder = struct {
         List,
     };
 
+    const TopLevelStep = struct {
+        step: Step,
+        description: []const u8,
+    };
+
     pub fn init(allocator: &Allocator) -> Builder {
         var self = Builder {
             .verbose = false,
             .invalid_user_input = false,
             .allocator = allocator,
-            .exe_list = List(&Exe).init(allocator),
             .lib_paths = List([]const u8).init(allocator),
             .include_paths = List([]const u8).init(allocator),
             .rpaths = List([]const u8).init(allocator),
             .user_input_options = UserInputOptionsMap.init(allocator),
             .available_options_map = AvailableOptionsMap.init(allocator),
             .available_options_list = List(AvailableOption).init(allocator),
+            .top_level_steps = List(&TopLevelStep).init(allocator),
+            .zig_exe = undefined,
+            .default_step = undefined,
+            .env_map = %%os.getEnvMap(allocator),
+            .prefix = undefined,
+            .out_dir = ".", // TODO organize
         };
         self.processNixOSEnvVars();
+        self.default_step = self.step("default", "Build the project");
         return self;
     }
 
     pub fn deinit(self: &Builder) {
-        self.exe_list.deinit();
         self.lib_paths.deinit();
         self.include_paths.deinit();
         self.rpaths.deinit();
+        self.env_map.deinit();
+        self.top_level_steps.deinit();
     }
 
-    pub fn addExe(self: &Builder, root_src: []const u8, name: []const u8) -> &Exe {
-        return self.addExeErr(root_src, name) %% |err| handleErr(err);
+    pub fn setInstallPrefix(self: &Builder, maybe_prefix: ?[]const u8) {
+        if (const prefix ?= maybe_prefix) {
+            self.prefix = prefix;
+            return;
+        }
+        // TODO better default
+        self.prefix = "/usr/local";
     }
 
-    pub fn addExeErr(self: &Builder, root_src: []const u8, name: []const u8) -> %&Exe {
-        const exe = %return self.allocator.create(Exe);
-        *exe = Exe {
-            .verbose = false,
-            .release = false,
-            .root_src = root_src,
-            .name = name,
-            .target = Target.Native,
-            .linker_script = LinkerScript.None,
-            .link_libs = BufSet.init(self.allocator),
-        };
-        %return self.exe_list.append(exe);
+    pub fn addExecutable(self: &Builder, name: []const u8, root_src: []const u8) -> &Exe {
+        const exe = %%self.allocator.create(Exe);
+        *exe = Exe.init(self, name, root_src);
         return exe;
     }
 
+    pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary {
+        const lib = %%self.allocator.create(CLibrary);
+        *lib = CLibrary.initStatic(self, name);
+        return lib;
+    }
+
+    pub fn addCSharedLibrary(self: &Builder, name: []const u8, ver: &const Version) -> &CLibrary {
+        const lib = %%self.allocator.create(CLibrary);
+        *lib = CLibrary.initShared(self, name, ver);
+        return lib;
+    }
+
+    pub fn addCExecutable(self: &Builder, name: []const u8) -> &CExecutable {
+        const exe = %%self.allocator.create(CExecutable);
+        *exe = CExecutable.init(self, name);
+        return exe;
+    }
+
+    pub fn addCommand(self: &Builder, cwd: []const u8, env_map: &const BufMap,
+        path: []const u8, args: []const []const u8) -> &CommandStep
+    {
+        const cmd = %%self.allocator.create(CommandStep);
+        *cmd = CommandStep.init(self, cwd, env_map, path, args);
+        return cmd;
+    }
+
+    pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
+        Version {
+            .major = major,
+            .minor = minor,
+            .patch = patch,
+        }
+    }
+
     pub fn addCIncludePath(self: &Builder, path: []const u8) {
         %%self.include_paths.append(path);
     }
@@ -108,103 +160,53 @@ pub const Builder = struct {
         %%self.lib_paths.append(path);
     }
 
-    pub fn make(self: &Builder, zig_exe: []const u8, targets: []const []const u8) -> %void {
-        if (targets.len != 0) {
-            debug.panic("TODO non default targets");
-        }
-
-        var env_map = %return os.getEnvMap(self.allocator);
-
-        for (self.exe_list.toSlice()) |exe| {
-            var zig_args = List([]const u8).init(self.allocator);
-            defer zig_args.deinit();
-
-            %return zig_args.append("build_exe");
-            %return zig_args.append(exe.root_src);
-
-            if (exe.verbose) {
-                %return zig_args.append("--verbose");
-            }
+    pub fn make(self: &Builder, step_names: []const []const u8) -> %void {
+        var wanted_steps = List(&Step).init(self.allocator);
+        defer wanted_steps.deinit();
 
-            if (exe.release) {
-                %return zig_args.append("--release");
+        if (step_names.len == 0) {
+            %%wanted_steps.append(&self.default_step);
+        } else {
+            for (step_names) |step_name| {
+                const s = %return self.getTopLevelStepByName(step_name);
+                %%wanted_steps.append(s);
             }
+        }
 
-            %return zig_args.append("--name");
-            %return zig_args.append(exe.name);
-
-            switch (exe.target) {
-                Target.Native => {},
-                Target.Cross => |cross_target| {
-                    %return zig_args.append("--target-arch");
-                    %return zig_args.append(@enumTagName(cross_target.arch));
-
-                    %return zig_args.append("--target-os");
-                    %return zig_args.append(@enumTagName(cross_target.os));
-
-                    %return zig_args.append("--target-environ");
-                    %return zig_args.append(@enumTagName(cross_target.environ));
-                },
-            }
+        for (wanted_steps.toSliceConst()) |s| {
+            %return self.makeOneStep(s);
+        }
+    }
 
-            switch (exe.linker_script) {
-                LinkerScript.None => {},
-                LinkerScript.Embed => |script| {
-                    const tmp_file_name = "linker.ld.tmp"; // TODO issue #298
-                    io.writeFile(tmp_file_name, script, self.allocator)
-                        %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err));
-                    %return zig_args.append("--linker-script");
-                    %return zig_args.append(tmp_file_name);
-                },
-                LinkerScript.Path => |path| {
-                    %return zig_args.append("--linker-script");
-                    %return zig_args.append(path);
-                },
-            }
+    fn makeOneStep(self: &Builder, s: &Step) -> %void {
+        if (s.loop_flag) {
+            %%io.stderr.printf("Dependency loop detected:\n  {}\n", s.name);
+            return error.DependencyLoopDetected;
+        }
+        s.loop_flag = true;
 
-            {
-                var it = exe.link_libs.iterator();
-                while (true) {
-                    const entry = it.next() ?? break;
-                    %return zig_args.append("--library");
-                    %return zig_args.append(entry.key);
+        for (s.dependencies.toSlice()) |dep| {
+            self.makeOneStep(dep) %% |err| {
+                if (err == error.DependencyLoopDetected) {
+                    %%io.stderr.printf("  {}\n", s.name);
                 }
-            }
-
-            for (self.include_paths.toSliceConst()) |include_path| {
-                %return zig_args.append("-isystem");
-                %return zig_args.append(include_path);
-            }
+                return err;
+            };
+        }
 
-            for (self.rpaths.toSliceConst()) |rpath| {
-                %return zig_args.append("-rpath");
-                %return zig_args.append(rpath);
-            }
+        s.loop_flag = false;
 
-            for (self.lib_paths.toSliceConst()) |lib_path| {
-                %return zig_args.append("--library-path");
-                %return zig_args.append(lib_path);
-            }
+        %return s.make();
+    }
 
-            if (self.verbose) {
-                printInvocation(zig_exe, zig_args);
+    fn getTopLevelStepByName(self: &Builder, name: []const u8) -> %&Step {
+        for (self.top_level_steps.toSliceConst()) |top_level_step| {
+            if (mem.eql(u8, top_level_step.step.name, name)) {
+                return &top_level_step.step;
             }
-            // TODO issue #301
-            var child = os.ChildProcess.spawn(zig_exe, zig_args.toSliceConst(), &env_map,
-                StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator)
-                %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err));
-            const term = %%child.wait();
-            switch (term) {
-                Term.Clean => |code| {
-                    if (code != 0) {
-                        return error.UncleanExit;
-                    }
-                },
-                else => {
-                    return error.UncleanExit;
-                },
-            };
         }
+        %%io.stderr.printf("Cannot run step '{}' because it does not exist.", name);
+        return error.InvalidStepName;
     }
 
     fn processNixOSEnvVars(self: &Builder) {
@@ -286,6 +288,16 @@ pub const Builder = struct {
         }
     }
 
+    pub fn step(self: &Builder, name: []const u8, description: []const u8) -> &Step {
+        const step_info = %%self.allocator.create(TopLevelStep);
+        *step_info = TopLevelStep {
+            .step = Step.initNoOp(name, self.allocator),
+            .description = description,
+        };
+        %%self.top_level_steps.append(step_info);
+        return &step_info.step;
+    }
+
     pub fn addUserInputOption(self: &Builder, name: []const u8, value: []const u8) -> bool {
         if (var prev_value ?= %%self.user_input_options.put(name, UserInputOption {
             .name = name,
@@ -381,7 +393,12 @@ pub const Builder = struct {
 
         return self.invalid_user_input;
     }
+};
 
+const Version = struct {
+    major: u32,
+    minor: u32,
+    patch: u32,
 };
 
 const CrossTarget = struct {
@@ -402,6 +419,8 @@ const LinkerScript = enum {
 };
 
 const Exe = struct {
+    step: Step,
+    builder: &Builder,
     root_src: []const u8,
     name: []const u8,
     target: Target,
@@ -410,6 +429,20 @@ const Exe = struct {
     verbose: bool,
     release: bool,
 
+    pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe {
+        Exe {
+            .builder = builder,
+            .verbose = false,
+            .release = false,
+            .root_src = root_src,
+            .name = name,
+            .target = Target.Native,
+            .linker_script = LinkerScript.None,
+            .link_libs = BufSet.init(builder.allocator),
+            .step = Step.init(name, builder.allocator, make),
+        }
+    }
+
     pub fn deinit(self: &Exe) {
         self.link_libs.deinit();
     }
@@ -445,11 +478,290 @@ const Exe = struct {
     pub fn setRelease(self: &Exe, value: bool) {
         self.release = value;
     }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(Exe, "step", step);
+        const exe = @ptrcast(&Exe, step);
+        const builder = exe.builder;
+
+        var zig_args = List([]const u8).init(builder.allocator);
+        defer zig_args.deinit();
+
+        %return zig_args.append("build_exe");
+        %return zig_args.append(exe.root_src);
+
+        if (exe.verbose) {
+            %return zig_args.append("--verbose");
+        }
+
+        if (exe.release) {
+            %return zig_args.append("--release");
+        }
+
+        %return zig_args.append("--name");
+        %return zig_args.append(exe.name);
+
+        switch (exe.target) {
+            Target.Native => {},
+            Target.Cross => |cross_target| {
+                %return zig_args.append("--target-arch");
+                %return zig_args.append(@enumTagName(cross_target.arch));
+
+                %return zig_args.append("--target-os");
+                %return zig_args.append(@enumTagName(cross_target.os));
+
+                %return zig_args.append("--target-environ");
+                %return zig_args.append(@enumTagName(cross_target.environ));
+            },
+        }
+
+        switch (exe.linker_script) {
+            LinkerScript.None => {},
+            LinkerScript.Embed => |script| {
+                const tmp_file_name = "linker.ld.tmp"; // TODO issue #298
+                io.writeFile(tmp_file_name, script, builder.allocator)
+                    %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err));
+                %return zig_args.append("--linker-script");
+                %return zig_args.append(tmp_file_name);
+            },
+            LinkerScript.Path => |path| {
+                %return zig_args.append("--linker-script");
+                %return zig_args.append(path);
+            },
+        }
+
+        {
+            var it = exe.link_libs.iterator();
+            while (true) {
+                const entry = it.next() ?? break;
+                %return zig_args.append("--library");
+                %return zig_args.append(entry.key);
+            }
+        }
+
+        for (builder.include_paths.toSliceConst()) |include_path| {
+            %return zig_args.append("-isystem");
+            %return zig_args.append(include_path);
+        }
+
+        for (builder.rpaths.toSliceConst()) |rpath| {
+            %return zig_args.append("-rpath");
+            %return zig_args.append(rpath);
+        }
+
+        for (builder.lib_paths.toSliceConst()) |lib_path| {
+            %return zig_args.append("--library-path");
+            %return zig_args.append(lib_path);
+        }
+
+        if (builder.verbose) {
+            printInvocation(builder.zig_exe, zig_args);
+        }
+        // TODO issue #301
+        var child = os.ChildProcess.spawn(builder.zig_exe, zig_args.toSliceConst(), &builder.env_map,
+            StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator)
+            %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err));
+        const term = %%child.wait();
+        switch (term) {
+            Term.Clean => |code| {
+                if (code != 0) {
+                    return error.UncleanExit;
+                }
+            },
+            else => {
+                return error.UncleanExit;
+            },
+        };
+    }
 };
 
-fn handleErr(err: error) -> noreturn {
-    debug.panic("error: {}\n", @errorName(err));
-}
+const CLibrary = struct {
+    step: Step,
+    name: []const u8,
+    static: bool,
+    version: Version,
+    cflags: List([]const u8),
+    source_files: List([]const u8),
+    link_libs: BufSet,
+
+    pub fn initShared(builder: &Builder, name: []const u8, version: &const Version) -> CLibrary {
+        return init(builder, name, version, false);
+    }
+
+    pub fn initStatic(builder: &Builder, name: []const u8) -> CLibrary {
+        return init(builder, name, undefined, true);
+    }
+
+    fn init(builder: &Builder, name: []const u8, version: &const Version, static: bool) -> CLibrary {
+        CLibrary {
+            .name = name,
+            .version = *version,
+            .static = static,
+            .cflags = List([]const u8).init(builder.allocator),
+            .source_files = List([]const u8).init(builder.allocator),
+            .step = Step.init(name, builder.allocator, make),
+            .link_libs = BufSet.init(builder.allocator),
+        }
+    }
+
+    pub fn linkLibrary(self: &CLibrary, name: []const u8) {
+        %%self.link_libs.put(name);
+    }
+
+    pub fn linkCLibrary(self: &CLibrary, other: &CLibrary) {
+        self.step.dependOn(&other.step);
+        %%self.link_libs.put(other.name);
+    }
+
+    pub fn addSourceFile(self: &CLibrary, file: []const u8) {
+        %%self.source_files.append(file);
+    }
+
+    pub fn addCompileFlagsForRelease(self: &CLibrary, release: bool) {
+        if (release) {
+            %%self.cflags.append("-g");
+            %%self.cflags.append("-O2");
+        } else {
+            %%self.cflags.append("-g");
+        }
+    }
+
+    pub fn addCompileFlags(self: &CLibrary, flags: []const []const u8) {
+        for (flags) |flag| {
+            %%self.cflags.append(flag);
+        }
+    }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(CLibrary, "step", step);
+        const self = @ptrcast(&CLibrary, step);
+
+        const cc = os.getEnv("CC") ?? {
+            %%io.stderr.printf("Unable to find C compiler\n");
+            return error.NoCompilerFound;
+        };
+        %%io.stderr.printf("TODO: build c library\n");
+    }
+};
+
+const CExecutable = struct {
+    step: Step,
+    name: []const u8,
+    cflags: List([]const u8),
+    source_files: List([]const u8),
+    link_libs: BufSet,
+
+    pub fn init(builder: &Builder, name: []const u8) -> CExecutable {
+        CExecutable {
+            .name = name,
+            .cflags = List([]const u8).init(builder.allocator),
+            .source_files = List([]const u8).init(builder.allocator),
+            .step = Step.init(name, builder.allocator, make),
+            .link_libs = BufSet.init(builder.allocator),
+        }
+    }
+
+    pub fn linkLibrary(self: &CExecutable, name: []const u8) {
+        %%self.link_libs.put(name);
+    }
+
+    pub fn linkCLibrary(self: &CExecutable, clib: &CLibrary) {
+        self.step.dependOn(&clib.step);
+        %%self.link_libs.put(clib.name);
+    }
+
+    pub fn addSourceFile(self: &CExecutable, file: []const u8) {
+        %%self.source_files.append(file);
+    }
+
+    pub fn addCompileFlagsForRelease(self: &CExecutable, release: bool) {
+        if (release) {
+            %%self.cflags.append("-g");
+            %%self.cflags.append("-O2");
+        } else {
+            %%self.cflags.append("-g");
+        }
+    }
+
+    pub fn addCompileFlags(self: &CExecutable, flags: []const []const u8) {
+        for (flags) |flag| {
+            %%self.cflags.append(flag);
+        }
+    }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(CExecutable, "step", step);
+        const self = @ptrcast(&CExecutable, step);
+
+        %%io.stderr.printf("TODO: build c exe\n");
+    }
+};
+
+const CommandStep = struct {
+    step: Step,
+    path: []const u8,
+    args: []const []const u8,
+    cwd: []const u8,
+    env_map: &const BufMap,
+
+    pub fn init(builder: &Builder, cwd: []const u8, env_map: &const BufMap,
+        path: []const u8, args: []const []const u8) -> CommandStep
+    {
+        CommandStep {
+            .step = Step.init(path, builder.allocator, make),
+            .path = path,
+            .args = args,
+            .cwd = cwd,
+            .env_map = env_map,
+        }
+    }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(CExecutable, "step", step);
+        const self = @ptrcast(&CommandStep, step);
+
+        %%io.stderr.printf("TODO: exec command\n");
+    }
+};
+
+const Step = struct {
+    name: []const u8,
+    makeFn: fn(self: &Step) -> %void,
+    dependencies: List(&Step),
+    loop_flag: bool,
+    done_flag: bool,
+
+    pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)->%void) -> Step {
+        Step {
+            .name = name,
+            .makeFn = makeFn,
+            .dependencies = List(&Step).init(allocator),
+            .loop_flag = false,
+            .done_flag = false,
+        }
+    }
+    pub fn initNoOp(name: []const u8, allocator: &Allocator) -> Step {
+        init(name, allocator, makeNoOp)
+    }
+
+    pub fn make(self: &Step) -> %void {
+        if (self.done_flag)
+            return;
+
+        %return self.makeFn(self);
+        self.done_flag = true;
+    }
+
+    pub fn dependOn(self: &Step, other: &Step) {
+        %%self.dependencies.append(other);
+    }
+
+    fn makeNoOp(self: &Step) -> %void {}
+};
 
 fn printInvocation(exe_name: []const u8, args: &const List([]const u8)) {
     %%io.stderr.printf("{}", exe_name);
std/mem.zig
@@ -76,7 +76,7 @@ pub const IncrementingAllocator = struct {
     }
 
     fn alloc(allocator: &Allocator, n: usize) -> %[]u8 {
-        // TODO
+        // TODO issue #320
         //const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator);
         const self = @ptrcast(&IncrementingAllocator, allocator);
         const new_end_index = self.end_index + n;