bundler icon indicating copy to clipboard operation
bundler copied to clipboard

Configuring path in which bundle runs

Open yock opened this issue 8 years ago • 5 comments

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

yock avatar Sep 13 '16 18:09 yock

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?

mattbrictson avatar Sep 13 '16 20:09 mattbrictson

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.

yock avatar Sep 20 '16 14:09 yock

@leehambley Since I think you have more Docker experience than I do, could you weigh in on this? Thanks 🙇

mattbrictson avatar Sep 20 '16 17:09 mattbrictson

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!

leehambley avatar Sep 20 '16 17:09 leehambley

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.

guilpejon avatar Jul 23 '22 14:07 guilpejon