1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Hashtable;
  21. import java.util.Vector;
  22. import org.apache.tools.ant.BuildException;
  23. import org.apache.tools.ant.DirectoryScanner;
  24. import org.apache.tools.ant.Project;
  25. import org.apache.tools.ant.types.Commandline;
  26. import org.apache.tools.ant.types.AbstractFileSet;
  27. import org.apache.tools.ant.types.DirSet;
  28. import org.apache.tools.ant.types.EnumeratedAttribute;
  29. import org.apache.tools.ant.types.FileList;
  30. import org.apache.tools.ant.types.FileSet;
  31. import org.apache.tools.ant.types.Mapper;
  32. import org.apache.tools.ant.util.FileNameMapper;
  33. import org.apache.tools.ant.util.SourceFileScanner;
  34. /**
  35. * Executes a given command, supplying a set of files as arguments.
  36. *
  37. * @since Ant 1.2
  38. *
  39. * @ant.task category="control" name="apply"
  40. */
  41. public class ExecuteOn extends ExecTask {
  42. private class ExtendedDirectoryScanner extends DirectoryScanner {
  43. public int getIncludedFilesCount() {
  44. if (filesIncluded == null) throw new IllegalStateException();
  45. return filesIncluded.size();
  46. }
  47. public int getIncludedDirsCount() {
  48. if (dirsIncluded == null) throw new IllegalStateException();
  49. return dirsIncluded.size();
  50. }
  51. }
  52. protected Vector filesets = new Vector(); // contains AbstractFileSet
  53. // (both DirSet and FileSet)
  54. private Vector filelists = new Vector();
  55. private boolean relative = false;
  56. private boolean parallel = false;
  57. private boolean forwardSlash = false;
  58. protected String type = "file";
  59. protected Commandline.Marker srcFilePos = null;
  60. private boolean skipEmpty = false;
  61. protected Commandline.Marker targetFilePos = null;
  62. protected Mapper mapperElement = null;
  63. protected FileNameMapper mapper = null;
  64. protected File destDir = null;
  65. private int maxParallel = -1;
  66. private boolean addSourceFile = true;
  67. private boolean verbose = false;
  68. private boolean ignoreMissing = true;
  69. /**
  70. * Has <srcfile> been specified before <targetfile>
  71. */
  72. protected boolean srcIsFirst = true;
  73. /**
  74. * Source files to operate upon.
  75. */
  76. public void addFileset(FileSet set) {
  77. filesets.addElement(set);
  78. }
  79. /**
  80. * Adds directories to operate on.
  81. *
  82. * @param set the DirSet to add.
  83. *
  84. * @since Ant 1.6
  85. */
  86. public void addDirset(DirSet set) {
  87. filesets.addElement(set);
  88. }
  89. /**
  90. * Source files to operate upon.
  91. */
  92. public void addFilelist(FileList list) {
  93. filelists.addElement(list);
  94. }
  95. /**
  96. * Whether the filenames should be passed on the command line as
  97. * absolute or relative pathnames. Paths are relative to the base
  98. * directory of the corresponding fileset for source files or the
  99. * dest attribute for target files.
  100. */
  101. public void setRelative(boolean relative) {
  102. this.relative = relative;
  103. }
  104. /**
  105. * If true, run the command only once, appending all files as arguments.
  106. * If false, command will be executed once for every file. Defaults to false.
  107. */
  108. public void setParallel(boolean parallel) {
  109. this.parallel = parallel;
  110. }
  111. /**
  112. * Whether the command works only on files, directories or both?
  113. */
  114. public void setType(FileDirBoth type) {
  115. this.type = type.getValue();
  116. }
  117. /**
  118. * If no source files have been found or are newer than their
  119. * corresponding target files, do not run the command.
  120. */
  121. public void setSkipEmptyFilesets(boolean skip) {
  122. skipEmpty = skip;
  123. }
  124. /**
  125. * The directory where target files are to be placed.
  126. */
  127. public void setDest(File destDir) {
  128. this.destDir = destDir;
  129. }
  130. /**
  131. * The source and target file names on Windows and OS/2 must use
  132. * forward slash as file separator.
  133. */
  134. public void setForwardslash(boolean forwardSlash) {
  135. this.forwardSlash = forwardSlash;
  136. }
  137. /**
  138. * Limit the command line length by passing at maximum this many
  139. * sourcefiles at once to the command.
  140. *
  141. * <p>Set to <= 0 for unlimited - this is the default.</p>
  142. *
  143. * @since Ant 1.6
  144. */
  145. public void setMaxParallel(int max) {
  146. maxParallel = max;
  147. }
  148. /**
  149. * Whether to send the source file name on the command line.
  150. *
  151. * <p>Defaults to <code>true</code>.
  152. *
  153. * @since Ant 1.6
  154. */
  155. public void setAddsourcefile(boolean b) {
  156. addSourceFile = b;
  157. }
  158. /**
  159. * Whether to print a verbose summary after execution.
  160. *
  161. * @since Ant 1.6
  162. */
  163. public void setVerbose(boolean b) {
  164. verbose = b;
  165. }
  166. /**
  167. * Whether to ignore nonexistent files from filelists.
  168. *
  169. * @since Ant 1.6.2
  170. */
  171. public void setIgnoremissing(boolean b) {
  172. ignoreMissing = b;
  173. }
  174. /**
  175. * Marker that indicates where the name of the source file should
  176. * be put on the command line.
  177. */
  178. public Commandline.Marker createSrcfile() {
  179. if (srcFilePos != null) {
  180. throw new BuildException(getTaskType() + " doesn\'t support multiple "
  181. + "srcfile elements.", getLocation());
  182. }
  183. srcFilePos = cmdl.createMarker();
  184. return srcFilePos;
  185. }
  186. /**
  187. * Marker that indicates where the name of the target file should
  188. * be put on the command line.
  189. */
  190. public Commandline.Marker createTargetfile() {
  191. if (targetFilePos != null) {
  192. throw new BuildException(getTaskType() + " doesn\'t support multiple "
  193. + "targetfile elements.", getLocation());
  194. }
  195. targetFilePos = cmdl.createMarker();
  196. srcIsFirst = (srcFilePos != null);
  197. return targetFilePos;
  198. }
  199. /**
  200. * Mapper to use for mapping source files to target files.
  201. */
  202. public Mapper createMapper() throws BuildException {
  203. if (mapperElement != null) {
  204. throw new BuildException("Cannot define more than one mapper",
  205. getLocation());
  206. }
  207. mapperElement = new Mapper(getProject());
  208. return mapperElement;
  209. }
  210. /**
  211. * @todo using taskName here is brittle, as a user could override it.
  212. * this should probably be modified to use the classname instead.
  213. */
  214. protected void checkConfiguration() {
  215. if ("execon".equals(getTaskName())) {
  216. log("!! execon is deprecated. Use apply instead. !!");
  217. }
  218. super.checkConfiguration();
  219. if (filesets.size() == 0 && filelists.size() == 0) {
  220. throw new BuildException("no filesets and no filelists specified",
  221. getLocation());
  222. }
  223. if (targetFilePos != null || mapperElement != null
  224. || destDir != null) {
  225. if (mapperElement == null) {
  226. throw new BuildException("no mapper specified", getLocation());
  227. }
  228. if (destDir == null) {
  229. throw new BuildException("no dest attribute specified",
  230. getLocation());
  231. }
  232. mapper = mapperElement.getImplementation();
  233. }
  234. }
  235. protected ExecuteStreamHandler createHandler() throws BuildException {
  236. //if we have a RedirectorElement, return a decoy
  237. return (redirectorElement == null)
  238. ? super.createHandler() : new PumpStreamHandler();
  239. }
  240. protected void setupRedirector() {
  241. super.setupRedirector();
  242. redirector.setAppendProperties(true);
  243. }
  244. protected void runExec(Execute exe) throws BuildException {
  245. int totalFiles = 0;
  246. int totalDirs = 0;
  247. boolean haveExecuted = false;
  248. try {
  249. Vector fileNames = new Vector();
  250. Vector baseDirs = new Vector();
  251. for (int i = 0; i < filesets.size(); i++) {
  252. String currentType = type;
  253. AbstractFileSet fs = (AbstractFileSet) filesets.elementAt(i);
  254. if (fs instanceof DirSet) {
  255. if (!"dir".equals(type)) {
  256. log("Found a nested dirset but type is " + type + ". "
  257. + "Temporarily switching to type=\"dir\" on the"
  258. + " assumption that you really did mean"
  259. + " <dirset> not <fileset>.", Project.MSG_DEBUG);
  260. currentType = "dir";
  261. }
  262. }
  263. File base = fs.getDir(getProject());
  264. ExtendedDirectoryScanner ds = new ExtendedDirectoryScanner();
  265. fs.setupDirectoryScanner(ds, getProject());
  266. ds.setFollowSymlinks(fs.isFollowSymlinks());
  267. ds.scan();
  268. if (!"dir".equals(currentType)) {
  269. String[] s = getFiles(base, ds);
  270. for (int j = 0; j < s.length; j++) {
  271. totalFiles++;
  272. fileNames.addElement(s[j]);
  273. baseDirs.addElement(base);
  274. }
  275. }
  276. if (!"file".equals(currentType)) {
  277. String[] s = getDirs(base, ds);
  278. for (int j = 0; j < s.length; j++) {
  279. totalDirs++;
  280. fileNames.addElement(s[j]);
  281. baseDirs.addElement(base);
  282. }
  283. }
  284. if (fileNames.size() == 0 && skipEmpty) {
  285. int includedCount
  286. = ((!"dir".equals(currentType))
  287. ? ds.getIncludedFilesCount() : 0)
  288. + ((!"file".equals(currentType))
  289. ? ds.getIncludedDirsCount() : 0);
  290. log("Skipping fileset for directory " + base + ". It is "
  291. + ((includedCount > 0) ? "up to date." : "empty."),
  292. Project.MSG_INFO);
  293. continue;
  294. }
  295. if (!parallel) {
  296. String[] s = new String[fileNames.size()];
  297. fileNames.copyInto(s);
  298. for (int j = 0; j < s.length; j++) {
  299. String[] command = getCommandline(s[j], base);
  300. log(Commandline.describeCommand(command),
  301. Project.MSG_VERBOSE);
  302. exe.setCommandline(command);
  303. if (redirectorElement != null) {
  304. setupRedirector();
  305. redirectorElement.configure(redirector, s[j]);
  306. }
  307. if (redirectorElement != null || haveExecuted) {
  308. // need to reset the stream handler to restart
  309. // reading of pipes;
  310. // go ahead and do it always w/ nested redirectors
  311. exe.setStreamHandler(redirector.createHandler());
  312. }
  313. runExecute(exe);
  314. haveExecuted = true;
  315. }
  316. fileNames.removeAllElements();
  317. baseDirs.removeAllElements();
  318. }
  319. }
  320. for (int i = 0; i < filelists.size(); i++) {
  321. FileList list = (FileList) filelists.elementAt(i);
  322. File base = list.getDir(getProject());
  323. String[] names = getFilesAndDirs(list);
  324. for (int j = 0; j < names.length; j++) {
  325. File f = new File(base, names[j]);
  326. if ((!ignoreMissing) || (f.isFile() && !"dir".equals(type))
  327. || (f.isDirectory() && !"file".equals(type))) {
  328. if (ignoreMissing || f.isFile()) {
  329. totalFiles++;
  330. } else {
  331. totalDirs++;
  332. }
  333. fileNames.addElement(names[j]);
  334. baseDirs.addElement(base);
  335. }
  336. }
  337. if (fileNames.size() == 0 && skipEmpty) {
  338. ExtendedDirectoryScanner ds = new ExtendedDirectoryScanner();
  339. ds.setBasedir(base);
  340. ds.setIncludes(list.getFiles(getProject()));
  341. ds.scan();
  342. int includedCount
  343. = ds.getIncludedFilesCount() + ds.getIncludedDirsCount();
  344. log("Skipping filelist for directory " + base + ". It is "
  345. + ((includedCount > 0) ? "up to date." : "empty."),
  346. Project.MSG_INFO);
  347. continue;
  348. }
  349. if (!parallel) {
  350. String[] s = new String[fileNames.size()];
  351. fileNames.copyInto(s);
  352. for (int j = 0; j < s.length; j++) {
  353. String[] command = getCommandline(s[j], base);
  354. log(Commandline.describeCommand(command),
  355. Project.MSG_VERBOSE);
  356. exe.setCommandline(command);
  357. if (redirectorElement != null) {
  358. setupRedirector();
  359. redirectorElement.configure(redirector, s[j]);
  360. }
  361. if (redirectorElement != null || haveExecuted) {
  362. // need to reset the stream handler to restart
  363. // reading of pipes;
  364. // go ahead and do it always w/ nested redirectors
  365. exe.setStreamHandler(redirector.createHandler());
  366. }
  367. runExecute(exe);
  368. haveExecuted = true;
  369. }
  370. fileNames.removeAllElements();
  371. baseDirs.removeAllElements();
  372. }
  373. }
  374. if (parallel && (fileNames.size() > 0 || !skipEmpty)) {
  375. runParallel(exe, fileNames, baseDirs);
  376. haveExecuted = true;
  377. }
  378. if (haveExecuted) {
  379. log("Applied " + cmdl.getExecutable() + " to "
  380. + totalFiles + " file"
  381. + (totalFiles != 1 ? "s" : "") + " and "
  382. + totalDirs + " director"
  383. + (totalDirs != 1 ? "ies" : "y") + ".",
  384. verbose ? Project.MSG_INFO : Project.MSG_VERBOSE);
  385. }
  386. } catch (IOException e) {
  387. throw new BuildException("Execute failed: " + e, e, getLocation());
  388. } finally {
  389. // close the output file if required
  390. logFlush();
  391. redirector.setAppendProperties(false);
  392. redirector.setProperties();
  393. }
  394. }
  395. /**
  396. * Construct the command line for parallel execution.
  397. *
  398. * @param srcFiles The filenames to add to the commandline
  399. * @param baseDirs filenames are relative to this dir
  400. */
  401. protected String[] getCommandline(String[] srcFiles, File[] baseDirs) {
  402. final char fileSeparator = File.separatorChar;
  403. Vector targets = new Vector();
  404. if (targetFilePos != null) {
  405. Hashtable addedFiles = new Hashtable();
  406. for (int i = 0; i < srcFiles.length; i++) {
  407. String[] subTargets = mapper.mapFileName(srcFiles[i]);
  408. if (subTargets != null) {
  409. for (int j = 0; j < subTargets.length; j++) {
  410. String name = null;
  411. if (!relative) {
  412. name = (new File(destDir, subTargets[j])).getAbsolutePath();
  413. } else {
  414. name = subTargets[j];
  415. }
  416. if (forwardSlash && fileSeparator != '/') {
  417. name = name.replace(fileSeparator, '/');
  418. }
  419. if (!addedFiles.contains(name)) {
  420. targets.addElement(name);
  421. addedFiles.put(name, name);
  422. }
  423. }
  424. }
  425. }
  426. }
  427. String[] targetFiles = new String[targets.size()];
  428. targets.copyInto(targetFiles);
  429. if (!addSourceFile) {
  430. srcFiles = new String[0];
  431. }
  432. String[] orig = cmdl.getCommandline();
  433. String[] result
  434. = new String[orig.length + srcFiles.length + targetFiles.length];
  435. int srcIndex = orig.length;
  436. if (srcFilePos != null) {
  437. srcIndex = srcFilePos.getPosition();
  438. }
  439. if (targetFilePos != null) {
  440. int targetIndex = targetFilePos.getPosition();
  441. if (srcIndex < targetIndex
  442. || (srcIndex == targetIndex && srcIsFirst)) {
  443. // 0 --> srcIndex
  444. System.arraycopy(orig, 0, result, 0, srcIndex);
  445. // srcIndex --> targetIndex
  446. System.arraycopy(orig, srcIndex, result,
  447. srcIndex + srcFiles.length,
  448. targetIndex - srcIndex);
  449. // targets are already absolute file names
  450. System.arraycopy(targetFiles, 0, result,
  451. targetIndex + srcFiles.length,
  452. targetFiles.length);
  453. // targetIndex --> end
  454. System.arraycopy(orig, targetIndex, result,
  455. targetIndex + srcFiles.length + targetFiles.length,
  456. orig.length - targetIndex);
  457. } else {
  458. // 0 --> targetIndex
  459. System.arraycopy(orig, 0, result, 0, targetIndex);
  460. // targets are already absolute file names
  461. System.arraycopy(targetFiles, 0, result,
  462. targetIndex,
  463. targetFiles.length);
  464. // targetIndex --> srcIndex
  465. System.arraycopy(orig, targetIndex, result,
  466. targetIndex + targetFiles.length,
  467. srcIndex - targetIndex);
  468. // srcIndex --> end
  469. System.arraycopy(orig, srcIndex, result,
  470. srcIndex + srcFiles.length + targetFiles.length,
  471. orig.length - srcIndex);
  472. srcIndex += targetFiles.length;
  473. }
  474. } else { // no targetFilePos
  475. // 0 --> srcIndex
  476. System.arraycopy(orig, 0, result, 0, srcIndex);
  477. // srcIndex --> end
  478. System.arraycopy(orig, srcIndex, result,
  479. srcIndex + srcFiles.length,
  480. orig.length - srcIndex);
  481. }
  482. // fill in source file names
  483. for (int i = 0; i < srcFiles.length; i++) {
  484. if (!relative) {
  485. result[srcIndex + i] =
  486. (new File(baseDirs[i], srcFiles[i])).getAbsolutePath();
  487. } else {
  488. result[srcIndex + i] = srcFiles[i];
  489. }
  490. if (forwardSlash && fileSeparator != '/') {
  491. result[srcIndex + i] =
  492. result[srcIndex + i].replace(fileSeparator, '/');
  493. }
  494. }
  495. return result;
  496. }
  497. /**
  498. * Construct the command line for serial execution.
  499. *
  500. * @param srcFile The filename to add to the commandline
  501. * @param baseDir filename is relative to this dir
  502. */
  503. protected String[] getCommandline(String srcFile, File baseDir) {
  504. return getCommandline(new String[] {srcFile}, new File[] {baseDir});
  505. }
  506. /**
  507. * Return the list of files from this DirectoryScanner that should
  508. * be included on the command line.
  509. */
  510. protected String[] getFiles(File baseDir, DirectoryScanner ds) {
  511. if (mapper != null) {
  512. SourceFileScanner sfs = new SourceFileScanner(this);
  513. return sfs.restrict(ds.getIncludedFiles(), baseDir, destDir,
  514. mapper);
  515. } else {
  516. return ds.getIncludedFiles();
  517. }
  518. }
  519. /**
  520. * Return the list of Directories from this DirectoryScanner that
  521. * should be included on the command line.
  522. */
  523. protected String[] getDirs(File baseDir, DirectoryScanner ds) {
  524. if (mapper != null) {
  525. SourceFileScanner sfs = new SourceFileScanner(this);
  526. return sfs.restrict(ds.getIncludedDirectories(), baseDir, destDir,
  527. mapper);
  528. } else {
  529. return ds.getIncludedDirectories();
  530. }
  531. }
  532. /**
  533. * Return the list of files or directories from this FileList that
  534. * should be included on the command line.
  535. *
  536. * @since Ant 1.6.2
  537. */
  538. protected String[] getFilesAndDirs(FileList list) {
  539. if (mapper != null) {
  540. SourceFileScanner sfs = new SourceFileScanner(this);
  541. return sfs.restrict(list.getFiles(getProject()),
  542. list.getDir(getProject()), destDir,
  543. mapper);
  544. } else {
  545. return list.getFiles(getProject());
  546. }
  547. }
  548. /**
  549. * Runs the command in "parallel" mode, making sure that at most
  550. * maxParallel sourcefiles get passed on the command line.
  551. *
  552. * @since Ant 1.6
  553. */
  554. protected void runParallel(Execute exe, Vector fileNames,
  555. Vector baseDirs)
  556. throws IOException, BuildException {
  557. String[] s = new String[fileNames.size()];
  558. fileNames.copyInto(s);
  559. File[] b = new File[baseDirs.size()];
  560. baseDirs.copyInto(b);
  561. if (maxParallel <= 0
  562. || s.length == 0 /* this is skipEmpty == false */) {
  563. String[] command = getCommandline(s, b);
  564. log(Commandline.describeCommand(command), Project.MSG_VERBOSE);
  565. exe.setCommandline(command);
  566. runExecute(exe);
  567. } else {
  568. int stillToDo = fileNames.size();
  569. int currentOffset = 0;
  570. while (stillToDo > 0) {
  571. int currentAmount = Math.min(stillToDo, maxParallel);
  572. String[] cs = new String[currentAmount];
  573. System.arraycopy(s, currentOffset, cs, 0, currentAmount);
  574. File[] cb = new File[currentAmount];
  575. System.arraycopy(b, currentOffset, cb, 0, currentAmount);
  576. String[] command = getCommandline(cs, cb);
  577. log(Commandline.describeCommand(command), Project.MSG_VERBOSE);
  578. exe.setCommandline(command);
  579. if (redirectorElement != null) {
  580. setupRedirector();
  581. redirectorElement.configure(redirector, null);
  582. }
  583. if (redirectorElement != null || currentOffset > 0) {
  584. // need to reset the stream handler to restart
  585. // reading of pipes;
  586. // go ahead and do it always w/ nested redirectors
  587. exe.setStreamHandler(redirector.createHandler());
  588. }
  589. runExecute(exe);
  590. stillToDo -= currentAmount;
  591. currentOffset += currentAmount;
  592. }
  593. }
  594. }
  595. /**
  596. * Enumerated attribute with the values "file", "dir" and "both"
  597. * for the type attribute.
  598. */
  599. public static class FileDirBoth extends EnumeratedAttribute {
  600. /**
  601. * @see EnumeratedAttribute#getValues
  602. */
  603. public String[] getValues() {
  604. return new String[] {"file", "dir", "both"};
  605. }
  606. }
  607. }