Skip to content
Open
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
108 changes: 93 additions & 15 deletions maven/lib/dependabot/maven/file_parser/repositories_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def super_pom
{ url: central_repo_url, id: "central" }
end

sig { params(entry: Nokogiri::XML::Element).returns(T::Hash[Symbol, T.nilable(String)]) }
sig { params(entry: Nokogiri::XML::Node).returns(T::Hash[Symbol, T.nilable(String)]) }
def serialize_mvn_repo(entry)
{
url: entry.at_css("url").content.strip,
Expand Down Expand Up @@ -130,22 +130,100 @@ def serialize_urls(entry, pom)
.returns(T::Array[T::Hash[Symbol, T.untyped]])
end
def gather_repository_urls(pom:, exclude_inherited: false)
repos_in_pom =
Nokogiri::XML(pom.content)
.css(REPOSITORY_SELECTOR)
.map { |node| serialize_mvn_repo(node) }
.reject { |entry| contains_property?(entry[:url]) && !evaluate_properties? }
.select { |entry| entry[:url].start_with?("http") }
.map { |entry| serialize_urls(entry, pom) }

return repos_in_pom if exclude_inherited

urls_in_pom = repos_in_pom.map { |repo| repo[:url] }
unless (parent = parent_pom(pom, urls_in_pom))
return repos_in_pom
repos = repositories_from_pom(pom)
return repos if exclude_inherited

parent = parent_with_repositories(pom, repos)
return repos unless parent

repos + gather_repository_urls(pom: parent)
end

sig do
params(
pom: Dependabot::DependencyFile
).returns(
T::Array[T::Hash[Symbol, T.untyped]]
)
end
def repositories_from_pom(pom)
doc = Nokogiri::XML(pom.content)
doc.remove_namespaces!

repository_nodes(doc)
.filter_map { |node| build_repo_entry(node, pom) }
end

sig do
params(
node: Nokogiri::XML::Node,
pom: Dependabot::DependencyFile
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
end
def build_repo_entry(node, pom)
url = node.at_css("url")&.text&.strip.to_s
return if url.empty?

entry = serialize_mvn_repo(node)

return if property_blocked?(entry)
return unless http_url?(entry)

serialize_urls(entry, pom)
end

sig { params(entry: T::Hash[Symbol, T.nilable(String)]).returns(T::Boolean) }
def property_blocked?(entry)
contains_property?(T.must(entry.fetch(:url))) && !evaluate_properties?
end

sig { params(entry: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
def http_url?(entry)
entry.fetch(:url)&.start_with?("http")
end

sig do
params(
pom: Dependabot::DependencyFile,
repos: T::Array[T::Hash[Symbol, T.untyped]]
).returns(T.nilable(Dependabot::DependencyFile))
end
def parent_with_repositories(pom, repos)
urls = repos.map { |r| r[:url] }
parent_pom(pom, urls)
end

# Returns the repository XML nodes that should be considered when resolving artifacts.
#
# Selection rules:
# - Always includes repositories declared at the project level.
# - Repositories declared inside <profiles> are included only activated explicitly
#
# @example With active profile
# <profile>
# <activation><activeByDefault>true</activeByDefault></activation>
# <repositories>...</repositories>
# </profile>
#
sig { params(doc: Nokogiri::XML::Document).returns(T::Array[Nokogiri::XML::Node]) }
def repository_nodes(doc)
doc.css(REPOSITORY_SELECTOR).select do |repo_node|
profile = repo_node.ancestors("profile").first

# Not in a profile => always include
next true unless profile

# In a profile => only include when activeByDefault=true
active_by_default_profile?(profile)
end
end

sig { params(profile: Nokogiri::XML::Element).returns(T::Boolean) }
def active_by_default_profile?(profile)
node = profile.at_xpath("./activation/activeByDefault")
return false unless node

repos_in_pom + gather_repository_urls(pom: parent)
node.text.strip.casecmp?("true")
end

sig { returns(T::Boolean) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,5 +348,21 @@
end
end
end

context "when there are repository declarations in profiles" do
let(:base_pom_fixture_name) { "custom_repositories_pom_with_profiles.xml" }

it "does not include repositories from profiles that are not activated by default" do
expect(repository_urls).to eq(
%w(
https://repo.jenkins-ci.org/public
https://repo.jenkins-ci.org/incrementals-activated
https://repo.jenkins-ci.org/incrementals-activated-2
https://repo.jenkins-ci.org/another-activated
https://repo.maven.apache.org/maven2
Comment on lines +355 to +362
Copy link

Choose a reason for hiding this comment

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

Looks right.

)
)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This POM demonstrates a project that declares repositories in two places:

1) Top-level <repositories>
- Always active.
- Dependabot should always consider these repositories.

2) Repositories within profiles:
- They are conditionally active depending on profile activation.
- Only profiles that are "active by default" should be considered
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo</groupId>
<artifactId>demo</artifactId>
<version>0-SNAPSHOT</version>
<packaging>pom</packaging>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>https://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<profiles>
<!--- The repositories in this profile should NOT be considered because the profile is not activated (lack of activation defaults to false)-->
<profile>
<id>consume-incrementals</id>
<repositories>
<repository>
<id>incrementals-not-activated</id>
<url>https://repo.jenkins-ci.org/incrementals-disabled/</url>
</repository>
</repositories>
</profile>
<profile>
<!--- The repositories in this profile should be considered because the profile is activated by default -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<id>incrementals-activated</id>
<repositories>
<repository>
<id>incrementals-activated</id>
<url>https://repo.jenkins-ci.org/incrementals-activated/</url>
</repository>
<repository>
<id>incrementals-activated-2</id>
<url>https://repo.jenkins-ci.org/incrementals-activated-2/</url>
</repository>
</repositories>
</profile>
<profile>
<!--- The repositories in this profile should be considered because the profile is activated by default -->
<activation>
<!-- Parsing should be case-insensitive because this is also allowed in Maven -->
<activeByDefault>TRUE</activeByDefault>
</activation>
<id>another-activated</id>
<repositories>
<repository>
<id>another-activated</id>
<url>https://repo.jenkins-ci.org/another-activated/</url>
</repository>
</repositories>
</profile>
<profile>
<!--- The repositories in this profile should NOT be considered because the profile is disabled explicitly -->
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<id>Another profile</id>
<repositories>
<repository>
<id>incrementals</id>
<url>https://repo.jenkins-ci.org/some other repo/</url>
</repository>
</repositories>
</profile>
</profiles>
</project>
Loading