/******************************************************************************* * CruiseControl, a Continuous Integration Toolkit Copyright (c) 2001, * 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.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import net.sourceforge.cruisecontrol.Builder; import net.sourceforge.cruisecontrol.CruiseControlException; import net.sourceforge.cruisecontrol.Progress; import net.sourceforge.cruisecontrol.util.EmptyElementFilter; import net.sourceforge.cruisecontrol.util.ValidationHelper; import net.sourceforge.cruisecontrol.util.DateUtil; import org.apache.log4j.Logger; import org.jdom.Attribute; import org.jdom.CDATA; import org.jdom.Element; import org.jdom.filter.ContentFilter; import org.jdom.input.SAXBuilder; import org.xml.sax.XMLFilter; public class NantBuilder extends Builder { protected static final String DEFAULT_LOGGER = "NAnt.Core.XmlLogger"; private static final Logger LOG = Logger.getLogger(NantBuilder.class); private String nantWorkingDir = null; private String buildFile = "default.build"; private String target = ""; private String tempFileName = "log.xml"; private boolean useLogger; private final List<Property> properties = new ArrayList<Property>(); private boolean useDebug = false; private boolean useQuiet = false; private String loggerClassName = DEFAULT_LOGGER; private File saveLogDir = null; private String targetFramework = null; private long timeout = ScriptRunner.NO_TIMEOUT; public void validate() throws CruiseControlException { super.validate(); ValidationHelper.assertIsSet(buildFile, "buildFile", this.getClass()); ValidationHelper.assertIsSet(target, "target", this.getClass()); ValidationHelper.assertFalse(useDebug && useQuiet, "'useDebug' and 'useQuiet' can't be used together"); if (saveLogDir != null) { ValidationHelper.assertTrue(saveLogDir.isDirectory(), "'saveLogDir' must exist and be a directory"); } } /** * 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; // @todo To support progress, determine text pattern indicating progress messages in output, // see AntScript.consumeLine() as an example final File workingDir = nantWorkingDir != null ? new File(nantWorkingDir) : null; final NantScript script = getNantScript(); script.setBuildFile(buildFile); script.setBuildProperties(buildProperties); script.setNantProperties(properties); script.setLoggerClassName(loggerClassName); script.setTarget(target); script.setTargetFramework(targetFramework); script.setTempFileName(tempFileName); script.setUseDebug(useDebug); // script.setUseDebug(useVerbose); script.setUseLogger(useLogger); script.setUseQuiet(useQuiet); final long startTime = System.currentTimeMillis(); final ScriptRunner scriptRunner = new ScriptRunner(); final boolean scriptCompleted = scriptRunner.runScript(workingDir, script, timeout, getBuildOutputConsumer(buildProperties.get(Builder.BUILD_PROP_PROJECTNAME), workingDir, null)); final long endTime = System.currentTimeMillis(); final File logFile = new File(nantWorkingDir, tempFileName); final Element buildLogElement; if (!scriptCompleted) { LOG.warn("Build timeout timer of " + timeout + " seconds has expired"); buildLogElement = new Element("build"); buildLogElement.setAttribute("error", "build timeout"); } else { //read in log file as element, return it buildLogElement = getNantLogAsElement(logFile); saveNantLog(logFile); logFile.delete(); } final Element element = translateNantErrorElements(buildLogElement); element.setAttribute("time", DateUtil.getDurationAsString((endTime - startTime))); return element; } public Element buildWithTarget(final Map<String, String> properties, final String buildTarget, final Progress progress) throws CruiseControlException { final String origTarget = target; try { target = buildTarget; return build(properties, progress); } finally { target = origTarget; } } // factory method for mock... protected NantScript getNantScript() { return new NantScript(); } /** * Set the location to which the NAnt log will be saved before Cruise * Control merges the file into its log. * * @param dir * the absolute path to the directory where the NAnt log will be * saved or relative path to where you started CruiseControl */ public void setSaveLogDir(String dir) { saveLogDir = null; if (dir != null && !dir.trim().equals("")) { saveLogDir = new File(dir.trim()); } } /** * Set the working directory where NAnt will be invoked. This parameter gets * set in the XML file via the nantWorkingDir attribute. The directory can * be relative (to the cruisecontrol current working directory) or absolute. * * @param dir * the directory to make the current working directory. */ public void setNantWorkingDir(String dir) { nantWorkingDir = dir; } /** * Set the name of the temporary file used to capture output. * * @param tempFileName the name of the temporary file used to capture output. */ public void setTempFile(String tempFileName) { this.tempFileName = tempFileName; } /** * Set the Ant target(s) to invoke. * * @param target * the target(s) name. */ public void setTarget(String target) { this.target = target; } /** * Sets the name of the build file that NAnt will use. The NAnt default is * default.build, use this to override it. * * @param buildFile * the name of the build file. */ public void setBuildFile(String buildFile) { this.buildFile = buildFile; } /** * Sets whether NAnt will use the custom loggers. * * @param useLogger whether NAnt will use the custom loggers. */ public void setUseLogger(boolean useLogger) { this.useLogger = useLogger; } void saveNantLog(File logFile) { if (saveLogDir == null) { return; } try { final File newNantLogFile = new File(saveLogDir, tempFileName); newNantLogFile.createNewFile(); final FileInputStream in = new FileInputStream(logFile); try { final FileOutputStream out = new FileOutputStream(newNantLogFile); try { byte[] buf = new byte[1024]; int len; while ((len = in.read(buf)) > 0) { out.write(buf, 0, len); } } finally { out.close(); } } finally { in.close(); } } catch (IOException ioe) { LOG.error(ioe); LOG.error("Unable to create file: " + new File(saveLogDir, tempFileName)); } } public Property createProperty() { final Property property = new Property(); properties.add(property); return property; } protected Element getNantLogAsElement(File file) throws CruiseControlException { if (!file.exists()) { throw new CruiseControlException("NAnt logfile " + file.getAbsolutePath() + " does not exist."); } try { SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser"); // get rid of empty <task>- and <message>-elements created by Ant's // XmlLogger XMLFilter emptyTaskFilter = new EmptyElementFilter("task"); XMLFilter emptyMessageFilter = new EmptyElementFilter("message"); emptyMessageFilter.setParent(emptyTaskFilter); builder.setXMLFilter(emptyMessageFilter); return builder.build(file).getRootElement(); } catch (Exception ee) { if (ee instanceof CruiseControlException) { throw (CruiseControlException) ee; } File saveFile = new File(file.getParentFile(), System.currentTimeMillis() + file.getName()); file.renameTo(saveFile); throw new CruiseControlException("Error reading : " + file.getAbsolutePath() + ". Saved as : " + saveFile.getAbsolutePath(), ee); } } public void setUseDebug(boolean debug) { useDebug = debug; } public void setUseQuiet(boolean quiet) { useQuiet = quiet; } public String getLoggerClassName() { return loggerClassName; } public void setLoggerClassName(String logger) { loggerClassName = logger; } public void setTargetFramework(String framework) { this.targetFramework = framework; } protected Element translateNantErrorElements(Element buildLogElement) throws CruiseControlException { buildLogElement.setName("build"); Element failure = buildLogElement.getChild("failure"); if (failure != null) { Element buildError = failure.getChild("builderror"); if (buildError == null) { throw new CruiseControlException("Expected a builderror element under build/failure"); } Element message = buildError.getChild("message"); if (message == null) { throw new CruiseControlException("Expected a message element under build/failure/builderror"); } List matches = message.getContent(new ContentFilter(ContentFilter.CDATA)); if (matches.size() == 0) { throw new CruiseControlException("Expected CDATA content in build/failure/builderror/message/element"); } String errorMessage = ((CDATA) matches.get(0)).getText(); buildLogElement.setAttribute(new Attribute("error", errorMessage)); } return buildLogElement; } /** * @param timeout The timeout to set. */ public void setTimeout(long timeout) { this.timeout = timeout; } }