diff --git a/lib/init/build.zig b/lib/init/build.zig index 4db31713e4..969c315c2e 100644 --- a/lib/init/build.zig +++ b/lib/init/build.zig @@ -1,84 +1,113 @@ +//! Use `zig init --strip` next time to generate a project without comments. const std = @import("std"); -// Although this function looks imperative, note that its job is to -// declaratively construct a build graph that will be executed by an external -// runner. +// Although this function looks imperative, it does not perform the build +// directly and instead it mutates the build graph (`b`) that will be then +// executed by an external runner. The functions in `std.Build` implement a DSL +// for defining build steps and express dependencies between them, allowing the +// build runner to parallelize the build automatically (and the cache system to +// know when a step doesn't need to be re-run). pub fn build(b: *std.Build) void { - // Standard target options allows the person running `zig build` to choose + // Standard target options allow the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{}); - // Standard optimization options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not // set a preferred release mode, allowing the user to decide how to optimize. const optimize = b.standardOptimizeOption(.{}); + // It's also possible to define more custom flags to toggle optional features + // of this build script using `b.option()`. All defined flags (including + // target and optimize options) will be listed when running `zig build --help` + // in this directory. - // This creates a "module", which represents a collection of source files alongside + // This creates a module, which represents a collection of source files alongside // some compilation options, such as optimization mode and linked system libraries. - // Every executable or library we compile will be based on one or more modules. - const lib_mod = b.createModule(.{ - // `root_source_file` is the Zig "entry point" of the module. If a module - // only contains e.g. external object files, you can make this `null`. - // In this case the main source file is merely a path, however, in more - // complicated build scripts, this could be a generated file. + // Zig modules are the preferred way of making Zig code available to consumers. + // addModule defines a module that we intend to make available for importing + // to our consumers. We must give it a name because a Zig package can expose + // multiple modules and consumers will need to be able to specify which + // module they want to access. + const mod = b.addModule(".NAME", .{ + // The root source file is the "entry point" of this module. Users of + // this module will only be able to access public declarations contained + // in this file, which means that if you have declarations that you + // intend to expose to consumers that were defined in other files part + // of this module, you will have to make sure to re-export them from + // the root file. .root_source_file = b.path("src/root.zig"), + // Later on we'll use this module as the root module of a test executable + // which requires us to specify a target. .target = target, - .optimize = optimize, }); - // We will also create a module for our other entry point, 'main.zig'. - const exe_mod = b.createModule(.{ - // `root_source_file` is the Zig "entry point" of the module. If a module - // only contains e.g. external object files, you can make this `null`. - // In this case the main source file is merely a path, however, in more - // complicated build scripts, this could be a generated file. - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - // Modules can depend on one another using the `std.Build.Module.addImport` function. - // This is what allows Zig source code to use `@import("foo")` where 'foo' is not a - // file path. In this case, we set up `exe_mod` to import `lib_mod`. - exe_mod.addImport(".NAME_lib", lib_mod); - - // Now, we will create a static library based on the module we created above. - // This creates a `std.Build.Step.Compile`, which is the build step responsible - // for actually invoking the compiler. - const lib = b.addLibrary(.{ - .linkage = .static, - .name = ".NAME", - .root_module = lib_mod, - }); - - // This declares intent for the library to be installed into the standard - // location when the user invokes the "install" step (the default step when - // running `zig build`). - b.installArtifact(lib); - - // This creates another `std.Build.Step.Compile`, but this one builds an executable - // rather than a static library. + // Here we define an executable. An executable needs to have a root module + // which needs to expose a `main` function. While we could add a main function + // to the module defined above, it's sometimes preferable to split business + // business logic and the CLI into two separate modules. + // + // If your goal is to create a Zig library for others to use, consider if + // it might benefit from also exposing a CLI tool. A parser library for a + // data serialization format could also bundle a CLI syntax checker, for example. + // + // If instead your goal is to create an executable, consider if users might + // be interested in also being able to embed the core functionality of your + // program in their own executable in order to avoid the overhead involved in + // subprocessing your CLI tool. + // + // If neither case applies to you, feel free to delete the declaration you + // don't need and to put everything under a single module. const exe = b.addExecutable(.{ .name = ".NAME", - .root_module = exe_mod, + .root_module = b.createModule(.{ + // b.createModule defines a new module just like b.addModule but, + // unlike b.addModule, it does not expose the module to consumers of + // this package, which is why in this case we don't have to give it a name. + .root_source_file = b.path("src/main.zig"), + // Target and optimization levels must be explicitly wired in when + // defining an executable or library (in the root module), and you + // can also hardcode a specific target for an executable or library + // definition if desireable (e.g. firmware for embedded devices). + .target = target, + .optimize = optimize, + // List of modules available for import in source files part of the + // root module. + .imports = &.{ + // Here ".NAME" is the name you will use in your source code to + // import this module (e.g. `@import(".NAME")`). The name is + // repeated because you are allowed to rename your imports, which + // can be extremely useful in case of collisions (which can happen + // importing modules from different packages). + .{ .name = ".NAME", .module = mod }, + }, + }), }); // This declares intent for the executable to be installed into the - // standard location when the user invokes the "install" step (the default - // step when running `zig build`). + // install prefix when running `zig build` (i.e. when executing the default + // step). By default the install prefix is `zig-out/` but can be overridden + // by passing `--prefix` or `-p`. b.installArtifact(exe); - // This *creates* a Run step in the build graph, to be executed when another - // step is evaluated that depends on it. The next line below will establish - // such a dependency. - const run_cmd = b.addRunArtifact(exe); + // This creates a top level step. Top level steps have a name and can be + // invoked by name when running `zig build` (e.g. `zig build run`). + // This will evaluate the `run` step rather than the default step. + // For a top level step to actually do something, it must depend on other + // steps (e.g. a Run step, as we will see in a moment). + const run_step = b.step("run", "Run the app"); - // By making the run step depend on the install step, it will be run from the + // This creates a RunArtifact step in the build graph. A RunArtifact step + // invokes an executable compiled by Zig. Steps will only be executed by the + // runner if invoked directly by the user (in the case of top level steps) + // or if another step depends on it, so it's up to you to define when and + // how this Run step will be executed. In our case we want to run it when + // the user runs `zig build run`, so we create a dependency link. + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + + // By making the run step depend on the default step, it will be run from the // installation directory rather than directly from within the cache directory. - // This is not necessary, however, if the application depends on other installed - // files, this ensures they will be present and in the expected location. run_cmd.step.dependOn(b.getInstallStep()); // This allows the user to pass arguments to the application in the build @@ -87,30 +116,42 @@ pub fn build(b: *std.Build) void { run_cmd.addArgs(args); } - // This creates a build step. It will be visible in the `zig build --help` menu, - // and can be selected like this: `zig build run` - // This will evaluate the `run` step rather than the default, which is "install". - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); - - // Creates a step for unit testing. This only builds the test executable - // but does not run it. - const lib_unit_tests = b.addTest(.{ - .root_module = lib_mod, + // Creates an executable that will run `test` blocks from the provided module. + // Here `mod` needs to define a target, which is why earlier we made sure to + // set the releative field. + const mod_tests = b.addTest(.{ + .root_module = mod, }); - const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + // A run step that will run the test executable. + const run_mod_tests = b.addRunArtifact(mod_tests); - const exe_unit_tests = b.addTest(.{ - .root_module = exe_mod, + // Creates an executable that will run `test` blocks from the executable's + // root module. Note that test executables only test one module at a time, + // hence why we have to create two separate ones. + const exe_tests = b.addTest(.{ + .root_module = exe.root_module, }); - const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests); + // A run step that will run the second test executable. + const run_exe_tests = b.addRunArtifact(exe_tests); - // Similar to creating the run step earlier, this exposes a `test` step to - // the `zig build --help` menu, providing a way for the user to request - // running the unit tests. - const test_step = b.step("test", "Run unit tests"); - test_step.dependOn(&run_lib_unit_tests.step); - test_step.dependOn(&run_exe_unit_tests.step); + // A top level step for running all tests. dependOn can be called multiple + // times and since the two run steps do not depend on one another, this will + // make the two of them run in parallel. + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); + test_step.dependOn(&run_exe_tests.step); + + // Just like flags, top level steps are also listed in the `--help` menu. + // + // The Zig build system is entirely implemented in userland, which means + // that it cannot hook into private compiler APIs. All compilation work + // orchestrated by the build system will result in other Zig compiler + // subcommands being invoked with the right flags defined. You can observe + // these invocations when one fails (or you pass a flag to increase + // verbosity) to validate assumptions and diagnose problems. + // + // Lastly, the Zig build system is relatively simple and self-contained, + // and reading its source code will allow you to master it. } diff --git a/lib/init/build.zig.zon b/lib/init/build.zig.zon index 8df8489bd9..ea1e6cc8f4 100644 --- a/lib/init/build.zig.zon +++ b/lib/init/build.zig.zon @@ -7,11 +7,9 @@ // It is redundant to include "zig" in this name because it is already // within the Zig package namespace. .name = .LITNAME, - // This is a [Semantic Version](https://semver.org/). // In a future version of Zig it will be used for package deduplication. .version = "0.0.0", - // Together with name, this represents a globally unique package // identifier. This field is generated by the Zig toolchain when the // package is first created, and then *never changes*. This allows @@ -25,11 +23,9 @@ // on the following line intact, so that it shows up in code reviews that // modify the field. .fingerprint = .FINGERPRINT, // Changing this has security and trust implications. - // Tracks the earliest Zig version that the package considers to be a // supported use case. .minimum_zig_version = ".ZIGVER", - // This field is optional. // Each dependency must either provide a `url` and `hash`, or a `path`. // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. @@ -66,7 +62,6 @@ // .lazy = false, //}, }, - // Specifies the set of files and directories that are included in this package. // Only files and directories listed here are included in the `hash` that // is computed for this package. Only files listed here will remain on disk diff --git a/lib/init/src/main.zig b/lib/init/src/main.zig index 85d8c93551..a5feb15048 100644 --- a/lib/init/src/main.zig +++ b/lib/init/src/main.zig @@ -1,21 +1,10 @@ -//! By convention, main.zig is where your main function lives in the case that -//! you are building an executable. If you are making a library, the convention -//! is to delete this file and start with root.zig instead. +const std = @import("std"); +const .NAME = @import(".NAME"); pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) + // Prints to stderr, ignoring potential errors. std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); - - // stdout is for the actual output of your application, for example if you - // are implementing gzip, then only the compressed bytes should be sent to - // stdout, not any debugging messages. - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); - - try stdout.print("Run `zig build test` to run the tests.\n", .{}); - - try bw.flush(); // Don't forget to flush! + try .NAME.advancedPrint(); } test "simple test" { @@ -25,10 +14,6 @@ test "simple test" { try std.testing.expectEqual(@as(i32, 42), list.pop()); } -test "use other module" { - try std.testing.expectEqual(@as(i32, 150), lib.add(100, 50)); -} - test "fuzz example" { const Context = struct { fn testOne(context: @This(), input: []const u8) anyerror!void { @@ -39,8 +24,3 @@ test "fuzz example" { }; try std.testing.fuzz(Context{}, Context.testOne, .{}); } - -const std = @import("std"); - -/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details. -const lib = @import(".NAME_lib"); diff --git a/lib/init/src/root.zig b/lib/init/src/root.zig index 27d2be80b5..2c06f1f87c 100644 --- a/lib/init/src/root.zig +++ b/lib/init/src/root.zig @@ -1,13 +1,23 @@ -//! By convention, root.zig is the root source file when making a library. If -//! you are making an executable, the convention is to delete this file and -//! start with main.zig instead. +//! By convention, root.zig is the root source file when making a library. const std = @import("std"); -const testing = std.testing; -pub export fn add(a: i32, b: i32) i32 { +pub fn advancedPrint() !void { + // Stdout is for the actual output of your application, for example if you + // are implementing gzip, then only the compressed bytes should be sent to + // stdout, not any debugging messages. + const stdout_file = std.io.getStdOut().writer(); + var bw = std.io.bufferedWriter(stdout_file); + const stdout = bw.writer(); + + try stdout.print("Run `zig build test` to run the tests.\n", .{}); + + try bw.flush(); // Don't forget to flush! +} + +pub fn add(a: i32, b: i32) i32 { return a + b; } test "basic add functionality" { - try testing.expect(add(3, 7) == 10); + try std.testing.expect(add(3, 7) == 10); } diff --git a/src/main.zig b/src/main.zig index d0006c3db3..10cbf1f77e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4749,6 +4749,7 @@ const usage_init = \\ directory. \\ \\Options: + \\ -s, --strip Generate files without comments \\ -h, --help Print this help and exit \\ \\ @@ -4757,12 +4758,15 @@ const usage_init = fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { dev.check(.init_command); + var strip = false; { var i: usize = 0; while (i < args.len) : (i += 1) { const arg = args[i]; if (mem.startsWith(u8, arg, "-")) { - if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { + if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--strip")) { + strip = true; + } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { try io.getStdOut().writeAll(usage_init); return cleanExit(); } else { @@ -4774,7 +4778,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { } } - var templates = findTemplates(gpa, arena); + var templates = findTemplates(gpa, arena, strip); defer templates.deinit(); const cwd_path = try introspect.getResolvedCwd(arena); @@ -7332,7 +7336,7 @@ fn loadManifest( ) catch |err| switch (err) { error.FileNotFound => { const fingerprint: Package.Fingerprint = .generate(options.root_name); - var templates = findTemplates(gpa, arena); + var templates = findTemplates(gpa, arena, true); defer templates.deinit(); templates.write(arena, options.dir, options.root_name, Package.Manifest.basename, fingerprint) catch |e| { fatal("unable to write {s}: {s}", .{ @@ -7378,6 +7382,7 @@ const Templates = struct { zig_lib_directory: Cache.Directory, dir: fs.Dir, buffer: std.ArrayList(u8), + strip: bool, fn deinit(templates: *Templates) void { templates.zig_lib_directory.handle.close(); @@ -7406,9 +7411,22 @@ const Templates = struct { }; templates.buffer.clearRetainingCapacity(); try templates.buffer.ensureUnusedCapacity(contents.len); + var new_line = templates.strip; var i: usize = 0; while (i < contents.len) { - if (contents[i] == '.') { + if (new_line) { + const trimmed = std.mem.trimLeft(u8, contents[i..], " "); + if (std.mem.startsWith(u8, trimmed, "//")) { + i += std.mem.indexOfScalar(u8, contents[i..], '\n') orelse break; + i += 1; + continue; + } else { + new_line = false; + } + } + if (templates.strip and contents[i] == '\n') { + new_line = true; + } else if (contents[i] == '.') { if (std.mem.startsWith(u8, contents[i..], ".LITNAME")) { try templates.buffer.append('.'); try templates.buffer.appendSlice(root_name); @@ -7440,7 +7458,7 @@ const Templates = struct { } }; -fn findTemplates(gpa: Allocator, arena: Allocator) Templates { +fn findTemplates(gpa: Allocator, arena: Allocator, strip: bool) Templates { const cwd_path = introspect.getResolvedCwd(arena) catch |err| { fatal("unable to get cwd: {s}", .{@errorName(err)}); }; @@ -7464,6 +7482,7 @@ fn findTemplates(gpa: Allocator, arena: Allocator) Templates { .zig_lib_directory = zig_lib_directory, .dir = template_dir, .buffer = std.ArrayList(u8).init(gpa), + .strip = strip, }; }