├── .github ├── release-drafter.yml └── workflows │ └── push.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Dangerfile ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── capistrano-bundler.gemspec └── lib ├── capistrano-bundler.rb └── capistrano ├── bundler.rb ├── bundler ├── hooks.rb └── tasks.rb └── tasks ├── bundler.cap └── hooks.cap /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: "$NEXT_PATCH_VERSION" 2 | tag-template: "v$NEXT_PATCH_VERSION" 3 | categories: 4 | - title: "⚠️ Breaking Changes" 5 | label: "⚠️ Breaking" 6 | - title: "✨ New Features" 7 | label: "✨ Feature" 8 | - title: "🐛 Bug Fixes" 9 | label: "🐛 Bug Fix" 10 | - title: "📚 Documentation" 11 | label: "📚 Docs" 12 | - title: "🏠 Housekeeping" 13 | label: "🏠 Housekeeping" 14 | change-template: "- $TITLE (#$NUMBER) @$AUTHOR" 15 | no-changes-template: "- No changes" 16 | template: | 17 | $CHANGES 18 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | cache: bundler 4 | branches: 5 | only: 6 | - master 7 | rvm: 8 | - 2.7.1 9 | script: bundle exec danger 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Release notes for this project are kept here: https://github.com/capistrano/bundler/releases 2 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | danger.import_dangerfile(github: "capistrano/danger", branch: "no-changelog") 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in capistrano-bundler.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Capistrano, your one stop deployment shop. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Capistrano::Bundler 2 | 3 | Bundler specific tasks for Capistrano v3: 4 | 5 | ```sh 6 | $ cap production bundler:install 7 | ``` 8 | 9 | It also prefixes certain binaries to use `bundle exec`. 10 | 11 | ## Installation 12 | 13 | Add these lines to your application's Gemfile **[Recommended]**: 14 | 15 | ```ruby 16 | gem 'capistrano', '~> 3.6' 17 | gem 'capistrano-bundler', '~> 2.0' 18 | ``` 19 | 20 | And then execute: 21 | 22 | ```sh 23 | $ bundle 24 | ``` 25 | 26 | Or install it yourself as: 27 | 28 | ```sh 29 | $ gem install capistrano-bundler 30 | ``` 31 | 32 | ## Usage 33 | 34 | Require in `Capfile` to use the default task: 35 | 36 | ```ruby 37 | require 'capistrano/bundler' 38 | ``` 39 | 40 | The task will run before `deploy:updated` as part of Capistrano's default deploy, or can be run in isolation with `cap production bundler:install`. 41 | 42 | In order for Bundler to work efficiently on the server, its project configuration directory (`/.bundle/`) should be persistent across releases. 43 | You need to add it to the `linked_dirs` Capistrano variable: 44 | 45 | Capistrano **3.5**+: 46 | 47 | ```ruby 48 | # config/deploy.rb 49 | 50 | append :linked_dirs, '.bundle' 51 | ``` 52 | 53 | Capistrano < 3.5: 54 | 55 | ```ruby 56 | # config/deploy.rb 57 | 58 | set :linked_dirs, fetch(:linked_dirs, []) << '.bundle' 59 | ``` 60 | 61 | It will still work fine with non-persistent configuration directory, but then it will have to re-resolve all gems on each deploy. 62 | 63 | By default, the plugin adds `bundle exec` prefix to common executables listed in `bundle_bins` option. This currently applies for `gem`, `rake` and `rails`. 64 | 65 | You can add any custom executable to this list: 66 | 67 | ```ruby 68 | set :bundle_bins, fetch(:bundle_bins, []).push('my_new_binary') 69 | ``` 70 | 71 | Configurable options: 72 | 73 | ```ruby 74 | set :bundle_roles, :all # this is default 75 | set :bundle_config, { deployment: true } # this is default 76 | set :bundle_servers, -> { release_roles(fetch(:bundle_roles)) } # this is default 77 | set :bundle_binstubs, -> { shared_path.join('bin') } # default: nil 78 | set :bundle_binstubs_command, :install # this is default 79 | set :bundle_gemfile, -> { release_path.join('MyGemfile') } # default: nil 80 | set :bundle_path, -> { shared_path.join('bundle') } # this is default. set it to nil to use bundler's default path 81 | set :bundle_without, %w{development test}.join(':') # this is default 82 | set :bundle_flags, '--quiet' # this is default 83 | set :bundle_env_variables, {} # this is default 84 | set :bundle_clean_options, "" # this is default. Use "--dry-run" if you just want to know what gems would be deleted, without actually deleting them 85 | set :bundle_check_before_install, true # default: true. Set this to false to bypass running `bundle check` before executing `bundle install` 86 | ``` 87 | 88 | You can parallelize the installation of gems with bundler's jobs feature. 89 | Choose a number less or equal than the number of cores your server. 90 | 91 | ```ruby 92 | set :bundle_jobs, 8 # default: 4, only available for Bundler >= 1.4 93 | ``` 94 | 95 | To generate binstubs on each deploy, set `:bundle_binstubs` path: 96 | 97 | ```ruby 98 | set :bundle_binstubs, -> { shared_path.join('bin') } 99 | ``` 100 | 101 | In the result this would execute the following bundle commands on all servers 102 | (actual paths depend on the real deploy directory): 103 | 104 | ```sh 105 | $ bundle config --local deployment true 106 | $ bundle config --local gemfile /my_app/releases/20130623094732/MyGemfile 107 | $ bundle config --local path /my_app/shared/bundle 108 | $ bundle config --local without "development test" 109 | $ bundle install --quiet --binstubs /my_app/shared/bin 110 | ``` 111 | 112 | If any option is set to `nil` it will be excluded from the final bundle commands. 113 | 114 | If you want to clean up gems after a successful deploy, add `after 'deploy:published', 'bundler:clean'` to config/deploy.rb. 115 | 116 | Downsides to cleaning: 117 | 118 | * If a rollback requires rebuilding a Gem with a large compiled binary component, such as Nokogiri, the rollback will take a while. 119 | * In rare cases, if a gem that was used in the previously deployed version was yanked, rollback would entirely fail. 120 | 121 | If you're using Bundler >= 2.1 and you are generating binstubs, you can configure capistrano-bundler to use the newer 122 | `bundle binstubs` command. This will avoid the deprecation warning that you'd otherwise get when using `bundle install` 123 | to generate binstubs: 124 | 125 | ```ruby 126 | set :bundle_binstubs_command, :binstubs 127 | ``` 128 | 129 | ### Environment Variables 130 | 131 | The `bundle_env_variables` option can be used to specify any environment variables you want present when running the `bundle` command: 132 | 133 | ```ruby 134 | # This translates to NOKOGIRI_USE_SYSTEM_LIBRARIES=1 when executed 135 | set :bundle_env_variables, { nokogiri_use_system_libraries: 1 } 136 | ``` 137 | 138 | ## Contributing 139 | 140 | 1. Fork it 141 | 2. Create your feature branch (`git checkout -b my-new-feature`) 142 | 3. Commit your changes (`git commit -am 'Add some feature'`) 143 | 4. Push to the branch (`git push origin my-new-feature`) 144 | 5. Create new Pull Request 145 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | # Do nothing by default 4 | task :default 5 | 6 | Rake::Task["release"].enhance do 7 | puts "Don't forget to publish the release on GitHub!" 8 | system "open https://github.com/capistrano/bundler/releases" 9 | end 10 | -------------------------------------------------------------------------------- /capistrano-bundler.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'capistrano-bundler' 7 | spec.version = '2.1.1' 8 | spec.license = 'MIT' 9 | spec.authors = ['Tom Clements', 'Lee Hambley', 'Kir Shatrov'] 10 | spec.email = ['seenmyfate@gmail.com', 'lee.hambley@gmail.com', 'shatrov@me.com'] 11 | spec.description = %q{Bundler support for Capistrano 3.x} 12 | spec.summary = %q{Bundler support for Capistrano 3.x} 13 | spec.homepage = 'https://github.com/capistrano/bundler' 14 | spec.metadata = { 15 | "changelog_uri" => "https://github.com/capistrano/bundler/releases" 16 | } 17 | 18 | spec.files = `git ls-files`.split($/) 19 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 20 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 21 | spec.require_paths = ['lib'] 22 | 23 | spec.add_dependency 'capistrano', '~> 3.1' 24 | 25 | spec.add_development_dependency 'bundler', '~> 2.1' 26 | spec.add_development_dependency 'danger' 27 | spec.add_development_dependency 'rake' 28 | end 29 | -------------------------------------------------------------------------------- /lib/capistrano-bundler.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capistrano/bundler/0a5b9dda84b53985743aa12210c6c62465f1f98d/lib/capistrano-bundler.rb -------------------------------------------------------------------------------- /lib/capistrano/bundler.rb: -------------------------------------------------------------------------------- 1 | require_relative 'bundler/tasks' 2 | require_relative 'bundler/hooks' 3 | -------------------------------------------------------------------------------- /lib/capistrano/bundler/hooks.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../../tasks/hooks.cap', __FILE__) 2 | -------------------------------------------------------------------------------- /lib/capistrano/bundler/tasks.rb: -------------------------------------------------------------------------------- 1 | load File.expand_path('../../tasks/bundler.cap', __FILE__) 2 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/bundler.cap: -------------------------------------------------------------------------------- 1 | require "shellwords" 2 | 3 | namespace :bundler do 4 | desc <<-DESC 5 | Configure the Bundler environment for the release so that subsequent 6 | `bundle check`, `bundle install`, `bundle clean`, and `bundle exec` 7 | commands all behave consistently. The following settings will be 8 | turned into the appropriate `bundle config` executions: 9 | 10 | :bundle_config 11 | :bundle_gemfile 12 | :bundle_path 13 | :bundle_without 14 | DESC 15 | task :config do 16 | on fetch(:bundle_servers) do 17 | within release_path do 18 | with fetch(:bundle_env_variables) do 19 | configuration = fetch(:bundle_config).dup || {} 20 | configuration[:gemfile] = fetch(:bundle_gemfile) 21 | configuration[:path] = fetch(:bundle_path) 22 | configuration[:without] = fetch(:bundle_without) 23 | 24 | configuration.each do |key, value| 25 | execute :bundle, "config", "--local", key, value.to_s.shellescape unless value.nil? 26 | end 27 | end 28 | end 29 | end 30 | end 31 | 32 | desc <<-DESC 33 | Install the current Bundler environment. By default, gems will be 34 | installed to the shared/bundle path. Gems in the development and 35 | test group will not be installed. The install command is executed 36 | with the --quiet and --jobs=4 flags. 37 | 38 | By default, bundler will not be run on servers with no_release: true. 39 | 40 | You can override any of these defaults by setting the variables shown below. 41 | 42 | set :bundle_roles, :all 43 | 44 | set :bundle_config, { deployment: true } 45 | set :bundle_servers, -> { release_roles(fetch(:bundle_roles)) } 46 | set :bundle_binstubs, nil 47 | set :bundle_binstubs_command, :install 48 | set :bundle_gemfile, -> { release_path.join('Gemfile') } 49 | set :bundle_path, -> { shared_path.join('bundle') } 50 | set :bundle_without, %w{development test}.join(':') 51 | set :bundle_flags, '--quiet' 52 | set :bundle_jobs, 4 53 | set :bundle_env_variables, {} 54 | set :bundle_clean_options, "" 55 | DESC 56 | task install: :config do 57 | on fetch(:bundle_servers) do 58 | within release_path do 59 | with fetch(:bundle_env_variables) do 60 | if fetch(:bundle_check_before_install) && test(:bundle, :check) 61 | info "The Gemfile's dependencies are satisfied, skipping installation" 62 | else 63 | options = [] 64 | if fetch(:bundle_binstubs) && 65 | fetch(:bundle_binstubs_command) == :install 66 | options << "--binstubs #{fetch(:bundle_binstubs)}" 67 | end 68 | options << "--jobs #{fetch(:bundle_jobs)}" if fetch(:bundle_jobs) 69 | options << "#{fetch(:bundle_flags)}" if fetch(:bundle_flags) 70 | execute :bundle, :install, *options 71 | if fetch(:bundle_binstubs) && 72 | fetch(:bundle_binstubs_command) == :binstubs 73 | execute :bundle, :binstubs, '--all', '--path', fetch(:bundle_binstubs) 74 | end 75 | end 76 | end 77 | end 78 | end 79 | end 80 | 81 | desc <<-DESC 82 | Maps all binaries to use `bundle exec` by default. 83 | Add your own binaries to the array with the command shown below. 84 | 85 | set :bundle_bins, fetch(:bundle_bins) + %w(my_new_binary) 86 | DESC 87 | task :map_bins do 88 | fetch(:bundle_bins).each do |command| 89 | SSHKit.config.command_map.prefix[command.to_sym].push("bundle exec") 90 | end 91 | end 92 | 93 | desc "Remove unused gems installed by bundler" 94 | task clean: :config do 95 | on fetch(:bundle_servers) do 96 | within release_path do 97 | with fetch(:bundle_env_variables) do 98 | execute :bundle, :clean, fetch(:bundle_clean_options) 99 | end 100 | end 101 | end 102 | end 103 | end 104 | 105 | Capistrano::DSL.stages.each do |stage| 106 | after stage, 'bundler:map_bins' 107 | end 108 | 109 | namespace :load do 110 | task :defaults do 111 | set :bundle_bins, %w{gem rake rails} 112 | 113 | set :bundle_roles, :all 114 | 115 | set :bundle_config, { deployment: true } 116 | set :bundle_servers, -> { release_roles(fetch(:bundle_roles)) } 117 | set :bundle_binstubs, nil 118 | set :bundle_binstubs_command, :install 119 | set :bundle_gemfile, nil 120 | set :bundle_path, -> { shared_path.join('bundle') } 121 | set :bundle_without, %w{development test}.join(':') 122 | set :bundle_flags, '--quiet' 123 | set :bundle_jobs, 4 124 | set :bundle_env_variables, {} 125 | set :bundle_clean_options, "" 126 | set :bundle_check_before_install, true 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /lib/capistrano/tasks/hooks.cap: -------------------------------------------------------------------------------- 1 | before 'deploy:updated', 'bundler:install' 2 | before 'deploy:reverted', 'bundler:install' 3 | --------------------------------------------------------------------------------