Copilot commented on code in PR #419:
URL: 
https://github.com/apache/maven-build-cache-extension/pull/419#discussion_r2640520230


##########
src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java:
##########
@@ -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(",")) {

Review Comment:
   The version range detection logic (`versionSpec.startsWith("[") || 
versionSpec.startsWith("(") || versionSpec.contains(",")`) is duplicated at 
line 910 and in the `isDynamicVersion` method at line 885. This duplicated 
logic makes the code harder to maintain. Consider using the `isDynamicVersion` 
helper method here instead of duplicating the range detection logic.
   ```suggestion
               if (isDynamicVersion(versionSpec)) {
   ```



##########
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());
+    }

Review Comment:
   The test should verify the error message when a system-scoped dependency's 
systemPath file does not exist. Currently, the test only covers the successful 
case where the file exists. Add a test case to verify that 
`DependencyNotResolvedException` is thrown with an appropriate error message 
when the system path file is missing.



##########
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);
+    }

Review Comment:
   The test only covers the "LATEST" dynamic version case. Consider adding test 
cases for other dynamic version scenarios such as "RELEASE" and version ranges 
(e.g., "[1.0,2.0)") to ensure the new dynamic version detection logic works 
correctly across all supported formats mentioned in the implementation.



##########
src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java:
##########
@@ -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(",");

Review Comment:
   The dynamic version detection using `versionSpec.contains(",")` could 
produce false positives. Maven version ranges use commas to separate version 
boundaries (e.g., `[1.0,2.0)`), but commas might also appear in other contexts 
such as build metadata in semantic versions (e.g., `1.0.0+build,metadata` in 
certain edge cases). While this is unlikely in practice for Maven versions, 
consider using a more precise detection method such as checking if the string 
matches a version range pattern or using `VersionRange.createFromVersionSpec()` 
and catching the exception to determine if it's a range.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to