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
8 changes: 4 additions & 4 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SMOKE_TEST_BRANCH: ${{ vars.SMOKE_TEST_BRANCH || 'main' }}
SMOKE_TEST_BRANCH: maven-wrapper
permissions: {}

jobs:
Expand Down Expand Up @@ -45,11 +45,11 @@
cat changes.json

# Read smoke-matrix.json, filtering out paths not in changes.json
jq -c --argjson changes "$(cat changes.json)" '[.[] | select(.core as $p | $changes | index($p))]' .github/smoke-matrix.json > filtered.json

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
cat filtered.json

# Curl the smoke-test tests directory to get a list of tests to run
URL=https://api.github.com/repos/${{ vars.SMOKE_TEST_REPO || 'dependabot/smoke-tests' }}/contents/tests?ref=${{ env.SMOKE_TEST_BRANCH }}
URL=https://api.github.com/repos/${{ vars.SMOKE_TEST_REPO || 'yeikel/smoke-tests' }}/contents/tests?ref=${{ env.SMOKE_TEST_BRANCH }}
curl $URL > tests.json

# Select the names that match smoke-$test*.yaml, where $test is the .text value from filtered.json
Expand Down Expand Up @@ -90,14 +90,14 @@
- name: Restore Smoke Test
id: cache-smoke-test
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:

Check notice

Code scanning / zizmor

code injection via template expansion Note

code injection via template expansion
path: smoke.yaml
key: ${{ matrix.suite.sha }}-${{ matrix.suite.name }}

- name: Download test
if: steps.cache-smoke-test.outputs.cache-hit != 'true'
run: |
gh api "repos/${{ vars.SMOKE_TEST_REPO || 'dependabot/smoke-tests' }}/contents/tests/${{ matrix.suite.name }}?ref=${{ env.SMOKE_TEST_BRANCH }}" -H "Accept: application/vnd.github.raw" > smoke.yaml
gh api "repos/${{ vars.SMOKE_TEST_REPO || 'yeikel/smoke-tests' }}/contents/tests/${{ matrix.suite.name }}?ref=maven-wrapper" -H "Accept: application/vnd.github.raw" > smoke.yaml

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intentional temporarily to execute the integration tests from my fork


- name: Cache Smoke Test
if: steps.cache-smoke-test.outputs.cache-hit != 'true'
Expand All @@ -117,7 +117,7 @@
CACHE=${CACHE%.yaml}
CACHE=${CACHE%.yml}

gh run download --repo dependabot/smoke-tests --name cache-$CACHE --dir cache
gh run download --repo yeikel/smoke-tests --name cache-$CACHE --dir cache
continue-on-error: true

- name: Build ecosystem image
Expand Down
29 changes: 29 additions & 0 deletions maven/lib/dependabot/maven/distributions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# typed: strict
# frozen_string_literal: true

module Dependabot
module Maven
module Distributions
extend T::Sig

# Used to distinguish wrapper requirements (which live in maven-wrapper.properties)
# from regular POM requirements (which live in pom.xml)
DISTRIBUTION_DEPENDENCY_TYPE = "maven-distribution"

# Maven and the maven-wrapper plugin release independently with separate cadences.
# Tracking them as distinct dependencies allows users to update each on their own
# schedule. Users who prefer batched updates can use grouped updates.

MAVEN_DISTRIBUTION_PACKAGE = "org.apache.maven:apache-maven"
MAVEN_WRAPPER_PACKAGE = "org.apache.maven.wrapper:maven-wrapper"

sig { params(requirements: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Boolean) }
def self.distribution_requirements?(requirements)
# Returns true if any requirement came from a maven-wrapper.properties
# file rather than a pom.xml. Used as the primary guard throughout the
# updater pipeline to short-circuit non-wrapper paths.
requirements.any? { |req| req.dig(:source, :type) == DISTRIBUTION_DEPENDENCY_TYPE }
end
end
end
end
70 changes: 69 additions & 1 deletion maven/lib/dependabot/maven/file_fetcher.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# typed: strict
# frozen_string_literal: true

require "base64"
require "nokogiri"
require "sorbet-runtime"

require "dependabot/file_fetchers"
require "dependabot/file_fetchers/base"
require "dependabot/file_filtering"
require "dependabot/experiments"
require "dependabot/maven/file_parser/wrapper_mojo"

module Dependabot
module Maven
Expand All @@ -17,6 +20,14 @@ class FileFetcher < Dependabot::FileFetchers::Base
MODULE_SELECTOR = "project > modules > module, " \
"profile > modules > module"

WRAPPER_PROPERTIES_RELATIVE = ".mvn/wrapper/maven-wrapper.properties"
WRAPPER_JAR_RELATIVE = ".mvn/wrapper/maven-wrapper.jar"
WRAPPER_DOWNLOADER_RELATIVE = ".mvn/wrapper/MavenWrapperDownloader.java"

WRAPPER_UNIX_SCRIPTS = %w(mvnw mvnwDebug).freeze
WRAPPER_WINDOWS_SCRIPTS = %w(mvnw.cmd mvnwDebug.cmd).freeze
WRAPPER_ALL_SCRIPTS = T.let((WRAPPER_UNIX_SCRIPTS + WRAPPER_WINDOWS_SCRIPTS).freeze, T::Array[String])

sig { override.params(filenames: T::Array[String]).returns(T::Boolean) }
def self.required_files_in?(filenames)
filenames.include?("pom.xml")
Expand All @@ -31,10 +42,13 @@ def self.required_files_message
def fetch_files
fetched_files = []
fetched_files << pom
fetched_files += child_poms
poms = child_poms
fetched_files += poms
fetched_files += relative_path_parents(fetched_files)
fetched_files += targetfiles
fetched_files << extensions if extensions
# Pass already-fetched poms so all_wrapper_files does not re-fetch them.
fetched_files += all_wrapper_files([T.must(pom)] + poms)

# Filter excluded files from final collection
filtered_files = fetched_files.uniq.reject do |file|
Expand All @@ -46,6 +60,60 @@ def fetch_files

private

sig { params(dir: String).returns(T::Array[DependencyFile]) }
def wrapper_files_for_dir(dir)
return [] unless Dependabot::Experiments.enabled?(:maven_wrapper_updater)

# Strip leading "./" from root-level paths
properties_path = File.join(dir, WRAPPER_PROPERTIES_RELATIVE).delete_prefix("./")
properties = fetch_file_if_present(properties_path)
return [] unless properties

files = T.let([properties], T::Array[DependencyFile])
WRAPPER_ALL_SCRIPTS.each do |script|
script_path = dir == "." ? script : File.join(dir, script)
f = fetch_file_if_present(script_path)
files << f if f
end

dist_type = FileParser::WrapperMojo.resolve_distribution_type(T.must(properties.content))
files + fetch_wrapper_artifact_files(dir, dist_type)
rescue Dependabot::DependencyFileNotFound
[]
end

sig { params(dir: String, dist_type: String).returns(T::Array[DependencyFile]) }
def fetch_wrapper_artifact_files(dir, dist_type)
case dist_type
when "bin", "script"
jar_path = File.join(dir, WRAPPER_JAR_RELATIVE).delete_prefix("./")
jar = fetch_file_if_present(jar_path)
return [] unless jar

jar.content = Base64.encode64(T.must(jar.content)) if jar.content
jar.content_encoding = DependencyFile::ContentEncoding::BASE64
[jar]
when "source"
dl_path = File.join(dir, WRAPPER_DOWNLOADER_RELATIVE).delete_prefix("./")
downloader = fetch_file_if_present(dl_path)
downloader ? [downloader] : []
else
[]
end
end

sig { params(poms: T::Array[DependencyFile]).returns(T::Array[DependencyFile]) }
def all_wrapper_files(poms)
seen_dirs = T.let(Set.new, T::Set[String])
poms.filter_map do |pom_file|
dir = File.dirname(pom_file.name)
next if seen_dirs.include?(dir)

seen_dirs << dir
wrapper_files_for_dir(dir)
end.flatten
end

sig { returns(T.nilable(Dependabot::DependencyFile)) }
def pom
@pom ||= T.let(fetch_file_from_host("pom.xml"), T.nilable(Dependabot::DependencyFile))
Expand Down
28 changes: 28 additions & 0 deletions maven/lib/dependabot/maven/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class FileParser < Dependabot::FileParsers::Base
require "dependabot/file_parsers/base/dependency_set"
require_relative "file_parser/maven_dependency_parser"
require_relative "file_parser/property_value_finder"
require_relative "file_parser/wrapper_mojo"

# The following "dependencies" are candidates for updating:
# - The project's parent
Expand Down Expand Up @@ -93,6 +94,17 @@ def parse_standard_dependencies
pomfiles.each { |pom| dependency_set += pomfile_dependencies(pom) }
extensionfiles.each { |extension| dependency_set += extensionfile_dependencies(extension) }
targetfiles.each { |target| dependency_set += targetfile_dependencies(target) }

if Dependabot::Experiments.enabled?(:maven_wrapper_updater)
wrapper_properties_files.each do |properties_file|
dir = File.dirname(properties_file.name).sub(%r{/\.mvn/wrapper$}, "")
scripts = wrapper_script_files_for(dir)
FileParser::WrapperMojo.resolve_dependencies(properties_file, script_files: scripts).each do |dep|
dependency_set << dep
end
end
end

dependency_set.dependencies
end

Expand Down Expand Up @@ -470,6 +482,22 @@ def targetfiles
)
end

sig { returns(T::Array[Dependabot::DependencyFile]) }
def wrapper_properties_files
@wrapper_properties_files ||= T.let(
dependency_files.select { |f| f.name.end_with?("maven-wrapper.properties") },
T.nilable(T::Array[Dependabot::DependencyFile])
)
end

sig { params(dir: String).returns(T::Array[Dependabot::DependencyFile]) }
def wrapper_script_files_for(dir)
script_names = %w(mvnw mvnw.cmd mvnwDebug mvnwDebug.cmd).map do |s|
dir == "." ? s : "#{dir}/#{s}"
end
dependency_files.select { |f| script_names.include?(f.name) }
end

sig { returns(T::Array[String]) }
def internal_dependency_names
@internal_dependency_names ||= T.let(
Expand Down
Loading
Loading