Skip to content

fix(builder/go): register the generated C header after compiling - #7113

Merged
caarlos0 merged 4 commits into
mainfrom
fix/go-c-headers
Sep 7, 2026
Merged

caarlos0 merged 4 commits into
mainfrom
fix/go-c-headers

Conversation

@caarlos0

@caarlos0 caarlos0 commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

Closes #6897

Problem

getHeaderArtifactForLibrary returns nil when the header file is absent, and it was called before base.Exec:

mains, allbinaries, err := checkBuild(build, options)
...
for _, a := range allbinaries {           // header discovery
    ...
}
...
if err := base.Exec(ctx, cmd, env, build.Dir); err != nil {   // compiler

The issue describes this as affecting clean builds. It is worse than that: GoReleaser refuses to run against a non-empty dist (dist is not empty, remove it before running goreleaser), so the header can never exist when discovery runs. Every c-archive and c-shared build was affected.

Verified on main with buildmode: c-shared — dist/lib_darwin_arm64_v8.0/cexport.h exists on disk, artifacts.json lists only the library, and tar tzf shows the archive contains cexport.dylib alone. Headers feed archives, nfpms, makeself and reportsizes, so a shipped C library was unusable by any consumer.

Introduced by c3bfb5a5b (#6254), which correctly stopped registering nonexistent headers but placed the check before compilation.

Fix

Header discovery moves into the loop that registers the binaries, after the build. The header also gets base.ChTimes, so a mod_timestamp build stays reproducible — without it the header would carry a wall-clock mtime into the archive while the library was pinned.

Tests

  • TestBuildCgoLibraryRegistersGeneratedHeader — real c-archive and c-shared builds, asserting the header artifact and its mod timestamp.
  • TestBuildCgoLibraryFailureDoesNotRegisterStaleHeader — keeps c3bfb5a5b's guarantee: a failed build must not register a leftover header.
  • TestBuildCgoLibraryHeaderTimestampFailure - a real C archive build verifies that a header-specific timestamp error is returned, no artifacts are registered, and the library timestamp is set. This covers the new error path without changing the coverage threshold.

Header discovery ran before the compiler, and it returns nil when the
file is absent. GoReleaser refuses a non-empty dist, so the header never
existed at that point and was never registered: every c-archive and
c-shared build shipped a library without its header.

Discovery now runs after the build, in the loop that registers the
binaries, and the header gets the same mod timestamp as its library so
archives containing both stay reproducible.

Closes #6897

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Deploying goreleaser with  Cloudflare Pages  Cloudflare Pages

Latest commit: a85cd79
Status: ✅  Deploy successful!
Preview URL: https://26e7d0f7.goreleaser.pages.dev
Branch Preview URL: https://fix-go-c-headers.goreleaser.pages.dev

View logs

@pull-request-size pull-request-size Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Sep 6, 2026
@codecov

codecov Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.52%. Comparing base (be63647) to head (a85cd79).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7113      +/-   ##
==========================================
+ Coverage   91.47%   91.52%   +0.04%     
==========================================
  Files         191      191              
  Lines       15877    15874       -3     
==========================================
+ Hits        14524    14529       +5     
+ Misses       1353     1345       -8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

caarlos0 and others added 3 commits September 6, 2026 22:35
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Address the PR patch-coverage failure with a real C archive build. Assert that a header-specific timestamp error is returned, no artifacts are registered, and the library timestamp was set successfully.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
Fix the Windows CI assertion to match the native artifact path used before registration. Keep the header-specific error and artifact assertions intact.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>

@caarlos0 caarlos0 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No findings. I could not find a correctness, scope, compatibility or determinism problem in this change.

What I verified

  • The premise holds. getHeaderArtifactForLibrary does an os.Stat, and GoReleaser refuses a non-empty dist, so the pre-Exec call always returned nil. The regression came from c3bfb5a (#6254), which added the existence check but left the call before compilation. Before that commit the header was registered unconditionally, so this restores behaviour rather than adding it.
  • The move is complete. The type guard is unchanged, so NoMainCheck and wasm c-shared (artifactType returns Binary) behave exactly as before. strings.TrimSuffix(a.Path, options.Ext) matches how the Go toolchain derives the header name (hdrTarget := target[:len(target)-len(filepath.Ext(target))] + ".h"), and extFor only ever returns single-dot extensions, so the two formulas agree.
  • No multi-library collision. go build -buildmode=c-archive -o out ./... fails with -buildmode=c-archive requires exactly one main package (reproduced locally), so len(allbinaries) == 1 whenever the branch fires. Artifact insertion order is therefore identical to before: [header, library].
  • No template leakage. Template.WithExtraFields calls copying(), which allocates a fresh Fields map, so tpl.WithArtifact(ha) cannot leak .ArtifactExt ".h" into a later use of tpl.
  • The ChTimes call on the header is justified, not scope creep. pkg/archive/tar and pkg/archive/zip fall back to the file's own mtime unless archives.files[].info.mtime is set, so without it the header would carry wall-clock time into an otherwise reproducible archive.
  • The tests earn their place. They fail against main ("[]" should have 1 item(s), but has 0), and deleting only the header ChTimes call makes the mtime assertion fail with a wall-clock time, so that assertion really covers the reproducibility claim.
  • Portability is proven, not assumed. These are the first tests here that need a C toolchain, so I checked the real matrix instead of reasoning about it: test (ubuntu-latest) and test (windows-latest) both pass at a85cd79. CGO_ENABLED=1 is set explicitly in build.Env, so an ambient CGO_ENABLED=0 cannot break them, and the failure test needs no compiler at all. Cost is about +13s on the Windows job.

Worth a changelog line

For c-archive and c-shared users, archive contents and checksums change relative to v2.13.x, because the .h comes back into archives and nFPM packages. One knock-on: a single archives entry that mixes native c-shared (gets a header) with wasm c-shared (no header) will now trip ErrArchiveDifferentBinaryCount, since checkArtifacts compares per-platform counts. That was also the pre-#6254 behaviour, so it is a restoration and not a new break, but it will look like a break to anyone upgrading from v2.13.x.

Disclosure

This review was produced by GitHub Copilot CLI, a bot, and includes independent maintainer-perspective and adversarial passes. It is posted as a comment rather than an approval only because GitHub does not allow approving your own pull request. No local build, lint or fmt was run, per instruction; CI covers them and is green.

@caarlos0
caarlos0 added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit 4a635ab Sep 7, 2026
19 checks passed
@caarlos0
caarlos0 deleted the fix/go-c-headers branch September 7, 2026 02:13
@github-actions github-actions Bot added this to the v2.18.0 milestone Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

go: clean C-library builds omit generated header artifacts

1 participant

⌂ 换网址