/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

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