Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand Centralized Ecosystem Format with Language Version Information for Bundler #10867

Merged
merged 12 commits into from
Nov 5, 2024
Merged
8 changes: 8 additions & 0 deletions bundler/helpers/v2/lib/functions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,12 @@ def self.git_source_credentials(credentials)
credentials
.select { |cred| cred["type"] == "git_source" }
end

def self.bundler_raw_version
Bundler::VERSION
end

def self.ruby_raw_version
RUBY_VERSION
end
end
3 changes: 2 additions & 1 deletion bundler/lib/dependabot/bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@

# These all need to be required so the various classes can be registered in a
# lookup table of package manager names to concrete classes.
require "dependabot/bundler/language"
require "dependabot/bundler/package_manager"
require "dependabot/bundler/file_fetcher"
require "dependabot/bundler/file_parser"
require "dependabot/bundler/update_checker"
require "dependabot/bundler/file_updater"
require "dependabot/bundler/metadata_finder"
require "dependabot/bundler/requirement"
require "dependabot/bundler/version"
require "dependabot/bundler/package_manager"

require "dependabot/pull_request_creator/labeler"
Dependabot::PullRequestCreator::Labeler
Expand Down
57 changes: 55 additions & 2 deletions bundler/lib/dependabot/bundler/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# frozen_string_literal: true

require "parallel"
require "dependabot/bundler/language"
require "dependabot/bundler/package_manager"
require "dependabot/dependency"
require "dependabot/file_parsers"
require "dependabot/file_parsers/base"
Expand Down Expand Up @@ -37,7 +39,8 @@
@ecosystem ||= T.let(
Ecosystem.new(
name: ECOSYSTEM,
package_manager: package_manager
package_manager: package_manager,
language: language
),
T.nilable(Ecosystem)
)
Expand All @@ -47,7 +50,12 @@

sig { returns(Ecosystem::VersionManager) }
def package_manager
PackageManager.new(bundler_version)
PackageManager.new(bundler_raw_version)
end

sig { returns(Ecosystem::VersionManager) }
def language
Language.new(ruby_raw_version)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Tip: If the package manager is unsupported, the language version may not be retrievable. Additionally, fetching the language version is unnecessary, as the process will raise an unsupported package manager error.

end

def check_external_code(dependencies)
Expand Down Expand Up @@ -327,6 +335,51 @@
.reject { |f| f.name == "gems.rb" }
end

sig { returns(String) }
def bundler_raw_version
return bundler_raw_version if defined?(@bundler_raw_version)

package_manager = PackageManager.new(bundler_version)

# If selected version is unsupported, we are going to throw unsupported error
# So we shoudn't try to get the raw version

Check failure on line 345 in bundler/lib/dependabot/bundler/file_parser.rb

View workflow job for this annotation

GitHub Actions / Check for spelling errors

shoudn't ==> shouldn't
return bundler_version if package_manager.unsupported?

# read raw version directly from the ecosystem environment
bundler_raw_version = SharedHelpers.in_a_temporary_repo_directory(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Tip: When retrieving the full raw version of Bundler, note that if an unsupported Bundler version (e.g., "1") is specified, retrieving the raw version won’t be possible. In such cases, an "unsupported version" is getting raised error so we won't need to fetch the raw version.

base_directory,
repo_contents_path
) do
write_temporary_dependency_files
NativeHelpers.run_bundler_subprocess(
function: "bundler_raw_version",
args: {},
bundler_version: bundler_version,
options: { timeout_per_operation_seconds: 10 }
)
end
bundler_raw_version || ::Bundler::VERSION
end

sig { returns(String) }
def ruby_raw_version
return @ruby_raw_version if defined?(@ruby_raw_version)

ruby_raw_version = SharedHelpers.in_a_temporary_repo_directory(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Tip: We want to find the raw version for language directly from the ecosystem environment.

base_directory,
repo_contents_path
) do
write_temporary_dependency_files
NativeHelpers.run_bundler_subprocess(
function: "ruby_raw_version",
args: {},
bundler_version: bundler_version,
options: { timeout_per_operation_seconds: 10 }
)
end
ruby_raw_version || RUBY_VERSION
end

sig { returns(String) }
def bundler_version
@bundler_version ||= Helpers.bundler_version(lockfile)
Expand Down
1 change: 1 addition & 0 deletions bundler/lib/dependabot/bundler/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module Helpers
# newest version we support
DEFAULT = V2
BUNDLER_MAJOR_VERSION_REGEX = /BUNDLED WITH\s+(?<version>\d+)\./m
RUBY_VERSION_REGEX = /RUBY VERSION\s+ruby\s+([^\s]+)/

sig { params(lockfile: T.nilable(Dependabot::DependencyFile)).returns(String) }
def self.bundler_version(lockfile)
Expand Down
24 changes: 24 additions & 0 deletions bundler/lib/dependabot/bundler/language.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# typed: strong
# frozen_string_literal: true

require "sorbet-runtime"
require "dependabot/bundler/version"
require "dependabot/ecosystem"

module Dependabot
module Bundler
LANGUAGE = "ruby"

class Language < Dependabot::Ecosystem::VersionManager
extend T::Sig

sig { params(raw_version: String).void }
def initialize(raw_version)
super(
LANGUAGE,
Version.new(raw_version)
)
end
end
end
end
88 changes: 79 additions & 9 deletions bundler/spec/dependabot/bundler/helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,59 @@
# frozen_string_literal: true

require "spec_helper"

require "dependabot/bundler/helpers"

