| /******************************************************************************* |
| * Copyright (c) 2010 Sonatype, Inc. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Sonatype, Inc. - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.m2e.core.internal.builder; |
| |
| import static org.eclipse.core.resources.IncrementalProjectBuilder.FULL_BUILD; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.QualifiedName; |
| |
| import org.apache.maven.execution.MavenExecutionResult; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.project.MavenProject; |
| |
| import org.eclipse.m2e.core.internal.ExtensionReader; |
| import org.eclipse.m2e.core.internal.IMavenConstants; |
| import org.eclipse.m2e.core.internal.MavenPluginActivator; |
| import org.eclipse.m2e.core.internal.URLConnectionCaches; |
| import org.eclipse.m2e.core.internal.builder.BuildResultCollector.Message; |
| import org.eclipse.m2e.core.internal.builder.IIncrementalBuildFramework.BuildContext; |
| import org.eclipse.m2e.core.internal.builder.plexusbuildapi.AbstractEclipseBuildContext; |
| import org.eclipse.m2e.core.internal.builder.plexusbuildapi.PlexusBuildAPI; |
| import org.eclipse.m2e.core.internal.embedder.MavenProjectMutableState; |
| import org.eclipse.m2e.core.internal.markers.IMavenMarkerManager; |
| import org.eclipse.m2e.core.internal.markers.SourceLocation; |
| import org.eclipse.m2e.core.internal.markers.SourceLocationHelper; |
| import org.eclipse.m2e.core.project.IMavenProjectFacade; |
| import org.eclipse.m2e.core.project.configurator.AbstractBuildParticipant; |
| import org.eclipse.m2e.core.project.configurator.MojoExecutionKey; |
| |
| |
| public class MavenBuilderImpl { |
| private static Logger log = LoggerFactory.getLogger(MavenBuilderImpl.class); |
| |
| public static final QualifiedName BUILD_CONTEXT_KEY = new QualifiedName(IMavenConstants.PLUGIN_ID, "BuildContext"); //$NON-NLS-1$ |
| |
| private static final String BUILD_PARTICIPANT_ID_ATTR_NAME = "buildParticipantId"; |
| |
| private final DeltaProvider deltaProvider; |
| |
| private final List<IIncrementalBuildFramework> incrementalBuildFrameworks; |
| |
| public MavenBuilderImpl(DeltaProvider deltaProvider) { |
| this.deltaProvider = deltaProvider; |
| this.incrementalBuildFrameworks = loadIncrementalBuildFrameworks(); |
| } |
| |
| private List<IIncrementalBuildFramework> loadIncrementalBuildFrameworks() { |
| List<IIncrementalBuildFramework> frameworks = new ArrayList<IIncrementalBuildFramework>(); |
| frameworks.add(new PlexusBuildAPI()); |
| frameworks.addAll(ExtensionReader.readIncrementalBuildFrameworks()); |
| return frameworks; |
| } |
| |
| public MavenBuilderImpl() { |
| this(project -> null); |
| } |
| |
| public Set<IProject> build(MavenSession session, IMavenProjectFacade projectFacade, int kind, |
| Map<String, String> args, Map<MojoExecutionKey, List<AbstractBuildParticipant>> participants, |
| IProgressMonitor monitor) throws CoreException { |
| |
| // 442524 safety guard |
| URLConnectionCaches.assertDisabled(); |
| |
| Collection<BuildDebugHook> debugHooks = MavenBuilder.getDebugHooks(); |
| |
| Set<IProject> dependencies = new HashSet<IProject>(); |
| |
| MavenProject mavenProject = projectFacade.getMavenProject(); |
| IProject project = projectFacade.getProject(); |
| |
| IResourceDelta delta = getDeltaProvider().getDelta(project); |
| |
| final BuildResultCollector participantResults = new BuildResultCollector(); |
| List<BuildContext> incrementalContexts = setupProjectBuildContext(project, kind, delta, participantResults); |
| |
| debugBuildStart(debugHooks, projectFacade, kind, args, participants, delta, monitor); |
| |
| Map<Throwable, MojoExecutionKey> buildErrors = new LinkedHashMap<Throwable, MojoExecutionKey>(); |
| MavenProjectMutableState snapshot = MavenProjectMutableState.takeSnapshot(mavenProject); |
| try { |
| for(Entry<MojoExecutionKey, List<AbstractBuildParticipant>> entry : participants.entrySet()) { |
| MojoExecutionKey mojoExecutionKey = entry.getKey(); |
| for(InternalBuildParticipant participant : entry.getValue()) { |
| Set<File> debugRefreshFiles = !debugHooks.isEmpty() ? new LinkedHashSet<File>(participantResults.getFiles()) |
| : null; |
| |
| log.debug("Executing build participant {} for plugin execution {}", participant.getClass().getName(), |
| mojoExecutionKey.toString()); |
| participantResults.setParticipantId(mojoExecutionKey.getKeyString() + "-" + participant.getClass().getName()); |
| participant.setMavenProjectFacade(projectFacade); |
| participant.setGetDeltaCallback(getDeltaProvider()); |
| participant.setSession(session); |
| participant.setBuildContext((AbstractEclipseBuildContext) incrementalContexts.get(0)); |
| if(participant instanceof InternalBuildParticipant2) { |
| ((InternalBuildParticipant2) participant).setArgs(args); |
| } |
| long executionStartTime = System.currentTimeMillis(); |
| try { |
| if(isApplicable(participant, kind, delta)) { |
| Set<IProject> sub = participant.build(kind, monitor); |
| if(sub != null) { |
| dependencies.addAll(sub); |
| } |
| } |
| } catch(Exception e) { |
| log.debug("Exception in build participant {}", participant.getClass().getName(), e); |
| buildErrors.put(e, mojoExecutionKey); |
| } finally { |
| log.debug("Finished executing build participant {} for plugin execution {} in {} ms", new Object[] { |
| participant.getClass().getName(), mojoExecutionKey.toString(), |
| System.currentTimeMillis() - executionStartTime}); |
| participant.setMavenProjectFacade(null); |
| participant.setGetDeltaCallback(null); |
| participant.setSession(null); |
| participant.setBuildContext(null); |
| if(participant instanceof InternalBuildParticipant2) { |
| ((InternalBuildParticipant2) participant).setArgs(Collections.<String, String> emptyMap()); |
| } |
| |
| processMavenSessionErrors(session, mojoExecutionKey, buildErrors); |
| } |
| |
| debugBuildParticipant(debugHooks, projectFacade, mojoExecutionKey, (AbstractBuildParticipant) participant, |
| diff(debugRefreshFiles, participantResults.getFiles()), monitor); |
| } |
| } |
| } catch(Exception e) { |
| log.debug("Unexpected build exception", e); |
| buildErrors.put(e, null); |
| } finally { |
| snapshot.restore(mavenProject); |
| for(IIncrementalBuildFramework.BuildContext context : incrementalContexts) { |
| context.release(); |
| } |
| } |
| |
| // Refresh files modified by build participants/maven plugins |
| refreshResources(project, participantResults.getFiles(), monitor); |
| |
| // Process errors and warnings |
| MavenExecutionResult result = session.getResult(); |
| processBuildResults(project, mavenProject, result, participantResults, buildErrors); |
| |
| return dependencies; |
| } |
| |
| private List<IIncrementalBuildFramework.BuildContext> setupProjectBuildContext(IProject project, int kind, |
| IResourceDelta delta, IIncrementalBuildFramework.BuildResultCollector results) throws CoreException { |
| List<IIncrementalBuildFramework.BuildContext> contexts = new ArrayList<IIncrementalBuildFramework.BuildContext>(); |
| for(IIncrementalBuildFramework framework : incrementalBuildFrameworks) { |
| contexts.add(framework.setupProjectBuildContext(project, kind, delta, results)); |
| } |
| return contexts; |
| } |
| |
| private void debugBuildParticipant(Collection<BuildDebugHook> hooks, IMavenProjectFacade projectFacade, |
| MojoExecutionKey mojoExecutionKey, AbstractBuildParticipant participant, Set<File> files, IProgressMonitor monitor) { |
| for(BuildDebugHook hook : hooks) { |
| hook.buildParticipant(projectFacade, mojoExecutionKey, participant, files, monitor); |
| } |
| } |
| |
| private Set<File> diff(Set<File> before, Set<File> after) { |
| if(before == null) { |
| return after; |
| } |
| Set<File> result = new LinkedHashSet<File>(after); |
| result.removeAll(before); |
| return result; |
| } |
| |
| private void debugBuildStart(Collection<BuildDebugHook> hooks, IMavenProjectFacade projectFacade, int kind, |
| Map<String, String> args, Map<MojoExecutionKey, List<AbstractBuildParticipant>> participants, |
| IResourceDelta delta, IProgressMonitor monitor) { |
| for(BuildDebugHook hook : hooks) { |
| hook.buildStart(projectFacade, kind, args, participants, delta, monitor); |
| } |
| } |
| |
| protected boolean isApplicable(InternalBuildParticipant participant, int kind, IResourceDelta delta) { |
| return FULL_BUILD == kind || delta != null || participant.callOnEmptyDelta(); |
| } |
| |
| private void processMavenSessionErrors(MavenSession session, MojoExecutionKey mojoExecutionKey, |
| Map<Throwable, MojoExecutionKey> buildErrors) { |
| MavenExecutionResult result = session.getResult(); |
| if(result.hasExceptions()) { |
| for(Throwable e : result.getExceptions()) { |
| log.debug("Exception during execution {}", mojoExecutionKey, e); |
| buildErrors.put(e, mojoExecutionKey); |
| } |
| result.getExceptions().clear(); |
| } |
| } |
| |
| private void refreshResources(IProject project, Collection<File> resources, IProgressMonitor monitor) |
| throws CoreException { |
| for(File file : resources) { |
| IPath path = getProjectRelativePath(project, file); |
| if(path == null) { |
| log.debug("Could not get relative path for file: ", file.getAbsoluteFile()); |
| continue; // odd |
| } |
| |
| IResource resource; |
| if(path.isEmpty()) { |
| resource = project; |
| } else if(!file.exists()) { |
| resource = project.findMember(path); |
| } else if(file.isDirectory()) { |
| resource = project.getFolder(path); |
| } else { |
| resource = project.getFile(path); |
| } |
| if(resource != null) { |
| resource.refreshLocal(IResource.DEPTH_INFINITE, monitor); |
| if(resource.exists()) { |
| // the resource has changed for certain, make sure resource sends IResourceChangeEvent |
| |
| // eclipse uses file lastModified timestamp to detect resource changes |
| // this can result in missing IResourceChangeEvent's under certain conditions |
| // - two builds happen within filesystem resolution (1s on linux and osx, causes problems during unit tests) |
| // - maven mojo deliberately keeps lastModified (unlikely, but theoretically possible) |
| // @see org.eclipse.core.internal.localstore.RefreshLocalVisitor.visit(UnifiedTreeNode) |
| resource.touch(monitor); |
| } |
| } |
| } |
| } |
| |
| public static IPath getProjectRelativePath(IProject project, File file) { |
| if(project == null || file == null) { |
| return null; |
| } |
| |
| IPath projectPath = project.getLocation(); |
| if(projectPath == null) { |
| return null; |
| } |
| |
| IPath filePath = new Path(file.getAbsolutePath()); |
| if(!projectPath.isPrefixOf(filePath)) { |
| return null; |
| } |
| |
| return filePath.removeFirstSegments(projectPath.segmentCount()); |
| } |
| |
| private void processBuildResults(IProject project, MavenProject mavenProject, MavenExecutionResult result, |
| BuildResultCollector results, Map<Throwable, MojoExecutionKey> buildErrors) { |
| IMavenMarkerManager markerManager = MavenPluginActivator.getDefault().getMavenMarkerManager(); |
| |
| // Remove obsolete markers for problems reported by build participants |
| for(Entry<String, List<File>> entry : results.getRemoveMessages().entrySet()) { |
| String buildParticipantId = entry.getKey(); |
| for(File file : entry.getValue()) { |
| deleteBuildParticipantMarkers(project, markerManager, file, buildParticipantId); |
| } |
| } |
| |
| // Create new markers for problems reported by build participants |
| for(Entry<String, List<Message>> messageEntry : results.getMessages().entrySet()) { |
| String buildParticipantId = messageEntry.getKey(); |
| for(Message buildMessage : messageEntry.getValue()) { |
| addBuildParticipantMarker(project, markerManager, buildMessage, buildParticipantId); |
| |
| if(buildMessage.cause != null && buildErrors.containsKey(buildMessage.cause)) { |
| buildErrors.remove(buildMessage.cause); |
| } |
| } |
| } |
| |
| // Create markers for the build errors linked to mojo/plugin executions |
| for(Throwable error : buildErrors.keySet()) { |
| MojoExecutionKey mojoExecutionKey = buildErrors.get(error); |
| SourceLocation markerLocation; |
| if(mojoExecutionKey != null) { |
| markerLocation = SourceLocationHelper.findLocation(mavenProject, mojoExecutionKey); |
| } else { |
| markerLocation = new SourceLocation(1, 0, 0); |
| } |
| BuildProblemInfo problem = new BuildProblemInfo(error, mojoExecutionKey, markerLocation); |
| markerManager.addErrorMarker(project.getFile(IMavenConstants.POM_FILE_NAME), IMavenConstants.MARKER_BUILD_ID, |
| problem); |
| } |
| |
| if(result.hasExceptions()) { |
| markerManager.addMarkers(project.getFile(IMavenConstants.POM_FILE_NAME), IMavenConstants.MARKER_BUILD_ID, result); |
| } |
| } |
| |
| private void deleteBuildParticipantMarkers(IProject project, IMavenMarkerManager markerManager, File file, |
| String buildParticipantId) { |
| IPath path = getProjectRelativePath(project, file); |
| IResource resource = null; |
| if(path != null) { |
| resource = project.findMember(path); |
| } |
| if(resource == null) { |
| resource = project.getFile(IMavenConstants.POM_FILE_NAME); |
| } |
| try { |
| markerManager.deleteMarkers(resource, IMavenConstants.MARKER_BUILD_PARTICIPANT_ID, |
| BUILD_PARTICIPANT_ID_ATTR_NAME, buildParticipantId); |
| } catch(CoreException ex) { |
| log.error(ex.getMessage(), ex); |
| } |
| } |
| |
| private void addBuildParticipantMarker(IProject project, IMavenMarkerManager markerManager, Message buildMessage, |
| String buildParticipantId) { |
| |
| IPath path = getProjectRelativePath(project, buildMessage.file); |
| IResource resource = null; |
| if(path != null) { |
| resource = project.findMember(path); |
| } |
| if(resource == null) { |
| resource = project.getFile(IMavenConstants.POM_FILE_NAME); |
| } |
| int at = buildParticipantId.lastIndexOf('-'); |
| String pluginExecutionKey = buildParticipantId.substring(0, at); |
| String message = buildMessage.message + " (" + pluginExecutionKey + ')'; //$NON-NLS-1$ |
| IMarker marker = markerManager.addMarker(resource, IMavenConstants.MARKER_BUILD_PARTICIPANT_ID, message, |
| buildMessage.line, buildMessage.severity); |
| try { |
| marker.setAttribute(BUILD_PARTICIPANT_ID_ATTR_NAME, buildParticipantId); |
| } catch(CoreException ex) { |
| log.error(ex.getMessage(), ex); |
| } |
| } |
| |
| public void clean(MavenSession session, IMavenProjectFacade projectFacade, |
| Map<MojoExecutionKey, List<AbstractBuildParticipant>> participants, IProgressMonitor monitor) |
| throws CoreException { |
| MavenProject mavenProject = projectFacade.getMavenProject(); |
| IProject project = projectFacade.getProject(); |
| |
| // TODO flush relevant caches |
| |
| final BuildResultCollector participantResults = new BuildResultCollector(); |
| List<BuildContext> incrementalContexts = setupProjectBuildContext(project, IncrementalProjectBuilder.CLEAN_BUILD, |
| null, participantResults); |
| |
| Map<Throwable, MojoExecutionKey> buildErrors = new LinkedHashMap<Throwable, MojoExecutionKey>(); |
| try { |
| for(Entry<MojoExecutionKey, List<AbstractBuildParticipant>> entry : participants.entrySet()) { |
| MojoExecutionKey mojoExecutionKey = entry.getKey(); |
| for(InternalBuildParticipant participant : entry.getValue()) { |
| participantResults.setParticipantId(mojoExecutionKey.getKeyString() + "-" + participant.getClass().getName()); |
| participant.setMavenProjectFacade(projectFacade); |
| participant.setGetDeltaCallback(getDeltaProvider()); |
| participant.setSession(session); |
| participant.setBuildContext((AbstractEclipseBuildContext) incrementalContexts.get(0)); |
| try { |
| participant.clean(monitor); |
| } catch(Exception e) { |
| log.debug("Exception in build participant", e); |
| buildErrors.put(e, mojoExecutionKey); |
| } finally { |
| participant.setMavenProjectFacade(null); |
| participant.setGetDeltaCallback(null); |
| participant.setSession(null); |
| participant.setBuildContext(null); |
| |
| processMavenSessionErrors(session, mojoExecutionKey, buildErrors); |
| } |
| } |
| } |
| } catch(Exception e) { |
| buildErrors.put(e, null); |
| } finally { |
| for(IIncrementalBuildFramework.BuildContext context : incrementalContexts) { |
| context.release(); |
| } |
| } |
| |
| // Refresh files modified by build participants/maven plugins |
| refreshResources(project, participantResults.getFiles(), monitor); |
| |
| MavenExecutionResult result = session.getResult(); |
| processBuildResults(project, mavenProject, result, participantResults, buildErrors); |
| } |
| |
| DeltaProvider getDeltaProvider() { |
| return deltaProvider; |
| } |
| } |