diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eee2e529d0..67ea2da218 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,6 +107,11 @@ jobs: run: | docker run --rm "$CORE_CI_IMAGE" bash -c \ "cd /home/dependabot/dependabot-core/bundler/helpers/v1 && BUNDLER_VERSION=1 bundle install && BUNDLER_VERSION=1 bundle exec rspec spec" + - name: Run bundler v2 native helper specs + if: matrix.suite == 'bundler' + run: | + docker run --rm "$CORE_CI_IMAGE" bash -c \ + "cd /home/dependabot/dependabot-core/bundler/helpers/v2 && BUNDLER_VERSION=2 bundle install && BUNDLER_VERSION=2 bundle exec rspec spec" - name: Run ${{ matrix.suite }} tests with rspec run: | docker run --env "CI=true" --env "DEPENDABOT_TEST_ACCESS_TOKEN=$DEPENDABOT_TEST_ACCESS_TOKEN" --rm "$CORE_CI_IMAGE" bash -c \ diff --git a/Dockerfile b/Dockerfile index 74d6d84614..9754e25b4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -196,6 +196,7 @@ RUN bash /opt/terraform/helpers/build /opt/terraform && \ bash /opt/python/helpers/build /opt/python && \ bash /opt/dep/helpers/build /opt/dep && \ mkdir -p /opt/bundler/v1 && bash /opt/bundler/helpers/v1/build /opt/bundler/v1 && \ + mkdir -p /opt/bundler/v2 && bash /opt/bundler/helpers/v2/build /opt/bundler/v2 && \ bash /opt/go_modules/helpers/build /opt/go_modules && \ bash /opt/npm_and_yarn/helpers/build /opt/npm_and_yarn && \ bash /opt/hex/helpers/build /opt/hex && \ diff --git a/bundler/helpers/v2/.bundle/config b/bundler/helpers/v2/.bundle/config new file mode 100644 index 0000000000..04f57bd2ed --- /dev/null +++ b/bundler/helpers/v2/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: ".bundle" diff --git a/bundler/helpers/v2/.gitignore b/bundler/helpers/v2/.gitignore new file mode 100644 index 0000000000..9e978b139f --- /dev/null +++ b/bundler/helpers/v2/.gitignore @@ -0,0 +1,9 @@ +/.bundle/* +!/.bundle/config +/.env +/tmp +/dependabot-*.gem +Gemfile.lock +spec/fixtures/projects/*/.bundle/ +!spec/fixtures/projects/**/Gemfile.lock +!spec/fixtures/projects/**/vendor diff --git a/bundler/helpers/v2/Gemfile b/bundler/helpers/v2/Gemfile new file mode 100644 index 0000000000..acfca8907f --- /dev/null +++ b/bundler/helpers/v2/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# NOTE: Used to run native helper specs +group :test do + gem "byebug", "11.1.3" + gem "rspec", "3.10.0" + gem "rspec-its", "1.3.0" + gem "vcr", "6.0.0" + gem "webmock", "3.12.1" +end diff --git a/bundler/helpers/v2/build b/bundler/helpers/v2/build new file mode 100755 index 0000000000..104852b802 --- /dev/null +++ b/bundler/helpers/v2/build @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +install_dir=$1 +if [ -z "$install_dir" ]; then + echo "usage: $0 INSTALL_DIR" + exit 1 +fi + +helpers_dir="$(dirname "${BASH_SOURCE[0]}")" +cp -r \ + "$helpers_dir/.bundle" \ + "$helpers_dir/lib" \ + "$helpers_dir/run.rb" \ + "$helpers_dir/Gemfile" \ + "$install_dir" + +cd "$install_dir" + +# NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock +# forcing specs and native helpers to run with the same version +BUNDLER_VERSION=2 bundle install diff --git a/bundler/helpers/v2/lib/functions.rb b/bundler/helpers/v2/lib/functions.rb new file mode 100644 index 0000000000..9b52d95d00 --- /dev/null +++ b/bundler/helpers/v2/lib/functions.rb @@ -0,0 +1,67 @@ +module Functions + class NotImplementedError < StandardError; end + + def self.parsed_gemfile(lockfile_name:, gemfile_name:, dir:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.parsed_gemspec(lockfile_name:, gemspec_name:, dir:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.vendor_cache_dir(dir:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.update_lockfile(dir:, gemfile_name:, lockfile_name:, using_bundler2:, + credentials:, dependencies:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.force_update(dir:, dependency_name:, target_version:, gemfile_name:, + lockfile_name:, using_bundler2:, credentials:, + update_multiple_dependencies:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.dependency_source_type(gemfile_name:, dependency_name:, dir:, + credentials:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.depencency_source_latest_git_version(gemfile_name:, dependency_name:, + dir:, credentials:, + dependency_source_url:, + dependency_source_branch:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.private_registry_versions(gemfile_name:, dependency_name:, dir:, + credentials:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.resolve_version(dependency_name:, dependency_requirements:, + gemfile_name:, lockfile_name:, using_bundler2:, + dir:, credentials:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.jfrog_source(dir:, gemfile_name:, credentials:, using_bundler2:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.git_specs(dir:, gemfile_name:, credentials:, using_bundler2:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.set_bundler_flags_and_credentials(dir:, credentials:, + using_bundler2:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end + + def self.conflicting_dependencies(dir:, dependency_name:, target_version:, + lockfile_name:, using_bundler2:, credentials:) + raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}" + end +end diff --git a/bundler/helpers/v2/run.rb b/bundler/helpers/v2/run.rb new file mode 100644 index 0000000000..c4a6dad8fc --- /dev/null +++ b/bundler/helpers/v2/run.rb @@ -0,0 +1,30 @@ +require "bundler" +require "json" + +$LOAD_PATH.unshift(File.expand_path("./lib", __dir__)) +$LOAD_PATH.unshift(File.expand_path("../v1/monkey_patches", __dir__)) + +# Bundler monkey patches +require "definition_ruby_version_patch" +require "definition_bundler_version_patch" +require "git_source_patch" + +require "functions" + +def output(obj) + print JSON.dump(obj) +end + +begin + request = JSON.parse($stdin.read) + + function = request["function"] + args = request["args"].transform_keys(&:to_sym) + + output({ result: Functions.send(function, **args) }) +rescue => error + output( + { error: error.message, error_class: error.class, trace: error.backtrace } + ) + exit(1) +end diff --git a/bundler/helpers/v2/spec/functions_spec.rb b/bundler/helpers/v2/spec/functions_spec.rb new file mode 100644 index 0000000000..f62d0f9153 --- /dev/null +++ b/bundler/helpers/v2/spec/functions_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "native_spec_helper" + +RSpec.describe Functions do + # Verify v1 method signatures are exist, but raise as NYI + { + parsed_gemfile: [ :lockfile_name, :gemfile_name, :dir ], + parsed_gemspec: [ :lockfile_name, :gemspec_name, :dir ], + vendor_cache_dir: [ :dir ], + update_lockfile: [ :dir, :gemfile_name, :lockfile_name, :using_bundler2, :credentials, :dependencies ], + force_update: [ :dir, :dependency_name, :target_version, :gemfile_name, :lockfile_name, :using_bundler2, + :credentials, :update_multiple_dependencies ], + dependency_source_type: [ :gemfile_name, :dependency_name, :dir, :credentials ], + depencency_source_latest_git_version: [ :gemfile_name, :dependency_name, :dir, :credentials, :dependency_source_url, + :dependency_source_branch ], + private_registry_versions: [:gemfile_name, :dependency_name, :dir, :credentials ], + resolve_version: [:dependency_name, :dependency_requirements, :gemfile_name, :lockfile_name, :using_bundler2, + :dir, :credentials], + jfrog_source: [:dir, :gemfile_name, :credentials, :using_bundler2], + git_specs: [:dir, :gemfile_name, :credentials, :using_bundler2], + set_bundler_flags_and_credentials: [:dir, :credentials, :using_bundler2], + conflicting_dependencies: [:dir, :dependency_name, :target_version, :lockfile_name, :using_bundler2, :credentials] + }.each do |function, kwargs| + describe "::#{function}" do + let(:args) do + kwargs.inject({}) do |args, keyword| + args.merge({ keyword => anything }) + end + end + + it "raises a NYI" do + expect { Functions.send(function, **args) }.to raise_error(Functions::NotImplementedError) + end + end + end +end diff --git a/bundler/helpers/v2/spec/native_spec_helper.rb b/bundler/helpers/v2/spec/native_spec_helper.rb new file mode 100644 index 0000000000..5e97205a85 --- /dev/null +++ b/bundler/helpers/v2/spec/native_spec_helper.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "rspec/its" +require "webmock/rspec" +require "byebug" + +$LOAD_PATH.unshift(File.expand_path("../lib", __dir__)) +# TODO: Fork `v1/monkey_patches` into `v2/monkey_patches` ? +$LOAD_PATH.unshift(File.expand_path("../../v1/monkey_patches", __dir__)) + +# Bundler monkey patches +require "definition_ruby_version_patch" +require "definition_bundler_version_patch" +require "git_source_patch" + +require "functions" + +RSpec.configure do |config| + config.color = true + config.order = :rand + config.mock_with(:rspec) { |mocks| mocks.verify_partial_doubles = true } + config.raise_errors_for_deprecations! +end + +# Duplicated in lib/dependabot/bundler/file_updater/lockfile_updater.rb +# TODO: Stop sanitizing the lockfile once we have bundler 2 installed +LOCKFILE_ENDING = /(?\s*(?:RUBY VERSION|BUNDLED WITH).*)/m.freeze + +def project_dependency_files(project) + project_path = File.expand_path(File.join("../../spec/fixtures/projects/bundler1", project)) + Dir.chdir(project_path) do + # NOTE: Include dotfiles (e.g. .npmrc) + files = Dir.glob("**/*", File::FNM_DOTMATCH) + files = files.select { |f| File.file?(f) } + files.map do |filename| + content = File.read(filename) + if filename == "Gemfile.lock" + content = content.gsub(LOCKFILE_ENDING, "") + end + { + name: filename, + content: content + } + end + end +end + +def fixture(*name) + File.read(File.join("../../spec/fixtures", File.join(*name))) +end diff --git a/bundler/lib/dependabot/bundler/file_parser.rb b/bundler/lib/dependabot/bundler/file_parser.rb index 4ff7cac3d8..84bbe09c85 100644 --- a/bundler/lib/dependabot/bundler/file_parser.rb +++ b/bundler/lib/dependabot/bundler/file_parser.rb @@ -301,7 +301,7 @@ def imported_ruby_files end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/file_updater.rb b/bundler/lib/dependabot/bundler/file_updater.rb index d22427f074..c44c6adf83 100644 --- a/bundler/lib/dependabot/bundler/file_updater.rb +++ b/bundler/lib/dependabot/bundler/file_updater.rb @@ -151,7 +151,8 @@ def updated_lockfile_content dependencies: dependencies, dependency_files: dependency_files, repo_contents_path: repo_contents_path, - credentials: credentials + credentials: credentials, + options: options ).updated_lockfile_content end @@ -162,7 +163,7 @@ def top_level_gemspecs end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/file_updater/lockfile_updater.rb b/bundler/lib/dependabot/bundler/file_updater/lockfile_updater.rb index ec2d6ec9b0..74ed50dee3 100644 --- a/bundler/lib/dependabot/bundler/file_updater/lockfile_updater.rb +++ b/bundler/lib/dependabot/bundler/file_updater/lockfile_updater.rb @@ -33,11 +33,12 @@ def gemspec_sources end def initialize(dependencies:, dependency_files:, - repo_contents_path: nil, credentials:) + repo_contents_path: nil, credentials:, options:) @dependencies = dependencies @dependency_files = dependency_files @repo_contents_path = repo_contents_path @credentials = credentials + @options = options end def updated_lockfile_content @@ -54,7 +55,7 @@ def updated_lockfile_content private attr_reader :dependencies, :dependency_files, :repo_contents_path, - :credentials + :credentials, :options def build_updated_lockfile base_dir = dependency_files.first.directory @@ -304,7 +305,7 @@ def using_bundler2? end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/helpers.rb b/bundler/lib/dependabot/bundler/helpers.rb index ef1e6b7ebe..ea9185722a 100644 --- a/bundler/lib/dependabot/bundler/helpers.rb +++ b/bundler/lib/dependabot/bundler/helpers.rb @@ -6,9 +6,14 @@ module Helpers V1 = "1" V2 = "2" - # TODO: Add support for bundler v2 - # return "v2" if lockfile.content.match?(/BUNDLED WITH\s+2/m) - def self.bundler_version(_lockfile) + # NOTE: options is a manditory argument to ensure we pass it from all calling classes + def self.bundler_version(_lockfile, options:) + # For now, force V2 if bundler_2_available + return V2 if options[:bundler_2_available] + + # TODO: Add support for bundler v2 based on lockfile + # return V2 if lockfile.content.match?(/BUNDLED WITH\s+2/m) + V1 end end diff --git a/bundler/lib/dependabot/bundler/native_helpers.rb b/bundler/lib/dependabot/bundler/native_helpers.rb index d5f7bfdf30..d0ce964544 100644 --- a/bundler/lib/dependabot/bundler/native_helpers.rb +++ b/bundler/lib/dependabot/bundler/native_helpers.rb @@ -22,6 +22,11 @@ def self.run_bundler_subprocess(function:, args:, bundler_version:) "GEM_HOME" => File.join(versioned_helper_path(bundler_version: bundler_version), ".bundle") } ) + rescue SharedHelpers::HelperSubprocessFailed => e + # TODO: Remove once we stop stubbing out the V2 native helper + raise Dependabot::NotImplemented, e.message if e.error_class == "Functions::NotImplementedError" + + raise end end diff --git a/bundler/lib/dependabot/bundler/update_checker.rb b/bundler/lib/dependabot/bundler/update_checker.rb index f4eb7a50c9..24b241eb89 100644 --- a/bundler/lib/dependabot/bundler/update_checker.rb +++ b/bundler/lib/dependabot/bundler/update_checker.rb @@ -110,7 +110,8 @@ def conflicting_dependencies ConflictingDependencyResolver.new( dependency_files: dependency_files, repo_contents_path: repo_contents_path, - credentials: credentials + credentials: credentials, + options: options ).conflicting_dependencies( dependency: dependency, target_version: lowest_security_fix_version @@ -162,7 +163,8 @@ def resolvable?(version) credentials: credentials, target_version: version, requirements_update_strategy: requirements_update_strategy, - update_multiple_dependencies: false + update_multiple_dependencies: false, + options: options ).updated_dependencies true rescue Dependabot::DependencyFileNotResolvable @@ -183,7 +185,8 @@ def git_tag_resolvable?(tag) credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: raise_on_ignored, - replacement_git_pin: tag + replacement_git_pin: tag, + options: options ).latest_resolvable_version_details true rescue Dependabot::DependencyFileNotResolvable @@ -339,7 +342,8 @@ def force_updater repo_contents_path: repo_contents_path, credentials: credentials, target_version: latest_version, - requirements_update_strategy: requirements_update_strategy + requirements_update_strategy: requirements_update_strategy, + options: options ) end @@ -365,7 +369,8 @@ def version_resolver(remove_git_source:, unlock_requirement: true) raise_on_ignored: raise_on_ignored, remove_git_source: remove_git_source, unlock_requirement: unlock_requirement, - latest_allowable_version: latest_version + latest_allowable_version: latest_version, + options: options ) end end @@ -386,7 +391,8 @@ def latest_version_finder(remove_git_source:) credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: raise_on_ignored, - security_advisories: security_advisories + security_advisories: security_advisories, + options: options ) end end diff --git a/bundler/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb b/bundler/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb index a78c3544ca..32ee2de04c 100644 --- a/bundler/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb +++ b/bundler/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb @@ -12,10 +12,13 @@ class ConflictingDependencyResolver require_relative "shared_bundler_helpers" include SharedBundlerHelpers - def initialize(dependency_files:, repo_contents_path:, credentials:) + attr_reader :options + + def initialize(dependency_files:, repo_contents_path:, credentials:, options:) @dependency_files = dependency_files @repo_contents_path = repo_contents_path @credentials = credentials + @options = options end # Finds any dependencies in the lockfile that have a subdependency on @@ -47,7 +50,7 @@ def conflicting_dependencies(dependency:, target_version:) private def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/update_checker/force_updater.rb b/bundler/lib/dependabot/bundler/update_checker/force_updater.rb index cfd5f32c29..93d30ff0bb 100644 --- a/bundler/lib/dependabot/bundler/update_checker/force_updater.rb +++ b/bundler/lib/dependabot/bundler/update_checker/force_updater.rb @@ -19,7 +19,8 @@ class ForceUpdater def initialize(dependency:, dependency_files:, repo_contents_path: nil, credentials:, target_version:, requirements_update_strategy:, - update_multiple_dependencies: true) + update_multiple_dependencies: true, + options:) @dependency = dependency @dependency_files = dependency_files @repo_contents_path = repo_contents_path @@ -27,6 +28,7 @@ def initialize(dependency:, dependency_files:, repo_contents_path: nil, @target_version = target_version @requirements_update_strategy = requirements_update_strategy @update_multiple_dependencies = update_multiple_dependencies + @options = options end def updated_dependencies @@ -36,7 +38,8 @@ def updated_dependencies private attr_reader :dependency, :dependency_files, :repo_contents_path, - :credentials, :target_version, :requirements_update_strategy + :credentials, :target_version, :requirements_update_strategy, + :options def update_multiple_dependencies? @update_multiple_dependencies @@ -149,7 +152,7 @@ def using_bundler2? end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb index fbfd464ccc..b43a3957e5 100644 --- a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb +++ b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder.rb @@ -15,7 +15,7 @@ class UpdateChecker class LatestVersionFinder def initialize(dependency:, dependency_files:, repo_contents_path: nil, credentials:, ignored_versions:, raise_on_ignored: false, - security_advisories:) + security_advisories:, options:) @dependency = dependency @dependency_files = dependency_files @repo_contents_path = repo_contents_path @@ -23,6 +23,7 @@ def initialize(dependency:, dependency_files:, repo_contents_path: nil, @ignored_versions = ignored_versions @raise_on_ignored = raise_on_ignored @security_advisories = security_advisories + @options = options end def latest_version_details @@ -36,7 +37,8 @@ def lowest_security_fix_version private attr_reader :dependency, :dependency_files, :repo_contents_path, - :credentials, :ignored_versions, :security_advisories + :credentials, :ignored_versions, :security_advisories, + :options def fetch_latest_version_details return dependency_source.latest_git_version_details if dependency_source.git? @@ -103,7 +105,8 @@ def dependency_source @dependency_source ||= DependencySource.new( dependency: dependency, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + options: options ) end diff --git a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb index 2943ba0b05..abb6acc91b 100644 --- a/bundler/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb +++ b/bundler/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb @@ -17,14 +17,16 @@ class DependencySource OTHER = "other" attr_reader :dependency, :dependency_files, :repo_contents_path, - :credentials + :credentials, :options def initialize(dependency:, dependency_files:, - credentials:) + credentials:, + options:) @dependency = dependency @dependency_files = dependency_files @credentials = credentials + @options = options end # The latest version details for the dependency from a registry @@ -145,7 +147,7 @@ def lockfile end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb b/bundler/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb index 9583332300..41dbbf9047 100644 --- a/bundler/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb +++ b/bundler/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb @@ -237,10 +237,6 @@ def using_bundler2? lockfile.content.match?(/BUNDLED WITH\s+2/m) end - - def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) - end end end end diff --git a/bundler/lib/dependabot/bundler/update_checker/version_resolver.rb b/bundler/lib/dependabot/bundler/update_checker/version_resolver.rb index a482b26967..7037ac4f4c 100644 --- a/bundler/lib/dependabot/bundler/update_checker/version_resolver.rb +++ b/bundler/lib/dependabot/bundler/update_checker/version_resolver.rb @@ -23,7 +23,8 @@ def initialize(dependency:, unprepared_dependency_files:, raise_on_ignored: false, replacement_git_pin: nil, remove_git_source: false, unlock_requirement: true, - latest_allowable_version: nil) + latest_allowable_version: nil, + options:) @dependency = dependency @unprepared_dependency_files = unprepared_dependency_files @credentials = credentials @@ -34,6 +35,7 @@ def initialize(dependency:, unprepared_dependency_files:, @remove_git_source = remove_git_source @unlock_requirement = unlock_requirement @latest_allowable_version = latest_allowable_version + @options = options end def latest_resolvable_version_details @@ -45,7 +47,8 @@ def latest_resolvable_version_details attr_reader :dependency, :unprepared_dependency_files, :repo_contents_path, :credentials, :ignored_versions, - :replacement_git_pin, :latest_allowable_version + :replacement_git_pin, :latest_allowable_version, + :options def remove_git_source? @remove_git_source @@ -164,7 +167,8 @@ def latest_version_details credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: @raise_on_ignored, - security_advisories: [] + security_advisories: [], + options: options ).latest_version_details end @@ -221,7 +225,7 @@ def using_bundler2? end def bundler_version - @bundler_version ||= Helpers.bundler_version(lockfile) + @bundler_version ||= Helpers.bundler_version(lockfile, options: options) end end end diff --git a/bundler/spec/dependabot/bundler/file_parser_spec.rb b/bundler/spec/dependabot/bundler/file_parser_spec.rb index 133528b664..59c2f5a363 100644 --- a/bundler/spec/dependabot/bundler/file_parser_spec.rb +++ b/bundler/spec/dependabot/bundler/file_parser_spec.rb @@ -725,4 +725,25 @@ end end end + + context "with bundler 2 support enabled" do + let(:parser) do + described_class.new( + dependency_files: dependency_files, + source: source, + reject_external_code: reject_external_code, + options: { + bundler_2_available: true + } + ) + end + + describe "parse" do + it "fails as the native helper is not yet implemented" do + expect { parser.parse }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement parsed_gemfile") + end + end + end end diff --git a/bundler/spec/dependabot/bundler/file_updater_spec.rb b/bundler/spec/dependabot/bundler/file_updater_spec.rb index dbfdc73ab1..b38d8dc6e6 100644 --- a/bundler/spec/dependabot/bundler/file_updater_spec.rb +++ b/bundler/spec/dependabot/bundler/file_updater_spec.rb @@ -1708,4 +1708,29 @@ end end end + + context "with bundler 2 support enabled" do + let(:updater) do + described_class.new( + dependency_files: dependency_files, + dependencies: dependencies, + credentials: [{ + "type" => "git_source", + "host" => "github.com" + }], + repo_contents_path: repo_contents_path, + options: { + bundler_2_available: true + } + ) + end + + describe "updated_dependency_files" do + it "fails as the native helper is not yet implemented" do + expect { updater.updated_dependency_files }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement update_lockfile") + end + end + end end diff --git a/bundler/spec/dependabot/bundler/update_checker/conflicting_dependency_resolver_spec.rb b/bundler/spec/dependabot/bundler/update_checker/conflicting_dependency_resolver_spec.rb index 832afb12aa..6cb468c7c7 100644 --- a/bundler/spec/dependabot/bundler/update_checker/conflicting_dependency_resolver_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker/conflicting_dependency_resolver_spec.rb @@ -20,7 +20,8 @@ "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + }], + options: {} ) end let(:dependency_files) { [gemfile, lockfile] } diff --git a/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb b/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb index 82c27846e6..3a77789618 100644 --- a/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb @@ -20,7 +20,8 @@ "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + }], + options: {} ) end let(:dependency_files) { [gemfile, lockfile] } diff --git a/bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb b/bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb index b831824a98..5becca856c 100644 --- a/bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb @@ -19,7 +19,8 @@ "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + }], + options: {} ) end let(:dependency_files) { [gemfile, lockfile] } diff --git a/bundler/spec/dependabot/bundler/update_checker/version_resolver_spec.rb b/bundler/spec/dependabot/bundler/update_checker/version_resolver_spec.rb index a8da86146d..767f39b959 100644 --- a/bundler/spec/dependabot/bundler/update_checker/version_resolver_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker/version_resolver_spec.rb @@ -19,7 +19,8 @@ "password" => "token" }], unlock_requirement: unlock_requirement, - latest_allowable_version: latest_allowable_version + latest_allowable_version: latest_allowable_version, + options: {} ) end let(:ignored_versions) { [] } diff --git a/bundler/spec/dependabot/bundler/update_checker_spec.rb b/bundler/spec/dependabot/bundler/update_checker_spec.rb index c5a098d496..00cc4c506f 100644 --- a/bundler/spec/dependabot/bundler/update_checker_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker_spec.rb @@ -2074,4 +2074,98 @@ it { is_expected.to eq(false) } end end + + context "with bundler 2 support enabled" do + let(:checker) do + described_class.new( + dependency: dependency, + dependency_files: dependency_files, + credentials: credentials, + ignored_versions: ignored_versions, + security_advisories: security_advisories, + options: { + bundler_2_available: true + } + ) + end + + describe "#latest_version" do + it "fails as the native helper is not yet implemented" do + expect { checker.latest_version }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#lowest_security_fix_version" do + it "fails as the native helper is not yet implemented" do + expect { checker.lowest_security_fix_version }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#latest_version_resolvable_with_full_unlock?" do + it "fails as the native helper is not yet implemented" do + expect { checker.send(:latest_version_resolvable_with_full_unlock?) }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#updated_dependencies_after_full_unlock" do + it "fails as the native helper is not yet implemented" do + expect { checker.send(:updated_dependencies_after_full_unlock) }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#conflicting_dependencies" do + it "fails as the native helper is not yet implemented" do + expect { checker.conflicting_dependencies }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#latest_resolvable_version" do + it "fails as the native helper is not yet implemented" do + expect { checker.latest_resolvable_version }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#preferred_resolvable_version" do + it "fails as the native helper is not yet implemented" do + expect { checker.preferred_resolvable_version }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#latest_resolvable_version_with_no_unlock" do + it "fails as the native helper is not yet implemented" do + expect { checker.latest_resolvable_version_with_no_unlock }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#updated_requirements" do + it "fails as the native helper is not yet implemented" do + expect { checker.updated_requirements }. + to raise_error(Dependabot::NotImplemented, + "Bundler 2 adapter does not yet implement dependency_source_type") + end + end + + describe "#requirements_unlocked_or_can_be?" do + it "does not raise as the native helper is not invoked" do + expect { checker.requirements_unlocked_or_can_be? }. + not_to raise_error + end + end + end end diff --git a/common/lib/dependabot/errors.rb b/common/lib/dependabot/errors.rb index d9b75db0d0..bd8ffc360c 100644 --- a/common/lib/dependabot/errors.rb +++ b/common/lib/dependabot/errors.rb @@ -47,6 +47,8 @@ class OutOfDisk < DependabotError; end class OutOfMemory < DependabotError; end + class NotImplemented < DependabotError; end + ##################### # Repo level errors # ##################### diff --git a/common/lib/dependabot/file_parsers/base.rb b/common/lib/dependabot/file_parsers/base.rb index 974e6fde11..7c4c7be95d 100644 --- a/common/lib/dependabot/file_parsers/base.rb +++ b/common/lib/dependabot/file_parsers/base.rb @@ -3,15 +3,16 @@ module Dependabot module FileParsers class Base - attr_reader :dependency_files, :repo_contents_path, :credentials, :source + attr_reader :dependency_files, :repo_contents_path, :credentials, :source, :options def initialize(dependency_files:, repo_contents_path: nil, source:, - credentials: [], reject_external_code: false) + credentials: [], reject_external_code: false, options: {}) @dependency_files = dependency_files @repo_contents_path = repo_contents_path @credentials = credentials @source = source @reject_external_code = reject_external_code + @options = options check_required_files end diff --git a/common/lib/dependabot/update_checkers/base.rb b/common/lib/dependabot/update_checkers/base.rb index fe30cae0d5..fa2fd12ef1 100644 --- a/common/lib/dependabot/update_checkers/base.rb +++ b/common/lib/dependabot/update_checkers/base.rb @@ -9,12 +9,14 @@ module UpdateCheckers class Base attr_reader :dependency, :dependency_files, :repo_contents_path, :credentials, :ignored_versions, :raise_on_ignored, - :security_advisories, :requirements_update_strategy + :security_advisories, :requirements_update_strategy, + :options def initialize(dependency:, dependency_files:, repo_contents_path: nil, credentials:, ignored_versions: [], raise_on_ignored: false, security_advisories: [], - requirements_update_strategy: nil) + requirements_update_strategy: nil, + options: {}) @dependency = dependency @dependency_files = dependency_files @repo_contents_path = repo_contents_path @@ -23,6 +25,7 @@ def initialize(dependency:, dependency_files:, repo_contents_path: nil, @ignored_versions = ignored_versions @raise_on_ignored = raise_on_ignored @security_advisories = security_advisories + @options = options end def up_to_date?