package org.schmant.hudson.builder; import hudson.Launcher; import hudson.model.Build; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.model.TaskListener; import hudson.tasks.Builder; import hudson.util.ArgumentListBuilder; import hudson.util.FormValidation; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.ServletException; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; public final class SchmantBuilder extends Builder { public static final SchmantBuilderDescriptor DESCRIPTOR = new SchmantBuilderDescriptor(); // The JAVA_HOME environment variable private static final String JAVA_HOME = "JAVA_HOME"; private static final String PATH_SEPARATOR = System.getProperty("path.separator"); private static final String SCHMANT_LAUNCHER_CLASS = "org.schmant.Launcher"; private final String m_scriptFile; private final String m_systemProperties; private final String m_variables; private final String m_scriptArguments; private final String m_taskPackagePath; private final String m_scriptEngineName; private final String m_verbosity; // Additional classpath, for loading script engine jars, etc. private final String m_additionalClasspath; @DataBoundConstructor public SchmantBuilder(String scriptFile, String variables, String scriptArguments, String systemProperties, String taskPackagePath, String scriptEngineName, String verbosity, String additionalClasspath) { m_scriptFile = scriptFile; m_systemProperties = systemProperties; m_variables = variables; m_scriptArguments = scriptArguments; m_taskPackagePath = taskPackagePath; m_scriptEngineName = scriptEngineName; m_verbosity = verbosity != null ? verbosity : "Info"; m_additionalClasspath = additionalClasspath; } public String getScriptFile() { return m_scriptFile; } public String getSystemProperties() { return m_systemProperties; } public String getVariables() { return m_variables; } public String getScriptArguments() { return m_scriptArguments; } public String getTaskPackagePath() { return m_taskPackagePath; } public String getScriptEngineName() { return m_scriptEngineName; } public String getVerbosity() { return m_verbosity; } public String getAdditionalClasspath() { return m_additionalClasspath; } private boolean validateConfiguration(BuildListener listener) { boolean ok = true; if (DESCRIPTOR.getSchmantHome() == null) { listener.fatalError("The Schmant home directory is not configured"); ok = false; } if (m_scriptFile == null) { listener.fatalError("No script file set"); ok = false; } return ok; } private String getJavaCommand(Build<?, ?> build, TaskListener log) throws IOException, InterruptedException { String javaHome = build.getEnvironment(log).get(JAVA_HOME); if (javaHome == null) { throw new RuntimeException("The " + JAVA_HOME + " environment variable is not set. Check the Hudson configuration"); } File javaHomef = new File(javaHome); File javaf = new File(javaHomef, "bin/java"); if (javaf.exists() && javaf.isFile()) { return javaf.getAbsolutePath(); } File javaexef = new File(javaHomef, "bin/java.exe"); if (!javaexef.exists() && javaexef.isFile()) { throw new RuntimeException("Neither of the " + javaf + " or " + javaexef + " files exist. Check Hudson's JAVA_HOME configuration"); } return javaexef.getAbsolutePath(); } private String createClasspath(File schmantHome) { StringBuilder sb = new StringBuilder(); if (m_additionalClasspath != null) { sb.append(m_additionalClasspath).append(PATH_SEPARATOR); } // Add all Jar files in schmantHome/lib to the classpath // Assume that schmantHome is an absolute path File lib = new File(schmantHome, "lib"); File[] jarFiles = lib.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".jar"); } }); for (File jarFile : jarFiles) { sb.append(jarFile.getPath()).append(PATH_SEPARATOR); } return sb.toString(); } private Map<String, String> getProperties(String text) throws IOException { Properties props = new Properties(); if (text != null) { props.load(new StringReader(text)); } HashMap<String, String> res = new HashMap<String, String>(props.size()); for (Map.Entry<Object, Object> prop : props.entrySet()) { res.put((String) prop.getKey(), (String) prop.getValue()); } return res; } private void addVerbosity(ArgumentListBuilder al) { if ("Warnings".equals(m_verbosity)) { al.add("-q"); } else if ("Errors".equals(m_verbosity)) { al.add("-q").add("-q"); } else if ("Debug".equals(m_verbosity)) { al.add("-v"); } else if ("Trace".equals(m_verbosity)) { al.add("-v").add("-v"); } else if ("Info".equals(m_verbosity)) { // Ignore } else { throw new RuntimeException("Invalid verbosity level: " + m_verbosity); } } private Map<String, String> createVariables(Build<?, ?> build) { Map<String, String> res = new HashMap<String, String>(); res.put("buildNumber", "" + build.getNumber()); res.put("buildId", build.getId()); res.put("jobName", build.getParent().getName()); res.put("buildTag", "hudson-" + build.getParent().getName() + "-" + build.getNumber()); res.put("executorNumber", Integer.toString(build.getExecutor().getNumber())); res.put("workspace", build.getWorkspace().getRemote()); return res; } private ArgumentListBuilder createArgumentListBuilder(Build<?, ?> build, BuildListener listener, File schmantHome) throws IOException, InterruptedException { ArgumentListBuilder res = new ArgumentListBuilder(); res.add(getJavaCommand(build, listener)); res.add("-cp", createClasspath(schmantHome)); if (m_systemProperties != null) { res.addKeyValuePairs("-D", getProperties(m_systemProperties)); } res.add(SCHMANT_LAUNCHER_CLASS); res.add("-sh", schmantHome.getPath()); if ((m_taskPackagePath != null) && (m_taskPackagePath.length() > 0)) { res.add("-t", m_taskPackagePath); } if ((m_scriptEngineName != null) && (m_scriptEngineName.length() > 0)) { res.add("--script-engine", m_scriptEngineName); } if ((m_verbosity != null) && (m_verbosity.length() > 0) && !"Info".equals(m_verbosity)) { addVerbosity(res); } // Create standard variables Map<String, String> variables = createVariables(build); if (m_variables != null) { // Set new variables and maybe override standard variables with // values set in the configuration variables.putAll(getProperties(m_variables)); } res.addKeyValuePairs("-p", variables); res.add(m_scriptFile); if ((m_scriptArguments != null) && (m_scriptArguments.length() > 0)) { res.addTokenized(m_scriptArguments); } return res; } private Launcher.ProcStarter createLauncher(Build<?, ?> build, BuildListener listener, File schmantHome) throws IOException, InterruptedException { Launcher l = build.getWorkspace().createLauncher(listener); Launcher.ProcStarter res = l.launch(); res.cmds(createArgumentListBuilder(build, listener, schmantHome)); res.stdout(listener); res.pwd(build.getWorkspace()); return res; } public boolean perform(Build<?, ?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { if (!validateConfiguration(listener)) { return false; } File schmantHome = new File(DESCRIPTOR.getSchmantHome()); listener.getLogger().println("Schmant home: " + schmantHome); Launcher.ProcStarter ps = createLauncher(build, listener, schmantHome); int exitCode = ps.join(); if (exitCode != 0) { listener.fatalError("The Schmant process exited with error code " + exitCode); return false; } else { return true; } } public Descriptor<Builder> getDescriptor() { return DESCRIPTOR; } /** * Apparently, this <i>must</i> be an inner class for the configuration to * work. */ public static class SchmantBuilderDescriptor extends Descriptor<Builder> { private String m_schmantHome; private SchmantBuilderDescriptor() { super(SchmantBuilder.class); load(); } public FormValidation doCheckSchmantHome(StaplerRequest req, StaplerResponse rsp, @QueryParameter final String value) throws IOException, ServletException { if (value.length() == 0) { return FormValidation.warning("Schmant home must be set"); } else { File f = new File(value); if (!f.exists()) { return FormValidation.error(f + " does not exist"); } else if (!f.isDirectory()) { return FormValidation.error(f + " is not a directory"); } else { return FormValidation.ok(); } } } /** * This human readable name is used in the configuration screen. */ public String getDisplayName() { return "Invoke Schmant"; } public boolean configure(StaplerRequest req, JSONObject o) throws FormException { m_schmantHome = o.getString("home"); save(); return super.configure(req, o); } public String getSchmantHome() { return m_schmantHome; } } }