RSpec.describe Dependabot::Bundler::Helpers do
let(:no_lockfile) { nil }
let(:no_gemfile) { nil }
let(:no_ruby_version_file) { nil }

let(:gemfile_with_ruby_version) do
Dependabot::DependencyFile.new(name: "Gemfile", content: <<~GEMFILE)
source 'https://rubygems.org'
ruby '3.0.0'
gem 'rails'
GEMFILE
end

let(:lockfile_bundled_with_missing) do
let(:lockfile_with_ruby_version) do
Dependabot::DependencyFile.new(name: "Gemfile.lock", content: <<~LOCKFILE)
Mock Gemfile.lock Content Goes Here
RUBY VERSION
ruby 2.7.2
LOCKFILE
end

let(:lockfile_bundled_with_v1) do
Dependabot::DependencyFile.new(name: "Gemfile.lock", content: <<~LOCKFILE)
Mock Gemfile.lock Content Goes Here

BUNDLED WITH
1.17.3
LOCKFILE
end

let(:lockfile_bundled_with_v2) do
Dependabot::DependencyFile.new(name: "Gemfile.lock", content: <<~LOCKFILE)
Mock Gemfile.lock Content Goes Here

BUNDLED WITH
2.2.11
LOCKFILE
end

let(:lockfile_bundled_with_future_version) do
Dependabot::DependencyFile.new(name: "Gemfile.lock", content: <<~LOCKFILE)
Mock Gemfile.lock Content Goes Here

BUNDLED WITH
3.9.99
LOCKFILE
end

let(:lockfile_bundled_with_missing) do
Dependabot::DependencyFile.new(name: "Gemfile.lock", content: <<~LOCKFILE)
Mock Gemfile.lock Content Goes Here
LOCKFILE
end

let(:ruby_version_file) do
Dependabot::DependencyFile.new(name: ".ruby-version", content: "ruby-2.7.1")
end

describe "#bundler_version" do
def described_method(lockfile)
described_class.bundler_version(lockfile)
Expand Down Expand Up @@ -92,4 +106,60 @@ def described_method(lockfile)
expect(described_method(lockfile_bundled_with_future_version)).to eql("3")
end
end

describe "#ruby_version" do
context "when there is lockfile, no gemfile, and no ruby version file" do
it "returns the Ruby version from the lockfile" do
expect(described_class.ruby_version(no_gemfile, lockfile_with_ruby_version)).to eq("2.7.2")
end
end

context "when there is lockfile, gemfile, and no ruby version file" do
it "returns the Ruby version from the lockfile" do
expect(described_class.ruby_version(gemfile_with_ruby_version, lockfile_with_ruby_version)).to eq("2.7.2")
end
end

context "when there is lockfile, no gemfile, and a ruby version file" do
it "returns the Ruby version from the .ruby-version file" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return("2.7.1")
expect(described_class.ruby_version(no_gemfile, lockfile_with_ruby_version)).to eq("2.7.1")
end
end

context "when there is lockfile, gemfile, and a ruby version file" do
it "returns the Ruby version from the .ruby-version file" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return("2.7.1")
expect(described_class.ruby_version(gemfile_with_ruby_version, lockfile_with_ruby_version)).to eq("2.7.1")
end
end

context "when there is no lockfile, but gemfile and a ruby version file" do
it "returns the Ruby version from the .ruby-version file" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return("2.7.1")
expect(described_class.ruby_version(gemfile_with_ruby_version, no_lockfile)).to eq("2.7.1")
end
end

context "when there is no lockfile, no gemfile, and a ruby version file" do
it "returns the Ruby version from the .ruby-version file" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return("2.7.1")
expect(described_class.ruby_version(no_gemfile, no_lockfile)).to eq("2.7.1")
end
end

context "when there is no lockfile, gemfile with ruby version, and no ruby version file" do
it "returns the Ruby version from the Gemfile" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return(nil)
expect(described_class.ruby_version(gemfile_with_ruby_version, no_lockfile)).to eq("3.0.0")
end
end

context "when there is no lockfile, no gemfile, and no ruby version file" do
it "falls back to the current Ruby version" do
allow(described_class).to receive(:ruby_version_from_ruby_version_file).and_return(nil)
expect(described_class.ruby_version(no_gemfile, no_lockfile)).to eq(RUBY_VERSION)
end
end
end
end
49 changes: 49 additions & 0 deletions bundler/spec/dependabot/bundler/language_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# typed: false
# frozen_string_literal: true

require "dependabot/bundler/language"
require "dependabot/ecosystem"
require "spec_helper"

RSpec.describe Dependabot::Bundler::Language do
let(:language) { described_class.new(version) }
let(:version) { "3.0.0" }

describe "#initialize" do
context "when version is a String" do
let(:version) { "3.0.0" }

it "sets the version correctly" do
expect(language.version).to eq(Dependabot::Bundler::Version.new(version))
end

it "sets the name correctly" do
expect(language.name).to eq(Dependabot::Bundler::LANGUAGE)
end
end

context "when version is a Dependabot::Bundler::Version" do
let(:version) { "3.0.0" }

it "sets the version correctly" do
expect(language.version).to eq(version)
end

it "sets the name correctly" do
expect(language.name).to eq(Dependabot::Bundler::LANGUAGE)
end
end
end

describe "#unsupported?" do
it "returns false by default as no specific support or deprecation for languages is currently defined" do
expect(language.unsupported?).to be false
end
end

describe "#deprecated?" do
it "returns false by default as no specific deprecation for languages is currently defined" do
expect(language.deprecated?).to be false
end
end
end
Loading
Loading