Skip to content

Commit

Permalink
Merge branch 'main' into Bump-Elixir-to-16
Browse files Browse the repository at this point in the history
  • Loading branch information
thavaahariharangit authored Jan 17, 2025
2 parents cfb37f8 + 1328acb commit e5c0391
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 93 deletions.
34 changes: 23 additions & 11 deletions bundler/lib/dependabot/bundler/file_updater/gemspec_updater.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "dependabot/bundler/file_updater"
Expand All @@ -9,13 +9,17 @@ class FileUpdater
class GemspecUpdater
require_relative "requirement_replacer"

extend T::Sig

sig { params(dependencies: T::Array[Dependabot::Dependency], gemspec: Dependabot::DependencyFile).void }
def initialize(dependencies:, gemspec:)
@dependencies = dependencies
@gemspec = gemspec
@dependencies = T.let(dependencies, T::Array[Dependabot::Dependency])
@gemspec = T.let(gemspec, Dependabot::DependencyFile)
end

sig { returns(String) }
def updated_gemspec_content
content = gemspec.content
content = T.let(T.must(gemspec.content), String)

dependencies.each do |dependency|
content = replace_gemspec_version_requirement(
Expand All @@ -28,21 +32,28 @@ def updated_gemspec_content

private

sig { returns(T::Array[Dependabot::Dependency]) }
attr_reader :dependencies

sig { returns(Dependabot::DependencyFile) }
attr_reader :gemspec

sig do
params(gemspec: Dependabot::DependencyFile, dependency: Dependabot::Dependency,
content: String).returns(String)
end
def replace_gemspec_version_requirement(gemspec, dependency, content)
return content unless requirement_changed?(gemspec, dependency)

updated_requirement =
dependency.requirements
.find { |r| r[:file] == gemspec.name }
.fetch(:requirement)
T.must(dependency.requirements
.find { |r| r[:file] == gemspec.name })
.fetch(:requirement)

previous_requirement =
dependency.previous_requirements
.find { |r| r[:file] == gemspec.name }
.fetch(:requirement)
T.must(T.must(dependency.previous_requirements)
.find { |r| r[:file] == gemspec.name })
.fetch(:requirement)

RequirementReplacer.new(
dependency: dependency,
Expand All @@ -52,9 +63,10 @@ def replace_gemspec_version_requirement(gemspec, dependency, content)
).rewrite(content)
end

sig { params(file: Dependabot::DependencyFile, dependency: Dependabot::Dependency).returns(T::Boolean) }
def requirement_changed?(file, dependency)
changed_requirements =
dependency.requirements - dependency.previous_requirements
dependency.requirements - T.must(dependency.previous_requirements)

changed_requirements.any? { |f| f[:file] == file.name }
end
Expand Down
111 changes: 69 additions & 42 deletions python/lib/dependabot/python/file_parser.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "dependabot/dependency"
Expand All @@ -17,13 +17,14 @@

module Dependabot
module Python
class FileParser < Dependabot::FileParsers::Base
class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
extend T::Sig
require_relative "file_parser/pipfile_files_parser"
require_relative "file_parser/pyproject_files_parser"
require_relative "file_parser/setup_file_parser"
require_relative "file_parser/python_requirement_parser"

DEPENDENCY_GROUP_KEYS = [
DEPENDENCY_GROUP_KEYS = T.let([
{
pipfile: "packages",
lockfile: "default"
Expand All @@ -32,7 +33,7 @@ class FileParser < Dependabot::FileParsers::Base
pipfile: "dev-packages",
lockfile: "develop"
}
].freeze
].freeze, T::Array[T::Hash[Symbol, String]])
REQUIREMENT_FILE_EVALUATION_ERRORS = %w(
InstallationError RequirementsFileParseError InvalidMarker
InvalidRequirement ValueError RecursionError
Expand All @@ -43,6 +44,7 @@ class FileParser < Dependabot::FileParsers::Base
# in any way if any metric collection exception start happening
UNDETECTED_PACKAGE_MANAGER_VERSION = "0.0"

sig { override.returns(T::Array[Dependabot::Dependency]) }
def parse
# TODO: setup.py from external dependencies is evaluated. Provide guards before removing this.
raise Dependabot::UnexpectedExternalCode if @reject_external_code
Expand All @@ -57,7 +59,7 @@ def parse
dependency_set.dependencies
end

sig { returns(Ecosystem) }
sig { override.returns(Ecosystem) }
def ecosystem
@ecosystem ||= T.let(
Ecosystem.new(
Expand All @@ -71,18 +73,16 @@ def ecosystem

private

sig { returns(Dependabot::Python::LanguageVersionManager) }
def language_version_manager
@language_version_manager ||=
LanguageVersionManager.new(
python_requirement_parser: python_requirement_parser
)
@language_version_manager ||= T.let(LanguageVersionManager.new(python_requirement_parser:
python_requirement_parser), T.nilable(LanguageVersionManager))
end

sig { returns(Dependabot::Python::FileParser::PythonRequirementParser) }
def python_requirement_parser
@python_requirement_parser ||=
FileParser::PythonRequirementParser.new(
dependency_files: dependency_files
)
@python_requirement_parser ||= T.let(FileParser::PythonRequirementParser.new(dependency_files:
dependency_files), T.nilable(FileParser::PythonRequirementParser))
end

sig { returns(Ecosystem::VersionManager) }
Expand All @@ -91,7 +91,7 @@ def package_manager
Dependabot.logger.info("Detected package manager : #{detected_package_manager.name}")
end

@package_manager ||= detected_package_manager
@package_manager ||= T.let(detected_package_manager, T.nilable(Dependabot::Ecosystem::VersionManager))
end

sig { returns(Ecosystem::VersionManager) }
Expand Down Expand Up @@ -188,7 +188,7 @@ def package_manager_version(package_manager)
end

# setup python local setup on file parser stage
sig { void }
sig { returns(T.nilable(String)) }
def setup_python_environment
language_version_manager.install_required_python

Expand All @@ -198,14 +198,15 @@ def setup_python_environment
nil
end

sig { params(package_manager: String, version: String).void }
sig { params(package_manager: String, version: String).returns(T::Boolean) }
def log_if_version_malformed(package_manager, version)
# logs warning if malformed version is found
return true if version.match?(/^\d+(?:\.\d+)*$/)

Dependabot.logger.warn(
"Detected #{package_manager} with malformed version #{version}"
)
if version.match?(/^\d+(?:\.\d+)*$/)
true
else
Dependabot.logger.warn("Detected #{package_manager} with malformed version #{version}")
false
end
end

sig { returns(String) }
Expand All @@ -231,24 +232,24 @@ def language
)
end

sig { returns(T::Array[Dependabot::DependencyFile]) }
def requirement_files
dependency_files.select { |f| f.name.end_with?(".txt", ".in") }
end

sig { returns(DependencySet) }
def pipenv_dependencies
@pipenv_dependencies ||=
PipfileFilesParser
.new(dependency_files: dependency_files)
.dependency_set
@pipenv_dependencies ||= T.let(PipfileFilesParser.new(dependency_files:
dependency_files).dependency_set, T.nilable(DependencySet))
end

sig { returns(DependencySet) }
def pyproject_file_dependencies
@pyproject_file_dependencies ||=
PyprojectFilesParser
.new(dependency_files: dependency_files)
.dependency_set
@pyproject_file_dependencies ||= T.let(PyprojectFilesParser.new(dependency_files:
dependency_files).dependency_set, T.nilable(DependencySet))
end

sig { returns(DependencySet) }
def requirement_dependencies
dependencies = DependencySet.new
parsed_requirement_files.each do |dep|
Expand Down Expand Up @@ -286,20 +287,23 @@ def requirement_dependencies
dependencies
end

sig { params(name: T.nilable(String), version: T.nilable(String)).returns(T::Boolean) }
def old_pyyaml?(name, version)
major_version = version&.split(".")&.first
return false unless major_version

name == "pyyaml" && major_version < "6"
end

sig { params(filename: String).returns(T::Array[String]) }
def group_from_filename(filename)
if filename.include?("dev") then ["dev-dependencies"]
else
["dependencies"]
end
end

sig { params(dep: T.untyped).returns(T::Boolean) }
def blocking_marker?(dep)
return false if dep["markers"] == "None"

Expand All @@ -316,6 +320,9 @@ def blocking_marker?(dep)
end
end

sig do
params(marker: T.untyped, python_version: T.any(String, Integer, Gem::Version)).returns(T::Boolean)
end
def marker_satisfied?(marker, python_version)
conditions = marker.split(/\s+(and|or)\s+/)

Expand All @@ -337,6 +344,10 @@ def marker_satisfied?(marker, python_version)
result
end

sig do
params(condition: T.untyped,
python_version: T.any(String, Integer, Gem::Version)).returns(T::Boolean)
end
def evaluate_condition(condition, python_version)
operator, version = condition.match(/([<>=!]=?)\s*"?([\d.]+)"?/)&.captures

Expand All @@ -356,13 +367,13 @@ def evaluate_condition(condition, python_version)
end
end

sig { returns(DependencySet) }
def setup_file_dependencies
@setup_file_dependencies ||=
SetupFileParser
.new(dependency_files: dependency_files)
.dependency_set
@setup_file_dependencies ||= T.let(SetupFileParser.new(dependency_files: dependency_files)
.dependency_set, T.nilable(DependencySet))
end

sig { returns(T.untyped) }
def parsed_requirement_files
SharedHelpers.in_a_temporary_directory do
write_temporary_dependency_files
Expand All @@ -383,6 +394,7 @@ def parsed_requirement_files
raise Dependabot::DependencyFileNotEvaluatable, e.message
end

sig { params(requirements: T.untyped).returns(T.untyped) }
def check_requirements(requirements)
requirements.each do |dep|
next unless dep["requirement"]
Expand All @@ -393,18 +405,22 @@ def check_requirements(requirements)
end
end

sig { returns(T::Boolean) }
def pipcompile_in_file
requirement_files.any? { |f| f.name.end_with?(PipCompilePackageManager::MANIFEST_FILENAME) }
end

sig { returns(T::Boolean) }
def pipenv_files
dependency_files.any? { |f| f.name == PipenvPackageManager::LOCKFILE_FILENAME }
end

sig { returns(T.nilable(TrueClass)) }
def poetry_files
true if get_original_file(PoetryPackageManager::LOCKFILE_NAME)
end

sig { returns(T::Array[Dependabot::DependencyFile]) }
def write_temporary_dependency_files
dependency_files
.reject { |f| f.name == ".python-version" }
Expand All @@ -415,6 +431,7 @@ def write_temporary_dependency_files
end
end

sig { params(file: T.untyped).returns(T.untyped) }
def remove_imports(file)
return file.content if file.path.end_with?(".tar.gz", ".whl", ".zip")

Expand All @@ -424,10 +441,12 @@ def remove_imports(file)
.join
end

sig { params(name: String, extras: T::Array[String]).returns(String) }
def normalised_name(name, extras = [])
NameNormaliser.normalise_including_extras(name, extras)
end

sig { override.returns(T.untyped) }
def check_required_files
filenames = dependency_files.map(&:name)
return if filenames.any? { |name| name.end_with?(".txt", ".in") }
Expand All @@ -439,37 +458,45 @@ def check_required_files
raise "Missing required files!"
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def pipfile
@pipfile ||= get_original_file("Pipfile")
@pipfile ||= T.let(get_original_file("Pipfile"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def pipfile_lock
@pipfile_lock ||= get_original_file("Pipfile.lock")
@pipfile_lock ||= T.let(get_original_file("Pipfile.lock"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def pyproject
@pyproject ||= get_original_file("pyproject.toml")
@pyproject ||= T.let(get_original_file("pyproject.toml"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def poetry_lock
@poetry_lock ||= get_original_file("poetry.lock")
@poetry_lock ||= T.let(get_original_file("poetry.lock"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def setup_file
@setup_file ||= get_original_file("setup.py")
@setup_file ||= T.let(get_original_file("setup.py"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def setup_cfg_file
@setup_cfg_file ||= get_original_file("setup.cfg")
@setup_cfg_file ||= T.let(get_original_file("setup.cfg"), T.nilable(Dependabot::DependencyFile))
end

sig { returns(T::Array[Dependabot::Python::Requirement]) }
def pip_compile_files
@pip_compile_files ||=
dependency_files.select { |f| f.name.end_with?(".in") }
@pip_compile_files ||= T.let(dependency_files.select { |f| f.name.end_with?(".in") }, T.untyped)
end

sig { returns(Dependabot::Python::PipCompileFileMatcher) }
def pip_compile_file_matcher
@pip_compile_file_matcher ||= PipCompileFileMatcher.new(pip_compile_files)
@pip_compile_file_matcher ||= T.let(PipCompileFileMatcher.new(pip_compile_files),
T.nilable(Dependabot::Python::PipCompileFileMatcher))
end
end
end
Expand Down
Loading

0 comments on commit e5c0391

Please sign in to comment.