// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Shawn Van Ness (nantluver@arithex.com)
// Gerry Shaw (gerry_shaw@yahoo.com)
// Ian MacLean (ian@maclean.ms)
// Eric V. Smith (ericsmith@windsor.com)
//
// TODO: review interface for future compatibility/customizations issues
using System;
using System.IO;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.VisualCpp.Types;
using NAnt.VisualCpp.Util;
namespace NAnt.VisualCpp.Tasks {
///
/// Links files using link.exe, Microsoft's Incremental Linker.
///
///
/// This task is intended for version 7.00.9466 of link.exe.
///
///
///
/// Combine all object files in the current directory into helloworld.exe.
///
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("link")]
public class LinkTask : ExternalProgramBase {
#region Private Instance Fields
private string _responseFileName;
private FileInfo _outputFile;
private FileInfo _pdbFile;
private bool _debug;
private FileSet _sources = new FileSet();
private FileSet _libdirs = new FileSet();
private FileSet _modules = new FileSet();
private FileSet _delayLoadedDlls = new FileSet();
private FileSet _embeddedResources = new FileSet();
private SymbolCollection _symbols = new SymbolCollection();
private LibraryCollection _ignoreLibraries = new LibraryCollection();
private string _options;
#endregion Private Instance Fields
#region Public Instance Properties
///
/// Options to pass to the compiler.
///
[TaskAttribute("options")]
public string Options {
get { return _options; }
set { _options = value; }
}
///
/// Create debugging information for the .exe file or DLL. The default is
/// .
///
public bool Debug {
get { return _debug; }
set { _debug = value; }
}
///
/// The output file.
///
[TaskAttribute("output", Required=true)]
public FileInfo OutputFile {
get { return _outputFile; }
set { _outputFile = value; }
}
///
/// A user-specified name for the program database (PDB) that the linker
/// creates. The default file name for the PDB has the base name of the
/// and the extension .pdb.
///
[TaskAttribute("pdbfile")]
public FileInfo ProgramDatabaseFile {
get {
if (Debug && _pdbFile == null) {
_pdbFile = new FileInfo(Path.ChangeExtension(OutputFile.FullName, ".pdb"));
}
return _pdbFile;
}
set { _pdbFile = value; }
}
///
/// Specified DLLs for delay loading.
///
[BuildElement("delayloaded")]
public FileSet DelayLoadedDlls {
get { return _delayLoadedDlls; }
set { _delayLoadedDlls = value; }
}
///
/// The list of files to combine into the output file.
///
[BuildElement("sources")]
public FileSet Sources {
get { return _sources; }
set { _sources = value; }
}
///
/// The list of additional library directories to search.
///
[BuildElement("libdirs")]
public FileSet LibDirs {
get { return _libdirs; }
set { _libdirs = value; }
}
///
/// Link the specified modules into this assembly.
///
[BuildElement("modules")]
public FileSet Modules {
get { return _modules; }
set { _modules = value; }
}
///
/// Embed the specified resources into this assembly.
///
[BuildElement("embeddedresources")]
public FileSet EmbeddedResources {
get { return _embeddedResources; }
set { _embeddedResources = value; }
}
///
/// Symbols to add to the symbol table.
///
[BuildElementCollection("symbols", "symbol")]
public SymbolCollection Symbols {
get { return _symbols; }
set { _symbols = value; }
}
///
/// Names of libraries that you want the linker to ignore when it
/// resolves external references.
///
[BuildElementCollection("ignorelibraries", "library")]
public LibraryCollection IgnoreLibraries {
get { return _ignoreLibraries; }
set { _ignoreLibraries = value; }
}
#endregion Public Instance Properties
#region Override implementation of ExternalProgramBase
///
/// Gets the filename of the external program to start.
///
/// The filename of the external program.
public override string ProgramFileName {
get { return Name; }
}
///
/// Gets the command-line arguments for the external program.
///
///
/// The command-line arguments for the external program.
///
public override string ProgramArguments {
get { return "@" + "\"" + _responseFileName + "\""; }
}
#endregion Override implementation of ExternalProgramBase
#region Override implementation of Task
///
/// Links the sources.
///
protected override void ExecuteTask() {
// ensure base directory is set, even if fileset was not initialized
// from XML
if (Sources.BaseDirectory == null) {
Sources.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (LibDirs.BaseDirectory == null) {
LibDirs.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (Modules.BaseDirectory == null) {
Modules.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (EmbeddedResources.BaseDirectory == null) {
EmbeddedResources.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (NeedsLinking()) {
Log(Level.Info, "Linking {0} files.",
Sources.FileNames.Count);
// create temp response file to hold compiler options
_responseFileName = Path.GetTempFileName();
StreamWriter writer = new StreamWriter(_responseFileName);
try {
// specify the output file
writer.WriteLine("/OUT:\"{0}\"", OutputFile.FullName);
// write user provided options
if (Options != null) {
writer.WriteLine(Options);
}
// write each of the libdirs
foreach (string libdir in LibDirs.DirectoryNames) {
writer.WriteLine("/LIBPATH:{0}", QuoteArgumentValue(libdir));
}
// write each of the module references
foreach (string module in Modules.FileNames) {
writer.WriteLine("/ASSEMBLYMODULE:{0}", QuoteArgumentValue(module));
}
// write delay loaded DLLs
foreach (string dll in DelayLoadedDlls.FileNames) {
writer.WriteLine("/DELAYLOAD:{0}", QuoteArgumentValue(dll));
}
// write each of the embedded resources
foreach (string resource in EmbeddedResources.FileNames) {
writer.WriteLine("/ASSEMBLYRESOURCE:{0}", QuoteArgumentValue(resource));
}
// write symbols
foreach (Symbol symbol in Symbols) {
if (symbol.IfDefined && !symbol.UnlessDefined) {
writer.WriteLine("/INCLUDE:{0}", QuoteArgumentValue(
symbol.SymbolName));
}
}
// names of default libraries to ignore
foreach (Library ignoreLibrary in IgnoreLibraries) {
if (ignoreLibrary.IfDefined && !ignoreLibrary.UnlessDefined) {
writer.WriteLine("/NODEFAULTLIB:{0}", QuoteArgumentValue(
ignoreLibrary.LibraryName));
}
}
if (Debug) {
writer.WriteLine("/DEBUG");
}
// write program database file
if (ProgramDatabaseFile != null) {
writer.WriteLine("/PDB:{0}", QuoteArgumentValue(
ProgramDatabaseFile.FullName));
}
// suppresses display of the sign-on banner
writer.WriteLine("/nologo");
// write each of the filenames
foreach (string filename in Sources.FileNames) {
writer.WriteLine(QuoteArgumentValue(filename));
}
writer.Close();
if (Verbose) {
// display response file contents
Log(Level.Info, "Contents of {0}.", _responseFileName);
StreamReader reader = File.OpenText(_responseFileName);
Log(Level.Info, reader.ReadToEnd());
reader.Close();
}
// call base class to do the actual work
base.ExecuteTask();
} finally {
// make sure we delete response file even if an exception is thrown
writer.Close(); // make sure stream is closed or file cannot be deleted
File.Delete(_responseFileName);
_responseFileName = null;
}
}
}
#endregion Override implementation of Task
#region Protected Instance Methods
///
/// Determines if the output needs linking.
///
protected virtual bool NeedsLinking() {
// return true as soon as we know we need to compile
if (ProgramDatabaseFile != null) {
if (!ProgramDatabaseFile.Exists) {
Log(Level.Verbose, "PDB file '{0}' does not exist, relinking.",
ProgramDatabaseFile.FullName);
return true;
}
// check if sources were updated
string fileName = FileSet.FindMoreRecentLastWriteTime(Sources.FileNames, ProgramDatabaseFile.LastWriteTime);
if (fileName != null) {
Log(Level.Verbose, "'{0}' has been updated, relinking.", fileName);
return true;
}
}
if (OutputFile != null) {
if (!OutputFile.Exists) {
Log(Level.Verbose, "Output file '{0}' does not exist, relinking.",
OutputFile.FullName);
return true;
}
// check if sources were updated
string fileName = FileSet.FindMoreRecentLastWriteTime(Sources.FileNames, OutputFile.LastWriteTime);
if (fileName != null) {
Log(Level.Verbose, "'{0}' has been updated, relinking.", fileName);
return true;
}
}
return false;
}
#endregion Protected Instance Methods
#region Public Static Methods
///
/// Quotes an argument value and duplicates trailing backslahes.
///
/// The argument value to quote.
///
/// The quotes argument value.
///
public static string QuoteArgumentValue(string value) {
return ArgumentUtils.QuoteArgumentValue(value,
BackslashProcessingMethod.Duplicate);
}
#endregion Public Static Methods
}
}
#if unused
Microsoft (R) Incremental Linker Version 7.00.9466
Copyright (C) Microsoft Corporation. All rights reserved.
usage: LINK [options] [files] [@commandfile]
options:
/ALIGN:#
/ALLOWBIND[:NO]
/ASSEMBLYMODULE:filename
/ASSEMBLYRESOURCE:filename
/BASE:{address|@filename,key}
/DEBUG
/DEF:filename
/DEFAULTLIB:library
/DELAY:{NOBIND|UNLOAD}
/DELAYLOAD:dll
/DLL
/DRIVER[:{UPONLY|WDM}]
/ENTRY:symbol
/EXETYPE:DYNAMIC
/EXPORT:symbol
/FIXED[:NO]
/FORCE[:{MULTIPLE|UNRESOLVED}]
/HEAP:reserve[,commit]
/IDLOUT:filename
/IGNOREIDL
/IMPLIB:filename
/INCLUDE:symbol
/INCREMENTAL[:NO]
/LARGEADDRESSAWARE[:NO]
/LIBPATH:dir
/LTCG[:{NOSTATUS|PGINSTRUMENT|PGOPTIMIZE|STATUS}]
(PGINSTRUMENT and PGOPTIMIZE are only available for IA64)
/MACHINE:{AM33|ARM|IA64|M32R|MIPS|MIPS16|MIPSFPU|MIPSFPU16|MIPSR41XX|
PPC|PPCFP|SH3|SH3DSP|SH4|SH5|THUMB|TRICORE|X86}
/MAP[:filename]
/MAPINFO:{EXPORTS|LINES}
/MERGE:from=to
/MIDL:@commandfile
/NOASSEMBLY
/NODEFAULTLIB[:library]
/NOENTRY
/NOLOGO
/OPT:{ICF[=iterations]|NOICF|NOREF|NOWIN98|REF|WIN98}
/ORDER:@filename
/OUT:filename
/PDB:filename
/PDBSTRIPPED:filename
/PGD:filename
/RELEASE
/SECTION:name,[E][R][W][S][D][K][L][P][X][,ALIGN=#]
/STACK:reserve[,commit]
/STUB:filename
/SUBSYSTEM:{CONSOLE|EFI_APPLICATION|EFI_BOOT_SERVICE_DRIVER|
EFI_ROM|EFI_RUNTIME_DRIVER|NATIVE|POSIX|WINDOWS|
WINDOWSCE}[,#[.##]]
/SWAPRUN:{CD|NET}
/TLBOUT:filename
/TSAWARE[:NO]
/TLBID:#
/VERBOSE[:LIB]
/VERSION:#[.#]
/VXD
/WINDOWSCE:{CONVERT|EMULATION}
/WS:AGGRESSIVE
#endif