| /* |
| * 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. |
| */ |
| package org.apache.maven.lifecycle.internal; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Provider; |
| import javax.inject.Singleton; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.apache.maven.api.services.MessageBuilderFactory; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.resolver.filter.ArtifactFilter; |
| import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter; |
| import org.apache.maven.execution.ExecutionEvent; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.internal.MultilineMessageHelper; |
| import org.apache.maven.lifecycle.LifecycleExecutionException; |
| import org.apache.maven.lifecycle.MissingProjectException; |
| import org.apache.maven.plugin.BuildPluginManager; |
| import org.apache.maven.plugin.MavenPluginManager; |
| import org.apache.maven.plugin.MojoExecution; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoExecutionRunner; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugin.MojosExecutionStrategy; |
| import org.apache.maven.plugin.PluginConfigurationException; |
| import org.apache.maven.plugin.PluginIncompatibleException; |
| import org.apache.maven.plugin.PluginManagerException; |
| import org.apache.maven.plugin.descriptor.MojoDescriptor; |
| import org.apache.maven.project.MavenProject; |
| import org.eclipse.aether.SessionData; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * <p> |
| * Executes an individual mojo |
| * </p> |
| * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice. |
| * |
| * @since 3.0 |
| */ |
| @Named |
| @Singleton |
| public class MojoExecutor { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(MojoExecutor.class); |
| |
| private final BuildPluginManager pluginManager; |
| private final MavenPluginManager mavenPluginManager; |
| private final LifecycleDependencyResolver lifeCycleDependencyResolver; |
| private final ExecutionEventCatapult eventCatapult; |
| |
| private final OwnerReentrantReadWriteLock aggregatorLock = new OwnerReentrantReadWriteLock(); |
| |
| private final Provider<MojosExecutionStrategy> mojosExecutionStrategy; |
| |
| private final MessageBuilderFactory messageBuilderFactory; |
| |
| private final Map<Thread, MojoDescriptor> mojos = new ConcurrentHashMap<>(); |
| |
| @Inject |
| public MojoExecutor( |
| BuildPluginManager pluginManager, |
| MavenPluginManager mavenPluginManager, |
| LifecycleDependencyResolver lifeCycleDependencyResolver, |
| ExecutionEventCatapult eventCatapult, |
| Provider<MojosExecutionStrategy> mojosExecutionStrategy, |
| MessageBuilderFactory messageBuilderFactory) { |
| this.pluginManager = pluginManager; |
| this.mavenPluginManager = mavenPluginManager; |
| this.lifeCycleDependencyResolver = lifeCycleDependencyResolver; |
| this.eventCatapult = eventCatapult; |
| this.mojosExecutionStrategy = mojosExecutionStrategy; |
| this.messageBuilderFactory = messageBuilderFactory; |
| } |
| |
| public DependencyContext newDependencyContext(MavenSession session, List<MojoExecution> mojoExecutions) { |
| Set<String> scopesToCollect = new TreeSet<>(); |
| Set<String> scopesToResolve = new TreeSet<>(); |
| |
| collectDependencyRequirements(scopesToResolve, scopesToCollect, mojoExecutions); |
| |
| return new DependencyContext(session.getCurrentProject(), scopesToCollect, scopesToResolve); |
| } |
| |
| private void collectDependencyRequirements( |
| Set<String> scopesToResolve, Set<String> scopesToCollect, Collection<MojoExecution> mojoExecutions) { |
| for (MojoExecution mojoExecution : mojoExecutions) { |
| MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); |
| |
| scopesToResolve.addAll(toScopes(mojoDescriptor.getDependencyResolutionRequired())); |
| |
| scopesToCollect.addAll(toScopes(mojoDescriptor.getDependencyCollectionRequired())); |
| } |
| } |
| |
| private Collection<String> toScopes(String classpath) { |
| Collection<String> scopes = Collections.emptyList(); |
| |
| if (classpath != null && !classpath.isEmpty()) { |
| if (Artifact.SCOPE_COMPILE.equals(classpath)) { |
| scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED); |
| } else if (Artifact.SCOPE_RUNTIME.equals(classpath)) { |
| scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME); |
| } else if (Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals(classpath)) { |
| scopes = Arrays.asList( |
| Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_RUNTIME); |
| } else if (Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals(classpath)) { |
| scopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME); |
| } else if (Artifact.SCOPE_TEST.equals(classpath)) { |
| scopes = Arrays.asList( |
| Artifact.SCOPE_COMPILE, |
| Artifact.SCOPE_SYSTEM, |
| Artifact.SCOPE_PROVIDED, |
| Artifact.SCOPE_RUNTIME, |
| Artifact.SCOPE_TEST); |
| } |
| } |
| return Collections.unmodifiableCollection(scopes); |
| } |
| |
| public void execute( |
| final MavenSession session, final List<MojoExecution> mojoExecutions, final ProjectIndex projectIndex) |
| throws LifecycleExecutionException { |
| |
| final DependencyContext dependencyContext = newDependencyContext(session, mojoExecutions); |
| |
| final PhaseRecorder phaseRecorder = new PhaseRecorder(session.getCurrentProject()); |
| |
| mojosExecutionStrategy.get().execute(mojoExecutions, session, new MojoExecutionRunner() { |
| @Override |
| public void run(MojoExecution mojoExecution) throws LifecycleExecutionException { |
| MojoExecutor.this.execute(session, mojoExecution, projectIndex, dependencyContext, phaseRecorder); |
| } |
| }); |
| } |
| |
| private void execute( |
| MavenSession session, |
| MojoExecution mojoExecution, |
| ProjectIndex projectIndex, |
| DependencyContext dependencyContext, |
| PhaseRecorder phaseRecorder) |
| throws LifecycleExecutionException { |
| execute(session, mojoExecution, projectIndex, dependencyContext); |
| phaseRecorder.observeExecution(mojoExecution); |
| } |
| |
| private void execute( |
| MavenSession session, |
| MojoExecution mojoExecution, |
| ProjectIndex projectIndex, |
| DependencyContext dependencyContext) |
| throws LifecycleExecutionException { |
| MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); |
| |
| try { |
| mavenPluginManager.checkPrerequisites(mojoDescriptor.getPluginDescriptor()); |
| } catch (PluginIncompatibleException e) { |
| throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, session.getCurrentProject(), e); |
| } |
| |
| if (mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent()) { |
| Throwable cause = new MissingProjectException( |
| "Goal requires a project to execute" + " but there is no POM in this directory (" |
| + session.getExecutionRootDirectory() + ")." |
| + " Please verify you invoked Maven from the correct directory."); |
| throw new LifecycleExecutionException(messageBuilderFactory, mojoExecution, null, cause); |
| } |
| |
| if (mojoDescriptor.isOnlineRequired() && session.isOffline()) { |
| if (MojoExecution.Source.CLI.equals(mojoExecution.getSource())) { |
| Throwable cause = new IllegalStateException( |
| "Goal requires online mode for execution" + " but Maven is currently offline."); |
| throw new LifecycleExecutionException( |
| messageBuilderFactory, mojoExecution, session.getCurrentProject(), cause); |
| } else { |
| eventCatapult.fire(ExecutionEvent.Type.MojoSkipped, session, mojoExecution); |
| |
| return; |
| } |
| } |
| |
| doExecute(session, mojoExecution, projectIndex, dependencyContext); |
| } |
| |
| /** |
| * Aggregating mojo executions (possibly) modify all MavenProjects, including those that are currently in use |
| * by concurrently running mojo executions. To prevent race conditions, an aggregating execution will block |
| * all other executions until finished. |
| * We also lock on a given project to forbid a forked lifecycle to be executed concurrently with the project. |
| * TODO: ideally, the builder should take care of the ordering in a smarter way |
| * TODO: and concurrency issues fixed with MNG-7157 |
| */ |
| private class ProjectLock implements AutoCloseable { |
| final Lock acquiredAggregatorLock; |
| final OwnerReentrantLock acquiredProjectLock; |
| |
| ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) { |
| mojos.put(Thread.currentThread(), mojoDescriptor); |
| if (session.getRequest().getDegreeOfConcurrency() > 1) { |
| boolean aggregator = mojoDescriptor.isAggregator(); |
| acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock(); |
| acquiredProjectLock = getProjectLock(session); |
| if (!acquiredAggregatorLock.tryLock()) { |
| Thread owner = aggregatorLock.getOwner(); |
| MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null; |
| String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An"; |
| String msg = str + " aggregator mojo is already being executed " |
| + "in this parallel build, those kind of mojos require exclusive access to " |
| + "reactor to prevent race conditions. This mojo execution will be blocked " |
| + "until the aggregator mojo is done."; |
| warn(msg); |
| acquiredAggregatorLock.lock(); |
| } |
| if (!acquiredProjectLock.tryLock()) { |
| Thread owner = acquiredProjectLock.getOwner(); |
| MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null; |
| String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A"; |
| String msg = str + " mojo is already being executed " |
| + "on the project " + session.getCurrentProject().getGroupId() |
| + ":" + session.getCurrentProject().getArtifactId() + ". " |
| + "This mojo execution will be blocked " |
| + "until the mojo is done."; |
| warn(msg); |
| acquiredProjectLock.lock(); |
| } |
| } else { |
| acquiredAggregatorLock = null; |
| acquiredProjectLock = null; |
| } |
| } |
| |
| @Override |
| public void close() { |
| // release the lock in the reverse order of the acquisition |
| if (acquiredProjectLock != null) { |
| acquiredProjectLock.unlock(); |
| } |
| if (acquiredAggregatorLock != null) { |
| acquiredAggregatorLock.unlock(); |
| } |
| mojos.remove(Thread.currentThread()); |
| } |
| |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| private OwnerReentrantLock getProjectLock(MavenSession session) { |
| SessionData data = session.getRepositorySession().getData(); |
| Map<MavenProject, OwnerReentrantLock> locks = |
| (Map) data.computeIfAbsent(ProjectLock.class, ConcurrentHashMap::new); |
| return locks.computeIfAbsent(session.getCurrentProject(), p -> new OwnerReentrantLock()); |
| } |
| } |
| |
| static class OwnerReentrantLock extends ReentrantLock { |
| @Override |
| public Thread getOwner() { |
| return super.getOwner(); |
| } |
| } |
| |
| static class OwnerReentrantReadWriteLock extends ReentrantReadWriteLock { |
| @Override |
| public Thread getOwner() { |
| return super.getOwner(); |
| } |
| } |
| |
| private static void warn(String msg) { |
| for (String s : MultilineMessageHelper.format(msg)) { |
| LOGGER.warn(s); |
| } |
| } |
| |
| private void doExecute( |
| MavenSession session, |
| MojoExecution mojoExecution, |
| ProjectIndex projectIndex, |
| DependencyContext dependencyContext) |
| throws LifecycleExecutionException { |
| MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); |
| |
| List<MavenProject> forkedProjects = executeForkedExecutions(mojoExecution, session, projectIndex); |
| |
| ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext); |
| |
| try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) { |
| doExecute2(session, mojoExecution); |
| } finally { |
| for (MavenProject forkedProject : forkedProjects) { |
| forkedProject.setExecutionProject(null); |
| } |
| } |
| } |
| |
| private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException { |
| eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution); |
| try { |
| try { |
| pluginManager.executeMojo(session, mojoExecution); |
| } catch (MojoFailureException |
| | PluginManagerException |
| | PluginConfigurationException |
| | MojoExecutionException e) { |
| throw new LifecycleExecutionException( |
| messageBuilderFactory, mojoExecution, session.getCurrentProject(), e); |
| } |
| |
| eventCatapult.fire(ExecutionEvent.Type.MojoSucceeded, session, mojoExecution); |
| } catch (LifecycleExecutionException e) { |
| eventCatapult.fire(ExecutionEvent.Type.MojoFailed, session, mojoExecution, e); |
| |
| throw e; |
| } |
| } |
| |
| public void ensureDependenciesAreResolved( |
| MojoDescriptor mojoDescriptor, MavenSession session, DependencyContext dependencyContext) |
| throws LifecycleExecutionException { |
| |
| MavenProject project = dependencyContext.getProject(); |
| boolean aggregating = mojoDescriptor.isAggregator(); |
| |
| if (dependencyContext.isResolutionRequiredForCurrentProject()) { |
| Collection<String> scopesToCollect = dependencyContext.getScopesToCollectForCurrentProject(); |
| Collection<String> scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject(); |
| |
| lifeCycleDependencyResolver.resolveProjectDependencies( |
| project, scopesToCollect, scopesToResolve, session, aggregating, Collections.emptySet()); |
| |
| dependencyContext.synchronizeWithProjectState(); |
| } |
| |
| if (aggregating) { |
| Collection<String> scopesToCollect = toScopes(mojoDescriptor.getDependencyCollectionRequired()); |
| Collection<String> scopesToResolve = toScopes(mojoDescriptor.getDependencyResolutionRequired()); |
| |
| if (dependencyContext.isResolutionRequiredForAggregatedProjects(scopesToCollect, scopesToResolve)) { |
| for (MavenProject aggregatedProject : session.getProjects()) { |
| if (aggregatedProject != project) { |
| lifeCycleDependencyResolver.resolveProjectDependencies( |
| aggregatedProject, |
| scopesToCollect, |
| scopesToResolve, |
| session, |
| aggregating, |
| Collections.emptySet()); |
| } |
| } |
| } |
| } |
| |
| ArtifactFilter artifactFilter = getArtifactFilter(mojoDescriptor); |
| List<MavenProject> projectsToResolve = LifecycleDependencyResolver.getProjects( |
| session.getCurrentProject(), session, mojoDescriptor.isAggregator()); |
| for (MavenProject projectToResolve : projectsToResolve) { |
| projectToResolve.setArtifactFilter(artifactFilter); |
| } |
| } |
| |
| private ArtifactFilter getArtifactFilter(MojoDescriptor mojoDescriptor) { |
| String scopeToResolve = mojoDescriptor.getDependencyResolutionRequired(); |
| String scopeToCollect = mojoDescriptor.getDependencyCollectionRequired(); |
| |
| List<String> scopes = new ArrayList<>(2); |
| if (scopeToCollect != null && !scopeToCollect.isEmpty()) { |
| scopes.add(scopeToCollect); |
| } |
| if (scopeToResolve != null && !scopeToResolve.isEmpty()) { |
| scopes.add(scopeToResolve); |
| } |
| |
| if (scopes.isEmpty()) { |
| return null; |
| } else { |
| return new CumulativeScopeArtifactFilter(scopes); |
| } |
| } |
| |
| public List<MavenProject> executeForkedExecutions( |
| MojoExecution mojoExecution, MavenSession session, ProjectIndex projectIndex) |
| throws LifecycleExecutionException { |
| List<MavenProject> forkedProjects = Collections.emptyList(); |
| |
| Map<String, List<MojoExecution>> forkedExecutions = mojoExecution.getForkedExecutions(); |
| |
| if (!forkedExecutions.isEmpty()) { |
| eventCatapult.fire(ExecutionEvent.Type.ForkStarted, session, mojoExecution); |
| |
| MavenProject project = session.getCurrentProject(); |
| |
| forkedProjects = new ArrayList<>(forkedExecutions.size()); |
| |
| try { |
| for (Map.Entry<String, List<MojoExecution>> fork : forkedExecutions.entrySet()) { |
| String projectId = fork.getKey(); |
| |
| int index = projectIndex.getIndices().get(projectId); |
| |
| MavenProject forkedProject = projectIndex.getProjects().get(projectId); |
| |
| forkedProjects.add(forkedProject); |
| |
| MavenProject executedProject = forkedProject.clone(); |
| |
| forkedProject.setExecutionProject(executedProject); |
| |
| List<MojoExecution> mojoExecutions = fork.getValue(); |
| |
| if (mojoExecutions.isEmpty()) { |
| continue; |
| } |
| |
| try { |
| session.setCurrentProject(executedProject); |
| session.getProjects().set(index, executedProject); |
| projectIndex.getProjects().put(projectId, executedProject); |
| |
| eventCatapult.fire(ExecutionEvent.Type.ForkedProjectStarted, session, mojoExecution); |
| |
| execute(session, mojoExecutions, projectIndex); |
| |
| eventCatapult.fire(ExecutionEvent.Type.ForkedProjectSucceeded, session, mojoExecution); |
| } catch (LifecycleExecutionException e) { |
| eventCatapult.fire(ExecutionEvent.Type.ForkedProjectFailed, session, mojoExecution, e); |
| |
| throw e; |
| } finally { |
| projectIndex.getProjects().put(projectId, forkedProject); |
| session.getProjects().set(index, forkedProject); |
| session.setCurrentProject(project); |
| } |
| } |
| |
| eventCatapult.fire(ExecutionEvent.Type.ForkSucceeded, session, mojoExecution); |
| } catch (LifecycleExecutionException e) { |
| eventCatapult.fire(ExecutionEvent.Type.ForkFailed, session, mojoExecution, e); |
| |
| throw e; |
| } |
| } |
| |
| return forkedProjects; |
| } |
| } |