[MNG-5971] Imported dependencies should be available to inheritance processing
o Updated the 'DefaultDependencyManagementImporter' to stop ignoring import dependency conflicts silently. Such conflicts need to be resolved manually by adding the conflicting dependency to the pom manually. o Updated to add support for an 'include' scope in dependency management processed before inheritance and interpolation. o Re-formatted 'DefaultModelBuilder'. o Documentation updates. Project: http://git-wip-us.apache.org/repos/asf/maven/repo Commit: http://git-wip-us.apache.org/repos/asf/maven/commit/f2d3f8f3 Tree: http://git-wip-us.apache.org/repos/asf/maven/tree/f2d3f8f3 Diff: http://git-wip-us.apache.org/repos/asf/maven/diff/f2d3f8f3 Branch: refs/heads/DEPMGMT-INCLUDE-IT Commit: f2d3f8f351347bf2ffb40e8bcb6188787bd25708 Parents: def2738 Author: Christian Schulte <schu...@apache.org> Authored: Thu Feb 18 14:07:02 2016 +0100 Committer: Christian Schulte <schu...@apache.org> Committed: Wed Feb 1 21:30:07 2017 +0100 ---------------------------------------------------------------------- .../model/building/DefaultModelBuilder.java | 384 ++++++++++++++----- .../building/DefaultModelBuildingResult.java | 26 +- .../model/building/ModelBuildingResult.java | 12 + .../DefaultDependencyManagementImporter.java | 213 +++++++++- maven-model-builder/src/site/apt/index.apt | 8 +- 5 files changed, 523 insertions(+), 120 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven/blob/f2d3f8f3/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java ---------------------------------------------------------------------- diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java index 69e95ab..e26fcbc 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuilder.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -82,6 +83,7 @@ import static org.apache.maven.model.building.Result.newResult; public class DefaultModelBuilder implements ModelBuilder { + @Requirement private ModelProcessor modelProcessor; @@ -248,8 +250,8 @@ public class DefaultModelBuilder DefaultProfileActivationContext profileActivationContext = getProfileActivationContext( request ); problems.setSource( "(external profiles)" ); - List<Profile> activeExternalProfiles = profileSelector.getActiveProfiles( request.getProfiles(), - profileActivationContext, problems ); + List<Profile> activeExternalProfiles = + profileSelector.getActiveProfiles( request.getProfiles(), profileActivationContext, problems ); result.setActiveExternalProfiles( activeExternalProfiles ); @@ -296,8 +298,9 @@ public class DefaultModelBuilder profileActivationContext.setProjectProperties( tmpModel.getProperties() ); - List<Profile> activePomProfiles = profileSelector.getActiveProfiles( rawModel.getProfiles(), - profileActivationContext, problems ); + List<Profile> activePomProfiles = + profileSelector.getActiveProfiles( rawModel.getProfiles(), profileActivationContext, problems ); + currentData.setActiveProfiles( activePomProfiles ); Map<String, Activation> interpolatedActivations = getProfileActivations( rawModel, false ); @@ -332,13 +335,13 @@ public class DefaultModelBuilder } else if ( currentData == resultData ) { // First iteration - add initial id after version resolution. - currentData.setGroupId( currentData.getRawModel().getGroupId() == null ? parentData.getGroupId() - : currentData.getRawModel() - .getGroupId() ); + currentData.setGroupId( currentData.getRawModel().getGroupId() == null + ? parentData.getGroupId() + : currentData.getRawModel().getGroupId() ); - currentData.setVersion( currentData.getRawModel().getVersion() == null ? parentData.getVersion() - : currentData.getRawModel() - .getVersion() ); + currentData.setVersion( currentData.getRawModel().getVersion() == null + ? parentData.getVersion() + : currentData.getRawModel().getVersion() ); currentData.setArtifactId( currentData.getRawModel().getArtifactId() ); parentIds.add( currentData.getId() ); @@ -357,8 +360,9 @@ public class DefaultModelBuilder } message += parentData.getId(); - problems.add( new ModelProblemCollectorRequest( ModelProblem.Severity.FATAL, ModelProblem.Version.BASE ) - .setMessage( message ) ); + problems.add( new ModelProblemCollectorRequest( ModelProblem.Severity.FATAL, + ModelProblem.Version.BASE ). + setMessage( message ) ); throw problems.newModelBuildingException(); } @@ -371,24 +375,34 @@ public class DefaultModelBuilder problems.setSource( inputModel ); checkPluginVersions( lineage, request, problems ); - // inheritance assembly - assembleInheritance( lineage, request, problems ); + // [MNG-4052] import scope dependencies prefer to download pom rather than find it in the current project + // [MNG-5971] Imported dependencies should be available to inheritance processing + // + // This first phase of model building is used for building models holding just enough information to map + // groupId:artifactId:version to pom files and to provide modules to build. For this, inheritance and + // interpolation needs to be performed. A temporary model is built in phase 1 applying inheritance and + // interpolation to fill in those values but is not returned. The rest of the model building takes place in + // phase 2. + final DefaultModelProblemCollector intermediateProblems = new DefaultModelProblemCollector( result ); + final List<Model> intermediateLineage = new ArrayList<>( lineage.size() ); + for ( final ModelData modelData : lineage ) + { + intermediateLineage.add( modelData.getModel().clone() ); + } + assembleInheritance( intermediateLineage, request, intermediateProblems ); + + Model intermediateModel = intermediateLineage.get( 0 ); + intermediateModel = interpolateModel( intermediateModel, request, intermediateProblems ); Model resultModel = resultData.getModel(); + resultModel.setGroupId( intermediateModel.getGroupId() ); + resultModel.setArtifactId( intermediateModel.getArtifactId() ); + resultModel.setVersion( intermediateModel.getVersion() ); + problems.setSource( resultModel ); problems.setRootModel( resultModel ); - // model interpolation - resultModel = interpolateModel( resultModel, request, problems ); - resultData.setModel( resultModel ); - - // url normalization - modelUrlNormalizer.normalize( resultModel, request ); - - // Now the fully interpolated model is available: reconfigure the resolver - configureResolver( request.getModelResolver(), resultModel, problems, true ); - resultData.setGroupId( resultModel.getGroupId() ); resultData.setArtifactId( resultModel.getArtifactId() ); resultData.setVersion( resultModel.getVersion() ); @@ -402,6 +416,7 @@ public class DefaultModelBuilder result.addModelId( modelId ); result.setActivePomProfiles( modelId, currentData.getActiveProfiles() ); result.setRawModel( modelId, currentData.getRawModel() ); + result.setEffectiveModel( modelId, currentData.getModel() ); } if ( !request.isTwoPhaseBuilding() ) @@ -416,20 +431,40 @@ public class DefaultModelBuilder public ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result ) throws ModelBuildingException { - return build( request, result, new LinkedHashSet<String>() ); - } - - private ModelBuildingResult build( ModelBuildingRequest request, ModelBuildingResult result, - Collection<String> imports ) - throws ModelBuildingException - { // phase 2 Model resultModel = result.getEffectiveModel(); + // Reset to on-disk values to not suppress any warnings from phase 1. + resultModel.setGroupId( result.getRawModel().getGroupId() ); + resultModel.setArtifactId( result.getRawModel().getArtifactId() ); + resultModel.setVersion( result.getRawModel().getVersion() ); + DefaultModelProblemCollector problems = new DefaultModelProblemCollector( result ); problems.setSource( resultModel ); problems.setRootModel( resultModel ); + final List<Model> lineage = new ArrayList<>( result.getModelIds().size() ); + + for ( final String modelId : result.getModelIds() ) + { + lineage.add( result.getEffectiveModel( modelId ) ); + } + + // [MNG-5971] Imported dependencies should be available to inheritance processing + processImports( lineage, "include", "pom", request, problems ); + problems.setSource( resultModel ); + + // inheritance assembly + assembleInheritance( lineage, request, problems ); + + resultModel = interpolateModel( resultModel, request, problems ); + + // url normalization + modelUrlNormalizer.normalize( resultModel, request ); + + // Now the fully interpolated model is available: reconfigure the resolver + configureResolver( request.getModelResolver(), resultModel, problems, true ); + // model path translation modelPathTranslator.alignToBaseDirectory( resultModel, resultModel.getProjectDirectory(), request ); @@ -449,8 +484,7 @@ public class DefaultModelBuilder lifecycleBindingsInjector.injectLifecycleBindings( resultModel, request, problems ); } - // dependency management import - importDependencyManagement( resultModel, request, problems, imports ); + this.importDependencyManagement( resultModel, "import", request, problems, new HashSet<String>() ); // dependency management injection dependencyManagementInjector.injectManagement( resultModel, request, problems ); @@ -483,10 +517,13 @@ public class DefaultModelBuilder @Override public Result<? extends Model> buildRawModel( File pomFile, int validationLevel, boolean locationTracking ) { - final ModelBuildingRequest request = new DefaultModelBuildingRequest().setValidationLevel( validationLevel ) - .setLocationTracking( locationTracking ); + final ModelBuildingRequest request = new DefaultModelBuildingRequest(). + setValidationLevel( validationLevel ). + setLocationTracking( locationTracking ); + final DefaultModelProblemCollector collector = new DefaultModelProblemCollector( new DefaultModelBuildingResult() ); + try { return newResult( readModel( null, pomFile, request, collector ), collector.getProblems() ); @@ -551,15 +588,17 @@ public class DefaultModelBuilder if ( pomFile != null ) { - problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.V20 ) - .setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() ) - .setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.V20 ). + setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() ). + setException( e ) ); + } else { - problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 ) - .setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() ) - .setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 ). + setMessage( "Malformed POM " + modelSource.getLocation() + ": " + e.getMessage() ). + setException( e ) ); + } } @@ -571,14 +610,16 @@ public class DefaultModelBuilder } catch ( ModelParseException e ) { - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ) - .setMessage( "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage() ) - .setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ). + setMessage( "Non-parseable POM " + modelSource.getLocation() + ": " + e.getMessage() ). + setException( e ) ); + throw problems.newModelBuildingException(); } catch ( IOException e ) { String msg = e.getMessage(); + if ( msg == null || msg.length() <= 0 ) { // NOTE: There's java.nio.charset.MalformedInputException and sun.io.MalformedInputException @@ -591,14 +632,18 @@ public class DefaultModelBuilder msg = e.getClass().getSimpleName(); } } - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ) - .setMessage( "Non-readable POM " + modelSource.getLocation() + ": " + msg ).setException( e ) ); + + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ). + setMessage( "Non-readable POM " + modelSource.getLocation() + ": " + msg ). + setException( e ) ); + throw problems.newModelBuildingException(); } model.setPomFile( pomFile ); problems.setSource( model ); + modelValidator.validateRawModel( model, request, problems ); if ( hasFatalErrors( problems ) ) @@ -647,9 +692,11 @@ public class DefaultModelBuilder } catch ( InvalidRepositoryException e ) { - problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) - .setMessage( "Invalid repository " + repository.getId() + ": " + e.getMessage() ) - .setLocation( repository.getLocation( "" ) ).setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ). + setMessage( "Invalid repository " + repository.getId() + ": " + e.getMessage() ). + setLocation( repository.getLocation( "" ) ). + setException( e ) ); + } } } @@ -701,21 +748,150 @@ public class DefaultModelBuilder if ( versions.get( key ) == null && managedVersions.get( key ) == null ) { InputLocation location = plugins.get( key ).getLocation( "" ); - problems - .add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 ) - .setMessage( "'build.plugins.plugin.version' for " + key + " is missing." ) - .setLocation( location ) ); + problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.V20 ). + setMessage( "'build.plugins.plugin.version' for " + key + " is missing." ). + setLocation( location ) ); + } } } - private void assembleInheritance( List<ModelData> lineage, ModelBuildingRequest request, + private void processImports( final List<Model> lineage, final String scope, final String packaging, + final ModelBuildingRequest request, final DefaultModelProblemCollector problems ) + { + // [MNG-5971] Imported dependencies should be available to inheritance processing + // It's not possible to support all ${project.xyz} properties in dependency management import declarations + // because import processing is performed before the final inheritance processing is performed. So the set of + // ${project.xyz} properties supported in dependency management import declarations is limited. + + final List<Model> intermediateLineage = new ArrayList<>( lineage.size() ); + + for ( int i = 0, s0 = lineage.size(); i < s0; i++ ) + { + intermediateLineage.add( lineage.get( i ).clone() ); + } + + for ( int i = intermediateLineage.size() - 2; i >= 0; i-- ) + { + final Model parent = intermediateLineage.get( i + 1 ); + final Model child = intermediateLineage.get( i ); + + if ( child.getGroupId() == null ) + { + // Support ${project.groupId} in dependency management import declarations. + child.setGroupId( parent.getGroupId() ); + } + if ( child.getVersion() == null ) + { + // Support ${project.version} in dependency management import declarations. + child.setVersion( parent.getVersion() ); + } + + final Properties properties = new Properties(); + properties.putAll( parent.getProperties() ); + properties.putAll( child.getProperties() ); + child.setProperties( properties ); + + final List<Repository> repositories = new ArrayList<>(); + repositories.addAll( child.getRepositories() ); + + for ( final Repository parentRepository : parent.getRepositories() ) + { + if ( !repositories.contains( parentRepository ) ) + { + repositories.add( parentRepository ); + } + } + + child.setRepositories( repositories ); + } + + final Properties effectiveProperties = intermediateLineage.get( 0 ).getProperties(); + + final DefaultModelProblemCollector intermediateProblems = + new DefaultModelProblemCollector( new DefaultModelBuildingResult() ); + + // Interpolates the intermediate model. + // MNG-6079: Uses the effective properties of the result model to support property overriding. + for ( int i = 0, s0 = intermediateLineage.size(); i < s0; i++ ) + { + final Model model = intermediateLineage.get( i ); + model.setProperties( effectiveProperties ); + intermediateProblems.setSource( model ); + this.interpolateModel( model, request, intermediateProblems ); + } + + // Exchanges 'include' scope dependencies in the original lineage with possibly interpolated values. + for ( int i = 0, s0 = lineage.size(); i < s0; i++ ) + { + final Model model = lineage.get( i ); + + if ( model.getDependencyManagement() != null ) + { + for ( int j = 0, s1 = model.getDependencyManagement().getDependencies().size(); j < s1; j++ ) + { + final Dependency dependency = model.getDependencyManagement().getDependencies().get( j ); + + if ( scope.equals( dependency.getScope() ) && packaging.equals( dependency.getType() ) ) + { + final Dependency interpolated = + intermediateLineage.get( i ).getDependencyManagement().getDependencies().get( j ); + + model.getDependencyManagement().getDependencies().set( j, interpolated ); + } + } + } + } + + // [MNG-4488] [regression] Parent POMs resolved from repository are validated in strict mode + ModelBuildingRequest lenientRequest = request; + if ( request.getValidationLevel() > ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 ) + { + lenientRequest = new FilterModelBuildingRequest( request ) + { + + @Override + public int getValidationLevel() + { + return ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0; + } + + }; + } + + // Sets up the resolver to use the effective repositories to support repository overriding. + if ( lenientRequest.getModelResolver() != null ) + { + for ( Repository repository : intermediateLineage.get( 0 ).getRepositories() ) + { + try + { + lenientRequest.getModelResolver().addRepository( repository, true ); + } + catch ( InvalidRepositoryException e ) + { + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) + .setMessage( "Invalid repository " + repository.getId() + ": " + e.getMessage() ) + .setLocation( repository.getLocation( "" ) ).setException( e ) ); + + } + } + } + + // Imports dependencies into the original model using the effective repositories. + for ( int i = 0, s0 = lineage.size(); i < s0; i++ ) + { + this.importDependencyManagement( lineage.get( i ), scope, lenientRequest, problems, new HashSet<String>() ); + } + } + + private void assembleInheritance( List<Model> lineage, ModelBuildingRequest request, ModelProblemCollector problems ) { for ( int i = lineage.size() - 2; i >= 0; i-- ) { - Model parent = lineage.get( i + 1 ).getModel(); - Model child = lineage.get( i ).getModel(); + Model parent = lineage.get( i + 1 ); + Model child = lineage.get( i ); inheritanceAssembler.assembleModelInheritance( child, parent, request, problems ); } } @@ -815,7 +991,7 @@ public class DefaultModelBuilder ModelSource expectedParentSource = getParentPomFile( childModel, childSource ); if ( expectedParentSource instanceof ModelSource2 - && !pomFile.toURI().equals( ( (ModelSource2) expectedParentSource ).getLocationURI() ) ) + && !pomFile.toURI().equals( ( (ModelSource2) expectedParentSource ).getLocationURI() ) ) { parentData = readParentExternally( childModel, request, problems ); } @@ -826,10 +1002,11 @@ public class DefaultModelBuilder if ( !"pom".equals( parentModel.getPackaging() ) ) { - problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) - .setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel ) - + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" ) - .setLocation( parentModel.getLocation( "packaging" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ). + setMessage( "Invalid packaging for parent POM " + ModelProblemUtils.toSourceHint( parentModel ) + + ", must be \"pom\" but is \"" + parentModel.getPackaging() + "\"" ). + setLocation( parentModel.getLocation( "packaging" ) ) ); + } } else @@ -871,11 +1048,15 @@ public class DefaultModelBuilder { candidateModel = resolver.resolveRawModel( parent.getGroupId(), parent.getArtifactId(), parent.getVersion() ); + } catch ( UnresolvableModelException e ) { - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ) // - .setMessage( e.getMessage().toString() ).setLocation( parent.getLocation( "" ) ).setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ). + setMessage( e.getMessage().toString() ). + setLocation( parent.getLocation( "" ) ). + setException( e ) ); + throw problems.newModelBuildingException(); } if ( candidateModel == null ) @@ -890,7 +1071,6 @@ public class DefaultModelBuilder // have a model that is suitable, yet more checks are done here and the one for the version is problematic // before because with parents as ranges it will never work in this scenario. // - String groupId = candidateModel.getGroupId(); if ( groupId == null && candidateModel.getParent() != null ) { @@ -904,7 +1084,7 @@ public class DefaultModelBuilder } if ( groupId == null || !groupId.equals( parent.getGroupId() ) || artifactId == null - || !artifactId.equals( parent.getArtifactId() ) ) + || !artifactId.equals( parent.getArtifactId() ) ) { StringBuilder buffer = new StringBuilder( 256 ); buffer.append( "'parent.relativePath'" ); @@ -917,8 +1097,10 @@ public class DefaultModelBuilder buffer.append( parent.getArtifactId() ).append( ", please verify your project structure" ); problems.setSource( childModel ); - problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.BASE ) - .setMessage( buffer.toString() ).setLocation( parent.getLocation( "" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.WARNING, Version.BASE ). + setMessage( buffer.toString() ). + setLocation( parent.getLocation( "" ) ) ); + return null; } if ( version != null && parent.getVersion() != null && !version.equals( parent.getVersion() ) ) @@ -972,7 +1154,6 @@ public class DefaultModelBuilder /* * if ( version == null || !version.equals( parent.getVersion() ) ) { return null; } */ - ModelData parentData = new ModelData( candidateSource, candidateModel, groupId, artifactId, version ); return parentData; @@ -1010,7 +1191,8 @@ public class DefaultModelBuilder ModelResolver modelResolver = request.getModelResolver(); Validate.notNull( modelResolver, "request.modelResolver cannot be null (parent POM %s and POM %s)", - ModelProblemUtils.toId( groupId, artifactId, version ), ModelProblemUtils.toSourceHint( childModel ) ); + ModelProblemUtils.toId( groupId, artifactId, version ), + ModelProblemUtils.toSourceHint( childModel ) ); ModelSource modelSource; try @@ -1042,8 +1224,11 @@ public class DefaultModelBuilder } } - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ) - .setMessage( buffer.toString() ).setLocation( parent.getLocation( "" ) ).setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ). + setMessage( buffer.toString() ). + setLocation( parent.getLocation( "" ) ). + setException( e ) ); + throw problems.newModelBuildingException(); } @@ -1052,11 +1237,13 @@ public class DefaultModelBuilder { lenientRequest = new FilterModelBuildingRequest( request ) { + @Override public int getValidationLevel() { return ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0; } + }; } @@ -1066,17 +1253,18 @@ public class DefaultModelBuilder { if ( childModel.getVersion() == null ) { - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ) - .setMessage( "Version must be a constant" ).setLocation( childModel.getLocation( "" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ). + setMessage( "Version must be a constant" ). + setLocation( childModel.getLocation( "" ) ) ); } else { if ( childModel.getVersion().contains( "${" ) ) { - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ) - .setMessage( "Version must be a constant" ) - .setLocation( childModel.getLocation( "version" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.V31 ). + setMessage( "Version must be a constant" ). + setLocation( childModel.getLocation( "version" ) ) ); } } @@ -1095,7 +1283,7 @@ public class DefaultModelBuilder return superPomProvider.getSuperModel( "4.0.0" ).clone(); } - private void importDependencyManagement( Model model, ModelBuildingRequest request, + private void importDependencyManagement( Model model, String scope, ModelBuildingRequest request, DefaultModelProblemCollector problems, Collection<String> importIds ) { DependencyManagement depMngt = model.getDependencyManagement(); @@ -1105,6 +1293,8 @@ public class DefaultModelBuilder return; } + problems.setSource( model ); + String importing = model.getGroupId() + ':' + model.getArtifactId() + ':' + model.getVersion(); importIds.add( importing ); @@ -1118,7 +1308,7 @@ public class DefaultModelBuilder { Dependency dependency = it.next(); - if ( !"pom".equals( dependency.getType() ) || !"import".equals( dependency.getScope() ) ) + if ( !"pom".equals( dependency.getType() ) || !scope.equals( dependency.getScope() ) ) { continue; } @@ -1139,18 +1329,20 @@ public class DefaultModelBuilder } if ( artifactId == null || artifactId.length() <= 0 ) { - problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) - .setMessage( "'dependencyManagement.dependencies.dependency.artifactId' for " - + dependency.getManagementKey() + " is missing." ) - .setLocation( dependency.getLocation( "" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ). + setMessage( "'dependencyManagement.dependencies.dependency.artifactId' for " + + dependency.getManagementKey() + " is missing." ). + setLocation( dependency.getLocation( "" ) ) ); + continue; } if ( version == null || version.length() <= 0 ) { - problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ) - .setMessage( "'dependencyManagement.dependencies.dependency.version' for " - + dependency.getManagementKey() + " is missing." ) - .setLocation( dependency.getLocation( "" ) ) ); + problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ). + setMessage( "'dependencyManagement.dependencies.dependency.version' for " + + dependency.getManagementKey() + " is missing." ). + setLocation( dependency.getLocation( "" ) ) ); + continue; } @@ -1158,14 +1350,13 @@ public class DefaultModelBuilder if ( importIds.contains( imported ) ) { - String message = "The dependencies of type=pom and with scope=import form a cycle: "; + String message = "The dependencies of type=pom and scope=" + scope + " form a cycle: "; for ( String modelId : importIds ) { message += modelId + " -> "; } message += imported; problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE ).setMessage( message ) ); - continue; } @@ -1178,9 +1369,10 @@ public class DefaultModelBuilder { throw new NullPointerException( String.format( "request.workspaceModelResolver and request.modelResolver cannot be null" - + " (parent POM %s and POM %s)", + + " (parent POM %s and POM %s)", ModelProblemUtils.toId( groupId, artifactId, version ), ModelProblemUtils.toSourceHint( model ) ) ); + } Model importModel = null; @@ -1192,8 +1384,10 @@ public class DefaultModelBuilder } catch ( UnresolvableModelException e ) { - problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ) - .setMessage( e.getMessage().toString() ).setException( e ) ); + problems.add( new ModelProblemCollectorRequest( Severity.FATAL, Version.BASE ). + setMessage( e.getMessage().toString() ). + setException( e ) ); + continue; } } @@ -1426,9 +1620,11 @@ public class DefaultModelBuilder private boolean containsCoordinates( String message, String groupId, String artifactId, String version ) { - return message != null && ( groupId == null || message.contains( groupId ) ) - && ( artifactId == null || message.contains( artifactId ) ) - && ( version == null || message.contains( version ) ); + return message != null + && ( groupId == null || message.contains( groupId ) ) + && ( artifactId == null || message.contains( artifactId ) ) + && ( version == null || message.contains( version ) ); + } protected boolean hasModelErrors( ModelProblemCollectorExt problems ) http://git-wip-us.apache.org/repos/asf/maven/blob/f2d3f8f3/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java ---------------------------------------------------------------------- diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java index 7bf45b5..fe1c7ae 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/DefaultModelBuildingResult.java @@ -39,11 +39,13 @@ class DefaultModelBuildingResult private Model effectiveModel; - private List<String> modelIds; + private final List<String> modelIds; - private Map<String, Model> rawModels; + private final Map<String, Model> rawModels; - private Map<String, List<Profile>> activePomProfiles; + private final Map<String, Model> effectiveModels; + + private final Map<String, List<Profile>> activePomProfiles; private List<Profile> activeExternalProfiles; @@ -56,6 +58,7 @@ class DefaultModelBuildingResult activePomProfiles = new HashMap<>(); activeExternalProfiles = new ArrayList<>(); problems = new ArrayList<>(); + effectiveModels = new HashMap<>(); } @Override @@ -110,6 +113,23 @@ class DefaultModelBuildingResult } @Override + public Model getEffectiveModel( final String modelId ) + { + return this.effectiveModels.get( modelId ); + } + + public DefaultModelBuildingResult setEffectiveModel( final String modelId, final Model model ) + { + // Intentionally notNull because Super POM may not contain a modelId + Validate.notNull( modelId, "modelId must not be null" ); + Validate.notNull( model, "model must not be null" ); + + this.effectiveModels.put( modelId, model ); + + return this; + } + + @Override public List<Profile> getActivePomProfiles( String modelId ) { return activePomProfiles.get( modelId ); http://git-wip-us.apache.org/repos/asf/maven/blob/f2d3f8f3/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java ---------------------------------------------------------------------- diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java index 44b1295..b21a670 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/building/ModelBuildingResult.java @@ -69,6 +69,18 @@ public interface ModelBuildingResult Model getRawModel( String modelId ); /** + * Gets the effective model for a given identifier. The model identifier should be from the collection obtained by + * {@link #getModelIds()}. As a special case, an empty string can be used as the identifier for the super POM. + * + * @param modelId The identifier of the desired effective model, must not be {@code null}. + * + * @return The effective model or {@code null} if the specified model id does not refer to a known model. + * + * @since 3.6 + */ + Model getEffectiveModel( String modelId ); + + /** * Gets the profiles from the specified model that were active during model building. The model identifier should be * from the collection obtained by {@link #getModelIds()}. As a special case, an empty string can be used as the * identifier for the super POM. http://git-wip-us.apache.org/repos/asf/maven/blob/f2d3f8f3/maven-model-builder/src/main/java/org/apache/maven/model/composition/DefaultDependencyManagementImporter.java ---------------------------------------------------------------------- diff --git a/maven-model-builder/src/main/java/org/apache/maven/model/composition/DefaultDependencyManagementImporter.java b/maven-model-builder/src/main/java/org/apache/maven/model/composition/DefaultDependencyManagementImporter.java index d895913..b8caaa7 100644 --- a/maven-model-builder/src/main/java/org/apache/maven/model/composition/DefaultDependencyManagementImporter.java +++ b/maven-model-builder/src/main/java/org/apache/maven/model/composition/DefaultDependencyManagementImporter.java @@ -20,15 +20,20 @@ package org.apache.maven.model.composition; */ import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; - import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; +import org.apache.maven.model.Exclusion; +import org.apache.maven.model.InputLocation; +import org.apache.maven.model.InputSource; import org.apache.maven.model.Model; import org.apache.maven.model.building.ModelBuildingRequest; +import org.apache.maven.model.building.ModelProblem; import org.apache.maven.model.building.ModelProblemCollector; +import org.apache.maven.model.building.ModelProblemCollectorRequest; import org.codehaus.plexus.component.annotations.Component; /** @@ -42,41 +47,215 @@ public class DefaultDependencyManagementImporter { @Override - public void importManagement( Model target, List<? extends DependencyManagement> sources, - ModelBuildingRequest request, ModelProblemCollector problems ) + public void importManagement( final Model target, final List<? extends DependencyManagement> sources, + final ModelBuildingRequest request, final ModelProblemCollector problems ) { if ( sources != null && !sources.isEmpty() ) { - Map<String, Dependency> dependencies = new LinkedHashMap<>(); + final Map<String, Dependency> targetDependencies = new LinkedHashMap<>(); + final DependencyManagement targetDependencyManagement = target.getDependencyManagement() != null + ? target.getDependencyManagement() + : new DependencyManagement(); + + target.setDependencyManagement( targetDependencyManagement ); + + for ( final Dependency targetDependency : targetDependencyManagement.getDependencies() ) + { + targetDependencies.put( targetDependency.getManagementKey(), targetDependency ); + } - DependencyManagement depMngt = target.getDependencyManagement(); + final Map<String, List<Dependency>> sourceDependencies = new LinkedHashMap<>(); - if ( depMngt != null ) + for ( final DependencyManagement source : sources ) { - for ( Dependency dependency : depMngt.getDependencies() ) + for ( final Dependency sourceDependency : source.getDependencies() ) { - dependencies.put( dependency.getManagementKey(), dependency ); + if ( !targetDependencies.containsKey( sourceDependency.getManagementKey() ) ) + { + List<Dependency> conflictCanditates = + sourceDependencies.get( sourceDependency.getManagementKey() ); + + if ( conflictCanditates == null ) + { + conflictCanditates = new ArrayList<>( source.getDependencies().size() ); + sourceDependencies.put( sourceDependency.getManagementKey(), conflictCanditates ); + } + + conflictCanditates.add( sourceDependency ); + } } } - else + + for ( final List<Dependency> conflictCanditates : sourceDependencies.values() ) { - depMngt = new DependencyManagement(); - target.setDependencyManagement( depMngt ); + final List<Dependency> conflictingDependencies = removeRedundantDependencies( conflictCanditates ); + + // First declaration wins. This is what makes the conflict resolution indeterministic because this + // solely relies on the order of declaration. There is no such thing as the "first" declaration. + targetDependencyManagement.getDependencies().add( conflictingDependencies.get( 0 ) ); + + // As of Maven 3.6, we print a warning about such conflicting imports using validation level Maven 3.1. + if ( conflictingDependencies.size() > 1 ) + { + final StringBuilder conflictsBuilder = new StringBuilder( conflictingDependencies.size() * 128 ); + + for ( final Dependency dependency : conflictingDependencies ) + { + final InputLocation location = dependency.getLocation( "" ); + + if ( location != null ) + { + final InputSource inputSource = location.getSource(); + + if ( inputSource != null ) + { + conflictsBuilder.append( ", '" ).append( inputSource.getModelId() ).append( '\'' ); + } + } + } + + problems.add( new ModelProblemCollectorRequest( + effectiveSeverity( request.getValidationLevel(), + ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1 ), + ModelProblem.Version.V20 ). + setMessage( String.format( + "Dependency '%1$s' has conflicting dependency management in model '%2$s'%3$s%4$s. " + + "To resolve the conflicts, declare the dependency management for dependency '%1$s' " + + "directly in the dependency management of model '%2$s' to override what gets " + + "imported. If the Maven version in use supports it, add exclusions for the " + + "conflicting dependencies or use the include scope feature to rearrange the " + + "causing dependencies in the inheritance hierarchy applying standard override logic " + + "based on artifact coordinates. Without resolving the conflicts, your build relies " + + "on indeterministic behaviour.", + conflictingDependencies.get( 0 ).getManagementKey(), target.getId(), + target.getPomFile() != null + ? " @ '" + target.getPomFile().getAbsolutePath() + "' " + : " ", conflictsBuilder.length() > 0 + ? "(" + conflictsBuilder.substring( 2 ) + ")" + : "" ) ) ); + + } } + } + } + + private static List<Dependency> removeRedundantDependencies( final List<Dependency> candidateDependencies ) + { + final List<Dependency> resultDependencies = new ArrayList<>( candidateDependencies.size() ); - for ( DependencyManagement source : sources ) + while ( !candidateDependencies.isEmpty() ) + { + final Dependency resultDependency = candidateDependencies.remove( 0 ); + resultDependencies.add( resultDependency ); + + // Removes redundant dependencies. + for ( final Iterator<Dependency> it = candidateDependencies.iterator(); it.hasNext(); ) { - for ( Dependency dependency : source.getDependencies() ) + final Dependency candidateDependency = it.next(); + boolean redundant = true; + + redundancy_check: { - String key = dependency.getManagementKey(); - if ( !dependencies.containsKey( key ) ) + if ( !( resultDependency.getOptional() != null + ? resultDependency.getOptional().equals( candidateDependency.getOptional() ) + : candidateDependency.getOptional() == null ) ) + { + redundant = false; + break redundancy_check; + } + + if ( !( effectiveScope( resultDependency ).equals( effectiveScope( candidateDependency ) ) ) ) + { + redundant = false; + break redundancy_check; + } + + if ( !( resultDependency.getSystemPath() != null + ? resultDependency.getSystemPath().equals( candidateDependency.getSystemPath() ) + : candidateDependency.getSystemPath() == null ) ) { - dependencies.put( key, dependency ); + redundant = false; + break redundancy_check; } + + if ( !( resultDependency.getVersion() != null + ? resultDependency.getVersion().equals( candidateDependency.getVersion() ) + : candidateDependency.getVersion() == null ) ) + { + redundant = false; + break redundancy_check; + } + + for ( int i = 0, s0 = resultDependency.getExclusions().size(); i < s0; i++ ) + { + final Exclusion resultExclusion = resultDependency.getExclusions().get( i ); + + if ( !containsExclusion( candidateDependency.getExclusions(), resultExclusion ) ) + { + redundant = false; + break redundancy_check; + } + } + + for ( int i = 0, s0 = candidateDependency.getExclusions().size(); i < s0; i++ ) + { + final Exclusion candidateExclusion = candidateDependency.getExclusions().get( i ); + + if ( !containsExclusion( resultDependency.getExclusions(), candidateExclusion ) ) + { + redundant = false; + break redundancy_check; + } + } + } + + if ( redundant ) + { + it.remove(); } } + } + + return resultDependencies; + } + + private static boolean containsExclusion( final List<Exclusion> exclusions, final Exclusion exclusion ) + { + for ( int i = 0, s0 = exclusions.size(); i < s0; i++ ) + { + final Exclusion current = exclusions.get( i ); + + if ( ( exclusion.getArtifactId() != null + ? exclusion.getArtifactId().equals( current.getArtifactId() ) + : current.getArtifactId() == null ) + && ( exclusion.getGroupId() != null + ? exclusion.getGroupId().equals( current.getGroupId() ) + : current.getGroupId() == null ) ) + { + return true; + } + } + + return false; + } - depMngt.setDependencies( new ArrayList<>( dependencies.values() ) ); + private static String effectiveScope( final Dependency dependency ) + { + return dependency.getScope() == null + ? "compile" + : dependency.getScope(); + + } + + private static ModelProblem.Severity effectiveSeverity( final int validationLevel, final int errorThreshold ) + { + if ( validationLevel < errorThreshold ) + { + return ModelProblem.Severity.WARNING; + } + else + { + return ModelProblem.Severity.ERROR; } } http://git-wip-us.apache.org/repos/asf/maven/blob/f2d3f8f3/maven-model-builder/src/site/apt/index.apt ---------------------------------------------------------------------- diff --git a/maven-model-builder/src/site/apt/index.apt b/maven-model-builder/src/site/apt/index.apt index 03946e6..23b47fc 100644 --- a/maven-model-builder/src/site/apt/index.apt +++ b/maven-model-builder/src/site/apt/index.apt @@ -61,7 +61,7 @@ Maven Model Builder * phase 2, with optional plugin processing - ** dependency management import (for dependencies of type <<<pom>>> in the <<<\<dependencyManagement\>>>> section) + ** dependency management include processing (for dependencies of type <<<pom>>> and scope <<<include>>> in the <<<\<dependencyManagement\>>>> section) ** inheritance assembly (see {{{./index.html#Inheritance_Assembly}below}}) @@ -71,10 +71,6 @@ Maven Model Builder with its <<<DefaultUrlNormalizer>>> implementation ({{{./xref/org/apache/maven/model/path/DefaultUrlNormalizer.html}source}}) - [] - - * phase 2, with optional plugin processing - ** model path translation: <<<ModelPathTranslator>>> ({{{./apidocs/org/apache/maven/model/path/ModelPathTranslator.html}javadoc}}), with its <<<DefaultModelPathTranslator>>> implementation ({{{./xref/org/apache/maven/model/path/DefaultModelPathTranslator.html}source}}) @@ -87,7 +83,7 @@ Maven Model Builder with its <<<DefaultLifecycleBindingsInjector>>> implementation ({{{./xref/org/apache/maven/model/plugin/DefaultLifecycleBindingsInjector.html}source}}) - ** dependency management import (for dependencies of type <<<pom>>> in the <<<\<dependencyManagement\>>>> section) + ** dependency management import processing (for dependencies of type <<<pom>>> and scope <<<import>>> in the <<<\<dependencyManagement\>>>> section) ** dependency management injection: <<<DependencyManagementInjector>>> ({{{./apidocs/org/apache/maven/model/management/DependencyManagementInjector.html}javadoc}}), with its <<<DefaultDependencyManagementInjector>>> implementation