package net.sourceforge.cruisecontrol.builders; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import net.sourceforge.cruisecontrol.Builder; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.Progress; import net.sourceforge.cruisecontrol.util.BuildOutputLogger; import net.sourceforge.cruisecontrol.util.DateUtil; import net.sourceforge.cruisecontrol.util.OSEnvironment; import net.sourceforge.cruisecontrol.util.Util; import net.sourceforge.cruisecontrol.util.ValidationHelper; import org.apache.log4j.Logger; import org.jdom.Element; import org.jdom.Attribute; /** * Maven2 builder class based on the Maven builder class from * <a href="mailto:fvancea@maxiq.com">Florin Vancea</a>. * <br> * Attempts to mimic the behavior of Ant builds, at least as far as CC is * concerned. Basically it's a (heavily) edited version of AntBuilder. No style * at all, but serves its purpose. :) * * @author Steria Benelux Sa/Nv - Provided without any warranty */ public class Maven2Builder extends Builder { private static final Logger LOG = Logger.getLogger(Maven2Builder.class); static final String MVN_BIN_DIR = "bin" + File.separator; private String mvnHome; private String mvnScript; private String pomFile; private String goal; private String sitegoal; private String settingsFile; private String activateProfiles; private long timeout = ScriptRunner.NO_TIMEOUT; private String flags; private final List<Property> properties = new ArrayList<Property>(); /** * Set an Alternate path for the user settings file. * @param settingsFile Alternate path for the user settings file. */ public void setSettingsFile(String settingsFile) { this.settingsFile = settingsFile; } String getSettingsFile() { return settingsFile; } /** * Set the comma-delimited list of profiles to activate. * @param activateProfiles comma-delimited list of profiles to activate. */ public void setActivateProfiles(String activateProfiles) { this.activateProfiles = activateProfiles; } String getActivateProfiles() { return activateProfiles; } /** * Set mvnHome. This will be used to find the mvn script which is mvnHome/bin/ * @param mvnHome the mvn home */ public void setMvnHome(String mvnHome) { if (!mvnHome.endsWith(File.separator)) { mvnHome = mvnHome + File.separator; } this.mvnHome = mvnHome; LOG.debug("MvnHome = " + this.mvnHome + " Mvn should be in " + this.mvnHome + MVN_BIN_DIR); } /** * @param mvnScipt Full path to Maven script, which overrides the default ".../bin/mvn" */ public void setMvnScript(final String mvnScipt) { this.mvnScript = mvnScipt; } String getMvnScript() { return mvnScript; } /** * Set the pom file. This is also used to find the working directory. * @param pomFile the pom file */ public void setPomFile(String pomFile) { this.pomFile = pomFile; LOG.debug("pom file : " + this.pomFile); } String getPomFile() { return pomFile; } public void setGoal(String goal) { this.goal = goal; } public void setSitegoal(String sitegoal) { this.sitegoal = sitegoal; } public Property createProperty() { final Property property = new Property(); properties.add(property); return property; } public void setTimeout(long timeout) { this.timeout = timeout; } /** * @param showMavenOutput true if a BuildOutputLogger will be used to show live output. * @deprecated Use {@link #setLiveOutput(boolean)} instead. */ public void setShowBuildOutput(final boolean showMavenOutput) { setLiveOutput(showMavenOutput); } /** * @return showMavenOutput true if a BuildOutputLogger will be used to show live output. * @deprecated Use {@link #isLiveOutput()} instead. */ boolean getShowBuildOutput() { return isLiveOutput(); } /** * Check at the starting of CC if required attributes are set * @throws CruiseControlException When the current settings are not valid. */ public void validate() throws CruiseControlException { super.validate(); ValidationHelper.assertFalse(mvnScript != null && mvnHome != null, "'mvnhome' and 'mvnscript' cannot both be set." + "\n\tmvnhome: " + mvnHome + ";\n\tmvnscript: " + mvnScript); if (mvnHome != null) { ValidationHelper.assertIsSet(mvnHome, "mvnhome", getClass()); final File mvnHomeDir = new File(mvnHome); ValidationHelper.assertTrue(mvnHomeDir.exists() && mvnHomeDir.isDirectory(), "'mvnhome' must exist and be a directory. Expected to find " + mvnHomeDir.getAbsolutePath() + "; Check the mvnhome attribute of the maven2 plugin"); mvnScript = findMaven2Script(Util.isWindows()); LOG.debug("Using mvnHome: " + mvnHome + ", mvnScript found and set to: " + mvnScript); } ValidationHelper.assertTrue(mvnScript != null, "'mvnhome' or 'mvnscript' must be set."); ValidationHelper.assertIsSet(pomFile, "pomfile", getClass()); ValidationHelper.assertIsSet(goal, "goal", getClass()); if (getGoalSets().isEmpty()) { ValidationHelper.assertIsSet(null, "goal", getClass()); } if (settingsFile != null) { ValidationHelper.assertTrue(new File(settingsFile).exists(), "The settings file could not be found : " + settingsFile); } } /** * build and return the results via xml. debug status can be determined * from log4j category once we get all the logging in place. */ public Element build(final Map<String, String> buildProperties, final Progress progressIn) throws CruiseControlException { final Progress progress = getShowProgress() ? progressIn : null; //This check is done here because the pom can be downloaded after CC is started // and before this plugin is run final File filePomFile = new File(pomFile); validatePomFile(filePomFile); final File workingDir = filePomFile.getAbsoluteFile().getParentFile(); LOG.debug("Working dir is : " + workingDir.toString()); final long startTime = System.currentTimeMillis(); final Element buildLogElement = new Element("build"); final List<String> goalSets = getGoalSets(); build(buildProperties, workingDir, goalSets, buildLogElement, progress); final List<String> sitegoalSets = getSitegoalSets(); if (!sitegoalSets.isEmpty()) { build(buildProperties, workingDir, sitegoalSets, buildLogElement, progress); } final long endTime = System.currentTimeMillis(); buildLogElement.setAttribute("time", DateUtil.getDurationAsString((endTime - startTime))); return buildLogElement; } private void build(final Map<String, String> buildProperties, final File workingDir, final List<String> goalSets, final Element buildLogElement, final Progress progress) throws CruiseControlException { Attribute saveErrorAttribute = buildLogElement.getAttribute("error"); if (saveErrorAttribute != null) { synchronized (buildLogElement) { buildLogElement.removeAttribute(saveErrorAttribute); } } for (final String goals : goalSets) { final OSEnvironment env = new OSEnvironment(); // Merge the environment with the configuration mergeEnv(env); final Maven2Script script = new Maven2Script(this, buildLogElement, goals, progress); script.setBuildProperties(buildProperties); script.setProperties(properties); script.setEnv(env); final BuildOutputLogger buildOutputConsumer = getBuildOutputConsumer(buildProperties.get(Builder.BUILD_PROP_PROJECTNAME), workingDir, null); final ScriptRunner scriptRunner = new ScriptRunner(); final boolean scriptCompleted = scriptRunner.runScript(workingDir, script, timeout, buildOutputConsumer); script.flushCurrentElement(); if (!scriptCompleted) { LOG.warn("Build timeout timer of " + timeout + " seconds has expired"); synchronized (buildLogElement) { buildLogElement.setAttribute("error", "build timeout"); } } else if (script.getExitCode() != 0) { // The maven.bat actually never returns error, // due to internal cleanup called after the execution itself... synchronized (buildLogElement) { buildLogElement.setAttribute("error", "Return code is " + script.getExitCode()); } } if (buildLogElement.getAttribute("error") != null) { break; } } if (saveErrorAttribute != null) { synchronized (buildLogElement) { buildLogElement.setAttribute(saveErrorAttribute); } } } void validatePomFile(final File filePomFile) throws CruiseControlException { ValidationHelper.assertTrue(filePomFile.exists(), "the pom file could not be found : " + filePomFile.getAbsolutePath() + "; Check the 'pomfile' attribute: " + pomFile); ValidationHelper.assertTrue(filePomFile.isFile(), "the pom file can't be a directory : " + filePomFile.getAbsolutePath() + "; Check the 'pomfile' attribute: " + pomFile); } public Element buildWithTarget(final Map<String, String> properties, final String target, final Progress progress) throws CruiseControlException { final String origGoal = goal; try { goal = target; return build(properties, progress); } finally { goal = origGoal; } } /** * Produces sets of goals, ready to be run each in a distinct call to Maven. * Separation of sets in "goal" attribute is made with '|'. * * @return a List containing String elements */ List<String> getGoalSets() { return getGoalSets(goal); } /** * Produces sets of site goals, ready to be run each in a distinct call to Maven. * Separation of sets in "sitegoal" attribute is made with '|'. * * @return a List containing String elements */ List<String> getSitegoalSets() { return getGoalSets(sitegoal); } private List<String> getGoalSets(String goalattribute) { final List<String> list = new ArrayList<String>(); if (goalattribute != null) { final StringTokenizer stok = new StringTokenizer(goalattribute, "|"); while (stok.hasMoreTokens()) { final String subSet = stok.nextToken().trim(); if (subSet == null || subSet.length() == 0) { continue; } list.add(subSet); } } return list; } /** * Set flags. E.g.: '-U -o' * @param flags set the flags */ public void setFlags(String flags) { this.flags = flags; } String getFlags() { return flags; } /** * If the mvnhome attribute is set, then this method returns the correct shell script * to use for a specific environment. * @param isWindows if True, return a path tailored for Windows (suffixed w/ .bat). * @return the path to the maven launch script based on the current OS and the mvnhome attribute value * @throws CruiseControlException if the mvnhome attributed is not set */ protected String findMaven2Script(boolean isWindows) throws CruiseControlException { if (mvnHome == null) { throw new CruiseControlException("mvnhome attribute not set."); } if (isWindows) { return mvnHome + MVN_BIN_DIR + "mvn.bat"; } else { return mvnHome + MVN_BIN_DIR + "mvn"; } } }