package net.sourceforge.cruisecontrol.builders; import java.io.File; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import net.sourceforge.cruisecontrol.util.OSEnvironment; 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; /** * Maven2 script class based on the Maven builder class from * <a href="mailto:epugh@opensourceconnections.com">Eric Pugh</a>. * <br> * Contains all the details related to running a Maven based build. * @author Steria Benelux Sa/Nv - Provided without any warranty */ public final class Maven2Script implements Script, StreamConsumer { private static final String ERROR = "error"; private static final String SUCCESS = "success"; private static final Logger LOG = Logger.getLogger(Maven2Script.class); private String goalset; private String mvn; private String pomFile; private final String settingsFile; private final String flags; private final Element buildLogElement; //Log to store result of the execution for CC private Map<String, String> buildProperties; private final String activateProfiles; private List<Property> properties; private final Progress progress; private OSEnvironment env; private int exitCode; private Element currentElement; /** * @param maven2Builder the maven2builder executing this script * @param buildLogElement Log to store result of the execution for CC * @param goals the goalset to execute * @param progress used to update progress */ public Maven2Script(final Maven2Builder maven2Builder, final Element buildLogElement, final String goals, final Progress progress) { this.buildLogElement = buildLogElement; this.mvn = maven2Builder.getMvnScript(); this.pomFile = maven2Builder.getPomFile(); this.goalset = goals; this.settingsFile = maven2Builder.getSettingsFile(); this.flags = maven2Builder.getFlags(); this.activateProfiles = maven2Builder.getActivateProfiles(); 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 { //usage: maven [options] [<goal(s)>] [<phase(s)>] Commandline cmdLine = new Commandline(); cmdLine.setExecutable(mvn); //Run in non-interactive mode. cmdLine.createArgument("-B"); //If log is enabled for CC, enable it for mvn if (LOG.isDebugEnabled()) { cmdLine.createArgument("-X"); } //Alternate path for the user settings file if (settingsFile != null) { cmdLine.createArguments("-s", settingsFile); } if (pomFile != null) { cmdLine.createArguments("-f", new File(pomFile).getName()); } //activate specified profiles if (activateProfiles != null) { cmdLine.createArguments("-P", activateProfiles); } if (flags != null) { StringTokenizer stok = new StringTokenizer(flags, " \t\r\n"); while (stok.hasMoreTokens()) { cmdLine.createArgument(stok.nextToken()); } } if (goalset != null) { StringTokenizer stok = new StringTokenizer(goalset, " \t\r\n"); while (stok.hasMoreTokens()) { cmdLine.createArgument(stok.nextToken()); } } for (final String key : buildProperties.keySet()) { final String value = buildProperties.get(key); if (value.indexOf(' ') == -1) { cmdLine.createArgument("-D" + key + "=" + value); } else { // @todo Find better way to handle when property values contains spaces. // Just using Commandline results in entire prop being quoted: // "-Dcvstimestamp=2006-11-29 03:41:04 GMT" // and using Commandline.quoteArgument() results in this when used on value only: // '-Dcvstimestamp="2006-11-29 03:41:04 GMT"' // all these (includeing no manipulation) cause errors in maven's parsing of the command line. // For now, we just replace spaces with underscores so at least some form of the prop is available final String spacelessValue = value.replace(' ', '_'); LOG.warn("Maven2Script altering build property with space. Key:" + key + "; Orig Value:" + value + "; New Value: " + spacelessValue); cmdLine.createArgument("-D" + key + "=" + spacelessValue); } } for (final Property property : properties) { cmdLine.createArgument("-D" + property.getName() + "=" + property.getValue()); } cmdLine.setEnv(env); //If log is enabled, log the command line 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; } /** * Analyze the output of the mvn command. This is used to detect errors * or successful build. */ public void consumeLine(final String line) { final String level; final String infoLine; if (line == null || line.length() == 0 || buildLogElement == null) { return; } synchronized (buildLogElement) { //To detect errors, we will parse the output of the mvn command. //Don't forget that this can stop working if changes are made in mvn. if (line.startsWith("[ERROR]")) { level = "error"; infoLine = line.substring(line.indexOf(']') + 1).trim(); } else if (line.startsWith("[INFO]") || line.startsWith("[DEBUG]")) { level = "info"; infoLine = line.substring(line.indexOf(']') + 1).trim(); } else { level = "info"; infoLine = line; } if (infoLine.startsWith("BUILD SUCCESSFUL")) { buildLogElement.setAttribute(SUCCESS, "BUILD SUCCESSFUL detected"); } else if (infoLine.startsWith("BUILD FAILURE")) { buildLogElement.setAttribute(ERROR, "BUILD FAILURE detected"); } else if (infoLine.startsWith("BUILD ERROR")) { buildLogElement.setAttribute(ERROR, "BUILD ERROR detected"); } else if (infoLine.startsWith("FATAL ERROR")) { buildLogElement.setAttribute(ERROR, "FATAL ERROR 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 (infoLine.startsWith("[") && infoLine.endsWith("]") && infoLine.indexOf(":") > -1) { // heuristically this is a goal marker, makeNewCurrentElement(infoLine.substring(1, infoLine.length() - 1)); 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", level); 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); if (progress != null) { progress.setValue(cTask); } } } protected void flushCurrentElement() { if (buildLogElement == null) { return; } synchronized (buildLogElement) { if (currentElement != null) { if (buildLogElement.getAttribute(SUCCESS) != null && buildLogElement.getAttribute(ERROR) == null) { LOG.debug("Ok : BUILD SUCCESSFUL"); // Ok build successfull } else if (buildLogElement.getAttribute(ERROR) != null) { // All the messages of the last (failed) goal should be // switched to priority error final List lst = currentElement.getChildren("message"); if (lst != null) { for (final Object aLst : lst) { final Element msg = (Element) aLst; 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 mvnScript The mavenScript to set. */ public void setMvnScript(String mvnScript) { this.mvn = mvnScript; } /** * @param pomFile The projectFile to set. */ public void setPomFile(String pomFile) { this.pomFile = pomFile; } /** * @param properties The properties to set. */ public void setProperties(final List<Property> properties) { this.properties = properties; } /** * @return the exitCode. */ public int getExitCode() { return exitCode; } /** * @param exitCode The exitCode to set. */ public void setExitCode(int exitCode) { this.exitCode = exitCode; } /** * @param env * The environment variables of the script, or <code>null</code> if to * inherit the environment of the current process. */ public void setEnv(final OSEnvironment env) { this.env = env; } // setEnv }