package hudson.plugins.ccm; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.Build; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.tasks.Builder; import hudson.util.ArgumentListBuilder; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Map; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; /** * <p> * When the user configures the project and enables this builder, * {@link CCMDescriptor#newInstance(StaplerRequest)} is invoked * and a new {@link CcmBuilder} is created. The created * instance is persisted to the project configuration XML by using * XStream, so this allows you to use instance fields (like {@link #name}) * to remember the configuration. * * <p> * When a build is performed, the {@link #perform(Build, Launcher, BuildListener)} method * will be invoked. * * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br * @since 7 april, 2010 */ public class CCMBuilder extends Builder { /** * Identifies {@link CCM} to be used. */ private final String ccmName; private final String srcFolder; private final Boolean recursive; private final Boolean outputXml; private final String numMetrics; @Extension public static final CCMDescriptor DESCRIPTOR = new CCMDescriptor(); @DataBoundConstructor public CCMBuilder(String ccmName, String srcFolder, Boolean recursive, Boolean outputXml, String numMetrics) { super(); this.ccmName = ccmName; this.srcFolder = srcFolder; this.recursive = recursive; this.outputXml = outputXml; this.numMetrics = ((numMetrics == null || numMetrics.length()<=0) ? "30" : numMetrics); } public String getCcmName() { return ccmName; } public String getSrcFolder() { return srcFolder; } public Boolean isRecursive() { return recursive; } public Boolean getRecursive() { return recursive; } public Boolean isOutputXml() { return outputXml; } public String getNumMetrics() { return numMetrics; } public Descriptor<Builder> getDescriptor() { return DESCRIPTOR; } public CCMInstallation getCCM() { CCMInstallation foundInstallation = null; for ( CCMInstallation installation : DESCRIPTOR.getInstallations() ) { if ( this.getCcmName() != null && installation.getName().equals(this.getCcmName())) { foundInstallation = installation; } } return foundInstallation; } private String yesOrNo(boolean flag) { if(flag) { return "yes"; } return "no"; } private String getSrcFolderRelativeToWorkspace(String srcFolder, FilePath workspace ) { return new File(workspace.getName(), srcFolder).getAbsolutePath(); } private void createXMLConfig(FilePath workspace, BuildListener listener) throws IOException { // TBD: the file name is hard coded... fix it later. File ccmConfigFile = new File(workspace.getName(), "ccm.config.xml"); listener.getLogger().println("Creating CCM config file " + ccmConfigFile.getAbsolutePath()); //TBD: improve this ccmConfigFile.createNewFile(); StringBuffer buffer = new StringBuffer(); // TBD: eck! correct this. later... buffer.append("<ccm>\n"); buffer.append("<exclude></exclude>\n"); buffer.append("<analyze>\n"); buffer.append("<folder>"+this.getSrcFolderRelativeToWorkspace(this.getSrcFolder(), workspace)+"</folder>\n"); buffer.append("</analyze>\n"); buffer.append("<recursive>"+this.yesOrNo(this.isRecursive())+"</recursive>\n"); buffer.append("<outputXML>yes</outputXML>\n"); buffer.append("<numMetrics>"+this.getNumMetrics()+"</numMetrics>\n"); buffer.append("</ccm>\n"); listener.getLogger().println("Writing CCM configuration into file"); listener.getLogger().println(buffer.toString()); FileWriter writer = new FileWriter(ccmConfigFile); writer.append(buffer.toString()); writer.flush(); // TBD: do it better. writer.close(); } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { ArgumentListBuilder args = new ArgumentListBuilder(); CCMInstallation installation = getCCM(); String execName = installation.getPathToCCM(); if ( installation == null ) { listener.fatalError("Invalid CCM installation"); return false; } else { File exec = installation.getExecutable(); if ( ! installation.getExists() ) { listener.fatalError(exec+" doesn't exist"); return false; } listener.getLogger().println("Path To CCM.exe: " + execName); args.add(execName); } // create project ccm config file FilePath workspace = build.getWorkspace(); try { createXMLConfig(workspace, listener); } catch (IOException e) { Util.displayIOException(e,listener); e.printStackTrace( listener.fatalError("Error creating CCM config file") ); return false; } args.add(new File(workspace.getName(), "ccm.config.xml")); //According to the Ant builder source code, in order to launch a program //from the command line in windows, we must wrap it into cmd.exe. This //way the return code can be used to determine whether or not the build failed. if(!launcher.isUnix()) { args.prepend("cmd.exe","/C"); args.add("&&","exit","%%ERRORLEVEL%%"); } else { listener.fatalError("CCM can be run only in Win platforms."); return false; } // Try to execute the command listener.getLogger().println("Executing command: "+args.toStringWithQuote()); try { Map<String,String> env = build.getEnvironment(listener); int r = launcher.launch().cmds(args).envs(env).stdout(listener).pwd(build.getModuleRoot()).join(); return r==0; } catch (IOException e) { Util.displayIOException(e,listener); e.printStackTrace( listener.fatalError("command execution failed") ); return false; } } }