Skip to content

Commit

Permalink
Expand Centralized Ecosystem Format with Language Version Information…
Browse files Browse the repository at this point in the history
… for Bundler (dependabot#10867)

* Add Bundler language version to centralized ecosystem structure
  • Loading branch information
kbukum1 authored Nov 5, 2024
1 parent 2cb56bd commit 957342d
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 27 deletions.
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
61 changes: 59 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 @@ def ecosystem
@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,16 @@ def ecosystem

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

sig { returns(T.nilable(Ecosystem::VersionManager)) }
def language
return @language if defined?(@language)

return nil if package_manager.unsupported?

Language.new(ruby_raw_version)
end

def check_external_code(dependencies)
Expand Down Expand Up @@ -327,6 +339,51 @@ def imported_ruby_files
.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 the selected version is unsupported, an unsupported error will be raised,
# so there’s no need to attempt retrieving the raw version.
return bundler_version if package_manager.unsupported?

# read raw version directly from the ecosystem environment
bundler_raw_version = SharedHelpers.in_a_temporary_repo_directory(
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(
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
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
32 changes: 23 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
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
27 changes: 19 additions & 8 deletions common/lib/dependabot/ecosystem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ class VersionManager
extend T::Helpers

abstract!
# Initialize version information with optional requirement
# @param name [String] the name for the package manager (e.g., "bundler", "npm").
# Initialize version information for a package manager or language.
# @param name [String] the name of the package manager or language (e.g., "bundler", "ruby").
# @param version [Dependabot::Version] the parsed current version.
# @param deprecated_versions [Array<Dependabot::Version>] an array of deprecated versions.
# @param supported_versions [Array<Dependabot::Version>] an array of supported versions.
# @example
# VersionManager.new("bundler", "2.1.4", Dependabot::Version.new("2.1.4"), nil)
# VersionManager.new("bundler", "2.1.4", nil)
sig do
params(
name: String,
Expand Down Expand Up @@ -46,7 +46,7 @@ def initialize(
sig { returns(String) }
attr_reader :name

# The current version of the package manager.
# The current version of the package manager or language.
# @example
# version #=> Dependabot::Version.new("2.1.4")
sig { returns(Dependabot::Version) }
Expand All @@ -68,6 +68,7 @@ def initialize(
# deprecated? #=> true
sig { returns(T::Boolean) }
def deprecated?
# If the version is unsupported, the unsupported error is getting raised separately.
return false if unsupported?

deprecated_versions.include?(version)
Expand Down Expand Up @@ -112,19 +113,23 @@ def support_later_versions?

# Initialize with mandatory name and optional language information.
# @param name [String] the name of the ecosystem (e.g., "bundler", "npm_and_yarn").
# @param package_manager [VersionManager] the package manager.
# @param package_manager [VersionManager] the package manager (mandatory).
# @param language [VersionManager] the language (optional).
sig do
params(
name: String,
package_manager: VersionManager
package_manager: VersionManager,
language: T.nilable(VersionManager)
).void
end
def initialize(
name:,
package_manager:
package_manager:,
language: nil
)
@name = T.let(name, String)
@package_manager = T.let(package_manager, VersionManager)
@language = T.let(language, T.nilable(VersionManager))
end

# The name of the ecosystem (mandatory).
Expand All @@ -135,10 +140,16 @@ def initialize(

# The information related to the package manager (mandatory).
# @example
# package_manager #=> VersionManager.new("bundler", "2.1.4", Version.new("2.1.4"), nil)
# package_manager #=> VersionManager.new("bundler", "2.1.4", deprecated_versions, supported_versions)
sig { returns(VersionManager) }
attr_reader :package_manager

# The information related to the language (optional).
# @example
# language #=> VersionManager.new("ruby", "3.9", deprecated_versions, supported_versions)
sig { returns(T.nilable(VersionManager)) }
attr_reader :language

# Checks if the current version is deprecated.
# Returns true if the version is in the deprecated_versions array; false otherwise.
sig { returns(T::Boolean) }
Expand Down
Loading

0 comments on commit 957342d

Please sign in to comment.