This is an automated email from the ASF dual-hosted git repository. cstamas pushed a commit to branch fix-the-build in repository https://gitbox.apache.org/repos/asf/maven-build-cache-extension.git
commit e6fedaa385e0486e4a39328edd2423b9c15fc428 Author: Tamas Cservenak <[email protected]> AuthorDate: Sun Jul 19 13:30:36 2026 +0200 Fix: fix the build on master --- .../maven/buildcache/CacheControllerImpl.java | 19 ++++- .../org/apache/maven/buildcache/CacheUtils.java | 68 +++++++++++++++++ .../buildcache/CacheUtilsPermissionsTest.java | 16 ++-- .../maven/buildcache/CacheUtilsTimestampTest.java | 88 +++++++++++----------- .../maven/buildcache/ModuleInfoCachingTest.java | 10 +-- 5 files changed, 140 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java index ba3f050..65f1566 100644 --- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java +++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java @@ -373,7 +373,11 @@ private void restoreDirectoryArtifact(File cachedZip, Artifact artifact, Path re if (!Files.exists(restorationPath)) { Files.createDirectories(restorationPath); } - CacheUtils.unzip(cachedZip.toPath(), restorationPath, cacheConfig.isPreservePermissions()); + CacheUtils.unzip( + cachedZip.toPath(), + restorationPath, + cacheConfig.isPreservePermissions(), + cacheConfig.isPreserveTimestamps()); LOGGER.debug("Restored directory artifact by unzipping: {} -> {}", artifact.getFileName(), restorationPath); } @@ -708,7 +712,12 @@ private void saveDirectoryArtifact( File originalFile) throws IOException { Path tempZip = Files.createTempFile("maven-cache-", "-" + project.getArtifactId() + ".zip"); - boolean hasFiles = CacheUtils.zip(originalFile.toPath(), tempZip, "*", cacheConfig.isPreservePermissions()); + boolean hasFiles = CacheUtils.zip( + originalFile.toPath(), + tempZip, + "*", + cacheConfig.isPreservePermissions(), + cacheConfig.isPreserveTimestamps()); if (hasFiles) { // Temporarily replace artifact file with zip for saving projectArtifact.setFile(tempZip.toFile()); @@ -1059,7 +1068,8 @@ private boolean zipAndAttachArtifact(MavenProject project, Path dir, String clas throws IOException { Path temp = Files.createTempFile("maven-incremental-", project.getArtifactId()); temp.toFile().deleteOnExit(); - boolean hasFile = CacheUtils.zip(dir, temp, glob, cacheConfig.isPreservePermissions(), cacheConfig.isPreserveTimestamps()); + boolean hasFile = CacheUtils.zip( + dir, temp, glob, cacheConfig.isPreservePermissions(), cacheConfig.isPreserveTimestamps()); if (hasFile) { projectHelper.attachArtifact(project, "zip", classifier, temp.toFile()); } @@ -1074,7 +1084,8 @@ private void restoreGeneratedSources(Artifact artifact, Path artifactFilePath, M if (!Files.exists(outputDir)) { Files.createDirectories(outputDir); } - CacheUtils.unzip(artifactFilePath, outputDir, cacheConfig.isPreservePermissions(), cacheConfig.isPreserveTimestamps()); + CacheUtils.unzip( + artifactFilePath, outputDir, cacheConfig.isPreservePermissions(), cacheConfig.isPreserveTimestamps()); } // TODO: move to config diff --git a/src/main/java/org/apache/maven/buildcache/CacheUtils.java b/src/main/java/org/apache/maven/buildcache/CacheUtils.java index e1e574e..fc0832a 100644 --- a/src/main/java/org/apache/maven/buildcache/CacheUtils.java +++ b/src/main/java/org/apache/maven/buildcache/CacheUtils.java @@ -46,17 +46,23 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.mutable.MutableBoolean; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.ArtifactHandler; +import org.apache.maven.buildcache.xml.build.CompletedExecution; +import org.apache.maven.buildcache.xml.build.PropertyValue; import org.apache.maven.buildcache.xml.build.Scm; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.PluginParameterExpressionEvaluator; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.apache.maven.artifact.Artifact.LATEST_VERSION; import static org.apache.maven.artifact.Artifact.SNAPSHOT_VERSION; @@ -65,6 +71,7 @@ * Cache Utils */ public class CacheUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(CacheUtils.class); public static boolean isPom(MavenProject project) { return project.getPackaging().equals("pom"); @@ -405,4 +412,65 @@ private static Set<PosixFilePermission> modeToPermissions(int mode) { } return permissions; } + + static Object interpolateExpression(String expression, MavenSession session, MojoExecution execution) { + try { + PluginParameterExpressionEvaluator evaluator = new PluginParameterExpressionEvaluator(session, execution); + return evaluator.evaluate(expression); + } catch (ExpressionEvaluationException e) { + LOGGER.warn("Cannot interpolate expression '{}': {}", expression, e.getMessage(), e); + return expression; // return the expression as is when interpolation fails + } + } + + static String normalizeValue(Object value, Path baseDirPath) { + if (value instanceof File) { + Path path = ((File) value).toPath(); + return normalizedPath(path, baseDirPath); + } else if (value instanceof Path) { + return normalizedPath(((Path) value), baseDirPath); + } else if (value != null && value.getClass().isArray()) { + return ArrayUtils.toString(value); + } else { + return String.valueOf(value); + } + } + + /* + Best effort to normalize paths from Mojo fields. + - all absolute paths under project root are relativized for portability + - redundant '..' and '.' are removed to have consistent views on all paths + - all relative paths are considered portable and are not be touched + - absolute paths outside of project directory cannot be deterministically + relativized and are not touched + */ + private static String normalizedPath(Path path, Path baseDirPath) { + boolean isProjectSubdir = path.isAbsolute() && path.startsWith(baseDirPath); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "normalizedPath isProjectSubdir {} path '{}' - baseDirPath '{}', path.isAbsolute() {}," + + " path.startsWith(baseDirPath) {}", + isProjectSubdir, + path, + baseDirPath, + path.isAbsolute(), + path.startsWith(baseDirPath)); + } + Path preparedPath = isProjectSubdir ? baseDirPath.relativize(path) : path; + String normalizedPath = preparedPath.normalize().toString(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("normalizedPath '{}' - {} return {}", path, baseDirPath, normalizedPath); + } + return normalizedPath; + } + + static void addProperty( + CompletedExecution execution, String propertyName, Object value, Path baseDirPath, boolean tracked) { + final PropertyValue valueType = new PropertyValue(); + valueType.setName(propertyName); + final String valueText = normalizeValue(value, baseDirPath); + valueType.setValue(valueText); + valueType.setTracked(tracked); + execution.addProperty(valueType); + } } diff --git a/src/test/java/org/apache/maven/buildcache/CacheUtilsPermissionsTest.java b/src/test/java/org/apache/maven/buildcache/CacheUtilsPermissionsTest.java index 0ce8685..0edaabc 100644 --- a/src/test/java/org/apache/maven/buildcache/CacheUtilsPermissionsTest.java +++ b/src/test/java/org/apache/maven/buildcache/CacheUtilsPermissionsTest.java @@ -78,8 +78,8 @@ void testPermissionsAffectFileHashWhenEnabled() throws IOException { // When: Create ZIP files with preservePermissions=true Path zip1 = tempDir.resolve("cache1.zip"); Path zip2 = tempDir.resolve("cache2.zip"); - CacheUtils.zip(sourceDir1, zip1, "*", true); - CacheUtils.zip(sourceDir2, zip2, "*", true); + CacheUtils.zip(sourceDir1, zip1, "*", true, false); + CacheUtils.zip(sourceDir2, zip2, "*", true, false); // Then: ZIP files should have different hashes despite identical content byte[] hash1 = Files.readAllBytes(zip1); @@ -124,16 +124,16 @@ void testPermissionsDoNotAffectHashWhenDisabled() throws IOException { // When: Create ZIP files with preservePermissions=false Path zip1 = tempDir.resolve("cache1.zip"); Path zip2 = tempDir.resolve("cache2.zip"); - CacheUtils.zip(sourceDir1, zip1, "*", false); - CacheUtils.zip(sourceDir2, zip2, "*", false); + CacheUtils.zip(sourceDir1, zip1, "*", false, false); + CacheUtils.zip(sourceDir2, zip2, "*", false, false); // Unzip and verify permissions are NOT preserved Path extractDir1 = tempDir.resolve("extracted1"); Path extractDir2 = tempDir.resolve("extracted2"); Files.createDirectories(extractDir1); Files.createDirectories(extractDir2); - CacheUtils.unzip(zip1, extractDir1, false); - CacheUtils.unzip(zip2, extractDir2, false); + CacheUtils.unzip(zip1, extractDir1, false, false); + CacheUtils.unzip(zip2, extractDir2, false, false); Path extractedFile1 = extractDir1.resolve("script.sh"); Path extractedFile2 = extractDir2.resolve("script.sh"); @@ -163,12 +163,12 @@ void testPermissionsAreRestoredOnUnzip() throws Exception { // When: ZIP file is created from that directory with "preservePermissions=true" Path zippedFile = tempDir.resolve("target.zip"); - CacheUtils.zip(source, zippedFile, "*", true); + CacheUtils.zip(source, zippedFile, "*", true, false); // And: ZIP file is extracted with "preservePermissions=true" Path unzippedDir = tempDir.resolve("target-unzipped"); Files.createDirectories(unzippedDir); - CacheUtils.unzip(zippedFile, unzippedDir, true); + CacheUtils.unzip(zippedFile, unzippedDir, true, false); // Then: extracted file should have executable permissions Path extractedScript = unzippedDir.resolve("script.sh"); diff --git a/src/test/java/org/apache/maven/buildcache/CacheUtilsTimestampTest.java b/src/test/java/org/apache/maven/buildcache/CacheUtilsTimestampTest.java index 238e5c4..b30c962 100644 --- a/src/test/java/org/apache/maven/buildcache/CacheUtilsTimestampTest.java +++ b/src/test/java/org/apache/maven/buildcache/CacheUtilsTimestampTest.java @@ -18,9 +18,6 @@ */ package org.apache.maven.buildcache; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - import java.io.IOException; import java.io.OutputStream; import java.nio.charset.StandardCharsets; @@ -38,7 +35,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -78,11 +77,11 @@ void testFileTimestampPreservation() throws IOException { // When: Zip and unzip using CacheUtils Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", true); + CacheUtils.zip(sourceDir, zipFile, "*", false, true); Path extractDir = tempDir.resolve("extracted"); Files.createDirectories(extractDir); - CacheUtils.unzip(zipFile, extractDir, true); + CacheUtils.unzip(zipFile, extractDir, false, true); // Then: File timestamps should be preserved Path extractedFile1 = extractDir.resolve("com").resolve("example").resolve("Service.class"); @@ -124,11 +123,11 @@ void testDirectoryTimestampPreservation() throws IOException { // When: Zip and unzip using CacheUtils Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", true); + CacheUtils.zip(sourceDir, zipFile, "*", false, true); Path extractDir = tempDir.resolve("extracted"); Files.createDirectories(extractDir); - CacheUtils.unzip(zipFile, extractDir, true); + CacheUtils.unzip(zipFile, extractDir, false, true); // Then: Directory timestamps should be preserved Path extractedDir = extractDir.resolve("com").resolve("example"); @@ -154,7 +153,7 @@ void testDirectoryEntriesStoredInZip() throws IOException { // When: Create zip Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", true); + CacheUtils.zip(sourceDir, zipFile, "*", false, true); // Then: Zip should contain directory entries List<String> entries = new ArrayList<>(); @@ -192,7 +191,7 @@ void testTimestampsInZipEntries() throws IOException { // When: Create zip Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", true); + CacheUtils.zip(sourceDir, zipFile, "*", false, true); // Then: Zip entries should have correct timestamps try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFile))) { @@ -250,7 +249,7 @@ void testMavenWarningScenario() throws IOException { Instant packageTime = compileTime.plus(5, ChronoUnit.SECONDS); Path jarFile = tempDir.resolve("target").resolve("my-module-1.0.jar"); Files.createDirectories(jarFile.getParent()); - CacheUtils.zip(classesDir, jarFile, "*", true); + CacheUtils.zip(classesDir, jarFile, "*", false, true); Files.setLastModifiedTime(jarFile, FileTime.from(packageTime)); long jarTimestamp = Files.getLastModifiedTime(jarFile).toMillis(); @@ -259,7 +258,7 @@ void testMavenWarningScenario() throws IOException { deleteRecursively(classesDir); // When: Simulate cache restoration - restore JAR contents back to target/classes - CacheUtils.unzip(jarFile, classesDir, true); + CacheUtils.unzip(jarFile, classesDir, false, true); // Then: Restored file should NOT be newer than JAR Path restoredClass = classesDir.resolve("com").resolve("example").resolve("Service.class"); @@ -270,11 +269,11 @@ void testMavenWarningScenario() throws IOException { if (restoredTimestamp > jarTimestamp) { long diffSeconds = (restoredTimestamp - jarTimestamp) / 1000; fail(String.format( - "[WARNING] File 'target/classes/com/example/Service.class' is more recent%n" + - " than the packaged artifact 'my-module-1.0.jar'%n" + - " (difference: %d seconds)%n" + - " Please run a full 'mvn clean package' build%n%n" + - "This indicates timestamps are not being preserved correctly during cache restoration.", + "[WARNING] File 'target/classes/com/example/Service.class' is more recent%n" + + " than the packaged artifact 'my-module-1.0.jar'%n" + + " (difference: %d seconds)%n" + + " Please run a full 'mvn clean package' build%n%n" + + "This indicates timestamps are not being preserved correctly during cache restoration.", diffSeconds)); } @@ -299,8 +298,7 @@ void testMultipleFilesTimestampConsistency() throws IOException { packageDir.resolve("Service.class"), packageDir.resolve("Repository.class"), packageDir.resolve("Controller.class"), - packageDir.resolve("Model.class") - ); + packageDir.resolve("Model.class")); for (Path file : files) { writeString(file, "// " + file.getFileName() + " content"); @@ -311,11 +309,11 @@ void testMultipleFilesTimestampConsistency() throws IOException { // When: Zip and unzip Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", true); + CacheUtils.zip(sourceDir, zipFile, "*", false, true); Path extractDir = tempDir.resolve("extracted"); Files.createDirectories(extractDir); - CacheUtils.unzip(zipFile, extractDir, true); + CacheUtils.unzip(zipFile, extractDir, false, true); // Then: All files should have consistent timestamps for (Path originalFile : files) { @@ -348,11 +346,11 @@ void testPreserveTimestampsFalse() throws IOException { // When: Zip and unzip with preserveTimestamps=false Path zipFile = tempDir.resolve("cache.zip"); - CacheUtils.zip(sourceDir, zipFile, "*", false); + CacheUtils.zip(sourceDir, zipFile, "*", false, false); Path extractDir = tempDir.resolve("extracted"); Files.createDirectories(extractDir); - CacheUtils.unzip(zipFile, extractDir, false); + CacheUtils.unzip(zipFile, extractDir, false, false); // Then: Extracted file should NOT have the original timestamp // (it should have a timestamp close to now, not 1 hour ago) @@ -365,17 +363,22 @@ void testPreserveTimestampsFalse() throws IOException { long diffFromCurrent = Math.abs(extractedTimestamp - currentTime); // The extracted file should be much closer to current time than to the old timestamp - assertTrue(diffFromCurrent < diffFromOriginal, - String.format("When preserveTimestamps=false, extracted file timestamp should be close to current time.%n" + - "Original timestamp (1 hour ago): %s (%d)%n" + - "Extracted timestamp: %s (%d)%n" + - "Current time: %s (%d)%n" + - "Diff from original: %d seconds%n" + - "Diff from current: %d seconds%n" + - "Expected: diff from current < diff from original", - Instant.ofEpochMilli(originalTimestamp), originalTimestamp, - Instant.ofEpochMilli(extractedTimestamp), extractedTimestamp, - Instant.ofEpochMilli(currentTime), currentTime, + assertTrue( + diffFromCurrent < diffFromOriginal, + String.format( + "When preserveTimestamps=false, extracted file timestamp should be close to current time.%n" + + "Original timestamp (1 hour ago): %s (%d)%n" + + "Extracted timestamp: %s (%d)%n" + + "Current time: %s (%d)%n" + + "Diff from original: %d seconds%n" + + "Diff from current: %d seconds%n" + + "Expected: diff from current < diff from original", + Instant.ofEpochMilli(originalTimestamp), + originalTimestamp, + Instant.ofEpochMilli(extractedTimestamp), + extractedTimestamp, + Instant.ofEpochMilli(currentTime), + currentTime, diffFromOriginal / 1000, diffFromCurrent / 1000)); } @@ -390,15 +393,14 @@ private void assertTimestampPreserved(String fileName, long expectedMs, long act if (diffMs > TIMESTAMP_TOLERANCE_MS) { String errorMessage = String.format( - "%s%n" + - "File: %s%n" + - "Expected timestamp: %s (%d)%n" + - "Actual timestamp: %s (%d)%n" + - "Difference: %d seconds (%.2f hours)%n" + - "%n" + - "Timestamps must be preserved within %d ms tolerance.%n" + - "This failure indicates CacheUtils.zip() or CacheUtils.unzip() is not%n" + - "correctly preserving file/directory timestamps.", + "%s%n" + "File: %s%n" + + "Expected timestamp: %s (%d)%n" + + "Actual timestamp: %s (%d)%n" + + "Difference: %d seconds (%.2f hours)%n" + + "%n" + + "Timestamps must be preserved within %d ms tolerance.%n" + + "This failure indicates CacheUtils.zip() or CacheUtils.unzip() is not%n" + + "correctly preserving file/directory timestamps.", message, fileName, Instant.ofEpochMilli(expectedMs), diff --git a/src/test/java/org/apache/maven/buildcache/ModuleInfoCachingTest.java b/src/test/java/org/apache/maven/buildcache/ModuleInfoCachingTest.java index 063b650..c6ddcf9 100644 --- a/src/test/java/org/apache/maven/buildcache/ModuleInfoCachingTest.java +++ b/src/test/java/org/apache/maven/buildcache/ModuleInfoCachingTest.java @@ -51,21 +51,19 @@ public void testModuleInfoClassIsIncludedInZip(@TempDir Path testDir) throws IOE // Zip using the default glob pattern "*" (same as attachedOutputs uses) Path zipFile = testDir.resolve("test.zip"); - boolean hasFiles = CacheUtils.zip(classesDir, zipFile, "*", true); + boolean hasFiles = CacheUtils.zip(classesDir, zipFile, "*", true, true); assertTrue(hasFiles, "Zip should contain files"); assertTrue(Files.exists(zipFile), "Zip file should be created"); // Extract and verify module-info.class is present Path extractDir = testDir.resolve("extracted"); - CacheUtils.unzip(zipFile, extractDir, true); + CacheUtils.unzip(zipFile, extractDir, true, true); Path extractedModuleInfo = extractDir.resolve("module-info.class"); - assertTrue(Files.exists(extractedModuleInfo), - "module-info.class should be present after extraction"); + assertTrue(Files.exists(extractedModuleInfo), "module-info.class should be present after extraction"); Path extractedRegularClass = extractDir.resolve("com/example/MyClass.class"); - assertTrue(Files.exists(extractedRegularClass), - "Regular .class file should be present after extraction"); + assertTrue(Files.exists(extractedRegularClass), "Regular .class file should be present after extraction"); } }
