Negative local Maven repo lookup persists after installing the artifact, fixes #482

This commit is contained in:
Guillaume Nodet
2021-10-06 20:27:18 +02:00
parent dff50ace35
commit dae96da65e
13 changed files with 560 additions and 1 deletions

View File

@@ -17,16 +17,27 @@ package org.mvndaemon.mvnd.cache.invalidating;
import java.io.File; import java.io.File;
import java.nio.file.Path; 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.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.DefaultProjectArtifactsCache; 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.eclipse.sisu.Priority;
import org.mvndaemon.mvnd.cache.Cache; import org.mvndaemon.mvnd.cache.Cache;
import org.mvndaemon.mvnd.cache.CacheFactory; import org.mvndaemon.mvnd.cache.CacheFactory;
@@ -36,6 +47,119 @@ import org.mvndaemon.mvnd.cache.CacheFactory;
@Priority(10) @Priority(10)
public class InvalidatingProjectArtifactsCache extends DefaultProjectArtifactsCache { 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<String> dependencyArtifacts;
private final WorkspaceRepository workspace;
private final LocalRepository localRepo;
private final List<RemoteRepository> repositories;
private final Set<String> collect;
private final Set<String> resolve;
private boolean aggregating;
private final int hashCode;
public CacheKey(MavenProject project, List<RemoteRepository> repositories,
Collection<String> scopesToCollect, Collection<String> scopesToResolve, boolean aggregating,
RepositorySystemSession session) {
groupId = project.getGroupId();
artifactId = project.getArtifactId();
version = project.getVersion();
Set<String> 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.<String> emptySet()
: Collections.unmodifiableSet(new HashSet<>(scopesToCollect));
resolve = scopesToResolve == null
? Collections.<String> 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 { static class Record implements org.mvndaemon.mvnd.cache.CacheRecord {
private final CacheRecord record; private final CacheRecord record;
@@ -66,6 +190,13 @@ public class InvalidatingProjectArtifactsCache extends DefaultProjectArtifactsCa
this.cache = cacheFactory.newCache(); this.cache = cacheFactory.newCache();
} }
@Override
public Key createKey(MavenProject project, Collection<String> scopesToCollect, Collection<String> scopesToResolve,
boolean aggregating, RepositorySystemSession session) {
return new CacheKey(project, project.getRemoteProjectRepositories(), scopesToCollect, scopesToResolve,
aggregating, session);
}
@Override @Override
public CacheRecord get(Key key) throws LifecycleExecutionException { public CacheRecord get(Key key) throws LifecycleExecutionException {
Record r = cache.get(key); Record r = cache.get(key);

View File

@@ -30,6 +30,7 @@ import org.apache.maven.eventspy.AbstractEventSpy;
import org.apache.maven.eventspy.EventSpy; import org.apache.maven.eventspy.EventSpy;
import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.project.MavenProject;
import org.eclipse.sisu.Typed; import org.eclipse.sisu.Typed;
import org.mvndaemon.mvnd.common.Environment; import org.mvndaemon.mvnd.common.Environment;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -44,15 +45,19 @@ public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy {
private final InvalidatingPluginRealmCache pluginCache; private final InvalidatingPluginRealmCache pluginCache;
private final InvalidatingExtensionRealmCache extensionCache; private final InvalidatingExtensionRealmCache extensionCache;
private final InvalidatingProjectArtifactsCache projectArtifactsCache;
private Path multiModuleProjectDirectory; private Path multiModuleProjectDirectory;
private String pattern; private String pattern;
private PathMatcher matcher; private PathMatcher matcher;
@Inject @Inject
public InvalidatingRealmCacheEventSpy( public InvalidatingRealmCacheEventSpy(
InvalidatingPluginRealmCache cache, InvalidatingExtensionRealmCache extensionCache) { InvalidatingPluginRealmCache cache,
InvalidatingExtensionRealmCache extensionCache,
InvalidatingProjectArtifactsCache projectArtifactsCache) {
this.pluginCache = cache; this.pluginCache = cache;
this.extensionCache = extensionCache; this.extensionCache = extensionCache;
this.projectArtifactsCache = projectArtifactsCache;
} }
@Override @Override
@@ -97,12 +102,21 @@ public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy {
/* Evict the entries referring to jars under multiModuleProjectDirectory */ /* Evict the entries referring to jars under multiModuleProjectDirectory */
pluginCache.cache.removeIf(this::shouldEvict); pluginCache.cache.removeIf(this::shouldEvict);
extensionCache.cache.removeIf(this::shouldEvict); extensionCache.cache.removeIf(this::shouldEvict);
MavenExecutionResult mer = (MavenExecutionResult) event;
List<MavenProject> projects = mer.getTopologicallySortedProjects();
projectArtifactsCache.cache
.removeIf((k, r) -> shouldEvict(projects, (InvalidatingProjectArtifactsCache.CacheKey) k, r));
} }
} catch (Exception e) { } catch (Exception e) {
LOG.warn("Could not notify CliPluginRealmCache", e); LOG.warn("Could not notify CliPluginRealmCache", e);
} }
} }
private boolean shouldEvict(List<MavenProject> 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) { private boolean shouldEvict(InvalidatingPluginRealmCache.Key k, InvalidatingPluginRealmCache.Record v) {
try { try {
for (URL url : v.record.getRealm().getURLs()) { for (URL url : v.record.getRealm().getURLs()) {

View File

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

View File

@@ -0,0 +1,3 @@
-Dmaven.wagon.httpconnectionManager.ttlSeconds=120
-Dmaven.wagon.http.retryHandler.requestSentEnabled=true
-Dmaven.wagon.http.retryHandler.count=10

View File

@@ -0,0 +1,30 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>multi-lookup-api</artifactId>
</project>

View File

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

View File

@@ -0,0 +1,44 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>multi-lookup-hello</artifactId>
<dependencies>
<dependency>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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";
}
}

View File

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

View File

@@ -0,0 +1,44 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>multi-lookup-hi</artifactId>
<dependencies>
<dependency>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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";
}
}

View File

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

View File

@@ -0,0 +1,77 @@
<!--
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.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mvndaemon.mvnd.test.multi-lookup</groupId>
<artifactId>multi-lookup</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<maven-clean-plugin.version>2.5</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.0</maven-compiler-plugin.version>
<maven-install-plugin.version>2.4</maven-install-plugin.version>
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
</properties>
<modules>
<module>api</module>
<module>hello</module>
<module>hi</module>
</modules>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven-install-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>