/* * Copyright 2012, CloudBees Inc., Synopsys Inc. and contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cloudbees.jenkins.plugins.customtools; import com.synopsys.arc.jenkinsci.plugins.customtools.CustomToolException; import com.synopsys.arc.jenkinsci.plugins.customtools.EnvStringParseHelper; import com.synopsys.arc.jenkinsci.plugins.customtools.LabelSpecifics; import com.synopsys.arc.jenkinsci.plugins.customtools.PathsList; import com.synopsys.arc.jenkinsci.plugins.customtools.versions.ToolVersion; import com.synopsys.arc.jenkinsci.plugins.customtools.versions.ToolVersionConfig; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.AbortException; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.model.EnvironmentSpecific; import hudson.model.JobProperty; import hudson.model.JobPropertyDescriptor; import hudson.model.TaskListener; import hudson.model.Node; import hudson.remoting.VirtualChannel; import hudson.slaves.NodeSpecific; import hudson.tools.ToolDescriptor; import hudson.tools.ToolInstaller; import hudson.tools.ToolInstallation; import hudson.tools.ZipExtractionInstaller; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Arrays; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import jenkins.MasterToSlaveFileCallable; import jenkins.plugins.customtools.util.envvars.VariablesSubstitutionHelper; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; /** * An arbitrary tool, which can add directories to the build's PATH. * @author rcampbell * @author Oleg Nenashev * */ @SuppressFBWarnings(value = "SE_NO_SERIALVERSIONID", justification = "Actually we do not send the class over the channel. Serial version ID is not required for XStream") public class CustomTool extends ToolInstallation implements NodeSpecific<CustomTool>, EnvironmentSpecific<CustomTool> { /** * File set includes string like **\/bin These will be added to the PATH */ private final @CheckForNull String exportedPaths; /** * Label-specific options. */ private final @CheckForNull LabelSpecifics[] labelSpecifics; /** * A cached value of the home directory. */ private transient @CheckForNull String correctedHome = null; /** * Optional field, which referenced the {@link ToolVersion} configuration. */ private final @CheckForNull ToolVersionConfig toolVersion; /** * Additional variables string. * Stores variables expression in *.properties format. */ private final @CheckForNull String additionalVariables; private static final LabelSpecifics[] EMPTY_LABELS = new LabelSpecifics[0]; @DataBoundConstructor public CustomTool(@Nonnull String name, @Nonnull String home, @CheckForNull List properties, @CheckForNull String exportedPaths, @CheckForNull LabelSpecifics[] labelSpecifics, @CheckForNull ToolVersionConfig toolVersion, @CheckForNull String additionalVariables) { super(name, home, properties); this.exportedPaths = exportedPaths; this.labelSpecifics = labelSpecifics != null ? Arrays.copyOf(labelSpecifics, labelSpecifics.length) : null; this.toolVersion = toolVersion; this.additionalVariables = additionalVariables; } public @CheckForNull String getExportedPaths() { return exportedPaths; } /** * Gets the tool version configuration. * @return Tool version configuration or null if it is not configured. */ public @CheckForNull ToolVersionConfig getToolVersion() { return toolVersion; } public boolean hasVersions() { return toolVersion != null; } @Override @CheckForNull public String getHome() { return (correctedHome != null) ? correctedHome : super.getHome(); } public void correctHome(@Nonnull PathsList pathList) { correctedHome = pathList.getHomeDir(); } public @Nonnull LabelSpecifics[] getLabelSpecifics() { return (labelSpecifics!=null) ? labelSpecifics : EMPTY_LABELS; } /** * Check if the tool has additional environment variables set. * @return true when the tool injects additional environment variables. */ public boolean hasAdditionalVariables() { return additionalVariables != null; } public @CheckForNull String getAdditionalVariables() { return additionalVariables; } @Override public CustomTool forEnvironment(EnvVars environment) { String substitutedHomeDir = VariablesSubstitutionHelper.PATH.resolveVariable(getHome(), environment); String substitutedPath = VariablesSubstitutionHelper.PATH.resolveVariable(exportedPaths, environment); String substitutedAdditionalVariables = VariablesSubstitutionHelper.PROP_FILE.resolveVariable(additionalVariables, environment); return new CustomTool(getName(), substitutedHomeDir, getProperties().toList(), substitutedPath, LabelSpecifics.substitute(getLabelSpecifics(), environment), toolVersion, substitutedAdditionalVariables); } @Override public @Nonnull CustomTool forNode(Node node, TaskListener log) throws IOException, InterruptedException { String substitutedHomeDir = VariablesSubstitutionHelper.PATH.resolveVariable(translateFor(node, log), node); String substitutedPath = VariablesSubstitutionHelper.PATH.resolveVariable(exportedPaths, node); String substitutedAdditionalVariables = VariablesSubstitutionHelper.PROP_FILE.resolveVariable(additionalVariables, node); return new CustomTool(getName(), substitutedHomeDir, getProperties().toList(), substitutedPath, LabelSpecifics.substitute(getLabelSpecifics(), node), toolVersion, substitutedAdditionalVariables); } //FIXME: just a stub @Deprecated @Restricted(NoExternalUse.class) public CustomTool forBuildProperties(Map<JobPropertyDescriptor,JobProperty> properties) { final String toolHome = getHome(); if (toolHome == null) { throw new IllegalStateException("Tool home must not be null at this stage, likely it's an API misusage"); } return new CustomTool(getName(), toolHome, getProperties().toList(), getExportedPaths(), getLabelSpecifics(), toolVersion, getAdditionalVariables()); } /** * Checks the tool consistency. * @throws CustomToolException Validation error */ public void check() throws CustomToolException { EnvStringParseHelper.checkStringForMacro("EXPORTED_PATHS", getExportedPaths()); EnvStringParseHelper.checkStringForMacro("HOME_DIR", getHome()); } /** * Get list of label specifics, which apply to the specified node. * @param node Node to be checked * @return List of the specifics to be applied * @since 0.3 */ public @Nonnull List<LabelSpecifics> getAppliedSpecifics(@Nonnull Node node) { List<LabelSpecifics> out = new LinkedList<LabelSpecifics>(); if (labelSpecifics != null) { for (LabelSpecifics spec : labelSpecifics) { if (spec.appliesTo(node)) { out.add(spec); } } } return out; } @Extension public static class DescriptorImpl extends ToolDescriptor<CustomTool> { public DescriptorImpl() { load(); } @Override public String getDisplayName() { return Messages.CustomTool_DescriptorImpl_DisplayName(); } @Override public void setInstallations(CustomTool... installations) { super.setInstallations(installations); save(); } /** * Gets a {@link CustomTool} by its name. * @param name A name of the tool to be retrieved. * @return A {@link CustomTool} or null if it has no found */ public @CheckForNull CustomTool byName(String name) { for (CustomTool tool : getInstallations()) { if (tool.getName().equals(name)) { return tool; } } return null; } @Override public List<? extends ToolInstaller> getDefaultInstallers() { return Collections.singletonList(new ZipExtractionInstaller(null, null, null)); } } /** * Finds the directories to add to the path, for the given node. * Uses Ant filesets to expand the patterns in the exportedPaths field. * * @param node where the tool has been installed * @return a list of directories to add to the $PATH * * @throws IOException * @throws InterruptedException Operation has been interrupted */ protected @Nonnull PathsList getPaths(@Nonnull Node node) throws IOException, InterruptedException { FilePath homePath = new FilePath(node.getChannel(), getHome()); //FIXME: Why? if (exportedPaths == null) { return PathsList.EMPTY; } final List<LabelSpecifics> specs = getAppliedSpecifics(node); PathsList pathsFound = homePath.act(new MasterToSlaveFileCallable<PathsList>() { private void parseLists(String pathList, List<String> target) { String[] items = pathList.split("\\s*,\\s*"); for (String item : items) { if (item.isEmpty()) { continue; } target.add(item); } } @Override public PathsList invoke(File f, VirtualChannel channel) throws IOException, InterruptedException { // Construct output paths List<String> items = new LinkedList<String>(); if (exportedPaths != null) { parseLists(exportedPaths, items); } for (LabelSpecifics spec : specs) { final String exportedPathsFromSpec = spec.getExportedPaths(); if (exportedPathsFromSpec != null) { parseLists(exportedPathsFromSpec, items); } } // Resolve exported paths List<String> outList = new LinkedList<String>(); for (String item : items) { File file = new File(item); if (!file.isAbsolute()) { file = new File (getHome(), item); } // Check if directory exists if (!file.isDirectory() || !file.exists()) { throw new AbortException("Wrong EXPORTED_PATHS configuration. Can't find "+file.getPath()); } outList.add(file.getAbsolutePath()); } // Resolve home dir final String toolHome = getHome(); if (toolHome == null) { throw new IOException("Cannot retrieve Tool home directory. Should never happen ant this stage, please file a bug"); } final File homeDir = new File(toolHome); return new PathsList(outList, homeDir.getAbsolutePath()); }; }); return pathsFound; } }