bundler
bundler copied to clipboard
Configuring path in which bundle runs
A current project of ours has us nesting a Ruby project inside a larger project which holds other pieces of a larger e-commerce system. We made this small change to allow the directory in which bundler
runs to be configurable, since our Gemfile
did not live at release_path
I appreciate the nice PR. However I should point out that this change was proposed once before in #49 and was voted down by the maintainers at the time. Generally we try to keep a tight lid on feature creep.
Is there any way you could accomplish the same thing with the existing options? For example, what if you set :bundle_gemfile
to subdir/Gemfile
?
We're using Docker on this project to aid development (while sticking to a Capistrano deployment of the entire project to a single server) and must be able to support running bundle install
inside the subdirectory. In addition to that our Ruby project is not a web project and runs from the command line. It must be executed with bundle exec
on the server from within the subdirectory. In the past I've linked in the .bundle
directory to achieve this, but that's no longer a solution given the Docker constraints. I haven't found a way for these two worlds to coexist without this new option.
@leehambley Since I think you have more Docker experience than I do, could you weigh in on this? Thanks 🙇
Thanks for pinging me @mattbrictson!
I think this falls in the realm of a workaround you should apply into your own code. Capistrano does typically document that "your project must live at the root of the repository" for this reason, it's one of our stipulations. I don't know if we still really make that claim in the docs, but we used to. Linus Torvalds also mentioned "repositories are cheap, each project should have it's own", but I can never find a citation for that quote!
In cases where you can't make it live at the root of the repository, it can be a submodule imported into a larger repository, so the directory structure is respected within the module we're expected to work with.
Consistent directory layouts are one of the cornerstones of Rails' and Capistrano's respective successes.
All that said, it's a really, really, really easy fix for you to apply to your own code, here's the content you should add:
Rake::Task["bundler:install"].clear
namespace :bundler do
task :install do
on fetch(:bundle_servers) do
within fetch(:bundle_exec_path, release_path) do
with fetch(:bundle_env_variables, {}) do
options = []
options << "--gemfile #{fetch(:bundle_gemfile)}" if fetch(:bundle_gemfile)
options << "--path #{fetch(:bundle_path)}" if fetch(:bundle_path)
unless test(:bundle, :check, *options)
options << "--binstubs #{fetch(:bundle_binstubs)}" if fetch(:bundle_binstubs)
options << "--jobs #{fetch(:bundle_jobs)}" if fetch(:bundle_jobs)
options << "--without #{fetch(:bundle_without)}" if fetch(:bundle_without)
options << "#{fetch(:bundle_flags)}" if fetch(:bundle_flags)
execute :bundle, :install, *options
end
end
end
end
end
end
Read more about this here in the docs
You might recognise the above as a vendored copy of the full function which you modified in this PR, which is totally fine. You'll still need to set(:bundle_exec_path, ...)
somewhere in your own code.
Capistrano is just Rake (ok, well not "just"), so the behaviour is to append to a task, not to replace it, for that reason we call #clear
on the task to empty it out first clear
actually clears three separate properties of a task, you might need to read the docs I linked and make a change to use #clear_actions
, YMMV.
This task is then local to your system, and you won't get bitten by changes in the Gem, as you're no longer user tasks taken from the Gem.
I'm sorry that people struggled with this so much, I personally come from a background where the world can only exist because nearly everyone I work with uses make
and rake
for everything, so "vendoring" of tasks, and overriding, augmenting, chaining all comes really naturally to us, that's why we based Capistrano on it, but not everyone knows how simple it can be!
For anyone stumbling upon this PR in 2022, here's what I did to solve this problem within my app:
# lib/tasks/bundler.rake
Rake::Task["bundler:install"].clear
namespace :bundler do
task install: :config do
on fetch(:bundle_servers) do
set :release_path, Pathname.new("#{fetch :release_path}/#{fetch :application}")
within fetch(:release_path) do
with fetch(:bundle_env_variables) do
if fetch(:bundle_check_before_install) && test(:bundle, :check)
info "The Gemfile's dependencies are satisfied, skipping installation"
else
options = []
if fetch(:bundle_binstubs) &&
fetch(:bundle_binstubs_command) == :install
options << "--binstubs #{fetch(:bundle_binstubs)}"
end
options << "--jobs #{fetch(:bundle_jobs)}" if fetch(:bundle_jobs)
options << "#{fetch(:bundle_flags)}" if fetch(:bundle_flags)
execute :bundle, :install, *options
if fetch(:bundle_binstubs) &&
fetch(:bundle_binstubs_command) == :binstubs
execute :bundle, :binstubs, '--all', '--path', fetch(:bundle_binstubs)
end
end
end
end
end
end
end
The important line is this one:
set :release_path, Pathname.new("#{fetch :release_path}/#{fetch :application}")
Everything else was copied and pasted from this task's current source code.
In my case, the app lives in a folder with the same name as the application, so I just had to append this to the end of release_path
. If you have a different setup, just update this line accordingly.
I had to do this on this specific task because release_path
was not defined in other configuration files like the Capfile
or deploy.rb
. This is also the first task where the code breaks because of my setup so if you set release_path
correctly here, it will be used across all later tasks that also break if you don't do this.