package hudson.plugins.doxygen; import hudson.AbortException; import hudson.FilePath; import hudson.plugins.doxygen.DoxygenArchiver.DoxygenArchiverDescriptor; import hudson.remoting.VirtualChannel; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; public class DoxygenDirectoryParser implements FilePath.FileCallable<FilePath>, Serializable{ private static final long serialVersionUID = 1L; private static final Pattern DRIVE_PATTERN = Pattern.compile("[A-Za-z]:\\\\.+"); private static final Logger LOGGER = Logger.getLogger(DoxygenDirectoryParser.class.getName()); private transient Map<String, String> doxyfileInfos = new HashMap<String, String>(); private static final String DOXYGEN_KEY_OUTPUT_DIRECTORY = "OUTPUT_DIRECTORY"; private static final String DOXYGEN_KEY_GENERATE_HTML = "GENERATE_HTML"; private static final String DOXYGEN_KEY_HTML_OUTPUT = "HTML_OUTPUT"; private static final String DOXYGEN_DEFAULT_HTML_OUTPUT = "html"; private static final String DOXYGEN_VALUE_YES = "YES"; private String publishType; private String doxygenHtmlDirectory; private String doxyfilePath; public DoxygenDirectoryParser(String publishType, String doxyfilePath, String doxygenHtmlDirectory){ this.publishType=publishType; this.doxyfilePath=doxyfilePath; this.doxygenHtmlDirectory=doxygenHtmlDirectory; } public FilePath invoke(java.io.File workspace, VirtualChannel channel) throws IOException { try{ return (DoxygenArchiverDescriptor.DOXYGEN_HTMLDIRECTORY_PUBLISHTYPE).equals(publishType) ?retrieveDoxygenDirectoryFromHudsonConfiguration(doxygenHtmlDirectory, new FilePath(workspace)) :retrieveDoxygenDirectoryFromDoxyfile(doxyfilePath,new FilePath(workspace)); } catch (InterruptedException ie){ throw new AbortException(ie.getMessage()); } } /** * Determine if Doxygen generate HTML reports */ private boolean isDoxygenGenerateHtml(){ if (doxyfileInfos==null) return false; String generatedHtmlKeyVal = doxyfileInfos.get(DOXYGEN_KEY_GENERATE_HTML); // If the 'GENERATE_HTML Key is not present, by default the HTML generated documentation is actived. if (generatedHtmlKeyVal==null){ return true; } return DOXYGEN_VALUE_YES.equalsIgnoreCase(generatedHtmlKeyVal); } /** * Retrieve the generated doxygen HTML directory from Hudson configuration given by the user */ private FilePath retrieveDoxygenDirectoryFromHudsonConfiguration(String doxygenHtmlDirectory, FilePath base) throws InterruptedException,IOException { FilePath doxygenGeneratedDir = null; LOGGER.log(Level.INFO,"Using the Doxygen HTML directory specified by the configuration."); if (doxygenHtmlDirectory==null){ throw new IllegalArgumentException("Error on the given doxygen html directory."); } if (doxygenHtmlDirectory.trim().length()==0){ throw new IllegalArgumentException("Error on the given doxygen html directory."); } doxygenGeneratedDir = new FilePath(base,doxygenHtmlDirectory); if (!doxygenGeneratedDir.exists()){ throw new AbortException("The directory '"+ doxygenGeneratedDir + "' doesn't exist."); } return doxygenGeneratedDir; } /** * Gets the directory where the Doxygen is generated for the given build. */ private FilePath getDoxygenGeneratedDir(FilePath base) { if (doxyfileInfos==null) return null; String outputDirectory = doxyfileInfos.get(DOXYGEN_KEY_OUTPUT_DIRECTORY); String doxyGenDir = null; if (outputDirectory!= null && outputDirectory.trim().length() != 0){ doxyGenDir = outputDirectory; } String outputHTML = doxyfileInfos.get(DOXYGEN_KEY_HTML_OUTPUT); if (outputHTML== null || outputHTML.trim().length() == 0){ outputHTML = "html"; LOGGER.log(Level.INFO,"The "+DOXYGEN_KEY_HTML_OUTPUT+" tag is not present or is left blank." + DOXYGEN_DEFAULT_HTML_OUTPUT+ " will be used as the default path."); } else { doxyGenDir = (doxyGenDir!=null)?(doxyGenDir+ File.separator + outputHTML):outputHTML; return new FilePath(base, doxyGenDir); } return null; } /** * Load the Doxyfile Doxygen file in memory */ private void loadDoxyFile(FilePath doxyfilePath) throws IOException, InterruptedException{ LOGGER.log(Level.INFO,"The Doxyfile path is '"+doxyfilePath.toURI()+"'."); final String separator = "="; InputStream ips=new FileInputStream(new File(doxyfilePath.toURI())); InputStreamReader ipsr=new InputStreamReader(ips); BufferedReader br=new BufferedReader(ipsr); String line=null; List<String> doxyfileDirectories = new ArrayList<String>(); if (doxyfileInfos==null){ doxyfileInfos=new HashMap<String, String>(); } while ((line=br.readLine())!=null){ if (doxyfileLineIsAComment(line)) { // Prevent that a comment containing a separator get's interpreted somehow continue; } String[] elements = line.split(separator); if (elements.length == 1) { // Either there is no separator in the line or there is nothing behind the separator (i.e. there's no value to the key) continue; } if (elements[0].startsWith("@INCLUDE_PATH")){ Collections.addAll(doxyfileDirectories,(elements[1].split(" "))); } else if (elements[0].startsWith("@INCLUDE")){ processIncludeFile(doxyfileDirectories, doxyfilePath.getParent(), elements[1].trim()); } else{ doxyfileInfos.put(elements[0].trim(), elements[1].trim()); } } br.close(); ipsr.close(); ips.close(); } private boolean doxyfileLineIsAComment(String line) { return line.trim().startsWith("#"); } private static boolean isAbsolute(String rel) { return rel.startsWith("/") || DRIVE_PATTERN.matcher(rel).matches(); } private void processIncludeFileWithNoIncludedDirectories(FilePath parentFile, String includedFile) throws IOException, InterruptedException{ FilePath includedFilePath = isAbsolute(includedFile)?new FilePath(new File(includedFile)):new FilePath(parentFile,includedFile); if (!includedFilePath.exists()){ throw new AbortException("Doxyfile is incorrect. Included file '" + includedFile + "' doesn't exist."); } //Call again loadDoxyFile with the the included doxygen file path loadDoxyFile(includedFilePath); } private void processIncludeFileWithIncludedDirectories(List<String> doxyfileDirectories,FilePath parentFile, String includedFile) throws IOException, InterruptedException{ FilePath includedFilePath = null; boolean findIncludedFileInDirectories = false; //Determine if the included doxygen file is absolute if (isAbsolute(includedFile)){ //Call again loadDoxyFile with the the included doxygen file path loadDoxyFile(new FilePath(new File(includedFile))); return; } // else, test if the included doxygen file is at the parent root if ((includedFilePath=new FilePath(parentFile,includedFile)).exists()){ //Call again loadDoxyFile with the the included doxygen file path loadDoxyFile(includedFilePath); return; } // else, iterate on each included directory for retrieve the included doxygen file for (String doxyfileDirectory : doxyfileDirectories){ //Retrieve the filepath for the included directory (it can be absolute) FilePath directoryFilePath=isAbsolute(doxyfileDirectory)?new FilePath(new File(doxyfileDirectory)):new FilePath(parentFile,doxyfileDirectory); //If the current included directory doesn't exist, continue, no errors if (!directoryFilePath.exists()){ continue; } //Retrieve the filepath for the included doxygen file includedFilePath=new FilePath(directoryFilePath,includedFile); //At this point, if the computed included file doesn't exist, perhaps, it's included in the next directory in directories list if (!includedFilePath.exists()){ continue; } //Call again loadDoxyFile with the the included doxygen file path loadDoxyFile(includedFilePath); findIncludedFileInDirectories = true; break; } //At this point, the included doxygen file path is not determined //Never happen, check by the doxygen tool if (!findIncludedFileInDirectories){ throw new AbortException("Doxyfile is incorrect. Included file '" + includedFile + "' doesn't exist."); } } private void processIncludeFile(List<String> doxyfileDirectories, FilePath parentFile, String includedFile) throws IOException, InterruptedException{ //We haven't any @INCLUDE_PATH if (doxyfileDirectories==null || doxyfileDirectories.isEmpty()){ processIncludeFileWithNoIncludedDirectories(parentFile,includedFile); } //We have some @INCLUDE_PATH else{ processIncludeFileWithIncludedDirectories(doxyfileDirectories, parentFile,includedFile); } } /** * Retrieve the generated doxygen HTML directory from Doxyfile */ private FilePath retrieveDoxygenDirectoryFromDoxyfile(String doxyfilePath, FilePath base) throws IOException, InterruptedException { FilePath doxygenGeneratedDir; LOGGER.log(Level.INFO,"Using the Doxyfile information."); //Load the Doxyfile loadDoxyFile(base.child(doxyfilePath)); //Process if the generate htnl tag is set to 'YES' if (isDoxygenGenerateHtml()){ //Retrieve the generated doxygen directory from the build doxygenGeneratedDir = getDoxygenGeneratedDir(base); if (!doxygenGeneratedDir.exists()){ throw new AbortException("The directory '"+ doxygenGeneratedDir + "' doesn't exist."); } } else { //The GENERATE_HTML tag is not set to 'YES' throw new AbortException("The tag "+DOXYGEN_KEY_GENERATE_HTML+" is not set to '" + DOXYGEN_VALUE_YES+ "'. The Doxygen plugin publishes only HTML documentations."); } return doxygenGeneratedDir; } }