This is an automated email from the ASF dual-hosted git repository.

olamy pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/maven-build-cache-extension.git


The following commit(s) were added to refs/heads/master by this push:
     new db0ce24  Fixes for regression after pr #379 (#419)
db0ce24 is described below

commit db0ce2428e8dbf1a53d1be70606538667aefdf34
Author: maximiln <[email protected]>
AuthorDate: Thu Dec 25 10:02:39 2025 +0100

    Fixes for regression after pr #379 (#419)
    
    Issues: #411 #417
    
    - Improved dependency resolution by adding checks for dynamic versions and 
system-scoped dependencies.
    
    Co-authored-by: maximiln <[email protected]>
---
 .../org/apache/maven/buildcache/CacheUtils.java    |   2 +-
 .../buildcache/checksum/MavenProjectInput.java     |  83 ++++++++-
 ...ctInputReactorAndSystemScopeRegressionTest.java | 207 +++++++++++++++++++++
 3 files changed, 287 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/maven/buildcache/CacheUtils.java 
b/src/main/java/org/apache/maven/buildcache/CacheUtils.java
index 67d639a..df3bb49 100644
--- a/src/main/java/org/apache/maven/buildcache/CacheUtils.java
+++ b/src/main/java/org/apache/maven/buildcache/CacheUtils.java
@@ -70,7 +70,7 @@ public static boolean isPom(Dependency dependency) {
     }
 
     public static boolean isSnapshot(String version) {
-        return version.endsWith(SNAPSHOT_VERSION) || 
version.endsWith(LATEST_VERSION);
+        return version != null && (version.endsWith(SNAPSHOT_VERSION) || 
version.endsWith(LATEST_VERSION));
     }
 
     public static String normalizedName(Artifact artifact) {
diff --git 
a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java 
b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
index d305a12..5cc53d8 100644
--- a/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
+++ b/src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java
@@ -39,6 +39,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
@@ -55,6 +56,7 @@
 import org.apache.maven.artifact.handler.ArtifactHandler;
 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
 import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
 import 
org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
 import org.apache.maven.artifact.versioning.VersionRange;
 import org.apache.maven.buildcache.CacheUtils;
@@ -776,11 +778,23 @@ private SortedMap<String, String> 
getMutableDependenciesHashes(String keyPrefix,
                 continue;
             }
 
+            final String versionSpec = dependency.getVersion();
+
             // saved to index by the end of dependency build
-            MavenProject dependencyProject = multiModuleSupport
-                    .tryToResolveProject(dependency.getGroupId(), 
dependency.getArtifactId(), dependency.getVersion())
-                    .orElse(null);
-            boolean isSnapshot = isSnapshot(dependency.getVersion());
+            MavenProject dependencyProject = versionSpec == null
+                    ? null
+                    : multiModuleSupport
+                            .tryToResolveProject(dependency.getGroupId(), 
dependency.getArtifactId(), versionSpec)
+                            .orElse(null);
+
+            // for dynamic versions (LATEST/RELEASE/ranges), reactor artifacts 
can be part of the build
+            // but cannot be resolved yet from the workspace (not built), so 
Aether may try remote download.
+            // If a matching reactor module exists, treat it as multi-module 
dependency and use project checksum.
+            if (dependencyProject == null && isDynamicVersion(versionSpec)) {
+                dependencyProject = 
tryResolveReactorProjectByGA(dependency).orElse(null);
+            }
+
+            boolean isSnapshot = isSnapshot(versionSpec);
             if (dependencyProject == null && !isSnapshot) {
                 // external immutable dependency, should skip
                 continue;
@@ -810,6 +824,19 @@ private SortedMap<String, String> 
getMutableDependenciesHashes(String keyPrefix,
     private DigestItem resolveArtifact(final Dependency dependency)
             throws IOException, ArtifactResolutionException, 
InvalidVersionSpecificationException {
 
+        // system-scoped dependencies are local files (systemPath) and must 
NOT be resolved via Aether.
+        if (Artifact.SCOPE_SYSTEM.equals(dependency.getScope()) && 
dependency.getSystemPath() != null) {
+            final Path systemPath = 
Paths.get(dependency.getSystemPath()).normalize();
+            if (!Files.exists(systemPath)) {
+                throw new DependencyNotResolvedException(
+                        "System dependency file does not exist: " + systemPath 
+ " for dependency: " + dependency);
+            }
+            final HashAlgorithm algorithm = 
config.getHashFactory().createAlgorithm();
+            final String hash = algorithm.hash(systemPath);
+            final Artifact artifact = createDependencyArtifact(dependency);
+            return DtoUtils.createDigestedFile(artifact, hash);
+        }
+
         org.eclipse.aether.artifact.Artifact dependencyArtifact = new 
org.eclipse.aether.artifact.DefaultArtifact(
                 dependency.getGroupId(),
                 dependency.getArtifactId(),
@@ -847,6 +874,54 @@ private DigestItem resolveArtifact(final Dependency 
dependency)
         return DtoUtils.createDigestedFile(artifact, hash);
     }
 
+    private static boolean isDynamicVersion(String versionSpec) {
+        if (versionSpec == null) {
+            return true;
+        }
+        if ("LATEST".equals(versionSpec) || "RELEASE".equals(versionSpec)) {
+            return true;
+        }
+        // Maven version ranges: [1.0,2.0), (1.0,), etc.
+        return versionSpec.startsWith("[") || versionSpec.startsWith("(") || 
versionSpec.contains(",");
+    }
+
+    private Optional<MavenProject> tryResolveReactorProjectByGA(Dependency 
dependency) {
+        final List<MavenProject> projects = session.getAllProjects();
+        if (projects == null || projects.isEmpty()) {
+            return Optional.empty();
+        }
+
+        final String groupId = dependency.getGroupId();
+        final String artifactId = dependency.getArtifactId();
+        final String versionSpec = dependency.getVersion();
+
+        for (MavenProject candidate : projects) {
+            if (!Objects.equals(groupId, candidate.getGroupId())
+                    || !Objects.equals(artifactId, candidate.getArtifactId())) 
{
+                continue;
+            }
+
+            // For null/LATEST/RELEASE, accept the reactor module directly.
+            if (versionSpec == null || "LATEST".equals(versionSpec) || 
"RELEASE".equals(versionSpec)) {
+                return Optional.of(candidate);
+            }
+
+            // For ranges, only accept if reactor version fits the range.
+            if (versionSpec.startsWith("[") || versionSpec.startsWith("(") || 
versionSpec.contains(",")) {
+                try {
+                    VersionRange range = 
VersionRange.createFromVersionSpec(versionSpec);
+                    if (range.containsVersion(new 
DefaultArtifactVersion(candidate.getVersion()))) {
+                        return Optional.of(candidate);
+                    }
+                } catch (InvalidVersionSpecificationException e) {
+                    // If the spec is not parseable as range, don't guess.
+                    return Optional.empty();
+                }
+            }
+        }
+        return Optional.empty();
+    }
+
     /**
      * PathIgnoringCaseComparator
      */
diff --git 
a/src/test/java/org/apache/maven/buildcache/checksum/MavenProjectInputReactorAndSystemScopeRegressionTest.java
 
b/src/test/java/org/apache/maven/buildcache/checksum/MavenProjectInputReactorAndSystemScopeRegressionTest.java
new file mode 100644
index 0000000..d9df110
--- /dev/null
+++ 
b/src/test/java/org/apache/maven/buildcache/checksum/MavenProjectInputReactorAndSystemScopeRegressionTest.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.buildcache.checksum;
+
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+import java.util.SortedMap;
+
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.buildcache.MultiModuleSupport;
+import org.apache.maven.buildcache.NormalizedModelProvider;
+import org.apache.maven.buildcache.ProjectInputCalculator;
+import org.apache.maven.buildcache.RemoteCacheRepository;
+import org.apache.maven.buildcache.hash.HashFactory;
+import org.apache.maven.buildcache.xml.CacheConfig;
+import org.apache.maven.buildcache.xml.build.DigestItem;
+import org.apache.maven.buildcache.xml.build.ProjectsInputInfo;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Regression tests for:
+ * - #411: avoid resolving/downloading reactor dependencies when their 
versions are dynamic (e.g. LATEST)
+ * - #417: system-scoped dependencies must be hashed from systemPath without 
Aether resolution
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class MavenProjectInputReactorAndSystemScopeRegressionTest {
+
+    @Mock
+    private MavenProject project;
+
+    @Mock
+    private MavenSession session;
+
+    @Mock
+    private RepositorySystem repoSystem;
+
+    @Mock
+    private RepositorySystemSession repositorySystemSession;
+
+    @Mock
+    private NormalizedModelProvider normalizedModelProvider;
+
+    @Mock
+    private MultiModuleSupport multiModuleSupport;
+
+    @Mock
+    private ProjectInputCalculator projectInputCalculator;
+
+    @Mock
+    private CacheConfig config;
+
+    @Mock
+    private RemoteCacheRepository remoteCache;
+
+    @Mock
+    private org.apache.maven.artifact.handler.manager.ArtifactHandlerManager 
artifactHandlerManager;
+
+    @TempDir
+    Path tempDir;
+
+    private MavenProjectInput mavenProjectInput;
+
+    @BeforeEach
+    void setUp() {
+        
when(session.getRepositorySession()).thenReturn(repositorySystemSession);
+        when(project.getBasedir()).thenReturn(tempDir.toFile());
+        when(project.getProperties()).thenReturn(new Properties());
+        when(config.getDefaultGlob()).thenReturn("*");
+        when(config.isProcessPlugins()).thenReturn("false");
+        when(config.getGlobalExcludePaths()).thenReturn(new ArrayList<>());
+        
when(config.calculateProjectVersionChecksum()).thenReturn(Boolean.FALSE);
+        when(config.getHashFactory()).thenReturn(HashFactory.SHA1);
+
+        org.apache.maven.model.Build build = new 
org.apache.maven.model.Build();
+        build.setDirectory(tempDir.toString());
+        build.setOutputDirectory(tempDir.resolve("target/classes").toString());
+        
build.setTestOutputDirectory(tempDir.resolve("target/test-classes").toString());
+        build.setSourceDirectory(tempDir.resolve("src/main/java").toString());
+        
build.setTestSourceDirectory(tempDir.resolve("src/test/java").toString());
+        build.setResources(new ArrayList<>());
+        build.setTestResources(new ArrayList<>());
+        when(project.getBuild()).thenReturn(build);
+
+        when(project.getDependencies()).thenReturn(new ArrayList<>());
+        when(project.getBuildPlugins()).thenReturn(new ArrayList<>());
+        when(project.getModules()).thenReturn(new ArrayList<>());
+        when(project.getPackaging()).thenReturn("jar");
+
+        ArtifactHandler handler = mock(ArtifactHandler.class);
+        when(handler.getClassifier()).thenReturn(null);
+        when(handler.getExtension()).thenReturn("jar");
+        
when(artifactHandlerManager.getArtifactHandler(org.mockito.ArgumentMatchers.anyString()))
+                .thenReturn(handler);
+
+        mavenProjectInput = new MavenProjectInput(
+                project,
+                normalizedModelProvider,
+                multiModuleSupport,
+                projectInputCalculator,
+                session,
+                config,
+                repoSystem,
+                remoteCache,
+                artifactHandlerManager);
+    }
+
+    @Test
+    void 
testSystemScopeDependencyHashedFromSystemPathWithoutAetherResolution() throws 
Exception {
+        Path systemJar = tempDir.resolve("local-lib.jar");
+        Files.write(systemJar, "abc".getBytes(StandardCharsets.UTF_8));
+
+        Dependency dependency = new Dependency();
+        dependency.setGroupId("com.example");
+        dependency.setArtifactId("local-lib");
+        dependency.setVersion("1.0");
+        dependency.setType("jar");
+        dependency.setScope("system");
+        dependency.setSystemPath(systemJar.toString());
+        dependency.setOptional(true);
+
+        Method resolveArtifact = 
MavenProjectInput.class.getDeclaredMethod("resolveArtifact", Dependency.class);
+        resolveArtifact.setAccessible(true);
+        DigestItem digest = (DigestItem) 
resolveArtifact.invoke(mavenProjectInput, dependency);
+
+        String expectedHash = 
HashFactory.SHA1.createAlgorithm().hash(systemJar);
+        assertEquals(expectedHash, digest.getHash());
+
+        verify(repoSystem, never())
+                .resolveArtifact(org.mockito.ArgumentMatchers.any(), 
org.mockito.ArgumentMatchers.any());
+    }
+
+    @Test
+    void 
testDynamicVersionReactorDependencyUsesProjectChecksumAndAvoidsAetherResolution()
 throws Exception {
+        Dependency dependency = new Dependency();
+        dependency.setGroupId("com.example");
+        dependency.setArtifactId("reactor-artifact");
+        dependency.setVersion("LATEST");
+        dependency.setType("jar");
+
+        when(multiModuleSupport.tryToResolveProject("com.example", 
"reactor-artifact", "LATEST"))
+                .thenReturn(java.util.Optional.empty());
+
+        MavenProject reactorProject = mock(MavenProject.class);
+        when(reactorProject.getGroupId()).thenReturn("com.example");
+        when(reactorProject.getArtifactId()).thenReturn("reactor-artifact");
+        when(reactorProject.getVersion()).thenReturn("1.0-SNAPSHOT");
+        
when(session.getAllProjects()).thenReturn(Collections.singletonList(reactorProject));
+
+        ProjectsInputInfo projectInfo = mock(ProjectsInputInfo.class);
+        when(projectInfo.getChecksum()).thenReturn("reactorChecksum");
+        
when(projectInputCalculator.calculateInput(reactorProject)).thenReturn(projectInfo);
+
+        Method getMutableDependenciesHashes =
+                
MavenProjectInput.class.getDeclaredMethod("getMutableDependenciesHashes", 
String.class, List.class);
+        getMutableDependenciesHashes.setAccessible(true);
+
+        SortedMap<String, String> hashes = (SortedMap<String, String>)
+                getMutableDependenciesHashes.invoke(mavenProjectInput, "", 
Collections.singletonList(dependency));
+
+        assertEquals("reactorChecksum", 
hashes.get("com.example:reactor-artifact:jar"));
+
+        verify(repoSystem, never())
+                .resolveArtifact(org.mockito.ArgumentMatchers.any(), 
org.mockito.ArgumentMatchers.any());
+        verify(projectInputCalculator).calculateInput(reactorProject);
+    }
+}

Reply via email to