Conversation
Summary of ChangesHello @GrapeBaBa, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a comprehensive build profile system, enabling users to define distinct configurations for their projects. It seamlessly integrates profile management into the existing configuration parsing and code generation workflows, allowing for dynamic and conditional application of build options based on the selected profile. This enhancement provides greater flexibility and control over project builds, catering to different development, testing, and production environments. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces an excellent feature by adding support for build profiles. The implementation is comprehensive, covering configuration parsing in Config.zig and code generation in ConfigBuildgen.zig. I've identified a few critical issues and areas for improvement. There are a couple of bugs in the code generation logic within ConfigBuildgen.zig that would produce invalid Zig code. Additionally, I found some memory leaks in the new parsing logic in src/Config.zig that occur on error paths. I've also included a suggestion to reduce code duplication. After addressing these points, the pull request should be in great shape.
| \\ }}; | ||
| \\ const profile_tag = blk: {{ | ||
| \\{s} | ||
| \\ }} else {{ |
There was a problem hiding this comment.
| fn profileDispatchBody(self: *ConfigBuildgen, profile_names: []const []const u8) ![]u8 { | ||
| var buffer = std.ArrayList(u8).init(self.allocator); | ||
| errdefer buffer.deinit(); | ||
| var writer = buffer.writer(); | ||
|
|
||
| for (profile_names, 0..) |name, index| { | ||
| const tag = try allocFmtId(self.allocator, "profile", name); | ||
| defer self.allocator.free(tag); | ||
|
|
||
| if (index == 0) { | ||
| try writer.print(" if (std.mem.eql(u8, profile, \"{s}\")) {{\n", .{name}); | ||
| } else { | ||
| try writer.print(" }} else if (std.mem.eql(u8, profile, \"{s}\")) {{\n", .{name}); | ||
| } | ||
|
|
||
| try writer.print(" break :blk ProfileTag.{s};\n", .{tag}); | ||
| } | ||
|
|
||
| return buffer.toOwnedSlice(); | ||
| } |
There was a problem hiding this comment.
The logic for generating the profile dispatch body is incorrect. It produces an extra closing brace in the else if condition and fails to close the final if block. This will cause a syntax error in the generated build.zig file. The function should generate a complete and valid if-else if chain.
fn profileDispatchBody(self: *ConfigBuildgen, profile_names: []const []const u8) ![]u8 {
var buffer = std.ArrayList(u8).init(self.allocator);
errdefer buffer.deinit();
var writer = buffer.writer();
for (profile_names, 0..) |name, index| {
const tag = try allocFmtId(self.allocator, "profile", name);
defer self.allocator.free(tag);
if (index == 0) {
try writer.print(" if (std.mem.eql(u8, profile, \"{s}\")) {{\n", .{name});
} else {
try writer.print(" }} else if (std.mem.eql(u8, profile, \"{s}\")) {{\n", .{name});
}
try writer.print(" break :blk ProfileTag.{s};\n", .{tag});
}
if (profile_names.len > 0) {
try writer.print(" }}", .{});
}
return buffer.toOwnedSlice();
}
There was a problem hiding this comment.
no issue here, the template has the final block
| } | ||
|
|
||
| fn parseProfiles(self: *Self, index: std.zig.Zoir.Node.Index) Error!Profiles { | ||
| var profiles: Profiles = .{}; |
There was a problem hiding this comment.
This function allocates memory for profiles and its contents, but it doesn't deallocate this memory if an error occurs during parsing, leading to a memory leak. To prevent this, you can use errdefer to ensure profiles.deinit() is called on all error paths, and then cancel the errdefer before successful returns.
Here's an example of the pattern:
fn parseProfiles(...) !Profiles {
var profiles: Profiles = .{};
errdefer profiles.deinit(self.gpa);
// ... existing function body ...
if (successful_condition) {
errdefer (void)0; // Cancel cleanup on success
return profiles;
}
// ... more logic ...
errdefer (void)0; // Cancel cleanup on success
return profiles;
}| const profile_name = try self.gpa.dupe(u8, field_name); | ||
| var profile = try self.parseProfile(field_value); | ||
|
|
||
| const gop = try map.getOrPut(profile_name); | ||
| if (!gop.found_existing) { | ||
| gop.value_ptr.* = profile; | ||
| } else { | ||
| self.gpa.free(profile_name); | ||
| profile.deinit(self.gpa); | ||
| try self.returnParseErrorFmt("duplicate profile '{s}'", .{field_name}, field_value.getAstNode(self.zoir)); | ||
| } |
There was a problem hiding this comment.
There's a potential memory leak in this block. If map.getOrPut(profile_name) fails, the memory allocated for profile_name and profile will be leaked. Using errdefer ensures these resources are cleaned up correctly on error paths.
const profile_name = try self.gpa.dupe(u8, field_name);
errdefer self.gpa.free(profile_name);
var profile = try self.parseProfile(field_value);
errdefer profile.deinit(self.gpa);
const gop = try map.getOrPut(profile_name);
if (!gop.found_existing) {
gop.value_ptr.* = profile;
errdefer (void)0; // ownership of profile transferred, cancel errdefer
errdefer (void)0; // ownership of profile_name transferred, cancel errdefer
} else {
// `profile_name` and `profile` are not stored, so they need to be freed.
// The `errdefer`s would handle this if this function returned an error,
// but `returnParseErrorFmt` is `noreturn`. So we must clean up manually.
self.gpa.free(profile_name);
profile.deinit(self.gpa);
try self.returnParseErrorFmt("duplicate profile '{s}'", .{field_name}, field_value.getAstNode(self.zoir));
}
| if (self.options_modules) |*modules| { | ||
| for (modules.values()) |*module| { | ||
| for (module.values()) |*override| override.deinit(gpa); | ||
| for (module.keys()) |k| gpa.free(k); | ||
| module.deinit(); | ||
| } | ||
| for (modules.keys()) |k| gpa.free(k); | ||
| modules.deinit(); | ||
| } |
There was a problem hiding this comment.
This deinitialization logic is duplicated in the errdefer block of the parseProfileOptionsModules function (lines 1270-1278). To improve maintainability and avoid potential bugs from inconsistent updates, this logic should be extracted into a private helper function within the Profiles struct. This function can then be called from both places.
For example:
// Inside `pub const Profiles = struct {`
fn deinitOptionsModules(modules: *ArrayHashMap(OptionsModule), gpa: std.mem.Allocator) void {
for (modules.values()) |*module| {
for (module.values()) |*override| override.deinit(gpa);
for (module.keys()) |k| gpa.free(k);
module.deinit();
}
for (modules.keys()) |k| gpa.free(k);
modules.deinit();
}This can then be called from Profile.deinit and parseProfileOptionsModules.
There was a problem hiding this comment.
Pull request overview
This pull request adds support for build profiles to the configuration system, enabling users to define multiple named profiles with option-specific overrides. Users can specify a default profile and override option values on a per-profile basis.
- Introduces a new
Profilesstructure with profile management methods and option override capabilities - Extends the parser to handle profile definitions, option overrides, and type compatibility validation
- Updates code generation to emit profile selection logic with runtime dispatch based on build options
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/sync.zig | Added new test case for basic7.zbuild.zon fixture to test profile functionality |
| test/fixtures/basic7.zbuild.zon | New test fixture demonstrating profile configuration with dev/prod profiles and option overrides |
| src/Config.zig | Added Profiles struct with nested Profile and OptionOverride types, parser methods for profile parsing, and compatibility checking logic |
| src/ConfigBuildgen.zig | Updated build.zig generation to emit profile enum, dispatch logic, and per-profile switch statements for option overrides with proper type handling |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| for (n.names, 0..) |name, i| { | ||
| const module_name = try self.gpa.dupe(u8, name.get(self.zoir)); |
There was a problem hiding this comment.
Memory leak: The module_name allocated on line 1281 will leak if parseProfileModuleOverrides fails on line 1283, since it hasn't been added to the modules HashMap yet and won't be cleaned up by the errdefer block. Add errdefer self.gpa.free(module_name); after line 1281 to handle this case.
| const module_name = try self.gpa.dupe(u8, name.get(self.zoir)); | |
| const module_name = try self.gpa.dupe(u8, name.get(self.zoir)); | |
| errdefer self.gpa.free(module_name); |
| }, | ||
| .@"enum" => |e| { | ||
| // Define a named enum type so we can use it in runtime control flow | ||
| const enum_id = try allocFmtId(self.allocator, "Enum", name); |
There was a problem hiding this comment.
Memory leak: The enum_id allocated by allocFmtId is stored in the t variable and later used in self.options.put(name, .{ .type_name = t, ... }) at line 522, but it's never freed. Since the options HashMap only stores pointers and doesn't own the strings, this allocated memory should be freed after the function completes. However, since t is used in the generated output (line 507/514) and stored in the HashMap, the lifetime management is complex. Consider tracking these allocations for proper cleanup in the deinit method, or refactor to avoid the allocation altogether.
| var final_default_expr: ?[]const u8 = default; | ||
| var switch_id: ?[]const u8 = null; | ||
|
|
||
| if (override_count > 0) { | ||
| if (!all_profiles_covered and default == null) return error.MissingProfileFallback; | ||
|
|
||
| switch_id = try allocFmtId(self.allocator, "profile_default", name); | ||
|
|
||
| try self.writeLn( | ||
| "const {s} = switch (profile_tag) {{", | ||
| .{switch_id.?}, | ||
| .{}, | ||
| ); | ||
|
|
||
| for (override_list.items) |ov| { | ||
| try self.writeLn( | ||
| ".{s} => {s},", | ||
| .{ ov.tag, ov.expr }, | ||
| .{ .indent = 8 }, | ||
| ); | ||
| } | ||
|
|
||
| if (!all_profiles_covered) { | ||
| try self.writeLn( | ||
| "else => {s},", | ||
| .{default.?}, | ||
| .{ .indent = 8 }, | ||
| ); | ||
| } | ||
|
|
||
| try self.writeLn("}};", .{}, .{}); | ||
|
|
||
| final_default_expr = switch_id.?; | ||
| } |
There was a problem hiding this comment.
Memory leak: When profile overrides exist, the default value (which may be an allocated string from the switch block at line 365-441) is assigned to final_default_expr on line 467, but then final_default_expr is overwritten with switch_id on line 499. The original default string is never freed. You should free default before overwriting final_default_expr on line 499, but only if it was allocated (not for static strings like "bool" or type names from the config).
|
|
||
| const module_struct = try self.parseStructLiteral(index); | ||
| for (module_struct.names, 0..) |option_name_node, j| { | ||
| const option_name = try self.gpa.dupe(u8, option_name_node.get(self.zoir)); |
There was a problem hiding this comment.
Memory leak: The option_name allocated on line 1300 will leak if parseProfileOptionOverride fails on line 1302, since it hasn't been added to the overrides HashMap yet and won't be cleaned up by the errdefer block. Add errdefer self.gpa.free(option_name); after line 1300 to handle this case.
| const option_name = try self.gpa.dupe(u8, option_name_node.get(self.zoir)); | |
| const option_name = try self.gpa.dupe(u8, option_name_node.get(self.zoir)); | |
| errdefer self.gpa.free(option_name); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| .@"enum" => |e| { | ||
| // Define a named enum type so we can use it in runtime control flow | ||
| const enum_id = try allocFmtId(self.allocator, "Enum", name); |
There was a problem hiding this comment.
If append fails, enum_id will be leaked since it was allocated on line 350 but never freed. Add errdefer self.allocator.free(enum_id); after line 350.
| const enum_id = try allocFmtId(self.allocator, "Enum", name); | |
| const enum_id = try allocFmtId(self.allocator, "Enum", name); | |
| errdefer self.allocator.free(enum_id); |
| }; | ||
| }, | ||
| .enum_list => |e| { | ||
| const enum_id = try allocFmtId(self.allocator, "Enum", name); |
There was a problem hiding this comment.
If append on line 361 fails, enum_id will be leaked. Add errdefer self.allocator.free(enum_id); after line 360.
| const enum_id = try allocFmtId(self.allocator, "Enum", name); | |
| const enum_id = try allocFmtId(self.allocator, "Enum", name); | |
| errdefer self.allocator.free(enum_id); |
| defer if (switch_id) |id| self.allocator.free(id); | ||
|
|
||
| if (override_count > 0) { | ||
| if (!all_profiles_covered and init_default == null) return error.MissingProfileFallback; |
There was a problem hiding this comment.
This error is returned without providing context about which option is missing a fallback. Consider using returnParseErrorFmt or including the option name in the error for better debugging.
| if (!all_profiles_covered and init_default == null) return error.MissingProfileFallback; | |
| if (!all_profiles_covered and init_default == null) | |
| return returnParseErrorFmt("Missing profile fallback for option '{s}' in module '{s}'", .{name, module_name}); |
| const profiles = maybe_profiles.?; | ||
| if (profiles.getOverride(profile_name, module_name, name)) |override_ptr| { | ||
| if (!override_ptr.isCompatible(item)) { | ||
| return error.ProfileOverrideTypeMismatch; |
There was a problem hiding this comment.
This error doesn't indicate which profile, module, or option has the type mismatch. Consider providing more context in the error message to aid debugging.
| if (n.names.len != 1) { | ||
| try self.returnParseError("profile must specify exactly one field 'options_modules'", index.getAstNode(self.zoir)); | ||
| } | ||
|
|
||
| const field_name = n.names[0].get(self.zoir); | ||
| const field_value = n.vals.at(0); | ||
| if (std.mem.eql(u8, field_name, "options_modules")) { | ||
| profile.options_modules = try self.parseProfileOptionsModules(field_value); | ||
| } else { | ||
| try self.returnParseErrorFmt("unknown field '{s}' in profile", .{field_name}, field_value.getAstNode(self.zoir)); |
There was a problem hiding this comment.
The hardcoded requirement for exactly one field named 'options_modules' makes the profile structure inflexible for future extensions. Consider allowing additional optional fields or documenting why this strict limitation exists.
| if (n.names.len != 1) { | |
| try self.returnParseError("profile must specify exactly one field 'options_modules'", index.getAstNode(self.zoir)); | |
| } | |
| const field_name = n.names[0].get(self.zoir); | |
| const field_value = n.vals.at(0); | |
| if (std.mem.eql(u8, field_name, "options_modules")) { | |
| profile.options_modules = try self.parseProfileOptionsModules(field_value); | |
| } else { | |
| try self.returnParseErrorFmt("unknown field '{s}' in profile", .{field_name}, field_value.getAstNode(self.zoir)); | |
| var found_options_modules = false; | |
| for (n.names, 0..) |name, i| { | |
| const field_name = name.get(self.zoir); | |
| const field_value = n.vals.at(@intCast(i)); | |
| if (std.mem.eql(u8, field_name, "options_modules")) { | |
| profile.options_modules = try self.parseProfileOptionsModules(field_value); | |
| found_options_modules = true; | |
| } else { | |
| // Ignore unknown fields for forward compatibility. | |
| // Optionally, log a warning or handle as needed. | |
| // For now, do nothing. | |
| } | |
| } | |
| if (!found_options_modules) { | |
| try self.returnParseError("profile must specify 'options_modules'", index.getAstNode(self.zoir)); |
wemeetagain
left a comment
There was a problem hiding this comment.
should we enforce that only fields from the overridden options module are used?
likewise, should we enforce that only options modules that are already named may be overridden?
7d47ba0 to
4dacac5
Compare
Signed-off-by: grapebaba <grapebaba@grapebabadeMacBook-Pro.local>
Signed-off-by: grapebaba <grapebaba@grapebabadeMacBook-Pro.local>
Signed-off-by: grapebaba <grapebaba@grapebabadeMacBook-Pro.local>
Signed-off-by: grapebaba <grapebaba@grapebabadeMacBook-Pro.local>
- #1: Clean stale build.zig.zon (remove deleted exe/test refs, bump to 0.3.0) - #2: Remove @ptrCast for dest_sub_path (Zig coerces comptime strings) - #3: Default modules to public (export to b.modules unless private = true) - #4: @CompileError for unknown option types + validateManifest for unknown top-level fields - #5: resolveImport returns error.ModuleNotFound instead of @Panic - #6: Remove duplicate modules.put (createModule handles it, callers don't) - #7: Add 8 comptime helper tests (toStringSlice, toEnumSlice, isIntType, isFloatType, isKnownField, validateManifest) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add architecture refactor design spec Three-phase refactor: single ZON file, std.zon.parse-based Config, static build.zig. See docs/superpowers/specs/ for details. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add implementation plan for three-phase architecture refactor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(phase-a): merge zbuild.zon into build.zig.zon — single source of truth - Rename test fixtures from .zbuild.zon to .build.zig.zon - Default zbuild_file to build.zig.zon - Remove syncManifest from cmd_sync (no more manifest generation) - Remove Manifest.zig dependency from cmd_init and cmd_fetch - Delete sync_manifest.zig and Manifest.zig (parallel data model eliminated) - Merge zbuild.zon content into build.zig.zon, delete zbuild.zon - Simplify cmd_fetch to delegate entirely to zig fetch Fixes: 2.9, 2.10, 2.12, 2.13, 4.5, 5.8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(phase-b): rewrite parser with std.zon.parse - Change fingerprint from []const u8 to u64 (matches ZON directly) - Remove all deinit methods (arena handles cleanup) - Replace hand-rolled if/else if parser with: - inline for over struct fields for typed values - fromZoirNode for standard types - Custom parsers only for Dependency, ModuleLink, Option - parseHashMap replaces both parseHashMap and parseOptionalHashMap - Ignore unknown top-level fields (enables single-file approach) - Fix dependency parsing to include hash and lazy fields - Fix parseRun to use parseString (Run = []const u8) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(phase-c): replace codegen with static build.zig + build_runner - Create build_runner.zig with configureBuild() that reads build.zig.zon and configures the build graph via direct std.Build API calls - Replace cmd_sync codegen with static build.zig template that imports zbuild and calls configureBuild - Delete ConfigBuildgen.zig (~1280 lines) and sync_build_file.zig (~38 lines) - Hand-write zbuild's own build.zig (can't self-reference) - Expose configureBuild as public API via main.zig - Update sync test to verify static template generation Eliminates string-concatenation codegen, scratch buffers, and zig fmt post-processing. Fixes bugs 1.6, 2.5, 2.6, 2.14, 3.7, 4.1, 4.3, 4.6, 5.5, 5.6, 5.10. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add comprehensive Config parser unit tests 16 inline tests covering: minimal config, modules, executables (inline and named module refs), dependencies (with hash/lazy/args), libraries, tests, runs, options, options_modules, fmts, module imports, description/ keywords, and error cases (missing required fields, invalid versions). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: complete serializer + add round-trip tests Bug fixes: - Implement serialization of libraries, objects, tests, fmts, runs sections (previously commented out, issue 2.7) - Implement enum and enum_list option serialization (previously TODO stubs, issue 2.8) - Add hash and lazy fields to dependency serialization (issue 2.10) Tests: - 8 round-trip tests that parse → serialize → re-parse and verify structural equivalence for each section type Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: wire depends_on for executables/libraries/objects The depends_on field was parsed but never used in the build graph. Now build_runner tracks install steps in a map and adds step dependencies in a final pass after all artifacts are created. Also adds parser + round-trip tests for depends_on. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: ZigEnv exit code, --no-sync loop, write_files parser, Args test Bug fixes: - ZigEnv: change 'and' to 'or' in exit code check so non-zero exits and signal terminations are properly detected (issue 1.3) - GlobalOptions: add args.next() when consuming --no-sync flag to prevent infinite loop (issue 1.4) - Config: implement write_files parser (was a stub that silently discarded all write_files entries) (issue 1.1) - Args: fix test calling non-existent Args.parse, should be Args.initFromString (issue 3.9) Tests: - GlobalOptions: --no-sync flag consumption (verifies no infinite loop) - Config: write_files parsing with file and dir items - Config: write_files round-trip serialization Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: wip_bundle memory leak and returnParseErrorFmt owned flag - main.zig: add defer wip_bundle.deinit() so the error bundle's internal allocations are freed on the success path (issue 3.4) - Config.zig: returnParseErrorFmt now sets .owned = true since the message is heap-allocated via allocPrint (issue 3.10) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: convert zbuild from CLI tool to library-only dependency zbuild no longer ships a CLI binary. Users consume it as a standard Zig dependency via build.zig.zon and call zbuild.configureBuild(b) from their build.zig. This eliminates ~1,100 lines of CLI indirection that was just wrapping zig build/fetch/init commands. Deleted: Args, GlobalOptions, ZigEnv, Package, run_zig, cmd_build, cmd_fetch, cmd_init, cmd_sync, test/sync, test/fixtures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: replace runtime parser with comptime @import("build.zig.zon") Delete Config.zig entirely (2,112 lines — parser, serializer, IR types, tests). The Zig compiler now parses build.zig.zon via @import, and build_runner.zig walks the comptime anonymous struct directly using inline for + @Hasfield. This resolves the dependency args impedance mismatch: since the manifest is comptime-known, dependency .args flow through to b.dependency() naturally without needing a runtime→comptime bridge. Project shrinks from ~2,630 to ~583 lines of source. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: manifest validation, error handling, and test coverage - #1: Clean stale build.zig.zon (remove deleted exe/test refs, bump to 0.3.0) - #2: Remove @ptrCast for dest_sub_path (Zig coerces comptime strings) - #3: Default modules to public (export to b.modules unless private = true) - #4: @CompileError for unknown option types + validateManifest for unknown top-level fields - #5: resolveImport returns error.ModuleNotFound instead of @Panic - #6: Remove duplicate modules.put (createModule handles it, callers don't) - #7: Add 8 comptime helper tests (toStringSlice, toEnumSlice, isIntType, isFloatType, isKnownField, validateManifest) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: replace field-name validation with cross-reference validation Instead of rejecting unknown top-level fields (which breaks forward compatibility with new Zig versions), validate semantic cross-references: root_module refs point to declared modules, depends_on refs point to declared artifacts, and imports reference modules/options_modules/deps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add runs field redesign spec Dual-form syntax (bare tuple for simple commands, struct with cmd + env/cwd/stdio/stdin/depends_on for complex ones), comptime validation, and cmd: step prefix to avoid collision with executable run: steps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add runs field redesign implementation plan Three tasks: validation, createRun rewrite, and tests. Covers dual-form syntax, stdin/stdin_file exclusion, depends_on wiring, and cmd: step prefix to avoid executable collision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add comptime validation for runs fields Cross-reference depends_on against declared artifacts and enforce stdin/stdin_file mutual exclusion at compile time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: rewrite createRun with dual-form comptime support Short form (bare tuple) for simple commands, long form (struct with cmd + cwd/env/inherit_stdio/stdin/stdin_file/depends_on) for complex ones. Replaces runtime string splitting with comptime toStringSlice. Step prefix changes from run: to cmd: to avoid executable collision. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add validation tests for runs dual-form syntax Covers short-form tuples, long-form structs with depends_on/env/cwd, run+executable name coexistence, and forward-compat unknown fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: consolidate validateManifest and extract installAndRegister Merge three separate artifact-section loops in validateManifest into a single pass that validates root_module refs, depends_on, and imports per item. Extract the repeated install-artifact-and-register pattern into installAndRegister helper used by all three artifact creators. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: unify string extraction and depends_on wiring patterns Use toComptimeString consistently for extracting strings from ZON tuples (wireDependsOnList, wireModuleImports). Have createRun reuse wireDependsOnList instead of duplicating the depends_on logic inline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: comptime string splitting for link_libraries Replace runtime splitScalar with comptime comptimeBaseName/comptimeAfterSep for link_libraries colon syntax. Also use toComptimeString so link_libraries accepts enum literals (consistent with imports and depends_on). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add built-in help step with project build information configureBuild now takes a comptime Options struct with a help_step field (default: "help"). When set, zig build help prints a formatted overview of the project's modules, artifacts, tests, runs, options, and dependencies — all derived from the manifest at comptime. The Options struct provides a natural extension point for future configuration without breaking the API. Breaking: configureBuild signature changed to accept a third opts parameter. Callers must add .{} as the third argument. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add documentation overhaul design spec B+C hybrid approach: README as landing page, docs/ for schema reference and motivation, examples/ as compilable annotated projects. Nukes all stale docs from the CLI tool era. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: fix spec review issues in docs overhaul design Add missing fields: zig_lib_dir/win32_manifest for libraries, zig_lib_dir for objects, passthrough/zig_lib_dir for tests, build-test step. Clarify target/optimize value types, link_libraries vs LazyPath colon syntax, LazyPath three-part form, inline module name override, help step metadata. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add documentation overhaul implementation plan 7 tasks: delete stale docs, write README/motivation/schema, create simple and full compilable examples, delete superpowers working docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove stale documentation from CLI tool era Delete MOTIVATION.md, TODO.md, AdvancedFeatures.md, and STRUCTURAL_ISSUES.md — all describe the old CLI tool architecture that no longer exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: rewrite README for library-based zbuild Pitch, before/after comparison, quickstart, feature list, and links to schema reference and examples. Replaces the stale CLI tool README. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add motivation doc explaining library approach Covers the problem (build.zig verbosity), the insight (@import + comptime), before/after comparison, and what zbuild is NOT. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add complete ZON schema reference Field tables for all manifest sections: modules, executables, libraries, objects, tests, fmts, runs, options_modules, dependencies. Plus LazyPath resolution, comptime validation, and configureBuild options. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: re-export API from build.zig and fix MakeOptions signature build.zig now re-exports configureBuild and Options so dependents can access them via @import("zbuild"). Fix help step makeFn to use Step.MakeOptions instead of Progress.Node (Zig 0.14.1 API). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add simple example project Minimal zbuild project with one executable. Uses inline manifest due to @import("build.zig.zon") type resolution constraint in Zig 0.14. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add full example project showcasing all features Demonstrates modules, executables, libraries, tests, fmts, runs (short + long form), options_modules with @import, and custom help step name. Also fixes LibraryOptions type name for Zig 0.14.1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: remove internal superpowers working documents Delete specs and plans from docs/superpowers/ — these were internal development artifacts, not user-facing documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: accept enum literals for option type field options_modules .type field now accepts both enum literals (.bool) and strings ("bool") via toComptimeString. Enum literal form is preferred in docs and examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add BuildResult API design spec configureBuild returns BuildResult with typed getters for all entities: executables, libraries, objects, tests, modules, dependencies, options_modules, runs, fmts. install_steps stays internal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: return BuildResult from configureBuild configureBuild now returns a BuildResult struct with typed getters for all created entities: executable(), library(), object(), testArtifact(), module(), dependency(), optionsModule(), run(), fmt(). BuildRunner's per-entity state is restructured into a BuildResult field that becomes the return value. install_steps remains internal for depends_on wiring. This enables post-configure artifact modification, hybrid builds, and access to private modules — the escape hatch made ergonomic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: fix BuildResult getters, propagate errors, extract help.zig Fix bug where three BuildResult getters referenced self.result.X instead of self.X. Propagate OOM errors consistently — createModule, createRun, createFmt now return Error instead of panicking. Extract ~180 lines of help text generation into src/help.zig, keeping build_runner.zig focused on build graph construction. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: depends_on supports explicit step references depends_on entries can now reference any named step using colon syntax: "test:unit", "build-test:unit", "cmd:deploy", "fmt:src", "run:myapp", "build-exe:myapp", "build-lib:mylib", "build-obj:myobj". Plain names (enum literals or strings without colons) continue to reference artifact install steps as before. Comptime validation maps step prefixes to manifest sections. Runtime wiring looks up b.top_level_steps for colon-form references. This makes tests, runs, and fmts valid depends_on targets — previously only executables, libraries, and objects worked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: move help tests to help.zig Move buildHelpText, comptimePad, describeValue tests to help.zig where the functions live. build_runner.zig keeps 20 tests, help.zig has 4. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: cleanup pass — re-export chain, version bump, depends_on for tests - build.zig imports build_runner.zig directly (skip main.zig passthrough) - Version bump to 0.4.0 (breaking API changes since 0.3.0) - wireDependsOn now handles tests via top_level_steps lookup - Delete leftover docs/specs/ - Update schema.md: depends_on step reference syntax, LazyPath collision note Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: remove main.zig, use build_runner.zig as module root main.zig was a pure passthrough with no added value. build_runner.zig is now the root source file for both the zbuild module and tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: centralize depends_on wiring in wireDependsOn Move runs depends_on wiring from createRun into wireDependsOn, alongside artifacts and tests. All dependency wiring now happens in Phase 11, keeping creation phases focused on creation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * build: migrate to Zig 0.16.0 * fix: generate typed options modules * fix: validate dependency-backed manifest references * fix: isolate inline root module namespaces * fix: support manual build.zig interop * fix: reject invalid manifest fields * test: add fixture integration coverage * fix: harden manifest validation and manual interop * fix: split manifest refs by ownership * fix: reserve unified import namespace * fix: harden fixture integration checks * fix: harden depends_on artifact refs * fix: tighten manual ref validation * docs: refine onboarding and examples * feat: add alias steps * ci: add GitHub Actions test workflow * feat: add named option presets * fix: forward target/optimize to dependencies by default `configureBuild` previously resolved every dependency that lacked an explicit `.args` field via `b.dependency(name, .{})`. Empty args mean the child build's `b.standardOptimizeOption` and `b.standardTargetOptions` fall back to their own defaults (host target, Debug) regardless of the parent's CLI flags. For C-heavy deps this is a real footgun: in Debug, Zig's default `sanitize_c=.full` emits `__ubsan_handle_*` external calls into the dep's object files. When the parent links those objects into a shared library (e.g. a Node NAPI `.node`), the symbols stay unresolved and the library fails to dlopen at runtime despite `-Doptimize=ReleaseSafe` being passed to the top-level `zig build`. Forward `runner.target` and `runner.optimize` by default so child builds inherit the parent's CLI resolution. Users who need full control (e.g. pin a dep to a specific optimize) can still supply `.args` explicitly; in that case zbuild passes their args through unchanged without injecting anything. Tested with: - zig build test (26/26) - zig build test:fixtures (all passing) * feat: support linker_allow_shlib_undefined for tests Tests that link against C symbols which the runtime resolves at dlopen time (e.g. napi C symbols Node provides when loading a `.node` file) need this flag so the linker doesn't fail standalone test binaries. Previously the option was only allowed for libraries; mirror the same behaviour for tests: - `isKnownArtifactField`: accept `linker_allow_shlib_undefined` in `tests` in addition to `libraries`. - `createTest`: copy the field onto the resulting Compile artifact post-create, mirroring `createLibrary`. - Extend the `stdlib_passthrough` fixture to set the field on both the library and the test entry, exercising both code paths. - Document the field on the `tests` schema table. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Chen Kai <grapebabamarch@outlook.com> Co-authored-by: Chen Kai <281165273grape@gmail.com>
|
similar feature now exists |
Motivation
This pull request adds support for build profiles to the configuration system, allowing users to define multiple profiles with module-specific option overrides and a default profile. It introduces a new
Profilesstructure, updates the parsing logic to handle profiles, and modifies code generation to emit profile-aware option values.Description
Profilesstruct toConfig.zig, supporting named profiles, a default profile, and per-module option overrides. Includes methods for deinitialization, querying profiles, and retrieving option overrides.profilesfield, including parsing of profiles, their options modules, and option overrides with type compatibility checks.ConfigBuildgen.zigto emit profile selection logic, including an enum of profile tags, dispatch code for selecting the active profile, and per-profile option overrides using a switch statement.