diff --git a/src/etc/testcases/taskdefs/optional/dotnet.xml b/src/etc/testcases/taskdefs/optional/dotnet.xml index eaa4e1c9a..7c4cfab69 100644 --- a/src/etc/testcases/taskdefs/optional/dotnet.xml +++ b/src/etc/testcases/taskdefs/optional/dotnet.xml @@ -6,7 +6,7 @@ - + @@ -48,6 +48,15 @@ mono.ilasm.found=${mono.ilasm.found} + + + + + + + + ilasm.found=${ildasm.found} + @@ -228,17 +237,17 @@ - + - + - No app ${testCSC.exe} created + No app ${testILASM.exe} created @@ -258,5 +267,29 @@ ${ilasm.string} + + + + + + No file ${testILDASM.il} created + + + + + + + + + + diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index bcf74f6dd..e259c024c 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -196,6 +196,7 @@ sshexec=org.apache.tools.ant.taskdefs.optional.ssh.SSHExec jsharpc=org.apache.tools.ant.taskdefs.optional.dotnet.JSharp rexec=org.apache.tools.ant.taskdefs.optional.net.RExecTask scriptdef=org.apache.tools.ant.taskdefs.optional.script.ScriptDef +ildasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ildasm # deprecated ant tasks (kept for back compatibility) starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java index 42fadf15e..6cb32362a 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java @@ -304,7 +304,7 @@ public class Ilasm /** * Set the output file; identical to setDestFile - * @see DotnetBaseMatchingTask.setDestFile + * @see DotnetBaseMatchingTask#setDestFile *@param params The new outputFile value */ public void setOutputFile(File params) { @@ -473,10 +473,6 @@ public class Ilasm */ public void execute() throws BuildException { - if (srcDir == null) { - srcDir = getProject().resolveFile("."); - } - NetCommand command = buildIlasmCommand(); addFilesAndExecute(command, false); diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ildasm.java b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ildasm.java new file mode 100644 index 000000000..b5bb501dc --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ildasm.java @@ -0,0 +1,481 @@ +/* + * 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 + * . + */ +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.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. + * + * As well as generating the named output file, the ildasm program + * will also generate resource files Icons.resources + * Message.resources 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 resourceDir if + * set, else in the same directory as the destFile. + * + * + * 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. + * + * + * 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 dest + * attribute is always deleted after an unsuccessful build. + */ +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 <property location> 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 orginal 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: + *
+     * pub : Public
+     * pri : Private
+     * fam : Family
+     * asm : Assembly
+     * faa : Family and Assembly
+     * foa : Family or Assembly
+     * psc : Private Scope
+     *
+ * e.g. visibility="pub+pri". + * Family means protected 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"); + } + } + + /** + * + * @return + */ + private boolean isDisassemblyNeeded() { + if(!destFile.exists()) { + return true; + } + long sourceTime=sourceFile.lastModified(); + long destTime=destFile.lastModified(); + return sourceTime>(destTime+ FileUtils.newFileUtils().getFileTimestampGranularity()); + + } + /** + * do the work + * @throws BuildException + */ + public void execute() throws BuildException { + validate(); + 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()) { + 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 opion + * @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 + }; + } + + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java index e0ceeb695..da6868d8f 100644 --- a/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java +++ b/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java @@ -124,6 +124,11 @@ public class NetCommand { */ protected boolean failOnError; + /** + * the directory to execute the command in. When null, the current + * directory is used. + */ + private File directory; /** * constructor @@ -174,6 +179,14 @@ public class NetCommand { } + /** + * set the directory to run from, if the default is inadequate + * @param directory + */ + public void setDirectory(File directory) { + this.directory = directory; + } + /** * verbose text log * @@ -206,7 +219,13 @@ public class NetCommand { } } - public void addArgument(String argument1, String argument2) { + /** + * concatenate two strings together and add them as a single argument, + * but only if argument2 is non-null and non-zero length + * + *@param argument1 The first argument + *@param argument2 The second argument + */ public void addArgument(String argument1, String argument2) { if (argument2 != null && argument2.length() != 0) { commandLine.createArgument().setValue(argument1 + argument2); } @@ -224,6 +243,10 @@ public class NetCommand { throw new RuntimeException("Owner has no project"); } File dir = owner.getProject().getBaseDir(); + if (directory != null) { + dir=directory; + } + ExecuteStreamHandler handler = new LogStreamHandler(owner, Project.MSG_INFO, Project.MSG_WARN); executable = new Execute(handler, null); diff --git a/src/testcases/org/apache/tools/ant/taskdefs/optional/DotnetTest.java b/src/testcases/org/apache/tools/ant/taskdefs/optional/DotnetTest.java index c48d6160b..9ddf7bcdd 100644 --- a/src/testcases/org/apache/tools/ant/taskdefs/optional/DotnetTest.java +++ b/src/testcases/org/apache/tools/ant/taskdefs/optional/DotnetTest.java @@ -129,11 +129,28 @@ public class DotnetTest extends BuildFileTest { } /** - * A unit test for JUnit + * test we can assemble */ public void testILASM() throws Exception { executeTarget("testILASM"); - } + } + + /** + * test we can disassemble + */ + public void testILDASM() throws Exception { + executeTarget("testILDASM"); + } + + /** + * test we can disassemble + */ + public void testILDASM_empty() throws Exception { + expectBuildExceptionContaining("testILDASM_empty", + "parameter validation", + "invalid"); + } + }