/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt, Peter Hayes * * 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 htmlpublisher; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.Hudson; import hudson.model.Result; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import hudson.util.FormValidation; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.Collection; import java.util.Collections; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import htmlpublisher.HtmlPublisherTarget; /** * Saves HTML reports for the project and publishes them. * * @author Kohsuke Kawaguchi * @author Mike Rooney */ public class HtmlPublisher extends Recorder { private final ArrayList<HtmlPublisherTarget> reportTargets; @DataBoundConstructor public HtmlPublisher(List<HtmlPublisherTarget> reportTargets) { this.reportTargets = new ArrayList<HtmlPublisherTarget>(reportTargets); } public ArrayList<HtmlPublisherTarget> getReportTargets() { return this.reportTargets; } private static void writeFile(ArrayList<String> lines, File path) throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter(path)); for (int i = 0; i < lines.size(); i++) { bw.write(lines.get(i)); bw.newLine(); } bw.close(); return; } public ArrayList<String> readFile(String filePath) throws java.io.FileNotFoundException, java.io.IOException { ArrayList<String> aList = new ArrayList<String>(); try { final InputStream is = this.getClass().getResourceAsStream(filePath); try { final Reader r = new InputStreamReader(is); try { final BufferedReader br = new BufferedReader(r); try { String line = null; while ((line = br.readLine()) != null) { aList.add(line); } br.close(); r.close(); is.close(); } finally { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } } finally { try { r.close(); } catch (IOException e) { e.printStackTrace(); } } } finally { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } catch (IOException e) { // failure e.printStackTrace(); } return aList; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException { listener.getLogger().println("[htmlpublisher] Archiving HTML reports..."); // Grab the contents of the header and footer as arrays ArrayList<String> headerLines; ArrayList<String> footerLines; try { headerLines = this.readFile("/htmlpublisher/HtmlPublisher/header.html"); footerLines = this.readFile("/htmlpublisher/HtmlPublisher/footer.html"); } catch (FileNotFoundException e1) { e1.printStackTrace(); return false; } catch (IOException e1) { e1.printStackTrace(); return false; } for (int i=0; i < this.reportTargets.size(); i++) { // Create an array of lines we will eventually write out, initially the header. ArrayList<String> reportLines = new ArrayList<String>(headerLines); HtmlPublisherTarget reportTarget = this.reportTargets.get(i); boolean keepAll = reportTarget.getKeepAll(); FilePath archiveDir = build.getWorkspace().child(reportTarget.getReportDir()); FilePath targetDir = reportTarget.getArchiveTarget(build); String levelString = keepAll ? "BUILD" : "PROJECT"; listener.getLogger().println("[htmlpublisher] Archiving at " + levelString + " level " + archiveDir + " to " + targetDir); // The index name might be a comma separated list of names, so let's figure out all the pages we should index. String[] csvReports = reportTarget.getReportFiles().split(","); ArrayList<String> reports = new ArrayList<String>(); for (int j=0; j < csvReports.length; j++) { String report = csvReports[j]; report = report.trim(); // Ignore blank report names caused by trailing or double commas. if (report.equals("")) {continue;} reports.add(report); String tabNo = "tab" + (j + 1); // Make the report name the filename without the extension. int end = report.lastIndexOf("."); String reportName; if (end > 0) { reportName = report.substring(0, end); } else { reportName = report; } String tabItem = "<li id=\"" + tabNo + "\" class=\"unselected\" onclick=\"updateBody('" + tabNo + "');\" value=\"" + report + "\">" + reportName + "</li>"; reportLines.add(tabItem); } // Add the JS to change the link as appropriate. String hudsonUrl = Hudson.getInstance().getRootUrl(); AbstractProject job = build.getProject(); reportLines.add("<script type=\"text/javascript\">document.getElementById(\"hudson_link\").innerHTML=\"Back to " + job.getName() + "\";</script>"); // If the URL isn't configured in Hudson, the best we can do is attempt to go Back. if (hudsonUrl == null) { reportLines.add("<script type=\"text/javascript\">document.getElementById(\"hudson_link\").onclick = function() { history.go(-1); return false; };</script>"); } else { String jobUrl = hudsonUrl + job.getUrl(); reportLines.add("<script type=\"text/javascript\">document.getElementById(\"hudson_link\").href=\"" + jobUrl + "\";</script>"); } try { if (!archiveDir.exists()) { listener.error("Specified HTML directory '" + archiveDir + "' does not exist."); build.setResult(Result.FAILURE); return true; } else if (!keepAll) { // We are only keeping one copy at the project level, so remove the old one. targetDir.deleteRecursive(); } if (archiveDir.copyRecursiveTo("**/*", targetDir) == 0) { listener.error("Directory '" + archiveDir + "' exists but failed copying to '" + targetDir + "'."); if (build.getResult().isBetterOrEqualTo(Result.UNSTABLE)) { // If the build failed, don't complain that there was no coverage. // The build probably didn't even get to the point where it produces coverage. listener.error("This is especially strange since your build otherwise succeeded."); } build.setResult(Result.FAILURE); return true; } } catch (IOException e) { Util.displayIOException(e, listener); e.printStackTrace(listener.fatalError("HTML Publisher failure")); build.setResult(Result.FAILURE); return true; } reportTarget.handleAction(build); // Now add the footer. reportLines.addAll(footerLines); // And write this as the index try { writeFile(reportLines, new File(targetDir.getRemote(), reportTarget.getWrapperName())); } catch (IOException e) { e.printStackTrace(); } } return true; } @Override public Collection<? extends Action> getProjectActions(AbstractProject<?, ?> project) { if (this.reportTargets.isEmpty()) { return Collections.emptyList(); } else { ArrayList<Action> actions = new ArrayList<Action>(); for (HtmlPublisherTarget target : this.reportTargets) { actions.add(target.getProjectAction(project)); } return actions; } } @Extension public static class DescriptorImpl extends BuildStepDescriptor<Publisher> { @Override public String getDisplayName() { // return Messages.JavadocArchiver_DisplayName(); return "Publish HTML reports"; } /** * Performs on-the-fly validation on the file mask wildcard. */ public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException, ServletException { FilePath ws = project.getSomeWorkspace(); return ws != null ? ws.validateRelativeDirectory(value) : FormValidation.ok(); } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } }