<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">From 8f139892d3854b8f4c1f9d1f10c4c57665edcbd5 Mon Sep 17 00:00:00 2001
From: Guo Yong &lt;wolfg1969@gmail.com&gt;
Date: Mon, 17 Dec 2012 23:22:34 +0800
Subject: [PATCH 1/4] add ToolInstallation support

---
 pom.xml                                            |    4 +-
 .../org/jvnet/hudson/plugins/SbtPluginBuilder.java |  227 +++++++++++++++-----
 2 files changed, 173 insertions(+), 58 deletions(-)

diff --git a/pom.xml b/pom.xml
index e5ae32e..38bf045 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,8 +3,8 @@
   &lt;parent&gt;
     &lt;groupId&gt;org.jenkins-ci.plugins&lt;/groupId&gt;
     &lt;artifactId&gt;plugin&lt;/artifactId&gt;
-    &lt;version&gt;1.396&lt;/version&gt;
-    &lt;relativePath&gt;../pom.xml&lt;/relativePath&gt;
+    &lt;version&gt;1.431&lt;/version&gt;
+    &lt;!--&lt;relativePath&gt;../pom.xml&lt;/relativePath&gt;--&gt;
   &lt;/parent&gt;
 
   &lt;artifactId&gt;sbt&lt;/artifactId&gt;
diff --git a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
index 3690f25..45d6141 100644
--- a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
+++ b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
@@ -1,18 +1,11 @@
 package org.jvnet.hudson.plugins;
 
-import hudson.EnvVars;
-import hudson.Extension;
-import hudson.FilePath;
-import hudson.Launcher;
-import hudson.Util;
-import hudson.model.BuildListener;
-import hudson.model.Computer;
-import hudson.model.JDK;
-import hudson.model.Result;
-import hudson.model.AbstractBuild;
-import hudson.model.AbstractProject;
+import hudson.*;
+import hudson.model.*;
+import hudson.slaves.NodeSpecific;
 import hudson.tasks.BuildStepDescriptor;
 import hudson.tasks.Builder;
+import hudson.tools.*;
 import hudson.util.ArgumentListBuilder;
 import hudson.util.FormValidation;
 
@@ -20,6 +13,7 @@ import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.logging.Logger;
 import java.util.regex.Matcher;
@@ -27,6 +21,7 @@ import java.util.regex.Pattern;
 
 import javax.servlet.ServletException;
 
+import jenkins.model.Jenkins;
 import jline.ArgumentCompletor;
 
 import net.sf.json.JSONObject;
