1. /*
  2. * Copyright 2003-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.optional.dotnet;
  18. import org.apache.tools.ant.types.EnumeratedAttribute;
  19. import org.apache.tools.ant.BuildException;
  20. import org.apache.tools.ant.Task;
  21. import org.apache.tools.ant.Project;
  22. import org.apache.tools.ant.util.FileUtils;
  23. import java.io.File;
  24. /**
  25. * Task to take a .NET or Mono -generated managed executable and turn it
  26. * into ILASM assembly code. Useful when converting imported typelibs into
  27. * assembler before patching and recompiling, as one has to do when doing
  28. * advanced typelib work.
  29. * <p>
  30. * As well as generating the named output file, the ildasm program
  31. * will also generate resource files <code>Icons.resources</code>
  32. * <code>Message.resources</code> and a .res file whose filename stub is derived
  33. * from the source in ways to obscure to determine.
  34. * There is no way to control whether or not these files are created, or where they are created
  35. * (they are created in the current directory; their names come from inside the
  36. * executable and may be those used by the original developer). This task
  37. * creates the resources in the directory specified by <code>resourceDir</code> if
  38. * set, else in the same directory as the <code>destFile</code>.
  39. *
  40. * <p>
  41. * This task requires the .NET SDK installed and ildasm on the path.
  42. * To disassemble using alternate CLR systems, set the executable attribute
  43. * to the name/path of the alternate implementation -one that must
  44. * support all the classic ildasm commands.
  45. *
  46. * <p>
  47. * Dependency logic: the task executes the command if the output file is missing
  48. * or older than the source file. It does not take into account changes
  49. * in the options of the task, or timestamp differences in resource files.
  50. * When the underlying ildasm executable fails for some reason, it leaves the
  51. * .il file in place with some error message. To prevent this from confusing
  52. * the dependency logic, the file specified by the <code>dest</code>
  53. * attribute is <i>always</i> deleted after an unsuccessful build.
  54. * @ant.task category="dotnet"
  55. */
  56. public class Ildasm extends Task {
  57. /**
  58. * source file (mandatory)
  59. */
  60. private File sourceFile;
  61. /**
  62. * dest file (mandatory)
  63. */
  64. private File destFile;
  65. /**
  66. * progress bar switch
  67. */
  68. private boolean progressBar = false;
  69. /**
  70. * what is our encoding
  71. */
  72. private String encoding;
  73. /**
  74. * /bytes flag for byte markup
  75. */
  76. private boolean bytes = false;
  77. /**
  78. * line numbers? /linenum
  79. */
  80. private boolean linenumbers = false;
  81. /**
  82. * /raweh flag for raw exception handling
  83. */
  84. private boolean rawExceptionHandling = false;
  85. /**
  86. * show the source; /source
  87. */
  88. private boolean showSource = false;
  89. /**
  90. * /quoteallnames to quote all names
  91. */
  92. private boolean quoteallnames = false;
  93. /**
  94. * /header for header information
  95. */
  96. private boolean header = false;
  97. /**
  98. * when false, sets the /noil attribute
  99. * to suppress assembly info
  100. */
  101. private boolean assembler = true;
  102. /**
  103. * include metadata
  104. * /tokens
  105. */
  106. private boolean metadata = false;
  107. /**
  108. * what visibility do we want.
  109. *
  110. */
  111. private String visibility;
  112. /**
  113. * specific item to disassemble
  114. */
  115. private String item;
  116. /**
  117. * override for the executable
  118. */
  119. private String executable = "ildasm";
  120. /**
  121. * name of the directory for resources to be created. We cannot control
  122. * their names, but we can say where they get created. If not set, the
  123. * directory of the dest file is used
  124. */
  125. private File resourceDir;
  126. /**
  127. * Set the name of the directory for resources to be created. We cannot control
  128. * their names, but we can say where they get created. If not set, the
  129. * directory of the dest file is used
  130. */
  131. public void setResourceDir(File resourceDir) {
  132. this.resourceDir = resourceDir;
  133. }
  134. /**
  135. * override the name of the executable (normally ildasm) or set
  136. * its full path. Do not set a relative path, as the ugly hacks
  137. * needed to create resource files in the dest directory
  138. * force us to change to this directory before running the application.
  139. * i.e use <property location> to create an absolute path from a
  140. * relative one before setting this value.
  141. * @param executable
  142. */
  143. public void setExecutable(String executable) {
  144. this.executable = executable;
  145. }
  146. /**
  147. * Select the output encoding: ascii, utf8 or unicode
  148. * @param encoding
  149. */
  150. public void setEncoding(EncodingTypes encoding) {
  151. this.encoding = encoding.getValue();
  152. }
  153. /**
  154. * enable (default) or disable assembly language in the output
  155. * @param assembler
  156. */
  157. public void setAssembler(boolean assembler) {
  158. this.assembler = assembler;
  159. }
  160. /**
  161. * enable or disable (default) the original bytes as comments
  162. * @param bytes
  163. */
  164. public void setBytes(boolean bytes) {
  165. this.bytes = bytes;
  166. }
  167. /**
  168. * the output file (required)
  169. * @param destFile
  170. */
  171. public void setDestFile(File destFile) {
  172. this.destFile = destFile;
  173. }
  174. /**
  175. * include header information; default false.
  176. * @param header
  177. */
  178. public void setHeader(boolean header) {
  179. this.header = header;
  180. }
  181. /**
  182. * name a single item to decode; a class or a method
  183. * e.g item="Myclass::method" or item="namespace1::namespace2::Myclass:method(void(int32))
  184. * @param item
  185. */
  186. public void setItem(String item) {
  187. this.item = item;
  188. }
  189. /**
  190. * include line number information; default=false
  191. * @param linenumbers
  192. */
  193. public void setLinenumbers(boolean linenumbers) {
  194. this.linenumbers = linenumbers;
  195. }
  196. /**
  197. * include metadata information
  198. * @param metadata
  199. */
  200. public void setMetadata(boolean metadata) {
  201. this.metadata = metadata;
  202. }
  203. /**
  204. * show a graphical progress bar in a window during the process; off by default
  205. * @param progressBar
  206. */
  207. public void setProgressBar(boolean progressBar) {
  208. this.progressBar = progressBar;
  209. }
  210. /**
  211. * quote all names.
  212. * @param quoteallnames
  213. */
  214. public void setQuoteallnames(boolean quoteallnames) {
  215. this.quoteallnames = quoteallnames;
  216. }
  217. /**
  218. * enable raw exception handling (default = false)
  219. * @param rawExceptionHandling
  220. */
  221. public void setRawExceptionHandling(boolean rawExceptionHandling) {
  222. this.rawExceptionHandling = rawExceptionHandling;
  223. }
  224. /**
  225. * include the source as comments (default=false)
  226. */
  227. public void setShowSource(boolean showSource) {
  228. this.showSource = showSource;
  229. }
  230. /**
  231. * the file to disassemble -required
  232. * @param sourceFile
  233. */
  234. public void setSourceFile(File sourceFile) {
  235. this.sourceFile = sourceFile;
  236. }
  237. /**
  238. * alternate name for sourceFile
  239. * @param sourceFile
  240. */
  241. public void setSrcFile(File sourceFile) {
  242. setSourceFile(sourceFile);
  243. }
  244. /**
  245. * This method sets the visibility options. It chooses one or more of the following, with + signs to
  246. * concatenate them:
  247. * <pre>
  248. * pub : Public
  249. * pri : Private
  250. * fam : Family
  251. * asm : Assembly
  252. * faa : Family and Assembly
  253. * foa : Family or Assembly
  254. * psc : Private Scope
  255. *</pre>
  256. * e.g. visibility="pub+pri".
  257. * Family means <code>protected</code> in C#;
  258. * @param visibility
  259. */
  260. public void setVisibility(String visibility) {
  261. this.visibility = visibility;
  262. }
  263. /**
  264. * verify that source and dest are ok
  265. */
  266. private void validate() {
  267. if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
  268. throw new BuildException("invalid source");
  269. }
  270. if (destFile == null || destFile.isDirectory()) {
  271. throw new BuildException("invalid dest");
  272. }
  273. if (resourceDir != null
  274. && (!resourceDir.exists() || !resourceDir.isDirectory())) {
  275. throw new BuildException("invalid resource directory");
  276. }
  277. }
  278. /**
  279. * Test for disassembly being needed; use existence and granularity
  280. * correct date stamps
  281. * @return true iff a rebuild is required.
  282. */
  283. private boolean isDisassemblyNeeded() {
  284. if (!destFile.exists()) {
  285. log("Destination file does not exist: a build is required",
  286. Project.MSG_VERBOSE);
  287. return true;
  288. }
  289. long sourceTime = sourceFile.lastModified();
  290. long destTime = destFile.lastModified();
  291. if(sourceTime > (destTime + FileUtils.newFileUtils().getFileTimestampGranularity())) {
  292. log("Source file is newer than the dest file: a rebuild is required",
  293. Project.MSG_VERBOSE);
  294. return true;
  295. } else {
  296. log("The .il file is up to date", Project.MSG_VERBOSE);
  297. return false;
  298. }
  299. }
  300. /**
  301. * do the work
  302. * @throws BuildException
  303. */
  304. public void execute() throws BuildException {
  305. validate();
  306. if(!isDisassemblyNeeded()) {
  307. return;
  308. }
  309. NetCommand command = new NetCommand(this, "ildasm", executable);
  310. command.setFailOnError(true);
  311. //fill in args
  312. command.addArgument("/text");
  313. command.addArgument("/out=" + destFile.toString());
  314. if (!progressBar) {
  315. command.addArgument("/nobar");
  316. }
  317. if (linenumbers) {
  318. command.addArgument("/linenum");
  319. }
  320. if (showSource) {
  321. command.addArgument("/source");
  322. }
  323. if (quoteallnames) {
  324. command.addArgument("/quoteallnames");
  325. }
  326. if (header) {
  327. command.addArgument("/header");
  328. }
  329. if (!assembler) {
  330. command.addArgument("/noil");
  331. }
  332. if (metadata) {
  333. command.addArgument("/tokens");
  334. }
  335. command.addArgument("/item:", item);
  336. if (rawExceptionHandling) {
  337. command.addArgument("/raweh");
  338. }
  339. command.addArgument(EncodingTypes.getEncodingOption(encoding));
  340. if (bytes) {
  341. command.addArgument("/bytes");
  342. }
  343. command.addArgument("/vis:", visibility);
  344. //add the source file
  345. command.addArgument(sourceFile.getAbsolutePath());
  346. //determine directory: resourceDir if set,
  347. //the dir of the destFile if not
  348. File execDir = resourceDir;
  349. if (execDir == null) {
  350. execDir = destFile.getParentFile();
  351. }
  352. command.setDirectory(execDir);
  353. //now run
  354. try {
  355. command.runCommand();
  356. } catch (BuildException e) {
  357. //forcibly delete the output file in case of trouble
  358. if (destFile.exists()) {
  359. log("Deleting destination file as it may be corrupt");
  360. destFile.delete();
  361. }
  362. //then rethrow the exception
  363. throw e;
  364. }
  365. }
  366. /**
  367. * encoding options; the default is ascii
  368. */
  369. public static class EncodingTypes extends EnumeratedAttribute {
  370. public final static String UNICODE = "unicode";
  371. public final static String UTF8 = "utf8";
  372. public final static String ASCII = "ascii";
  373. public String[] getValues() {
  374. return new String[]{
  375. ASCII,
  376. UTF8,
  377. UNICODE,
  378. };
  379. }
  380. /**
  381. * This method maps from an encoding enum to an encoding option.
  382. * @param enumValue
  383. * @return The encoding option indicated by the enum value.
  384. */
  385. public static String getEncodingOption(String enumValue) {
  386. if (UNICODE.equals(enumValue)) {
  387. return "/unicode";
  388. }
  389. if (UTF8.equals(enumValue)) {
  390. return "/utf8";
  391. }
  392. return null;
  393. }
  394. }
  395. /**
  396. * visibility options for decoding
  397. */
  398. public static class VisibilityOptions extends EnumeratedAttribute {
  399. public String[] getValues() {
  400. return new String[]{
  401. "pub", //Public
  402. "pri", //Private
  403. "fam", //Family
  404. "asm", //Assembly
  405. "faa", //Family and Assembly
  406. "foa", //Family or Assembly
  407. "psc", //Private Scope
  408. };
  409. }
  410. }
  411. }