/******************************************************************************* * 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 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(new DeltaProvider() { public IResourceDelta getDelta(IProject project) { return 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(!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; } }