/** * */ package hudson.plugins.buckminster; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Functions; import hudson.Launcher; import hudson.Util; import hudson.model.EnvironmentSpecific; import hudson.model.Hudson; import hudson.model.Node; import hudson.model.TaskListener; import hudson.model.DownloadService.Downloadable; import hudson.plugins.buckminster.command.CommandLineBuilder; import hudson.plugins.buckminster.install.BuckminsterInstallable; import hudson.plugins.buckminster.install.BuckminsterInstallable.BuckminsterInstallableList; import hudson.plugins.buckminster.install.BuckminsterInstallable.Feature; import hudson.plugins.buckminster.install.BuckminsterInstallable.Repository; import hudson.plugins.buckminster.util.ReadDelegatingTextFile; import hudson.remoting.Callable; import hudson.slaves.NodeSpecific; import hudson.tools.DownloadFromUrlInstaller; import hudson.tools.ToolDescriptor; import hudson.tools.ToolInstallation; import hudson.tools.ToolInstaller; import hudson.tools.ToolProperty; import hudson.util.TextFile; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; /** * @author Johannes Utzig * */ public class BuckminsterInstallation extends ToolInstallation implements EnvironmentSpecific<BuckminsterInstallation>, NodeSpecific<BuckminsterInstallation> { private static final long serialVersionUID = -7771376893768609115L; private String version; private String params; @DataBoundConstructor public BuckminsterInstallation(String name, String home, String version, String params, List<ToolProperty<?>> properties) { super(name, home, properties); this.version = version; this.params = params; } public String getParams() { return params; } public String getVersion() { return version; } public BuckminsterInstallation forEnvironment(EnvVars environment) { return new BuckminsterInstallation(getName(), environment.expand(getHome()), version, params, getProperties().toList()); } public BuckminsterInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException { return new BuckminsterInstallation(getName(), translateFor(node, log), version, params, getProperties().toList()); } public boolean exists() { String home = getHome(); File f = new File(home); File buckyDir = new File(f, "buckminster"); if (!buckyDir.exists()) return false; File executableWin = new File(buckyDir, "buckminster.bat"); File executableUnix = new File(buckyDir, "buckminster"); return executableWin.exists() || executableUnix.exists(); } /** * Gets the executable path of this Ant on the given target system. */ public String getBuckminsterExecutable(Launcher launcher) throws IOException, InterruptedException { return launcher.getChannel().call(new Callable<String,IOException>() { private static final long serialVersionUID = -9191417273316445886L; public String call() throws IOException { File exe = getBuckminsterExeFile(); if(exe.exists()) return exe.getPath(); throw new FileNotFoundException("The File "+exe.getAbsolutePath()+" could not be found."); } }); } private File getBuckminsterExeFile() { String execName = Functions.isWindows() ? "buckminster.bat" : "buckminster"; String home = Util.replaceMacro(getHome(), EnvVars.masterEnvVars); return new File(home,execName); } /** * Gets the executable path of this Ant on the given target system. */ public String getDirectorExecutable(Launcher launcher) throws IOException, InterruptedException { return launcher.getChannel().call(new Callable<String,IOException>() { private static final long serialVersionUID = -6913910944225418236L; public String call() throws IOException { File exe = getDirectorExeFile(); if(exe.exists()) return exe.getPath(); return null; } }); } private File getDirectorExeFile() { String execName = Functions.isWindows() ? "buckminster.bat" : "buckminster"; String home = Util.replaceMacro(getHome(), EnvVars.masterEnvVars); File director = new File(new File(home).getParent(),"director"); return new File(director,execName); } @Extension public static class DescriptorImpl extends ToolDescriptor<BuckminsterInstallation> { @Override public String getDisplayName() { return "Buckminster"; } @Override public List<? extends ToolInstaller> getDefaultInstallers() { return Collections.singletonList(new BuckminsterInstaller(null,false)); } // // for compatibility reasons, the persistence is done by Ant.DescriptorImpl @Override public BuckminsterInstallation[] getInstallations() { return Hudson.getInstance().getDescriptorByType(EclipseBuckminsterBuilder.DescriptorImpl.class).getBuckminsterInstallations(); } @Override public void setInstallations(BuckminsterInstallation... installations) { Hudson.getInstance().getDescriptorByType(EclipseBuckminsterBuilder.DescriptorImpl.class).setBuckminsterInstallations(installations); } } public static class BuckminsterInstaller extends DownloadFromUrlInstaller { private boolean update; @DataBoundConstructor public BuckminsterInstaller(String id, boolean update) { super(id); this.update = update; } @Override protected FilePath findPullUpDirectory(FilePath root) throws IOException, InterruptedException { return null; } public boolean isUpdate() { return update; } @Override public FilePath performInstallation(ToolInstallation tool, Node node, TaskListener log) throws IOException, InterruptedException { FilePath toolHome = super.performInstallation(tool, node, log); FilePath buckminsterDir = toolHome.child("buckminster"); BuckminsterInstallable inst = (BuckminsterInstallable) getInstallable(); Set<String> repositories = new HashSet<String>(); Set<String> featuresToInstall = new HashSet<String>(); Set<String> featuresToUninstall = new HashSet<String>(); if (buckminsterDir.exists()) { FilePath executableWin = buckminsterDir.child("buckminster.bat"); FilePath executableUnix = buckminsterDir.child("buckminster"); if (executableUnix.exists() || executableWin.exists()) { if (isUpdate()) { // it exists and should be updated so execute the update // script log.getLogger().println("Checking for Buckminster Updates"); Map<String, Set<String>> installedFeatures = readInstalledFeatures(buckminsterDir, log); for (Entry<String, Set<String>> entry : installedFeatures.entrySet()) { for (String feature : entry.getValue()) { featuresToUninstall.add(feature+".feature.group"); } } for (Repository repo : inst.repositories) { repositories.add(repo.url); for (Feature feature : repo.features) { featuresToInstall.add(feature.id+".feature.group"); } } List<String> commands = CommandLineBuilder.createDirectorScript(inst, toolHome, node, log,repositories,featuresToInstall,featuresToUninstall); executeScript(node, log, toolHome, commands); writeInstallationDetails(node, log, buckminsterDir,inst); } return buckminsterDir; } } // the tool did not exist, so we install it freshly featuresToInstall.add(inst.iu); repositories.add(inst.repositoryURL); for (Repository repo : inst.repositories) { repositories.add(repo.url); for (Feature feature : repo.features) { featuresToInstall.add(feature.id+".feature.group"); } } List<String> commands = CommandLineBuilder.createDirectorScript(inst, toolHome, node, log,repositories,featuresToInstall); executeScript(node, log, toolHome, commands); writeInstallationDetails(node, log, buckminsterDir, inst); return buckminsterDir; } private void writeInstallationDetails(Node node, TaskListener log, FilePath buckminsterDir, BuckminsterInstallable inst) throws InterruptedException, IOException { FilePath installedFeatures = buckminsterDir.child(".installedFeatures"); StringBuilder installed = new StringBuilder(); for (Repository repo : inst.repositories) { installed.append(repo.url); installed.append("\n"); for (Feature feature : repo.features) { installed.append("-"); installed.append(feature.id); installed.append("\n"); } } try { installedFeatures.write(installed.toString(), "UTF-8"); } catch (InterruptedException e) { installedFeatures.delete(); throw e; } } /** * reads the contents of DIRECTOR_DIR/.installedFeatures and returns a * map that contains the repository url as key and the set of features * installed from that url as value * * @param log * @return * @throws IOException * @throws InterruptedException */ private Map<String, Set<String>> readInstalledFeatures( FilePath buckminsterDir, TaskListener log) throws IOException, InterruptedException { Map<String, Set<String>> installed = new HashMap<String, Set<String>>(); FilePath installedFeatures = buckminsterDir.child(".installedFeatures"); if (!installedFeatures.exists()) { String message = "{0} is missing. This file contains the information which features have already been installed into buckminster. The Update will not be accurate without this file."; message = MessageFormat.format(message, installedFeatures.toURI().getPath()); log.error(message); return installed; } BufferedReader reader = new BufferedReader(new InputStreamReader(installedFeatures.read(), "UTF-8")); String s = null; String url = null; Set<String> features = new HashSet<String>(); while ((s = reader.readLine()) != null) { if (s.startsWith("-")) { features.add(s.substring(1)); } else { if (url != null && features.size() > 0) { installed.put(url, features); features = new HashSet<String>(); } url = s; } } if (url != null && features.size() > 0) { installed.put(url, features); features = new HashSet<String>(); } url = s; return installed; } private void executeScript(Node node, TaskListener log, FilePath targetDir, List<String> commands) throws IOException, InterruptedException { int r = node.createLauncher(log).launch().cmds(commands).stdout(log).pwd(targetDir).join(); if (r != 0) { throw new IOException("Command returned status " + r); } } @Extension public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<BuckminsterInstaller> { public String getDisplayName() { return "Install from Eclipse.org and Cloudsmith.com"; } @Override public boolean isApplicable( Class<? extends ToolInstallation> toolType) { return toolType == BuckminsterInstallation.class; } @Override protected Downloadable createDownloadable() { return new Downloadable(getId()) { public TextFile getDataFile() { // use a delagating text file to allow user to override // the server json with a custom one. TextFile updateFile = super.getDataFile(); TextFile userOverride = new TextFile(new File(Hudson.getInstance().getRootDir(),"userContent/buckminster/buckminster.json")); return new ReadDelegatingTextFile(updateFile,userOverride); } }; } @Override public List<? extends Installable> getInstallables() throws IOException { JSONObject d = Downloadable.get(getId()).getData(); if (d == null) return Collections.emptyList(); return Arrays.asList(((BuckminsterInstallableList) JSONObject.toBean(d, BuckminsterInstallableList.class)).buckminsters); } } } }