Skip to content

Commit

Permalink
Update uv to remove other python package managers
Browse files Browse the repository at this point in the history
  • Loading branch information
markhallen committed Feb 27, 2025
1 parent f54284d commit 1dcd9c9
Show file tree
Hide file tree
Showing 43 changed files with 188 additions and 8,350 deletions.
167 changes: 16 additions & 151 deletions uv/lib/dependabot/uv/file_fetcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,12 @@ def self.required_files_in?(filenames)
# If there is a directory of requirements return true
return true if filenames.include?("requirements")

# If this repo is using a Pipfile return true
return true if filenames.include?("Pipfile")

# If this repo is using pyproject.toml return true
return true if filenames.include?("pyproject.toml")

return true if filenames.include?("setup.py")

filenames.include?("setup.cfg")
filenames.include?("pyproject.toml")
end

def self.required_files_message
"Repo must contain a requirements.txt, setup.py, setup.cfg, pyproject.toml, " \
"or a Pipfile."
"Repo must contain a requirements.txt, requirements.in, or pyproject.toml" \
end

def ecosystem_versions
Expand Down Expand Up @@ -72,16 +64,12 @@ def ecosystem_versions
def fetch_files
fetched_files = []

fetched_files += pipenv_files
fetched_files += pyproject_files

fetched_files += requirements_in_files
fetched_files += requirement_files if requirements_txt_files.any?

fetched_files << setup_file if setup_file
fetched_files << setup_cfg_file if setup_cfg_file
fetched_files += project_files
fetched_files << pip_conf if pip_conf
fetched_files << python_version_file if python_version_file

uniq_files(fetched_files)
Expand All @@ -95,12 +83,8 @@ def uniq_files(fetched_files)
.reject { |f| uniq_files.map(&:name).include?(f.name) }
end

def pipenv_files
[pipfile, pipfile_lock].compact
end

def pyproject_files
[pyproject, poetry_lock, pdm_lock].compact
[pyproject].compact
end

def requirement_files
Expand All @@ -111,24 +95,6 @@ def requirement_files
]
end

def setup_file
return @setup_file if defined?(@setup_file)

@setup_file = fetch_file_if_present("setup.py")
end

def setup_cfg_file
return @setup_cfg_file if defined?(@setup_cfg_file)

@setup_cfg_file = fetch_file_if_present("setup.cfg")
end

def pip_conf
return @pip_conf if defined?(@pip_conf)

@pip_conf = fetch_support_file("pip.conf")
end

def python_version_file
return @python_version_file if defined?(@python_version_file)

Expand All @@ -144,36 +110,12 @@ def python_version_file
&.tap { |f| f.name = ".python-version" }
end

def pipfile
return @pipfile if defined?(@pipfile)

@pipfile = fetch_file_if_present("Pipfile")
end

def pipfile_lock
return @pipfile_lock if defined?(@pipfile_lock)

@pipfile_lock = fetch_file_if_present("Pipfile.lock")
end

def pyproject
return @pyproject if defined?(@pyproject)

@pyproject = fetch_file_if_present("pyproject.toml")
end

def poetry_lock
return @poetry_lock if defined?(@poetry_lock)

@poetry_lock = fetch_file_if_present("poetry.lock")
end

def pdm_lock
return @pdm_lock if defined?(@pdm_lock)

@pdm_lock = fetch_file_if_present("pdm.lock")
end

def requirements_txt_files
req_txt_and_in_files.select { |f| f.name.end_with?(".txt") }
end
Expand All @@ -183,14 +125,6 @@ def requirements_in_files
child_requirement_in_files
end

def parsed_pipfile
raise "No Pipfile" unless pipfile

@parsed_pipfile ||= TomlRB.parse(pipfile.content)
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
raise Dependabot::DependencyFileNotParseable, pipfile.path
end

def parsed_pyproject
raise "No pyproject.toml" unless pyproject