@@ -154,50 +149,70 @@ public class SbtPluginBuilder extends Builder {
 			throws IllegalArgumentException, InterruptedException, IOException {
 		ArgumentListBuilder args = new ArgumentListBuilder();
 
-		DescriptorImpl descriptor = (DescriptorImpl) getDescriptor();
+//		DescriptorImpl descriptor = (DescriptorImpl) getDescriptor();
 
-		String launcherPath = descriptor.getJar(name).getPath();
+        EnvVars env = build.getEnvironment(listener);
+        env.overrideAll(build.getBuildVariables());
 
-		if (StringUtils.isBlank(launcherPath)) {
-			throw new IllegalArgumentException("SBT jar path is empty");
-		}
+		SbtInstallation sbt = getSbt();
+        if (sbt == null) {
+            throw new IllegalArgumentException("SBT jar path is empty");
+        } else {
+            sbt = sbt.forNode(Computer.currentComputer().getNode(), listener);
+            sbt = sbt.forEnvironment(env);
 
-		if (!launcher.isUnix()) {
-			args.add("cmd.exe", "/C");
-		}
+            String launcherPath = sbt.getHome();
 
-		// java
-		String javaExePath;
-		
-		JDK jdk = build.getProject().getJDK();
-		Computer computer = Computer.currentComputer();
-		if (computer != null &amp;&amp; jdk != null) { // just in case were not in a build
-		    // use node specific installers, etc
-		    jdk = jdk.forNode(computer.getNode(), listener);
-		}
-				
-		if (jdk != null) {
-			javaExePath = new File(jdk.getBinDir()
-					+ "/java").getAbsolutePath();
-		} else {
-			javaExePath = "java";
-		}
-		args.add(javaExePath);
+            if (launcherPath == null) {
+                throw new IllegalArgumentException("SBT jar path is empty");
+            }
+
+            if (!launcher.isUnix()) {
+                args.add("cmd.exe", "/C");
+            }
 
-		splitAndAddArgs(jvmFlags, args);
-		splitAndAddArgs(sbtFlags, args);
+            // java
+            String javaExePath;
 
-		args.add("-jar");
+            JDK jdk = build.getProject().getJDK();
+            Computer computer = Computer.currentComputer();
+            if (computer != null &amp;&amp; jdk != null) { // just in case were not in a build
+                // use node specific installers, etc
+                jdk = jdk.forNode(computer.getNode(), listener);
+            }
 
-		args.add(launcherPath);
+            if (jdk != null) {
+                javaExePath = new File(jdk.getBinDir()
+                        + "/java").getAbsolutePath();
+            } else {
+                javaExePath = "java";
+            }
+            args.add(javaExePath);
 
-		for (String action : split(actions)) {
-			args.add(action);
-		}
+            splitAndAddArgs(jvmFlags, args);
+            splitAndAddArgs(sbtFlags, args);
+
+            args.add("-jar");
+
+            args.add(launcherPath);
+
+            for (String action : split(actions)) {
+                args.add(action);
+            }
+        }
 
 		return args;
 	}
 
+    private SbtInstallation getSbt() {
+        for (SbtInstallation sbtInstallation: getDescriptor().getInstallations()) {
+            if (name != null &amp;&amp; name.equals(sbtInstallation.getName())) {
+                return sbtInstallation;
+            }
+        }
+        return null;
+    }
+
 	/**
 	 * Split arguments and add them to the args list
 	 * 
@@ -257,7 +272,10 @@ public class SbtPluginBuilder extends Builder {
 	public static final class DescriptorImpl extends
 			BuildStepDescriptor&lt;Builder&gt; {
 
-		private volatile Jar[] jars = new Jar[0];
+//		private volatile Jar[] jars = new Jar[0];
+
+        @CopyOnWrite
+        private volatile SbtInstallation[] installations = new SbtInstallation[0];
 
 		public DescriptorImpl() {
 			super(SbtPluginBuilder.class);
@@ -276,16 +294,16 @@ public class SbtPluginBuilder extends Builder {
 			return "Build using sbt";
 		}
 
-		public Jar getJar(String name) {
+		/*public Jar getJar(String name) {
 			for (Jar jar : jars) {
 				if (jar.getName().equals(name)) {
 					return jar;
 				}
 			}
 			return null;
-		}
+		}*/
 
-		@Override
+		/*@Override
 		public boolean configure(StaplerRequest req, JSONObject formData)
 				throws FormException {
 			
@@ -303,11 +321,24 @@ public class SbtPluginBuilder extends Builder {
 
 				return false;
 			}
-		}
+		}*/
 
-		public Jar[] getJars() {
+		/*public Jar[] getJars() {
 			return jars;
-		}
+		}*/
+
+        public SbtInstallation.DescriptorImpl getToolDescriptor() {
+            return ToolInstallation.all().get(SbtInstallation.DescriptorImpl.class);
+        }
+
+        public SbtInstallation[] getInstallations() {
+            return installations;
+        }
+
+        public void setInstallations(SbtInstallation... sbtInstallations) {
+            this.installations = sbtInstallations;
+            save();
+        }
 
 	}
 
@@ -315,15 +346,15 @@ public class SbtPluginBuilder extends Builder {
 	 * Representation of an sbt launcher. Several such launchers can be defined
 	 * in Jenkins properties to choose among when running a project.
 	 */
-	public static final class Jar implements Serializable {
+	/*public static final class Jar implements Serializable {
 		private static final long serialVersionUID = 1L;
-
+    */
 		/** The human-friendly name of this launcher */
-		private String name;
+	//	private String name;
 		
 		/** The path to the launcher */
-		private String path;
-
+	//	private String path;
+    /*
 		@DataBoundConstructor
 		public Jar(String name, String path) {
 			this.name = name;
@@ -345,5 +376,89 @@ public class SbtPluginBuilder extends Builder {
 		public void setPath(String path) {
 			this.path = path;
 		}
-	}
+	} */
+
+    public static final class SbtInstallation extends ToolInstallation implements
+            EnvironmentSpecific&lt;SbtInstallation&gt;, NodeSpecific&lt;SbtInstallation&gt; {
+
+        @DataBoundConstructor
+        public SbtInstallation(String name, String home, List&lt;? extends ToolProperty&lt;?&gt;&gt; properties) {
+            super(name, home, properties);
+        }
+
+        public SbtInstallation forEnvironment(EnvVars environment) {
+            return new SbtInstallation(getName(), environment.expand(getHome()), getProperties().toList());
+        }
+
+        public SbtInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException {
+            return new SbtInstallation(getName(), translateFor(node, log), getProperties().toList());
+        }
+
+        @Extension
+        public static class DescriptorImpl extends ToolDescriptor&lt;SbtInstallation&gt; {
+
+            @Override
+            public SbtInstallation[] getInstallations() {
+                return Jenkins.getInstance().getDescriptorByType(SbtPluginBuilder.DescriptorImpl.class)
+                        .getInstallations();
+            }
+
+            @Override
+            public void setInstallations(SbtInstallation... installations) {
+                Jenkins.getInstance().getDescriptorByType(SbtPluginBuilder.DescriptorImpl.class)
+                        .setInstallations(installations);
+            }
+
+            @Override
+            public List&lt;? extends ToolInstaller&gt; getDefaultInstallers() {
+                return Collections.singletonList(new SbtInstaller(null));
+            }
+
+            @Override
+            public String getDisplayName() {
+                return "Sbt";
+            }
+
+            /**
+             * Checks if the sbt-launch.jar is exist.
+             */
+            public FormValidation doCheckHome(@QueryParameter File value) {
+
+                if (!Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) {
+                    return FormValidation.ok();
+                }
+
+                if (!value.exists()) {
+                    return FormValidation.error("sbt-launch.jar not found");
+                }
+
+                return FormValidation.ok();
+            }
+        }
+    }
+
+    /**
+     * Automatic Sbt installer from scala-sbt.org
+     */
+    public static class SbtInstaller extends DownloadFromUrlInstaller {
+
+        @DataBoundConstructor
+        protected SbtInstaller(String id) {
+            super(id);
+        }
+
+        @Extension
+        public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl&lt;SbtInstaller&gt; {
+
+            @Override
+            public String getDisplayName() {
+                return "";
+            }
+
+            @Override
+            public boolean isApplicable(Class&lt;? extends ToolInstallation&gt; toolType) {
+                return toolType == SbtInstallation.class;
+            }
+        }
+    }
 }
-- 
1.7.9.5


From 6260744235294c34ac280cbeee7c1bffb3bf2320 Mon Sep 17 00:00:00 2001
From: wolfg1969 &lt;wolfg1969@gmail.com&gt;
Date: Tue, 18 Dec 2012 16:54:41 +0800
Subject: [PATCH 2/4] config gui

---
 .../org/jvnet/hudson/plugins/SbtPluginBuilder.java |  368 +++++++++++---------
 .../SbtPluginBuilder/SbtInstallation/config.jelly  |   11 +
 .../hudson/plugins/SbtPluginBuilder/config.jelly   |    4 +-
 .../hudson/plugins/SbtPluginBuilder/global.jelly   |   31 --
 4 files changed, 209 insertions(+), 205 deletions(-)
 create mode 100644 src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
 delete mode 100644 src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.jelly
 delete mode 100644 src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.out.xml

diff --git a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
index 45d6141..d91876b 100644
--- a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
+++ b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
@@ -33,128 +33,128 @@ import org.kohsuke.stapler.StaplerRequest;
 
 /**
  * sbt plugin {@link Builder}.
- * 
- * &lt;p&gt;
+ * &lt;p/&gt;
+ * &lt;p/&gt;
  * When the user configures the project and enables this builder,
  * {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked and a new
  * {@link SbtPluginBuilder} 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.
- * 
- * &lt;p&gt;
+ * &lt;p/&gt;
+ * &lt;p/&gt;
  * When a build is performed, the
  * {@link #perform(AbstractBuild, Launcher, BuildListener)} method will be
  * invoked.
- * 
+ *
  * @author Uzi Landsmann
  */
 public class SbtPluginBuilder extends Builder {
 
-	public static final Logger LOGGER = Logger.getLogger(SbtPluginBuilder.class
-			.getName());
+    public static final Logger LOGGER = Logger.getLogger(SbtPluginBuilder.class
+        .getName());
 
-	private final String name;
-	private final String jvmFlags;
-	private final String sbtFlags;
-	private final String actions;
+    private final String name;
+    private final String jvmFlags;
+    private final String sbtFlags;
+    private final String actions;
     private String subdirPath;
 
-	// Fields in config.jelly must match the parameter names in the
-	// "DataBoundConstructor"
-	@DataBoundConstructor
-	public SbtPluginBuilder(String name, String jvmFlags, String sbtFlags,
-			String actions, String subdirPath) {
-		this.name = name;
-		this.jvmFlags = jvmFlags;
-		this.sbtFlags = sbtFlags;
-		this.actions = actions;
+    // Fields in config.jelly must match the parameter names in the
+    // "DataBoundConstructor"
+    @DataBoundConstructor
+    public SbtPluginBuilder(String name, String jvmFlags, String sbtFlags,
+                            String actions, String subdirPath) {
+        this.name = name;
+        this.jvmFlags = jvmFlags;
+        this.sbtFlags = sbtFlags;
+        this.actions = actions;
         this.subdirPath = subdirPath;
-	}
+    }
 
-	public String getName() {
-		return name;
-	}
+    public String getName() {
+        return name;
+    }
 
-	public String getJvmFlags() {
-		return jvmFlags;
-	}
+    public String getJvmFlags() {
+        return jvmFlags;
+    }
 
-	public String getSbtFlags() {
-		return sbtFlags;
-	}
+    public String getSbtFlags() {
+        return sbtFlags;
+    }
 
-	public String getActions() {
-		return actions;
-	}
+    public String getActions() {
+        return actions;
+    }
 
     public String getSubdirPath() {
         return subdirPath;
     }
 
-	/**
-	 * Perform the sbt build. Interpret the command arguments and create a
-	 * command line, then run it.
-	 */
-	@Override
-	public boolean perform(AbstractBuild build, Launcher launcher,
-			BuildListener listener) {
+    /**
+     * Perform the sbt build. Interpret the command arguments and create a
+     * command line, then run it.
+     */
+    @Override
+    public boolean perform(AbstractBuild build, Launcher launcher,
+                           BuildListener listener) {
 
-		EnvVars env = null;
-		FilePath workDir = build.getModuleRoot();
-		try {
+        EnvVars env = null;
+        FilePath workDir = build.getModuleRoot();
+        try {
 
-			ArgumentListBuilder cmdLine = buildCmdLine(build, launcher,
-					listener);
-			String[] cmds = cmdLine.toCommandArray();
-			env = build.getEnvironment(listener);
+            ArgumentListBuilder cmdLine = buildCmdLine(build, launcher,
+                listener);
+            String[] cmds = cmdLine.toCommandArray();
+            env = build.getEnvironment(listener);
 
             if (subdirPath != null &amp;&amp; subdirPath.length() &gt; 0) {
                 workDir = new FilePath(workDir, subdirPath);
             }
 
             int exitValue = launcher.launch().cmds(cmds).envs(env)
-					.stdout(listener).pwd(workDir).join();
-
-			boolean success = (exitValue == 0);
-			build.setResult(success ? Result.SUCCESS : Result.FAILURE);
-			return success;
-		} catch (IllegalArgumentException e) {
-			// Util.displayIOException(e, listener);
-			e.printStackTrace(listener.fatalError("command execution failed: "
-					+ e.getMessage()));
-			build.setResult(Result.FAILURE);
-			return false;
-		} catch (IOException e) {
-			Util.displayIOException(e, listener);
-			e.printStackTrace(listener.fatalError("command execution failed: "
-					+ e.getMessage()));
-			build.setResult(Result.FAILURE);
-			return false;
-		} catch (InterruptedException e) {
-			// Util.displayIOException(e, listener);
-			e.printStackTrace(listener.fatalError("command execution failed: "
-					+ e.getMessage()));
-			build.setResult(Result.ABORTED);
-			return false;
-		}
+                .stdout(listener).pwd(workDir).join();
+
+            boolean success = (exitValue == 0);
+            build.setResult(success ? Result.SUCCESS : Result.FAILURE);
+            return success;
+        } catch (IllegalArgumentException e) {
+            // Util.displayIOException(e, listener);
+            e.printStackTrace(listener.fatalError("command execution failed: "
+                + e.getMessage()));
+            build.setResult(Result.FAILURE);
+            return false;
+        } catch (IOException e) {
+            Util.displayIOException(e, listener);
+            e.printStackTrace(listener.fatalError("command execution failed: "
+                + e.getMessage()));
+            build.setResult(Result.FAILURE);
+            return false;
+        } catch (InterruptedException e) {
+            // Util.displayIOException(e, listener);
+            e.printStackTrace(listener.fatalError("command execution failed: "
+                + e.getMessage()));
+            build.setResult(Result.ABORTED);
+            return false;
+        }
 
-	}
+    }
 
-	/**
-	 * Create an {@link ArgumentListBuilder} to run the build, given command
-	 * arguments.
-	 */
-	private ArgumentListBuilder buildCmdLine(AbstractBuild build,
-			Launcher launcher, BuildListener listener)
-			throws IllegalArgumentException, InterruptedException, IOException {
-		ArgumentListBuilder args = new ArgumentListBuilder();
+    /**
+     * Create an {@link ArgumentListBuilder} to run the build, given command
+     * arguments.
+     */
+    private ArgumentListBuilder buildCmdLine(AbstractBuild build,
+                                             Launcher launcher, BuildListener listener)
+        throws IllegalArgumentException, InterruptedException, IOException {
+        ArgumentListBuilder args = new ArgumentListBuilder();
 
 //		DescriptorImpl descriptor = (DescriptorImpl) getDescriptor();
 
         EnvVars env = build.getEnvironment(listener);
         env.overrideAll(build.getBuildVariables());
 
-		SbtInstallation sbt = getSbt();
+        SbtInstallation sbt = getSbt();
         if (sbt == null) {
             throw new IllegalArgumentException("SBT jar path is empty");
         } else {
@@ -183,7 +183,7 @@ public class SbtPluginBuilder extends Builder {
 
             if (jdk != null) {
                 javaExePath = new File(jdk.getBinDir()
-                        + "/java").getAbsolutePath();
+                    + "/java").getAbsolutePath();
             } else {
                 javaExePath = "java";
             }
@@ -201,11 +201,11 @@ public class SbtPluginBuilder extends Builder {
             }
         }
 
-		return args;
-	}
+        return args;
+    }
 
     private SbtInstallation getSbt() {
-        for (SbtInstallation sbtInstallation: getDescriptor().getInstallations()) {
+        for (SbtInstallation sbtInstallation : getDescriptor().getInstallations()) {
             if (name != null &amp;&amp; name.equals(sbtInstallation.getName())) {
                 return sbtInstallation;
             }
@@ -213,89 +213,87 @@ public class SbtPluginBuilder extends Builder {
         return null;
     }
 
-	/**
-	 * Split arguments and add them to the args list
-	 * 
-	 * @param argsToSplit
-	 *            the arguments to split
-	 * @param args
-	 *            java/sbt command arguments
-	 */
-	private void splitAndAddArgs(String argsToSplit, ArgumentListBuilder args) {
-		if (StringUtils.isBlank(argsToSplit)) {
-			return;
-		}
+    /**
+     * Split arguments and add them to the args list
+     *
+     * @param argsToSplit the arguments to split
+     * @param args        java/sbt command arguments
+     */
+    private void splitAndAddArgs(String argsToSplit, ArgumentListBuilder args) {
+        if (StringUtils.isBlank(argsToSplit)) {
+            return;
+        }
 
-		String[] split = argsToSplit.split(" ");
-		for (String flag : split) {
-			args.add(flag);
-		}
-	}
-
-	/*
-	 * Splits by whitespace except if surrounded by quotes. See
-	 * http://stackoverflow
-	 * .com/questions/366202/regex-for-splitting-a-string-using
-	 * -space-when-not-surrounded-by-single-or-double/366532#366532
-	 */
-	private List&lt;String&gt; split(String s) {
-		List&lt;String&gt; result = new ArrayList&lt;String&gt;();
-		Matcher matcher = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'")
-				.matcher(s);
-		while (matcher.find()) {
-			if (matcher.group(1) != null)
-				result.add(matcher.group(1));
-			else if (matcher.group(2) != null)
-				result.add(matcher.group(2));
-			else
-				result.add(matcher.group());
-		}
-		return result;
-	}
-
-	@Override
-	public DescriptorImpl getDescriptor() {
-		return (DescriptorImpl) super.getDescriptor();
-	}
-
-	/**
-	 * Descriptor for {@link SbtPluginBuilder}. Used as a singleton. The class
-	 * is marked as public so that it can be accessed from views.
-	 * 
-	 * &lt;p&gt;
-	 * See &lt;tt&gt;SbtPluginBuilder/*.jelly&lt;/tt&gt; for the actual HTML fragment for
-	 * the configuration screen.
-	 */
-	@Extension
-	// this marker indicates Hudson that this is an implementation of an
-	// extension point.
-	public static final class DescriptorImpl extends
-			BuildStepDescriptor&lt;Builder&gt; {
+        String[] split = argsToSplit.split(" ");
+        for (String flag : split) {
+            args.add(flag);
+        }
+    }
+
+    /*
+     * Splits by whitespace except if surrounded by quotes. See
+     * http://stackoverflow
+     * .com/questions/366202/regex-for-splitting-a-string-using
+     * -space-when-not-surrounded-by-single-or-double/366532#366532
+     */
+    private List&lt;String&gt; split(String s) {
+        List&lt;String&gt; result = new ArrayList&lt;String&gt;();
+        Matcher matcher = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'")
+            .matcher(s);
+        while (matcher.find()) {
+            if (matcher.group(1) != null)
+                result.add(matcher.group(1));
+            else if (matcher.group(2) != null)
+                result.add(matcher.group(2));
+            else
+                result.add(matcher.group());
+        }
+        return result;
+    }
+
+    @Override
+    public DescriptorImpl getDescriptor() {
+        return (DescriptorImpl) super.getDescriptor();
+    }
+
+    /**
+     * Descriptor for {@link SbtPluginBuilder}. Used as a singleton. The class
+     * is marked as public so that it can be accessed from views.
+     * &lt;p/&gt;
+     * &lt;p/&gt;
+     * See &lt;tt&gt;SbtPluginBuilder/*.jelly&lt;/tt&gt; for the actual HTML fragment for
+     * the configuration screen.
+     */
+    @Extension
+    // this marker indicates Hudson that this is an implementation of an
+    // extension point.
+    public static final class DescriptorImpl extends
+        BuildStepDescriptor&lt;Builder&gt; {
 
 //		private volatile Jar[] jars = new Jar[0];
 
         @CopyOnWrite
         private volatile SbtInstallation[] installations = new SbtInstallation[0];
 
-		public DescriptorImpl() {
-			super(SbtPluginBuilder.class);
-			load();
-		}
+        public DescriptorImpl() {
+            super(SbtPluginBuilder.class);
+            load();
+        }
 
-		@Override
-		public boolean isApplicable(Class&lt;? extends AbstractProject&gt; aClass) {
-			return true;
-		}
+        @Override
+        public boolean isApplicable(Class&lt;? extends AbstractProject&gt; aClass) {
+            return true;
+        }
 
-		/**
-		 * This human readable name is used in the configuration screen.
-		 */
-		public String getDisplayName() {
-			return "Build using sbt";
-		}
+        /**
+         * This human readable name is used in the configuration screen.
+         */
+        public String getDisplayName() {
+            return "Build using sbt";
+        }
 
 		/*public Jar getJar(String name) {
-			for (Jar jar : jars) {
+            for (Jar jar : jars) {
 				if (jar.getName().equals(name)) {
 					return jar;
 				}
@@ -340,20 +338,22 @@ public class SbtPluginBuilder extends Builder {
             save();
         }
 
-	}
+    }
 
-	/**
-	 * Representation of an sbt launcher. Several such launchers can be defined
-	 * in Jenkins properties to choose among when running a project.
-	 */
+    /**
+     * Representation of an sbt launcher. Several such launchers can be defined
+     * in Jenkins properties to choose among when running a project.
+     */
 	/*public static final class Jar implements Serializable {
 		private static final long serialVersionUID = 1L;
     */
-		/** The human-friendly name of this launcher */
-	//	private String name;
-		
-		/** The path to the launcher */
-	//	private String path;
+    /** The human-friendly name of this launcher */
+    //	private String name;
+
+    /**
+     * The path to the launcher
+     */
+    //	private String path;
     /*
 		@DataBoundConstructor
 		public Jar(String name, String path) {
@@ -379,11 +379,35 @@ public class SbtPluginBuilder extends Builder {
 	} */
 
     public static final class SbtInstallation extends ToolInstallation implements
-            EnvironmentSpecific&lt;SbtInstallation&gt;, NodeSpecific&lt;SbtInstallation&gt; {
+        EnvironmentSpecific&lt;SbtInstallation&gt;, NodeSpecific&lt;SbtInstallation&gt;, Serializable {
+
+        private static final long serialVersionUID = -2281774135009218882L;
+
+        private String sbtLaunchJar;
+
 
         @DataBoundConstructor
         public SbtInstallation(String name, String home, List&lt;? extends ToolProperty&lt;?&gt;&gt; properties) {
-            super(name, home, properties);
+            super(name, launderHome(home), properties);
+            this.sbtLaunchJar = super.getHome();
+        }
+
+        private static String launderHome(String home) {
+            if (home.endsWith("/") || home.endsWith("\\")) {
+                // see https://issues.apache.org/bugzilla/show_bug.cgi?id=26947
+                // Ant doesn't like the trailing slash, especially on Windows
+                return home.substring(0, home.length() - 1);
+            } else {
+                return home;
+            }
+        }
+
+        @Override
+        public String getHome() {
+            if (sbtLaunchJar != null) {
+                return sbtLaunchJar;
+            }
+            return super.getHome();
         }
 
         public SbtInstallation forEnvironment(EnvVars environment) {
@@ -400,13 +424,13 @@ public class SbtPluginBuilder extends Builder {
             @Override
             public SbtInstallation[] getInstallations() {
                 return Jenkins.getInstance().getDescriptorByType(SbtPluginBuilder.DescriptorImpl.class)
-                        .getInstallations();
+                    .getInstallations();
             }
 
             @Override
             public void setInstallations(SbtInstallation... installations) {
                 Jenkins.getInstance().getDescriptorByType(SbtPluginBuilder.DescriptorImpl.class)
-                        .setInstallations(installations);
+                    .setInstallations(installations);
             }
 
             @Override
@@ -452,7 +476,7 @@ public class SbtPluginBuilder extends Builder {
 
             @Override
             public String getDisplayName() {
-                return "";
+                return "Install from repo.typesafe.com";
             }
 
             @Override
diff --git a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
new file mode 100644
index 0000000..741fd40
--- /dev/null
+++ b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
@@ -0,0 +1,11 @@
+&lt;!--
+  Config page
+--&gt;
+&lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"&gt;
+    &lt;f:entry title="${%name}" field="name"&gt;
+        &lt;f:textbox /&gt;
+    &lt;/f:entry&gt;
+    &lt;f:entry title="sbt launch jar" field="home"&gt;
+        &lt;f:textbox /&gt;
+    &lt;/f:entry&gt;
+&lt;/j:jelly&gt;
\ No newline at end of file
diff --git a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/config.jelly b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/config.jelly
index 56a7eb0..30eaf9d 100644
--- a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/config.jelly
+++ b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/config.jelly
@@ -7,8 +7,8 @@
   
   &lt;f:entry title="sbt launcher"&gt;
   	&lt;select class="setting-input" name="jar.name" description="Select configured sbt launcher"&gt;
-        &lt;j:forEach var="jar" items="${descriptor.jars}"&gt;
-          &lt;f:option selected="${jar.name.equals(instance.name)}"&gt;${jar.name}&lt;/f:option&gt;
+        &lt;j:forEach var="inst" items="${descriptor.installations}"&gt;
+          &lt;f:option selected="${inst.name.equals(instance.name)}"&gt;${inst.name}&lt;/f:option&gt;
         &lt;/j:forEach&gt;
       &lt;/select&gt;
   &lt;/f:entry&gt;
diff --git a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.jelly b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.jelly
deleted file mode 100644
index 58fe222..0000000
--- a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.jelly
+++ /dev/null
@@ -1,31 +0,0 @@
-&lt;j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"&gt;
-  &lt;!--
-    This Jelly script is used to produce the global configuration option.
-  --&gt;
-  &lt;f:section title="sbt"&gt;
-    
-    &lt;f:entry title="sbt launch jars"&gt;
-    	&lt;f:repeatable var="jar" items="${descriptor.jars}"&gt;
-        	&lt;table width="100%"&gt; 
-        		
-        		&lt;f:entry title="Name"&gt;
-            		&lt;f:textbox name="jar.name" value="${jar.name}"/&gt;
-          		&lt;/f:entry&gt;
-          		
-          		&lt;f:entry title="Path"&gt;
-            		&lt;f:textbox name="jar.path" value="${jar.path}"/&gt;
-          		&lt;/f:entry&gt;
-          		
-          		&lt;f:entry title=""&gt;
-            		&lt;div align="right"&gt;
-              			&lt;f:repeatableDeleteButton /&gt;
-            		&lt;/div&gt;
-          		&lt;/f:entry&gt;
-          		
-			&lt;/table&gt;
-      	&lt;/f:repeatable&gt;
-    &lt;/f:entry&gt;
-	
-    
-  &lt;/f:section&gt;
-&lt;/j:jelly&gt;
diff --git a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.out.xml b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/global.out.xml
deleted file mode 100644
index e69de29..0000000
-- 
1.7.9.5


From af8f3f69ddba1ff00314eb6256dcb6b620535b45 Mon Sep 17 00:00:00 2001
From: wolfg1969 &lt;wolfg1969@gmail.com&gt;
Date: Tue, 18 Dec 2012 17:49:16 +0800
Subject: [PATCH 3/4] TODO: add auto installer

---
 .../org/jvnet/hudson/plugins/SbtPluginBuilder.java |    8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
index d91876b..afd24c5 100644
--- a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
+++ b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
@@ -433,10 +433,10 @@ public class SbtPluginBuilder extends Builder {
                     .setInstallations(installations);
             }
 
-            @Override
+            /*@Override
             public List&lt;? extends ToolInstaller&gt; getDefaultInstallers() {
                 return Collections.singletonList(new SbtInstaller(null));
-            }
+            }*/
 
             @Override
             public String getDisplayName() {
@@ -464,7 +464,7 @@ public class SbtPluginBuilder extends Builder {
     /**
      * Automatic Sbt installer from scala-sbt.org
      */
-    public static class SbtInstaller extends DownloadFromUrlInstaller {
+    /*public static class SbtInstaller extends DownloadFromUrlInstaller {
 
         @DataBoundConstructor
         protected SbtInstaller(String id) {
@@ -484,5 +484,5 @@ public class SbtPluginBuilder extends Builder {
                 return toolType == SbtInstallation.class;
             }
         }
-    }
+    }*/
 }
-- 
1.7.9.5


From fa96195ebc66634cb51293857a85b519652d2173 Mon Sep 17 00:00:00 2001
From: wolfg1969 &lt;wolfg1969@gmail.com&gt;
Date: Wed, 19 Dec 2012 13:46:03 +0800
Subject: [PATCH 4/4] add auto installer

---
 .../org/jvnet/hudson/plugins/SbtPluginBuilder.java |    9 +++++----
 .../SbtPluginBuilder/SbtInstallation/config.jelly  |    2 +-
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
index afd24c5..67bb40d 100644
--- a/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
+++ b/src/main/java/org/jvnet/hudson/plugins/SbtPluginBuilder.java
@@ -288,6 +288,7 @@ public class SbtPluginBuilder extends Builder {
         /**
          * This human readable name is used in the configuration screen.
          */
+        @Override
         public String getDisplayName() {
             return "Build using sbt";
         }
@@ -433,10 +434,10 @@ public class SbtPluginBuilder extends Builder {
                     .setInstallations(installations);
             }
 
-            /*@Override
+            @Override
             public List&lt;? extends ToolInstaller&gt; getDefaultInstallers() {
                 return Collections.singletonList(new SbtInstaller(null));
-            }*/
+            }
 
             @Override
             public String getDisplayName() {
@@ -464,7 +465,7 @@ public class SbtPluginBuilder extends Builder {
     /**
      * Automatic Sbt installer from scala-sbt.org
      */
-    /*public static class SbtInstaller extends DownloadFromUrlInstaller {
+    public static class SbtInstaller extends DownloadFromUrlInstaller {
 
         @DataBoundConstructor
         protected SbtInstaller(String id) {
@@ -484,5 +485,5 @@ public class SbtPluginBuilder extends Builder {
                 return toolType == SbtInstallation.class;
             }
         }
-    }*/
+    }
 }
diff --git a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
index 741fd40..c5163e7 100644
--- a/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
+++ b/src/main/resources/org/jvnet/hudson/plugins/SbtPluginBuilder/SbtInstallation/config.jelly
@@ -8,4 +8,4 @@
     &lt;f:entry title="sbt launch jar" field="home"&gt;
         &lt;f:textbox /&gt;
     &lt;/f:entry&gt;
-&lt;/j:jelly&gt;
\ No newline at end of file
+&lt;/j:jelly&gt;
-- 
1.7.9.5

</pre></body></html>