PageRenderTime 40ms CodeModel.GetById 15ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/hudson-core/src/main/java/hudson/tasks/Ant.java

http://github.com/hudson/hudson
Java | 495 lines | 372 code | 46 blank | 77 comment | 23 complexity | 7d86d76430cce125a0966fedc1e2a0cb MD5 | raw file
  1/*
  2 * The MIT License
  3 * 
  4 * Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Tom Huybrechts, Yahoo! Inc.,
  5 * Anton Kozak, Nikita Levyankov
  6 * 
  7 * Permission is hereby granted, free of charge, to any person obtaining a copy
  8 * of this software and associated documentation files (the "Software"), to deal
  9 * in the Software without restriction, including without limitation the rights
 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11 * copies of the Software, and to permit persons to whom the Software is
 12 * furnished to do so, subject to the following conditions:
 13 * 
 14 * The above copyright notice and this permission notice shall be included in
 15 * all copies or substantial portions of the Software.
 16 * 
 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23 * THE SOFTWARE.
 24 */
 25package hudson.tasks;
 26
 27import hudson.CopyOnWrite;
 28import hudson.EnvVars;
 29import hudson.Extension;
 30import hudson.FilePath;
 31import hudson.Functions;
 32import hudson.Launcher;
 33import hudson.Util;
 34import hudson.model.AbstractBuild;
 35import hudson.model.AbstractProject;
 36import hudson.model.BuildListener;
 37import hudson.model.Computer;
 38import hudson.model.EnvironmentSpecific;
 39import hudson.model.Hudson;
 40import hudson.model.Node;
 41import hudson.model.TaskListener;
 42import hudson.remoting.Callable;
 43import hudson.slaves.NodeSpecific;
 44import hudson.tasks._ant.AntConsoleAnnotator;
 45import hudson.tools.ToolDescriptor;
 46import hudson.tools.ToolInstallation;
 47import hudson.tools.DownloadFromUrlInstaller;
 48import hudson.tools.ToolInstaller;
 49import hudson.tools.ToolProperty;
 50import hudson.util.ArgumentListBuilder;
 51import hudson.util.VariableResolver;
 52import hudson.util.FormValidation;
 53import hudson.util.XStream2;
 54import net.sf.json.JSONObject;
 55import org.apache.commons.lang3.StringUtils;
 56import org.apache.commons.lang3.builder.EqualsBuilder;
 57import org.apache.commons.lang3.builder.HashCodeBuilder;
 58import org.kohsuke.stapler.DataBoundConstructor;
 59import org.kohsuke.stapler.StaplerRequest;
 60import org.kohsuke.stapler.QueryParameter;
 61
 62import java.io.File;
 63import java.io.IOException;
 64import java.util.ArrayList;
 65import java.util.Properties;
 66import java.util.List;
 67import java.util.Collections;
 68import java.util.Set;
 69
 70/**
 71 * Ant launcher.
 72 *
 73 * @author Kohsuke Kawaguchi
 74 */
 75public class Ant extends Builder {
 76    /**
 77     * The targets, properties, and other Ant options.
 78     * Either separated by whitespace or newline.
 79     */
 80    private final String targets;
 81
 82    /**
 83     * Identifies {@link AntInstallation} to be used.
 84     */
 85    private final String antName;
 86
 87    /**
 88     * ANT_OPTS if not null.
 89     */
 90    private final String antOpts;
 91
 92    /**
 93     * Optional build script path relative to the workspace.
 94     * Used for the Ant '-f' option.
 95     */
 96    private final String buildFile;
 97
 98    /**
 99     * Optional properties to be passed to Ant. Follows {@link Properties} syntax.
100     */
101    private final String properties;
102    
103    @DataBoundConstructor
104    public Ant(String targets,String antName, String antOpts, String buildFile, String properties) {
105        this.targets = targets;
106        this.antName = antName;
107        this.antOpts = StringUtils.trimToNull(antOpts);
108        this.buildFile = StringUtils.trimToNull(buildFile);
109        this.properties = StringUtils.trimToNull(properties);
110    }
111
112	public String getBuildFile() {
113		return buildFile;
114	}
115
116	public String getProperties() {
117		return properties;
118	}
119
120    public String getTargets() {
121        return targets;
122    }
123
124    /**
125     * Gets the Ant to invoke,
126     * or null to invoke the default one.
127     */
128    public AntInstallation getAnt() {
129        for( AntInstallation i : getDescriptor().getInstallations() ) {
130            if(antName!=null && antName.equals(i.getName()))
131                return i;
132        }
133        return null;
134    }
135
136    /**
137     * Gets the ANT_OPTS parameter, or null.
138     */
139    public String getAntOpts() {
140        return antOpts;
141    }
142
143    @Override
144    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
145        ArgumentListBuilder args = new ArgumentListBuilder();
146
147        EnvVars env = build.getEnvironment(listener);
148        
149        AntInstallation ai = getAnt();
150        if(ai==null) {
151            args.add(launcher.isUnix() ? "ant" : "ant.bat");
152        } else {
153            ai = ai.forNode(Computer.currentComputer().getNode(), listener);
154            ai = ai.forEnvironment(env);
155            String exe = ai.getExecutable(launcher);
156            if (exe==null) {
157                listener.fatalError(Messages.Ant_ExecutableNotFound(ai.getName()));
158                return false;
159            }
160            args.add(exe);
161        }
162
163        VariableResolver<String> vr = build.getBuildVariableResolver();
164
165        String buildFile = env.expand(this.buildFile);
166        String targets = Util.replaceMacro(env.expand(this.targets), vr);
167        
168        FilePath buildFilePath = buildFilePath(build.getModuleRoot(), buildFile, targets);
169
170        if(!buildFilePath.exists()) {
171            // because of the poor choice of getModuleRoot() with CVS/Subversion, people often get confused
172            // with where the build file path is relative to. Now it's too late to change this behavior
173            // due to compatibility issue, but at least we can make this less painful by looking for errors
174            // and diagnosing it nicely. See HUDSON-1782
175
176            // first check if this appears to be a valid relative path from workspace root
177            FilePath buildFilePath2 = buildFilePath(build.getWorkspace(), buildFile, targets);
178            if(buildFilePath2.exists()) {
179                // This must be what the user meant. Let it continue.
180                buildFilePath = buildFilePath2;
181            } else {
182                // neither file exists. So this now really does look like an error.
183                listener.fatalError("Unable to find build script at "+buildFilePath);
184                return false;
185            }
186        }
187
188        if(buildFile!=null) {
189            args.add("-file", buildFilePath.getName());
190        }
191
192        Set<String> sensitiveVars = build.getSensitiveBuildVariables();
193
194        args.addKeyValuePairs("-D",build.getBuildVariables(),sensitiveVars);
195
196        args.addKeyValuePairsFromPropertyString("-D",properties,vr,sensitiveVars);
197
198        args.addTokenized(targets.replaceAll("[\t\r\n]+"," "));
199
200        if(ai!=null)
201            env.put("ANT_HOME",ai.getHome());
202        if(antOpts!=null)
203            env.put("ANT_OPTS",env.expand(antOpts));
204
205        if(!launcher.isUnix()) {
206            args = args.toWindowsCommand();
207            // For some reason, ant on windows rejects empty parameters but unix does not.
208            // Add quotes for any empty parameter values:
209            List<String> newArgs = new ArrayList<String>(args.toList());
210            newArgs.set(newArgs.size() - 1, newArgs.get(newArgs.size() - 1).replaceAll(
211                    "(?<= )(-D[^\" ]+)= ", "$1=\"\" "));
212            args = new ArgumentListBuilder(newArgs.toArray(new String[newArgs.size()]));
213        }
214
215        long startTime = System.currentTimeMillis();
216        try {
217            AntConsoleAnnotator aca = new AntConsoleAnnotator(listener.getLogger(),build.getCharset());
218            int r;
219            try {
220                r = launcher.launch().cmds(args).envs(env).stdout(aca).pwd(buildFilePath.getParent()).join();
221            } finally {
222                aca.forceEol();
223            }
224            return r==0;
225        } catch (IOException e) {
226            Util.displayIOException(e,listener);
227
228            String errorMessage = Messages.Ant_ExecFailed();
229            if(ai==null && (System.currentTimeMillis()-startTime)<1000) {
230                if(getDescriptor().getInstallations()==null)
231                    // looks like the user didn't configure any Ant installation
232                    errorMessage += Messages.Ant_GlobalConfigNeeded();
233                else
234                    // There are Ant installations configured but the project didn't pick it
235                    errorMessage += Messages.Ant_ProjectConfigNeeded();
236            }
237            e.printStackTrace( listener.fatalError(errorMessage) );
238            return false;
239        }
240    }
241
242    private static FilePath buildFilePath(FilePath base, String buildFile, String targets) {
243        if(buildFile!=null)     return base.child(buildFile);
244        // some users specify the -f option in the targets field, so take that into account as well.
245        // see 
246        String[] tokens = Util.tokenize(targets);
247        for (int i = 0; i<tokens.length-1; i++) {
248            String a = tokens[i];
249            if(a.equals("-f") || a.equals("-file") || a.equals("-buildfile"))
250                return base.child(tokens[i+1]);
251        }
252        return base.child("build.xml");
253    }
254
255    @Override
256    public DescriptorImpl getDescriptor() {
257        return (DescriptorImpl)super.getDescriptor();
258    }
259
260    @Extension
261    public static class DescriptorImpl extends BuildStepDescriptor<Builder> {
262        @CopyOnWrite
263        private volatile AntInstallation[] installations = new AntInstallation[0];
264
265        public DescriptorImpl() {
266            load();
267        }
268
269        protected DescriptorImpl(Class<? extends Ant> clazz) {
270            super(clazz);
271        }
272
273        /**
274         * Obtains the {@link AntInstallation.DescriptorImpl} instance.
275         */
276        public AntInstallation.DescriptorImpl getToolDescriptor() {
277            return ToolInstallation.all().get(AntInstallation.DescriptorImpl.class);
278        }
279
280        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
281            return true;
282        }
283
284        @Override
285        public String getHelpFile() {
286            return "/help/project-config/ant.html";
287        }
288
289        public String getDisplayName() {
290            return Messages.Ant_DisplayName();
291        }
292
293        public AntInstallation[] getInstallations() {
294            return installations;
295        }
296
297        @Override
298        public Ant newInstance(StaplerRequest req, JSONObject formData) throws FormException {
299            return (Ant)req.bindJSON(clazz,formData);
300        }
301
302        public void setInstallations(AntInstallation... antInstallations) {
303            this.installations = antInstallations;
304            save();
305        }
306    }
307
308    /**
309     * Represents the Ant installation on the system.
310     */
311    public static final class AntInstallation extends ToolInstallation implements
312            EnvironmentSpecific<AntInstallation>, NodeSpecific<AntInstallation> {
313        // to remain backward compatible with earlier Hudson that stored this field here.
314        @Deprecated
315        private transient String antHome;
316
317        @DataBoundConstructor
318        public AntInstallation(String name, String home, List<? extends ToolProperty<?>> properties) {
319            super(name, launderHome(home), properties);
320        }
321
322        /**
323         * @deprecated as of 1.308
324         *      Use {@link #AntInstallation(String, String, List)}
325         */
326        public AntInstallation(String name, String home) {
327            this(name,home,Collections.<ToolProperty<?>>emptyList());
328        }
329
330        private static String launderHome(String home) {
331            if(home.endsWith("/") || home.endsWith("\\")) {
332                // see https://issues.apache.org/bugzilla/show_bug.cgi?id=26947
333                // Ant doesn't like the trailing slash, especially on Windows
334                return home.substring(0,home.length()-1);
335            } else {
336                return home;
337            }
338        }
339
340        /**
341         * install directory.
342         *
343         * @deprecated as of 1.307. Use {@link #getHome()}.
344         */
345        public String getAntHome() {
346            return getHome();
347        }
348
349        /**
350         * Gets the executable path of this Ant on the given target system.
351         */
352        public String getExecutable(Launcher launcher) throws IOException, InterruptedException {
353            return launcher.getChannel().call(new Callable<String,IOException>() {
354                public String call() throws IOException {
355                    File exe = getExeFile();
356                    if(exe.exists())
357                        return exe.getPath();
358                    return null;
359                }
360            });
361        }
362
363        private File getExeFile() {
364            String execName = Functions.isWindows() ? "ant.bat" : "ant";
365            String home = Util.replaceMacro(getHome(), EnvVars.masterEnvVars);
366
367            return new File(home,"bin/"+execName);
368        }
369
370        /**
371         * Returns true if the executable exists.
372         */
373        public boolean getExists() throws IOException, InterruptedException {
374            return getExecutable(new Launcher.LocalLauncher(TaskListener.NULL))!=null;
375        }
376
377        private static final long serialVersionUID = 1L;
378
379        public AntInstallation forEnvironment(EnvVars environment) {
380            return new AntInstallation(getName(), environment.expand(getHome()), getProperties().toList());
381        }
382
383        public AntInstallation forNode(Node node, TaskListener log) throws IOException, InterruptedException {
384            return new AntInstallation(getName(), translateFor(node, log), getProperties().toList());
385        }
386
387        @Extension
388        public static class DescriptorImpl extends ToolDescriptor<AntInstallation> {
389
390            @Override
391            public String getDisplayName() {
392                return "Ant";
393            }
394
395            // for compatibility reasons, the persistence is done by Ant.DescriptorImpl  
396            @Override
397            public AntInstallation[] getInstallations() {
398                return Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).getInstallations();
399            }
400
401            @Override
402            public void setInstallations(AntInstallation... installations) {
403                Hudson.getInstance().getDescriptorByType(Ant.DescriptorImpl.class).setInstallations(installations);
404            }
405
406            @Override
407            public List<? extends ToolInstaller> getDefaultInstallers() {
408                return Collections.singletonList(new AntInstaller(null));
409            }
410
411            /**
412             * Checks if the ANT_HOME is valid.
413             */
414            public FormValidation doCheckHome(@QueryParameter File value) {
415                // this can be used to check the existence of a file on the server, so needs to be protected
416                if(!Hudson.getInstance().hasPermission(Hudson.ADMINISTER))
417                    return FormValidation.ok();
418
419                if(value.getPath().equals(""))
420                    return FormValidation.ok();
421
422                if(!value.isDirectory())
423                    return FormValidation.error(Messages.Ant_NotADirectory(value));
424
425                File antJar = new File(value,"lib/ant.jar");
426                if(!antJar.exists())
427                    return FormValidation.error(Messages.Ant_NotAntDirectory(value));
428
429                return FormValidation.ok();
430            }
431
432            public FormValidation doCheckName(@QueryParameter String value) {
433                return FormValidation.validateRequired(value);
434            }
435        }
436
437        public static class ConverterImpl extends ToolConverter {
438            public ConverterImpl(XStream2 xstream) { super(xstream); }
439            @Override protected String oldHomeField(ToolInstallation obj) {
440                return ((AntInstallation)obj).antHome;
441            }
442        }
443    }
444
445    /**
446     * Automatic Ant installer from apache.org.
447     */
448    public static class AntInstaller extends DownloadFromUrlInstaller {
449        @DataBoundConstructor
450        public AntInstaller(String id) {
451            super(id);
452        }
453
454        @Extension
455        public static final class DescriptorImpl extends DownloadFromUrlInstaller.DescriptorImpl<AntInstaller> {
456            public String getDisplayName() {
457                return Messages.InstallFromApache();
458            }
459
460            @Override
461            public boolean isApplicable(Class<? extends ToolInstallation> toolType) {
462                return toolType==AntInstallation.class;
463            }
464        }
465    }
466
467    @Override
468    public boolean equals(Object o) {
469        if (this == o) {
470            return true;
471        }
472        if (o == null || getClass() != o.getClass()) {
473            return false;
474        }
475        Ant that = (Ant) o;
476        return new EqualsBuilder()
477            .append(antName, that.antName)
478            .append(antOpts, that.antOpts)
479            .append(buildFile, that.buildFile)
480            .append(properties, that.properties)
481            .append(targets, that.targets)
482            .isEquals();
483    }
484
485    @Override
486    public int hashCode() {
487        return new HashCodeBuilder()
488            .append(targets)
489            .append(antName)
490            .append(antOpts)
491            .append(buildFile)
492            .append(properties)
493            .toHashCode();
494    }
495}