blob: 5075635498e321c3d3433a396727e4fad5b6c66f [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Ant" and "Apache Software Foundation"
* must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.taskdefs.optional.dotnet;
import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.util.FileUtils;
import java.io.File;
/**
* Task to take a .NET or Mono -generated managed executable and turn it
* into ILASM assembly code. Useful when converting imported typelibs into
* assembler before patching and recompiling, as one has to do when doing
* advanced typelib work.
* <p>
* As well as generating the named output file, the ildasm program
* will also generate resource files <code>Icons.resources</code>
* <code>Message.resources</code> and a .res file whose filename stub is derived
* from the source in ways to obscure to determine.
* There is no way to control whether or not these files are created, or where they are created
* (they are created in the current directory; their names come from inside the
* executable and may be those used by the original developer). This task
* creates the resources in the directory specified by <code>resourceDir</code> if
* set, else in the same directory as the <code>destFile</code>.
*
* <p>
* This task requires the .NET SDK installed and ildasm on the path.
* To disassemble using alternate CLR systems, set the executable attribute
* to the name/path of the alternate implementation -one that must
* support all the classic ildasm commands.
*
* <p>
* Dependency logic: the task executes the command if the output file is missing
* or older than the source file. It does not take into account changes
* in the options of the task, or timestamp differences in resource files.
* When the underlying ildasm executable fails for some reason, it leaves the
* .il file in place with some error message. To prevent this from confusing
* the dependency logic, the file specified by the <code>dest</code>
* attribute is <i>always</i> deleted after an unsuccessful build.
* @ant.task category="dotnet"
*/
public class Ildasm extends Task {
/**
* source file (mandatory)
*/
private File sourceFile;
/**
* dest file (mandatory)
*/
private File destFile;
/**
* progress bar switch
*/
private boolean progressBar = false;
/**
* what is our encoding
*/
private String encoding;
/**
* /bytes flag for byte markup
*/
private boolean bytes = false;
/**
* line numbers? /linenum
*/
private boolean linenumbers = false;
/**
* /raweh flag for raw exception handling
*/
private boolean rawExceptionHandling = false;
/**
* show the source; /source
*/
private boolean showSource = false;
/**
* /quoteallnames to quote all names
*/
private boolean quoteallnames = false;
/**
* /header for header information
*/
private boolean header = false;
/**
* when false, sets the /noil attribute
* to suppress assembly info
*/
private boolean assembler = true;
/**
* include metadata
* /tokens
*/
private boolean metadata = false;
/**
* what visibility do we want.
*
*/
private String visibility;
/**
* specific item to disassemble
*/
private String item;
/**
* override for the executable
*/
private String executable = "ildasm";
/**
* name of the directory for resources to be created. We cannot control
* their names, but we can say where they get created. If not set, the
* directory of the dest file is used
*/
private File resourceDir;
/**
* Set the name of the directory for resources to be created. We cannot control
* their names, but we can say where they get created. If not set, the
* directory of the dest file is used
*/
public void setResourceDir(File resourceDir) {
this.resourceDir = resourceDir;
}
/**
* override the name of the executable (normally ildasm) or set
* its full path. Do not set a relative path, as the ugly hacks
* needed to create resource files in the dest directory
* force us to change to this directory before running the application.
* i.e use &lt;property location&gt to create an absolute path from a
* relative one before setting this value.
* @param executable
*/
public void setExecutable(String executable) {
this.executable = executable;
}
/**
* Select the output encoding: ascii, utf8 or unicode
* @param encoding
*/
public void setEncoding(EncodingTypes encoding) {
this.encoding = encoding.getValue();
}
/**
* enable (default) or disable assembly language in the output
* @param assembler
*/
public void setAssembler(boolean assembler) {
this.assembler = assembler;
}
/**
* enable or disable (default) the original bytes as comments
* @param bytes
*/
public void setBytes(boolean bytes) {
this.bytes = bytes;
}
/**
* the output file (required)
* @param destFile
*/
public void setDestFile(File destFile) {
this.destFile = destFile;
}
/**
* include header information; default false.
* @param header
*/
public void setHeader(boolean header) {
this.header = header;
}
/**
* name a single item to decode; a class or a method
* e.g item="Myclass::method" or item="namespace1::namespace2::Myclass:method(void(int32))
* @param item
*/
public void setItem(String item) {
this.item = item;
}
/**
* include line number information; default=false
* @param linenumbers
*/
public void setLinenumbers(boolean linenumbers) {
this.linenumbers = linenumbers;
}
/**
* include metadata information
* @param metadata
*/
public void setMetadata(boolean metadata) {
this.metadata = metadata;
}
/**
* show a graphical progress bar in a window during the process; off by default
* @param progressBar
*/
public void setProgressBar(boolean progressBar) {
this.progressBar = progressBar;
}
/**
* quote all names.
* @param quoteallnames
*/
public void setQuoteallnames(boolean quoteallnames) {
this.quoteallnames = quoteallnames;
}
/**
* enable raw exception handling (default = false)
* @param rawExceptionHandling
*/
public void setRawExceptionHandling(boolean rawExceptionHandling) {
this.rawExceptionHandling = rawExceptionHandling;
}
/**
* include the source as comments (default=false)
*/
public void setShowSource(boolean showSource) {
this.showSource = showSource;
}
/**
* the file to disassemble -required
* @param sourceFile
*/
public void setSourceFile(File sourceFile) {
this.sourceFile = sourceFile;
}
/**
* alternate name for sourceFile
* @param sourceFile
*/
public void setSrcFile(File sourceFile) {
setSourceFile(sourceFile);
}
/**
* visibility options: one or more of the following, with + signs to
* concatenate them:
* <pre>
* pub : Public
* pri : Private
* fam : Family
* asm : Assembly
* faa : Family and Assembly
* foa : Family or Assembly
* psc : Private Scope
*</pre>
* e.g. visibility="pub+pri".
* Family means <code>protected</code> in C#;
* @param visibility
*/
public void setVisibility(String visibility) {
this.visibility = visibility;
}
/**
* verify that source and dest are ok
*/
private void validate() {
if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
throw new BuildException("invalid source");
}
if (destFile == null || destFile.isDirectory()) {
throw new BuildException("invalid dest");
}
if (resourceDir != null
&& (!resourceDir.exists() || !resourceDir.isDirectory())) {
throw new BuildException("invalid resource directory");
}
}
/**
* Test for disassembly being needed; use existence and granularity
* correct date stamps
* @return true iff a rebuild is required.
*/
private boolean isDisassemblyNeeded() {
if (!destFile.exists()) {
log("Destination file does not exist: a build is required",
Project.MSG_VERBOSE);
return true;
}
long sourceTime = sourceFile.lastModified();
long destTime = destFile.lastModified();
if(sourceTime > (destTime + FileUtils.newFileUtils().getFileTimestampGranularity())) {
log("Source file is newer than the dest file: a rebuild is required",
Project.MSG_VERBOSE);
return true;
} else {
log("The .il file is up to date", Project.MSG_VERBOSE);
return false;
}
}
/**
* do the work
* @throws BuildException
*/
public void execute() throws BuildException {
validate();
if(!isDisassemblyNeeded()) {
return;
}
NetCommand command = new NetCommand(this, "ildasm", executable);
command.setFailOnError(true);
//fill in args
command.addArgument("/text");
command.addArgument("/out=" + destFile.toString());
if (!progressBar) {
command.addArgument("/nobar");
}
if (linenumbers) {
command.addArgument("/linenum");
}
if (showSource) {
command.addArgument("/source");
}
if (quoteallnames) {
command.addArgument("/quoteallnames");
}
if (header) {
command.addArgument("/header");
}
if (!assembler) {
command.addArgument("/noil");
}
if (metadata) {
command.addArgument("/tokens");
}
command.addArgument("/item:", item);
if (rawExceptionHandling) {
command.addArgument("/raweh");
}
command.addArgument(EncodingTypes.getEncodingOption(encoding));
if (bytes) {
command.addArgument("/bytes");
}
command.addArgument("/vis:", visibility);
//add the source file
command.addArgument(sourceFile.getAbsolutePath());
//determine directory: resourceDir if set,
//the dir of the destFile if not
File execDir = resourceDir;
if (execDir == null) {
execDir = destFile.getParentFile();
}
command.setDirectory(execDir);
//now run
try {
command.runCommand();
} catch (BuildException e) {
//forcibly delete the output file in case of trouble
if (destFile.exists()) {
log("Deleting destination file as it may be corrupt");
destFile.delete();
}
//then rethrow the exception
throw e;
}
}
/**
* encoding options; the default is ascii
*/
public static class EncodingTypes extends EnumeratedAttribute {
public final static String UNICODE = "unicode";
public final static String UTF8 = "utf8";
public final static String ASCII = "ascii";
public String[] getValues() {
return new String[]{
ASCII,
UTF8,
UNICODE,
};
}
/**
* map from an encoding enum to an encoding option
* @param enumValue
* @return
*/
public static String getEncodingOption(String enumValue) {
if (UNICODE.equals(enumValue)) {
return "/unicode";
}
if (UTF8.equals(enumValue)) {
return "/utf8";
}
return null;
}
}
/**
* visibility options for decoding
*/
public static class VisibilityOptions extends EnumeratedAttribute {
public String[] getValues() {
return new String[]{
"pub", //Public
"pri", //Private
"fam", //Family
"asm", //Assembly
"faa", //Family and Assembly
"foa", //Family or Assembly
"psc", //Private Scope
};
}
}
}