/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2003, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.builders; import java.io.File; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.log4j.Logger; import org.jdom.CDATA; import org.jdom.Element; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.Progress; import net.sourceforge.cruisecontrol.util.Commandline; import net.sourceforge.cruisecontrol.util.StreamConsumer; /** * Maven script class. * * Contains all the details related to running a Maven based build. * @author <a href="mailto:epugh@opensourceconnections.com">Eric Pugh</a> */ public class MavenScript implements Script, StreamConsumer { private static final Logger LOG = Logger.getLogger(MavenScript.class); private Map<String, String> buildProperties; private String goalset; private String mavenScript; private String projectFile; private int exitCode; /** Global log to produce, passed in from MavenBuilder */ private final Element buildLogElement; private Element currentElement = null; private final Progress progress; /** * @param buildLogElement Global log to produce, passed in from MavenBuilder. * @param progress progress callback object, may be null. */ public MavenScript(final Element buildLogElement, final Progress progress) { this.buildLogElement = buildLogElement; this.progress = progress; } /** * construct the command that we're going to execute. * @return Commandline holding command to be executed * @throws CruiseControlException */ public Commandline buildCommandline() throws CruiseControlException { Commandline cmdLine = new Commandline(); if (mavenScript != null) { cmdLine.setExecutable(mavenScript); } else { throw new CruiseControlException( "Non-script running is not implemented yet.\n" + "As of 1.0-beta-10 Maven startup mechanism is still changing..."); } for (final String key : buildProperties.keySet()) { cmdLine.createArgument("-D" + key + "=" + buildProperties.get(key)); } if (LOG.isDebugEnabled()) { cmdLine.createArgument("-X"); } cmdLine.createArgument("-b"); // no banner if (projectFile != null) { // we need only the name of the file File pFile = new File(projectFile); cmdLine.createArguments("-p", pFile.getName()); } if (goalset != null) { StringTokenizer stok = new StringTokenizer(goalset, " \t\r\n"); while (stok.hasMoreTokens()) { cmdLine.createArgument(stok.nextToken()); } } if (LOG.isDebugEnabled()) { final StringBuilder sb = new StringBuilder(); sb.append("Executing Command: "); final String[] args = cmdLine.getCommandline(); for (final String arg : args) { sb.append(arg); sb.append(" "); } LOG.debug(sb.toString()); } return cmdLine; } /** * Ugly parsing of Maven output into some Elements. * Gets called from StreamPumper. */ public synchronized void consumeLine(final String line) { if (line == null || line.length() == 0 || buildLogElement == null) { return; } synchronized (buildLogElement) { // The BAT never returns errors, so I'll catch it like this. Brrr. if (line.startsWith("BUILD FAILED")) { buildLogElement.setAttribute("error", "BUILD FAILED detected"); } else if (line.startsWith("org.apache.maven.MavenException")) { buildLogElement.setAttribute("error", "You have encountered an unknown error running Maven: " + line); } else if (line.startsWith("The build cannot continue")) { buildLogElement.setAttribute("error", "The build cannot continue: Unsatisfied Dependency"); } else if ( line.endsWith(":") // heuristically this is a goal marker, && !line.startsWith(" ") // but debug lines might look like that && !line.startsWith("\t")) { makeNewCurrentElement(line.substring(0, line.lastIndexOf(':'))); return; // Do not log the goal itself } final Element msg = new Element("message"); msg.addContent(new CDATA(line)); // Initially call it "info" level. // If "the going gets tough" we'll switch this to "error" msg.setAttribute("priority", "info"); if (currentElement == null) { buildLogElement.addContent(msg); } else { currentElement.addContent(msg); } } } private void makeNewCurrentElement(String cTask) { if (buildLogElement == null) { return; } synchronized (buildLogElement) { flushCurrentElement(); currentElement = new Element("mavengoal"); currentElement.setAttribute("name", cTask); currentElement.setAttribute("time", "? seconds"); } if (progress != null) { progress.setValue(cTask); } } protected void flushCurrentElement() { if (buildLogElement == null) { return; } synchronized (buildLogElement) { if (currentElement != null) { if (buildLogElement.getAttribute("error") != null) { // All the messages of the last (failed) goal should be // switched to priority error List lst = currentElement.getChildren("message"); if (lst != null) { Iterator it = lst.iterator(); while (it.hasNext()) { Element msg = (Element) it.next(); msg.setAttribute("priority", "error"); } } } buildLogElement.addContent(currentElement); } currentElement = null; } } /** * @param buildProperties The buildProperties to set. */ public void setBuildProperties(final Map<String, String> buildProperties) { this.buildProperties = buildProperties; } /** * @param goalset The goalset to set. */ public void setGoalset(String goalset) { this.goalset = goalset; } /** * @param mavenScript The mavenScript to set. */ public void setMavenScript(String mavenScript) { this.mavenScript = mavenScript; } /** * @param projectFile The projectFile to set. */ public void setProjectFile(String projectFile) { this.projectFile = projectFile; } /** * @return Returns the exitCode. */ public int getExitCode() { return exitCode; } /** * @param exitCode The exitCode to set. */ public void setExitCode(int exitCode) { this.exitCode = exitCode; } }