Skip to content

Stop installing native extension build logs - #9700

Merged
hsbt merged 12 commits into
masterfrom
claude/dreamy-wu-b7dbe4
Sep 7, 2026
Merged

Stop installing native extension build logs#9700
hsbt merged 12 commits into
masterfrom
claude/dreamy-wu-b7dbe4

Conversation

@hsbt

@hsbt hsbt commented Jul 17, 2026

Copy link
Copy Markdown
Member

Building a C extension writes mkmf.log and gem_make.out into the installed extension directory under extensions/. Those logs are noisy and non-reproducible, so they pollute the install tree and break bit-for-bit reproducibility checks on distributions like Guix and Nix that verify build output.

A successful build now leaves no logs anywhere in the installation tree, neither in the extension directory nor in the unpacked gem source ext directory. A failed build still keeps the logs for inspection, but writes them to the build_info directory as <full_name>.mkmf.log and <full_name>.gem_make.out, next to the existing <full_name>.info. The build error message points at those new paths, and uninstalling the gem removes both files.

This changes a long-standing behavior. gem_make.out was previously always present in the extension directory after a successful build. Anything that relied on reading that file on success will no longer find it, and on failure it now lives in build_info rather than the extension directory.

A failed build now reports the log location like this.

ERROR: Failed to build gem native extension.

Gem files will remain installed in /path/to/gems/foo-1.0 for inspection.
Results logged to /path/to/build_info/foo-1.0.gem_make.out

Refs:
https://bugs.ruby-lang.org/issues/21995
#6259

Copilot AI review requested due to automatic review settings July 17, 2026 03:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

hsbt and others added 4 commits August 27, 2026 16:05
mkmf.log and gem_make.out were written into the installed extension
directory, polluting the install tree and breaking bit-for-bit
reproducibility checks on distros like Guix and Nix. A successful build
now leaves no logs behind, and a failed build writes them to build_info
(<full_name>.mkmf.log / <full_name>.gem_make.out) for inspection.

https://bugs.ruby-lang.org/issues/21995
#6259

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clean up the per-gem mkmf.log and gem_make.out left in build_info by a
failed extension build when the gem is uninstalled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These specs read the `make -jN` command line from `gem_make.out`, which a
successful build no longer writes. The integration specs in `install_spec`
now force the build to fail so the command lands in `build_info`, and the
`parallel_installer` specs assert on the number of jobserver slots each
gem's build acquired, which is exactly what becomes `make -jN`, instead of
reading a build log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Older system RubyGems writes the build log to the extension directory, so
under RGV=system the example read the new build_info path and hit ENOENT.
The `-j` suppression it verifies only exists with the jobserver support in
RubyGems 4.1, so gate it like its sibling examples.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hsbt
hsbt force-pushed the claude/dreamy-wu-b7dbe4 branch from 53e2342 to 16c6908 Compare August 27, 2026 07:08
hsbt and others added 8 commits September 2, 2026 11:23
Two behaviours regressed against the extension directory the logs used to
live in. "clean" is the first make target and mkmf lists mkmf.log in
CLEANFILES, so the log was already gone by the time a compile failure
reached the handler that moves it to build_info. And nothing cleared a
failed build's logs afterwards, where previously the installer wiped the
extension directory on every install, so a later successful install kept
reporting an old failure.

Park mkmf.log next to the built extension right after extconf, the way
ExtConfBuilder did before, and let build_extension decide from there
whether to drop it or keep it. Drop both logs again on success, along with
any gem_make.out an older RubyGems left in the extension directory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build_info entries are matched by stripping ".info", so the new
<full_name>.mkmf.log and <full_name>.gem_make.out never matched an
installed gem and were removed as strays, including the log the build
error had just told the user to read. Let a subdirectory declare several
suffixes and give build_info all three.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Logs for a git source land in bundler/gems/build_info, which `bundle
clean` globs as a git checkout and reports as "Removing  (build_info)"
before deleting it. Exclude it the way the sibling extensions directory
already is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Preserving a log happens while a build failure is being reported, so a
filesystem error there replaced the compile output the user needed with a
bare Errno, and the extension builder stopped raising Gem::Ext::BuildError
at all. Callers only rescue the Gem::InstallError family, so the failure
escaped as an unhandled exception. Swallow errors from moving mkmf.log and
from writing gem_make.out, and drop the "Results logged to" line when there
is no log to point at.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An extconf that exits cleanly without generating a Makefile leaves the
extension unbuilt while the install still reports success. That case used
to be described in gem_make.out; since the success path stopped writing one
it went unrecorded entirely. Let the no-Makefile case reach
Gem::Ext::Builder, which already owns where logs end up, and have it write
the explanation to build_info.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
base_dir for a git source points at the directory holding every checkout, so
logs keyed by full_name collided between revisions of the same gem and landed
in bundler/gems/build_info, which nothing prunes and which `bundle clean`
mistook for a stale checkout. Resolve them from extension_dir instead, which
Bundler already makes unique per revision and which `bundle clean` removes
along with the checkout. That also drops the clean exclusion added for the
directory this no longer creates.

Path sources are unaffected: Bundler installs them with extensions disabled,
so they never produce a build log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The git extension spec overwrote the C source after build_git had already
committed the checkout, so bundle installed the original working source and
the build succeeded, leaving no log to find. Write the broken source inside
the build_git block, and assert the log is the one this failure produced.

ExtConfBuilder no longer swallows NoMakefileError, so on JRuby, where the
fixture extconf returns before creating a Makefile, calling the class method
directly now raises instead of returning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Where a build log goes is decided by the RubyGems running the install, and
under RGV=system that is an older one which writes a bare gem_make.out into
the extension directory. Gate the example the way the other specs that assert
on log locations already are.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hsbt
hsbt merged commit 54c843c into master Sep 7, 2026
109 checks passed
@hsbt
hsbt deleted the claude/dreamy-wu-b7dbe4 branch September 7, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants