Skip to content

✨ feat: Add Maven build extensions support to lockfile#1529

Open
tkubas-rh wants to merge 11 commits intochains-project:mainfrom
tkubas-rh:build-extensions
Open

✨ feat: Add Maven build extensions support to lockfile#1529
tkubas-rh wants to merge 11 commits intochains-project:mainfrom
tkubas-rh:build-extensions

Conversation

@tkubas-rh
Copy link
Copy Markdown

Summary

Adds support for tracking Maven build extensions in lockfiles. Build extensions modify Maven's core behavior and are loaded before the build starts, making them critical for supply chain security and build reproducibility.

Implementation

The algorithm resolves build extensions through the following process:

  • Use project.getBuildExtensions() (non-deprecated API) to get extension model objects
  • Resolve extensions using Eclipse Aether's RepositorySystem with batch resolution via CollectRequest/DependencyRequest
  • Reuse resolveComponentDependencies() (renamed from resolvePluginDependencies) for transitive dependency resolution
  • Filter to compile+runtime scopes only (exclude TEST scope dependencies)
  • Add extensions to the mavenExtensions key in the lockfile

Changes

  • Extracts common plugin/extension logic into AbstractMavenComponent base class
  • Adds MavenExtension data class with dependencies support
  • Updates LockFile to include mavenExtensions field
  • Updates LockFileDifference to diff extensions
  • Implements extension resolution in LockFileFacade
  • Adds extension validation to ValidateChecksumMojo
  • Includes 3 integration tests covering simple, multiple, and validation failure scenarios

Bug Fixes

  • Fixes ValidateChecksumMojo to respect includeEnvironment config flag

Closes #1513

@tkubas-rh tkubas-rh changed the title WIP: ✨ feat: Add Maven build extensions support to lockfile ✨ feat: Add Maven build extensions support to lockfile Mar 16, 2026
- Create AbstractMavenComponent base class for plugins and extensions
- Add MavenExtension data class with dependencies support
- Update LockFile to include mavenExtensions field
- Update LockFileDifference to diff extensions
- Include extension diffing in lockfile validation
- Show missing extensions in validation error output
- Add RepositorySystem injection to AbstractLockfileMojo
- Implement getAllExtensions() using project.getBuildExtensions()
- Use batch resolution with Aether's CollectRequest/DependencyRequest
- Rename resolvePluginDependencies to resolveComponentDependencies
- Resolve extension dependencies using compile+runtime scopes
- Pass repositorySystem to generateLockFileFromProject()

Assisted-by: Claude Code
- buildExtensionsSimple: single extension with dependencies
- buildExtensionsMultiple: multiple extensions
- buildExtensionsValidationFail: tampered checksum detection

🐛 fix: Respect includeEnvironment config in ValidateChecksumMojo

- Only generate environment metadata when config.isIncludeEnvironment() is true
- Read lockfile config before generating environment metadata
- Fixes false validation failures when lockfile has includeEnvironment=false

🐛 fix: Add checksum tiebreakers in AbstractMavenComponent.compareTo()

- Add checksumAlgorithm and checksum comparison after version
- Ensures consistent ordering and equals()/compareTo() contract
- Fixes MavenPluginTest comparison test failures

Assisted-by: Claude Code
- Verify extension dependencies are not empty
- Verify all dependencies have valid scopes
- Ensure TEST scope dependencies are excluded
- Match thoroughness of plugin tests

Assisted-by: Claude Code
- Update buildExtensionsValidationFail lockfile POM checksum
- Allows test to reach extension validation instead of failing on POM
Signed-off-by: Timotej Kubas <tkubas@redhat.com>
  Replace CRTP pattern with simple runtime typechecks. This simplifies the code while maintaining type isolation between MavenPlugin and MavenExtension.

  Changes:
  - Remove <T extends AbstractMavenComponent<T>> type parameter
  - Fix equals() to check exact type (this.getClass() != obj.getClass())
  - Add type-first ordering in compareTo()
  - Add cross-type comparison tests to MavenPluginTest
  - Add MavenExtensionTest with comprehensive test coverage

Signed-off-by: Timotej Kubas <tkubas@redhat.com>
  Remove redundant buildExtensionsSimple test as buildExtensionsMultiple provides sufficient coverage for build extensions functionality.

  Changes:
  - Remove buildExtensionsSimple test directory
  - Remove buildExtensionsSimple test method from IntegrationTestsIT

Signed-off-by: Timotej Kubas <tkubas@redhat.com>
Signed-off-by: Timotej Kubas <tkubas@redhat.com>
@brunoapimentel
Copy link
Copy Markdown
Contributor

@algomaster99 Can we also get a review on this one?

Signed-off-by: Timotej Kubas <tkubas@redhat.com>
@algomaster99
Copy link
Copy Markdown
Member

@brunoapimentel sure! I can go through it over the weekend. Personally, I have not directly used build extensions so I will also need to spend some time understanding its usecase :)

Copy link
Copy Markdown
Member

@algomaster99 algomaster99 left a comment

Choose a reason for hiding this comment

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

Overall, looks good. I left some minor comments below.

Comment on lines +100 to +101
&& Objects.equals(resolved, other.resolved)
&& Objects.equals(repositoryId, other.repositoryId)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

resolved and repositoryId were not used to compare MavenPlugin before, but now they would be. I believe that is correct behaviour. Just flagging it here in case.

transitiveDeps));
}
} catch (DependencyResolutionException e) {
PluginLogManager.getLog().warn("Failed to resolve extension dependencies", e);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess a follow up PR could be to have an option to enforce failure here and I also think you would want this option in Konflux otherwise hermetic builds won't succeed.

Comment on lines +16 to +27
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>3.5.3</version>
</extension>
</extensions>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a not a question related to the PR, but more to enhance my knowledge. What behaviour difference to the build one will observe where these two extensions are configured? I also don't understand how extensions are invoked. Plugins can be invoked using certain goals from example so I am trying to find an analogy.

Comment on lines +756 to +761
assertThat(lockFile.getMavenExtensions())
.anyMatch(ext -> ext.getGroupId().getValue().equals("kr.motd.maven")
&& ext.getArtifactId().getValue().equals("os-maven-plugin"));
assertThat(lockFile.getMavenExtensions())
.anyMatch(ext -> ext.getGroupId().getValue().equals("org.apache.maven.wagon")
&& ext.getArtifactId().getValue().equals("wagon-ssh"));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Typecasting them to list and then comparing would also assert the order implicitly although we know TreeSet is ordered.

public void buildExtensionsValidationFail(MavenExecutionResult result) throws Exception {
// contract: if an extension checksum in the lockfile doesn't match the actual extension,
// validation should fail
System.out.println("Running 'buildExtensionsValidationFail' integration test.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
System.out.println("Running 'buildExtensionsValidationFail' integration test.");

Probably a debugging statement?

@SuppressWarnings("null")
public void buildExtensionsMultiple(MavenExecutionResult result) throws Exception {
// contract: if a project uses multiple build extensions, all should be recorded in the lockfile
System.out.println("Running 'buildExtensionsMultiple' integration test.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
System.out.println("Running 'buildExtensionsMultiple' integration test.");

@algomaster99
Copy link
Copy Markdown
Member

@tkubas-rh can you please run spotless:apply to fix Maven test workflow? For the rest, @LogFlames do you know what is the exact issue and how to address them?

@LogFlames
Copy link
Copy Markdown
Member

Lockfile PR / check-lockfile fails due to the maven version being updated in GitHub CI runners. I have created #1535 to update, after which a rebase should fix it.

Doc Generation and Maven test / maven-quality are both caused by spotless violations. Quality fails since there are spotless violations, and doc fails since it tries to fix them and push to update (but it cannot push the update to the fork).

I'll have to investigate why Smoke Tests are failing and come back!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Record Build Extensions

4 participants