This is an automated email from the ASF dual-hosted git repository. michaelo pushed a commit to branch MRESOLVER-10 in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
commit a08cb20e53f206ca720cb850ff392544b73268fb Author: Christian Schulte <c...@schulte.it> AuthorDate: Sat Mar 11 22:38:00 2017 +0100 [MRESOLVER-10] New 'TransitiveDependencyManager' supporting transitive dependency management This closes #35 --- .../collect/DefaultDependencyCollectorTest.java | 49 ++++ .../managed/gid_direct_ver.ini | 5 + .../artifact-descriptions/managed/gid_root_ver.ini | 5 + .../managed/gid_transitive-1_managed-by-root.ini | 5 + .../managed/gid_transitive-2_managed-by-direct.ini | 5 + .../gid_transitive-3_managed-by-transitive-1.ini | 4 + .../gid_transitive-4_managed-by-transitive-2.ini | 1 + .../managed/management-tree.txt | 6 + .../graph/manager/TransitiveDependencyManager.java | 307 +++++++++++++++++++++ 9 files changed, 387 insertions(+) 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/DefaultDependencyCollectorTest.java index 0c10469..232cf9f 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/DefaultDependencyCollectorTest.java @@ -21,6 +21,7 @@ package org.eclipse.aether.internal.impl.collect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; 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; @@ -47,6 +48,7 @@ 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; @@ -64,6 +66,7 @@ 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.DependencyManagerUtils; +import org.eclipse.aether.util.graph.manager.TransitiveDependencyManager; import org.eclipse.aether.util.graph.selector.ScopeDependencySelector; import org.eclipse.aether.util.graph.version.HighestVersionFilter; import org.junit.Before; @@ -481,6 +484,52 @@ public class DefaultDependencyCollectorTest } @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 ) ); + } + + 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 { diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_direct_ver.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_direct_ver.ini new file mode 100644 index 0000000..94ba9fb --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_direct_ver.ini @@ -0,0 +1,5 @@ +[dependencies] +gid:transitive-1:ext:ver +[manageddependencies] +gid:transitive-1:ext:must-retain-core-management +gid:transitive-2:ext:managed-by-direct \ No newline at end of file diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_root_ver.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_root_ver.ini new file mode 100644 index 0000000..15db99e --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_root_ver.ini @@ -0,0 +1,5 @@ +[dependencies] +gid:direct:ext:ver +[manageddependencies] +gid:direct:ext:must-retain-core-management +gid:transitive-1:ext:managed-by-root \ No newline at end of file diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-1_managed-by-root.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-1_managed-by-root.ini new file mode 100644 index 0000000..8fd9f0d --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-1_managed-by-root.ini @@ -0,0 +1,5 @@ +[dependencies] +gid:transitive-2:ext:ver +[manageddependencies] +gid:transitive-2:ext:must-retain-core-management +gid:transitive-3:ext:managed-by-transitive-1 \ No newline at end of file diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-2_managed-by-direct.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-2_managed-by-direct.ini new file mode 100644 index 0000000..96cc8f5 --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-2_managed-by-direct.ini @@ -0,0 +1,5 @@ +[dependencies] +gid:transitive-3:ext:ver +[manageddependencies] +gid:transitive-3:ext:must-retain-core-management +gid:transitive-4:ext:managed-by-transitive-2 \ No newline at end of file diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-3_managed-by-transitive-1.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-3_managed-by-transitive-1.ini new file mode 100644 index 0000000..2b8f3cf --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-3_managed-by-transitive-1.ini @@ -0,0 +1,4 @@ +[dependencies] +gid:transitive-4:ext:ver +[manageddependencies] +gid:transitive-4:ext:must-retain-core-management diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-4_managed-by-transitive-2.ini b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-4_managed-by-transitive-2.ini new file mode 100644 index 0000000..61a252c --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/gid_transitive-4_managed-by-transitive-2.ini @@ -0,0 +1 @@ +[dependencies] diff --git a/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/management-tree.txt b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/management-tree.txt new file mode 100644 index 0000000..1371426 --- /dev/null +++ b/maven-resolver-impl/src/test/resources/artifact-descriptions/managed/management-tree.txt @@ -0,0 +1,6 @@ +gid:root:ext:ver compile ++- gid:direct:ext:ver compile + +- gid:transitive-1:ext:managed-by-root compile + +- gid:transitive-2:ext:managed-by-direct compile + +- gid:transitive-3:ext:managed-by-transitive-1 compile + +- gid:transitive-4:ext:managed-by-transitive-2 compile diff --git a/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java new file mode 100644 index 0000000..3536e42 --- /dev/null +++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/graph/manager/TransitiveDependencyManager.java @@ -0,0 +1,307 @@ +package org.eclipse.aether.util.graph.manager; + +/* + * 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.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.aether.artifact.Artifact; +import org.eclipse.aether.artifact.ArtifactProperties; +import org.eclipse.aether.collection.DependencyCollectionContext; +import org.eclipse.aether.collection.DependencyManagement; +import org.eclipse.aether.collection.DependencyManager; +import org.eclipse.aether.graph.Dependency; +import org.eclipse.aether.graph.Exclusion; +import org.eclipse.aether.util.artifact.JavaScopes; + +/** + * A dependency manager managing transitive dependencies supporting transitive dependency management. + * + * @author Christian Schulte + * @since 1.4.0 + */ +public final class TransitiveDependencyManager + implements DependencyManager +{ + + private final Map<Object, String> managedVersions; + + private final Map<Object, String> managedScopes; + + private final Map<Object, Boolean> managedOptionals; + + private final Map<Object, String> managedLocalPaths; + + private final Map<Object, Collection<Exclusion>> managedExclusions; + + private final int depth; + + private int hashCode; + + /** + * Creates a new dependency manager without any management information. + */ + public TransitiveDependencyManager() + { + this( 0, Collections.<Object, String>emptyMap(), Collections.<Object, String>emptyMap(), + Collections.<Object, Boolean>emptyMap(), Collections.<Object, String>emptyMap(), + Collections.<Object, Collection<Exclusion>>emptyMap() ); + } + + private TransitiveDependencyManager( final int depth, + final Map<Object, String> managedVersions, + final Map<Object, String> managedScopes, + final Map<Object, Boolean> managedOptionals, + final Map<Object, String> managedLocalPaths, + final Map<Object, Collection<Exclusion>> managedExclusions ) + { + super(); + this.depth = depth; + this.managedVersions = managedVersions; + this.managedScopes = managedScopes; + this.managedOptionals = managedOptionals; + this.managedLocalPaths = managedLocalPaths; + this.managedExclusions = managedExclusions; + } + + public DependencyManager deriveChildManager( final DependencyCollectionContext context ) + { + Map<Object, String> versions = managedVersions; + Map<Object, String> scopes = managedScopes; + Map<Object, Boolean> optionals = managedOptionals; + Map<Object, String> localPaths = managedLocalPaths; + Map<Object, Collection<Exclusion>> exclusions = managedExclusions; + + for ( Dependency managedDependency : context.getManagedDependencies() ) + { + Artifact artifact = managedDependency.getArtifact(); + Object key = getKey( artifact ); + + String version = artifact.getVersion(); + if ( version.length() > 0 && !versions.containsKey( key ) ) + { + if ( versions == managedVersions ) + { + versions = new HashMap<>( managedVersions ); + } + versions.put( key, version ); + } + + String scope = managedDependency.getScope(); + if ( scope.length() > 0 && !scopes.containsKey( key ) ) + { + if ( scopes == this.managedScopes ) + { + scopes = new HashMap<>( this.managedScopes ); + } + scopes.put( key, scope ); + } + + Boolean optional = managedDependency.getOptional(); + if ( optional != null && !optionals.containsKey( key ) ) + { + if ( optionals == managedOptionals ) + { + optionals = new HashMap<>( managedOptionals ); + } + optionals.put( key, optional ); + } + + String localPath = managedDependency.getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null ); + if ( localPath != null && !localPaths.containsKey( key ) ) + { + if ( localPaths == this.managedLocalPaths ) + { + localPaths = new HashMap<>( managedLocalPaths ); + } + localPaths.put( key, localPath ); + } + + if ( !managedDependency.getExclusions().isEmpty() ) + { + if ( exclusions == managedExclusions ) + { + exclusions = new HashMap<>( managedExclusions ); + } + Collection<Exclusion> managed = exclusions.get( key ); + if ( managed == null ) + { + managed = new LinkedHashSet<>(); + exclusions.put( key, managed ); + } + managed.addAll( managedDependency.getExclusions() ); + } + } + + return new TransitiveDependencyManager( depth + 1, versions, scopes, optionals, localPaths, + exclusions ); + + } + + public DependencyManagement manageDependency( Dependency dependency ) + { + DependencyManagement management = null; + + Object key = getKey( dependency.getArtifact() ); + + if ( depth >= 2 ) + { + String version = managedVersions.get( key ); + if ( version != null ) + { + management = new DependencyManagement(); + management.setVersion( version ); + } + + String scope = managedScopes.get( key ); + if ( scope != null ) + { + if ( management == null ) + { + management = new DependencyManagement(); + } + management.setScope( scope ); + + if ( !JavaScopes.SYSTEM.equals( scope ) && dependency.getArtifact().getProperty( + ArtifactProperties.LOCAL_PATH, null ) != null ) + { + Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() ); + properties.remove( ArtifactProperties.LOCAL_PATH ); + management.setProperties( properties ); + } + } + + if ( ( JavaScopes.SYSTEM.equals( scope ) ) + || ( scope == null && JavaScopes.SYSTEM.equals( dependency.getScope() ) ) ) + { + String localPath = managedLocalPaths.get( key ); + if ( localPath != null ) + { + if ( management == null ) + { + management = new DependencyManagement(); + } + Map<String, String> properties = new HashMap<>( dependency.getArtifact().getProperties() ); + properties.put( ArtifactProperties.LOCAL_PATH, localPath ); + management.setProperties( properties ); + } + } + + Boolean optional = managedOptionals.get( key ); + if ( optional != null ) + { + if ( management == null ) + { + management = new DependencyManagement(); + } + management.setOptional( optional ); + } + } + + Collection<Exclusion> exclusions = managedExclusions.get( key ); + if ( exclusions != null ) + { + if ( management == null ) + { + management = new DependencyManagement(); + } + Collection<Exclusion> result = new LinkedHashSet<>( dependency.getExclusions() ); + result.addAll( exclusions ); + management.setExclusions( result ); + } + + return management; + } + + private Object getKey( Artifact a ) + { + return new Key( a ); + } + + @Override + public boolean equals( final Object obj ) + { + boolean equal = obj instanceof TransitiveDependencyManager; + + if ( equal ) + { + final TransitiveDependencyManager that = (TransitiveDependencyManager) obj; + return depth == that.depth + && Objects.equals( managedVersions, that.managedVersions ) + && Objects.equals( managedScopes, that.managedScopes ) + && Objects.equals( managedOptionals, that.managedOptionals ) + && Objects.equals( managedExclusions, that.managedExclusions ); + } + + return false; + } + + @Override + public int hashCode() + { + if ( hashCode == 0 ) + { + hashCode = Objects.hash( depth, managedVersions, managedScopes, managedOptionals, managedExclusions ); + } + return hashCode; + } + + static class Key + { + private final Artifact artifact; + + private final int hashCode; + + Key( final Artifact artifact ) + { + this.artifact = artifact; + this.hashCode = Objects.hash( artifact.getGroupId(), artifact.getArtifactId() ); + } + + @Override + public boolean equals( final Object obj ) + { + boolean equal = obj instanceof Key; + + if ( equal ) + { + final Key that = (Key) obj; + return Objects.equals( artifact.getArtifactId(), that.artifact.getArtifactId() ) + && Objects.equals( artifact.getGroupId(), that.artifact.getGroupId() ) + && Objects.equals( artifact.getExtension(), that.artifact.getExtension() ) + && Objects.equals( artifact.getClassifier(), that.artifact.getClassifier() ); + } + + return equal; + } + + @Override + public int hashCode() + { + return this.hashCode; + } + + } + +}