Expand Down Expand Up @@ -300,18 +234,8 @@ def project_files
path_dependencies.each do |dep|
path = dep[:path]
project_files += fetch_project_file(path)
rescue Dependabot::DependencyFileNotFound => e
unfetchable_deps << if sdist_or_wheel?(path)
e.file_path&.gsub(%r{^/}, "")
else
"\"#{dep[:name]}\" at #{cleanpath(File.join(directory, dep[:file]))}"
end
end

poetry_path_dependencies.each do |path|
project_files += fetch_project_file(path)
rescue Dependabot::DependencyFileNotFound => e
unfetchable_deps << e.file_path&.gsub(%r{^/}, "")
rescue Dependabot::DependencyFileNotFound
unfetchable_deps << "\"#{dep[:name]}\" at #{cleanpath(File.join(directory, dep[:file]))}"
end

raise Dependabot::PathDependenciesNotReachable, unfetchable_deps if unfetchable_deps.any?
Expand All @@ -322,48 +246,22 @@ def project_files
def fetch_project_file(path)
project_files = []

path = cleanpath(File.join(path, "setup.py")) unless sdist_or_wheel?(path)

return [] if path == "setup.py" && setup_file
path = cleanpath(File.join(path, "pyproject.toml")) unless sdist_or_wheel?(path)

project_files <<
begin
fetch_file_from_host(
path,
fetch_submodules: true
).tap { |f| f.support_file = true }
rescue Dependabot::DependencyFileNotFound
# For projects with pyproject.toml attempt to fetch a pyproject.toml
# at the given path instead of a setup.py.
fetch_file_from_host(
path.gsub("setup.py", "pyproject.toml"),
fetch_submodules: true
).tap { |f| f.support_file = true }
end
return [] if path == "pyproject.toml" && pyproject

return project_files unless path.end_with?(".py")
project_files << fetch_file_from_host(
path,
fetch_submodules: true
).tap { |f| f.support_file = true }

project_files + cfg_files_for_setup_py(path)
project_files
end

def sdist_or_wheel?(path)
path.end_with?(".tar.gz", ".whl", ".zip")
end

def cfg_files_for_setup_py(path)
cfg_path = path.gsub(/\.py$/, ".cfg")

begin
[
fetch_file_from_host(cfg_path, fetch_submodules: true)
.tap { |f| f.support_file = true }
]
rescue Dependabot::DependencyFileNotFound
# Ignore lack of a setup.cfg
[]
end
end

def requirements_file?(file)
return false unless file.content.valid_encoding?
return true if file.name.match?(/requirements/x)
Expand All @@ -377,9 +275,10 @@ def requirements_file?(file)
end

def path_dependencies
requirement_txt_path_dependencies +
requirement_in_path_dependencies +
pipfile_path_dependencies
[
*requirement_txt_path_dependencies,
*requirement_in_path_dependencies
]
end

def requirement_txt_path_dependencies
Expand Down Expand Up @@ -415,40 +314,6 @@ def parse_requirement_path_dependencies(req_file)
uneditable_reqs + editable_reqs
end

def pipfile_path_dependencies
return [] unless pipfile

deps = []
DEPENDENCY_TYPES.each do |dep_type|
next unless parsed_pipfile[dep_type]

parsed_pipfile[dep_type].each do |_, req|
next unless req.is_a?(Hash) && req["path"]

deps << { name: req["path"], path: req["path"], file: pipfile.name }
end
end

deps
end

def poetry_path_dependencies
return [] unless pyproject

paths = []
Dependabot::Uv::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |dep_type|
next unless parsed_pyproject.dig("tool", "poetry", dep_type)

parsed_pyproject.dig("tool", "poetry", dep_type).each do |_, req|
next unless req.is_a?(Hash) && req["path"]

paths << req["path"]
end
end

paths
end

def cleanpath(path)
Pathname.new(path).cleanpath.to_path
end
Expand Down
81 changes: 6 additions & 75 deletions uv/lib/dependabot/uv/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

module Dependabot
module Uv
class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
class FileParser < Dependabot::FileParsers::Base
extend T::Sig
require_relative "file_parser/pipfile_files_parser"
require_relative "file_parser/pyproject_files_parser"
Expand Down Expand Up @@ -51,10 +51,8 @@ def parse

