diff --git a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java index e71dcb7a..2934cab9 100644 --- a/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java +++ b/src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -135,13 +136,24 @@ public class CacheControllerImpl implements CacheController { private volatile Scm scm; /** - * A map dedicated to store the base path of resources stored to the cache which are not original artifacts - * (ex : generated source basedir). - * Used to link the resource to its path on disk + * Per-project cache state to ensure thread safety in multi-threaded builds. + * Each project gets isolated state for resource tracking, counters, and restored output tracking. */ - private final Map attachedResourcesPathsById = new HashMap<>(); + private static class ProjectCacheState { + final Map attachedResourcesPathsById = new HashMap<>(); + int attachedResourceCounter = 0; + final Set restoredOutputClassifiers = new HashSet<>(); + } + + private final ConcurrentMap projectStates = new ConcurrentHashMap<>(); - private int attachedResourceCounter = 0; + /** + * Get or create cache state for the given project (thread-safe). + */ + private ProjectCacheState getProjectState(MavenProject project) { + String key = getVersionlessProjectKey(project); + return projectStates.computeIfAbsent(key, k -> new ProjectCacheState()); + } // CHECKSTYLE_OFF: ParameterNumber @Inject public CacheControllerImpl( @@ -356,6 +368,7 @@ public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult final Build build = cacheResult.getBuildInfo(); final CacheContext context = cacheResult.getContext(); final MavenProject project = context.getProject(); + final ProjectCacheState state = getProjectState(project); ArtifactRestorationReport restorationReport = new ArtifactRestorationReport(); try { @@ -397,6 +410,8 @@ public ArtifactRestorationReport restoreProjectArtifacts(CacheResult cacheResult final Path attachedArtifactFile = localCache.getArtifactFile(context, cacheResult.getSource(), attachedArtifactInfo); restoreGeneratedSources(attachedArtifactInfo, attachedArtifactFile, project); + // Track this classifier as restored so save() includes it even with old timestamp + state.restoredOutputClassifiers.add(attachedArtifactInfo.getClassifier()); } } else { Future downloadTask = createDownloadTask( @@ -497,25 +512,47 @@ public void save( final MavenProject project = context.getProject(); final MavenSession session = context.getSession(); + final ProjectCacheState state = getProjectState(project); try { + state.attachedResourcesPathsById.clear(); + state.attachedResourceCounter = 0; + + // Get build start time to filter out stale artifacts from previous builds + final long buildStartTime = session.getRequest().getStartTime().getTime(); + final HashFactory hashFactory = cacheConfig.getHashFactory(); + final HashAlgorithm algorithm = hashFactory.createAlgorithm(); final org.apache.maven.artifact.Artifact projectArtifact = project.getArtifact(); - final List attachedArtifacts; - final List attachedArtifactDtos; - final Artifact projectArtifactDto; - if (project.hasLifecyclePhase("package")) { - final HashAlgorithm algorithm = hashFactory.createAlgorithm(); - attachGeneratedSources(project); - attachOutputs(project); - attachedArtifacts = project.getAttachedArtifacts() != null - ? project.getAttachedArtifacts() - : Collections.emptyList(); - attachedArtifactDtos = artifactDtos(attachedArtifacts, algorithm, project); - projectArtifactDto = artifactDto(project.getArtifact(), algorithm, project); - } else { - attachedArtifacts = Collections.emptyList(); - attachedArtifactDtos = new ArrayList<>(); - projectArtifactDto = null; + final boolean hasPackagePhase = project.hasLifecyclePhase("package"); + + // Cache compile outputs (classes, test-classes, generated sources) if enabled + // This allows compile-only builds to create restorable cache entries + // Can be disabled with -Dmaven.build.cache.cacheCompile=false to reduce IO overhead + final boolean cacheCompile = cacheConfig.isCacheCompile(); + if (cacheCompile) { + attachGeneratedSources(project, state, buildStartTime); + attachOutputs(project, state, buildStartTime); + } + + final List attachedArtifacts = project.getAttachedArtifacts() != null + ? project.getAttachedArtifacts() + : Collections.emptyList(); + final List attachedArtifactDtos = artifactDtos(attachedArtifacts, algorithm, project, state); + final Artifact projectArtifactDto = hasPackagePhase ? artifactDto(project.getArtifact(), algorithm, project, state) + : null; + + // CRITICAL: Don't create incomplete cache entries! + // Only save cache entry if we have SOMETHING useful to restore. + // Exclude consumer POMs (Maven metadata) from the "useful artifacts" check. + // This prevents the bug where: + // 1. mvn compile (cacheCompile=false) creates cache entry with only metadata + // 2. mvn compile (cacheCompile=true) tries to restore incomplete cache and fails + boolean hasUsefulArtifacts = projectArtifactDto != null + || attachedArtifactDtos.stream() + .anyMatch(a -> !"consumer".equals(a.getClassifier()) || !"pom".equals(a.getType())); + if (!hasUsefulArtifacts) { + LOGGER.info("Skipping cache save: no artifacts to save (only metadata present)"); + return; } List completedExecution = buildExecutionInfo(mojoExecutions, executionEvents); @@ -534,22 +571,19 @@ public void save( localCache.beforeSave(context); // if package phase presence means new artifacts were packaged - if (project.hasLifecyclePhase("package")) { - if (projectArtifact.getFile() != null) { - localCache.saveArtifactFile(cacheResult, projectArtifact); - } - for (org.apache.maven.artifact.Artifact attachedArtifact : attachedArtifacts) { - if (attachedArtifact.getFile() != null) { - boolean storeArtifact = - isOutputArtifact(attachedArtifact.getFile().getName()); - if (storeArtifact) { - localCache.saveArtifactFile(cacheResult, attachedArtifact); - } else { - LOGGER.debug( - "Skipping attached project artifact '{}' = " - + " it is marked for exclusion from caching", - attachedArtifact.getFile().getName()); - } + if (hasPackagePhase && projectArtifact.getFile() != null) { + localCache.saveArtifactFile(cacheResult, projectArtifact); + } + for (org.apache.maven.artifact.Artifact attachedArtifact : attachedArtifacts) { + if (attachedArtifact.getFile() != null) { + boolean storeArtifact = isOutputArtifact(attachedArtifact.getFile().getName()); + if (storeArtifact) { + localCache.saveArtifactFile(cacheResult, attachedArtifact); + } else { + LOGGER.debug( + "Skipping attached project artifact '{}' = " + + " it is marked for exclusion from caching", + attachedArtifact.getFile().getName()); } } } @@ -567,6 +601,10 @@ public void save( } catch (Exception ex) { LOGGER.error("Failed to clean cache due to unexpected error:", ex); } + } finally { + // Cleanup project state to free memory (thread-safe removal) + String key = getVersionlessProjectKey(project); + projectStates.remove(key); } } @@ -624,20 +662,22 @@ public void produceDiffReport(CacheResult cacheResult, Build build) { } private List artifactDtos( - List attachedArtifacts, HashAlgorithm digest, MavenProject project) + List attachedArtifacts, HashAlgorithm digest, MavenProject project, + ProjectCacheState state) throws IOException { List result = new ArrayList<>(); for (org.apache.maven.artifact.Artifact attachedArtifact : attachedArtifacts) { if (attachedArtifact.getFile() != null && isOutputArtifact(attachedArtifact.getFile().getName())) { - result.add(artifactDto(attachedArtifact, digest, project)); + result.add(artifactDto(attachedArtifact, digest, project, state)); } } return result; } private Artifact artifactDto( - org.apache.maven.artifact.Artifact projectArtifact, HashAlgorithm algorithm, MavenProject project) + org.apache.maven.artifact.Artifact projectArtifact, HashAlgorithm algorithm, MavenProject project, + ProjectCacheState state) throws IOException { final Artifact dto = DtoUtils.createDto(projectArtifact); if (projectArtifact.getFile() != null && projectArtifact.getFile().isFile()) { @@ -646,7 +686,7 @@ private Artifact artifactDto( dto.setFileSize(Files.size(file)); // Get the relative path of any extra zip directory added to the cache - Path relativePath = attachedResourcesPathsById.get(projectArtifact.getClassifier()); + Path relativePath = state.attachedResourcesPathsById.get(projectArtifact.getClassifier()); if (relativePath == null) { // If the path was not a member of this map, we are in presence of an original artifact. // we get its location on the disk @@ -900,15 +940,15 @@ private void restoreGeneratedSources(Artifact artifact, Path artifactFilePath, M } // TODO: move to config - public void attachGeneratedSources(MavenProject project) throws IOException { + public void attachGeneratedSources(MavenProject project, ProjectCacheState state, long buildStartTime) throws IOException { final Path targetDir = Paths.get(project.getBuild().getDirectory()); final Path generatedSourcesDir = targetDir.resolve("generated-sources"); - attachDirIfNotEmpty(generatedSourcesDir, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB); + attachDirIfNotEmpty(generatedSourcesDir, targetDir, project, state, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB, buildStartTime); final Path generatedTestSourcesDir = targetDir.resolve("generated-test-sources"); attachDirIfNotEmpty( - generatedTestSourcesDir, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB); + generatedTestSourcesDir, targetDir, project, state, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB, buildStartTime); Set sourceRoots = new TreeSet<>(); if (project.getCompileSourceRoots() != null) { @@ -924,18 +964,18 @@ public void attachGeneratedSources(MavenProject project) throws IOException { && sourceRootPath.startsWith(targetDir) && !(sourceRootPath.startsWith(generatedSourcesDir) || sourceRootPath.startsWith(generatedTestSourcesDir))) { // dir within target - attachDirIfNotEmpty(sourceRootPath, targetDir, project, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB); + attachDirIfNotEmpty(sourceRootPath, targetDir, project, state, OutputType.GENERATED_SOURCE, DEFAULT_FILE_GLOB, buildStartTime); } } } - private void attachOutputs(MavenProject project) throws IOException { + private void attachOutputs(MavenProject project, ProjectCacheState state, long buildStartTime) throws IOException { final List attachedDirs = cacheConfig.getAttachedOutputs(); for (DirName dir : attachedDirs) { final Path targetDir = Paths.get(project.getBuild().getDirectory()); final Path outputDir = targetDir.resolve(dir.getValue()); if (isPathInsideProject(project, outputDir)) { - attachDirIfNotEmpty(outputDir, targetDir, project, OutputType.EXTRA_OUTPUT, dir.getGlob()); + attachDirIfNotEmpty(outputDir, targetDir, project, state, OutputType.EXTRA_OUTPUT, dir.getGlob(), buildStartTime); } else { LOGGER.warn("Outside project output candidate directory discarded ({})", outputDir.normalize()); } @@ -946,16 +986,32 @@ private void attachDirIfNotEmpty( Path candidateSubDir, Path parentDir, MavenProject project, + ProjectCacheState state, final OutputType attachedOutputType, - final String glob) + final String glob, + final long buildStartTime) throws IOException { if (Files.isDirectory(candidateSubDir) && hasFiles(candidateSubDir)) { final Path relativePath = project.getBasedir().toPath().relativize(candidateSubDir); - attachedResourceCounter++; - final String classifier = attachedOutputType.getClassifierPrefix() + attachedResourceCounter; + state.attachedResourceCounter++; + final String classifier = attachedOutputType.getClassifierPrefix() + state.attachedResourceCounter; + + // Check if directory was modified during this build OR was restored from cache + long lastModified = Files.getLastModifiedTime(candidateSubDir).toMillis(); + boolean isRestoredThisBuild = state.restoredOutputClassifiers.contains(classifier); + + if (lastModified < buildStartTime && !isRestoredThisBuild) { + LOGGER.debug( + "Skipping stale directory: {} (modified at {}, build started at {}, not restored)", + candidateSubDir, + lastModified, + buildStartTime); + return; + } + boolean success = zipAndAttachArtifact(project, candidateSubDir, classifier, glob); if (success) { - attachedResourcesPathsById.put(classifier, relativePath); + state.attachedResourcesPathsById.put(classifier, relativePath); LOGGER.debug("Attached directory: {}", candidateSubDir); } } diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java index d86b15d4..56d33147 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java +++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java @@ -152,4 +152,15 @@ public interface CacheConfig { * Flag to save in cache only if a build went through the clean lifecycle */ boolean isMandatoryClean(); + + /** + * Flag to cache compile phase outputs (classes, test-classes, generated sources). + * When enabled (default), compile-only builds create cache entries that can be restored + * by subsequent builds. When disabled, caching only occurs during package phase or later. + *

+ * Use: -Dmaven.build.cache.cacheCompile=(true|false) + *

+ * Default: true + */ + boolean isCacheCompile(); } diff --git a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java index cd6e87c0..9a6dfd4c 100644 --- a/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java +++ b/src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java @@ -97,6 +97,7 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources"; public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins"; public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean"; + public static final String CACHE_COMPILE = "maven.build.cache.cacheCompile"; /** * Flag to control if we should skip lookup for cached artifacts globally or for a particular project even if @@ -541,6 +542,11 @@ public boolean isMandatoryClean() { return getProperty(MANDATORY_CLEAN, getConfiguration().isMandatoryClean()); } + @Override + public boolean isCacheCompile() { + return getProperty(CACHE_COMPILE, true); + } + @Override public String getId() { checkInitializedState(); diff --git a/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java b/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java new file mode 100644 index 00000000..47e7dcbc --- /dev/null +++ b/src/test/java/org/apache/maven/buildcache/its/CacheCompileDisabledTest.java @@ -0,0 +1,137 @@ +/* + * 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.its; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Stream; + +import org.apache.maven.buildcache.its.junit.IntegrationTest; +import org.apache.maven.it.VerificationException; +import org.apache.maven.it.Verifier; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests that the maven.build.cache.cacheCompile property correctly disables + * caching of compile-phase outputs. + */ +@IntegrationTest("src/test/projects/issue-393-compile-restore") +class CacheCompileDisabledTest { + + @Test + void compileDoesNotCacheWhenDisabled(Verifier verifier) throws VerificationException, IOException { + verifier.setAutoclean(false); + + // The actual cache is stored in target/build-cache (relative to the extension root, not test project) + Path localCache = Paths.get(System.getProperty("maven.multiModuleProjectDirectory")) + .resolve("target/build-cache"); + + // Clean cache before test + if (Files.exists(localCache)) { + deleteDirectory(localCache); + } + + // First compile with cacheCompile disabled - compile only the app module to avoid dependency issues + verifier.setLogFileName("../log-compile-disabled.txt"); + verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false"); + verifier.addCliOption("-pl"); + verifier.addCliOption("app"); + verifier.executeGoals(Arrays.asList("clean", "compile")); + verifier.verifyErrorFreeLog(); + + // Verify NO cache entry was created (no buildinfo.xml in local cache) + boolean hasCacheEntry = Files.walk(localCache) + .anyMatch(p -> p.getFileName().toString().equals("buildinfo.xml")); + assertFalse(hasCacheEntry, + "Cache entry should NOT be created when maven.build.cache.cacheCompile=false"); + + // Clean project and run compile again + verifier.setLogFileName("../log-compile-disabled-2.txt"); + verifier.addCliOption("-Dmaven.build.cache.cacheCompile=false"); + verifier.addCliOption("-pl"); + verifier.addCliOption("app"); + verifier.executeGoals(Arrays.asList("clean", "compile")); + verifier.verifyErrorFreeLog(); + + // Verify cache miss (should NOT restore from cache) + Path logFile = Paths.get(verifier.getBasedir()).getParent().resolve("log-compile-disabled-2.txt"); + String logContent = new String(Files.readAllBytes(logFile)); + assertFalse(logContent.contains("Found cached build, restoring"), + "Should NOT restore from cache when cacheCompile was disabled"); + } + + @Test + void compileCreatesCacheEntryWhenEnabled(Verifier verifier) throws VerificationException, IOException { + verifier.setAutoclean(false); + + // The actual cache is stored in target/build-cache (relative to the extension root, not test project) + Path localCache = Paths.get(System.getProperty("maven.multiModuleProjectDirectory")) + .resolve("target/build-cache"); + + // Clean cache before test + if (Files.exists(localCache)) { + deleteDirectory(localCache); + } + + // First compile with cacheCompile enabled (default) - compile only the app module + verifier.setLogFileName("../log-compile-enabled.txt"); + verifier.addCliOption("-pl"); + verifier.addCliOption("app"); + verifier.executeGoals(Arrays.asList("clean", "compile")); + verifier.verifyErrorFreeLog(); + + // Verify cache entry WAS created + boolean hasCacheEntry = Files.walk(localCache) + .anyMatch(p -> p.getFileName().toString().equals("buildinfo.xml")); + assertTrue(hasCacheEntry, + "Cache entry should be created when maven.build.cache.cacheCompile=true (default)"); + + // Clean project and run compile again + verifier.setLogFileName("../log-compile-enabled-2.txt"); + verifier.addCliOption("-pl"); + verifier.addCliOption("app"); + verifier.executeGoals(Arrays.asList("clean", "compile")); + verifier.verifyErrorFreeLog(); + + // Verify cache hit (should restore from cache) + verifier.verifyTextInLog("Found cached build, restoring"); + verifier.verifyTextInLog("Skipping plugin execution (cached): compiler:compile"); + } + + private void deleteDirectory(Path directory) throws IOException { + if (Files.exists(directory)) { + try (Stream walk = Files.walk(directory)) { + walk.sorted((a, b) -> b.compareTo(a)) + .forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + // Ignore + } + }); + } + } + } +} diff --git a/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java b/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java new file mode 100644 index 00000000..26089fd9 --- /dev/null +++ b/src/test/java/org/apache/maven/buildcache/its/Issue393CompileRestoreTest.java @@ -0,0 +1,51 @@ +/* + * 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.its; + +import java.util.Arrays; + +import org.apache.maven.buildcache.its.junit.IntegrationTest; +import org.apache.maven.it.VerificationException; +import org.apache.maven.it.Verifier; +import org.junit.jupiter.api.Test; + +@IntegrationTest("src/test/projects/issue-393-compile-restore") +class Issue393CompileRestoreTest { + + @Test + void restoresAttachedOutputsAfterCompileOnlyBuild(Verifier verifier) throws VerificationException { + verifier.setAutoclean(false); + + verifier.setLogFileName("../log-compile.txt"); + verifier.executeGoals(Arrays.asList("clean", "compile")); + verifier.verifyErrorFreeLog(); + verifier.verifyFilePresent("app/target/classes/module-info.class"); + verifier.verifyFilePresent("consumer/target/classes/module-info.class"); + + verifier.setLogFileName("../log-verify.txt"); + verifier.executeGoals(Arrays.asList("clean", "verify")); + verifier.verifyErrorFreeLog(); + verifier.verifyTextInLog("Found cached build, restoring org.apache.maven.caching.test.jpms:issue-393-app from cache"); + verifier.verifyTextInLog("Skipping plugin execution (cached): compiler:compile"); + + verifier.verifyFilePresent("app/target/classes/module-info.class"); + verifier.verifyFilePresent("consumer/target/classes/module-info.class"); + verifier.verifyFilePresent("consumer/target/test-classes/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.class"); + } +} diff --git a/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml b/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml new file mode 100644 index 00000000..8568c4d0 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/.mvn/extensions.xml @@ -0,0 +1,28 @@ + + + + + org.apache.maven.extensions + maven-build-cache-extension + ${projectVersion} + + diff --git a/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml b/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml new file mode 100644 index 00000000..6a91d577 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/.mvn/maven-build-cache-config.xml @@ -0,0 +1,34 @@ + + + + + + + + + classes + test-classes + maven-status + + + + diff --git a/src/test/projects/issue-393-compile-restore/app/pom.xml b/src/test/projects/issue-393-compile-restore/app/pom.xml new file mode 100644 index 00000000..f732e706 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/app/pom.xml @@ -0,0 +1,37 @@ + + + + 4.0.0 + + + org.apache.maven.caching.test.jpms + issue-393-compile-restore + 0.0.1-SNAPSHOT + + + issue-393-app + Issue 393 Compile Restore - App Module + jar + + diff --git a/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java b/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java new file mode 100644 index 00000000..ce82afdc --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/app/src/main/java/module-info.java @@ -0,0 +1,3 @@ +module org.apache.maven.caching.test.jpms.app { + exports org.apache.maven.caching.test.jpms.app; +} diff --git a/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java b/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java new file mode 100644 index 00000000..44483f7b --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/app/src/main/java/org/apache/maven/caching/test/jpms/app/Greeting.java @@ -0,0 +1,12 @@ +package org.apache.maven.caching.test.jpms.app; + +public final class Greeting { + + private Greeting() { + // utility + } + + public static String message() { + return "hello from module"; + } +} diff --git a/src/test/projects/issue-393-compile-restore/consumer/pom.xml b/src/test/projects/issue-393-compile-restore/consumer/pom.xml new file mode 100644 index 00000000..ab7a00ee --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/consumer/pom.xml @@ -0,0 +1,64 @@ + + + + 4.0.0 + + + org.apache.maven.caching.test.jpms + issue-393-compile-restore + 0.0.1-SNAPSHOT + + + issue-393-consumer + Issue 393 Compile Restore - Consumer Module + jar + + + + ${project.groupId} + issue-393-app + ${project.version} + + + org.junit.jupiter + junit-jupiter + 5.10.2 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + true + + + + + + diff --git a/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java new file mode 100644 index 00000000..52d0dc94 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module org.apache.maven.caching.test.jpms.consumer { + requires org.apache.maven.caching.test.jpms.app; + exports org.apache.maven.caching.test.jpms.consumer; +} diff --git a/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java new file mode 100644 index 00000000..a64ad993 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/consumer/src/main/java/org/apache/maven/caching/test/jpms/consumer/Consumer.java @@ -0,0 +1,14 @@ +package org.apache.maven.caching.test.jpms.consumer; + +import org.apache.maven.caching.test.jpms.app.Greeting; + +public final class Consumer { + + private Consumer() { + // utility + } + + public static String message() { + return Greeting.message(); + } +} diff --git a/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java b/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java new file mode 100644 index 00000000..292a7a52 --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/consumer/src/test/java/org/apache/maven/caching/test/jpms/consumer/ConsumerTest.java @@ -0,0 +1,13 @@ +package org.apache.maven.caching.test.jpms.consumer; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ConsumerTest { + + @Test + void messageIsProvidedByUpstreamModule() { + assertEquals("hello from module", Consumer.message()); + } +} diff --git a/src/test/projects/issue-393-compile-restore/pom.xml b/src/test/projects/issue-393-compile-restore/pom.xml new file mode 100644 index 00000000..f504867a --- /dev/null +++ b/src/test/projects/issue-393-compile-restore/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + org.apache.maven.caching.test.jpms + issue-393-compile-restore + 0.0.1-SNAPSHOT + pom + Issue 393 Compile Restore Aggregator + + + app + consumer + + + + UTF-8 + 17 + + +