authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-23 11:59:19+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-07-25 16:24:02+01:00
logbb71a18ede1f976ad6d6bb7e84b88f0edc7d30e9
tree0e9e71ae3feab6895666cade38096a1a8e3e2bda
parentd407c4e52b49a23ba039c6e2f324bed95bc5130c

init: replace '--strip' with '--minimal'

This option never worked properly (it emitted wrongly-formatted code), and it doesn't seem particularly *useful* -- someone who's proficient enough with `std.Build` to not need explanations probably just wants to write their own thing. Meanwhile, the use case of writing your own `build.zig` was extremely poorly served, because `build.zig.zon` *needs* to be generated programmatically for a correct `fingerprint`, but the only ways to do that were to a) do it wrong and get an error, or b) get the full init template and delete the vast majority of it. Both of these were pretty clunky, and `-s` didn't really help. So, replace this flag with a new one, `--minimal`/`-m`, which uses a different template. This template is trivial enough that I opted to just hardcode it into the compiler for simplicity. The main job of `zig init -m` is to generate a correct `build.zig.zon` (if it is unable to do this, it exits with a fatal error). In addition, it will *attempt* to generate a tiny stub `build.zig`, with only an `std` import and an empty `pub fn build`. However, if `build.zig` already exists, it will avoid overwriting it, and doesn't even complain. This serves the use case of writing `build.zig` manually and *then* running `zig init -m` to generate an appropriate `build.zig.zon`.

3 files changed, 102 insertions(+), 55 deletions(-)