dependency_set = DependencySet.new

dependency_set += pipenv_dependencies if pipfile
dependency_set += pyproject_file_dependencies if pyproject
dependency_set += requirement_dependencies if requirement_files.any?
dependency_set += setup_file_dependencies if setup_file || setup_cfg_file

dependency_set.dependencies
end
Expand Down Expand Up @@ -98,38 +96,14 @@ def package_manager
def detected_package_manager
setup_python_environment if Dependabot::Experiments.enabled?(:enable_file_parser_python_local)

return PipenvPackageManager.new(T.must(detect_pipenv_version)) if detect_pipenv_version

return PoetryPackageManager.new(T.must(detect_poetry_version)) if detect_poetry_version

return PipCompilePackageManager.new(T.must(detect_pipcompile_version)) if detect_pipcompile_version

PipPackageManager.new(detect_pip_version)
end

# Detects the version of poetry. If the version cannot be detected, it returns nil
sig { returns(T.nilable(String)) }
def detect_poetry_version
if poetry_files
package_manager = PoetryPackageManager::NAME

version = package_manager_version(package_manager)
.to_s.split("version ").last&.split(")")&.first

log_if_version_malformed(package_manager, version)

# makes sure we have correct version format returned
version if version&.match?(/^\d+(?:\.\d+)*$/)
end
rescue StandardError
nil
PackageManager.new(T.must(detect_pipcompile_version))
end

# Detects the version of pip-compile. If the version cannot be detected, it returns nil
sig { returns(T.nilable(String)) }
def detect_pipcompile_version
if pipcompile_in_file
package_manager = PipCompilePackageManager::NAME
package_manager = PackageManager::NAME

version = package_manager_version(package_manager)
.to_s.split("version ").last&.split(")")&.first
Expand All @@ -143,45 +117,12 @@ def detect_pipcompile_version
nil
end

# Detects the version of pipenv. If the version cannot be detected, it returns nil
sig { returns(T.nilable(String)) }
def detect_pipenv_version
if pipenv_files
package_manager = PipenvPackageManager::NAME

version = package_manager_version(package_manager)
.to_s.split("version ").last&.strip

log_if_version_malformed(package_manager, version)

# makes sure we have correct version format returned
version if version&.match?(/^\d+(?:\.\d+)*$/)
end
rescue StandardError
nil
end

# Detects the version of pip. If the version cannot be detected, it returns 0.0
sig { returns(String) }
def detect_pip_version
package_manager = PipPackageManager::NAME

version = package_manager_version(package_manager)
.split("from").first&.split("pip")&.last&.strip

log_if_version_malformed(package_manager, version)

version&.match?(/^\d+(?:\.\d+)*$/) ? version : UNDETECTED_PACKAGE_MANAGER_VERSION
rescue StandardError
nil
end

sig { params(package_manager: String).returns(T.any(String, T.untyped)) }
def package_manager_version(package_manager)
version_info = SharedHelpers.run_shell_command("pyenv exec #{package_manager} --version")
Dependabot.logger.info("Package manager #{package_manager}, Info : #{version_info}")

version_info
version_info.match(/\d+(?:\.\d+)*/)&.to_s
rescue StandardError => e
Dependabot.logger.error(e.message)
nil
Expand Down Expand Up @@ -281,7 +222,7 @@ def requirement_dependencies
name: normalised_name(name, dep["extras"]),
version: version&.include?("*") ? nil : version,
requirements: requirements,
package_manager: "pip"
package_manager: "uv"
)
end
dependencies
Expand Down Expand Up @@ -408,17 +349,7 @@ def check_requirements(requirements)

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)
requirement_files.any? { |f| f.name.end_with?(PackageManager::MANIFEST_FILENAME) }
end

sig { returns(T::Array[Dependabot::DependencyFile]) }
Expand Down
Loading

0 comments on commit 1dcd9c9

Please sign in to comment.