/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Olivier Lamy, CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.maven; import hudson.maven.MavenBuild.ProxyImpl2; import hudson.maven.util.ExecutionEventLogger; import hudson.model.BuildListener; import hudson.model.Result; import hudson.remoting.Channel; import hudson.remoting.DelegatingCallable; import hudson.util.IOException2; import org.apache.maven.cli.PrintStreamLogger; import org.apache.maven.execution.AbstractExecutionListener; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.jvnet.hudson.maven3.agent.Maven3Main; import org.jvnet.hudson.maven3.launcher.Maven3Launcher; import org.jvnet.hudson.maven3.listeners.HudsonMavenExecutionResult; import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.text.NumberFormat; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import static hudson.Util.*; /** * @author Olivier Lamy * @author Kohsuke Kawaguchi */ public class Maven3Builder extends AbstractMavenBuilder implements DelegatingCallable { /** * Flag needs to be set at the constructor, so that this reflects * the setting at master. */ private final boolean profile = MavenProcessFactory.profile; HudsonMavenExecutionResult mavenExecutionResult; protected Maven3Builder(BuildListener listener,Map proxies, Collection modules, List goals, Map systemProps, MavenBuildInformation mavenBuildInformation) { super( listener, modules, goals, systemProps ); this.sourceProxies.putAll(proxies); this.proxies = new HashMap(); for (Entry e : this.sourceProxies.entrySet()) { this.proxies.put(e.getKey(), new FilterImpl(e.getValue(), mavenBuildInformation)); } } public Result call() throws IOException { try { initializeAsynchronousExecutions(); MavenExecutionListener mavenExecutionListener = new MavenExecutionListener( this ); Maven3Launcher.setMavenExecutionListener( mavenExecutionListener ); markAsSuccess = false; registerSystemProperties(); listener.getLogger().println(formatArgs(goals)); int r = Maven3Main.launch( goals.toArray(new String[goals.size()])); // now check the completion status of async ops long startTime = System.nanoTime(); Result waitForAsyncExecutionsResult = waitForAsynchronousExecutions(); if (waitForAsyncExecutionsResult != null) { return waitForAsyncExecutionsResult; } mavenExecutionListener.overheadTime += System.nanoTime()-startTime; if(profile) { NumberFormat n = NumberFormat.getInstance(); PrintStream logger = listener.getLogger(); logger.println("Total overhead was "+format(n,mavenExecutionListener.overheadTime)+"ms"); Channel ch = Channel.current(); logger.println("Class loading " +format(n,ch.classLoadingTime.get()) +"ms, "+ch.classLoadingCount+" classes"); logger.println("Resource loading "+format(n,ch.resourceLoadingTime.get())+"ms, "+ch.resourceLoadingCount+" times"); } mavenExecutionResult = Maven3Launcher.getMavenExecutionResult(); PrintStream logger = listener.getLogger(); if(r==0 && mavenExecutionResult.getThrowables().isEmpty()) return Result.SUCCESS; if (!mavenExecutionResult.getThrowables().isEmpty()) { logger.println( "mavenExecutionResult exceptions not empty"); for(Throwable throwable : mavenExecutionResult.getThrowables()) { logger.println("message : " + throwable.getMessage()); if (throwable.getCause()!=null) { logger.println("cause : " + throwable.getCause().getMessage()); } logger.println("Stack trace : "); throwable.printStackTrace( logger ); } } if(markAsSuccess) { listener.getLogger().println(Messages.MavenBuilder_Failed()); return Result.SUCCESS; } return Result.FAILURE; } catch (NoSuchMethodException e) { throw new IOException2(e); } catch (IllegalAccessException e) { throw new IOException2(e); } catch (InvocationTargetException e) { throw new IOException2(e); } catch (ClassNotFoundException e) { throw new IOException2(e); } catch (Exception e) { throw new IOException2(e); } } private static final class MavenExecutionListener extends AbstractExecutionListener implements Serializable, ExecutionListener { private static final long serialVersionUID = 4942789836756366116L; private final Maven3Builder maven3Builder; /** * Number of total nanoseconds {@link Maven3Builder} spent. */ long overheadTime; private final Map proxies; private final Map> executedMojosPerModule = new ConcurrentHashMap>(); private final Map> reporters; private final Map currentMojoStartPerModuleName = new ConcurrentHashMap(); private ExecutionEventLogger eventLogger; public MavenExecutionListener(Maven3Builder maven3Builder) { this.maven3Builder = maven3Builder; this.proxies = new ConcurrentHashMap(maven3Builder.proxies); for (ModuleName name : this.proxies.keySet()) { executedMojosPerModule.put( name, new CopyOnWriteArrayList() ); } this.reporters = new ConcurrentHashMap>(maven3Builder.reporters); this.eventLogger = new ExecutionEventLogger( new PrintStreamLogger( maven3Builder.listener.getLogger() ) ); } private MavenBuildProxy2 getMavenBuildProxy2(MavenProject mavenProject) { for (Entry entry : proxies.entrySet()) { if (entry.getKey().compareTo( new ModuleName( mavenProject ) ) == 0) { return entry.getValue(); } } return null; } private List getMavenReporters(MavenProject mavenProject) { return reporters.get( new ModuleName( mavenProject ) ); } private void initMojoStartTime( MavenProject mavenProject) { this.currentMojoStartPerModuleName.put( new ModuleName( mavenProject), System.currentTimeMillis()); } private Long getMojoStartTime(MavenProject mavenProject) { return currentMojoStartPerModuleName.get( new ModuleName(mavenProject) ); } /** * @see org.apache.maven.execution.ExecutionListener#projectDiscoveryStarted(org.apache.maven.execution.ExecutionEvent) */ public void projectDiscoveryStarted( ExecutionEvent event ) { this.eventLogger.projectDiscoveryStarted( event ); } /** * @see org.apache.maven.execution.ExecutionListener#sessionStarted(org.apache.maven.execution.ExecutionEvent) */ public void sessionStarted( ExecutionEvent event ) { this.eventLogger.sessionStarted(event); Map buildingProjects = getSessionProjects(event); for (Entry e : this.proxies.entrySet()) { MavenProject project = buildingProjects.get(e.getKey()); if (project!=null) { for (MavenReporter mavenReporter : fixNull(reporters.get(e.getKey()))) { try { mavenReporter.preBuild( e.getValue() ,project, maven3Builder.listener); } catch ( InterruptedException x ) { x.printStackTrace(); } catch ( IOException x ) { x.printStackTrace(); } } } else { // set all modules which are not actually being build (in incremental builds) to NOT_BUILD (JENKINS-9072) LOGGER.fine("Project " + e.getKey() + " needs not be build"); MavenBuildProxy2 proxy = e.getValue(); proxy.start(); proxy.setResult(Result.NOT_BUILT); proxy.end(); } } } /** * @see org.apache.maven.execution.ExecutionListener#sessionEnded(org.apache.maven.execution.ExecutionEvent) */ public void sessionEnded( ExecutionEvent event ) { debug( "sessionEnded" ); this.eventLogger.sessionEnded( event ); Map buildingProjects = getSessionProjects(event); for (Entry e : fixNull(this.proxies.entrySet())) { MavenProject project = buildingProjects.get(e.getKey()); if (project!=null) { for (MavenReporter mavenReporter : reporters.get(e.getKey())) { try { mavenReporter.postBuild( e.getValue() ,project, maven3Builder.listener); } catch ( InterruptedException x ) { x.printStackTrace(); } catch ( IOException x ) { x.printStackTrace(); } } } } } /** * All {@link MavenProject}s in the current session, keyed by their names. */ private Map getSessionProjects(ExecutionEvent event) { List projects = event.getSession().getProjects(); debug("Projects to build: " + projects); Map buildingProjects = new HashMap(); for (MavenProject p : projects) { buildingProjects.put(new ModuleName(p), p); } return buildingProjects; } /** * @see org.apache.maven.execution.ExecutionListener#projectSkipped(org.apache.maven.execution.ExecutionEvent) */ public void projectSkipped( ExecutionEvent event ) { debug("projectSkipped " + gav(event.getProject())); this.eventLogger.projectSkipped( event ); } /** * @see org.apache.maven.execution.ExecutionListener#projectStarted(org.apache.maven.execution.ExecutionEvent) */ public void projectStarted( ExecutionEvent event ) { debug( "projectStarted " + gav(event.getProject())); recordProjectStarted(event); this.eventLogger.projectStarted( event ); } private void recordProjectStarted(ExecutionEvent event) { MavenProject mavenProject = event.getProject(); List mavenReporters = getMavenReporters( mavenProject ); MavenBuildProxy2 mavenBuildProxy2 = getMavenBuildProxy2( mavenProject ); mavenBuildProxy2.start(); for (MavenReporter mavenReporter : fixNull(mavenReporters)) { try { mavenReporter.enterModule( mavenBuildProxy2 ,mavenProject, maven3Builder.listener); } catch ( InterruptedException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } } } /** * @see org.apache.maven.execution.ExecutionListener#projectSucceeded(org.apache.maven.execution.ExecutionEvent) */ public void projectSucceeded( ExecutionEvent event ) { debug( "projectSucceeded "+gav(event.getProject())); recordProjectEnded(event,Result.SUCCESS); this.eventLogger.projectSucceeded( event ); } /** * @see org.apache.maven.execution.ExecutionListener#projectFailed(org.apache.maven.execution.ExecutionEvent) */ public void projectFailed( ExecutionEvent event ) { debug("projectFailed " + gav(event.getProject())); recordProjectEnded(event,Result.FAILURE); this.eventLogger.projectFailed(event); } private void recordProjectEnded(ExecutionEvent event, Result result) { MavenBuildProxy2 mavenBuildProxy2 = getMavenBuildProxy2( event.getProject() ); mavenBuildProxy2.setResult(result); List mavenReporters = getMavenReporters( event.getProject() ); for ( MavenReporter mavenReporter : fixNull(mavenReporters)) { try { mavenReporter.leaveModule( mavenBuildProxy2, event.getProject(), maven3Builder.listener); } catch ( InterruptedException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } } mavenBuildProxy2.end(); } /** * @see org.apache.maven.execution.ExecutionListener#mojoSkipped(org.apache.maven.execution.ExecutionEvent) */ public void mojoSkipped( ExecutionEvent event ) { debug("mojoSkipped " + mojoExec(event)); this.eventLogger.mojoSkipped( event ); } /** * @see org.apache.maven.execution.ExecutionListener#mojoStarted(org.apache.maven.execution.ExecutionEvent) */ public void mojoStarted( ExecutionEvent event ) { debug("mojoStarted " + mojoExec(event)); recordMojoStarted(event); this.eventLogger.mojoStarted( event ); } private void recordMojoStarted(ExecutionEvent event) { initMojoStartTime( event.getProject() ); MavenProject mavenProject = event.getProject(); MojoInfo mojoInfo = new MojoInfo(event); List mavenReporters = getMavenReporters( mavenProject ); MavenBuildProxy2 mavenBuildProxy2 = getMavenBuildProxy2( mavenProject ); for (MavenReporter mavenReporter : fixNull(mavenReporters)) { try { mavenReporter.preExecute( mavenBuildProxy2, mavenProject, mojoInfo, maven3Builder.listener); } catch ( InterruptedException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } } } /** * @see org.apache.maven.execution.ExecutionListener#mojoSucceeded(org.apache.maven.execution.ExecutionEvent) */ public void mojoSucceeded( ExecutionEvent event ) { debug("mojoSucceeded " + mojoExec(event)); recordMojoEnded(event,null); this.eventLogger.mojoSucceeded( event ); } private void recordMojoEnded(ExecutionEvent event, Exception problem) { MavenProject mavenProject = event.getProject(); MojoInfo mojoInfo = new MojoInfo(event); recordExecutionTime(event,mojoInfo); List mavenReporters = getMavenReporters( mavenProject ); MavenBuildProxy2 mavenBuildProxy2 = getMavenBuildProxy2( mavenProject ); mavenBuildProxy2.setExecutedMojos( this.executedMojosPerModule.get( new ModuleName(event) ) ); for (MavenReporter mavenReporter : fixNull(mavenReporters)) { try { mavenReporter.postExecute( mavenBuildProxy2, mavenProject, mojoInfo, maven3Builder.listener, problem); } catch ( InterruptedException e ) { e.printStackTrace(); } catch ( IOException e ) { e.printStackTrace(); } } } /** * Record how long it took to run this mojo. */ private void recordExecutionTime(ExecutionEvent event, MojoInfo mojoInfo) { MavenProject p = event.getProject(); List m = executedMojosPerModule.get(new ModuleName(p)); if (m==null) // defensive check executedMojosPerModule.put(new ModuleName(p), m=new CopyOnWriteArrayList()); Long startTime = getMojoStartTime( event.getProject() ); m.add(new ExecutedMojo( mojoInfo, startTime == null ? 0 : System.currentTimeMillis() - startTime )); } /** * @see org.apache.maven.execution.ExecutionListener#mojoFailed(org.apache.maven.execution.ExecutionEvent) */ public void mojoFailed( ExecutionEvent event ) { debug("mojoFailed " + mojoExec(event)); recordMojoEnded(event, getExecutionException(event)); this.eventLogger.mojoFailed( event ); } private void debug(String msg) { LOGGER.fine(msg); if (DEBUG) maven3Builder.listener.getLogger().println(msg); } private Exception getExecutionException(ExecutionEvent event) { // http://issues.jenkins-ci.org/browse/JENKINS-8493 // with maven 3.0.2 see http://jira.codehaus.org/browse/MNG-4922 // catch NoSuchMethodError if folks not using 3.0.2+ try { return event.getException(); } catch (NoSuchMethodError e) { return new MojoExecutionException(event.getMojoExecution()+" failed"); } } /** * @see org.apache.maven.execution.ExecutionListener#forkStarted(org.apache.maven.execution.ExecutionEvent) */ public void forkStarted( ExecutionEvent event ) { LOGGER.fine("mojo forkStarted " + mojoExec(event)); recordMojoStarted(event); } /** * @see org.apache.maven.execution.ExecutionListener#forkSucceeded(org.apache.maven.execution.ExecutionEvent) */ public void forkSucceeded( ExecutionEvent event ) { LOGGER.fine("mojo forkSucceeded " + mojoExec(event)); recordMojoEnded(event,null); } /** * @see org.apache.maven.execution.ExecutionListener#forkFailed(org.apache.maven.execution.ExecutionEvent) */ public void forkFailed( ExecutionEvent event ) { LOGGER.fine("mojo forkFailed " + mojoExec(event)); recordMojoEnded(event, getExecutionException(event)); } /* Forked life cycle handling -------------------------- As discussed in MavenBuildProxy2, Jenkins has a simplistic view of Maven build sequence, and in particular it doesn't recognize the forked life cycle as a first-class citizen. So to map the reality with the Jenkins' simplified model, we don't report forked project as a separate module start/end. Doing so would require that we remember the nesting, and when the forking is over, we need to tell MavenBuildProxy2 of the right module that its build has resumed. */ public void forkedProjectStarted( ExecutionEvent event ) { debug("forkedProjectStarted " + gav(event.getProject())); // recordProjectStarted(event); this.eventLogger.forkedProjectStarted( event ); } public void forkedProjectSucceeded( ExecutionEvent event ) { debug("forkedProjectSucceeded " +gav(event.getProject())); // recordProjectEnded(event,Result.SUCCESS); this.eventLogger.forkedProjectSucceeded(event); } public void forkedProjectFailed( ExecutionEvent event ) { debug("forkedProjectFailed " +gav(event.getProject())); // recordProjectEnded(event,Result.FAILURE); } private String gav(MavenProject p) { return String.format("%s:%s:%s", p.getGroupId(), p.getArtifactId(), p.getVersion()); } private String mojoExec(ExecutionEvent event) { MojoExecution me = event.getMojoExecution(); return String.format("%s:%s:%s(%s)", me.getGroupId(), me.getArtifactId(), me.getVersion(), me.getExecutionId()); } } public static boolean markAsSuccess; private static final long serialVersionUID = 1L; public static boolean DEBUG = true; private static final Logger LOGGER = Logger.getLogger(Maven3Builder.class.getName()); }