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

Add Metrics Collection for Composer Ecosystem: Package Manager and Language Details #11025

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions composer/lib/dependabot/composer/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
@ecosystem ||= T.let(
Ecosystem.new(
name: ECOSYSTEM,
package_manager: package_manager
package_manager: package_manager,
language: language
),
T.nilable(Ecosystem)
)
Expand All @@ -50,7 +51,40 @@

sig { returns(Ecosystem::VersionManager) }
def package_manager
PackageManager.new(composer_version)
raw_composer_version = env_versions[:composer] || composer_version
PackageManager.new(
raw_composer_version
)
end

sig { returns(T.nilable(Ecosystem::VersionManager)) }
def language
php_version = env_versions[:php]

return unless php_version

Language.new(
php_version,
requirement: php_requirement
)
end

sig { returns(T::Hash[Symbol, T.nilable(String)]) }
def env_versions
@env_versions ||= T.let(
Helpers.fetch_composer_and_php_versions,
T.nilable(T::Hash[Symbol, T.nilable(String)])
)
end

# Capture PHP requirement from the composer.json
sig { returns(T.nilable(Requirement)) }
def php_requirement
requirement_string = Helpers.php_constraint(parsed_composer_json)

return nil unless requirement_string

Requirement.new(requirement_string.strip.split(Requirement::OR_SEPARATOR))

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on a
library input
may run slow on strings with many repetitions of '\t'.
This
regular expression
that depends on a
library input
may run slow on strings with many repetitions of '\t'.
end

sig { returns(DependencySet) }
Expand Down
49 changes: 49 additions & 0 deletions composer/lib/dependabot/composer/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,55 @@ def self.extract_and_clean_dependency_url(message, regex)
nil
end

# Run single composer command returning stdout/stderr
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
def self.package_manager_run_command(command, fingerprint: nil)
full_command = "composer #{command}"

Dependabot.logger.info("Running composer command: #{full_command}")

result = Dependabot::SharedHelpers.run_shell_command(
full_command,
fingerprint: "composer #{fingerprint || command}"
).strip

Dependabot.logger.info("Command executed successfully: #{full_command}")
result
rescue StandardError => e
Dependabot.logger.error("Error running composer command: #{full_command}, Error: #{e.message}")
raise
end

# Example output:
# [dependabot] ~ $ composer --version
# Composer version 2.7.7 2024-06-10 22:11:12
# PHP version 7.4.33 (/usr/bin/php7.4)
# Run the "diagnose" command to get more detailed diagnostics output.
# Get the version of the composer and php form the command output
# @return [Hash] with the composer and php version
# => { composer: "2.7.7", php: "7.4.33" }
sig { returns(T::Hash[Symbol, T.nilable(String)]) }
def self.fetch_composer_and_php_versions
output = package_manager_run_command("--version").strip

composer_version = capture_version(output, /Composer version (?<version>\d+\.\d+\.\d+)/)
php_version = capture_version(output, /PHP version (?<version>\d+\.\d+\.\d+)/)

Dependabot.logger.info("Dependabot running with Composer version: #{composer_version}")
Dependabot.logger.info("Dependabot running with PHP version: #{php_version}")

{ composer: composer_version, php: php_version }
rescue StandardError => e
Dependabot.logger.error("Error fetching versions for package manager and language #{name}: #{e.message}")
raise
end

sig { params(output: String, regex: Regexp).returns(T.nilable(String)) }
def self.capture_version(output, regex)
match = output.match(regex)
match&.named_captures&.fetch("version", nil)
end

# Capture the platform PHP version from composer.json
sig { params(parsed_composer_json: T::Hash[String, T.untyped]).returns(T.nilable(String)) }
def self.capture_platform_php(parsed_composer_json)
Expand Down
7 changes: 5 additions & 2 deletions composer/lib/dependabot/composer/language.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ class Language < Dependabot::Ecosystem::VersionManager

NAME = "php"

sig { params(raw_version: String).void }
def initialize(raw_version)
sig { params(raw_version: String, requirement: T.nilable(Requirement)).void }
def initialize(raw_version, requirement: nil)
super(
NAME,
Version.new(raw_version),
[],
[],
requirement
)
end

Expand Down
14 changes: 12 additions & 2 deletions composer/spec/dependabot/composer/file_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -410,9 +410,19 @@
end
end

describe "#package_manager" do
it "returns the correct package manager" do
describe "#ecosystem" do
it "returns the correct ecosystem" do
expect(parser.ecosystem).to be_a(Dependabot::Ecosystem)
end

it "returns package manager with version" do
expect(parser.ecosystem.package_manager).to be_a(Dependabot::Composer::PackageManager)
expect(parser.ecosystem.package_manager.version).to eq("2.7.7")
end

it "returns language with version" do
expect(parser.ecosystem.language).to be_a(Dependabot::Composer::Language)
expect(parser.ecosystem.language.version).to eq("7.4.33")
end
end
end
Loading