diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingProjectArtifactsCache.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingProjectArtifactsCache.java index 73b54045..ee0531d2 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingProjectArtifactsCache.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingProjectArtifactsCache.java @@ -17,16 +17,27 @@ package org.mvndaemon.mvnd.cache.invalidating; import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.project.MavenProject; import org.apache.maven.project.artifact.DefaultProjectArtifactsCache; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.LocalRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.repository.WorkspaceRepository; import org.eclipse.sisu.Priority; import org.mvndaemon.mvnd.cache.Cache; import org.mvndaemon.mvnd.cache.CacheFactory; @@ -36,6 +47,119 @@ import org.mvndaemon.mvnd.cache.CacheFactory; @Priority(10) public class InvalidatingProjectArtifactsCache extends DefaultProjectArtifactsCache { + protected static class CacheKey + implements Key { + + private final String groupId; + + private final String artifactId; + + private final String version; + + private final Set dependencyArtifacts; + + private final WorkspaceRepository workspace; + + private final LocalRepository localRepo; + + private final List repositories; + + private final Set collect; + + private final Set resolve; + + private boolean aggregating; + + private final int hashCode; + + public CacheKey(MavenProject project, List repositories, + Collection scopesToCollect, Collection scopesToResolve, boolean aggregating, + RepositorySystemSession session) { + + groupId = project.getGroupId(); + artifactId = project.getArtifactId(); + version = project.getVersion(); + + Set deps = new LinkedHashSet<>(); + if (project.getDependencyArtifacts() != null) { + for (Artifact dep : project.getDependencyArtifacts()) { + deps.add(dep.toString()); + } + } + dependencyArtifacts = Collections.unmodifiableSet(deps); + + workspace = RepositoryUtils.getWorkspace(session); + this.localRepo = session.getLocalRepository(); + this.repositories = new ArrayList<>(repositories.size()); + for (RemoteRepository repository : repositories) { + if (repository.isRepositoryManager()) { + this.repositories.addAll(repository.getMirroredRepositories()); + } else { + this.repositories.add(repository); + } + } + collect = scopesToCollect == null + ? Collections. emptySet() + : Collections.unmodifiableSet(new HashSet<>(scopesToCollect)); + resolve = scopesToResolve == null + ? Collections. emptySet() + : Collections.unmodifiableSet(new HashSet<>(scopesToResolve)); + this.aggregating = aggregating; + + int hash = 17; + hash = hash * 31 + Objects.hashCode(groupId); + hash = hash * 31 + Objects.hashCode(artifactId); + hash = hash * 31 + Objects.hashCode(version); + hash = hash * 31 + Objects.hashCode(dependencyArtifacts); + hash = hash * 31 + Objects.hashCode(workspace); + hash = hash * 31 + Objects.hashCode(localRepo); + hash = hash * 31 + RepositoryUtils.repositoriesHashCode(repositories); + hash = hash * 31 + Objects.hashCode(collect); + hash = hash * 31 + Objects.hashCode(resolve); + hash = hash * 31 + Objects.hashCode(aggregating); + this.hashCode = hash; + } + + public boolean matches(String groupId, String artifactId, String version) { + return Objects.equals(this.groupId, groupId) + && Objects.equals(this.artifactId, artifactId) + && Objects.equals(this.version, version); + } + + @Override + public String toString() { + return groupId + ":" + artifactId + ":" + version; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof CacheKey)) { + return false; + } + + CacheKey that = (CacheKey) o; + + return Objects.equals(groupId, that.groupId) && Objects.equals(artifactId, that.artifactId) + && Objects.equals(version, that.version) + && Objects.equals(dependencyArtifacts, that.dependencyArtifacts) + && Objects.equals(workspace, that.workspace) + && Objects.equals(localRepo, that.localRepo) + && RepositoryUtils.repositoriesEquals(repositories, that.repositories) + && Objects.equals(collect, that.collect) + && Objects.equals(resolve, that.resolve) + && aggregating == that.aggregating; + } + } + static class Record implements org.mvndaemon.mvnd.cache.CacheRecord { private final CacheRecord record; @@ -66,6 +190,13 @@ public class InvalidatingProjectArtifactsCache extends DefaultProjectArtifactsCa this.cache = cacheFactory.newCache(); } + @Override + public Key createKey(MavenProject project, Collection scopesToCollect, Collection scopesToResolve, + boolean aggregating, RepositorySystemSession session) { + return new CacheKey(project, project.getRemoteProjectRepositories(), scopesToCollect, scopesToResolve, + aggregating, session); + } + @Override public CacheRecord get(Key key) throws LifecycleExecutionException { Record r = cache.get(key); diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java b/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java index 104c8048..fda2d6b1 100644 --- a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java +++ b/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java @@ -30,6 +30,7 @@ import org.apache.maven.eventspy.AbstractEventSpy; import org.apache.maven.eventspy.EventSpy; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionResult; +import org.apache.maven.project.MavenProject; import org.eclipse.sisu.Typed; import org.mvndaemon.mvnd.common.Environment; import org.slf4j.Logger; @@ -44,15 +45,19 @@ public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy { private final InvalidatingPluginRealmCache pluginCache; private final InvalidatingExtensionRealmCache extensionCache; + private final InvalidatingProjectArtifactsCache projectArtifactsCache; private Path multiModuleProjectDirectory; private String pattern; private PathMatcher matcher; @Inject public InvalidatingRealmCacheEventSpy( - InvalidatingPluginRealmCache cache, InvalidatingExtensionRealmCache extensionCache) { + InvalidatingPluginRealmCache cache, + InvalidatingExtensionRealmCache extensionCache, + InvalidatingProjectArtifactsCache projectArtifactsCache) { this.pluginCache = cache; this.extensionCache = extensionCache; + this.projectArtifactsCache = projectArtifactsCache; } @Override @@ -97,12 +102,21 @@ public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy { /* Evict the entries referring to jars under multiModuleProjectDirectory */ pluginCache.cache.removeIf(this::shouldEvict); extensionCache.cache.removeIf(this::shouldEvict); + MavenExecutionResult mer = (MavenExecutionResult) event; + List projects = mer.getTopologicallySortedProjects(); + projectArtifactsCache.cache + .removeIf((k, r) -> shouldEvict(projects, (InvalidatingProjectArtifactsCache.CacheKey) k, r)); } } catch (Exception e) { LOG.warn("Could not notify CliPluginRealmCache", e); } } + private boolean shouldEvict(List projects, InvalidatingProjectArtifactsCache.CacheKey k, + InvalidatingProjectArtifactsCache.Record v) { + return projects.stream().anyMatch(p -> k.matches(p.getGroupId(), p.getArtifactId(), p.getVersion())); + } + private boolean shouldEvict(InvalidatingPluginRealmCache.Key k, InvalidatingPluginRealmCache.Record v) { try { for (URL url : v.record.getRealm().getURLs()) { diff --git a/integration-tests/src/test/java/org/mvndaemon/mvnd/it/MultiLookupTest.java b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/MultiLookupTest.java new file mode 100644 index 00000000..146a2caf --- /dev/null +++ b/integration-tests/src/test/java/org/mvndaemon/mvnd/it/MultiLookupTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.it; + +import java.io.IOException; +import javax.inject.Inject; +import org.junit.jupiter.api.Test; +import org.mvndaemon.mvnd.assertj.TestClientOutput; +import org.mvndaemon.mvnd.junit.ClientFactory; +import org.mvndaemon.mvnd.junit.MvndTest; +import org.mvndaemon.mvnd.junit.TestParameters; + +@MvndTest(projectDir = "src/test/projects/multi-lookup") +public class MultiLookupTest { + + @Inject + ClientFactory clientFactory; + + @Inject + TestParameters parameters; + + @Test + void cleanInstall() throws IOException, InterruptedException { + + final TestClientOutput output = new TestClientOutput(); + + clientFactory + .newClient(parameters.cd(parameters.getTestDir().resolve("project/hello"))) + .execute(output, "clean", "install", "-e").assertFailure(); + + clientFactory + .newClient(parameters) + .execute(output, "clean", "install", "-e").assertSuccess(); + + clientFactory + .newClient(parameters.cd(parameters.getTestDir().resolve("project/hello"))) + .execute(output, "clean", "install", "-e").assertSuccess(); + } +} diff --git a/integration-tests/src/test/projects/multi-lookup/.mvn/maven.config b/integration-tests/src/test/projects/multi-lookup/.mvn/maven.config new file mode 100644 index 00000000..4230c241 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/.mvn/maven.config @@ -0,0 +1,3 @@ +-Dmaven.wagon.httpconnectionManager.ttlSeconds=120 +-Dmaven.wagon.http.retryHandler.requestSentEnabled=true +-Dmaven.wagon.http.retryHandler.count=10 diff --git a/integration-tests/src/test/projects/multi-lookup/api/pom.xml b/integration-tests/src/test/projects/multi-lookup/api/pom.xml new file mode 100644 index 00000000..928e9c92 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/api/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup + 0.0.1-SNAPSHOT + ../pom.xml + + + multi-lookup-api + + \ No newline at end of file diff --git a/integration-tests/src/test/projects/multi-lookup/api/src/main/java/org/mvndaemon/mvnd/test/multi/module/api/Greeting.java b/integration-tests/src/test/projects/multi-lookup/api/src/main/java/org/mvndaemon/mvnd/test/multi/module/api/Greeting.java new file mode 100644 index 00000000..5afbb8dd --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/api/src/main/java/org/mvndaemon/mvnd/test/multi/module/api/Greeting.java @@ -0,0 +1,22 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.test.multi.module.api; + +public interface Greeting { + + public String greet(); + +} diff --git a/integration-tests/src/test/projects/multi-lookup/hello/pom.xml b/integration-tests/src/test/projects/multi-lookup/hello/pom.xml new file mode 100644 index 00000000..453e6f60 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hello/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup + 0.0.1-SNAPSHOT + ../pom.xml + + + multi-lookup-hello + + + + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup-api + ${project.version} + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + + \ No newline at end of file diff --git a/integration-tests/src/test/projects/multi-lookup/hello/src/main/java/org/mvndaemon/mvnd/test/multi/module/hello/Hello.java b/integration-tests/src/test/projects/multi-lookup/hello/src/main/java/org/mvndaemon/mvnd/test/multi/module/hello/Hello.java new file mode 100644 index 00000000..7a2651a2 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hello/src/main/java/org/mvndaemon/mvnd/test/multi/module/hello/Hello.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.test.multi.module.hello; + +import org.mvndaemon.mvnd.test.multi.module.api.Greeting; + +public class Hello implements Greeting { + + public String greet() { + return "Hello"; + } + +} diff --git a/integration-tests/src/test/projects/multi-lookup/hello/src/test/java/org/mvndaemon/mvnd/test/multi/module/hello/HelloTest.java b/integration-tests/src/test/projects/multi-lookup/hello/src/test/java/org/mvndaemon/mvnd/test/multi/module/hello/HelloTest.java new file mode 100644 index 00000000..b80fdbe7 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hello/src/test/java/org/mvndaemon/mvnd/test/multi/module/hello/HelloTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.test.multi.module.hello; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class HelloTest { + + @Test + void greet() throws IOException { + final String actual = new Hello().greet(); + Files.write(Paths.get("target/hello.txt"), actual.getBytes(StandardCharsets.UTF_8)); + Assertions.assertEquals("Hello", actual); + + /* Have some random delay so that hi and hello may finish in random order */ + if (Math.random() >= 0.5) { + try { + System.out.println("HelloTest sleeps for 500 ms"); + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + +} diff --git a/integration-tests/src/test/projects/multi-lookup/hi/pom.xml b/integration-tests/src/test/projects/multi-lookup/hi/pom.xml new file mode 100644 index 00000000..d6bbe149 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hi/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup + 0.0.1-SNAPSHOT + ../pom.xml + + + multi-lookup-hi + + + + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup-api + ${project.version} + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + test + + + + \ No newline at end of file diff --git a/integration-tests/src/test/projects/multi-lookup/hi/src/main/java/org/mvndaemon/mvnd/test/multi/module/hi/Hi.java b/integration-tests/src/test/projects/multi-lookup/hi/src/main/java/org/mvndaemon/mvnd/test/multi/module/hi/Hi.java new file mode 100644 index 00000000..a7f135e1 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hi/src/main/java/org/mvndaemon/mvnd/test/multi/module/hi/Hi.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.test.multi.module.hi; + +import org.mvndaemon.mvnd.test.multi.module.api.Greeting; + +public class Hi implements Greeting { + + public String greet() { + return "Hi"; + } + +} diff --git a/integration-tests/src/test/projects/multi-lookup/hi/src/test/java/org/mvndaemon/mvnd/test/multi/module/hi/HiTest.java b/integration-tests/src/test/projects/multi-lookup/hi/src/test/java/org/mvndaemon/mvnd/test/multi/module/hi/HiTest.java new file mode 100644 index 00000000..24155017 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/hi/src/test/java/org/mvndaemon/mvnd/test/multi/module/hi/HiTest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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.mvndaemon.mvnd.test.multi.module.hi; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class HiTest { + + @Test + void greet() throws IOException { + final String actual = new Hi().greet(); + Files.write(Paths.get("target/hi.txt"), actual.getBytes(StandardCharsets.UTF_8)); + Assertions.assertEquals("Hi", actual); + + /* Have some random delay so that hi and hello may finish in random order */ + if (Math.random() >= 0.5) { + try { + System.out.println("HiTest sleeps for 500 ms"); + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + +} diff --git a/integration-tests/src/test/projects/multi-lookup/pom.xml b/integration-tests/src/test/projects/multi-lookup/pom.xml new file mode 100644 index 00000000..df279514 --- /dev/null +++ b/integration-tests/src/test/projects/multi-lookup/pom.xml @@ -0,0 +1,77 @@ + + + + 4.0.0 + org.mvndaemon.mvnd.test.multi-lookup + multi-lookup + 0.0.1-SNAPSHOT + pom + + + UTF-8 + 1.8 + 1.8 + + 2.5 + 3.8.0 + 2.4 + 2.6 + 2.22.2 + + + + api + hello + hi + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-install-plugin + ${maven-install-plugin.version} + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + + + + \ No newline at end of file