Skip to content

support linker_allow_shlib_undefined#5

Merged
wemeetagain merged 1 commit into
mainfrom
bing/linker-allow-shlib-undefined
Dec 11, 2025
Merged

support linker_allow_shlib_undefined#5
wemeetagain merged 1 commit into
mainfrom
bing/linker-allow-shlib-undefined

Conversation

@spiral-ladder

Copy link
Copy Markdown
Member

unblocks ChainSafe/zapi#1

@spiral-ladder spiral-ladder self-assigned this Dec 11, 2025
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @spiral-ladder, 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 enhances the build system by adding support for the linker_allow_shlib_undefined option. This feature provides finer control over linker behavior for shared libraries, specifically allowing or disallowing undefined symbols. The change involves updating the configuration structure, its parsing, and the generation of build system code to reflect this new setting, which is essential for unblocking a related project.

Highlights

  • New Configuration Option: Introduced a new optional boolean field, linker_allow_shlib_undefined, to the Library configuration struct. This allows specifying whether the linker should permit undefined symbols in shared libraries.
  • Configuration Parsing: Added logic to the configuration parser to correctly interpret and apply the linker_allow_shlib_undefined setting when it is present in a library's configuration.
  • Build Configuration Generation: Implemented the necessary changes in the build configuration generator to propagate the linker_allow_shlib_undefined setting into the generated build system files for libraries.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for the linker_allow_shlib_undefined option for libraries. The implementation is straightforward and correct. While reviewing, I noticed a small, pre-existing omission in the library parser where the version field was not being handled. I've included a suggestion to address this for completeness.

Comment thread src/Config.zig
Comment on lines +1038 to 1040
} else if (std.mem.eql(u8, field_name, "linker_allow_shlib_undefined")) {
library.linker_allow_shlib_undefined = try self.parseBool(field_value);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While adding support for linker_allow_shlib_undefined, I noticed that the version field for a Library is not being parsed. The Library struct has a version field, and it's used when generating the build.zig file, but it's missing from the parser logic here. This is inconsistent with parseExecutable, which does handle its version field.

This seems like a good opportunity to fix this omission. I've updated the suggestion to include parsing for the version field.

            } else if (std.mem.eql(u8, field_name, "version")) {
                library.version = try self.parseVersionString(field_value);
            } else if (std.mem.eql(u8, field_name, "linker_allow_shlib_undefined")) {
                library.linker_allow_shlib_undefined = try self.parseBool(field_value);
            }

@wemeetagain wemeetagain merged commit 2320194 into main Dec 11, 2025
wemeetagain added a commit that referenced this pull request Mar 17, 2026
- #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>
wemeetagain added a commit that referenced this pull request May 11, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants