Skip to content

Add optional support for building native extensions of path sources - #9820

Open
akiellor wants to merge 5 commits into
ruby:masterfrom
Hummingbird-RegTech:andrew/path-native-ext
Open

Add optional support for building native extensions of path sources#9820
akiellor wants to merge 5 commits into
ruby:masterfrom
Hummingbird-RegTech:andrew/path-native-ext

Conversation

@akiellor

Copy link
Copy Markdown

What was the end-user or developer problem that led to this PR?

Bundler did not build native extensions for gems sourced from a local :path, which is a common setup in monorepos. That meant path-based gems that relied on compiled code could not be used unless developers built those extensions manually in their checkout. In practice, this made local development in monorepos inconsistent with out-of-tree gems, broke bundle exec / require for those gems, and added manual setup steps that made the developer experience worse.

What is your fix for the problem, implemented in this PR?

This adds a new build_path_extensions setting for local path gems with native extensions. When enabled, Bundler compiles those extensions out of tree under BUNDLE_PATH, keeps the checkout untouched, and puts the built extension directory ahead of the gem's own load paths so bundle exec and require can load the compiled artifacts.

The feature supports per-gem opt in via build_path_extensions.<gem>, reuses existing build flags from build.<gem>, avoids rebuilding when nothing changed, and rebuilds when native extension sources or build flags change. It also surfaces native extension build output for path sources and preserves the existing failure mode when a required extension has not been built or has gone stale.

Make sure the following tasks are checked

@akiellor
akiellor force-pushed the andrew/path-native-ext branch 2 times, most recently from 7dab41b to 2e1090d Compare August 27, 2026 00:42
@akiellor

akiellor commented Aug 27, 2026

Copy link
Copy Markdown
Author

This PR is an attempt to tackle this issue. Feedback is welcome 🙏.

@akiellor
akiellor force-pushed the andrew/path-native-ext branch 2 times, most recently from 15b702a to 70a12e4 Compare August 31, 2026 03:16
This adds a new `build_path_extensions` setting for local path gems with native extensions. When enabled, Bundler compiles those extensions out of tree under `BUNDLE_PATH`, keeps the checkout untouched, and puts the built extension directory ahead of the gem's own load paths so `bundle exec` and `require` can load the compiled artifacts.

The feature supports per-gem opt in via `build_path_extensions.<gem>`, reuses existing build flags from `build.<gem>`, avoids rebuilding when nothing changed, and rebuilds when native extension sources or build flags change. It also surfaces native extension build output for path sources and preserves the existing failure mode when a required extension has not been built or has gone stale.
@akiellor
akiellor force-pushed the andrew/path-native-ext branch from 70a12e4 to 8da90ad Compare September 2, 2026 18:37
@akiellor

akiellor commented Sep 2, 2026

Copy link
Copy Markdown
Author

@hsbt, do you have any feedback on this proposed change? IMHO, it would meaningfully improve the developer experience of working with gems with native extensions in a monorepo.

Thanks for all your efforts on rubygems.

def missing_extensions?(spec)
return false unless extension_dir_for(spec)

spec.missing_extensions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

spec.missing_extensions? is forced to false for Bundler::Source::Path by FixPathSourceMissingExtensions, which rubygems_ext.rb prepends whenever Gem.rubygems_version < 3.5.22. Ruby 3.2 ships RubyGems 3.4.19, so build_path_extensions builds nothing there, and the new specs will fail on the system-rubygems-bundler (ruby-3.2) job.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Interesting.

I've reimplemented the feature to avoid missing_extensions? to drive if the extension should be built.

An alternative would have been to version gate this feature and report an error if is attempted to be used on an old version of rubygems.

Your feedback on a preferred approach is welcome 🙏

Comment thread lib/bundler/source/path/installer.rb Outdated
SharedHelpers.filesystem_access(@extension_build_dir, :create) do |path|
FileUtils.rm_rf(path)
FileUtils.mkdir_p(path)
FileUtils.cp_r("#{@spec.full_gem_path}/.", path)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When the gem directory contains BUNDLE_PATH -- gem "app", path: "." with bundle config set path vendor/bundle -- the staging directory sits inside the copy source, and FileUtils.cp_r recurses into itself until Errno::ENAMETOOLONG. That writes a deep tree into the checkout this feature promises never to touch.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. I've added a spec and fix to avoid this problem.

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