001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.dotnet;
019:
020: import org.apache.tools.ant.types.EnumeratedAttribute;
021: import org.apache.tools.ant.BuildException;
022: import org.apache.tools.ant.Task;
023: import org.apache.tools.ant.Project;
024: import org.apache.tools.ant.util.FileUtils;
025:
026: import java.io.File;
027:
028: /**
029: * Task to take a .NET or Mono -generated managed executable and turn it
030: * into ILASM assembly code. Useful when converting imported typelibs into
031: * assembler before patching and recompiling, as one has to do when doing
032: * advanced typelib work.
033: * <p>
034: * As well as generating the named output file, the ildasm program
035: * will also generate resource files <code>Icons.resources</code>
036: * <code>Message.resources</code> and a .res file whose filename stub is derived
037: * from the source in ways to obscure to determine.
038: * There is no way to control whether or not these files are created, or where they are created
039: * (they are created in the current directory; their names come from inside the
040: * executable and may be those used by the original developer). This task
041: * creates the resources in the directory specified by <code>resourceDir</code> if
042: * set, else in the same directory as the <code>destFile</code>.
043: *
044: * <p>
045: * This task requires the .NET SDK installed and ildasm on the path.
046: * To disassemble using alternate CLR systems, set the executable attribute
047: * to the name/path of the alternate implementation -one that must
048: * support all the classic ildasm commands.
049: *
050: * <p>
051: * Dependency logic: the task executes the command if the output file is missing
052: * or older than the source file. It does not take into account changes
053: * in the options of the task, or timestamp differences in resource files.
054: * When the underlying ildasm executable fails for some reason, it leaves the
055: * .il file in place with some error message. To prevent this from confusing
056: * the dependency logic, the file specified by the <code>dest</code>
057: * attribute is <i>always</i> deleted after an unsuccessful build.
058: * @ant.task category="dotnet"
059: */
060: public class Ildasm extends Task {
061:
062: private static final FileUtils FILE_UTILS = FileUtils
063: .getFileUtils();
064:
065: /**
066: * source file (mandatory)
067: */
068: private File sourceFile;
069:
070: /**
071: * dest file (mandatory)
072: */
073: private File destFile;
074: /**
075: * progress bar switch
076: */
077: private boolean progressBar = false;
078:
079: /**
080: * what is our encoding
081: */
082: private String encoding;
083:
084: /**
085: * /bytes flag for byte markup
086: */
087:
088: private boolean bytes = false;
089:
090: /**
091: * line numbers? /linenum
092: */
093: private boolean linenumbers = false;
094:
095: /**
096: * /raweh flag for raw exception handling
097: */
098: private boolean rawExceptionHandling = false;
099:
100: /**
101: * show the source; /source
102: */
103: private boolean showSource = false;
104:
105: /**
106: * /quoteallnames to quote all names
107: */
108: private boolean quoteallnames = false;
109:
110: /**
111: * /header for header information
112: */
113: private boolean header = false;
114:
115: /**
116: * when false, sets the /noil attribute
117: * to suppress assembly info
118: */
119: private boolean assembler = true;
120:
121: /**
122: * include metadata
123: * /tokens
124: */
125:
126: private boolean metadata = false;
127:
128: /**
129: * what visibility do we want.
130: *
131: */
132: private String visibility;
133:
134: /**
135: * specific item to disassemble
136: */
137:
138: private String item;
139:
140: /**
141: * override for the executable
142: */
143: private String executable = "ildasm";
144:
145: /**
146: * name of the directory for resources to be created. We cannot control
147: * their names, but we can say where they get created. If not set, the
148: * directory of the dest file is used
149: */
150: private File resourceDir;
151:
152: /**
153: * Set the name of the directory for resources to be created. We cannot control
154: * their names, but we can say where they get created. If not set, the
155: * directory of the dest file is used
156: * @param resourceDir the directory in which to create resources.
157: */
158: public void setResourceDir(File resourceDir) {
159: this .resourceDir = resourceDir;
160: }
161:
162: /**
163: * override the name of the executable (normally ildasm) or set
164: * its full path. Do not set a relative path, as the ugly hacks
165: * needed to create resource files in the dest directory
166: * force us to change to this directory before running the application.
167: * i.e use <property location> to create an absolute path from a
168: * relative one before setting this value.
169: * @param executable the name of the executable to use.
170: */
171: public void setExecutable(String executable) {
172: this .executable = executable;
173: }
174:
175: /**
176: * Select the output encoding: ascii, utf8 or unicode
177: * @param encoding the enumerated value.
178: */
179: public void setEncoding(EncodingTypes encoding) {
180: this .encoding = encoding.getValue();
181: }
182:
183: /**
184: * enable (default) or disable assembly language in the output
185: * @param assembler a <code>boolean</code> value.
186: */
187: public void setAssembler(boolean assembler) {
188: this .assembler = assembler;
189: }
190:
191: /**
192: * enable or disable (default) the original bytes as comments
193: * @param bytes a <code>boolean</code> value.
194: */
195: public void setBytes(boolean bytes) {
196: this .bytes = bytes;
197: }
198:
199: /**
200: * the output file (required)
201: * @param destFile the destination file.
202: */
203: public void setDestFile(File destFile) {
204: this .destFile = destFile;
205: }
206:
207: /**
208: * include header information; default false.
209: * @param header a <code>boolean</code> value.
210: */
211: public void setHeader(boolean header) {
212: this .header = header;
213: }
214:
215: /**
216: * name a single item to decode; a class or a method
217: * e.g item="Myclass::method" or item="namespace1::namespace2::Myclass:method(void(int32))
218: * @param item the item to decode.
219: */
220: public void setItem(String item) {
221: this .item = item;
222: }
223:
224: /**
225: * include line number information; default=false
226: * @param linenumbers a <code>boolean</code> value.
227: */
228: public void setLinenumbers(boolean linenumbers) {
229: this .linenumbers = linenumbers;
230: }
231:
232: /**
233: * include metadata information
234: * @param metadata a <code>boolean</code> value.
235: */
236: public void setMetadata(boolean metadata) {
237: this .metadata = metadata;
238: }
239:
240: /**
241: * show a graphical progress bar in a window during the process; off by default
242: * @param progressBar a <code>boolean</code> value.
243: */
244: public void setProgressBar(boolean progressBar) {
245: this .progressBar = progressBar;
246: }
247:
248: /**
249: * quote all names.
250: * @param quoteallnames a <code>boolean</code> value.
251: */
252: public void setQuoteallnames(boolean quoteallnames) {
253: this .quoteallnames = quoteallnames;
254: }
255:
256: /**
257: * enable raw exception handling (default = false)
258: * @param rawExceptionHandling a <code>boolean</code> value.
259: */
260: public void setRawExceptionHandling(boolean rawExceptionHandling) {
261: this .rawExceptionHandling = rawExceptionHandling;
262: }
263:
264: /**
265: * include the source as comments (default=false)
266: * @param showSource a <code>boolean</code> value.
267: */
268: public void setShowSource(boolean showSource) {
269: this .showSource = showSource;
270: }
271:
272: /**
273: * the file to disassemble -required
274: * @param sourceFile the file to disassemble.
275: */
276: public void setSourceFile(File sourceFile) {
277: this .sourceFile = sourceFile;
278: }
279:
280: /**
281: * alternate name for sourceFile
282: * @param sourceFile the source file.
283: */
284: public void setSrcFile(File sourceFile) {
285: setSourceFile(sourceFile);
286: }
287:
288: /**
289: * This method sets the visibility options. It chooses one
290: * or more of the following, with + signs to concatenate them:
291: * <pre>
292: * pub : Public
293: * pri : Private
294: * fam : Family
295: * asm : Assembly
296: * faa : Family and Assembly
297: * foa : Family or Assembly
298: * psc : Private Scope
299: *</pre>
300: * e.g. visibility="pub+pri".
301: * Family means <code>protected</code> in C#;
302: * @param visibility the options to use.
303: */
304: public void setVisibility(String visibility) {
305: this .visibility = visibility;
306: }
307:
308: /**
309: * verify that source and dest are ok
310: */
311: private void validate() {
312: if (sourceFile == null || !sourceFile.exists()
313: || !sourceFile.isFile()) {
314: throw new BuildException("invalid source");
315: }
316: if (destFile == null || destFile.isDirectory()) {
317: throw new BuildException("invalid dest");
318: }
319: if (resourceDir != null
320: && (!resourceDir.exists() || !resourceDir.isDirectory())) {
321: throw new BuildException("invalid resource directory");
322: }
323: }
324:
325: /**
326: * Test for disassembly being needed; use existence and granularity
327: * correct date stamps
328: * @return true iff a rebuild is required.
329: */
330: private boolean isDisassemblyNeeded() {
331: if (!destFile.exists()) {
332: log("Destination file does not exist: a build is required",
333: Project.MSG_VERBOSE);
334: return true;
335: }
336: long sourceTime = sourceFile.lastModified();
337: long destTime = destFile.lastModified();
338: if (sourceTime > (destTime + FILE_UTILS
339: .getFileTimestampGranularity())) {
340: log(
341: "Source file is newer than the dest file: a rebuild is required",
342: Project.MSG_VERBOSE);
343: return true;
344: } else {
345: log("The .il file is up to date", Project.MSG_VERBOSE);
346: return false;
347: }
348:
349: }
350:
351: /**
352: * do the work
353: * @throws BuildException if there is an error.
354: */
355: public void execute() throws BuildException {
356: log(
357: "This task is deprecated and will be removed in a future version\n"
358: + "of Ant. It is now part of the .NET Antlib:\n"
359: + "http://ant.apache.org/antlibs/dotnet/index.html",
360: Project.MSG_WARN);
361: validate();
362: if (!isDisassemblyNeeded()) {
363: return;
364: }
365: NetCommand command = new NetCommand(this , "ildasm", executable);
366: command.setFailOnError(true);
367: //fill in args
368: command.addArgument("/text");
369: command.addArgument("/out=" + destFile.toString());
370: if (!progressBar) {
371: command.addArgument("/nobar");
372: }
373: if (linenumbers) {
374: command.addArgument("/linenum");
375: }
376: if (showSource) {
377: command.addArgument("/source");
378: }
379: if (quoteallnames) {
380: command.addArgument("/quoteallnames");
381: }
382: if (header) {
383: command.addArgument("/header");
384: }
385: if (!assembler) {
386: command.addArgument("/noil");
387: }
388: if (metadata) {
389: command.addArgument("/tokens");
390: }
391: command.addArgument("/item:", item);
392: if (rawExceptionHandling) {
393: command.addArgument("/raweh");
394: }
395: command.addArgument(EncodingTypes.getEncodingOption(encoding));
396: if (bytes) {
397: command.addArgument("/bytes");
398: }
399: command.addArgument("/vis:", visibility);
400:
401: //add the source file
402: command.addArgument(sourceFile.getAbsolutePath());
403:
404: //determine directory: resourceDir if set,
405: //the dir of the destFile if not
406: File execDir = resourceDir;
407: if (execDir == null) {
408: execDir = destFile.getParentFile();
409: }
410: command.setDirectory(execDir);
411:
412: //now run
413: try {
414: command.runCommand();
415: } catch (BuildException e) {
416: //forcibly delete the output file in case of trouble
417: if (destFile.exists()) {
418: log("Deleting destination file as it may be corrupt");
419: destFile.delete();
420: }
421: //then rethrow the exception
422: throw e;
423: }
424:
425: }
426:
427: /**
428: * encoding options; the default is ascii
429: */
430: public static class EncodingTypes extends EnumeratedAttribute {
431: /** Unicode */
432: public static final String UNICODE = "unicode";
433: /** UTF8 */
434: public static final String UTF8 = "utf8";
435: /** ASCII */
436: public static final String ASCII = "ascii";
437:
438: /** {@inheritDoc}. */
439: public String[] getValues() {
440: return new String[] { ASCII, UTF8, UNICODE, };
441: }
442:
443: /**
444: * This method maps from an encoding enum to an encoding option.
445: * @param enumValue the value to use.
446: * @return The encoding option indicated by the enum value.
447: */
448: public static String getEncodingOption(String enumValue) {
449: if (UNICODE.equals(enumValue)) {
450: return "/unicode";
451: }
452: if (UTF8.equals(enumValue)) {
453: return "/utf8";
454: }
455: return null;
456: }
457: }
458:
459: /**
460: * visibility options for decoding
461: */
462: public static class VisibilityOptions extends EnumeratedAttribute {
463: /** {@inheritDoc}. */
464: public String[] getValues() {
465: return new String[] { "pub", //Public
466: "pri", //Private
467: "fam", //Family
468: "asm", //Assembly
469: "faa", //Family and Assembly
470: "foa", //Family or Assembly
471: "psc", //Private Scope
472: };
473: }
474:
475: }
476: }
|