Bundler 1.8
moves the executables directory in generated gemspecs from bin/
to exe/
.
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
This means that the Bundler-generated gems can use and commit binstubs,
such as bin/rake
, to the bin/
directory. Only files in the exe/
directory
will be built with the gem. Prior to this change, we would need to either not
commit binstubs or change the gemspec not to include all files in bin
as
executables.
There’s nothing that needs to be done for existing gems. To modify an existing gem
to use this convention, we only need to move the executable(s), if any, into exe/
,
and modify the gemspec executables
directory to exe/
, as above.
Using the exe/
directory for gem executables frees up bin/
to be used for
bundle binstubs rspec-core; bin/rspec
and other libraries that have adopted this
convention, such as Rails, which installs the scripts bin/rails
, bin/rake
, and
bin/setup
with all generated apps.
Background
This is a new convention. The current practice of both specifying bin/
as the
executables
directory and where we put binstubs and other development-only
executables such as bin/rails
or bin/setup
, meant that Bundler-generated
gems with executables were quite likely to have these development executables
included in the built gem, and then installed along with the gem.
Rather than make the gemspec template more restrictive by only specifying one
executable in bin/
as an executable
, the Bundler team has chosen to use a
different directory, exe/
, as the executables
directory in the template.
This change is just part of the evolving conventions in gem development. RSpec,
for example, has had its executable in exe
since 2011.
Here’s an example of the kind of buggy behavior you might see when binstubs are build with a gem as executables.
Demo
bundle gem new_gem
git add .; git commit -am "Initial Commit"
bundle binstub rake
git add bin/rake; git commit -am "bundle binstub rake"
bundle
which rake #=> ~/.rvm/gems/ruby-2.1.5/bin/rake
cat `which rake`
and
#!/usr/bin/env ruby_executable_hooks
#
# This file was generated by RubyGems.
#
# The application 'new_gem' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
if ARGV.first
str = ARGV.first
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
end
gem 'new_gem', version
load Gem.bin_path('new_gem', 'rake', version)
The problem is found in the last two lines:
gem 'new_gem', version
load Gem.bin_path('new_gem', 'rake', version)
The rake executable no longer uses the rake
gem. It now requires rake
from new_gem
.
Now, when I run rake elsewhere, I get the warning:
Bundler is using a binstub that was created for a different gem.
This is deprecated, in future versions you may need to `bundle binstub new_gem` to work around a system/bundle conflict.
This happened because of the line in the gemspec that installed all git-versioned files in bin/
:
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
To fix our rake install:
gem pristine rake
# (In your gem folder, you may also want to run `bundle exec gem pristine rake`)
cat `which rake`
and
#!/usr/bin/env ruby_executable_hooks
#
# This file was generated by RubyGems.
#
# The application 'rake' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require 'rubygems'
version = ">= 0"
if ARGV.first
str = ARGV.first
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
version = $1
ARGV.shift
end
end
gem 'rake', version
load Gem.bin_path('rake', 'rake', version)
To fix our gemspec:
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
Or just delete the line altogether, since new_gem doesn’t have an executable.
If it did, we could continue to use bin/
, create the file as bin/new_gem
and
specify it as the only executable.
spec.executables = "new_gem"
Or use the new exe/
convention, create a file such as exe/new_gem
, and not worry.
Enjoy!