Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ChainedLocalRepositoryManager to support -Dmaven.repo.local.tail #43352

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@
import org.eclipse.aether.repository.ArtifactRepository;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactDescriptorException;
import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
import org.eclipse.aether.transfer.TransferListener;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.eclipse.aether.util.repository.ChainedLocalRepositoryManager;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector;
Expand Down Expand Up @@ -128,6 +130,8 @@ public class BootstrapMavenContext {
private List<RemoteRepository> remotePluginRepos;
private RemoteRepositoryManager remoteRepoManager;
private String localRepo;
private String[] localRepoTail;
private Boolean localRepoTailIgnoreAvailability;
private Path currentPom;
private Boolean currentProjectExists;
private String alternatePomName;
Expand Down Expand Up @@ -157,6 +161,8 @@ public BootstrapMavenContext(BootstrapMavenContextConfig<?> config)
this.alternatePomName = config.alternatePomName;
this.artifactTransferLogging = config.artifactTransferLogging;
this.localRepo = config.localRepo;
this.localRepoTail = config.localRepoTail;
this.localRepoTailIgnoreAvailability = config.localRepoTailIgnoreAvailability;
this.offline = config.offline;
this.warnOnFailingWorkspaceModules = config.warnOnFailedWorkspaceModules;
this.repoSystem = config.repoSystem;
Expand Down Expand Up @@ -363,6 +369,16 @@ public String getLocalRepo() throws BootstrapMavenException {
return localRepo == null ? localRepo = resolveLocalRepo(getEffectiveSettings()) : localRepo;
}

private String[] getLocalRepoTail() {
return localRepoTail == null ? localRepoTail = resolveLocalRepoTail() : localRepoTail;
}

private boolean getLocalRepoTailIgnoreAvailability() {
return localRepoTailIgnoreAvailability == null
? localRepoTailIgnoreAvailability = resolveLocalRepoTailIgnoreAvailability()
: localRepoTailIgnoreAvailability;
}

private LocalProject resolveCurrentProject(Function<Path, Model> modelProvider) throws BootstrapMavenException {
try {
return LocalProject.loadWorkspace(this, modelProvider);
Expand All @@ -384,6 +400,29 @@ private String resolveLocalRepo(Settings settings) {
return localRepo == null ? new File(getUserMavenConfigurationHome(), "repository").getAbsolutePath() : localRepo;
}

private String[] resolveLocalRepoTail() {
final String localRepoTail = getProperty("maven.repo.local.tail");
if (localRepoTail == null) {
return new String[] {};
}
if (localRepoTail.trim().isEmpty()) {
return new String[] {};
}
return localRepoTail.split(",");
}

private boolean resolveLocalRepoTailIgnoreAvailability() {
final String ignoreAvailability = getProperty("maven.repo.local.tail.ignoreAvailability");

// The only "falsy" value is `false` itself
if ("false".equalsIgnoreCase(ignoreAvailability)) {
return false;
}

//All other strings are interpreted as `true`.
return true;
}

private File resolveSettingsFile(String settingsArg, Supplier<File> supplier) {
File userSettings;
if (settingsArg != null) {
Expand Down Expand Up @@ -460,9 +499,23 @@ private DefaultRepositorySystemSession newRepositorySystemSession() throws Boots
}
session.setMirrorSelector(ms);
}

final String localRepoPath = getLocalRepo();
session.setLocalRepositoryManager(
getRepositorySystem().newLocalRepositoryManager(session, new LocalRepository(localRepoPath)));
final String[] localRepoTailPaths = getLocalRepoTail();

final LocalRepositoryManager head = getRepositorySystem().newLocalRepositoryManager(session,
new LocalRepository(localRepoPath));

if (localRepoTailPaths.length == 0) {
session.setLocalRepositoryManager(head);
} else {
final List<LocalRepositoryManager> tail = new ArrayList<>(localRepoTailPaths.length);
for (final String tailPath : localRepoTailPaths) {
tail.add(getRepositorySystem().newLocalRepositoryManager(session, new LocalRepository(tailPath)));
}
session.setLocalRepositoryManager(
new ChainedLocalRepositoryManager(head, tail, getLocalRepoTailIgnoreAvailability()));
}

session.setOffline(isOffline());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
public class BootstrapMavenContextConfig<T extends BootstrapMavenContextConfig<?>> {

protected String localRepo;
protected String[] localRepoTail;
protected Boolean localRepoTailIgnoreAvailability;
protected Boolean offline;
protected LocalProject currentProject;
protected boolean workspaceDiscovery = true;
Expand Down Expand Up @@ -92,6 +94,30 @@ public T setLocalRepository(String localRepo) {
return (T) this;
}

/**
* Local repository tail locations (comma-separated)
*
* @param localRepoTail local repository tail locations (comma-separated)
* @return this instance
*/
@SuppressWarnings("unchecked")
public T setLocalRepositoryTail(String[] localRepoTail) {
this.localRepoTail = localRepoTail;
return (T) this;
}

/**
* Wheter to ignore availability on local repository tail (default: true)
*
* @param localRepoTailIgnoreAvailability whether to ignore availability on local repository tail
* @return this instance
*/
@SuppressWarnings("unchecked")
public T setLocalRepositoryTailIgnoreAvailability(boolean localRepoTailIgnoreAvailability) {
this.localRepoTailIgnoreAvailability = localRepoTailIgnoreAvailability;
return (T) this;
}

/**
* Whether to operate offline
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ protected RemoteRepository newRepo(String id, String url) {
}

protected BootstrapMavenContext bootstrapMavenContextForProject(String projectOnCp) throws Exception {

final BootstrapMavenContextConfig<?> config = BootstrapMavenContext.config();
initBootstrapMavenContextConfig(config);
return bootstrapMavenContextForProject(projectOnCp, config);
}

protected BootstrapMavenContext bootstrapMavenContextForProject(String projectOnCp, BootstrapMavenContextConfig<?> config)
throws Exception {
final Path projectLocation = getProjectLocation(projectOnCp);
config.setCurrentProject(projectLocation.toString());

Expand All @@ -56,9 +58,6 @@ protected BootstrapMavenContext bootstrapMavenContextForProject(String projectOn
return new BootstrapMavenContext(config);
}

protected void initBootstrapMavenContextConfig(BootstrapMavenContextConfig<?> config) throws Exception {
}

protected BootstrapMavenContext bootstrapMavenContextWithSettings(String configDirOnCp) throws Exception {

final BootstrapMavenContextConfig<?> config = initBootstrapMavenContextConfig();
Expand All @@ -75,7 +74,7 @@ protected BootstrapMavenContextConfig<?> initBootstrapMavenContextConfig() throw
return BootstrapMavenContext.config().setWorkspaceDiscovery(false);
}

protected Path getProjectLocation(String projectOnCp) throws URISyntaxException {
protected static Path getProjectLocation(String projectOnCp) throws URISyntaxException {
final URL basedirUrl = Thread.currentThread().getContextClassLoader().getResource(projectOnCp);
assertNotNull(basedirUrl);
return Paths.get(basedirUrl.toURI());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package io.quarkus.bootstrap.resolver.maven.test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;

import java.net.URISyntaxException;
import java.nio.file.Paths;

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.junit.jupiter.api.Test;

import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;

public class ChainedLocalRepositoryManagerTest extends BootstrapMavenContextTestBase {

private static final String M2_LOCAL_1;
private static final String M2_LOCAL_2;
private static final String M2_FROM_REMOTE;

static {
final String projectLocation;
try {
projectLocation = getProjectLocation("workspace-with-local-repo-tail").toString();
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}

M2_LOCAL_1 = Paths.get(projectLocation, ".m2-local-1", "repository").toAbsolutePath().toString();
M2_LOCAL_2 = Paths.get(projectLocation, ".m2-local-2", "repository").toAbsolutePath().toString();
M2_FROM_REMOTE = Paths.get(projectLocation, ".m2-from-remote", "repository").toAbsolutePath().toString();
}

// Tail configuration tests

@Test
public void testNoTail() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail");
assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testTailConfiguredButEmptyString() throws Exception {
setSystemProp("maven.repo.local.tail", "");
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail");
assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testTailConfiguredButBlank() throws Exception {
setSystemProp("maven.repo.local.tail", " ");
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail");
assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testTailConfiguredButNonExistent() throws Exception {
setSystemProp("maven.repo.local.tail", "/tmp/this-dir-does-not-exist");
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail");
assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailViaSystemProp() throws Exception {
setSystemProp("maven.repo.local.tail", M2_LOCAL_1);
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail");
assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailViaConfig() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_LOCAL_1 }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailResolutionOrder() throws Exception {
final BootstrapMavenContext mvnLocal1first = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_LOCAL_1, M2_LOCAL_2 }));

final BootstrapMavenContext mvnLocal2first = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_LOCAL_2, M2_LOCAL_1 }));

assertEquals(resolveOrgAcmeFooJar001(mvnLocal1first).getFile().getAbsolutePath(),
Paths.get(M2_LOCAL_1, "org", "acme", "foo", "0.0.1", "foo-0.0.1.jar").toAbsolutePath().toString());
assertEquals(resolveOrgAcmeFooJar001(mvnLocal2first).getFile().getAbsolutePath(),
Paths.get(M2_LOCAL_2, "org", "acme", "foo", "0.0.1", "foo-0.0.1.jar").toAbsolutePath().toString());
}

@Test
public void testValidTailMultiplicity() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_LOCAL_1, M2_LOCAL_2 }));

final Artifact foo = resolveOrgAcmeFooJar001(mvn);
assertNotNull(foo);
assertEquals(foo.getFile().getAbsolutePath(),
Paths.get(M2_LOCAL_1, "org", "acme", "foo", "0.0.1", "foo-0.0.1.jar").toAbsolutePath().toString());

final Artifact bar = resolveOrgAcmeBarJar002(mvn);
assertNotNull(bar);
assertEquals(bar.getFile().getAbsolutePath(),
Paths.get(M2_LOCAL_2, "org", "acme", "bar", "0.0.2", "bar-0.0.2.jar").toAbsolutePath().toString());
}

// ignoreAvailability tests

@Test
public void testValidTailLocalCheckingForAvailabilityViaConfig() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTailIgnoreAvailability(false)
.setLocalRepositoryTail(new String[] { M2_LOCAL_1 }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailFromRemoteCheckingForAvailabilityViaConfig() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTailIgnoreAvailability(false)
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailFromRemoteCheckingForAvailabilityViaSystemProp() throws Exception {
setSystemProp("maven.repo.local.tail.ignoreAvailability", "false");
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertThrowsExactly(BootstrapMavenException.class, () -> resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailFromRemoteIgnoringAvailabilityViaSystemPropEmpty() throws Exception {
setSystemProp("maven.repo.local.tail.ignoreAvailability", ""); // will become `true`
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailFromRemoteIgnoringAvailabilityViaSystemPropBlank() throws Exception {
setSystemProp("maven.repo.local.tail.ignoreAvailability", " "); // will become `true`
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tiagobento could you please explain this one. ignoreAvailability is true. Is it availability of artifacts that should be ignored?

Copy link
Contributor Author

@tiagobento tiagobento Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. So maybe the ignoreAvailability name kind of gives the wrong idea of what it actually does, and maybe a more explicit, albeit verbose, name would be ignoreRemoteAvailability? cc @cstamas

This flag is only applicable for dependencies that have been downloaded from a remote repository prior to being consumed as part of a tail local repository ("tracked", in Maven Resolver's terminology) . See the _remote.repositories file.

This particular test succeeds because maven.repo.local.tail.ignoreAvailability ends up being true, so even though org.acme:foo:jar:0.0.1 was supposedly initially resolved from a remote repository with id = some-repo-id, this fact is ignored, and it being available in the local repository suffices. When maven.repo.local.tail.ignoreAvailability is false, it fails. That case is exactly what is covered by testValidTailFromRemoteCheckingForAvailabilityViaSystemProp.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. The property name is indeed very confusing. It sounds like the meaning is "trust any remote repo" that provided an artifact.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"availability" in resolver means that tracked remote repository (by repoID) is available in the context artifact is asked for. So "remote" may come in as "remote repo ID is available in current context asking for artifact" :)

}

@Test
public void testValidTailFromRemoteIgnoringAvailabilityViaSystemPropTruthy() throws Exception {
setSystemProp("maven.repo.local.tail.ignoreAvailability", "fals"); // will become `true`
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailLocalIgnoringAvailabilityViaConfig() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTailIgnoreAvailability(true)
.setLocalRepositoryTail(new String[] { M2_LOCAL_1 }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

@Test
public void testValidTailFromRemoteIgnoringAvailabilityViaConfig() throws Exception {
final BootstrapMavenContext mvn = bootstrapMavenContextForProject("workspace-with-local-repo-tail",
BootstrapMavenContext.config()
.setLocalRepositoryTailIgnoreAvailability(true)
.setLocalRepositoryTail(new String[] { M2_FROM_REMOTE }));

assertNotNull(resolveOrgAcmeFooJar001(mvn));
}

private Artifact resolveOrgAcmeFooJar001(BootstrapMavenContext ctx) throws BootstrapMavenException {
final MavenArtifactResolver resolver = new MavenArtifactResolver(ctx);
return resolver.resolve(new DefaultArtifact("org.acme", "foo", "", "jar", "0.0.1")).getArtifact();
}

private Artifact resolveOrgAcmeBarJar002(BootstrapMavenContext ctx) throws BootstrapMavenException {
final MavenArtifactResolver resolver = new MavenArtifactResolver(ctx);
return resolver.resolve(new DefaultArtifact("org.acme", "bar", "", "jar", "0.0.2")).getArtifact();
}
}
Loading
Loading