/hudson-core/src/main/java/hudson/tasks/Ant.java
http://github.com/hudson/hudson · Java · 495 lines · 326 code · 71 blank · 98 comment · 35 complexity · 7d86d76430cce125a0966fedc1e2a0cb MD5 · raw file
- /*
- * The MIT License
- *
- * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Tom Huybrechts, Yahoo! Inc.,
- * Anton Kozak, Nikita Levyankov
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- package hudson.tasks;
- import hudson.CopyOnWrite;
- import hudson.EnvVars;
- import hudson.Extension;
- import hudson.FilePath;
- import hudson.Functions;
- import hudson.Launcher;
- import hudson.Util;
- import hudson.model.AbstractBuild;
- import hudson.model.AbstractProject;
- import hudson.model.BuildListener;
- import hudson.model.Computer;
- import hudson.model.EnvironmentSpecific;
- import hudson.model.Hudson;
- import hudson.model.Node;
- import hudson.model.TaskListener;
- import hudson.remoting.Callable;
- import hudson.slaves.NodeSpecific;
- import hudson.tasks._ant.AntConsoleAnnotator;
- import hudson.tools.ToolDescriptor;
- import hudson.tools.ToolInstallation;
- import hudson.tools.DownloadFromUrlInstaller;
- import hudson.tools.ToolInstaller;
- import hudson.tools.ToolProperty;
- import hudson.util.ArgumentListBuilder;
- import hudson.util.VariableResolver;
- import hudson.util.FormValidation;
- import hudson.util.XStream2;
- import net.sf.json.JSONObject;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.commons.lang3.builder.EqualsBuilder;
- import org.apache.commons.lang3.builder.HashCodeBuilder;
- import org.kohsuke.stapler.DataBoundConstructor;
- import org.kohsuke.stapler.StaplerRequest;
- import org.kohsuke.stapler.QueryParameter;
- import java.io.File;
- import java.io.IOException;
- import java.util.ArrayList;
- import java.util.Properties;
- import java.util.List;
- import java.util.Collections;
- import java.util.Set;
- /**
- * Ant launcher.
- *
- * @author Kohsuke Kawaguchi
- */
- public class Ant extends Builder {
- /**
- * The targets, properties, and other Ant options.
- * Either separated by whitespace or newline.
- */
- private final String targets;
- /**
- * Identifies {@link AntInstallation} to be used.
- */
- private final String antName;
- /**
- * ANT_OPTS if not null.
- */
- private final String antOpts;
- /**
- * Optional build script path relative to the workspace.
- * Used for the Ant '-f' option.
- */
- private final String buildFile;
- /**
- * Optional properties to be passed to Ant. Follows {@link Properties} syntax.
- */
- private final String properties;
-
- @DataBoundConstructor
- public Ant(String targets,String antName, String antOpts, String buildFile, String properties) {
- this.targets = targets;
- this.antName = antName;
- this.antOpts = StringUtils.trimToNull(antOpts);
- this.buildFile = StringUtils.trimToNull(buildFile);
- this.properties = StringUtils.trimToNull(properties);
- }
- public String getBuildFile() {
- return buildFile;
- }
- public String getProperties() {
- return properties;
- }
- public String getTargets() {
- return targets;
- }
- /**
- * Gets the Ant to invoke,
- * or null to invoke the default one.
- */
- public AntInstallation getAnt() {
- for( AntInstallation i : getDescriptor().getInstallations() ) {
- if(antName!=null && antName.equals(i.getName()))
- return i;
- }
- return null;
- }
- /**
- * Gets the ANT_OPTS parameter, or null.
- */
- public String getAntOpts() {
- return antOpts;
- }
- @Override
- public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
- ArgumentListBuilder args = new ArgumentListBuilder();
- EnvVars env = build.getEnvironment(listener);
-
- AntInstallation ai = getAnt();
- if(ai==null) {
- args.add(launcher.isUnix() ? "ant" : "ant.bat");
- } else {
- ai = ai.forNode(Computer.currentComputer().getNode(), listener);
- ai = ai.forEnvironment(env);
- String exe = ai.getExecutable(launcher);
- if (exe==null) {
- listener.fatalError(Messages.Ant_ExecutableNotFound(ai.getName()));
- return false;
- }
- args.add(exe);
- }
- VariableResolver<String> vr = build.getBuildVariableResolver();
- String buildFile = env.expand(this.buildFile);
- String targets = Util.replaceMacro(env.expand(this.targets), vr);
-
- FilePath buildFilePath = buildFilePath(build.getModuleRoot(), buildFile, targets);
- if(!buildFilePath.exists()) {
- // because of the poor choice of getModuleRoot() with CVS/Subversion, people often get confused
- // with where the build file path is relative to. Now it's too late to change this behavior
- // due to compatibility issue, but at least we can make this less painful by looking for errors
- // and diagnosing it nicely. See HUDSON-1782
- // first check if this appears to be a valid relative path from workspace root
- FilePath buildFilePath2 = buildFilePath(build.getWorkspace(), buildFile, targets);
- if(buildFilePath2.exists()) {
- // This must be what the user meant. Let it continue.
- buildFilePath = buildFilePath2;
- } else {
- // neither file exists. So this now really does look like an error.
- listener.fatalError("Unable to find build script at "+buildFilePath);
- return false;
- }
- }
- if(buildFile!=null) {
- args.add("-file", buildFilePath.getName());
- }
- Set<String> sensitiveVars = build.getSensitiveBuildVariables();
- args.addKeyValuePairs("-D",build.getBuildVariables(),sensitiveVars);
- args.addKeyValuePairsFromPropertyString("-D",properties,vr,sensitiveVars);
- args.addTokenized(targets.replaceAll("[\t\r\n]+"," "));
- if(ai!=null)
- env.put("ANT_HOME",ai.getHome());
- if(antOpts!=null)
- env.put("ANT_OPTS",env.expand(antOpts));
- if(!launcher.isUnix()) {
- args = args.toWindowsCommand();
- // For some reason, ant on windows rejects empty parameters but unix does not.
- // Add quotes for any empty parameter values:
- List<String> newArgs = new ArrayList<String>(args.toList());
- newArgs.set(newArgs.size() - 1, newArgs.get(newArgs.size() - 1).replaceAll(
- "(?<= )(-D[^\" ]+)= ", "$1=\"\" "));
- args = new ArgumentListBuilder(newArgs.toArray(new String[newArgs.size()]));
- }
- long startTime = System.currentTimeMillis();
- try {
- AntConsoleAnnotator aca = new AntConsoleAnnotator(listener.getLogger(),build.getCharset());
- int r;
- try {
- r = launcher.launch().cmds(args).envs(env).stdout(aca).pwd(buildFilePath.getParent()).join();
- } finally {
- aca.forceEol();
- }
- return r==0;
- } catch (IOException e) {
- Util.displayIOException(e,listener);
- String errorMessage = Messages.Ant_ExecFailed();
- if(ai==null && (System.currentTimeMillis()-startTime)<1000) {
- if(getDescriptor().getInstallations()==null)
- // looks like the user didn't configure any Ant installation
- errorMessage += Messages.Ant_GlobalConfigNeeded();
- else
- // There are Ant installations configured but the project didn't pick it
- errorMessage += Messages.Ant_ProjectConfigNeeded();
- }
- e.printStackTrace( listener.fatalError(errorMessage) );
- return false;
- }
- }
- private static FilePath buildFilePath(FilePath base, String buildFile, String targets) {
- if(buildFile!=null) return base.child(buildFile);
- // some users specify the -f option in the targets field, so take that into account as well.
- // see
- String[] tokens = Util.tokenize(targets);
- for (int i = 0; i<tokens.length-1; i++) {
- String a = tokens[i];
- if(a.equals("-f") || a.equals("-file") || a.equals("-buildfile"))
- return base.child(tokens[i+1]);
- }
- return base.child("build.xml");
- }
- @Override
- public DescriptorImpl getDescriptor() {
- return (DescriptorImpl)super.getDescriptor();
- }
- @Extension
- public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
- @CopyOnWrite
- private volatile AntInstallation[] installations = new AntInstallation[0];
- public DescriptorImpl() {
- load();
- }
- protected DescriptorImpl(Class<? extends Ant> clazz) {
- super(clazz);
- }
- /**
- * Obtains the {@link AntInstallation.DescriptorImpl} instance.
- */
- public AntInstallation.DescriptorImpl getToolDescriptor() {
- return ToolInstallation.all().get(AntInstallation.DescriptorImpl.class);
- }
- public boolean isApplicable(Class<? extends AbstractProject> jobType) {
- return true;
- }
- @Override
- public String getHelpFile() {
- return "/help/project-config/ant.html";
- }
- public String getDisplayName() {
- return Messages.Ant_DisplayName();
- }
- public AntInstallation[] getInstallations() {
- return installations;
- }
- @Override
- public Ant newInstance(StaplerRequest req, JSONObject formData) throws FormException {
- return (Ant)req.bindJSON(clazz,formData);
- }
- public void setInstallations(AntInstallation... antInstallations) {
- this.installations = antInstallations;
- save();
- }
- }
- /**
- * Represents the Ant installation on the system.
- */
- public static final class AntInstallation extends ToolInstallation implements
- EnvironmentSpecific<AntInstallation>, NodeSpecific<AntInstallation> {
- // to remain backward compatible with earlier Hudson that stored this field here.
- @Deprecated
- private transient String antHome;
- @DataBoundConstructor
- public AntInstallation(String name, String home, List<? extends ToolProperty<?>> properties) {
- super(name, launderHome(home), properties);
- }
- /**
- * @deprecated as of 1.308
- * Use {@link #AntInstallation(String, String, List)}
- */
- public AntInstallation(String name, String home) {
- this(name,home,Collections.<ToolProperty<?>>emptyList());
- }
- 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;
- }
- }
- /**
- * install directory.
- *
- * @deprecated as of 1.307. Use {@link #getHome()}.
- */
- public String getAntHome() {
- return getHome();
- }
- /**
- * Gets the executable path of this Ant on the given target system.
- */
- public String getExecutable(Launcher launcher) throws IOException, InterruptedException {
- return launcher.getChannel().call(new Callable<String,IOException>() {
- public String call() throws IOException {
- File exe = getExeFile();
- if(exe.exists())
- return exe.getPath();
- return null;
- }
- });
- }
- private File getExeFile() {
- String execName = Functions.isWindows() ? "ant.bat" : "ant";
- String home = Util.replaceMacro(getHome(), EnvVars.masterEnvVars);
- return new File(home,"bin/"+execName);
- }
- /**
- * Returns true if the executable exists.
- */
- public boolean getExists() throws IOException, InterruptedException {
- return getExecutable(new Launcher.LocalLauncher(TaskListener.NULL))!=null;
- }
- private static final long serialVersionUID = 1L;
- public AntInstallation forEnvironment(EnvVars environment) {
- return new AntInstallation(getName(), environment.expand(getHome()), getProperties().toList());
- }
- public AntInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException {
- return new AntInstallation(getName(), translateFor(node, log), getProperties().toList());
- }
- @Extension
- public static class DescriptorImpl extends ToolDescriptor<AntInstallation> {
- @Override
- public String getDisplayName() {
- return "Ant";
- }
- // for compatibility reasons, the persistence is done by Ant.DescriptorImpl
- @Override
- public AntInstallation[] getInstallations() {
- return Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).getInstallations();
- }
- @Override
- public void setInstallations(AntInstallation... installations) {
- Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).setInstallations(installations);
- }
- @Override
- public List<? extends ToolInstaller> getDefaultInstallers() {
- return Collections.singletonList(new AntInstaller(null));
- }
- /**
- * Checks if the ANT_HOME is valid.
- */
- public FormValidation doCheckHome(@QueryParameter File value) {
- // this can be used to check the existence of a file on the server, so needs to be protected
- if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER))
- return FormValidation.ok();
- if(value.getPath().equals(""))
- return FormValidation.ok();
- if(!value.isDirectory())
- return FormValidation.error(Messages.Ant_NotADirectory(value));
- File antJar = new File(value,"lib/ant.jar");
- if(!antJar.exists())
- return FormValidation.error(Messages.Ant_NotAntDirectory(value));
- return FormValidation.ok();
- }
- public FormValidation doCheckName(@QueryParameter String value) {
- return FormValidation.validateRequired(value);
- }
- }
- public static class ConverterImpl extends ToolConverter {
- public ConverterImpl(XStream2 xstream) { super(xstream); }
- @Override protected String oldHomeField(ToolInstallation obj) {
- return ((AntInstallation)obj).antHome;
- }
- }
- }
- /**
- * Automatic Ant installer from apache.org.
- */
- public static class AntInstaller extends DownloadFromUrlInstaller {
- @DataBoundConstructor
- public AntInstaller(String id) {
- super(id);
- }
- @Extension
- public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<AntInstaller> {
- public String getDisplayName() {
- return Messages.InstallFromApache();
- }
- @Override
- public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
- return toolType==AntInstallation.class;
- }
- }
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Ant that = (Ant) o;
- return new EqualsBuilder()
- .append(antName, that.antName)
- .append(antOpts, that.antOpts)
- .append(buildFile, that.buildFile)
- .append(properties, that.properties)
- .append(targets, that.targets)
- .isEquals();
- }
- @Override
- public int hashCode() {
- return new HashCodeBuilder()
- .append(targets)
- .append(antName)
- .append(antOpts)
- .append(buildFile)
- .append(properties)
- .toHashCode();
- }
- }