From 4b9255bdf3b060e7c50fe6f09c091c2d8c9ff399 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Fri, 8 Apr 2022 13:16:17 +0200 Subject: [PATCH] [MRESOLVER-248] Make DF and BF collector implementations coexist (#161) Revive replaced DF collector, and make them both coexists. Default one remains "old" DF, while new BF may be activated on demand (based on session config). --- .../impl/DependencyResolutionSkipper.java | 59 -- .../aether/impl/guice/AetherModule.java | 22 + .../collect/CachingArtifactTypeRegistry.java | 5 +- .../internal/impl/collect/DataPool.java | 23 +- .../DefaultDependencyCollectionContext.java | 6 +- .../collect/DefaultDependencyCollector.java | 903 +----------------- .../impl/collect/DefaultDependencyCycle.java | 10 +- ...tDependencyGraphTransformationContext.java | 9 +- .../DefaultDependencyResolutionSkipper.java | 290 ------ .../collect/DefaultVersionFilterContext.java | 14 +- .../collect/DependencyCollectorDelegate.java | 96 ++ .../NeverDependencyResolutionSkipper.java | 51 - .../internal/impl/collect/ObjectPool.java | 1 + .../collect/bf/BfDependencyCollector.java | 894 +++++++++++++++++ .../{ => bf}/DependencyProcessingContext.java | 7 +- .../bf/DependencyResolutionSkipper.java | 359 +++++++ .../collect/df/DfDependencyCollector.java | 867 +++++++++++++++++ .../internal/impl/collect/df/NodeStack.java | 79 ++ .../collect/DefaultDependencyCycleTest.java | 6 +- .../BfDependencyCollectorTest.java} | 29 +- .../BfDependencyCollectorUseSkipTest.java} | 14 +- .../DependencyResolutionSkipperTest.java | 57 +- .../collect/df/DfDependencyCollectorTest.java | 649 +++++++++++++ src/site/markdown/configuration.md | 3 +- 24 files changed, 3104 insertions(+), 1349 deletions(-) delete mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DependencyResolutionSkipper.java delete mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyResolutionSkipper.java create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java delete mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/NeverDependencyResolutionSkipper.java create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java rename maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/{ => bf}/DependencyProcessingContext.java (95%) create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper.java create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java create mode 100644 maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/NodeStack.java rename maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/{DefaultDependencyCollectorTest.java => bf/BfDependencyCollectorTest.java} (98%) rename maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/{DefaultDependencyCollectorUseSkipTest.java => bf/BfDependencyCollectorUseSkipTest.java} (96%) rename maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/{ => bf}/DependencyResolutionSkipperTest.java (82%) create mode 100644 maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollectorTest.java diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DependencyResolutionSkipper.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DependencyResolutionSkipper.java deleted file mode 100644 index 649f6d36d..000000000 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/DependencyResolutionSkipper.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.eclipse.aether.impl; - -/* - * 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. - */ - -import org.eclipse.aether.graph.DependencyNode; - -import java.util.List; - -/** - * A skipper that determines whether to skip resolving given node during the dependency collection. - * - * @noimplement This interface is not intended to be implemented by clients. - * @noextend This interface is not intended to be extended by clients. - * @provisional This type is provisional and can be changed, moved or removed without prior notice. - * @since 1.8.0 - */ -public interface DependencyResolutionSkipper -{ - /** - * Check whether the resolution of current node can be skipped before resolving. - * - * @param node Current node - * @param parents All parent nodes of current node - * - * @return {@code true} if the node can be skipped for resolution, {@code false} if resolution required. - */ - boolean skipResolution( DependencyNode node, List parents ); - - /** - * Cache the resolution result when a node is resolved by @See DependencyCollector after resolution. - * - * @param node Current node - * @param parents All parent nodes of current node - */ - void cache( DependencyNode node, List parents ); - - /** - * Print the skip/resolve status report for all nodes. - */ - void report(); - -} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java index 9629dc979..93ef99ec3 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/guice/AetherModule.java @@ -48,6 +48,9 @@ import org.eclipse.aether.internal.impl.checksum.Sha256ChecksumAlgorithmFactory; import org.eclipse.aether.internal.impl.checksum.Sha512ChecksumAlgorithmFactory; import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector; +import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate; +import org.eclipse.aether.internal.impl.collect.bf.BfDependencyCollector; +import org.eclipse.aether.internal.impl.collect.df.DfDependencyCollector; import org.eclipse.aether.internal.impl.synccontext.DefaultSyncContextFactory; import org.eclipse.aether.internal.impl.synccontext.named.NamedLockFactorySelector; import org.eclipse.aether.internal.impl.synccontext.named.SimpleNamedLockFactorySelector; @@ -132,8 +135,14 @@ protected void configure() .to( DefaultRepositorySystem.class ).in( Singleton.class ); bind( ArtifactResolver.class ) // .to( DefaultArtifactResolver.class ).in( Singleton.class ); + bind( DependencyCollector.class ) // .to( DefaultDependencyCollector.class ).in( Singleton.class ); + bind( DependencyCollectorDelegate.class ).annotatedWith( Names.named( BfDependencyCollector.NAME ) ) + .to( BfDependencyCollector.class ).in( Singleton.class ); + bind( DependencyCollectorDelegate.class ).annotatedWith( Names.named( DfDependencyCollector.NAME ) ) + .to( DfDependencyCollector.class ).in( Singleton.class ); + bind( Deployer.class ) // .to( DefaultDeployer.class ).in( Singleton.class ); bind( Installer.class ) // @@ -212,6 +221,19 @@ protected void configure() } + @Provides + @Singleton + Map dependencyCollectorDelegates( + @Named( BfDependencyCollector.NAME ) DependencyCollectorDelegate bf, + @Named( DfDependencyCollector.NAME ) DependencyCollectorDelegate df + ) + { + Map dependencyCollectorDelegates = new HashMap<>(); + dependencyCollectorDelegates.put( BfDependencyCollector.NAME, bf ); + dependencyCollectorDelegates.put( DfDependencyCollector.NAME, df ); + return dependencyCollectorDelegates; + } + @Provides @Singleton Map provideChecksumSources( diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CachingArtifactTypeRegistry.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CachingArtifactTypeRegistry.java index a26023440..1a9a98f5b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CachingArtifactTypeRegistry.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/CachingArtifactTypeRegistry.java @@ -27,9 +27,10 @@ import org.eclipse.aether.artifact.ArtifactTypeRegistry; /** - * A short-lived artifact type registry that caches results from a presumedly slower type registry. + * A short-lived artifact type registry that caches results from a presumably slower type registry. + * Internal helper class for collector implementations. */ -class CachingArtifactTypeRegistry +public class CachingArtifactTypeRegistry implements ArtifactTypeRegistry { diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java index 4a145558d..04ebdf41d 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DataPool.java @@ -48,8 +48,9 @@ import org.eclipse.aether.version.VersionConstraint; /** + * Internal helper class for collector implementations. */ -final class DataPool +public final class DataPool { private static final String ARTIFACT_POOL = DataPool.class.getName() + "$Artifact"; @@ -58,7 +59,7 @@ final class DataPool private static final String DESCRIPTORS = DataPool.class.getName() + "$Descriptors"; - static final ArtifactDescriptorResult NO_DESCRIPTOR = + public static final ArtifactDescriptorResult NO_DESCRIPTOR = new ArtifactDescriptorResult( new ArtifactDescriptorRequest() ); private ObjectPool artifacts; @@ -72,7 +73,7 @@ final class DataPool private final Map> nodes = new HashMap<>( 256 ); @SuppressWarnings( "unchecked" ) - DataPool( RepositorySystemSession session ) + public DataPool( RepositorySystemSession session ) { RepositoryCache cache = session.getCache(); @@ -103,7 +104,7 @@ final class DataPool if ( descriptors == null ) { - descriptors = Collections.synchronizedMap( new WeakHashMap( 256 ) ); + descriptors = Collections.synchronizedMap( new WeakHashMap<>( 256 ) ); if ( cache != null ) { cache.put( session, DESCRIPTORS, descriptors ); @@ -121,12 +122,12 @@ public Dependency intern( Dependency dependency ) return dependencies.intern( dependency ); } - Object toKey( ArtifactDescriptorRequest request ) + public Object toKey( ArtifactDescriptorRequest request ) { return request.getArtifact(); } - ArtifactDescriptorResult getDescriptor( Object key, ArtifactDescriptorRequest request ) + public ArtifactDescriptorResult getDescriptor( Object key, ArtifactDescriptorRequest request ) { Descriptor descriptor = descriptors.get( key ); if ( descriptor != null ) @@ -136,22 +137,22 @@ ArtifactDescriptorResult getDescriptor( Object key, ArtifactDescriptorRequest re return null; } - void putDescriptor( Object key, ArtifactDescriptorResult result ) + public void putDescriptor( Object key, ArtifactDescriptorResult result ) { descriptors.put( key, new GoodDescriptor( result ) ); } - void putDescriptor( Object key, ArtifactDescriptorException e ) + public void putDescriptor( Object key, ArtifactDescriptorException e ) { descriptors.put( key, BadDescriptor.INSTANCE ); } - Object toKey( VersionRangeRequest request ) + public Object toKey( VersionRangeRequest request ) { return new ConstraintKey( request ); } - VersionRangeResult getConstraint( Object key, VersionRangeRequest request ) + public VersionRangeResult getConstraint( Object key, VersionRangeRequest request ) { Constraint constraint = constraints.get( key ); if ( constraint != null ) @@ -161,7 +162,7 @@ VersionRangeResult getConstraint( Object key, VersionRangeRequest request ) return null; } - void putConstraint( Object key, VersionRangeResult result ) + public void putConstraint( Object key, VersionRangeResult result ) { constraints.put( key, new Constraint( result ) ); } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectionContext.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectionContext.java index 3bf4fe1a0..117af8804 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectionContext.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectionContext.java @@ -27,9 +27,9 @@ import org.eclipse.aether.graph.Dependency; /** - * @see DefaultDependencyCollector + * Internal helper class for collector implementations. */ -final class DefaultDependencyCollectionContext +public final class DefaultDependencyCollectionContext implements DependencyCollectionContext { @@ -41,7 +41,7 @@ final class DefaultDependencyCollectionContext private List managedDependencies; - DefaultDependencyCollectionContext( RepositorySystemSession session, Artifact artifact, + public DefaultDependencyCollectionContext( RepositorySystemSession session, Artifact artifact, Dependency dependency, List managedDependencies ) { this.session = session; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector.java index a895bb9bd..f7f6ea265 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollector.java @@ -19,914 +19,79 @@ * under the License. */ -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Queue; - -import static java.util.Objects.requireNonNull; -import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositoryException; +import java.util.HashMap; +import java.util.Map; + import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.RequestTrace; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.ArtifactProperties; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.collection.CollectResult; import org.eclipse.aether.collection.DependencyCollectionException; -import org.eclipse.aether.collection.DependencyGraphTransformer; -import org.eclipse.aether.collection.DependencyManagement; -import org.eclipse.aether.collection.DependencyManager; -import org.eclipse.aether.collection.DependencySelector; -import org.eclipse.aether.collection.DependencyTraverser; -import org.eclipse.aether.collection.VersionFilter; -import org.eclipse.aether.graph.DefaultDependencyNode; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.graph.Exclusion; -import org.eclipse.aether.impl.ArtifactDescriptorReader; import org.eclipse.aether.impl.DependencyCollector; -import org.eclipse.aether.impl.DependencyResolutionSkipper; -import org.eclipse.aether.impl.RemoteRepositoryManager; -import org.eclipse.aether.impl.VersionRangeResolver; -import org.eclipse.aether.repository.ArtifactRepository; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactDescriptorException; -import org.eclipse.aether.resolution.ArtifactDescriptorRequest; -import org.eclipse.aether.resolution.ArtifactDescriptorResult; -import org.eclipse.aether.resolution.VersionRangeRequest; -import org.eclipse.aether.resolution.VersionRangeResolutionException; -import org.eclipse.aether.resolution.VersionRangeResult; +import org.eclipse.aether.internal.impl.collect.bf.BfDependencyCollector; +import org.eclipse.aether.internal.impl.collect.df.DfDependencyCollector; import org.eclipse.aether.spi.locator.Service; import org.eclipse.aether.spi.locator.ServiceLocator; import org.eclipse.aether.util.ConfigUtils; -import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; -import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; -import org.eclipse.aether.version.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static java.util.Objects.requireNonNull; /** + * Default implementation of {@link DependencyCollector} that merely indirect to selected delegate. */ @Singleton @Named public class DefaultDependencyCollector - implements DependencyCollector, Service + implements DependencyCollector, Service { + private static final String CONFIG_PROP_COLLECTOR_IMPL = "aether.collector.impl"; - /** - * The key in the repository session's {@link org.eclipse.aether.RepositorySystemSession#getConfigProperties() - * configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode. - * - * @since 1.8.0 - */ - public static final String CONFIG_PROP_USE_SKIP = "aether.dependencyCollector.useSkip"; + private static final String DEFAULT_COLLECTOR_IMPL = DfDependencyCollector.NAME; + + private final Map delegates; /** - * The default value for {@link #CONFIG_PROP_USE_SKIP}, {@code true}. + * Default ctor for SL. * - * @since 1.8.0 + * @deprecated SL is to be removed. */ - public static final boolean CONFIG_PROP_USE_SKIP_DEFAULT = true; - - private static final String CONFIG_PROP_MAX_EXCEPTIONS = "aether.dependencyCollector.maxExceptions"; - - private static final int CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT = 50; - - private static final String CONFIG_PROP_MAX_CYCLES = "aether.dependencyCollector.maxCycles"; - - private static final int CONFIG_PROP_MAX_CYCLES_DEFAULT = 10; - - private static final Logger LOGGER = LoggerFactory.getLogger( DefaultDependencyCollector.class ); - - private RemoteRepositoryManager remoteRepositoryManager; - - private ArtifactDescriptorReader descriptorReader; - - private VersionRangeResolver versionRangeResolver; - + @Deprecated public DefaultDependencyCollector() { - // enables default constructor + this.delegates = new HashMap<>(); } @Inject - DefaultDependencyCollector( RemoteRepositoryManager remoteRepositoryManager, - ArtifactDescriptorReader artifactDescriptorReader, - VersionRangeResolver versionRangeResolver ) + public DefaultDependencyCollector( Map delegates ) { - setRemoteRepositoryManager( remoteRepositoryManager ); - setArtifactDescriptorReader( artifactDescriptorReader ); - setVersionRangeResolver( versionRangeResolver ); + this.delegates = requireNonNull( delegates ); } + @Override public void initService( ServiceLocator locator ) { - setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) ); - setArtifactDescriptorReader( locator.getService( ArtifactDescriptorReader.class ) ); - setVersionRangeResolver( locator.getService( VersionRangeResolver.class ) ); + BfDependencyCollector bf = new BfDependencyCollector(); + bf.initService( locator ); + DfDependencyCollector df = new DfDependencyCollector(); + df.initService( locator ); + this.delegates.put( BfDependencyCollector.NAME, bf ); + this.delegates.put( DfDependencyCollector.NAME, df ); } - public DefaultDependencyCollector setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager ) - { - this.remoteRepositoryManager = - requireNonNull( remoteRepositoryManager, "remote repository provider cannot be null" ); - return this; - } - - public DefaultDependencyCollector setArtifactDescriptorReader( ArtifactDescriptorReader artifactDescriptorReader ) - { - descriptorReader = requireNonNull( artifactDescriptorReader, "artifact descriptor reader cannot be null" ); - return this; - } - - public DefaultDependencyCollector setVersionRangeResolver( VersionRangeResolver versionRangeResolver ) - { - this.versionRangeResolver = - requireNonNull( versionRangeResolver, "version range resolver cannot be null" ); - return this; - } - - @SuppressWarnings( "checkstyle:methodlength" ) + @Override public CollectResult collectDependencies( RepositorySystemSession session, CollectRequest request ) - throws DependencyCollectionException - { - requireNonNull( session, "session cannot be null" ); - requireNonNull( request, "request cannot be null" ); - session = optimizeSession( session ); - - boolean useSkip = ConfigUtils.getBoolean( - session, CONFIG_PROP_USE_SKIP_DEFAULT, CONFIG_PROP_USE_SKIP - ); - if ( useSkip ) - { - LOGGER.debug( "Collector skip mode enabled" ); - } - - RequestTrace trace = RequestTrace.newChild( request.getTrace(), request ); - - CollectResult result = new CollectResult( request ); - - DependencySelector depSelector = session.getDependencySelector(); - DependencyManager depManager = session.getDependencyManager(); - DependencyTraverser depTraverser = session.getDependencyTraverser(); - VersionFilter verFilter = session.getVersionFilter(); - - Dependency root = request.getRoot(); - List repositories = request.getRepositories(); - List dependencies = request.getDependencies(); - List managedDependencies = request.getManagedDependencies(); - - Map stats = new LinkedHashMap<>(); - long time1 = System.nanoTime(); - - DefaultDependencyNode node; - if ( root != null ) - { - List versions; - VersionRangeResult rangeResult; - try - { - VersionRangeRequest rangeRequest = - new VersionRangeRequest( root.getArtifact(), request.getRepositories(), - request.getRequestContext() ); - rangeRequest.setTrace( trace ); - rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); - versions = filterVersions( root, rangeResult, verFilter, new DefaultVersionFilterContext( session ) ); - } - catch ( VersionRangeResolutionException e ) - { - result.addException( e ); - throw new DependencyCollectionException( result, e.getMessage() ); - } - - Version version = versions.get( versions.size() - 1 ); - root = root.setArtifact( root.getArtifact().setVersion( version.toString() ) ); - - ArtifactDescriptorResult descriptorResult; - try - { - ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); - descriptorRequest.setArtifact( root.getArtifact() ); - descriptorRequest.setRepositories( request.getRepositories() ); - descriptorRequest.setRequestContext( request.getRequestContext() ); - descriptorRequest.setTrace( trace ); - if ( isLackingDescriptor( root.getArtifact() ) ) - { - descriptorResult = new ArtifactDescriptorResult( descriptorRequest ); - } - else - { - descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); - } - } - catch ( ArtifactDescriptorException e ) - { - result.addException( e ); - throw new DependencyCollectionException( result, e.getMessage() ); - } - - root = root.setArtifact( descriptorResult.getArtifact() ); - - if ( !session.isIgnoreArtifactDescriptorRepositories() ) - { - repositories = remoteRepositoryManager.aggregateRepositories( session, repositories, - descriptorResult.getRepositories(), - true ); - } - dependencies = mergeDeps( dependencies, descriptorResult.getDependencies() ); - managedDependencies = mergeDeps( managedDependencies, descriptorResult.getManagedDependencies() ); - - node = new DefaultDependencyNode( root ); - node.setRequestContext( request.getRequestContext() ); - node.setRelocations( descriptorResult.getRelocations() ); - node.setVersionConstraint( rangeResult.getVersionConstraint() ); - node.setVersion( version ); - node.setAliases( descriptorResult.getAliases() ); - node.setRepositories( request.getRepositories() ); - } - else - { - node = new DefaultDependencyNode( request.getRootArtifact() ); - node.setRequestContext( request.getRequestContext() ); - node.setRepositories( request.getRepositories() ); - } - - result.setRoot( node ); - - boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency( root ); - String errorPath = null; - if ( traverse && !dependencies.isEmpty() ) - { - DataPool pool = new DataPool( session ); - - DefaultDependencyCollectionContext context = - new DefaultDependencyCollectionContext( session, request.getRootArtifact(), root, managedDependencies ); - - DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext( session ); - - Args args = - new Args( session, trace, pool, context, versionContext, request, - useSkip ? new DefaultDependencyResolutionSkipper() - : NeverDependencyResolutionSkipper.INSTANCE ); - Results results = new Results( result, session ); - - DependencySelector rootDepSelector = - depSelector != null ? depSelector.deriveChildSelector( context ) : null; - DependencyManager rootDepManager = depManager != null ? depManager.deriveChildManager( context ) : null; - DependencyTraverser rootDepTraverser = - depTraverser != null ? depTraverser.deriveChildTraverser( context ) : null; - VersionFilter rootVerFilter = verFilter != null ? verFilter.deriveChildFilter( context ) : null; - - List parents = Collections.singletonList( node ); - for ( Dependency dependency : dependencies ) - { - args.dependencyProcessingQueue.add( - new DependencyProcessingContext( rootDepSelector, rootDepManager, rootDepTraverser, - rootVerFilter, repositories, managedDependencies, parents, - dependency ) ); - } - - while ( !args.dependencyProcessingQueue.isEmpty() ) - { - processDependency( args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), - false ); - } - - args.skipper.report(); - errorPath = results.errorPath; - } - - long time2 = System.nanoTime(); - - DependencyGraphTransformer transformer = session.getDependencyGraphTransformer(); - if ( transformer != null ) - { - try - { - DefaultDependencyGraphTransformationContext context = - new DefaultDependencyGraphTransformationContext( session ); - context.put( TransformationContextKeys.STATS, stats ); - result.setRoot( transformer.transformGraph( node, context ) ); - } - catch ( RepositoryException e ) - { - result.addException( e ); - } - } - - long time3 = System.nanoTime(); - stats.put( "DefaultDependencyCollector.collectTime", time2 - time1 ); - stats.put( "DefaultDependencyCollector.transformTime", time3 - time2 ); - LOGGER.debug( "Dependency collection stats {}", stats ); - - if ( errorPath != null ) - { - throw new DependencyCollectionException( result, "Failed to collect dependencies at " + errorPath ); - } - if ( !result.getExceptions().isEmpty() ) - { - throw new DependencyCollectionException( result ); - } - - return result; - } - - private static RepositorySystemSession optimizeSession( RepositorySystemSession session ) - { - DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession( session ); - optimized.setArtifactTypeRegistry( CachingArtifactTypeRegistry.newInstance( session ) ); - return optimized; - } - - private List mergeDeps( List dominant, List recessive ) - { - List result; - if ( dominant == null || dominant.isEmpty() ) - { - result = recessive; - } - else if ( recessive == null || recessive.isEmpty() ) - { - result = dominant; - } - else - { - int initialCapacity = dominant.size() + recessive.size(); - result = new ArrayList<>( initialCapacity ); - Collection ids = new HashSet<>( initialCapacity, 1.0f ); - for ( Dependency dependency : dominant ) - { - ids.add( getId( dependency.getArtifact() ) ); - result.add( dependency ); - } - for ( Dependency dependency : recessive ) - { - if ( !ids.contains( getId( dependency.getArtifact() ) ) ) - { - result.add( dependency ); - } - } - } - return result; - } - - private static String getId( Artifact a ) - { - return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); - } - - @SuppressWarnings( "checkstyle:parameternumber" ) - private void processDependency( Args args, Results results, DependencyProcessingContext context, - List relocations, boolean disableVersionManagement ) - { - - if ( context.depSelector != null && !context.depSelector.selectDependency( context.dependency ) ) - { - return; - } - - PremanagedDependency preManaged = - PremanagedDependency.create( context.depManager, context.dependency, disableVersionManagement, - args.premanagedState ); - Dependency dependency = preManaged.managedDependency; - - boolean noDescriptor = isLackingDescriptor( dependency.getArtifact() ); - - boolean traverse = - !noDescriptor && ( context.depTraverser == null || context.depTraverser.traverseDependency( - dependency ) ); - - List versions; - VersionRangeResult rangeResult; - try - { - VersionRangeRequest rangeRequest = createVersionRangeRequest( args, context.repositories, dependency ); - - rangeResult = cachedResolveRangeResult( rangeRequest, args.pool, args.session ); - - versions = filterVersions( dependency, rangeResult, context.verFilter, args.versionContext ); - } - catch ( VersionRangeResolutionException e ) - { - results.addException( dependency, e, context.parents ); - return; - } - - //Resolve newer version first to maximize benefits of skipper - Collections.reverse( versions ); - for ( Version version : versions ) - { - Artifact originalArtifact = dependency.getArtifact().setVersion( version.toString() ); - Dependency d = dependency.setArtifact( originalArtifact ); - - ArtifactDescriptorRequest descriptorRequest = - createArtifactDescriptorRequest( args, context.repositories, d ); - - final ArtifactDescriptorResult descriptorResult = - noDescriptor - ? new ArtifactDescriptorResult( descriptorRequest ) - : resolveCachedArtifactDescriptor( args.pool, descriptorRequest, args.session, - context.withDependency( d ), results ); - - if ( descriptorResult != null ) - { - d = d.setArtifact( descriptorResult.getArtifact() ); - - int cycleEntry = find( context.parents, d.getArtifact() ); - if ( cycleEntry >= 0 ) - { - results.addCycle( context.parents, cycleEntry, d ); - DependencyNode cycleNode = context.parents.get( cycleEntry ); - if ( cycleNode.getDependency() != null ) - { - DefaultDependencyNode child = - createDependencyNode( relocations, preManaged, rangeResult, version, d, - descriptorResult, cycleNode ); - context.getParent().getChildren().add( child ); - continue; - } - } - - if ( !descriptorResult.getRelocations().isEmpty() ) - { - boolean disableVersionManagementSubsequently = - originalArtifact.getGroupId().equals( d.getArtifact().getGroupId() ) - && originalArtifact.getArtifactId().equals( d.getArtifact().getArtifactId() ); - - processDependency( args, results, context.withDependency( d ), descriptorResult.getRelocations(), - disableVersionManagementSubsequently ); - return; - } - else - { - d = args.pool.intern( d.setArtifact( args.pool.intern( d.getArtifact() ) ) ); - - List repos = - getRemoteRepositories( rangeResult.getRepository( version ), context.repositories ); - - DefaultDependencyNode child = - createDependencyNode( relocations, preManaged, rangeResult, version, d, - descriptorResult.getAliases(), repos, args.request.getRequestContext() ); - - context.getParent().getChildren().add( child ); - - boolean recurse = traverse && !descriptorResult.getDependencies().isEmpty(); - if ( recurse ) - { - doRecurse( args, context.withDependency( d ), descriptorResult, child ); - } - } - } - else - { - List repos = - getRemoteRepositories( rangeResult.getRepository( version ), context.repositories ); - DefaultDependencyNode child = - createDependencyNode( relocations, preManaged, rangeResult, version, d, null, repos, - args.request.getRequestContext() ); - context.getParent().getChildren().add( child ); - } - } - } - - @SuppressWarnings( "checkstyle:parameternumber" ) - private void doRecurse( Args args, DependencyProcessingContext parentContext, - ArtifactDescriptorResult descriptorResult, DefaultDependencyNode child ) + throws DependencyCollectionException { - DefaultDependencyCollectionContext context = args.collectionContext; - context.set( parentContext.dependency, descriptorResult.getManagedDependencies() ); - - DependencySelector childSelector = - parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector( context ) : null; - DependencyManager childManager = - parentContext.depManager != null ? parentContext.depManager.deriveChildManager( context ) : null; - DependencyTraverser childTraverser = - parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser( context ) : null; - VersionFilter childFilter = - parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter( context ) : null; - - final List childRepos = - args.ignoreRepos - ? parentContext.repositories - : remoteRepositoryManager.aggregateRepositories( args.session, parentContext.repositories, - descriptorResult.getRepositories(), true ); - - Object key = - args.pool.toKey( parentContext.dependency.getArtifact(), childRepos, childSelector, childManager, - childTraverser, childFilter ); - - List children = args.pool.getChildren( key ); - if ( children == null ) - { - boolean skipResolution = args.skipper.skipResolution( child, parentContext.parents ); - if ( !skipResolution ) - { - List parents = new ArrayList<>( parentContext.parents.size() + 1 ); - parents.addAll( parentContext.parents ); - parents.add( child ); - for ( Dependency dependency : descriptorResult.getDependencies() ) - { - args.dependencyProcessingQueue.add( - new DependencyProcessingContext( childSelector, childManager, childTraverser, childFilter, - childRepos, descriptorResult.getManagedDependencies(), parents, dependency ) ); - } - args.pool.putChildren( key, child.getChildren() ); - args.skipper.cache( child, parents ); - } - } - else - { - child.setChildren( children ); - } - } - - private ArtifactDescriptorResult resolveCachedArtifactDescriptor( DataPool pool, - ArtifactDescriptorRequest descriptorRequest, - RepositorySystemSession session, - DependencyProcessingContext context, - Results results ) - { - Object key = pool.toKey( descriptorRequest ); - ArtifactDescriptorResult descriptorResult = pool.getDescriptor( key, descriptorRequest ); - if ( descriptorResult == null ) - { - try - { - descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); - pool.putDescriptor( key, descriptorResult ); - } - catch ( ArtifactDescriptorException e ) - { - results.addException( context.dependency, e, context.parents ); - pool.putDescriptor( key, e ); - return null; - } - - } - else if ( descriptorResult == DataPool.NO_DESCRIPTOR ) + String delegateName = ConfigUtils.getString( session, DEFAULT_COLLECTOR_IMPL, CONFIG_PROP_COLLECTOR_IMPL ); + DependencyCollectorDelegate delegate = delegates.get( delegateName ); + if ( delegate == null ) { - return null; + throw new IllegalArgumentException( "Unknown collector impl: '" + delegateName + + "', known implementations are " + delegates.keySet() ); } - - return descriptorResult; - } - - @SuppressWarnings( "checkstyle:parameternumber" ) - private static DefaultDependencyNode createDependencyNode( List relocations, - PremanagedDependency preManaged, - VersionRangeResult rangeResult, Version version, - Dependency d, Collection aliases, - List repos, String requestContext ) - { - DefaultDependencyNode child = new DefaultDependencyNode( d ); - preManaged.applyTo( child ); - child.setRelocations( relocations ); - child.setVersionConstraint( rangeResult.getVersionConstraint() ); - child.setVersion( version ); - child.setAliases( aliases ); - child.setRepositories( repos ); - child.setRequestContext( requestContext ); - return child; - } - - private static DefaultDependencyNode createDependencyNode( List relocations, - PremanagedDependency preManaged, - VersionRangeResult rangeResult, Version version, - Dependency d, ArtifactDescriptorResult descriptorResult, - DependencyNode cycleNode ) - { - DefaultDependencyNode child = - createDependencyNode( relocations, preManaged, rangeResult, version, d, descriptorResult.getAliases(), - cycleNode.getRepositories(), cycleNode.getRequestContext() ); - child.setChildren( cycleNode.getChildren() ); - return child; + return delegate.collectDependencies( session, request ); } - - private static ArtifactDescriptorRequest createArtifactDescriptorRequest( Args args, - List repositories, - Dependency d ) - { - ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); - descriptorRequest.setArtifact( d.getArtifact() ); - descriptorRequest.setRepositories( repositories ); - descriptorRequest.setRequestContext( args.request.getRequestContext() ); - descriptorRequest.setTrace( args.trace ); - return descriptorRequest; - } - - private static VersionRangeRequest createVersionRangeRequest( Args args, List repositories, - Dependency dependency ) - { - VersionRangeRequest rangeRequest = new VersionRangeRequest(); - rangeRequest.setArtifact( dependency.getArtifact() ); - rangeRequest.setRepositories( repositories ); - rangeRequest.setRequestContext( args.request.getRequestContext() ); - rangeRequest.setTrace( args.trace ); - return rangeRequest; - } - - private VersionRangeResult cachedResolveRangeResult( VersionRangeRequest rangeRequest, DataPool pool, - RepositorySystemSession session ) - throws VersionRangeResolutionException - { - Object key = pool.toKey( rangeRequest ); - VersionRangeResult rangeResult = pool.getConstraint( key, rangeRequest ); - if ( rangeResult == null ) - { - rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); - pool.putConstraint( key, rangeResult ); - } - return rangeResult; - } - - private static boolean isLackingDescriptor( Artifact artifact ) - { - return artifact.getProperty( ArtifactProperties.LOCAL_PATH, null ) != null; - } - - private static List getRemoteRepositories( ArtifactRepository repository, - List repositories ) - { - if ( repository instanceof RemoteRepository ) - { - return Collections.singletonList( (RemoteRepository) repository ); - } - if ( repository != null ) - { - return Collections.emptyList(); - } - return repositories; - } - - private static List filterVersions( Dependency dependency, VersionRangeResult rangeResult, - VersionFilter verFilter, - DefaultVersionFilterContext verContext ) - throws VersionRangeResolutionException - { - if ( rangeResult.getVersions().isEmpty() ) - { - throw new VersionRangeResolutionException( rangeResult, - "No versions available for " + dependency.getArtifact() - + " within specified range" ); - } - - List versions; - if ( verFilter != null && rangeResult.getVersionConstraint().getRange() != null ) - { - verContext.set( dependency, rangeResult ); - try - { - verFilter.filterVersions( verContext ); - } - catch ( RepositoryException e ) - { - throw new VersionRangeResolutionException( rangeResult, - "Failed to filter versions for " + dependency.getArtifact() - + ": " + e.getMessage(), e ); - } - versions = verContext.get(); - if ( versions.isEmpty() ) - { - throw new VersionRangeResolutionException( rangeResult, - "No acceptable versions for " + dependency.getArtifact() - + ": " + rangeResult.getVersions() ); - } - } - else - { - versions = rangeResult.getVersions(); - } - return versions; - } - - static class Args - { - - final RepositorySystemSession session; - - final boolean ignoreRepos; - - final boolean premanagedState; - - final RequestTrace trace; - - final DataPool pool; - - final Queue dependencyProcessingQueue = new ArrayDeque<>( 128 ); - - final DefaultDependencyCollectionContext collectionContext; - - final DefaultVersionFilterContext versionContext; - - final CollectRequest request; - - final DependencyResolutionSkipper skipper; - - Args( RepositorySystemSession session, RequestTrace trace, DataPool pool, - DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext, - CollectRequest request, DependencyResolutionSkipper skipper ) - { - this.session = session; - this.request = request; - this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); - this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE ); - this.trace = trace; - this.pool = pool; - this.collectionContext = collectionContext; - this.versionContext = versionContext; - this.skipper = skipper; - } - - } - - static class Results - { - - private final CollectResult result; - - final int maxExceptions; - - final int maxCycles; - - String errorPath; - - Results( CollectResult result, RepositorySystemSession session ) - { - this.result = result; - - maxExceptions = - ConfigUtils.getInteger( session, CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT, CONFIG_PROP_MAX_EXCEPTIONS ); - - maxCycles = ConfigUtils.getInteger( session, CONFIG_PROP_MAX_CYCLES_DEFAULT, CONFIG_PROP_MAX_CYCLES ); - } - - public void addException( Dependency dependency, Exception e, List nodes ) - { - if ( maxExceptions < 0 || result.getExceptions().size() < maxExceptions ) - { - result.addException( e ); - if ( errorPath == null ) - { - StringBuilder buffer = new StringBuilder( 256 ); - for ( DependencyNode node : nodes ) - { - if ( buffer.length() > 0 ) - { - buffer.append( " -> " ); - } - Dependency dep = node.getDependency(); - if ( dep != null ) - { - buffer.append( dep.getArtifact() ); - } - } - if ( buffer.length() > 0 ) - { - buffer.append( " -> " ); - } - buffer.append( dependency.getArtifact() ); - errorPath = buffer.toString(); - } - } - } - - public void addCycle( List nodes, int cycleEntry, Dependency dependency ) - { - if ( maxCycles < 0 || result.getCycles().size() < maxCycles ) - { - result.addCycle( new DefaultDependencyCycle( nodes, cycleEntry, dependency ) ); - } - } - - } - - static class PremanagedDependency - { - - final String premanagedVersion; - - final String premanagedScope; - - final Boolean premanagedOptional; - - /** - * @since 1.1.0 - */ - final Collection premanagedExclusions; - - /** - * @since 1.1.0 - */ - final Map premanagedProperties; - - final int managedBits; - - final Dependency managedDependency; - - final boolean premanagedState; - - @SuppressWarnings( "checkstyle:parameternumber" ) - PremanagedDependency( String premanagedVersion, String premanagedScope, Boolean premanagedOptional, - Collection premanagedExclusions, Map premanagedProperties, - int managedBits, Dependency managedDependency, boolean premanagedState ) - { - this.premanagedVersion = premanagedVersion; - this.premanagedScope = premanagedScope; - this.premanagedOptional = premanagedOptional; - this.premanagedExclusions = - premanagedExclusions != null - ? Collections.unmodifiableCollection( new ArrayList<>( premanagedExclusions ) ) - : null; - - this.premanagedProperties = - premanagedProperties != null - ? Collections.unmodifiableMap( new HashMap<>( premanagedProperties ) ) - : null; - - this.managedBits = managedBits; - this.managedDependency = managedDependency; - this.premanagedState = premanagedState; - } - - static PremanagedDependency create( DependencyManager depManager, Dependency dependency, - boolean disableVersionManagement, boolean premanagedState ) - { - DependencyManagement depMngt = depManager != null ? depManager.manageDependency( dependency ) : null; - - int managedBits = 0; - String premanagedVersion = null; - String premanagedScope = null; - Boolean premanagedOptional = null; - Collection premanagedExclusions = null; - Map premanagedProperties = null; - - if ( depMngt != null ) - { - if ( depMngt.getVersion() != null && !disableVersionManagement ) - { - Artifact artifact = dependency.getArtifact(); - premanagedVersion = artifact.getVersion(); - dependency = dependency.setArtifact( artifact.setVersion( depMngt.getVersion() ) ); - managedBits |= DependencyNode.MANAGED_VERSION; - } - if ( depMngt.getProperties() != null ) - { - Artifact artifact = dependency.getArtifact(); - premanagedProperties = artifact.getProperties(); - dependency = dependency.setArtifact( artifact.setProperties( depMngt.getProperties() ) ); - managedBits |= DependencyNode.MANAGED_PROPERTIES; - } - if ( depMngt.getScope() != null ) - { - premanagedScope = dependency.getScope(); - dependency = dependency.setScope( depMngt.getScope() ); - managedBits |= DependencyNode.MANAGED_SCOPE; - } - if ( depMngt.getOptional() != null ) - { - premanagedOptional = dependency.isOptional(); - dependency = dependency.setOptional( depMngt.getOptional() ); - managedBits |= DependencyNode.MANAGED_OPTIONAL; - } - if ( depMngt.getExclusions() != null ) - { - premanagedExclusions = dependency.getExclusions(); - dependency = dependency.setExclusions( depMngt.getExclusions() ); - managedBits |= DependencyNode.MANAGED_EXCLUSIONS; - } - } - return new PremanagedDependency( premanagedVersion, premanagedScope, premanagedOptional, - premanagedExclusions, premanagedProperties, managedBits, dependency, - premanagedState ); - - } - - public void applyTo( DefaultDependencyNode child ) - { - child.setManagedBits( managedBits ); - if ( premanagedState ) - { - child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_VERSION, premanagedVersion ); - child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_SCOPE, premanagedScope ); - child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_OPTIONAL, premanagedOptional ); - child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_EXCLUSIONS, premanagedExclusions ); - child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_PROPERTIES, premanagedProperties ); - } - } - - } - } diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java index dd1565c9c..d417b70e2 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycle.java @@ -30,17 +30,17 @@ import org.eclipse.aether.util.artifact.ArtifactIdUtils; /** - * @see DefaultDependencyCollector + * Default implementation of {@link DependencyCycle}. + * Internal helper class for collector implementations. */ -final class DefaultDependencyCycle +public final class DefaultDependencyCycle implements DependencyCycle { - private final List dependencies; private final int cycleEntry; - DefaultDependencyCycle( List nodes, int cycleEntry, Dependency dependency ) + public DefaultDependencyCycle( List nodes, int cycleEntry, Dependency dependency ) { // skip root node unless it actually has a dependency or is considered the cycle entry (due to its label) int offset = ( cycleEntry > 0 && nodes.get( 0 ).getDependency() == null ) ? 1 : 0; @@ -60,11 +60,13 @@ final class DefaultDependencyCycle this.cycleEntry = cycleEntry; } + @Override public List getPrecedingDependencies() { return dependencies.subList( 0, cycleEntry ); } + @Override public List getCyclicDependencies() { return dependencies.subList( cycleEntry, dependencies.size() ); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyGraphTransformationContext.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyGraphTransformationContext.java index 41c012699..721eab5e2 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyGraphTransformationContext.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyGraphTransformationContext.java @@ -27,8 +27,10 @@ import org.eclipse.aether.collection.DependencyGraphTransformationContext; /** + * Default implementation of {@link DependencyGraphTransformationContext}. + * Internal helper class for collector implementations. */ -class DefaultDependencyGraphTransformationContext +public class DefaultDependencyGraphTransformationContext implements DependencyGraphTransformationContext { @@ -36,22 +38,25 @@ class DefaultDependencyGraphTransformationContext private final Map map; - DefaultDependencyGraphTransformationContext( RepositorySystemSession session ) + public DefaultDependencyGraphTransformationContext( RepositorySystemSession session ) { this.session = session; this.map = new HashMap<>(); } + @Override public RepositorySystemSession getSession() { return session; } + @Override public Object get( Object key ) { return map.get( requireNonNull( key, "key cannot be null" ) ); } + @Override public Object put( Object key, Object value ) { requireNonNull( key, "key cannot be null" ); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyResolutionSkipper.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyResolutionSkipper.java deleted file mode 100644 index 408b0e169..000000000 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyResolutionSkipper.java +++ /dev/null @@ -1,290 +0,0 @@ -package org.eclipse.aether.internal.impl.collect; - -/* - * 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. - */ - -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.impl.DependencyResolutionSkipper; -import org.eclipse.aether.util.artifact.ArtifactIdUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -final class DefaultDependencyResolutionSkipper implements DependencyResolutionSkipper -{ - private static final Logger LOGGER = LoggerFactory.getLogger( DefaultDependencyResolutionSkipper.class ); - - private Map results = new LinkedHashMap<>( 256 ); - private CacheManager cacheManager = new CacheManager(); - private CoordinateManager coordinateManager = new CoordinateManager(); - - DefaultDependencyResolutionSkipper() - { - // enables default constructor - } - - @Override - public boolean skipResolution( DependencyNode node, List parents ) - { - DependencyResolutionResult result = new DependencyResolutionResult( node ); - results.put( node, result ); - - int depth = parents.size() + 1; - coordinateManager.createCoordinate( node, depth ); - - if ( cacheManager.isVersionConflict( node ) ) - { - /* - * Skip resolving version conflict losers (omitted for conflict) - */ - result.skippedAsVersionConflict = true; - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( "Skipped resolving node: {} as version conflict", - ArtifactIdUtils.toId( node.getArtifact() ) ); - } - } - else if ( cacheManager.isDuplicate( node ) ) - { - if ( coordinateManager.isLeftmost( node, parents ) ) - { - /* - * Force resolving the node to retain conflict paths when its coordinate is more left than last resolved - * This is because Maven picks the widest scope present among conflicting dependencies - */ - result.forceResolution = true; - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( "Force resolving node: {} for scope selection", - ArtifactIdUtils.toId( node.getArtifact() ) ); - } - } - else - { - /* - * Skip resolving as duplicate (depth deeper, omitted for duplicate) - * No need to compare depth as the depth of winner for given artifact is always shallower - */ - result.skippedAsDuplicate = true; - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( "Skipped resolving node: {} as duplicate", - ArtifactIdUtils.toId( node.getArtifact() ) ); - } - } - } - else - { - result.resolve = true; - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( "Resolving node: {}", - ArtifactIdUtils.toId( node.getArtifact() ) ); - } - } - - if ( result.toResolve() ) - { - coordinateManager.updateLeftmost( node ); - return false; - } - - return true; - } - - @Override - public void cache( DependencyNode node, List parents ) - { - boolean parentForceResolution = parents.stream() - .anyMatch( n -> results.containsKey( n ) && results.get( n ).forceResolution ); - if ( parentForceResolution ) - { - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( - "Won't cache as node: {} inherits from a force-resolved node and will be omitted for duplicate", - ArtifactIdUtils.toId( node.getArtifact() ) ); - } - } - else - { - cacheManager.cacheWinner( node ); - } - } - - @Override - public void report() - { - if ( LOGGER.isTraceEnabled() ) - { - LOGGER.trace( "Skipped {} nodes as duplicate", - results.entrySet().stream().filter( n -> n.getValue().skippedAsDuplicate ).count() ); - LOGGER.trace( "Skipped {} nodes as having version conflict", - results.entrySet().stream().filter( n -> n.getValue().skippedAsVersionConflict ).count() ); - LOGGER.trace( "Resolved {} nodes", - results.entrySet().stream().filter( n -> n.getValue().resolve ).count() ); - LOGGER.trace( "Forced resolving {} nodes for scope selection", - results.entrySet().stream().filter( n -> n.getValue().forceResolution ).count() ); - } - } - - public Map getResults() - { - return results; - } - - static final class DependencyResolutionResult - { - DependencyNode current; - boolean skippedAsVersionConflict; //omitted for conflict - boolean skippedAsDuplicate; //omitted for duplicate, depth is deeper - boolean resolve; //node to resolve (winner node) - boolean forceResolution; //force resolving (duplicate node) for scope selection - - DependencyResolutionResult( DependencyNode current ) - { - this.current = current; - } - - boolean toResolve() - { - return resolve || forceResolution; - } - } - - static final class CacheManager - { - - /** - * artifact -> node - */ - private final Map winners = new HashMap<>( 256 ); - - - /** - * versionLessId -> Artifact, only cache winners - */ - private final Map winnerGAs = new HashMap<>( 256 ); - - boolean isVersionConflict( DependencyNode node ) - { - String ga = ArtifactIdUtils.toVersionlessId( node.getArtifact() ); - if ( winnerGAs.containsKey( ga ) ) - { - Artifact result = winnerGAs.get( ga ); - return !node.getArtifact().getVersion().equals( result.getVersion() ); - } - - return false; - } - - void cacheWinner( DependencyNode node ) - { - winners.put( node.getArtifact(), node ); - winnerGAs.put( ArtifactIdUtils.toVersionlessId( node.getArtifact() ), node.getArtifact() ); - } - - boolean isDuplicate( DependencyNode node ) - { - return winners.containsKey( node.getArtifact() ); - } - - } - - - static final class CoordinateManager - { - private final Map sequenceGen = new HashMap<>( 256 ); - - /** - * Dependency node -> Coordinate - */ - private final Map coordinateMap = new HashMap<>( 256 ); - - /** - * Leftmost coordinate of given artifact - */ - private final Map leftmostCoordinates = new HashMap<>( 256 ); - - - Coordinate getCoordinate( DependencyNode node ) - { - return coordinateMap.get( node ); - } - - Coordinate createCoordinate( DependencyNode node, int depth ) - { - int seq = sequenceGen.computeIfAbsent( depth, k -> new AtomicInteger() ).incrementAndGet(); - Coordinate coordinate = new Coordinate( depth, seq ); - coordinateMap.put( node, coordinate ); - return coordinate; - } - - void updateLeftmost( DependencyNode current ) - { - leftmostCoordinates.put( current.getArtifact(), getCoordinate( current ) ); - } - - boolean isLeftmost( DependencyNode node, List parents ) - { - Coordinate leftmost = leftmostCoordinates.get( node.getArtifact() ); - if ( leftmost != null && leftmost.depth <= parents.size() ) - { - DependencyNode sameLevelNode = parents.get( leftmost.depth - 1 ); - if ( getCoordinate( sameLevelNode ).sequence < leftmost.sequence ) - { - return true; - } - } - - return false; - } - } - - static final class Coordinate - { - int depth; - int sequence; - - Coordinate( int depth, int sequence ) - { - this.depth = depth; - this.sequence = sequence; - } - - @Override - public String toString() - { - return "{" - + "depth=" - + depth - + ", sequence=" - + sequence - + '}'; - } - } - - -} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java index bfea0626e..97c16c63b 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DefaultVersionFilterContext.java @@ -34,9 +34,10 @@ import org.eclipse.aether.version.VersionConstraint; /** - * @see DefaultDependencyCollector + * Default implementation of {@link VersionFilter.VersionFilterContext}. + * Internal helper class for collector implementations. */ -final class DefaultVersionFilterContext +public final class DefaultVersionFilterContext implements VersionFilter.VersionFilterContext { private final RepositorySystemSession session; @@ -47,7 +48,7 @@ final class DefaultVersionFilterContext private List versions; - DefaultVersionFilterContext( RepositorySystemSession session ) + public DefaultVersionFilterContext( RepositorySystemSession session ) { this.session = session; } @@ -64,36 +65,43 @@ public List get() return new ArrayList<>( versions ); } + @Override public RepositorySystemSession getSession() { return session; } + @Override public Dependency getDependency() { return dependency; } + @Override public VersionConstraint getVersionConstraint() { return result.getVersionConstraint(); } + @Override public int getCount() { return versions.size(); } + @Override public ArtifactRepository getRepository( Version version ) { return result.getRepository( version ); } + @Override public List getRepositories() { return Collections.unmodifiableList( result.getRequest().getRepositories() ); } + @Override public Iterator iterator() { return versions.iterator(); diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java new file mode 100644 index 000000000..c7c2c985e --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java @@ -0,0 +1,96 @@ +package org.eclipse.aether.internal.impl.collect; + +/* + * 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. + */ + +import org.eclipse.aether.impl.ArtifactDescriptorReader; +import org.eclipse.aether.impl.DependencyCollector; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.impl.VersionRangeResolver; +import org.eclipse.aether.spi.locator.ServiceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.Objects.requireNonNull; + +/** + * Helper class for delegate implementations, they MUST subclass this class. + * + * @since 1.8.0 + */ +public abstract class DependencyCollectorDelegate implements DependencyCollector +{ + protected static final String CONFIG_PROP_MAX_EXCEPTIONS = "aether.dependencyCollector.maxExceptions"; + + protected static final int CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT = 50; + + protected static final String CONFIG_PROP_MAX_CYCLES = "aether.dependencyCollector.maxCycles"; + + protected static final int CONFIG_PROP_MAX_CYCLES_DEFAULT = 10; + + protected final Logger logger = LoggerFactory.getLogger( getClass() ); + + protected RemoteRepositoryManager remoteRepositoryManager; + + protected ArtifactDescriptorReader descriptorReader; + + protected VersionRangeResolver versionRangeResolver; + + public DependencyCollectorDelegate() + { + // enables default constructor + } + + protected DependencyCollectorDelegate( RemoteRepositoryManager remoteRepositoryManager, + ArtifactDescriptorReader artifactDescriptorReader, + VersionRangeResolver versionRangeResolver ) + { + setRemoteRepositoryManager( remoteRepositoryManager ); + setArtifactDescriptorReader( artifactDescriptorReader ); + setVersionRangeResolver( versionRangeResolver ); + } + + public void initService( ServiceLocator locator ) + { + setRemoteRepositoryManager( locator.getService( RemoteRepositoryManager.class ) ); + setArtifactDescriptorReader( locator.getService( ArtifactDescriptorReader.class ) ); + setVersionRangeResolver( locator.getService( VersionRangeResolver.class ) ); + } + + public DependencyCollector setRemoteRepositoryManager( RemoteRepositoryManager remoteRepositoryManager ) + { + this.remoteRepositoryManager = + requireNonNull( remoteRepositoryManager, "remote repository manager cannot be null" ); + return this; + } + + public DependencyCollector setArtifactDescriptorReader( ArtifactDescriptorReader artifactDescriptorReader ) + { + descriptorReader = requireNonNull( artifactDescriptorReader, "artifact descriptor reader cannot be null" ); + return this; + } + + public DependencyCollector setVersionRangeResolver( VersionRangeResolver versionRangeResolver ) + { + this.versionRangeResolver = + requireNonNull( versionRangeResolver, "version range resolver cannot be null" ); + return this; + } + +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/NeverDependencyResolutionSkipper.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/NeverDependencyResolutionSkipper.java deleted file mode 100644 index acd6636fb..000000000 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/NeverDependencyResolutionSkipper.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.eclipse.aether.internal.impl.collect; - -/* - * 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. - */ - -import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.impl.DependencyResolutionSkipper; - -import java.util.List; - -/** - * Skipper for Non-skip approach. - */ -final class NeverDependencyResolutionSkipper implements DependencyResolutionSkipper -{ - static final DependencyResolutionSkipper INSTANCE = new NeverDependencyResolutionSkipper(); - - @Override - public boolean skipResolution( DependencyNode node, List parents ) - { - return false; - } - - @Override - public void cache( DependencyNode node, List parents ) - { - - } - - @Override - public void report() - { - - } -} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/ObjectPool.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/ObjectPool.java index c4117eb02..0d5f37997 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/ObjectPool.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/ObjectPool.java @@ -27,6 +27,7 @@ /** * Pool of immutable object instances, used to avoid excessive memory consumption of (dirty) dependency graph which * tends to have many duplicate artifacts/dependencies. + * Internal helper class for collector implementations. */ class ObjectPool { diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java new file mode 100644 index 000000000..4beca7990 --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java @@ -0,0 +1,894 @@ +package org.eclipse.aether.internal.impl.collect.bf; + +/* + * 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. + */ + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositoryException; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.RequestTrace; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.ArtifactProperties; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.CollectResult; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.collection.DependencyManagement; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.collection.DependencyTraverser; +import org.eclipse.aether.collection.VersionFilter; +import org.eclipse.aether.graph.DefaultDependencyNode; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.graph.Exclusion; +import org.eclipse.aether.impl.ArtifactDescriptorReader; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.impl.VersionRangeResolver; +import org.eclipse.aether.internal.impl.collect.CachingArtifactTypeRegistry; +import org.eclipse.aether.internal.impl.collect.DataPool; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyGraphTransformationContext; +import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext; +import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate; +import org.eclipse.aether.repository.ArtifactRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactDescriptorException; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.VersionRangeRequest; +import org.eclipse.aether.resolution.VersionRangeResolutionException; +import org.eclipse.aether.resolution.VersionRangeResult; +import org.eclipse.aether.spi.locator.Service; +import org.eclipse.aether.util.ConfigUtils; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; +import org.eclipse.aether.version.Version; + +import static java.util.Objects.requireNonNull; +import static org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle.find; + +/** + * Breadth-first {@link org.eclipse.aether.impl.DependencyCollector} + * + * @since 1.8.0 + */ +@Singleton +@Named( BfDependencyCollector.NAME ) +public class BfDependencyCollector + extends DependencyCollectorDelegate implements Service +{ + public static final String NAME = "bf"; + + /** + * The key in the repository session's {@link RepositorySystemSession#getConfigProperties() + * configuration properties} used to store a {@link Boolean} flag controlling the resolver's skip mode. + * + * @since 1.8.0 + */ + public static final String CONFIG_PROP_SKIPPER = "aether.dependencyCollector.bf.skipper"; + + /** + * The default value for {@link #CONFIG_PROP_SKIPPER}, {@code true}. + * + * @since 1.8.0 + */ + public static final boolean CONFIG_PROP_SKIPPER_DEFAULT = true; + + public BfDependencyCollector() + { + // enables default constructor + } + + @Inject + BfDependencyCollector( RemoteRepositoryManager remoteRepositoryManager, + ArtifactDescriptorReader artifactDescriptorReader, + VersionRangeResolver versionRangeResolver ) + { + super( remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver ); + } + + @SuppressWarnings( "checkstyle:methodlength" ) + public CollectResult collectDependencies( RepositorySystemSession session, CollectRequest request ) + throws DependencyCollectionException + { + requireNonNull( session, "session cannot be null" ); + requireNonNull( request, "request cannot be null" ); + session = optimizeSession( session ); + + boolean useSkip = ConfigUtils.getBoolean( + session, CONFIG_PROP_SKIPPER_DEFAULT, CONFIG_PROP_SKIPPER + ); + if ( useSkip ) + { + logger.debug( "Collector skip mode enabled" ); + } + + RequestTrace trace = RequestTrace.newChild( request.getTrace(), request ); + + CollectResult result = new CollectResult( request ); + + DependencySelector depSelector = session.getDependencySelector(); + DependencyManager depManager = session.getDependencyManager(); + DependencyTraverser depTraverser = session.getDependencyTraverser(); + VersionFilter verFilter = session.getVersionFilter(); + + Dependency root = request.getRoot(); + List repositories = request.getRepositories(); + List dependencies = request.getDependencies(); + List managedDependencies = request.getManagedDependencies(); + + Map stats = new LinkedHashMap<>(); + long time1 = System.nanoTime(); + + DefaultDependencyNode node; + if ( root != null ) + { + List versions; + VersionRangeResult rangeResult; + try + { + VersionRangeRequest rangeRequest = + new VersionRangeRequest( root.getArtifact(), request.getRepositories(), + request.getRequestContext() ); + rangeRequest.setTrace( trace ); + rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); + versions = filterVersions( root, rangeResult, verFilter, new DefaultVersionFilterContext( session ) ); + } + catch ( VersionRangeResolutionException e ) + { + result.addException( e ); + throw new DependencyCollectionException( result, e.getMessage() ); + } + + Version version = versions.get( versions.size() - 1 ); + root = root.setArtifact( root.getArtifact().setVersion( version.toString() ) ); + + ArtifactDescriptorResult descriptorResult; + try + { + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact( root.getArtifact() ); + descriptorRequest.setRepositories( request.getRepositories() ); + descriptorRequest.setRequestContext( request.getRequestContext() ); + descriptorRequest.setTrace( trace ); + if ( isLackingDescriptor( root.getArtifact() ) ) + { + descriptorResult = new ArtifactDescriptorResult( descriptorRequest ); + } + else + { + descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); + } + } + catch ( ArtifactDescriptorException e ) + { + result.addException( e ); + throw new DependencyCollectionException( result, e.getMessage() ); + } + + root = root.setArtifact( descriptorResult.getArtifact() ); + + if ( !session.isIgnoreArtifactDescriptorRepositories() ) + { + repositories = remoteRepositoryManager.aggregateRepositories( session, repositories, + descriptorResult.getRepositories(), + true ); + } + dependencies = mergeDeps( dependencies, descriptorResult.getDependencies() ); + managedDependencies = mergeDeps( managedDependencies, descriptorResult.getManagedDependencies() ); + + node = new DefaultDependencyNode( root ); + node.setRequestContext( request.getRequestContext() ); + node.setRelocations( descriptorResult.getRelocations() ); + node.setVersionConstraint( rangeResult.getVersionConstraint() ); + node.setVersion( version ); + node.setAliases( descriptorResult.getAliases() ); + node.setRepositories( request.getRepositories() ); + } + else + { + node = new DefaultDependencyNode( request.getRootArtifact() ); + node.setRequestContext( request.getRequestContext() ); + node.setRepositories( request.getRepositories() ); + } + + result.setRoot( node ); + + boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency( root ); + String errorPath = null; + if ( traverse && !dependencies.isEmpty() ) + { + DataPool pool = new DataPool( session ); + + DefaultDependencyCollectionContext context = + new DefaultDependencyCollectionContext( session, request.getRootArtifact(), root, managedDependencies ); + + DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext( session ); + + Args args = + new Args( session, trace, pool, context, versionContext, request, + useSkip ? DependencyResolutionSkipper.defaultSkipper() + : DependencyResolutionSkipper.neverSkipper() ); + Results results = new Results( result, session ); + + DependencySelector rootDepSelector = + depSelector != null ? depSelector.deriveChildSelector( context ) : null; + DependencyManager rootDepManager = depManager != null ? depManager.deriveChildManager( context ) : null; + DependencyTraverser rootDepTraverser = + depTraverser != null ? depTraverser.deriveChildTraverser( context ) : null; + VersionFilter rootVerFilter = verFilter != null ? verFilter.deriveChildFilter( context ) : null; + + List parents = Collections.singletonList( node ); + for ( Dependency dependency : dependencies ) + { + args.dependencyProcessingQueue.add( + new DependencyProcessingContext( rootDepSelector, rootDepManager, rootDepTraverser, + rootVerFilter, repositories, managedDependencies, parents, + dependency ) ); + } + + while ( !args.dependencyProcessingQueue.isEmpty() ) + { + processDependency( args, results, args.dependencyProcessingQueue.remove(), Collections.emptyList(), + false ); + } + + args.skipper.report(); + errorPath = results.errorPath; + } + + long time2 = System.nanoTime(); + + DependencyGraphTransformer transformer = session.getDependencyGraphTransformer(); + if ( transformer != null ) + { + try + { + DefaultDependencyGraphTransformationContext context = + new DefaultDependencyGraphTransformationContext( session ); + context.put( TransformationContextKeys.STATS, stats ); + result.setRoot( transformer.transformGraph( node, context ) ); + } + catch ( RepositoryException e ) + { + result.addException( e ); + } + } + + long time3 = System.nanoTime(); + if ( logger.isDebugEnabled() ) + { + stats.put( "BfDependencyCollector.collectTime", time2 - time1 ); + stats.put( "BfDependencyCollector.transformTime", time3 - time2 ); + logger.debug( "Dependency collection stats {}", stats ); + } + + if ( errorPath != null ) + { + throw new DependencyCollectionException( result, "Failed to collect dependencies at " + errorPath ); + } + if ( !result.getExceptions().isEmpty() ) + { + throw new DependencyCollectionException( result ); + } + + return result; + } + + private static RepositorySystemSession optimizeSession( RepositorySystemSession session ) + { + DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession( session ); + optimized.setArtifactTypeRegistry( CachingArtifactTypeRegistry.newInstance( session ) ); + return optimized; + } + + private List mergeDeps( List dominant, List recessive ) + { + List result; + if ( dominant == null || dominant.isEmpty() ) + { + result = recessive; + } + else if ( recessive == null || recessive.isEmpty() ) + { + result = dominant; + } + else + { + int initialCapacity = dominant.size() + recessive.size(); + result = new ArrayList<>( initialCapacity ); + Collection ids = new HashSet<>( initialCapacity, 1.0f ); + for ( Dependency dependency : dominant ) + { + ids.add( getId( dependency.getArtifact() ) ); + result.add( dependency ); + } + for ( Dependency dependency : recessive ) + { + if ( !ids.contains( getId( dependency.getArtifact() ) ) ) + { + result.add( dependency ); + } + } + } + return result; + } + + private static String getId( Artifact a ) + { + return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void processDependency( Args args, Results results, DependencyProcessingContext context, + List relocations, boolean disableVersionManagement ) + { + + if ( context.depSelector != null && !context.depSelector.selectDependency( context.dependency ) ) + { + return; + } + + PremanagedDependency preManaged = + PremanagedDependency.create( context.depManager, context.dependency, disableVersionManagement, + args.premanagedState ); + Dependency dependency = preManaged.managedDependency; + + boolean noDescriptor = isLackingDescriptor( dependency.getArtifact() ); + + boolean traverse = + !noDescriptor && ( context.depTraverser == null || context.depTraverser.traverseDependency( + dependency ) ); + + List versions; + VersionRangeResult rangeResult; + try + { + VersionRangeRequest rangeRequest = createVersionRangeRequest( args, context.repositories, dependency ); + + rangeResult = cachedResolveRangeResult( rangeRequest, args.pool, args.session ); + + versions = filterVersions( dependency, rangeResult, context.verFilter, args.versionContext ); + } + catch ( VersionRangeResolutionException e ) + { + results.addException( dependency, e, context.parents ); + return; + } + + //Resolve newer version first to maximize benefits of skipper + Collections.reverse( versions ); + for ( Version version : versions ) + { + Artifact originalArtifact = dependency.getArtifact().setVersion( version.toString() ); + Dependency d = dependency.setArtifact( originalArtifact ); + + ArtifactDescriptorRequest descriptorRequest = + createArtifactDescriptorRequest( args, context.repositories, d ); + + final ArtifactDescriptorResult descriptorResult = + noDescriptor + ? new ArtifactDescriptorResult( descriptorRequest ) + : resolveCachedArtifactDescriptor( args.pool, descriptorRequest, args.session, + context.withDependency( d ), results ); + + if ( descriptorResult != null ) + { + d = d.setArtifact( descriptorResult.getArtifact() ); + + int cycleEntry = find( context.parents, d.getArtifact() ); + if ( cycleEntry >= 0 ) + { + results.addCycle( context.parents, cycleEntry, d ); + DependencyNode cycleNode = context.parents.get( cycleEntry ); + if ( cycleNode.getDependency() != null ) + { + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, + descriptorResult, cycleNode ); + context.getParent().getChildren().add( child ); + continue; + } + } + + if ( !descriptorResult.getRelocations().isEmpty() ) + { + boolean disableVersionManagementSubsequently = + originalArtifact.getGroupId().equals( d.getArtifact().getGroupId() ) + && originalArtifact.getArtifactId().equals( d.getArtifact().getArtifactId() ); + + processDependency( args, results, context.withDependency( d ), descriptorResult.getRelocations(), + disableVersionManagementSubsequently ); + return; + } + else + { + d = args.pool.intern( d.setArtifact( args.pool.intern( d.getArtifact() ) ) ); + + List repos = + getRemoteRepositories( rangeResult.getRepository( version ), context.repositories ); + + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, + descriptorResult.getAliases(), repos, args.request.getRequestContext() ); + + context.getParent().getChildren().add( child ); + + boolean recurse = traverse && !descriptorResult.getDependencies().isEmpty(); + if ( recurse ) + { + doRecurse( args, context.withDependency( d ), descriptorResult, child ); + } + } + } + else + { + List repos = + getRemoteRepositories( rangeResult.getRepository( version ), context.repositories ); + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, null, repos, + args.request.getRequestContext() ); + context.getParent().getChildren().add( child ); + } + } + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void doRecurse( Args args, DependencyProcessingContext parentContext, + ArtifactDescriptorResult descriptorResult, DefaultDependencyNode child ) + { + DefaultDependencyCollectionContext context = args.collectionContext; + context.set( parentContext.dependency, descriptorResult.getManagedDependencies() ); + + DependencySelector childSelector = + parentContext.depSelector != null ? parentContext.depSelector.deriveChildSelector( context ) : null; + DependencyManager childManager = + parentContext.depManager != null ? parentContext.depManager.deriveChildManager( context ) : null; + DependencyTraverser childTraverser = + parentContext.depTraverser != null ? parentContext.depTraverser.deriveChildTraverser( context ) : null; + VersionFilter childFilter = + parentContext.verFilter != null ? parentContext.verFilter.deriveChildFilter( context ) : null; + + final List childRepos = + args.ignoreRepos + ? parentContext.repositories + : remoteRepositoryManager.aggregateRepositories( args.session, parentContext.repositories, + descriptorResult.getRepositories(), true ); + + Object key = + args.pool.toKey( parentContext.dependency.getArtifact(), childRepos, childSelector, childManager, + childTraverser, childFilter ); + + List children = args.pool.getChildren( key ); + if ( children == null ) + { + boolean skipResolution = args.skipper.skipResolution( child, parentContext.parents ); + if ( !skipResolution ) + { + List parents = new ArrayList<>( parentContext.parents.size() + 1 ); + parents.addAll( parentContext.parents ); + parents.add( child ); + for ( Dependency dependency : descriptorResult.getDependencies() ) + { + args.dependencyProcessingQueue.add( + new DependencyProcessingContext( childSelector, childManager, childTraverser, childFilter, + childRepos, descriptorResult.getManagedDependencies(), parents, dependency ) ); + } + args.pool.putChildren( key, child.getChildren() ); + args.skipper.cache( child, parents ); + } + } + else + { + child.setChildren( children ); + } + } + + private ArtifactDescriptorResult resolveCachedArtifactDescriptor( DataPool pool, + ArtifactDescriptorRequest descriptorRequest, + RepositorySystemSession session, + DependencyProcessingContext context, + Results results ) + { + Object key = pool.toKey( descriptorRequest ); + ArtifactDescriptorResult descriptorResult = pool.getDescriptor( key, descriptorRequest ); + if ( descriptorResult == null ) + { + try + { + descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); + pool.putDescriptor( key, descriptorResult ); + } + catch ( ArtifactDescriptorException e ) + { + results.addException( context.dependency, e, context.parents ); + pool.putDescriptor( key, e ); + return null; + } + + } + else if ( descriptorResult == DataPool.NO_DESCRIPTOR ) + { + return null; + } + + return descriptorResult; + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private static DefaultDependencyNode createDependencyNode( List relocations, + PremanagedDependency preManaged, + VersionRangeResult rangeResult, Version version, + Dependency d, Collection aliases, + List repos, String requestContext ) + { + DefaultDependencyNode child = new DefaultDependencyNode( d ); + preManaged.applyTo( child ); + child.setRelocations( relocations ); + child.setVersionConstraint( rangeResult.getVersionConstraint() ); + child.setVersion( version ); + child.setAliases( aliases ); + child.setRepositories( repos ); + child.setRequestContext( requestContext ); + return child; + } + + private static DefaultDependencyNode createDependencyNode( List relocations, + PremanagedDependency preManaged, + VersionRangeResult rangeResult, Version version, + Dependency d, ArtifactDescriptorResult descriptorResult, + DependencyNode cycleNode ) + { + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, descriptorResult.getAliases(), + cycleNode.getRepositories(), cycleNode.getRequestContext() ); + child.setChildren( cycleNode.getChildren() ); + return child; + } + + private static ArtifactDescriptorRequest createArtifactDescriptorRequest( Args args, + List repositories, + Dependency d ) + { + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact( d.getArtifact() ); + descriptorRequest.setRepositories( repositories ); + descriptorRequest.setRequestContext( args.request.getRequestContext() ); + descriptorRequest.setTrace( args.trace ); + return descriptorRequest; + } + + private static VersionRangeRequest createVersionRangeRequest( Args args, List repositories, + Dependency dependency ) + { + VersionRangeRequest rangeRequest = new VersionRangeRequest(); + rangeRequest.setArtifact( dependency.getArtifact() ); + rangeRequest.setRepositories( repositories ); + rangeRequest.setRequestContext( args.request.getRequestContext() ); + rangeRequest.setTrace( args.trace ); + return rangeRequest; + } + + private VersionRangeResult cachedResolveRangeResult( VersionRangeRequest rangeRequest, DataPool pool, + RepositorySystemSession session ) + throws VersionRangeResolutionException + { + Object key = pool.toKey( rangeRequest ); + VersionRangeResult rangeResult = pool.getConstraint( key, rangeRequest ); + if ( rangeResult == null ) + { + rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); + pool.putConstraint( key, rangeResult ); + } + return rangeResult; + } + + private static boolean isLackingDescriptor( Artifact artifact ) + { + return artifact.getProperty( ArtifactProperties.LOCAL_PATH, null ) != null; + } + + private static List getRemoteRepositories( ArtifactRepository repository, + List repositories ) + { + if ( repository instanceof RemoteRepository ) + { + return Collections.singletonList( (RemoteRepository) repository ); + } + if ( repository != null ) + { + return Collections.emptyList(); + } + return repositories; + } + + private static List filterVersions( Dependency dependency, VersionRangeResult rangeResult, + VersionFilter verFilter, + DefaultVersionFilterContext verContext ) + throws VersionRangeResolutionException + { + if ( rangeResult.getVersions().isEmpty() ) + { + throw new VersionRangeResolutionException( rangeResult, + "No versions available for " + dependency.getArtifact() + + " within specified range" ); + } + + List versions; + if ( verFilter != null && rangeResult.getVersionConstraint().getRange() != null ) + { + verContext.set( dependency, rangeResult ); + try + { + verFilter.filterVersions( verContext ); + } + catch ( RepositoryException e ) + { + throw new VersionRangeResolutionException( rangeResult, + "Failed to filter versions for " + dependency.getArtifact(), e ); + } + versions = verContext.get(); + if ( versions.isEmpty() ) + { + throw new VersionRangeResolutionException( rangeResult, + "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions() ); + } + } + else + { + versions = rangeResult.getVersions(); + } + return versions; + } + + static class Args + { + + final RepositorySystemSession session; + + final boolean ignoreRepos; + + final boolean premanagedState; + + final RequestTrace trace; + + final DataPool pool; + + final Queue dependencyProcessingQueue = new ArrayDeque<>( 128 ); + + final DefaultDependencyCollectionContext collectionContext; + + final DefaultVersionFilterContext versionContext; + + final CollectRequest request; + + final DependencyResolutionSkipper skipper; + + Args( RepositorySystemSession session, RequestTrace trace, DataPool pool, + DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext, + CollectRequest request, DependencyResolutionSkipper skipper ) + { + this.session = session; + this.request = request; + this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); + this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE ); + this.trace = trace; + this.pool = pool; + this.collectionContext = collectionContext; + this.versionContext = versionContext; + this.skipper = skipper; + } + + } + + static class Results + { + + private final CollectResult result; + + final int maxExceptions; + + final int maxCycles; + + String errorPath; + + Results( CollectResult result, RepositorySystemSession session ) + { + this.result = result; + + maxExceptions = + ConfigUtils.getInteger( session, CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT, CONFIG_PROP_MAX_EXCEPTIONS ); + + maxCycles = ConfigUtils.getInteger( session, CONFIG_PROP_MAX_CYCLES_DEFAULT, CONFIG_PROP_MAX_CYCLES ); + } + + public void addException( Dependency dependency, Exception e, List nodes ) + { + if ( maxExceptions < 0 || result.getExceptions().size() < maxExceptions ) + { + result.addException( e ); + if ( errorPath == null ) + { + StringBuilder buffer = new StringBuilder( 256 ); + for ( DependencyNode node : nodes ) + { + if ( buffer.length() > 0 ) + { + buffer.append( " -> " ); + } + Dependency dep = node.getDependency(); + if ( dep != null ) + { + buffer.append( dep.getArtifact() ); + } + } + if ( buffer.length() > 0 ) + { + buffer.append( " -> " ); + } + buffer.append( dependency.getArtifact() ); + errorPath = buffer.toString(); + } + } + } + + public void addCycle( List nodes, int cycleEntry, Dependency dependency ) + { + if ( maxCycles < 0 || result.getCycles().size() < maxCycles ) + { + result.addCycle( new DefaultDependencyCycle( nodes, cycleEntry, dependency ) ); + } + } + + } + + static class PremanagedDependency + { + + final String premanagedVersion; + + final String premanagedScope; + + final Boolean premanagedOptional; + + /** + * @since 1.1.0 + */ + final Collection premanagedExclusions; + + /** + * @since 1.1.0 + */ + final Map premanagedProperties; + + final int managedBits; + + final Dependency managedDependency; + + final boolean premanagedState; + + @SuppressWarnings( "checkstyle:parameternumber" ) + PremanagedDependency( String premanagedVersion, String premanagedScope, Boolean premanagedOptional, + Collection premanagedExclusions, Map premanagedProperties, + int managedBits, Dependency managedDependency, boolean premanagedState ) + { + this.premanagedVersion = premanagedVersion; + this.premanagedScope = premanagedScope; + this.premanagedOptional = premanagedOptional; + this.premanagedExclusions = + premanagedExclusions != null + ? Collections.unmodifiableCollection( new ArrayList<>( premanagedExclusions ) ) + : null; + + this.premanagedProperties = + premanagedProperties != null + ? Collections.unmodifiableMap( new HashMap<>( premanagedProperties ) ) + : null; + + this.managedBits = managedBits; + this.managedDependency = managedDependency; + this.premanagedState = premanagedState; + } + + static PremanagedDependency create( DependencyManager depManager, Dependency dependency, + boolean disableVersionManagement, boolean premanagedState ) + { + DependencyManagement depMngt = depManager != null ? depManager.manageDependency( dependency ) : null; + + int managedBits = 0; + String premanagedVersion = null; + String premanagedScope = null; + Boolean premanagedOptional = null; + Collection premanagedExclusions = null; + Map premanagedProperties = null; + + if ( depMngt != null ) + { + if ( depMngt.getVersion() != null && !disableVersionManagement ) + { + Artifact artifact = dependency.getArtifact(); + premanagedVersion = artifact.getVersion(); + dependency = dependency.setArtifact( artifact.setVersion( depMngt.getVersion() ) ); + managedBits |= DependencyNode.MANAGED_VERSION; + } + if ( depMngt.getProperties() != null ) + { + Artifact artifact = dependency.getArtifact(); + premanagedProperties = artifact.getProperties(); + dependency = dependency.setArtifact( artifact.setProperties( depMngt.getProperties() ) ); + managedBits |= DependencyNode.MANAGED_PROPERTIES; + } + if ( depMngt.getScope() != null ) + { + premanagedScope = dependency.getScope(); + dependency = dependency.setScope( depMngt.getScope() ); + managedBits |= DependencyNode.MANAGED_SCOPE; + } + if ( depMngt.getOptional() != null ) + { + premanagedOptional = dependency.isOptional(); + dependency = dependency.setOptional( depMngt.getOptional() ); + managedBits |= DependencyNode.MANAGED_OPTIONAL; + } + if ( depMngt.getExclusions() != null ) + { + premanagedExclusions = dependency.getExclusions(); + dependency = dependency.setExclusions( depMngt.getExclusions() ); + managedBits |= DependencyNode.MANAGED_EXCLUSIONS; + } + } + return new PremanagedDependency( premanagedVersion, premanagedScope, premanagedOptional, + premanagedExclusions, premanagedProperties, managedBits, dependency, + premanagedState ); + + } + + public void applyTo( DefaultDependencyNode child ) + { + child.setManagedBits( managedBits ); + if ( premanagedState ) + { + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_VERSION, premanagedVersion ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_SCOPE, premanagedScope ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_OPTIONAL, premanagedOptional ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_EXCLUSIONS, premanagedExclusions ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_PROPERTIES, premanagedProperties ); + } + } + + } + +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyProcessingContext.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyProcessingContext.java similarity index 95% rename from maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyProcessingContext.java rename to maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyProcessingContext.java index 9820ad07a..ee2e77204 100644 --- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyProcessingContext.java +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyProcessingContext.java @@ -1,4 +1,4 @@ -package org.eclipse.aether.internal.impl.collect; +package org.eclipse.aether.internal.impl.collect.bf; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -29,6 +29,11 @@ import org.eclipse.aether.graph.DependencyNode; import org.eclipse.aether.repository.RemoteRepository; +/** + * Internal helper for {@link BfDependencyCollector}. + * + * @since 1.8.0 + */ final class DependencyProcessingContext { final DependencySelector depSelector; diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper.java new file mode 100644 index 000000000..b82a8799c --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipper.java @@ -0,0 +1,359 @@ +package org.eclipse.aether.internal.impl.collect.bf; + +/* + * 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. + */ + +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.util.artifact.ArtifactIdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A skipper that determines whether to skip resolving given node during the dependency collection. + * Internal helper for {@link BfDependencyCollector}. + * + * @since 1.8.0 + */ +abstract class DependencyResolutionSkipper +{ + /** + * Check whether the resolution of current node can be skipped before resolving. + * + * @param node Current node + * @param parents All parent nodes of current node + * + * @return {@code true} if the node can be skipped for resolution, {@code false} if resolution required. + */ + abstract boolean skipResolution( DependencyNode node, List parents ); + + /** + * Cache the resolution result when a node is resolved by {@link BfDependencyCollector) after resolution. + * + * @param node Current node + * @param parents All parent nodes of current node + */ + abstract void cache( DependencyNode node, List parents ); + + /** + * Print the skip/resolve status report for all nodes. + */ + abstract void report(); + + /** + * Returns new instance of "default" skipper. + * + * Note: type is specialized for testing purposes. + */ + public static DefaultDependencyResolutionSkipper defaultSkipper() + { + return new DefaultDependencyResolutionSkipper(); + } + + /** + * Returns instance of "never" skipper. + */ + public static DependencyResolutionSkipper neverSkipper() + { + return NeverDependencyResolutionSkipper.INSTANCE; + } + + /** + * NEVER implementation. + */ + private static final class NeverDependencyResolutionSkipper extends DependencyResolutionSkipper + { + private static final DependencyResolutionSkipper INSTANCE = new NeverDependencyResolutionSkipper(); + + @Override + public boolean skipResolution( DependencyNode node, List parents ) + { + return false; + } + + @Override + public void cache( DependencyNode node, List parents ) + { + } + + @Override + public void report() + { + } + } + + /** + * Visible for testing. + */ + static final class DefaultDependencyResolutionSkipper extends DependencyResolutionSkipper + { + private static final Logger LOGGER = LoggerFactory.getLogger( DependencyResolutionSkipper.class ); + + private final Map results = new LinkedHashMap<>( 256 ); + private final CacheManager cacheManager = new CacheManager(); + private final CoordinateManager coordinateManager = new CoordinateManager(); + + @Override + public boolean skipResolution( DependencyNode node, List parents ) + { + DependencyResolutionResult result = new DependencyResolutionResult( node ); + results.put( node, result ); + + int depth = parents.size() + 1; + coordinateManager.createCoordinate( node, depth ); + + if ( cacheManager.isVersionConflict( node ) ) + { + /* + * Skip resolving version conflict losers (omitted for conflict) + */ + result.skippedAsVersionConflict = true; + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Skipped resolving node: {} as version conflict", + ArtifactIdUtils.toId( node.getArtifact() ) ); + } + } + else if ( cacheManager.isDuplicate( node ) ) + { + if ( coordinateManager.isLeftmost( node, parents ) ) + { + /* + * Force resolving the node to retain conflict paths when its coordinate is + * more left than last resolved + * This is because Maven picks the widest scope present among conflicting dependencies + */ + result.forceResolution = true; + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Force resolving node: {} for scope selection", + ArtifactIdUtils.toId( node.getArtifact() ) ); + } + } + else + { + /* + * Skip resolving as duplicate (depth deeper, omitted for duplicate) + * No need to compare depth as the depth of winner for given artifact is always shallower + */ + result.skippedAsDuplicate = true; + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Skipped resolving node: {} as duplicate", + ArtifactIdUtils.toId( node.getArtifact() ) ); + } + } + } + else + { + result.resolve = true; + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Resolving node: {}", + ArtifactIdUtils.toId( node.getArtifact() ) ); + } + } + + if ( result.toResolve() ) + { + coordinateManager.updateLeftmost( node ); + return false; + } + + return true; + } + + @Override + public void cache( DependencyNode node, List parents ) + { + boolean parentForceResolution = parents.stream() + .anyMatch( n -> results.containsKey( n ) && results.get( n ).forceResolution ); + if ( parentForceResolution ) + { + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Won't cache as node: {} inherits from a force-resolved node " + + "and will be omitted for duplicate", ArtifactIdUtils.toId( node.getArtifact() ) ); + } + } + else + { + cacheManager.cacheWinner( node ); + } + } + + @Override + public void report() + { + if ( LOGGER.isTraceEnabled() ) + { + LOGGER.trace( "Skipped {} nodes as duplicate", + results.entrySet().stream().filter( n -> n.getValue().skippedAsDuplicate ).count() ); + LOGGER.trace( "Skipped {} nodes as having version conflict", + results.entrySet().stream().filter( n -> n.getValue().skippedAsVersionConflict ).count() ); + LOGGER.trace( "Resolved {} nodes", + results.entrySet().stream().filter( n -> n.getValue().resolve ).count() ); + LOGGER.trace( "Forced resolving {} nodes for scope selection", + results.entrySet().stream().filter( n -> n.getValue().forceResolution ).count() ); + } + } + + public Map getResults() + { + return results; + } + + private static final class CacheManager + { + + /** + * artifact -> node + */ + private final Map winners = new HashMap<>( 256 ); + + + /** + * versionLessId -> Artifact, only cache winners + */ + private final Map winnerGAs = new HashMap<>( 256 ); + + boolean isVersionConflict( DependencyNode node ) + { + String ga = ArtifactIdUtils.toVersionlessId( node.getArtifact() ); + if ( winnerGAs.containsKey( ga ) ) + { + Artifact result = winnerGAs.get( ga ); + return !node.getArtifact().getVersion().equals( result.getVersion() ); + } + + return false; + } + + void cacheWinner( DependencyNode node ) + { + winners.put( node.getArtifact(), node ); + winnerGAs.put( ArtifactIdUtils.toVersionlessId( node.getArtifact() ), node.getArtifact() ); + } + + boolean isDuplicate( DependencyNode node ) + { + return winners.containsKey( node.getArtifact() ); + } + + } + + + private static final class CoordinateManager + { + private final Map sequenceGen = new HashMap<>( 256 ); + + /** + * Dependency node -> Coordinate + */ + private final Map coordinateMap = new HashMap<>( 256 ); + + /** + * Leftmost coordinate of given artifact + */ + private final Map leftmostCoordinates = new HashMap<>( 256 ); + + + Coordinate getCoordinate( DependencyNode node ) + { + return coordinateMap.get( node ); + } + + Coordinate createCoordinate( DependencyNode node, int depth ) + { + int seq = sequenceGen.computeIfAbsent( depth, k -> new AtomicInteger() ).incrementAndGet(); + Coordinate coordinate = new Coordinate( depth, seq ); + coordinateMap.put( node, coordinate ); + return coordinate; + } + + void updateLeftmost( DependencyNode current ) + { + leftmostCoordinates.put( current.getArtifact(), getCoordinate( current ) ); + } + + boolean isLeftmost( DependencyNode node, List parents ) + { + Coordinate leftmost = leftmostCoordinates.get( node.getArtifact() ); + if ( leftmost != null && leftmost.depth <= parents.size() ) + { + DependencyNode sameLevelNode = parents.get( leftmost.depth - 1 ); + return getCoordinate( sameLevelNode ).sequence < leftmost.sequence; + } + + return false; + } + } + + private static final class Coordinate + { + int depth; + int sequence; + + Coordinate( int depth, int sequence ) + { + this.depth = depth; + this.sequence = sequence; + } + + @Override + public String toString() + { + return "{" + + "depth=" + + depth + + ", sequence=" + + sequence + + '}'; + } + } + } + + /** + * Visible for testing. + */ + static final class DependencyResolutionResult + { + DependencyNode current; + boolean skippedAsVersionConflict; //omitted for conflict + boolean skippedAsDuplicate; //omitted for duplicate, depth is deeper + boolean resolve; //node to resolve (winner node) + boolean forceResolution; //force resolving (duplicate node) for scope selection + + DependencyResolutionResult( DependencyNode current ) + { + this.current = current; + } + + boolean toResolve() + { + return resolve || forceResolution; + } + } +} diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java new file mode 100644 index 000000000..a310af3be --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java @@ -0,0 +1,867 @@ +package org.eclipse.aether.internal.impl.collect.df; + +/* + * 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. + */ + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import static java.util.Objects.requireNonNull; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositoryException; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.RequestTrace; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.ArtifactProperties; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.CollectResult; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.collection.DependencyGraphTransformer; +import org.eclipse.aether.collection.DependencyManagement; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.collection.DependencySelector; +import org.eclipse.aether.collection.DependencyTraverser; +import org.eclipse.aether.collection.VersionFilter; +import org.eclipse.aether.graph.DefaultDependencyNode; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.graph.Exclusion; +import org.eclipse.aether.impl.ArtifactDescriptorReader; +import org.eclipse.aether.impl.RemoteRepositoryManager; +import org.eclipse.aether.impl.VersionRangeResolver; +import org.eclipse.aether.internal.impl.collect.CachingArtifactTypeRegistry; +import org.eclipse.aether.internal.impl.collect.DataPool; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyCollectionContext; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyCycle; +import org.eclipse.aether.internal.impl.collect.DefaultDependencyGraphTransformationContext; +import org.eclipse.aether.internal.impl.collect.DefaultVersionFilterContext; +import org.eclipse.aether.internal.impl.collect.DependencyCollectorDelegate; +import org.eclipse.aether.repository.ArtifactRepository; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactDescriptorException; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.resolution.VersionRangeRequest; +import org.eclipse.aether.resolution.VersionRangeResolutionException; +import org.eclipse.aether.resolution.VersionRangeResult; +import org.eclipse.aether.spi.locator.Service; +import org.eclipse.aether.util.ConfigUtils; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; +import org.eclipse.aether.version.Version; + +/** + * Depth-first {@link org.eclipse.aether.impl.DependencyCollector} (the "original" default). Originally + * this class was located a package higher (as "default" implementation). + * + * @since 1.8.0 + */ +@Singleton +@Named( DfDependencyCollector.NAME ) +public class DfDependencyCollector + extends DependencyCollectorDelegate implements Service +{ + public static final String NAME = "df"; + + public DfDependencyCollector() + { + // enables default constructor + } + + @Inject + DfDependencyCollector( RemoteRepositoryManager remoteRepositoryManager, + ArtifactDescriptorReader artifactDescriptorReader, + VersionRangeResolver versionRangeResolver ) + { + super( remoteRepositoryManager, artifactDescriptorReader, versionRangeResolver ); + } + + @SuppressWarnings( "checkstyle:methodlength" ) + public CollectResult collectDependencies( RepositorySystemSession session, CollectRequest request ) + throws DependencyCollectionException + { + requireNonNull( session, "session cannot be null" ); + requireNonNull( request, "request cannot be null" ); + session = optimizeSession( session ); + + RequestTrace trace = RequestTrace.newChild( request.getTrace(), request ); + + CollectResult result = new CollectResult( request ); + + DependencySelector depSelector = session.getDependencySelector(); + DependencyManager depManager = session.getDependencyManager(); + DependencyTraverser depTraverser = session.getDependencyTraverser(); + VersionFilter verFilter = session.getVersionFilter(); + + Dependency root = request.getRoot(); + List repositories = request.getRepositories(); + List dependencies = request.getDependencies(); + List managedDependencies = request.getManagedDependencies(); + + Map stats = new LinkedHashMap<>(); + long time1 = System.nanoTime(); + + DefaultDependencyNode node; + if ( root != null ) + { + List versions; + VersionRangeResult rangeResult; + try + { + VersionRangeRequest rangeRequest = + new VersionRangeRequest( root.getArtifact(), request.getRepositories(), + request.getRequestContext() ); + rangeRequest.setTrace( trace ); + rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); + versions = filterVersions( root, rangeResult, verFilter, new DefaultVersionFilterContext( session ) ); + } + catch ( VersionRangeResolutionException e ) + { + result.addException( e ); + throw new DependencyCollectionException( result, e.getMessage() ); + } + + Version version = versions.get( versions.size() - 1 ); + root = root.setArtifact( root.getArtifact().setVersion( version.toString() ) ); + + ArtifactDescriptorResult descriptorResult; + try + { + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact( root.getArtifact() ); + descriptorRequest.setRepositories( request.getRepositories() ); + descriptorRequest.setRequestContext( request.getRequestContext() ); + descriptorRequest.setTrace( trace ); + if ( isLackingDescriptor( root.getArtifact() ) ) + { + descriptorResult = new ArtifactDescriptorResult( descriptorRequest ); + } + else + { + descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); + } + } + catch ( ArtifactDescriptorException e ) + { + result.addException( e ); + throw new DependencyCollectionException( result, e.getMessage() ); + } + + root = root.setArtifact( descriptorResult.getArtifact() ); + + if ( !session.isIgnoreArtifactDescriptorRepositories() ) + { + repositories = remoteRepositoryManager.aggregateRepositories( session, repositories, + descriptorResult.getRepositories(), + true ); + } + dependencies = mergeDeps( dependencies, descriptorResult.getDependencies() ); + managedDependencies = mergeDeps( managedDependencies, descriptorResult.getManagedDependencies() ); + + node = new DefaultDependencyNode( root ); + node.setRequestContext( request.getRequestContext() ); + node.setRelocations( descriptorResult.getRelocations() ); + node.setVersionConstraint( rangeResult.getVersionConstraint() ); + node.setVersion( version ); + node.setAliases( descriptorResult.getAliases() ); + node.setRepositories( request.getRepositories() ); + } + else + { + node = new DefaultDependencyNode( request.getRootArtifact() ); + node.setRequestContext( request.getRequestContext() ); + node.setRepositories( request.getRepositories() ); + } + + result.setRoot( node ); + + boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency( root ); + String errorPath = null; + if ( traverse && !dependencies.isEmpty() ) + { + DataPool pool = new DataPool( session ); + + NodeStack nodes = new NodeStack(); + nodes.push( node ); + + DefaultDependencyCollectionContext context = + new DefaultDependencyCollectionContext( session, request.getRootArtifact(), root, managedDependencies ); + + DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext( session ); + + Args args = new Args( session, trace, pool, nodes, context, versionContext, request ); + Results results = new Results( result, session ); + + process( args, results, dependencies, repositories, + depSelector != null ? depSelector.deriveChildSelector( context ) : null, + depManager != null ? depManager.deriveChildManager( context ) : null, + depTraverser != null ? depTraverser.deriveChildTraverser( context ) : null, + verFilter != null ? verFilter.deriveChildFilter( context ) : null ); + + errorPath = results.errorPath; + } + + long time2 = System.nanoTime(); + + DependencyGraphTransformer transformer = session.getDependencyGraphTransformer(); + if ( transformer != null ) + { + try + { + DefaultDependencyGraphTransformationContext context = + new DefaultDependencyGraphTransformationContext( session ); + context.put( TransformationContextKeys.STATS, stats ); + result.setRoot( transformer.transformGraph( node, context ) ); + } + catch ( RepositoryException e ) + { + result.addException( e ); + } + } + + long time3 = System.nanoTime(); + if ( logger.isDebugEnabled() ) + { + stats.put( "BfDependencyCollector.collectTime", time2 - time1 ); + stats.put( "BfDependencyCollector.transformTime", time3 - time2 ); + logger.debug( "Dependency collection stats {}", stats ); + } + + if ( errorPath != null ) + { + throw new DependencyCollectionException( result, "Failed to collect dependencies at " + errorPath ); + } + if ( !result.getExceptions().isEmpty() ) + { + throw new DependencyCollectionException( result ); + } + + return result; + } + + private static RepositorySystemSession optimizeSession( RepositorySystemSession session ) + { + DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession( session ); + optimized.setArtifactTypeRegistry( CachingArtifactTypeRegistry.newInstance( session ) ); + return optimized; + } + + private List mergeDeps( List dominant, List recessive ) + { + List result; + if ( dominant == null || dominant.isEmpty() ) + { + result = recessive; + } + else if ( recessive == null || recessive.isEmpty() ) + { + result = dominant; + } + else + { + int initialCapacity = dominant.size() + recessive.size(); + result = new ArrayList<>( initialCapacity ); + Collection ids = new HashSet<>( initialCapacity, 1.0f ); + for ( Dependency dependency : dominant ) + { + ids.add( getId( dependency.getArtifact() ) ); + result.add( dependency ); + } + for ( Dependency dependency : recessive ) + { + if ( !ids.contains( getId( dependency.getArtifact() ) ) ) + { + result.add( dependency ); + } + } + } + return result; + } + + private static String getId( Artifact a ) + { + return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void process( final Args args, Results results, List dependencies, + List repositories, DependencySelector depSelector, + DependencyManager depManager, DependencyTraverser depTraverser, VersionFilter verFilter ) + { + for ( Dependency dependency : dependencies ) + { + processDependency( args, results, repositories, depSelector, depManager, depTraverser, verFilter, + dependency ); + } + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void processDependency( Args args, Results results, List repositories, + DependencySelector depSelector, DependencyManager depManager, + DependencyTraverser depTraverser, VersionFilter verFilter, Dependency dependency ) + { + + List relocations = Collections.emptyList(); + processDependency( args, results, repositories, depSelector, depManager, depTraverser, verFilter, dependency, + relocations, false ); + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void processDependency( Args args, Results results, List repositories, + DependencySelector depSelector, DependencyManager depManager, + DependencyTraverser depTraverser, VersionFilter verFilter, Dependency dependency, + List relocations, boolean disableVersionManagement ) + { + + if ( depSelector != null && !depSelector.selectDependency( dependency ) ) + { + return; + } + + PremanagedDependency preManaged = + PremanagedDependency.create( depManager, dependency, disableVersionManagement, args.premanagedState ); + dependency = preManaged.managedDependency; + + boolean noDescriptor = isLackingDescriptor( dependency.getArtifact() ); + + boolean traverse = !noDescriptor && ( depTraverser == null || depTraverser.traverseDependency( dependency ) ); + + List versions; + VersionRangeResult rangeResult; + try + { + VersionRangeRequest rangeRequest = createVersionRangeRequest( args, repositories, dependency ); + + rangeResult = cachedResolveRangeResult( rangeRequest, args.pool, args.session ); + + versions = filterVersions( dependency, rangeResult, verFilter, args.versionContext ); + } + catch ( VersionRangeResolutionException e ) + { + results.addException( dependency, e, args.nodes ); + return; + } + + for ( Version version : versions ) + { + Artifact originalArtifact = dependency.getArtifact().setVersion( version.toString() ); + Dependency d = dependency.setArtifact( originalArtifact ); + + ArtifactDescriptorRequest descriptorRequest = createArtifactDescriptorRequest( args, repositories, d ); + + final ArtifactDescriptorResult descriptorResult = + getArtifactDescriptorResult( args, results, noDescriptor, d, descriptorRequest ); + if ( descriptorResult != null ) + { + d = d.setArtifact( descriptorResult.getArtifact() ); + + DependencyNode node = args.nodes.top(); + + int cycleEntry = DefaultDependencyCycle.find( args.nodes.nodes, d.getArtifact() ); + if ( cycleEntry >= 0 ) + { + results.addCycle( args.nodes, cycleEntry, d ); + DependencyNode cycleNode = args.nodes.get( cycleEntry ); + if ( cycleNode.getDependency() != null ) + { + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, descriptorResult, + cycleNode ); + node.getChildren().add( child ); + continue; + } + } + + if ( !descriptorResult.getRelocations().isEmpty() ) + { + boolean disableVersionManagementSubsequently = + originalArtifact.getGroupId().equals( d.getArtifact().getGroupId() ) + && originalArtifact.getArtifactId().equals( d.getArtifact().getArtifactId() ); + + processDependency( args, results, repositories, depSelector, depManager, depTraverser, verFilter, d, + descriptorResult.getRelocations(), disableVersionManagementSubsequently ); + return; + } + else + { + d = args.pool.intern( d.setArtifact( args.pool.intern( d.getArtifact() ) ) ); + + List repos = + getRemoteRepositories( rangeResult.getRepository( version ), repositories ); + + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, + descriptorResult.getAliases(), repos, args.request.getRequestContext() ); + + node.getChildren().add( child ); + + boolean recurse = traverse && !descriptorResult.getDependencies().isEmpty(); + if ( recurse ) + { + doRecurse( args, results, repositories, depSelector, depManager, depTraverser, verFilter, d, + descriptorResult, child ); + } + } + } + else + { + DependencyNode node = args.nodes.top(); + List repos = + getRemoteRepositories( rangeResult.getRepository( version ), repositories ); + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, null, repos, + args.request.getRequestContext() ); + node.getChildren().add( child ); + } + } + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private void doRecurse( Args args, Results results, List repositories, + DependencySelector depSelector, DependencyManager depManager, + DependencyTraverser depTraverser, VersionFilter verFilter, Dependency d, + ArtifactDescriptorResult descriptorResult, DefaultDependencyNode child ) + { + DefaultDependencyCollectionContext context = args.collectionContext; + context.set( d, descriptorResult.getManagedDependencies() ); + + DependencySelector childSelector = depSelector != null ? depSelector.deriveChildSelector( context ) : null; + DependencyManager childManager = depManager != null ? depManager.deriveChildManager( context ) : null; + DependencyTraverser childTraverser = depTraverser != null ? depTraverser.deriveChildTraverser( context ) : null; + VersionFilter childFilter = verFilter != null ? verFilter.deriveChildFilter( context ) : null; + + final List childRepos = + args.ignoreRepos + ? repositories + : remoteRepositoryManager.aggregateRepositories( args.session, repositories, + descriptorResult.getRepositories(), true ); + + Object key = + args.pool.toKey( d.getArtifact(), childRepos, childSelector, childManager, childTraverser, childFilter ); + + List children = args.pool.getChildren( key ); + if ( children == null ) + { + args.pool.putChildren( key, child.getChildren() ); + + args.nodes.push( child ); + + process( args, results, descriptorResult.getDependencies(), childRepos, childSelector, childManager, + childTraverser, childFilter ); + + args.nodes.pop(); + } + else + { + child.setChildren( children ); + } + } + + private ArtifactDescriptorResult getArtifactDescriptorResult( Args args, Results results, boolean noDescriptor, + Dependency d, + ArtifactDescriptorRequest descriptorRequest ) + { + return noDescriptor + ? new ArtifactDescriptorResult( descriptorRequest ) + : resolveCachedArtifactDescriptor( args.pool, descriptorRequest, args.session, d, results, args ); + + } + + private ArtifactDescriptorResult resolveCachedArtifactDescriptor( DataPool pool, + ArtifactDescriptorRequest descriptorRequest, + RepositorySystemSession session, Dependency d, + Results results, Args args ) + { + Object key = pool.toKey( descriptorRequest ); + ArtifactDescriptorResult descriptorResult = pool.getDescriptor( key, descriptorRequest ); + if ( descriptorResult == null ) + { + try + { + descriptorResult = descriptorReader.readArtifactDescriptor( session, descriptorRequest ); + pool.putDescriptor( key, descriptorResult ); + } + catch ( ArtifactDescriptorException e ) + { + results.addException( d, e, args.nodes ); + pool.putDescriptor( key, e ); + return null; + } + + } + else if ( descriptorResult == DataPool.NO_DESCRIPTOR ) + { + return null; + } + + return descriptorResult; + } + + @SuppressWarnings( "checkstyle:parameternumber" ) + private static DefaultDependencyNode createDependencyNode( List relocations, + PremanagedDependency preManaged, + VersionRangeResult rangeResult, Version version, + Dependency d, Collection aliases, + List repos, String requestContext ) + { + DefaultDependencyNode child = new DefaultDependencyNode( d ); + preManaged.applyTo( child ); + child.setRelocations( relocations ); + child.setVersionConstraint( rangeResult.getVersionConstraint() ); + child.setVersion( version ); + child.setAliases( aliases ); + child.setRepositories( repos ); + child.setRequestContext( requestContext ); + return child; + } + + private static DefaultDependencyNode createDependencyNode( List relocations, + PremanagedDependency preManaged, + VersionRangeResult rangeResult, Version version, + Dependency d, ArtifactDescriptorResult descriptorResult, + DependencyNode cycleNode ) + { + DefaultDependencyNode child = + createDependencyNode( relocations, preManaged, rangeResult, version, d, descriptorResult.getAliases(), + cycleNode.getRepositories(), cycleNode.getRequestContext() ); + child.setChildren( cycleNode.getChildren() ); + return child; + } + + private static ArtifactDescriptorRequest createArtifactDescriptorRequest( Args args, + List repositories, + Dependency d ) + { + ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); + descriptorRequest.setArtifact( d.getArtifact() ); + descriptorRequest.setRepositories( repositories ); + descriptorRequest.setRequestContext( args.request.getRequestContext() ); + descriptorRequest.setTrace( args.trace ); + return descriptorRequest; + } + + private static VersionRangeRequest createVersionRangeRequest( Args args, List repositories, + Dependency dependency ) + { + VersionRangeRequest rangeRequest = new VersionRangeRequest(); + rangeRequest.setArtifact( dependency.getArtifact() ); + rangeRequest.setRepositories( repositories ); + rangeRequest.setRequestContext( args.request.getRequestContext() ); + rangeRequest.setTrace( args.trace ); + return rangeRequest; + } + + private VersionRangeResult cachedResolveRangeResult( VersionRangeRequest rangeRequest, DataPool pool, + RepositorySystemSession session ) + throws VersionRangeResolutionException + { + Object key = pool.toKey( rangeRequest ); + VersionRangeResult rangeResult = pool.getConstraint( key, rangeRequest ); + if ( rangeResult == null ) + { + rangeResult = versionRangeResolver.resolveVersionRange( session, rangeRequest ); + pool.putConstraint( key, rangeResult ); + } + return rangeResult; + } + + private static boolean isLackingDescriptor( Artifact artifact ) + { + return artifact.getProperty( ArtifactProperties.LOCAL_PATH, null ) != null; + } + + private static List getRemoteRepositories( ArtifactRepository repository, + List repositories ) + { + if ( repository instanceof RemoteRepository ) + { + return Collections.singletonList( (RemoteRepository) repository ); + } + if ( repository != null ) + { + return Collections.emptyList(); + } + return repositories; + } + + private static List filterVersions( Dependency dependency, VersionRangeResult rangeResult, + VersionFilter verFilter, + DefaultVersionFilterContext verContext ) + throws VersionRangeResolutionException + { + if ( rangeResult.getVersions().isEmpty() ) + { + throw new VersionRangeResolutionException( rangeResult, + "No versions available for " + dependency.getArtifact() + + " within specified range" ); + } + + List versions; + if ( verFilter != null && rangeResult.getVersionConstraint().getRange() != null ) + { + verContext.set( dependency, rangeResult ); + try + { + verFilter.filterVersions( verContext ); + } + catch ( RepositoryException e ) + { + throw new VersionRangeResolutionException( rangeResult, + "Failed to filter versions for " + dependency.getArtifact(), e ); + } + versions = verContext.get(); + if ( versions.isEmpty() ) + { + throw new VersionRangeResolutionException( rangeResult, + "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions() ); + } + } + else + { + versions = rangeResult.getVersions(); + } + return versions; + } + + static class Args + { + + final RepositorySystemSession session; + + final boolean ignoreRepos; + + final boolean premanagedState; + + final RequestTrace trace; + + final DataPool pool; + + final NodeStack nodes; + + final DefaultDependencyCollectionContext collectionContext; + + final DefaultVersionFilterContext versionContext; + + final CollectRequest request; + + Args( RepositorySystemSession session, RequestTrace trace, DataPool pool, NodeStack nodes, + DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext, + CollectRequest request ) + { + this.session = session; + this.request = request; + this.ignoreRepos = session.isIgnoreArtifactDescriptorRepositories(); + this.premanagedState = ConfigUtils.getBoolean( session, false, DependencyManagerUtils.CONFIG_PROP_VERBOSE ); + this.trace = trace; + this.pool = pool; + this.nodes = nodes; + this.collectionContext = collectionContext; + this.versionContext = versionContext; + } + + } + + static class Results + { + + private final CollectResult result; + + final int maxExceptions; + + final int maxCycles; + + String errorPath; + + Results( CollectResult result, RepositorySystemSession session ) + { + this.result = result; + + maxExceptions = + ConfigUtils.getInteger( session, CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT, CONFIG_PROP_MAX_EXCEPTIONS ); + + maxCycles = ConfigUtils.getInteger( session, CONFIG_PROP_MAX_CYCLES_DEFAULT, CONFIG_PROP_MAX_CYCLES ); + } + + public void addException( Dependency dependency, Exception e, NodeStack nodes ) + { + if ( maxExceptions < 0 || result.getExceptions().size() < maxExceptions ) + { + result.addException( e ); + if ( errorPath == null ) + { + StringBuilder buffer = new StringBuilder( 256 ); + for ( int i = 0; i < nodes.size(); i++ ) + { + if ( buffer.length() > 0 ) + { + buffer.append( " -> " ); + } + Dependency dep = nodes.get( i ).getDependency(); + if ( dep != null ) + { + buffer.append( dep.getArtifact() ); + } + } + if ( buffer.length() > 0 ) + { + buffer.append( " -> " ); + } + buffer.append( dependency.getArtifact() ); + errorPath = buffer.toString(); + } + } + } + + public void addCycle( NodeStack nodes, int cycleEntry, Dependency dependency ) + { + if ( maxCycles < 0 || result.getCycles().size() < maxCycles ) + { + result.addCycle( new DefaultDependencyCycle( nodes.nodes, cycleEntry, dependency ) ); + } + } + + } + + static class PremanagedDependency + { + + final String premanagedVersion; + + final String premanagedScope; + + final Boolean premanagedOptional; + + /** + * @since 1.1.0 + */ + final Collection premanagedExclusions; + + /** + * @since 1.1.0 + */ + final Map premanagedProperties; + + final int managedBits; + + final Dependency managedDependency; + + final boolean premanagedState; + + @SuppressWarnings( "checkstyle:parameternumber" ) + PremanagedDependency( String premanagedVersion, String premanagedScope, Boolean premanagedOptional, + Collection premanagedExclusions, Map premanagedProperties, + int managedBits, Dependency managedDependency, boolean premanagedState ) + { + this.premanagedVersion = premanagedVersion; + this.premanagedScope = premanagedScope; + this.premanagedOptional = premanagedOptional; + this.premanagedExclusions = + premanagedExclusions != null + ? Collections.unmodifiableCollection( new ArrayList<>( premanagedExclusions ) ) + : null; + + this.premanagedProperties = + premanagedProperties != null + ? Collections.unmodifiableMap( new HashMap<>( premanagedProperties ) ) + : null; + + this.managedBits = managedBits; + this.managedDependency = managedDependency; + this.premanagedState = premanagedState; + } + + static PremanagedDependency create( DependencyManager depManager, Dependency dependency, + boolean disableVersionManagement, boolean premanagedState ) + { + DependencyManagement depMngt = depManager != null ? depManager.manageDependency( dependency ) : null; + + int managedBits = 0; + String premanagedVersion = null; + String premanagedScope = null; + Boolean premanagedOptional = null; + Collection premanagedExclusions = null; + Map premanagedProperties = null; + + if ( depMngt != null ) + { + if ( depMngt.getVersion() != null && !disableVersionManagement ) + { + Artifact artifact = dependency.getArtifact(); + premanagedVersion = artifact.getVersion(); + dependency = dependency.setArtifact( artifact.setVersion( depMngt.getVersion() ) ); + managedBits |= DependencyNode.MANAGED_VERSION; + } + if ( depMngt.getProperties() != null ) + { + Artifact artifact = dependency.getArtifact(); + premanagedProperties = artifact.getProperties(); + dependency = dependency.setArtifact( artifact.setProperties( depMngt.getProperties() ) ); + managedBits |= DependencyNode.MANAGED_PROPERTIES; + } + if ( depMngt.getScope() != null ) + { + premanagedScope = dependency.getScope(); + dependency = dependency.setScope( depMngt.getScope() ); + managedBits |= DependencyNode.MANAGED_SCOPE; + } + if ( depMngt.getOptional() != null ) + { + premanagedOptional = dependency.isOptional(); + dependency = dependency.setOptional( depMngt.getOptional() ); + managedBits |= DependencyNode.MANAGED_OPTIONAL; + } + if ( depMngt.getExclusions() != null ) + { + premanagedExclusions = dependency.getExclusions(); + dependency = dependency.setExclusions( depMngt.getExclusions() ); + managedBits |= DependencyNode.MANAGED_EXCLUSIONS; + } + } + return new PremanagedDependency( premanagedVersion, premanagedScope, premanagedOptional, + premanagedExclusions, premanagedProperties, managedBits, dependency, + premanagedState ); + + } + + public void applyTo( DefaultDependencyNode child ) + { + child.setManagedBits( managedBits ); + if ( premanagedState ) + { + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_VERSION, premanagedVersion ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_SCOPE, premanagedScope ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_OPTIONAL, premanagedOptional ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_EXCLUSIONS, premanagedExclusions ); + child.setData( DependencyManagerUtils.NODE_DATA_PREMANAGED_PROPERTIES, premanagedProperties ); + } + } + + } + +} \ No newline at end of file diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/NodeStack.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/NodeStack.java new file mode 100644 index 000000000..dafe7a2fe --- /dev/null +++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/NodeStack.java @@ -0,0 +1,79 @@ +package org.eclipse.aether.internal.impl.collect.df; + +/* + * 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. + */ + +import java.util.ArrayList; + +import org.eclipse.aether.graph.DependencyNode; + +/** + * Internal helper for {@link DfDependencyCollector}. Originally (pre-1.8.0) this same class was located a + * package higher. + * + * @since 1.8.0 + */ +final class NodeStack +{ + + @SuppressWarnings( {"checkstyle:magicnumber" } ) + // CHECKSTYLE_OFF: MagicNumber + ArrayList nodes = new ArrayList<>( 96 ); + // CHECKSTYLE_ON: MagicNumber + + public DependencyNode top() + { + if ( nodes.isEmpty() ) + { + throw new IllegalStateException( "stack empty" ); + } + return nodes.get( nodes.size() - 1 ); + } + + public void push( DependencyNode node ) + { + nodes.add( node ); + } + + public void pop() + { + if ( nodes.isEmpty() ) + { + throw new IllegalStateException( "stack empty" ); + } + nodes.remove( nodes.size() - 1 ); + } + + public int size() + { + return nodes.size(); + } + + public DependencyNode get( int index ) + { + return nodes.get( index ); + } + + @Override + public String toString() + { + return nodes.toString(); + } + +} \ No newline at end of file diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java index 0a382f8c4..66dade8c1 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCycleTest.java @@ -19,6 +19,9 @@ * under the License. */ +import java.util.ArrayList; +import java.util.List; + import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; @@ -26,9 +29,6 @@ import org.eclipse.aether.graph.DependencyNode; import org.junit.Test; -import java.util.ArrayList; -import java.util.List; - import static org.junit.Assert.assertEquals; public class DefaultDependencyCycleTest diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorTest.java similarity index 98% rename from maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorTest.java rename to maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorTest.java index 7d034bddd..ea162f945 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorTest.java @@ -1,4 +1,4 @@ -package org.eclipse.aether.internal.impl.collect; +package org.eclipse.aether.internal.impl.collect.bf; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -18,14 +18,6 @@ * specific language governing permissions and limitations * under the License. */ -import static java.util.Objects.requireNonNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import java.io.IOException; import java.util.ArrayList; @@ -73,12 +65,21 @@ import org.junit.Before; import org.junit.Test; +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** */ -public class DefaultDependencyCollectorTest +public class BfDependencyCollectorTest { - protected DefaultDependencyCollector collector; + protected BfDependencyCollector collector; protected DefaultRepositorySystemSession session; @@ -107,12 +108,12 @@ public void setup() setupCollector( false ); } - public void setupCollector( boolean useSkip ) + public void setupCollector( boolean useSkipper ) { session = TestUtils.newSession(); - session.setConfigProperty( DefaultDependencyCollector.CONFIG_PROP_USE_SKIP, useSkip ); + session.setConfigProperty( BfDependencyCollector.CONFIG_PROP_SKIPPER, useSkipper ); - collector = new DefaultDependencyCollector(); + collector = new BfDependencyCollector(); collector.setArtifactDescriptorReader( newReader( "" ) ); collector.setVersionRangeResolver( new StubVersionRangeResolver() ); collector.setRemoteRepositoryManager( new StubRemoteRepositoryManager() ); diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorUseSkipTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorUseSkipTest.java similarity index 96% rename from maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorUseSkipTest.java rename to maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorUseSkipTest.java index 716ecdef7..5daa678b5 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DefaultDependencyCollectorUseSkipTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollectorUseSkipTest.java @@ -1,4 +1,4 @@ -package org.eclipse.aether.internal.impl.collect; +package org.eclipse.aether.internal.impl.collect.bf; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -18,6 +18,11 @@ * under the License. */ +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.collection.CollectRequest; import org.eclipse.aether.collection.CollectResult; @@ -29,14 +34,9 @@ import org.eclipse.aether.util.graph.selector.ExclusionDependencySelector; import org.junit.Test; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - import static org.junit.Assert.assertEquals; -public class DefaultDependencyCollectorUseSkipTest extends DefaultDependencyCollectorTest +public class BfDependencyCollectorUseSkipTest extends BfDependencyCollectorTest { private Dependency newDep( String coords, String scope, Collection exclusions ) diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DependencyResolutionSkipperTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipperTest.java similarity index 82% rename from maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DependencyResolutionSkipperTest.java rename to maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipperTest.java index 1d92acc01..eeb0a02d7 100644 --- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/DependencyResolutionSkipperTest.java +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/bf/DependencyResolutionSkipperTest.java @@ -1,4 +1,4 @@ -package org.eclipse.aether.internal.impl.collect; +package org.eclipse.aether.internal.impl.collect.bf; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -18,7 +18,12 @@ * under the License. */ -import org.eclipse.aether.RepositoryException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.graph.DefaultDependencyNode; import org.eclipse.aether.graph.Dependency; @@ -27,12 +32,6 @@ import org.eclipse.aether.internal.test.util.TestVersionConstraint; import org.junit.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -61,7 +60,7 @@ private static DependencyNode makeDependencyNode( String groupId, String artifac } @Test - public void testSkipVersionConflict() throws RepositoryException + public void testSkipVersionConflict() { // A -> B -> C 3.0 -> D => C3.0 SHOULD BE SKIPPED // | -> E -> F -> G @@ -84,7 +83,7 @@ public void testSkipVersionConflict() throws RepositoryException c2Node.setChildren( mutableList( hNode ) ); //follow the BFS resolve sequence - DefaultDependencyResolutionSkipper skipper = new DefaultDependencyResolutionSkipper(); + DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper = DependencyResolutionSkipper.defaultSkipper(); assertFalse( skipper.skipResolution( aNode, new ArrayList<>() ) ); skipper.cache( aNode, new ArrayList<>() ); assertFalse( skipper.skipResolution( bNode, mutableList( aNode ) ) ); @@ -99,19 +98,19 @@ public void testSkipVersionConflict() throws RepositoryException assertFalse( skipper.skipResolution( gNode, mutableList( aNode, eNode, fNode ) ) ); skipper.cache( gNode, mutableList( aNode, eNode, fNode ) ); - Map results = skipper.getResults(); + Map results = skipper.getResults(); assertEquals( results.size(), 7 ); - List skipped = - results.entrySet().stream().filter( n -> n.getValue().skippedAsVersionConflict ) - .map( s -> s.getValue() ).collect( - Collectors.toList() ); + List skipped = + results.values().stream() + .filter( dependencyResolutionResult -> dependencyResolutionResult.skippedAsVersionConflict ) + .collect( Collectors.toList() ); assertEquals( skipped.size(), 1 ); assertTrue( skipped.get( 0 ).current == c3Node ); } @Test - public void testSkipDeeperDuplicateNode() throws RepositoryException + public void testSkipDeeperDuplicateNode() { // A -> B // |--> C -> B => B here will be skipped @@ -129,7 +128,7 @@ public void testSkipDeeperDuplicateNode() throws RepositoryException dNode.setChildren( mutableList( c1Node ) ); //follow the BFS resolve sequence - DefaultDependencyResolutionSkipper skipper = new DefaultDependencyResolutionSkipper(); + DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper = DependencyResolutionSkipper.defaultSkipper(); assertFalse( skipper.skipResolution( aNode, new ArrayList<>() ) ); skipper.cache( aNode, new ArrayList<>() ); assertFalse( skipper.skipResolution( bNode, mutableList( aNode ) ) ); @@ -145,13 +144,13 @@ public void testSkipDeeperDuplicateNode() throws RepositoryException assertTrue( skipper.skipResolution( c1Node, mutableList( aNode, dNode ) ) ); skipper.cache( c1Node, mutableList( aNode, dNode ) ); - Map results = skipper.getResults(); + Map results = skipper.getResults(); assertEquals( results.size(), 6 ); - List skipped = - results.entrySet().stream().filter( n -> n.getValue().skippedAsDuplicate ) - .map( s -> s.getValue() ).collect( - Collectors.toList() ); + List skipped = + results.values().stream() + .filter( dependencyResolutionResult -> dependencyResolutionResult.skippedAsDuplicate ) + .collect( Collectors.toList() ); assertEquals( skipped.size(), 2 ); assertTrue( skipped.get( 0 ).current == b1Node ); assertTrue( skipped.get( 1 ).current == c1Node ); @@ -159,7 +158,7 @@ public void testSkipDeeperDuplicateNode() throws RepositoryException @Test - public void testForceResolution() throws RepositoryException + public void testForceResolution() { // A -> B -> C -> D => 3rd D here will be force-resolved // |--> C -> D => 2nd D will be force-resolved @@ -179,7 +178,7 @@ public void testForceResolution() throws RepositoryException dNode.setChildren( new ArrayList<>() ); //follow the BFS resolve sequence - DefaultDependencyResolutionSkipper skipper = new DefaultDependencyResolutionSkipper(); + DependencyResolutionSkipper.DefaultDependencyResolutionSkipper skipper = DependencyResolutionSkipper.defaultSkipper(); assertFalse( skipper.skipResolution( aNode, new ArrayList<>() ) ); skipper.cache( aNode, new ArrayList<>() ); assertFalse( skipper.skipResolution( bNode, mutableList( aNode ) ) ); @@ -198,13 +197,13 @@ public void testForceResolution() throws RepositoryException assertFalse( skipper.skipResolution( d2Node, mutableList( aNode, bNode, c1Node ) ) ); skipper.cache( d2Node, mutableList( aNode, bNode, c1Node ) ); - Map results = skipper.getResults(); + Map results = skipper.getResults(); assertEquals( results.size(), 7 ); - List forceResolved = - results.entrySet().stream().filter( n -> n.getValue().forceResolution ) - .map( s -> s.getValue() ).collect( - Collectors.toList() ); + List forceResolved = + results.values().stream() + .filter( dependencyResolutionResult -> dependencyResolutionResult.forceResolution ) + .collect( Collectors.toList() ); assertEquals( forceResolved.size(), 3 ); assertTrue( forceResolved.get( 0 ).current == c1Node ); assertTrue( forceResolved.get( 1 ).current == d1Node ); diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollectorTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollectorTest.java new file mode 100644 index 000000000..b641bbd64 --- /dev/null +++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollectorTest.java @@ -0,0 +1,649 @@ +package org.eclipse.aether.internal.impl.collect.df; + +/* + * 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. + */ +import static java.util.Objects.requireNonNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.ArtifactProperties; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.collection.CollectRequest; +import org.eclipse.aether.collection.CollectResult; +import org.eclipse.aether.collection.DependencyCollectionContext; +import org.eclipse.aether.collection.DependencyCollectionException; +import org.eclipse.aether.collection.DependencyManagement; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.graph.DefaultDependencyNode; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.DependencyCycle; +import org.eclipse.aether.graph.DependencyNode; +import org.eclipse.aether.graph.Exclusion; +import org.eclipse.aether.impl.ArtifactDescriptorReader; +import org.eclipse.aether.internal.impl.IniArtifactDescriptorReader; +import org.eclipse.aether.internal.impl.StubRemoteRepositoryManager; +import org.eclipse.aether.internal.impl.StubVersionRangeResolver; +import org.eclipse.aether.internal.test.util.DependencyGraphParser; +import org.eclipse.aether.internal.test.util.TestUtils; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactDescriptorException; +import org.eclipse.aether.resolution.ArtifactDescriptorRequest; +import org.eclipse.aether.resolution.ArtifactDescriptorResult; +import org.eclipse.aether.util.artifact.ArtifactIdUtils; +import org.eclipse.aether.util.graph.manager.ClassicDependencyManager; +import org.eclipse.aether.util.graph.manager.DefaultDependencyManager; +import org.eclipse.aether.util.graph.manager.DependencyManagerUtils; +import org.eclipse.aether.util.graph.manager.TransitiveDependencyManager; +import org.eclipse.aether.util.graph.version.HighestVersionFilter; +import org.junit.Before; +import org.junit.Test; + +/** + */ +public class DfDependencyCollectorTest +{ + + private DfDependencyCollector collector; + + private DefaultRepositorySystemSession session; + + private DependencyGraphParser parser; + + private RemoteRepository repository; + + private IniArtifactDescriptorReader newReader( String prefix ) + { + return new IniArtifactDescriptorReader( "artifact-descriptions/" + prefix ); + } + + private Dependency newDep( String coords ) + { + return newDep( coords, "" ); + } + + private Dependency newDep( String coords, String scope ) + { + return new Dependency( new DefaultArtifact( coords ), scope ); + } + + @Before + public void setup() + { + session = TestUtils.newSession(); + + collector = new DfDependencyCollector(); + collector.setArtifactDescriptorReader( newReader( "" ) ); + collector.setVersionRangeResolver( new StubVersionRangeResolver() ); + collector.setRemoteRepositoryManager( new StubRemoteRepositoryManager() ); + + parser = new DependencyGraphParser( "artifact-descriptions/" ); + + repository = new RemoteRepository.Builder( "id", "default", "file:///" ).build(); + } + + private static void assertEqualSubtree( DependencyNode expected, DependencyNode actual ) + { + assertEqualSubtree( expected, actual, new LinkedList() ); + } + + private static void assertEqualSubtree( DependencyNode expected, DependencyNode actual, + LinkedList parents ) + { + assertEquals( "path: " + parents, expected.getDependency(), actual.getDependency() ); + + if ( actual.getDependency() != null ) + { + Artifact artifact = actual.getDependency().getArtifact(); + for ( DependencyNode parent : parents ) + { + if ( parent.getDependency() != null && artifact.equals( parent.getDependency().getArtifact() ) ) + { + return; + } + } + } + + parents.addLast( expected ); + + assertEquals( "path: " + parents + ", expected: " + expected.getChildren() + ", actual: " + + actual.getChildren(), expected.getChildren().size(), actual.getChildren().size() ); + + Iterator iterator1 = expected.getChildren().iterator(); + Iterator iterator2 = actual.getChildren().iterator(); + + while ( iterator1.hasNext() ) + { + assertEqualSubtree( iterator1.next(), iterator2.next(), parents ); + } + + parents.removeLast(); + } + + private Dependency dep( DependencyNode root, int... coords ) + { + return path( root, coords ).getDependency(); + } + + private DependencyNode path( DependencyNode root, int... coords ) + { + try + { + DependencyNode node = root; + for ( int coord : coords ) + { + node = node.getChildren().get( coord ); + } + + return node; + } + catch ( IndexOutOfBoundsException | NullPointerException e ) + { + throw new IllegalArgumentException( "illegal coordinates for child", e ); + } + } + + @Test + public void testSimpleCollection() + throws DependencyCollectionException + { + Dependency dependency = newDep( "gid:aid:ext:ver", "compile" ); + CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) ); + CollectResult result = collector.collectDependencies( session, request ); + + assertEquals( 0, result.getExceptions().size() ); + + DependencyNode root = result.getRoot(); + Dependency newDependency = root.getDependency(); + + assertEquals( dependency, newDependency ); + assertEquals( dependency.getArtifact(), newDependency.getArtifact() ); + + assertEquals( 1, root.getChildren().size() ); + + Dependency expect = newDep( "gid:aid2:ext:ver", "compile" ); + assertEquals( expect, root.getChildren().get( 0 ).getDependency() ); + } + + @Test + public void testMissingDependencyDescription() + { + CollectRequest request = + new CollectRequest( newDep( "missing:description:ext:ver" ), Arrays.asList( repository ) ); + try + { + collector.collectDependencies( session, request ); + fail( "expected exception" ); + } + catch ( DependencyCollectionException e ) + { + CollectResult result = e.getResult(); + assertSame( request, result.getRequest() ); + assertNotNull( result.getExceptions() ); + assertEquals( 1, result.getExceptions().size() ); + + assertTrue( result.getExceptions().get( 0 ) instanceof ArtifactDescriptorException ); + + assertEquals( request.getRoot(), result.getRoot().getDependency() ); + } + } + + @Test + public void testDuplicates() + throws DependencyCollectionException + { + Dependency dependency = newDep( "duplicate:transitive:ext:dependency" ); + CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) ); + + CollectResult result = collector.collectDependencies( session, request ); + + assertEquals( 0, result.getExceptions().size() ); + + DependencyNode root = result.getRoot(); + Dependency newDependency = root.getDependency(); + + assertEquals( dependency, newDependency ); + assertEquals( dependency.getArtifact(), newDependency.getArtifact() ); + + assertEquals( 2, root.getChildren().size() ); + + Dependency dep = newDep( "gid:aid:ext:ver", "compile" ); + assertEquals( dep, dep( root, 0 ) ); + + dep = newDep( "gid:aid2:ext:ver", "compile" ); + assertEquals( dep, dep( root, 1 ) ); + assertEquals( dep, dep( root, 0, 0 ) ); + assertEquals( dep( root, 1 ), dep( root, 0, 0 ) ); + } + + @Test + public void testEqualSubtree() + throws IOException, DependencyCollectionException + { + DependencyNode root = parser.parseResource( "expectedSubtreeComparisonResult.txt" ); + Dependency dependency = root.getDependency(); + CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) ); + + CollectResult result = collector.collectDependencies( session, request ); + assertEqualSubtree( root, result.getRoot() ); + } + + @Test + public void testCyclicDependencies() + throws Exception + { + DependencyNode root = parser.parseResource( "cycle.txt" ); + CollectRequest request = new CollectRequest( root.getDependency(), Arrays.asList( repository ) ); + CollectResult result = collector.collectDependencies( session, request ); + assertEqualSubtree( root, result.getRoot() ); + } + + @Test + public void testCyclicDependenciesBig() + throws Exception + { + CollectRequest request = new CollectRequest( newDep( "1:2:pom:5.50-SNAPSHOT" ), Arrays.asList( repository ) ); + collector.setArtifactDescriptorReader( newReader( "cycle-big/" ) ); + CollectResult result = collector.collectDependencies( session, request ); + assertNotNull( result.getRoot() ); + // we only care about the performance here, this test must not hang or run out of mem + } + + @Test + public void testCyclicProjects() + throws Exception + { + CollectRequest request = new CollectRequest( newDep( "test:a:2" ), Arrays.asList( repository ) ); + collector.setArtifactDescriptorReader( newReader( "versionless-cycle/" ) ); + CollectResult result = collector.collectDependencies( session, request ); + DependencyNode root = result.getRoot(); + DependencyNode a1 = path( root, 0, 0 ); + assertEquals( "a", a1.getArtifact().getArtifactId() ); + assertEquals( "1", a1.getArtifact().getVersion() ); + for ( DependencyNode child : a1.getChildren() ) + { + assertNotEquals( "1", child.getArtifact().getVersion() ); + } + + assertEquals( 1, result.getCycles().size() ); + DependencyCycle cycle = result.getCycles().get( 0 ); + assertEquals( Arrays.asList(), cycle.getPrecedingDependencies() ); + assertEquals( Arrays.asList( root.getDependency(), path( root, 0 ).getDependency(), a1.getDependency() ), + cycle.getCyclicDependencies() ); + } + + @Test + public void testCyclicProjects_ConsiderLabelOfRootlessGraph() + throws Exception + { + Dependency dep = newDep( "gid:aid:ver", "compile" ); + CollectRequest request = + new CollectRequest().addDependency( dep ).addRepository( repository ).setRootArtifact( dep.getArtifact() ); + CollectResult result = collector.collectDependencies( session, request ); + DependencyNode root = result.getRoot(); + DependencyNode a1 = root.getChildren().get( 0 ); + assertEquals( "aid", a1.getArtifact().getArtifactId() ); + assertEquals( "ver", a1.getArtifact().getVersion() ); + DependencyNode a2 = a1.getChildren().get( 0 ); + assertEquals( "aid2", a2.getArtifact().getArtifactId() ); + assertEquals( "ver", a2.getArtifact().getVersion() ); + + assertEquals( 1, result.getCycles().size() ); + DependencyCycle cycle = result.getCycles().get( 0 ); + assertEquals( Arrays.asList(), cycle.getPrecedingDependencies() ); + assertEquals( Arrays.asList( new Dependency( dep.getArtifact(), null ), a1.getDependency() ), + cycle.getCyclicDependencies() ); + } + + @Test + public void testPartialResultOnError() + throws IOException + { + DependencyNode root = parser.parseResource( "expectedPartialSubtreeOnError.txt" ); + + Dependency dependency = root.getDependency(); + CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) ); + + CollectResult result; + try + { + result = collector.collectDependencies( session, request ); + fail( "expected exception " ); + } + catch ( DependencyCollectionException e ) + { + result = e.getResult(); + + assertSame( request, result.getRequest() ); + assertNotNull( result.getExceptions() ); + assertEquals( 1, result.getExceptions().size() ); + + assertTrue( result.getExceptions().get( 0 ) instanceof ArtifactDescriptorException ); + + assertEqualSubtree( root, result.getRoot() ); + } + } + + @Test + public void testCollectMultipleDependencies() + throws DependencyCollectionException + { + Dependency root1 = newDep( "gid:aid:ext:ver", "compile" ); + Dependency root2 = newDep( "gid:aid2:ext:ver", "compile" ); + List dependencies = Arrays.asList( root1, root2 ); + CollectRequest request = new CollectRequest( dependencies, null, Arrays.asList( repository ) ); + CollectResult result = collector.collectDependencies( session, request ); + + assertEquals( 0, result.getExceptions().size() ); + assertEquals( 2, result.getRoot().getChildren().size() ); + assertEquals( root1, dep( result.getRoot(), 0 ) ); + + assertEquals( 1, path( result.getRoot(), 0 ).getChildren().size() ); + assertEquals( root2, dep( result.getRoot(), 0, 0 ) ); + + assertEquals( 0, path( result.getRoot(), 1 ).getChildren().size() ); + assertEquals( root2, dep( result.getRoot(), 1 ) ); + } + + @Test + public void testArtifactDescriptorResolutionNotRestrictedToRepoHostingSelectedVersion() + throws Exception + { + RemoteRepository repo2 = new RemoteRepository.Builder( "test", "default", "file:///" ).build(); + + final List repos = new ArrayList<>(); + + collector.setArtifactDescriptorReader( new ArtifactDescriptorReader() + { + public ArtifactDescriptorResult readArtifactDescriptor( RepositorySystemSession session, + ArtifactDescriptorRequest request ) + { + repos.addAll( request.getRepositories() ); + return new ArtifactDescriptorResult( request ); + } + } ); + + List dependencies = Arrays.asList( newDep( "verrange:parent:jar:1[1,)", "compile" ) ); + CollectRequest request = new CollectRequest( dependencies, null, Arrays.asList( repository, repo2 ) ); + CollectResult result = collector.collectDependencies( session, request ); + + assertEquals( 0, result.getExceptions().size() ); + assertEquals( 2, repos.size() ); + assertEquals( "id", repos.get( 0 ).getId() ); + assertEquals( "test", repos.get( 1 ).getId() ); + } + + @Test + public void testManagedVersionScope() + throws DependencyCollectionException + { + Dependency dependency = newDep( "managed:aid:ext:ver" ); + CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) ); + + session.setDependencyManager( new ClassicDependencyManager() ); + + CollectResult result = collector.collectDependencies( session, request ); + + assertEquals( 0, result.getExceptions().size() ); + + DependencyNode root = result.getRoot(); + + assertEquals( dependency, dep( root ) ); + assertEquals( dependency.getArtifact(), dep( root ).getArtifact() ); + + assertEquals( 1, root.getChildren().size() ); + Dependency expect = newDep( "gid:aid:ext:ver", "compile" ); + assertEquals( expect, dep( root, 0 ) ); + + assertEquals( 1, path( root, 0 ).getChildren().size() ); + expect = newDep( "gid:aid2:ext:managedVersion", "managedScope" ); + assertEquals( expect, dep( root, 0, 0 ) ); + } + + @Test + public void testDependencyManagement() + throws IOException, DependencyCollectionException + { + collector.setArtifactDescriptorReader( newReader( "managed/" ) ); + + DependencyNode root = parser.parseResource( "expectedSubtreeComparisonResult.txt" ); + TestDependencyManager depMgmt = new TestDependencyManager(); + depMgmt.add( dep( root, 0 ), "managed", null, null ); + depMgmt.add( dep( root, 0, 1 ), "managed", "managed", null ); + depMgmt.add( dep( root, 1 ), null, null, "managed" ); + session.setDependencyManager( depMgmt ); + + // collect result will differ from expectedSubtreeComparisonResult.txt + // set localPath -> no dependency traversal + CollectRequest request = new CollectRequest( dep( root ), Arrays.asList( repository ) ); + CollectResult result = collector.collectDependencies( session, request ); + + DependencyNode node = result.getRoot(); + assertEquals( "managed", dep( node, 0, 1 ).getArtifact().getVersion() ); + assertEquals( "managed", dep( node, 0, 1 ).getScope() ); + + assertEquals( "managed", dep( node, 1 ).getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null ) ); + assertEquals( "managed", dep( node, 0, 0 ).getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null ) ); + } + + @Test + public void testDependencyManagement_VerboseMode() + throws Exception + { + String depId = "gid:aid2:ext"; + TestDependencyManager depMgmt = new TestDependencyManager(); + depMgmt.version( depId, "managedVersion" ); + depMgmt.scope( depId, "managedScope" ); + depMgmt.optional( depId, Boolean.TRUE ); + depMgmt.path( depId, "managedPath" ); + depMgmt.exclusions( depId, new Exclusion( "gid", "aid", "*", "*" ) ); + session.setDependencyManager( depMgmt ); + session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, Boolean.TRUE ); + + CollectRequest request = new CollectRequest().setRoot( newDep( "gid:aid:ver" ) ); + CollectResult result = collector.collectDependencies( session, request ); + DependencyNode node = result.getRoot().getChildren().get( 0 ); + assertEquals( DependencyNode.MANAGED_VERSION | DependencyNode.MANAGED_SCOPE | DependencyNode.MANAGED_OPTIONAL + | DependencyNode.MANAGED_PROPERTIES | DependencyNode.MANAGED_EXCLUSIONS, node.getManagedBits() ); + assertEquals( "ver", DependencyManagerUtils.getPremanagedVersion( node ) ); + assertEquals( "compile", DependencyManagerUtils.getPremanagedScope( node ) ); + assertEquals( Boolean.FALSE, DependencyManagerUtils.getPremanagedOptional( node ) ); + } + + @Test + public void testDependencyManagement_TransitiveDependencyManager() + throws DependencyCollectionException, IOException + { + collector.setArtifactDescriptorReader( newReader( "managed/" ) ); + parser = new DependencyGraphParser( "artifact-descriptions/managed/" ); + session.setDependencyManager( new TransitiveDependencyManager() ); + final Dependency root = newDep( "gid:root:ext:ver", "compile" ); + CollectRequest request = new CollectRequest( root, Collections.singletonList( repository ) ); + request.addManagedDependency( newDep( "gid:root:ext:must-retain-core-management" ) ); + CollectResult result = collector.collectDependencies( session, request ); + + final DependencyNode expectedTree = parser.parseResource( "management-tree.txt" ); + assertEqualSubtree( expectedTree, result.getRoot() ); + + // Same test for root artifact (POM) request. + final CollectRequest rootArtifactRequest = new CollectRequest(); + rootArtifactRequest.setRepositories( Collections.singletonList( repository ) ); + rootArtifactRequest.setRootArtifact( new DefaultArtifact( "gid:root:ext:ver" ) ); + rootArtifactRequest.addDependency( newDep( "gid:direct:ext:ver", "compile" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:root:ext:must-retain-core-management" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:direct:ext:must-retain-core-management" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:transitive-1:ext:managed-by-root" ) ); + session.setDependencyManager( new TransitiveDependencyManager() ); + result = collector.collectDependencies( session, rootArtifactRequest ); + assertEqualSubtree( expectedTree, toDependencyResult( result.getRoot(), "compile", null ) ); + } + + @Test + public void testDependencyManagement_DefaultDependencyManager() + throws DependencyCollectionException, IOException + { + collector.setArtifactDescriptorReader( newReader( "managed/" ) ); + parser = new DependencyGraphParser( "artifact-descriptions/managed/" ); + session.setDependencyManager( new DefaultDependencyManager() ); + final Dependency root = newDep( "gid:root:ext:ver", "compile" ); + CollectRequest request = new CollectRequest( root, Arrays.asList( repository ) ); + request.addManagedDependency( newDep( "gid:root:ext:must-not-manage-root" ) ); + request.addManagedDependency( newDep( "gid:direct:ext:managed-by-dominant-request" ) ); + CollectResult result = collector.collectDependencies( session, request ); + + final DependencyNode expectedTree = parser.parseResource( "default-management-tree.txt" ); + assertEqualSubtree( expectedTree, result.getRoot() ); + + // Same test for root artifact (POM) request. + final CollectRequest rootArtifactRequest = new CollectRequest(); + rootArtifactRequest.setRepositories( Arrays.asList( repository ) ); + rootArtifactRequest.setRootArtifact( new DefaultArtifact( "gid:root:ext:ver" ) ); + rootArtifactRequest.addDependency( newDep( "gid:direct:ext:ver", "compile" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:root:ext:must-not-manage-root" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:direct:ext:managed-by-dominant-request" ) ); + rootArtifactRequest.addManagedDependency( newDep( "gid:transitive-1:ext:managed-by-root" ) ); + session.setDependencyManager( new DefaultDependencyManager() ); + result = collector.collectDependencies( session, rootArtifactRequest ); + assertEqualSubtree( expectedTree, toDependencyResult( result.getRoot(), "compile", null ) ); + } + + private DependencyNode toDependencyResult( final DependencyNode root, final String rootScope, + final Boolean optional ) + { + // Make the root artifact resultion result a dependency resolution result for the subtree check. + assertNull( "Expected root artifact resolution result.", root.getDependency() ); + final DefaultDependencyNode defaultNode = + new DefaultDependencyNode( new Dependency( root.getArtifact(), rootScope ) ); + + defaultNode.setChildren( root.getChildren() ); + + if ( optional != null ) + { + defaultNode.setOptional( optional ); + } + + return defaultNode; + } + + @Test + public void testVersionFilter() + throws Exception + { + session.setVersionFilter( new HighestVersionFilter() ); + CollectRequest request = new CollectRequest().setRoot( newDep( "gid:aid:1" ) ); + CollectResult result = collector.collectDependencies( session, request ); + assertEquals( 1, result.getRoot().getChildren().size() ); + } + + static class TestDependencyManager + implements DependencyManager + { + + private Map versions = new HashMap<>(); + + private Map scopes = new HashMap<>(); + + private Map optionals = new HashMap<>(); + + private Map paths = new HashMap<>(); + + private Map> exclusions = new HashMap<>(); + + public void add( Dependency d, String version, String scope, String localPath ) + { + String id = toKey( d ); + version( id, version ); + scope( id, scope ); + path( id, localPath ); + } + + public void version( String id, String version ) + { + versions.put( id, version ); + } + + public void scope( String id, String scope ) + { + scopes.put( id, scope ); + } + + public void optional( String id, Boolean optional ) + { + optionals.put( id, optional ); + } + + public void path( String id, String path ) + { + paths.put( id, path ); + } + + public void exclusions( String id, Exclusion... exclusions ) + { + this.exclusions.put( id, exclusions != null ? Arrays.asList( exclusions ) : null ); + } + + public DependencyManagement manageDependency( Dependency dependency ) + { + requireNonNull( dependency, "dependency cannot be null" ); + String id = toKey( dependency ); + DependencyManagement mgmt = new DependencyManagement(); + mgmt.setVersion( versions.get( id ) ); + mgmt.setScope( scopes.get( id ) ); + mgmt.setOptional( optionals.get( id ) ); + String path = paths.get( id ); + if ( path != null ) + { + mgmt.setProperties( Collections.singletonMap( ArtifactProperties.LOCAL_PATH, path ) ); + } + mgmt.setExclusions( exclusions.get( id ) ); + return mgmt; + } + + private String toKey( Dependency dependency ) + { + return ArtifactIdUtils.toVersionlessId( dependency.getArtifact() ); + } + + public DependencyManager deriveChildManager( DependencyCollectionContext context ) + { + requireNonNull( context, "context cannot be null" ); + return this; + } + + } + +} \ No newline at end of file diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 6c3a20d0f..1524530a1 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -42,9 +42,10 @@ Option | Type | Description | Default Value | Supports Repo ID Suffix `aether.connector.smartChecksums` | boolean | Flag indicating that instead of comparing the external checksum fetched from the remote repo with the calculated one, it should try to extract the reference checksum from the actual artifact requests's response headers (several (strategies supported)[included-checksum-strategies.html]). This only works for transport-http transport. | `true` | no `aether.connector.userAgent` | String | The user agent that repository connectors should report to servers. | `"Aether"` | no `aether.connector.wagon.config` | Object | The configuration to use for the Wagon provider. | - | yes (must be used) -`aether.dependencyCollector.useSkip` | boolean | Flag controlling whether to skip resolving duplicate/conflicting nodes during the dependency collection process. | `true` | no `aether.dependencyCollector.maxCycles` | int | Only up to the given amount cyclic dependencies are emitted. | `10` | no `aether.dependencyCollector.maxExceptions` | int | Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed that number are swallowed. | `50` | no +`aether.dependencyCollector.impl` | String | The name of the dependency collector implementation to use: depth-first (original) named `df`, and breadth-first (new in 1.8.0) named `bf`. Both collectors produce equivalent results, but they may differ performance wise, depending on project being applied to. Our experience shows that existing `df` is well suited for smaller to medium size projects, while `bf` may perform better on huge projects with many dependencies. Experiment (and come back to us!) to figure out which one suits you the better. | `"df"` | no +`aether.dependencyCollector.bf.skipper` | boolean | Flag controlling whether to skip resolving duplicate/conflicting nodes during the breadth-first (`bf`) dependency collection process. | `true` | no `aether.dependencyManager.verbose` | boolean | Flag controlling the verbose mode for dependency management. If enabled, the original attributes of a dependency before its update due to dependency managemnent will be recorded in the node's `DependencyNode#getData()` when building a dependency graph. | `false` | no `aether.enhancedLocalRepository.trackingFilename` | String | Filename of the file in which to track the remote repositories. | `"_remote.repositories"` | no `aether.interactive` | boolean | A flag indicating whether interaction with the user is allowed. | `false` | no