lib/init/build.zig-1
......@@ -1,4 +1,3 @@
1//! Use `zig init --strip` next time to generate a project without comments.
21const std = @import("std");
32
43// Although this function looks imperative, it does not perform the build
src/main.zig+92-54
......@@ -4648,14 +4648,14 @@ const usage_init =
46484648fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46494649 dev.check(.init_command);
46504650
4651 var strip = false;
4651 var template: enum { example, minimal } = .example;
46524652 {
46534653 var i: usize = 0;
46544654 while (i < args.len) : (i += 1) {
46554655 const arg = args[i];
46564656 if (mem.startsWith(u8, arg, "-")) {
4657 if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--strip")) {
4658 strip = true;
4657 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {
4658 template = .minimal;
46594659 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
46604660 try fs.File.stdout().writeAll(usage_init);
46614661 return cleanExit();
......@@ -4668,40 +4668,79 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
46684668 }
46694669 }
46704670
4671 var templates = findTemplates(gpa, arena, strip);
4672 defer templates.deinit();
4673
46744671 const cwd_path = try introspect.getResolvedCwd(arena);
46754672 const cwd_basename = fs.path.basename(cwd_path);
46764673 const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);
46774674
4678 const s = fs.path.sep_str;
4679 const template_paths = [_][]const u8{
4680 Package.build_zig_basename,
4681 Package.Manifest.basename,
4682 "src" ++ s ++ "main.zig",
4683 "src" ++ s ++ "root.zig",
4684 };
4685 var ok_count: usize = 0;
4686
46874675 const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
46884676
4689 for (template_paths) |template_path| {
4690 if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {
4691 std.log.info("created {s}", .{template_path});
4692 ok_count += 1;
4693 } else |err| switch (err) {
4694 error.PathAlreadyExists => std.log.info("preserving already existing file: {s}", .{
4695 template_path,
4696 }),
4697 else => std.log.err("unable to write {s}: {s}\n", .{ template_path, @errorName(err) }),
4698 }
4699 }
4677 switch (template) {
4678 .example => {
4679 var templates = findTemplates(gpa, arena);
4680 defer templates.deinit();
4681
4682 const s = fs.path.sep_str;
4683 const template_paths = [_][]const u8{
4684 Package.build_zig_basename,
4685 Package.Manifest.basename,
4686 "src" ++ s ++ "main.zig",
4687 "src" ++ s ++ "root.zig",
4688 };
4689 var ok_count: usize = 0;
4690
4691 for (template_paths) |template_path| {
4692 if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {
4693 std.log.info("created {s}", .{template_path});
4694 ok_count += 1;
4695 } else |err| switch (err) {
4696 error.PathAlreadyExists => std.log.info("preserving already existing file: {s}", .{
4697 template_path,
4698 }),
4699 else => std.log.err("unable to write {s}: {s}\n", .{ template_path, @errorName(err) }),
4700 }
4701 }
47004702
4701 if (ok_count == template_paths.len) {
4702 std.log.info("see `zig build --help` for a menu of options", .{});
4703 if (ok_count == template_paths.len) {
4704 std.log.info("see `zig build --help` for a menu of options", .{});
4705 }
4706 return cleanExit();
4707 },
4708 .minimal => {
4709 writeSimpleTemplateFile(Package.Manifest.basename,
4710 \\.{{
4711 \\ .name = .{s},
4712 \\ .version = "{s}",
4713 \\ .paths = .{{""}},
4714 \\ .fingerprint = 0x{x},
4715 \\}}
4716 \\
4717 , .{
4718 sanitized_root_name,
4719 build_options.version,
4720 fingerprint.int(),
4721 }) catch |err| switch (err) {
4722 else => fatal("failed to create '{s}': {s}", .{ Package.Manifest.basename, @errorName(err) }),
4723 error.PathAlreadyExists => fatal("refusing to overwrite '{s}'", .{Package.Manifest.basename}),
4724 };
4725 writeSimpleTemplateFile(Package.build_zig_basename,
4726 \\const std = @import("std");
4727 \\pub fn build(b: *std.Build) void {{
4728 \\ _ = b; // stub
4729 \\}}
4730 \\
4731 , .{}) catch |err| switch (err) {
4732 else => fatal("failed to create '{s}': {s}", .{ Package.build_zig_basename, @errorName(err) }),
4733 // `build.zig` already existing is okay: the user has just used `zig init` to set up
4734 // their `build.zig.zon` *after* writing their `build.zig`. So this one isn't fatal.
4735 error.PathAlreadyExists => {
4736 std.log.info("successfully populated '{s}', preserving existing '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4737 return cleanExit();
4738 },
4739 };
4740 std.log.info("successfully populated '{s}' and '{s}'", .{ Package.Manifest.basename, Package.build_zig_basename });
4741 return cleanExit();
4742 },
47034743 }
4704 return cleanExit();
47054744}
47064745
47074746fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![]const u8 {
......@@ -7229,13 +7268,20 @@ fn loadManifest(
72297268 0,
72307269 ) catch |err| switch (err) {
72317270 error.FileNotFound => {
7232 const fingerprint: Package.Fingerprint = .generate(options.root_name);
7233 var templates = findTemplates(gpa, arena, true);
7234 defer templates.deinit();
7235 templates.write(arena, options.dir, options.root_name, Package.Manifest.basename, fingerprint) catch |e| {
7236 fatal("unable to write {s}: {s}", .{
7237 Package.Manifest.basename, @errorName(e),
7238 });
7271 writeSimpleTemplateFile(Package.Manifest.basename,
7272 \\.{{
7273 \\ .name = .{s},
7274 \\ .version = "{s}",
7275 \\ .paths = .{{""}},
7276 \\ .fingerprint = 0x{x},
7277 \\}}
7278 \\
7279 , .{
7280 options.root_name,
7281 build_options.version,
7282 Package.Fingerprint.generate(options.root_name).int(),
7283 }) catch |e| {
7284 fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) });
72397285 };
72407286 continue;
72417287 },
......@@ -7276,7 +7322,6 @@ const Templates = struct {
72767322 zig_lib_directory: Cache.Directory,
72777323 dir: fs.Dir,
72787324 buffer: std.ArrayList(u8),
7279 strip: bool,
72807325
72817326 fn deinit(templates: *Templates) void {
72827327 templates.zig_lib_directory.handle.close();
......@@ -7305,23 +7350,9 @@ const Templates = struct {
73057350 };
73067351 templates.buffer.clearRetainingCapacity();
73077352 try templates.buffer.ensureUnusedCapacity(contents.len);
7308 var new_line = templates.strip;
73097353 var i: usize = 0;
73107354 while (i < contents.len) {
7311 if (new_line) {
7312 const trimmed = std.mem.trimLeft(u8, contents[i..], " ");
7313 if (std.mem.startsWith(u8, trimmed, "//")) {
7314 i += std.mem.indexOfScalar(u8, contents[i..], '\n') orelse break;
7315 i += 1;
7316 continue;
7317 } else {
7318 new_line = false;
7319 }
7320 }
7321
7322 if (templates.strip and contents[i] == '\n') {
7323 new_line = true;
7324 } else if (contents[i] == '_' or contents[i] == '.') {
7355 if (contents[i] == '_' or contents[i] == '.') {
73257356 // Both '_' and '.' are allowed because depending on the context
73267357 // one prefix will be valid, while the other might not.
73277358 if (std.mem.startsWith(u8, contents[i + 1 ..], "NAME")) {
......@@ -7350,8 +7381,16 @@ const Templates = struct {
73507381 });
73517382 }
73527383};
7384fn writeSimpleTemplateFile(file_name: []const u8, comptime fmt: []const u8, args: anytype) !void {
7385 const f = try fs.cwd().createFile(file_name, .{ .exclusive = true });
7386 defer f.close();
7387 var buf: [4096]u8 = undefined;
7388 var fw = f.writer(&buf);
7389 try fw.interface.print(fmt, args);
7390 try fw.interface.flush();
7391}
73537392
7354fn findTemplates(gpa: Allocator, arena: Allocator, strip: bool) Templates {
7393fn findTemplates(gpa: Allocator, arena: Allocator) Templates {
73557394 const cwd_path = introspect.getResolvedCwd(arena) catch |err| {
73567395 fatal("unable to get cwd: {s}", .{@errorName(err)});
73577396 };
......@@ -7375,7 +7414,6 @@ fn findTemplates(gpa: Allocator, arena: Allocator, strip: bool) Templates {
73757414 .zig_lib_directory = zig_lib_directory,
73767415 .dir = template_dir,
73777416 .buffer = std.ArrayList(u8).init(gpa),
7378 .strip = strip,
73797417 };
73807418}
73817419
test/tests.zig+10
......@@ -2016,6 +2016,16 @@ pub fn addCliTests(b: *std.Build) *Step {
20162016 step.dependOn(&cleanup.step);
20172017 }
20182018
2019 {
2020 // Test `zig init -m`.
2021 const tmp_path = b.makeTempPath();
2022 const init_exe = b.addSystemCommand(&.{ b.graph.zig_exe, "init", "-m" });
2023 init_exe.setCwd(.{ .cwd_relative = tmp_path });
2024 init_exe.setName("zig init -m");
2025 init_exe.expectStdOutEqual("");
2026 init_exe.expectStdErrEqual("info: successfully populated 'build.zig.zon' and 'build.zig'\n");
2027 }
2028
20192029 // Test Godbolt API
20202030 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
20212031 const tmp_path = b.makeTempPath();