| package org.apache.maven.lifecycle.internal.builder; |
| |
| /* |
| * 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.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Singleton; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.execution.BuildFailure; |
| import org.apache.maven.execution.ExecutionEvent; |
| import org.apache.maven.execution.MavenExecutionRequest; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.lifecycle.LifecycleExecutionException; |
| import org.apache.maven.lifecycle.LifecycleNotFoundException; |
| import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; |
| import org.apache.maven.lifecycle.MavenExecutionPlan; |
| import org.apache.maven.lifecycle.internal.DefaultLifecyclePluginAnalyzer; |
| import org.apache.maven.lifecycle.internal.ExecutionEventCatapult; |
| import org.apache.maven.lifecycle.internal.LifecycleDebugLogger; |
| import org.apache.maven.lifecycle.internal.LifecycleExecutionPlanCalculator; |
| import org.apache.maven.lifecycle.internal.ReactorContext; |
| import org.apache.maven.lifecycle.internal.TaskSegment; |
| import org.apache.maven.model.InputLocation; |
| import org.apache.maven.model.Plugin; |
| import org.apache.maven.model.PluginExecution; |
| import org.apache.maven.plugin.InvalidPluginDescriptorException; |
| import org.apache.maven.plugin.MojoExecution; |
| import org.apache.maven.plugin.MojoNotFoundException; |
| import org.apache.maven.plugin.PluginDescriptorParsingException; |
| import org.apache.maven.plugin.PluginNotFoundException; |
| import org.apache.maven.plugin.PluginResolutionException; |
| import org.apache.maven.plugin.descriptor.MojoDescriptor; |
| import org.apache.maven.plugin.descriptor.Parameter; |
| import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException; |
| import org.apache.maven.plugin.version.PluginVersionResolutionException; |
| import org.apache.maven.project.MavenProject; |
| import org.codehaus.plexus.classworlds.realm.ClassRealm; |
| import org.codehaus.plexus.logging.Logger; |
| import org.codehaus.plexus.util.xml.Xpp3Dom; |
| |
| /** |
| * Common code that is shared by the LifecycleModuleBuilder and the LifeCycleWeaveBuilder |
| * |
| * @since 3.0 |
| * @author Kristian Rosenvold |
| * Builds one or more lifecycles for a full module |
| * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. |
| */ |
| @Named |
| @Singleton |
| public class BuilderCommon |
| { |
| @Inject |
| private LifecycleDebugLogger lifecycleDebugLogger; |
| |
| @Inject |
| private LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator; |
| |
| @Inject |
| private ExecutionEventCatapult eventCatapult; |
| |
| @Inject |
| private Logger logger; |
| |
| |
| public BuilderCommon() |
| { |
| } |
| |
| public BuilderCommon( LifecycleDebugLogger lifecycleDebugLogger, |
| LifecycleExecutionPlanCalculator lifeCycleExecutionPlanCalculator, Logger logger ) |
| { |
| this.lifecycleDebugLogger = lifecycleDebugLogger; |
| this.lifeCycleExecutionPlanCalculator = lifeCycleExecutionPlanCalculator; |
| this.logger = logger; |
| } |
| |
| public MavenExecutionPlan resolveBuildPlan( MavenSession session, MavenProject project, TaskSegment taskSegment, |
| Set<Artifact> projectArtifacts ) |
| throws PluginNotFoundException, PluginResolutionException, LifecyclePhaseNotFoundException, |
| PluginDescriptorParsingException, MojoNotFoundException, InvalidPluginDescriptorException, |
| NoPluginFoundForPrefixException, LifecycleNotFoundException, PluginVersionResolutionException, |
| LifecycleExecutionException |
| { |
| MavenExecutionPlan executionPlan = |
| lifeCycleExecutionPlanCalculator.calculateExecutionPlan( session, project, taskSegment.getTasks() ); |
| |
| lifecycleDebugLogger.debugProjectPlan( project, executionPlan ); |
| |
| findNonThreadSafePlugins( project, executionPlan, session ); |
| |
| findUnversionedPlugins( project, executionPlan ); |
| |
| findUnknownParameterNames( project, executionPlan ); |
| |
| return executionPlan; |
| } |
| |
| private void findNonThreadSafePlugins( MavenProject project, MavenExecutionPlan executionPlan, |
| MavenSession session ) |
| { |
| if ( session.getRequest().getDegreeOfConcurrency() > 1 ) |
| { |
| final Set<Plugin> unsafePlugins = executionPlan.getNonThreadSafePlugins(); |
| if ( !unsafePlugins.isEmpty() ) |
| { |
| logger.warn( "*****************************************************************" ); |
| logger.warn( "* Your build is requesting parallel execution, but project *" ); |
| logger.warn( "* contains the following plugin(s) that have goals not marked *" ); |
| logger.warn( "* as @threadSafe to support parallel building. *" ); |
| logger.warn( "* While this /may/ work fine, please look for plugin updates *" ); |
| logger.warn( "* and/or request plugins be made thread-safe. *" ); |
| logger.warn( "* If reporting an issue, report it against the plugin in *" ); |
| logger.warn( "* question, not against maven-core *" ); |
| logger.warn( "*****************************************************************" ); |
| if ( logger.isDebugEnabled() ) |
| { |
| final Set<MojoDescriptor> unsafeGoals = executionPlan.getNonThreadSafeMojos(); |
| logger.warn( "The following goals are not marked @threadSafe in " + project.getName() + ":" ); |
| for ( MojoDescriptor unsafeGoal : unsafeGoals ) |
| { |
| logger.warn( unsafeGoal.getId() ); |
| } |
| } |
| else |
| { |
| logger.warn( "The following plugins are not marked @threadSafe in " + project.getName() + ":" ); |
| for ( Plugin unsafePlugin : unsafePlugins ) |
| { |
| logger.warn( unsafePlugin.getId() ); |
| } |
| logger.warn( "Enable debug to see more precisely which goals are not marked @threadSafe." ); |
| } |
| logger.warn( "*****************************************************************" ); |
| } |
| } |
| } |
| |
| private void findUnversionedPlugins( MavenProject project, MavenExecutionPlan executionPlan ) |
| { |
| final String defaulModelId = DefaultLifecyclePluginAnalyzer.DEFAULTLIFECYCLEBINDINGS_MODELID; |
| |
| List<String> unversionedPlugins = executionPlan.getMojoExecutions().stream() |
| .map( MojoExecution::getPlugin ) |
| .filter( p -> p.getLocation( "version" ) != null ) // versionless cli goal (?) |
| .filter( p -> p.getLocation( "version" ).getSource() != null ) // versionless in pom (?) |
| .filter( p -> defaulModelId.equals( p.getLocation( "version" ).getSource().getModelId() ) ) |
| .distinct() |
| .map( Plugin::getArtifactId ) // managed by us, groupId is always o.a.m.plugins |
| .collect( Collectors.toList() ); |
| |
| if ( !unversionedPlugins.isEmpty() ) |
| { |
| logger.warn( "Version not locked for default bindings plugins " + unversionedPlugins |
| + ", you should define versions in pluginManagement section of your " + "pom.xml or parent" ); |
| } |
| } |
| |
| private void findUnknownParameterNames( MavenProject project, MavenExecutionPlan executionPlan ) |
| { |
| // Verify that all configured parameters in the pom match a parameter of the mojo |
| Map<Plugin, List<MojoExecution>> sharedExecutions = executionPlan.getMojoExecutions().stream() |
| .collect( Collectors.groupingBy( MojoExecution::getPlugin ) ); |
| |
| for ( Map.Entry<Plugin, List<MojoExecution>> entry : sharedExecutions.entrySet() ) |
| { |
| Map<String, Set<String>> validParametersPerExecutionId = new HashMap<>(); |
| |
| entry.getValue().stream() |
| .filter( e -> e.getMojoDescriptor().getParameters() != null ) |
| .forEach( e -> validParametersPerExecutionId.computeIfAbsent( e.getExecutionId(), |
| k -> new HashSet<>() ).addAll( parameterNames( e.getMojoDescriptor().getParameters() ) ) ); |
| |
| for ( Map.Entry<String, PluginExecution> pc : entry.getKey().getExecutionsAsMap().entrySet() ) |
| { |
| Set<String> validParams = |
| validParametersPerExecutionId.getOrDefault( pc.getKey(), Collections.emptySet() ); |
| Xpp3Dom pluginConfiguration = (Xpp3Dom) pc.getValue().getConfiguration(); |
| |
| if ( pluginConfiguration != null ) |
| { |
| List<Xpp3Dom> unknownParameters = Arrays.stream( pluginConfiguration.getChildren() ) |
| .filter( e -> !validParams.contains( e.getName() ) ) |
| .collect( Collectors.toList() ); |
| |
| if ( !unknownParameters.isEmpty() ) |
| { |
| logger.warn( String.format( "Unknown parameters for %s (%s):", |
| entry.getKey().getId(), pc.getKey(), unknownParameters ) ); |
| |
| for ( Xpp3Dom param : unknownParameters ) |
| { |
| InputLocation loc = (InputLocation) param.getInputLocation(); |
| |
| // in case the parameter is defined in 'project', we could break the build |
| logger.warn( String.format( " <%s> @ %s, line %s", |
| param.getName(), loc.getSource().getModelId(), loc.getLineNumber() ) ); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private Set<String> parameterNames( List<Parameter> parameters ) |
| { |
| Set<String> names = new HashSet<>( parameters.size() * 2 ); |
| for ( Parameter p : parameters ) |
| { |
| names.add( p.getName() ); |
| names.add( p.getAlias() ); |
| } |
| return names; |
| } |
| |
| public void handleBuildError( final ReactorContext buildContext, final MavenSession rootSession, |
| final MavenSession currentSession, final MavenProject mavenProject, Throwable t, |
| final long buildStartTime ) |
| { |
| // record the error and mark the project as failed |
| long buildEndTime = System.currentTimeMillis(); |
| buildContext.getResult().addException( t ); |
| buildContext.getResult().addBuildSummary( new BuildFailure( mavenProject, buildEndTime - buildStartTime, t ) ); |
| |
| // notify listeners about "soft" project build failures only |
| if ( t instanceof Exception && !( t instanceof RuntimeException ) ) |
| { |
| eventCatapult.fire( ExecutionEvent.Type.ProjectFailed, currentSession, null, (Exception) t ); |
| } |
| |
| // reactor failure modes |
| if ( t instanceof RuntimeException || !( t instanceof Exception ) ) |
| { |
| // fail fast on RuntimeExceptions, Errors and "other" Throwables |
| // assume these are system errors and further build is meaningless |
| buildContext.getReactorBuildStatus().halt(); |
| } |
| else if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( rootSession.getReactorFailureBehavior() ) ) |
| { |
| // continue the build |
| } |
| else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( rootSession.getReactorFailureBehavior() ) ) |
| { |
| // continue the build but ban all projects that depend on the failed one |
| buildContext.getReactorBuildStatus().blackList( mavenProject ); |
| } |
| else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( rootSession.getReactorFailureBehavior() ) ) |
| { |
| buildContext.getReactorBuildStatus().halt(); |
| } |
| else |
| { |
| logger.error( "invalid reactor failure behavior " + rootSession.getReactorFailureBehavior() ); |
| buildContext.getReactorBuildStatus().halt(); |
| } |
| } |
| |
| public static void attachToThread( MavenProject currentProject ) |
| { |
| ClassRealm projectRealm = currentProject.getClassRealm(); |
| if ( projectRealm != null ) |
| { |
| Thread.currentThread().setContextClassLoader( projectRealm ); |
| } |
| } |
| |
| // TODO I'm really wondering where this method belongs; smells like it should be on MavenProject, but for some |
| // reason it isn't ? This localization is kind-of a code smell. |
| |
| public static String getKey( MavenProject project ) |
| { |
| return project.getGroupId() + ':' + project.getArtifactId() + ':' + project.getVersion(); |
| } |
| |
| } |