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,4 +1,3 @@
1//! Use `zig init --strip` next time to generate a project without comments.
2const std = @import("std");1const std = @import("std");
32
4// Although this function looks imperative, it does not perform the build3// Although this function looks imperative, it does not perform the build
src/main.zig+92-54
...@@ -4648,14 +4648,14 @@ const usage_init =...@@ -4648,14 +4648,14 @@ const usage_init =
4648fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {4648fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4649 dev.check(.init_command);4649 dev.check(.init_command);
46504650
4651 var strip = false;4651 var template: enum { example, minimal } = .example;
4652 {4652 {
4653 var i: usize = 0;4653 var i: usize = 0;
4654 while (i < args.len) : (i += 1) {4654 while (i < args.len) : (i += 1) {
4655 const arg = args[i];4655 const arg = args[i];
4656 if (mem.startsWith(u8, arg, "-")) {4656 if (mem.startsWith(u8, arg, "-")) {
4657 if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--strip")) {4657 if (mem.eql(u8, arg, "-m") or mem.eql(u8, arg, "--minimal")) {
4658 strip = true;4658 template = .minimal;
4659 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {4659 } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
4660 try fs.File.stdout().writeAll(usage_init);4660 try fs.File.stdout().writeAll(usage_init);
4661 return cleanExit();4661 return cleanExit();
...@@ -4668,40 +4668,79 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {...@@ -4668,40 +4668,79 @@ fn cmdInit(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
4668 }4668 }
4669 }4669 }
46704670
4671 var templates = findTemplates(gpa, arena, strip);
4672 defer templates.deinit();
4673
4674 const cwd_path = try introspect.getResolvedCwd(arena);4671 const cwd_path = try introspect.getResolvedCwd(arena);
4675 const cwd_basename = fs.path.basename(cwd_path);4672 const cwd_basename = fs.path.basename(cwd_path);
4676 const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename);4673 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
4687 const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);4675 const fingerprint: Package.Fingerprint = .generate(sanitized_root_name);
46884676
4689 for (template_paths) |template_path| {4677 switch (template) {
4690 if (templates.write(arena, fs.cwd(), sanitized_root_name, template_path, fingerprint)) |_| {4678 .example => {
4691 std.log.info("created {s}", .{template_path});4679 var templates = findTemplates(gpa, arena);
4692 ok_count += 1;4680 defer templates.deinit();
4693 } else |err| switch (err) {4681
4694 error.PathAlreadyExists => std.log.info("preserving already existing file: {s}", .{4682 const s = fs.path.sep_str;
4695 template_path,4683 const template_paths = [_][]const u8{
4696 }),4684 Package.build_zig_basename,
4697 else => std.log.err("unable to write {s}: {s}\n", .{ template_path, @errorName(err) }),4685 Package.Manifest.basename,
4698 }4686 "src" ++ s ++ "main.zig",
4699 }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) {4703 if (ok_count == template_paths.len) {
4702 std.log.info("see `zig build --help` for a menu of options", .{});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 },
4703 }4743 }
4704 return cleanExit();
4705}4744}
47064745
4707fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![]const u8 {4746fn sanitizeExampleName(arena: Allocator, bytes: []const u8) error{OutOfMemory}![]const u8 {
...@@ -7229,13 +7268,20 @@ fn loadManifest(...@@ -7229,13 +7268,20 @@ fn loadManifest(
7229 0,7268 0,
7230 ) catch |err| switch (err) {7269 ) catch |err| switch (err) {
7231 error.FileNotFound => {7270 error.FileNotFound => {
7232 const fingerprint: Package.Fingerprint = .generate(options.root_name);7271 writeSimpleTemplateFile(Package.Manifest.basename,
7233 var templates = findTemplates(gpa, arena, true);7272 \\.{{
7234 defer templates.deinit();7273 \\ .name = .{s},
7235 templates.write(arena, options.dir, options.root_name, Package.Manifest.basename, fingerprint) catch |e| {7274 \\ .version = "{s}",
7236 fatal("unable to write {s}: {s}", .{7275 \\ .paths = .{{""}},
7237 Package.Manifest.basename, @errorName(e),7276 \\ .fingerprint = 0x{x},
7238 });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) });
7239 };7285 };
7240 continue;7286 continue;
7241 },7287 },
...@@ -7276,7 +7322,6 @@ const Templates = struct {...@@ -7276,7 +7322,6 @@ const Templates = struct {
7276 zig_lib_directory: Cache.Directory,7322 zig_lib_directory: Cache.Directory,
7277 dir: fs.Dir,7323 dir: fs.Dir,
7278 buffer: std.ArrayList(u8),7324 buffer: std.ArrayList(u8),
7279 strip: bool,
72807325
7281 fn deinit(templates: *Templates) void {7326 fn deinit(templates: *Templates) void {
7282 templates.zig_lib_directory.handle.close();7327 templates.zig_lib_directory.handle.close();
...@@ -7305,23 +7350,9 @@ const Templates = struct {...@@ -7305,23 +7350,9 @@ const Templates = struct {
7305 };7350 };
7306 templates.buffer.clearRetainingCapacity();7351 templates.buffer.clearRetainingCapacity();
7307 try templates.buffer.ensureUnusedCapacity(contents.len);7352 try templates.buffer.ensureUnusedCapacity(contents.len);
7308 var new_line = templates.strip;
7309 var i: usize = 0;7353 var i: usize = 0;
7310 while (i < contents.len) {7354 while (i < contents.len) {
7311 if (new_line) {7355 if (contents[i] == '_' or contents[i] == '.') {
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] == '.') {
7325 // Both '_' and '.' are allowed because depending on the context7356 // Both '_' and '.' are allowed because depending on the context
7326 // one prefix will be valid, while the other might not.7357 // one prefix will be valid, while the other might not.
7327 if (std.mem.startsWith(u8, contents[i + 1 ..], "NAME")) {7358 if (std.mem.startsWith(u8, contents[i + 1 ..], "NAME")) {
...@@ -7350,8 +7381,16 @@ const Templates = struct {...@@ -7350,8 +7381,16 @@ const Templates = struct {
7350 });7381 });
7351 }7382 }
7352};7383};
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 {
7355 const cwd_path = introspect.getResolvedCwd(arena) catch |err| {7394 const cwd_path = introspect.getResolvedCwd(arena) catch |err| {
7356 fatal("unable to get cwd: {s}", .{@errorName(err)});7395 fatal("unable to get cwd: {s}", .{@errorName(err)});
7357 };7396 };
...@@ -7375,7 +7414,6 @@ fn findTemplates(gpa: Allocator, arena: Allocator, strip: bool) Templates {...@@ -7375,7 +7414,6 @@ fn findTemplates(gpa: Allocator, arena: Allocator, strip: bool) Templates {
7375 .zig_lib_directory = zig_lib_directory,7414 .zig_lib_directory = zig_lib_directory,
7376 .dir = template_dir,7415 .dir = template_dir,
7377 .buffer = std.ArrayList(u8).init(gpa),7416 .buffer = std.ArrayList(u8).init(gpa),
7378 .strip = strip,
7379 };7417 };
7380}7418}
73817419
test/tests.zig+10
...@@ -2016,6 +2016,16 @@ pub fn addCliTests(b: *std.Build) *Step {...@@ -2016,6 +2016,16 @@ pub fn addCliTests(b: *std.Build) *Step {
2016 step.dependOn(&cleanup.step);2016 step.dependOn(&cleanup.step);
2017 }2017 }
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
2019 // Test Godbolt API2029 // Test Godbolt API
2020 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {2030 if (builtin.os.tag == .linux and builtin.cpu.arch == .x86_64) {
2021 const tmp_path = b.makeTempPath();2031 const tmp_path = b.makeTempPath();