Author: bentmann Date: Wed Jul 29 13:48:19 2009 New Revision: 798906 URL: http://svn.apache.org/viewvc?rev=798906&view=rev Log: [MNG-3814] Reactor builds fail due to erroneous cycle in project sorting which does not consider versions
Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/ProjectSorter.java maven/components/trunk/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java Modified: maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/ProjectSorter.java URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/ProjectSorter.java?rev=798906&r1=798905&r2=798906&view=diff ============================================================================== --- maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/ProjectSorter.java (original) +++ maven/components/trunk/maven-core/src/main/java/org/apache/maven/execution/ProjectSorter.java Wed Jul 29 13:48:19 2009 @@ -29,10 +29,15 @@ import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.model.Dependency; +import org.apache.maven.model.Extension; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Plugin; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.dag.CycleDetectedException; import org.codehaus.plexus.util.dag.DAG; import org.codehaus.plexus.util.dag.TopologicalSorter; +import org.codehaus.plexus.util.dag.Vertex; public class ProjectSorter { @@ -70,81 +75,88 @@ { dag = new DAG(); - Map<String,MavenProject> projectMap = new HashMap<String,MavenProject>(); + // groupId:artifactId:version -> project + Map<String, MavenProject> projectMap = new HashMap<String, MavenProject>( projects.size() * 2 ); + + // groupId:artifactId -> (version -> vertex) + Map<String, Map<String, Vertex>> vertexMap = new HashMap<String, Map<String, Vertex>>( projects.size() * 2 ); for ( MavenProject project : projects ) { - String id = getId( project ); + String projectId = getId( project ); - if ( dag.getVertex( id ) != null ) - { - MavenProject conflictingProject = projectMap.get( id ); + MavenProject conflictingProject = projectMap.put( projectId, project ); - throw new DuplicateProjectException( id, conflictingProject.getFile(), project.getFile(), "Project '" + id + "' is duplicated in the reactor" ); + if ( conflictingProject != null ) + { + throw new DuplicateProjectException( projectId, conflictingProject.getFile(), project.getFile(), + "Project '" + projectId + "' is duplicated in the reactor" ); } - dag.addVertex( id ); + String projectKey = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ); - projectMap.put( id, project ); + Map<String, Vertex> vertices = vertexMap.get( projectKey ); + if ( vertices == null ) + { + vertices = new HashMap<String, Vertex>( 2, 1 ); + vertexMap.put( projectKey, vertices ); + } + vertices.put( project.getVersion(), dag.addVertex( projectId ) ); } - for ( MavenProject project : projects ) + for ( Vertex projectVertex : (List<Vertex>) dag.getVerticies() ) { - String id = getId( project ); + String projectId = projectVertex.getLabel(); - for( Dependency dependency : project.getDependencies() ) + MavenProject project = projectMap.get( projectId ); + + for ( Dependency dependency : project.getDependencies() ) { - String dependencyId = ArtifactUtils.versionlessKey( dependency.getGroupId(), dependency.getArtifactId() ); + addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(), + dependency.getArtifactId(), dependency.getVersion(), false, false ); + } - if ( dag.getVertex( dependencyId ) != null ) - { - project.addProjectReference( projectMap.get( dependencyId ) ); + Parent parent = project.getModel().getParent(); - dag.addEdge( id, dependencyId ); - } + if ( parent != null ) + { + // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has in conflict + addEdge( projectMap, vertexMap, null, projectVertex, parent.getGroupId(), parent.getArtifactId(), + parent.getVersion(), true, false ); } - MavenProject parent = project.getParent(); - - if ( parent != null ) + List<Plugin> buildPlugins = project.getBuildPlugins(); + if ( buildPlugins != null ) { - String parentId = ArtifactUtils.versionlessKey( parent.getGroupId(), parent.getArtifactId() ); - if ( dag.getVertex( parentId ) != null ) + for ( Plugin plugin : buildPlugins ) { - // Parent is added as an edge, but must not cause a cycle - so we remove any other edges it has in conflict - if ( dag.hasEdge( parentId, id ) ) + addEdge( projectMap, vertexMap, project, projectVertex, plugin.getGroupId(), + plugin.getArtifactId(), plugin.getVersion(), false, true ); + + for ( Dependency dependency : plugin.getDependencies() ) { - dag.removeEdge( parentId, id ); + addEdge( projectMap, vertexMap, project, projectVertex, dependency.getGroupId(), + dependency.getArtifactId(), dependency.getVersion(), false, true ); } - - dag.addEdge( id, parentId ); } } - - /* - - TODO: Now that the build plan is fully fleshed out we have cycles - - if ( project.getBuildPlugins() != null ) + + List<Extension> buildExtensions = project.getBuildExtensions(); + if ( buildExtensions != null ) { - for( Plugin plugin : project.getBuildPlugins() ) + for ( Extension extension : buildExtensions ) { - String pluginId = ArtifactUtils.versionlessKey( plugin.getGroupId(), plugin.getArtifactId() ); - - if ( ( dag.getVertex( pluginId ) != null ) && !pluginId.equals( id ) ) - { - addEdgeWithParentCheck( projectMap, pluginId, project, id ); - } + addEdge( projectMap, vertexMap, project, projectVertex, extension.getGroupId(), + extension.getArtifactId(), extension.getVersion(), false, true ); } } - */ } - List<MavenProject> sortedProjects = new ArrayList<MavenProject>(); + List<MavenProject> sortedProjects = new ArrayList<MavenProject>( projects.size() ); List<String> sortedProjectLabels = TopologicalSorter.sort( dag ); - - for( String id : sortedProjectLabels ) + + for ( String id : sortedProjectLabels ) { sortedProjects.add( projectMap.get( id ) ); } @@ -152,30 +164,73 @@ this.sortedProjects = Collections.unmodifiableList( sortedProjects ); } - private void addEdgeWithParentCheck( Map<String,MavenProject> projectMap, String projectRefId, MavenProject project, String id ) + private void addEdge( Map<String, MavenProject> projectMap, Map<String, Map<String, Vertex>> vertexMap, + MavenProject project, Vertex projectVertex, String groupId, String artifactId, + String version, boolean force, boolean safe ) throws CycleDetectedException { - MavenProject extProject = projectMap.get( projectRefId ); + String projectKey = ArtifactUtils.versionlessKey( groupId, artifactId ); + + Map<String, Vertex> vertices = vertexMap.get( projectKey ); - if ( extProject == null ) + if ( vertices != null ) + { + if ( isSpecificVersion( version ) ) + { + Vertex vertex = vertices.get( version ); + if ( vertex != null ) + { + addEdge( projectVertex, vertex, project, projectMap, force, safe ); + } + } + else + { + for ( Vertex vertex : vertices.values() ) + { + addEdge( projectVertex, vertex, project, projectMap, force, safe ); + } + } + } + } + + private void addEdge( Vertex fromVertex, Vertex toVertex, MavenProject fromProject, + Map<String, MavenProject> projectMap, boolean force, boolean safe ) + throws CycleDetectedException + { + if ( fromVertex.equals( toVertex ) ) { return; } - project.addProjectReference( extProject ); + if ( fromProject != null ) + { + MavenProject toProject = projectMap.get( toVertex.getLabel() ); + fromProject.addProjectReference( toProject ); + } + + if ( force && toVertex.getChildren().contains( fromVertex ) ) + { + dag.removeEdge( toVertex, fromVertex ); + } - MavenProject extParent = extProject.getParent(); - if ( extParent != null ) + try { - String parentId = ArtifactUtils.versionlessKey( extParent.getGroupId(), extParent.getArtifactId() ); - // Don't add edge from parent to extension if a reverse edge already exists - if ( !dag.hasEdge( projectRefId, id ) || !parentId.equals( id ) ) + dag.addEdge( fromVertex, toVertex ); + } + catch ( CycleDetectedException e ) + { + if ( !safe ) { - dag.addEdge( id, projectRefId ); + throw e; } } } + private boolean isSpecificVersion( String version ) + { + return !( StringUtils.isEmpty( version ) || version.startsWith( "[" ) || version.startsWith( "(" ) ); + } + // TODO: !![jc; 28-jul-2005] check this; if we're using '-r' and there are aggregator tasks, this will result in weirdness. public MavenProject getTopLevelProject() { @@ -216,7 +271,7 @@ public static String getId( MavenProject project ) { - return ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ); + return ArtifactUtils.key( project.getGroupId(), project.getArtifactId(), project.getVersion() ); } } Modified: maven/components/trunk/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java URL: http://svn.apache.org/viewvc/maven/components/trunk/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java?rev=798906&r1=798905&r2=798906&view=diff ============================================================================== --- maven/components/trunk/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java (original) +++ maven/components/trunk/maven-core/src/test/java/org/apache/maven/execution/ProjectSorterTest.java Wed Jul 29 13:48:19 2009 @@ -29,6 +29,9 @@ import org.apache.maven.model.Dependency; import org.apache.maven.model.Extension; import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginManagement; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.dag.CycleDetectedException; @@ -42,25 +45,115 @@ extends TestCase { - public void testShouldNotFailWhenProjectReferencesNonExistentProject() + private Parent createParent( MavenProject project ) + { + return createParent( project.getGroupId(), project.getArtifactId(), project.getVersion() ); + } + + private Parent createParent( String groupId, String artifactId, String version ) + { + Parent plugin = new Parent(); + plugin.setGroupId( groupId ); + plugin.setArtifactId( artifactId ); + plugin.setVersion( version ); + return plugin; + } + + private Dependency createDependency( MavenProject project ) + { + return createDependency( project.getGroupId(), project.getArtifactId(), project.getVersion() ); + } + + private Dependency createDependency( String groupId, String artifactId, String version ) + { + Dependency depdendency = new Dependency(); + depdendency.setGroupId( groupId ); + depdendency.setArtifactId( artifactId ); + depdendency.setVersion( version ); + return depdendency; + } + + private Plugin createPlugin( MavenProject project ) + { + return createPlugin( project.getGroupId(), project.getArtifactId(), project.getVersion() ); + } + + private Plugin createPlugin( String groupId, String artifactId, String version ) + { + Plugin plugin = new Plugin(); + plugin.setGroupId( groupId ); + plugin.setArtifactId( artifactId ); + plugin.setVersion( version ); + return plugin; + } + + private Extension createExtension( String groupId, String artifactId, String version ) + { + Extension extension = new Extension(); + extension.setGroupId( groupId ); + extension.setArtifactId( artifactId ); + extension.setVersion( version ); + return extension; + } + + private static MavenProject createProject( String groupId, String artifactId, String version ) + { + Model model = new Model(); + model.setGroupId( groupId ); + model.setArtifactId( artifactId ); + model.setVersion( version ); + model.setBuild( new Build() ); + return new MavenProject( model ); + } + + public void testShouldNotFailWhenPluginDepReferencesCurrentProject() throws CycleDetectedException, DuplicateProjectException { MavenProject project = createProject( "group", "artifact", "1.0" ); - Model model = project.getModel(); - Build build = model.getBuild(); + Build build = project.getModel().getBuild(); - if ( build == null ) - { - build = new Build(); - model.setBuild( build ); - } + Plugin plugin = createPlugin( "other.group", "other-artifact", "1.0" ); - Extension extension = new Extension(); + Dependency dep = createDependency( "group", "artifact", "1.0" ); + + plugin.addDependency( dep ); + + build.addPlugin( plugin ); + + new ProjectSorter( Collections.singletonList( project ) ); + } + + public void testShouldNotFailWhenManagedPluginDepReferencesCurrentProject() + throws CycleDetectedException, DuplicateProjectException + { + MavenProject project = createProject( "group", "artifact", "1.0" ); + + Build build = project.getModel().getBuild(); + + PluginManagement pMgmt = new PluginManagement(); + + Plugin plugin = createPlugin( "other.group", "other-artifact", "1.0" ); + + Dependency dep = createDependency( "group", "artifact", "1.0" ); - extension.setArtifactId( "other-artifact" ); - extension.setGroupId( "other.group" ); - extension.setVersion( "1.0" ); + plugin.addDependency( dep ); + + pMgmt.addPlugin( plugin ); + + build.setPluginManagement( pMgmt ); + + new ProjectSorter( Collections.singletonList( project ) ); + } + + public void testShouldNotFailWhenProjectReferencesNonExistentProject() + throws CycleDetectedException, DuplicateProjectException + { + MavenProject project = createProject( "group", "artifact", "1.0" ); + + Build build = project.getModel().getBuild(); + + Extension extension = createExtension( "other.group", "other-artifact", "1.0" ); build.addExtension( extension ); @@ -70,7 +163,7 @@ public void testMatchingArtifactIdsDifferentGroupIds() throws CycleDetectedException, DuplicateProjectException { - List projects = new ArrayList(); + List<MavenProject> projects = new ArrayList<MavenProject>(); MavenProject project1 = createProject( "groupId1", "artifactId", "1.0" ); projects.add( project1 ); MavenProject project2 = createProject( "groupId2", "artifactId", "1.0" ); @@ -86,7 +179,7 @@ public void testMatchingGroupIdsDifferentArtifactIds() throws CycleDetectedException, DuplicateProjectException { - List projects = new ArrayList(); + List<MavenProject> projects = new ArrayList<MavenProject>(); MavenProject project1 = createProject( "groupId", "artifactId1", "1.0" ); projects.add( project1 ); MavenProject project2 = createProject( "groupId", "artifactId2", "1.0" ); @@ -102,7 +195,7 @@ public void testMatchingIdsAndVersions() throws CycleDetectedException { - List projects = new ArrayList(); + List<MavenProject> projects = new ArrayList<MavenProject>(); MavenProject project1 = createProject( "groupId", "artifactId", "1.0" ); projects.add( project1 ); MavenProject project2 = createProject( "groupId", "artifactId", "1.0" ); @@ -121,41 +214,152 @@ } public void testMatchingIdsAndDifferentVersions() - throws CycleDetectedException + throws CycleDetectedException, DuplicateProjectException { - List projects = new ArrayList(); + List<MavenProject> projects = new ArrayList<MavenProject>(); MavenProject project1 = createProject( "groupId", "artifactId", "1.0" ); projects.add( project1 ); MavenProject project2 = createProject( "groupId", "artifactId", "2.0" ); projects.add( project2 ); - try - { - projects = new ProjectSorter( projects ).getSortedProjects(); - fail( "Duplicate projects should fail" ); - } - catch ( DuplicateProjectException e ) - { - // expected - assertTrue( true ); - } + projects = new ProjectSorter( projects ).getSortedProjects(); + assertEquals( project1, projects.get( 0 ) ); + assertEquals( project2, projects.get( 1 ) ); } - private Dependency createDependency( MavenProject project ) + public void testPluginDependenciesInfluenceSorting() + throws Exception { - Dependency depdendency = new Dependency(); - depdendency.setArtifactId( project.getArtifactId() ); - depdendency.setGroupId( project.getGroupId() ); - depdendency.setVersion( project.getVersion() ); - return depdendency; + List<MavenProject> projects = new ArrayList<MavenProject>(); + + MavenProject parentProject = createProject( "groupId", "parent", "1.0" ); + projects.add( parentProject ); + + MavenProject declaringProject = createProject( "groupId", "declarer", "1.0" ); + declaringProject.setParent( parentProject ); + declaringProject.getModel().setParent( createParent( parentProject ) ); + projects.add( declaringProject ); + + MavenProject pluginLevelDepProject = createProject( "groupId", "plugin-level-dep", "1.0" ); + pluginLevelDepProject.setParent( parentProject ); + pluginLevelDepProject.getModel().setParent( createParent( parentProject ) ); + projects.add( pluginLevelDepProject ); + + MavenProject pluginProject = createProject( "groupId", "plugin", "1.0" ); + pluginProject.setParent( parentProject ); + pluginProject.getModel().setParent( createParent( parentProject ) ); + projects.add( pluginProject ); + + Plugin plugin = createPlugin( pluginProject ); + + plugin.addDependency( createDependency( pluginLevelDepProject ) ); + + Build build = declaringProject.getModel().getBuild(); + + build.addPlugin( plugin ); + + projects = new ProjectSorter( projects ).getSortedProjects(); + + assertEquals( parentProject, projects.get( 0 ) ); + + // the order of these two is non-deterministic, based on when they're added to the reactor. + assertTrue( projects.contains( pluginProject ) ); + assertTrue( projects.contains( pluginLevelDepProject ) ); + + // the declaring project MUST be listed after the plugin and its plugin-level dep, though. + assertEquals( declaringProject, projects.get( 3 ) ); } - private static MavenProject createProject( String groupId, String artifactId, String version ) + public void testPluginDependenciesInfluenceSorting_DeclarationInParent() + throws Exception { - Model model = new Model(); - model.setGroupId( groupId ); - model.setArtifactId( artifactId ); - model.setVersion( version ); - return new MavenProject( model ); + List<MavenProject> projects = new ArrayList<MavenProject>(); + + MavenProject parentProject = createProject( "groupId", "parent-declarer", "1.0" ); + projects.add( parentProject ); + + MavenProject pluginProject = createProject( "groupId", "plugin", "1.0" ); + pluginProject.setParent( parentProject ); + pluginProject.getModel().setParent( createParent( parentProject ) ); + projects.add( pluginProject ); + + MavenProject pluginLevelDepProject = createProject( "groupId", "plugin-level-dep", "1.0" ); + pluginLevelDepProject.setParent( parentProject ); + pluginLevelDepProject.getModel().setParent( createParent( parentProject ) ); + projects.add( pluginLevelDepProject ); + + Plugin plugin = createPlugin( pluginProject ); + + plugin.addDependency( createDependency( pluginLevelDepProject ) ); + + Build build = parentProject.getModel().getBuild(); + + build.addPlugin( plugin ); + + projects = new ProjectSorter( projects ).getSortedProjects(); + + System.out.println( projects ); + + assertEquals( parentProject, projects.get( 0 ) ); + + // the order of these two is non-deterministic, based on when they're added to the reactor. + assertTrue( projects.contains( pluginProject ) ); + assertTrue( projects.contains( pluginLevelDepProject ) ); } + + public void testPluginVersionsAreConsidered() + throws Exception + { + List<MavenProject> projects = new ArrayList<MavenProject>(); + + MavenProject pluginProjectA = createProject( "group", "plugin-a", "2.0-SNAPSHOT" ); + projects.add( pluginProjectA ); + pluginProjectA.getModel().getBuild().addPlugin( createPlugin( "group", "plugin-b", "1.0" ) ); + + MavenProject pluginProjectB = createProject( "group", "plugin-b", "2.0-SNAPSHOT" ); + projects.add( pluginProjectB ); + pluginProjectB.getModel().getBuild().addPlugin( createPlugin( "group", "plugin-a", "1.0" ) ); + + projects = new ProjectSorter( projects ).getSortedProjects(); + + assertTrue( projects.contains( pluginProjectA ) ); + assertTrue( projects.contains( pluginProjectB ) ); + } + + public void testDependencyPrecedesProjectThatUsesSpecificDependencyVersion() + throws Exception + { + List<MavenProject> projects = new ArrayList<MavenProject>(); + + MavenProject usingProject = createProject( "group", "project", "1.0" ); + projects.add( usingProject ); + usingProject.getModel().addDependency( createDependency( "group", "dependency", "1.0" ) ); + + MavenProject pluginProject = createProject( "group", "dependency", "1.0" ); + projects.add( pluginProject ); + + projects = new ProjectSorter( projects ).getSortedProjects(); + + assertEquals( pluginProject, projects.get( 0 ) ); + assertEquals( usingProject, projects.get( 1 ) ); + } + + public void testDependencyPrecedesProjectThatUsesUnresolvedDependencyVersion() + throws Exception + { + List<MavenProject> projects = new ArrayList<MavenProject>(); + + MavenProject usingProject = createProject( "group", "project", "1.0" ); + projects.add( usingProject ); + usingProject.getModel().addDependency( createDependency( "group", "dependency", "[1.0,)" ) ); + + MavenProject pluginProject = createProject( "group", "dependency", "1.0" ); + projects.add( pluginProject ); + + projects = new ProjectSorter( projects ).getSortedProjects(); + + assertEquals( pluginProject, projects.get( 0 ) ); + assertEquals( usingProject, projects.get( 1 ) ); + } + }