diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 0a68cc3..42fb8bf 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -23,13 +23,14 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.0' - '3.2' - '3.3' + - '3.4' bundler-version: - - '2.3.19' - - '2.4.14' + - '2.4.19' - '2.5.4' + - '2.6.0' + - '2.6.9' env: TEST_BUNDLER_VERSION: ${{ matrix.bundler-version }} diff --git a/README.md b/README.md index bbe44fb..93743f0 100644 --- a/README.md +++ b/README.md @@ -37,30 +37,41 @@ require File.join(Bundler::Plugin.index.load_paths("bundler-override")[0], "bund 2. Add _'override'_ block to the _Gemfile_, e.g.: ~~~ruby -override 'chef-config', :drop => ['chef-utils', 'mixlib-config'] +if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :drop => ['chef-utils', 'mixlib-config'] +end ~~~ or ~~~ruby -override 'chef-config', :drop => 'mixlib-config', :requirements => { - 'chef-utils' => '17.10.68' -} +if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :drop => 'mixlib-config', :requirements => { + 'chef-utils' => '17.10.68' + } +end ~~~ or ~~~ruby -override 'chef-config', :requirements => { - 'chef-utils' => '17.10.68', - 'mixlib-config' => '2.0.0' -} +if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :requirements => { + 'chef-utils' => '17.10.68', + 'mixlib-config' => '2.0.0' + } +end ~~~ + + ### override `override` is a command that allows to drop or replace dependency for a gem with desired version +It is a good practice to check if the plugin is installed since it will allow bundler to install it +automatically if it is missing. + ### drop Takes a gem name or list of gem names to be totally dropped from the dependencies. diff --git a/bundler-override.gemspec b/bundler-override.gemspec index 36059e7..b7d3395 100644 --- a/bundler-override.gemspec +++ b/bundler-override.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |spec| spec.summary = "This bundler plugin allows to change dependencies for a gem. It can be helpful in situation when a developer needs to use some other dependency than default for the gem. For usage details, please see: https://github.com/tarnowsc/bundler-override#usage" spec.homepage = "https://github.com/tarnowsc/bundler-override" spec.license = "Apache-2.0" - spec.required_ruby_version = ">= 3.0.0" + spec.required_ruby_version = ">= 3.2.0" spec.metadata["allowed_push_host"] = "https://rubygems.org" diff --git a/lib/bundler-override.rb b/lib/bundler-override.rb index 7ecc835..28c1b4d 100644 --- a/lib/bundler-override.rb +++ b/lib/bundler-override.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true require "set" require_relative "bundler/override/dsl_patch" -require_relative "bundler/override/specset_patch" -require_relative "bundler/override/sharedhelpers_patch" +require_relative "bundler/override/dependency_patch" require "bundler/friendly_errors.rb" module Bundler @@ -24,7 +23,7 @@ def add(name, drop, requirements) return if @gems.include? name @gems << name @params = Array.new unless @params - @params << { :name=>name, :drop=>drop || [], :requirements=>requirements } + @params << { :name => name, :drop => drop || [], :requirements => requirements } end end end @@ -34,7 +33,3 @@ def add(name, drop, requirements) ObjectSpace.each_object(Bundler::Dsl) do |o| o.singleton_class.prepend(Bundler::Override::DslPatch) end - -Bundler::SpecSet.prepend(Bundler::Override::SpecSetPatch) - -Bundler::SharedHelpers.prepend(Bundler::Override::SharedHelpersPatch) \ No newline at end of file diff --git a/lib/bundler/override/dependency_patch.rb b/lib/bundler/override/dependency_patch.rb new file mode 100644 index 0000000..cfcb440 --- /dev/null +++ b/lib/bundler/override/dependency_patch.rb @@ -0,0 +1,49 @@ +module Bundler + module Override + module DependencyPatch + def self.included(base) + base.class_eval do + alias_method :dependencies_orig, :dependencies + + def dependencies + override_dependencies || [] + end + + def override_dependencies + deps = dependencies_orig + return deps unless Bundler::Override.override? name + param = Bundler::Override.params(name) + drop = Array(param[:drop]) + requirements = param[:requirements] + if requirements && !requirements.empty? + requirements.each do |name, requirement| + existing = deps.find { |d| d.name == name } + deps.delete_if { |d| d.name == name } + deps << Gem::Dependency.new(name, requirement, existing&.type || :runtime) + end + end + deps.delete_if { |d| drop.include? d.name } + + deps + end + end + end + end + end +end + +module Bundler + class RemoteSpecification + include Override::DependencyPatch + end + + class EndpointSpecification + include Override::DependencyPatch + end +end + +module Gem + class Specification + include Bundler::Override::DependencyPatch + end +end \ No newline at end of file diff --git a/lib/bundler/override/sharedhelpers_patch.rb b/lib/bundler/override/sharedhelpers_patch.rb deleted file mode 100644 index 6701059..0000000 --- a/lib/bundler/override/sharedhelpers_patch.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Bundler - module Override - module SharedHelpersPatch - - def ensure_same_dependencies(spec, old_deps, new_deps) - if Bundler::Override.override? spec.name - new_deps.clear() - new_deps.push(*old_deps) - end - super - end - end - end -end \ No newline at end of file diff --git a/lib/bundler/override/specset_patch.rb b/lib/bundler/override/specset_patch.rb deleted file mode 100644 index e075926..0000000 --- a/lib/bundler/override/specset_patch.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "set" - -module Bundler - module Override - module SpecSetPatch - - def specs_for_dependency(dep, platform) - spec = super - return spec if spec.empty? - name = if dep.is_a?(String) then dep else dep.name end - if Bundler::Override.override? name - s = spec.first - param = Bundler::Override.params(name) - drop = param[:drop] - s.dependencies.delete_if { |d| drop.include? d.name } - requirements = param[:requirements] - if requirements - gems = Set.new(requirements.keys) - s.dependencies.delete_if { |d| gems.include? d.name } - requirements.each { |name, requirement| s.dependencies << Gem::Dependency.new(name, requirement) } - end - end - spec - end - - end - end -end diff --git a/spec/bundler_override_spec.rb b/spec/bundler_override_spec.rb index a092304..d86f019 100644 --- a/spec/bundler_override_spec.rb +++ b/spec/bundler_override_spec.rb @@ -54,20 +54,20 @@ def verify_installation context "on installation of the plugin - override block in Gemfile" do before do write_gemfile <<~G - #{base_gemfile} - - gem 'chef-config', '~> 18.2', '>= 18.2.7' - - override 'chef-config', :drop => 'chef-utils', :requirements => { - 'chef-utils' => '17.10.68' - } + #{base_gemfile} + + gem 'chef-config', '~> 18.2', '>= 18.2.7' + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :drop => 'chef-utils', :requirements => { + 'chef-utils' => '17.10.95' + } + end G end it "installs the plugin using: bundle update" do - bundle(:update, expect_error: true) - - expect(err).to include "Undefined local variable or method `override'" + bundle(:update) + verify_installation end it "installs the plugin using: bundle plugin install" do @@ -101,29 +101,29 @@ def verify_installation context "on non existing gems" do it "when the gem doesn't exist" do write_gemfile <<~G - #{base_gemfile} - - override 'not-existing-gem', :drop => 'chef-utils', :requirements => { - 'chef-utils' => '17.10.68' - } + #{base_gemfile} + if Bundler::Plugin.installed?('bundler-override') + override 'not-existing-gem', :drop => 'chef-utils', :requirements => { + 'chef-utils' => '17.10.68' + } + end G - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") bundle(:update) end it "when the gem's dependency doesn't exist in rubygems.org" do write_gemfile <<~G - #{base_gemfile} - - gem 'chef-config', '~> 18.2', '>= 18.2.7' - - override 'chef-config', :drop => 'chef-utils', :requirements => { - 'not-existing-gem' => '6.6.6' - } + #{base_gemfile} + gem 'chef-config', '~> 18.2', '>= 18.2.7' + + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :drop => 'chef-utils', :requirements => { + 'not-existing-gem' => '6.6.6' + } + end G - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") bundle(:update, expect_error: true) unless bundler_version == '2.3.19' # bundler 2.3.19 does fail with different error message @@ -135,69 +135,69 @@ def verify_installation context "overriding gem" do before do write_gemfile <<~G - #{base_gemfile} + #{base_gemfile} - gem 'chef-config', '~> 18.2', '>= 18.2.7' + gem 'chef-config', '~> 18.2', '>= 18.2.7' - override 'chef-config', :drop => 'chef-utils', :requirements => { - 'chef-utils' => '17.10.70' - } + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :requirements => { + 'chef-utils' => '17.10.95' + } + end G - - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") end it "with different version" do bundle(:update) - expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.70"]) + expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.95"]) end it "with ENV['RAILS_ENV'] = 'production'" do bundle(:update,env: { "RAILS_ENV" => "production" }) - expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.70"]) + expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.95"]) end it "with ENV['RAILS_ENV'] = 'production' and the Bundler::Setting false" do env_var = "BUNDLE_BUNDLER_INJECT__DISABLE_WARN_OVERRIDE_GEM" bundle(:update, env: { "RAILS_ENV" => "production", env_var => 'false' }) - expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.70"]) + expect(lockfile_deps_for_spec("chef-config")).to include(["chef-utils", "= 17.10.95"]) end end context "overriding gems" do before do write_gemfile <<~G - #{base_gemfile} + #{base_gemfile} gem 'chef-config', '~> 18.2', '>= 18.2.7' gem 'sequel' - override 'chef-config', :drop => ['chef-utils', 'mixlib-config'], :requirements => { - 'chef-utils' => '17.10.70', - 'mixlib-config' => '2.0.1' - } + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :requirements => { + 'chef-utils' => '17.10.95', + 'mixlib-config' => '2.1.0' + } + end G - - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") end it "with different version" do bundle(:update) expect(lockfile_deps_for_spec("chef-config")).to include( - ["chef-utils", "= 17.10.70"], - ["mixlib-config", "= 2.0.1"]) + ["chef-utils", "= 17.10.95"], + ["mixlib-config", "= 2.1.0"]) end it "with ENV['RAILS_ENV'] = 'production'" do bundle(:update,env: { "RAILS_ENV" => "production" }) expect(lockfile_deps_for_spec("chef-config")).to include( - ["chef-utils", "= 17.10.70"], - ["mixlib-config", "= 2.0.1"]) + ["chef-utils", "= 17.10.95"], + ["mixlib-config", "= 2.1.0"]) end it "with ENV['RAILS_ENV'] = 'production' and the Bundler::Setting false" do @@ -205,8 +205,8 @@ def verify_installation bundle(:update, env: { "RAILS_ENV" => "production", env_var => 'false' }) expect(lockfile_deps_for_spec("chef-config")).to include( - ["chef-utils", "= 17.10.70"], - ["mixlib-config", "= 2.0.1"]) + ["chef-utils", "= 17.10.95"], + ["mixlib-config", "= 2.1.0"]) end end @@ -216,13 +216,13 @@ def verify_installation gem 'chef-config', '~> 18.2', '= 18.2.7' - override 'chef-config', :drop => 'chef-utils', :requirements => { - 'chef-utils' => '18.2.7', - 'mixlib-config' => '2.0.0' - } - G - - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :requirements => { + 'chef-utils' => '18.2.7', + 'mixlib-config' => '2.0.0' + } + end + G bundle(:update) expect(lockfile_deps_for_spec("chef-config")).to include( @@ -234,13 +234,32 @@ def verify_installation write_gemfile <<~G #{base_gemfile} gem 'rails-dom-testing' - override 'rails-dom-testing', drop: ['nokogiri'] - G - - bundle("plugin install \"bundler-override\" --local-git #{bundler_override_root}") + if Bundler::Plugin.installed?('bundler-override') + override 'rails-dom-testing', drop: ['nokogiri'] + end + G bundle(:update) - puts lockfile_deps_for_spec("rails-dom-testing") expect(lockfile_deps_for_spec("rails-dom-testing").to_s).to_not include("nokogiri") end -end + + it "drop takes precedence over overwrite" do + write_gemfile <<~G + #{base_gemfile} + + gem 'chef-config', '~> 18.2', '>= 18.2.7' + gem 'sequel' + + if Bundler::Plugin.installed?('bundler-override') + override 'chef-config', :drop => ['chef-utils', 'mixlib-config'],:requirements => { + 'chef-utils' => '17.10.95', + 'mixlib-config' => '2.1.0' + } + end + G + bundle(:update) + expect(lockfile_deps_for_spec("chef-config").to_s).to_not include('chef-utils') + expect(lockfile_deps_for_spec("chef-config").to_s).to_not include('mixlib-config') + end + + end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index c701f75..9eb5f90 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -82,6 +82,8 @@ def with_path_based_gem(source_repo) def write_gemfile(contents) contents = "#{coverage_prelude}\n\n#{contents}" File.write(app_dir.join("Gemfile"), contents) + lockfile_path = app_dir.join("Gemfile.lock") + File.delete(lockfile_path) if File.exist?(lockfile_path) end def lockfile @@ -108,10 +110,15 @@ def raw_bundle(command, verbose: false, env: {}) return command, out, err, process_status end - def bundle(command, expect_error: false, verbose: false, env: {}) + def bundle(command, expect_error: false, verbose: true, env: {}) command, @out, @err, @process_status = raw_bundle(command, verbose: verbose, env: env) + if verbose + puts "\n#{'=' * 80}\n#{command}\n#{'=' * 80}\n#{bundler_output}\n#{'=' * 80}\n" + puts "Gemfile.lock:\n#{File.read(app_dir.join("Gemfile.lock"))}" if File.exist?(app_dir.join("Gemfile.lock")) + puts "App dir: #{app_dir}" + end if expect_error - expect(@process_status.exitstatus).to_not eq(0), "#{command.inspect} succeeded but was not expected to." + expect(@process_status.exitstatus).to_not eq(0), "#{command.inspect} succeeded but was not expected to:\n#{bundler_output}" else expect(@process_status.exitstatus).to eq(0), "#{command.inspect} failed with:\n#{bundler_output}" end