// 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 // // Joe Jones (joejo@microsoft.com) // Gerry Shaw (gerry_shaw@yahoo.com) // Gert Driesen (gert.driesen@ardatis.com) // Giuseppe Greco (giuseppe.greco@agamura.com) using System; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Text; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Types; using NAnt.Core.Util; using NAnt.DotNet.Types; namespace NAnt.DotNet.Tasks { /// /// Wraps al.exe, the assembly linker for the .NET Framework. /// /// /// /// All specified sources will be embedded using the /embed flag. /// Other source types are not supported. /// /// /// /// /// Create a library containing all icon files in the current directory. /// /// /// /// /// /// /// /// ]]> /// /// [TaskName("al")] [ProgramLocation(LocationType.FrameworkDir)] public class AssemblyLinkerTask : NAnt.Core.Tasks.ExternalProgramBase { #region Private Instance Fields private string _responseFileName; private FileInfo _outputFile; private string _target; private string _algorithmID; private string _company; private string _configuration; private string _copyright; private string _culture; private bool _delaySign; private string _description; private FileInfo _evidenceFile; private string _fileVersion; private string _flags; private string _keyContainer; private FileInfo _keyfile; private string _mainMethod; private string _product; private string _productVersion; private FileSet _resources = new FileSet(); private EmbeddedResourceCollection _embeddedResources = new EmbeddedResourceCollection(); private FileInfo _templateFile; private string _title; private string _trademark; private string _version; private FileInfo _win32Icon; private FileInfo _win32Res; // framework configuration settings private bool _supportsTemplate = true; #endregion Private Instance Fields #region Public Instance Properties /// /// Specifies an algorithm (in hexadecimal) to hash all files in a /// multifile assembly except the file that contains the assembly /// manifest. The default algorithm is CALG_SHA1. /// [TaskAttribute("algid", Required=false)] [Int32Validator(Base=16)] public string AlgorithmID { get { return _algorithmID; } set { _algorithmID = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies a string for the Company field in the assembly. /// /// /// A string for the Company field in the assembly. /// /// /// If is an empty string (""), the Win32 /// Company resource appears as a single space. /// [TaskAttribute("company", Required=false)] public string Company { get { return _company; } set { _company = value; } } /// /// Specifies a string for the Configuration field in the assembly. /// /// /// A string for the Configuration field in the assembly. /// /// /// If is an empty string (""), the Win32 /// Configuration resource appears as a single space. /// [TaskAttribute("configuration", Required=false)] public string Configuration { get { return _configuration; } set { _configuration = value; } } /// /// Specifies a string for the Copyright field in the assembly. /// /// /// A string for the Copyright field in the assembly. /// /// /// If is an empty string (""), the Win32 /// Copyright resource appears as a single space. /// [TaskAttribute("copyright", Required=false)] public string Copyright { get { return _copyright; } set { _copyright = value; } } /// /// The culture string associated with the output assembly. /// The string must be in RFC 1766 format, such as "en-US". /// /// /// /// Corresponds with the /c[ulture]: flag. /// /// [TaskAttribute("culture", Required=false)] public string Culture { get { return _culture; } set { _culture = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies whether the assembly should be partially signed. The default /// is . /// [TaskAttribute("delaysign", Required=false)] public bool DelaySign { get { return _delaySign; } set { _delaySign = value; } } /// /// Specifies a string for the Description field in the assembly. /// /// /// A string for the Description field in the assembly. /// /// /// If is an empty string (""), the Win32 /// Description resource appears as a single space. /// [TaskAttribute("description", Required=false)] public string Description { get { return _description; } set { _description = value; } } /// /// Security evidence file to embed. /// /// /// The security evidence file to embed. /// /// /// /// Corresponds with the /e[vidence] flag. /// /// [TaskAttribute("evidence", Required=false)] public FileInfo EvidenceFile { get { return _evidenceFile; } set { _evidenceFile = value; } } /// /// Specifies a string for the File Version field in the assembly. /// /// /// A string for the File Version field in the assembly. /// [TaskAttribute("fileversion", Required=false)] public string FileVersion { get { return _fileVersion; } set { _fileVersion = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies a value (in hexadecimal) for the Flags field in /// the assembly. /// /// /// A value (in hexadecimal) for the Flags field in the assembly. /// [TaskAttribute("flags", Required=false)] [Int32Validator(Base=16)] public string Flags { get { return _flags; } set { _flags = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies a container that holds a key pair. /// [TaskAttribute("keycontainer")] public string KeyContainer { get { return _keyContainer; } set { _keyContainer = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies a file (filename) that contains a key pair or /// just a public key to sign an assembly. /// /// /// The complete path to the key file. /// /// /// /// Corresponds with the /keyf[ile]: flag. /// /// [TaskAttribute("keyfile", Required=false)] public FileInfo KeyFile { get { return _keyfile; } set { _keyfile = value; } } /// /// Specifies the fully-qualified name (class.method) of the method to /// use as an entry point when converting a module to an executable file. /// /// /// The fully-qualified name (class.method) of the method to use as an /// entry point when converting a module to an executable file. /// [TaskAttribute("main")] public string MainMethod { get { return _mainMethod; } set { _mainMethod = StringUtils.ConvertEmptyToNull(value); } } /// /// The name of the output file for the assembly manifest. /// /// /// The complete output path for the assembly manifest. /// /// /// /// Corresponds with the /out flag. /// /// [TaskAttribute("output", Required=true)] public FileInfo OutputFile { get { return _outputFile; } set { _outputFile = value; } } /// /// The target type (one of lib, exe, or winexe). /// /// /// /// Corresponds with the /t[arget]: flag. /// /// [TaskAttribute("target", Required=true)] [StringValidator(AllowEmpty=false)] public string OutputTarget { get { return _target; } set { _target = StringUtils.ConvertEmptyToNull(value); } } /// /// Specifies a string for the Product field in the assembly. /// /// /// A string for the Product field in the assembly. /// [TaskAttribute("product", Required=false)] public string Product { get { return _product; } set { _product = value; } } /// /// Specifies a string for the Product Version field in the assembly. /// /// /// A string for the Product Version field in the assembly. /// [TaskAttribute("productversion", Required=false)] public string ProductVersion { get { return _productVersion; } set { _productVersion = StringUtils.ConvertEmptyToNull(value); } } /// /// The set of resources to embed. /// [BuildElement("sources")] public FileSet Resources { get { return _resources; } set { _resources = value; } } /// /// The set of compiled resources to embed. /// /// /// Do not yet expose this to build authors. /// public EmbeddedResourceCollection EmbeddedResources { get { return _embeddedResources; } set { _embeddedResources = value; } } /// /// Indicates whether the assembly linker for a given target framework /// supports the "template" option, which takes an assembly from which /// to get all options except the culture field. /// The default is . /// /// /// TODO: remove this once Mono bug #74814 is fixed. /// [FrameworkConfigurable("supportstemplate")] public bool SupportsTemplate { get { return _supportsTemplate; } set { _supportsTemplate = value; } } /// /// Specifies an assembly from which to get all options except the /// culture field. /// /// /// The complete path to the assembly template. /// /// /// /// Corresponds with the /template: flag. /// /// [TaskAttribute("template", Required=false)] public FileInfo TemplateFile { get { return _templateFile; } set { _templateFile = value; } } /// /// Specifies a string for the Title field in the assembly. /// /// /// A string for the Title field in the assembly. /// [TaskAttribute("title", Required=false)] public string Title { get { return _title; } set { _title = value; } } /// /// Specifies a string for the Trademark field in the assembly. /// /// /// A string for the Trademark field in the assembly. /// [TaskAttribute("trademark", Required=false)] public string Trademark { get { return _trademark; } set { _trademark = value; } } /// /// Specifies version information for the assembly. The format of the /// version string is major.minor.build.revision. /// [TaskAttribute("version", Required=false)] public string Version { get { return _version; } set { _version = StringUtils.ConvertEmptyToNull(value); } } /// /// Icon to associate with the assembly. /// [TaskAttribute("win32icon", Required=false)] public FileInfo Win32Icon { get { return _win32Icon; } set { _win32Icon = value; } } /// /// Inserts a Win32 resource (.res file) in the output file. /// [TaskAttribute("win32res", Required=false)] public FileInfo Win32Res { get { return _win32Res; } set { _win32Res = value; } } #endregion Public Instance Properties #region Override implementation of ExternalProgramBase /// /// Gets the command-line arguments for the external program. /// /// /// The command-line arguments for the external program or /// if the task is not being executed. /// public override string ProgramArguments { get { if (_responseFileName != null) { return "@" + "\"" + _responseFileName + "\""; } else { return null; } } } /// /// Generates an assembly manifest. /// protected override void ExecuteTask() { // ensure base directory is set, even if fileset was not initialized // from XML if (Resources.BaseDirectory == null) { Resources.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } if (NeedsCompiling()) { // create temp response file to hold compiler options _responseFileName = Path.GetTempFileName(); StreamWriter writer = new StreamWriter(_responseFileName); try { Log(Level.Info, ResourceUtils.GetString("String_CompilingFiles"), Resources.FileNames.Count + EmbeddedResources.Count, OutputFile.FullName); // write output target writer.WriteLine("/target:\"{0}\"", OutputTarget); // write output file writer.WriteLine("/out:\"{0}\"", OutputFile.FullName); // algorithm (in hexadecimal) if (AlgorithmID != null) { writer.WriteLine("/algid:\"{0}\"", AlgorithmID); } // company field if (Company != null) { writer.WriteLine("/company:\"{0}\"", Company); } // configuration field if (Configuration != null) { writer.WriteLine("/configuration:\"{0}\"", Configuration); } // copyright field if (Copyright != null) { writer.WriteLine("/copyright:\"{0}\"", Copyright); } // write culture associated with output assembly if (Culture != null) { writer.WriteLine("/culture:\"{0}\"", Culture); } // delay sign the assembly if (DelaySign) { writer.WriteLine("/delaysign+"); } // description field if (Description != null) { writer.WriteLine("/description:\"{0}\"", Description); } // write path to security evidence file if (EvidenceFile != null) { writer.WriteLine("/evidence:\"{0}\"", EvidenceFile.FullName); } // file version field if (FileVersion != null) { writer.WriteLine("/fileversion:\"{0}\"", FileVersion); } // flags field if (Flags != null) { writer.WriteLine("/flags:\"{0}\"", Flags); } // main method if (MainMethod != null) { writer.WriteLine("/main:\"{0}\"", MainMethod); } // keycontainer if (KeyContainer != null) { writer.WriteLine("/keyname:\"{0}\"", KeyContainer); } // product field if (Product != null) { writer.WriteLine("/product:\"{0}\"", Product); } // product version field if (ProductVersion != null) { writer.WriteLine("/productversion:\"{0}\"", ProductVersion); } // write path to template assembly if (TemplateFile != null) { if (SupportsTemplate) { writer.WriteLine("/template:\"{0}\"", TemplateFile.FullName); } else { Log(Level.Warning, ResourceUtils.GetString("String_LinkerDoesNotSupportTemplateAssembly"), Project.TargetFramework.Description); } } // title field if (Title != null) { writer.WriteLine("/title:\"{0}\"", Title); } // trademark field if (Trademark != null) { writer.WriteLine("/trademark:\"{0}\"", Trademark); } // key file if (KeyFile != null) { writer.WriteLine("/keyfile:\"{0}\"", KeyFile.FullName); } // assembly version if (Version != null) { writer.WriteLine("/version:\"{0}\"", Version); } // win32 icon if (Win32Icon != null) { writer.WriteLine("/win32icon:\"{0}\"", Win32Icon.FullName); } // win32 resource if (Win32Res != null) { writer.WriteLine("/win32res:\"{0}\"", Win32Res.FullName); } // write embedded resources to response file foreach (string resourceFile in Resources.FileNames) { writer.WriteLine("/embed:\"{0}\"", resourceFile); } // write embedded resources to response file foreach (EmbeddedResource embeddedResource in EmbeddedResources) { writer.WriteLine("/embed:\"{0}\",{1}", embeddedResource.File, embeddedResource.ManifestResourceName); } // suppresses display of the sign-on banner writer.WriteLine("/nologo"); // make sure to close the response file otherwise contents // Will not be written to disk and ExecuteTask() will fail. writer.Close(); if (Verbose) { // display response file contents Log(Level.Verbose, ResourceUtils.GetString("String_ContentsOf"), _responseFileName); StreamReader reader = File.OpenText(_responseFileName); Log(Level.Verbose, reader.ReadToEnd()); reader.Close(); } // call base class to do the work base.ExecuteTask(); } finally { // make sure stream is closed or response file cannot be deleted writer.Close(); // make sure we delete response file even if an exception is thrown File.Delete(_responseFileName); // initialize name of response file _responseFileName = null; } } } #endregion Override implementation of ExternalProgramBase #region Protected Instance Methods /// /// Determines whether the assembly manifest needs compiling or is /// uptodate. /// /// /// if the assembly manifest needs compiling; /// otherwise, . /// protected virtual bool NeedsCompiling() { if (!OutputFile.Exists) { Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileDoesNotExist"), OutputFile.FullName); return true; } // check if (embedded)resources were updated string fileName = FileSet.FindMoreRecentLastWriteTime(Resources.FileNames, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } // check if evidence file was updated if (EvidenceFile != null) { fileName = FileSet.FindMoreRecentLastWriteTime(EvidenceFile.FullName, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check if template file was updated if (TemplateFile != null) { fileName = FileSet.FindMoreRecentLastWriteTime(TemplateFile.FullName, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check if key file was updated if (KeyFile != null) { fileName = FileSet.FindMoreRecentLastWriteTime(KeyFile.FullName, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check if win32 icon file was updated if (Win32Icon != null) { fileName = FileSet.FindMoreRecentLastWriteTime(Win32Icon.FullName, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check if win32 resource file was updated if (Win32Res != null) { fileName = FileSet.FindMoreRecentLastWriteTime(Win32Res.FullName, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check if embedded resource files were updated foreach (EmbeddedResource embeddedResource in EmbeddedResources) { fileName = FileSet.FindMoreRecentLastWriteTime(embeddedResource.File, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } } // check the arguments for /embed or /embedresource options StringCollection embeddedResourceFiles = new StringCollection(); foreach (Argument argument in Arguments) { if (argument.IfDefined && !argument.UnlessDefined) { string argumentValue = argument.Value; // check whether argument specifies resource file to embed if (argumentValue != null && (argumentValue.StartsWith("/embed:") || argumentValue.StartsWith("/embedresource:"))) { // determine path to resource file string path = argumentValue.Substring(argumentValue.IndexOf(':') + 1); int indexOfComma = path.IndexOf(','); if (indexOfComma != -1) { path = path.Substring(0, indexOfComma); } bool isQuoted = path.Length > 2 && path.StartsWith("\"") && path.EndsWith("\""); if (isQuoted) { path = path.Substring(1, path.Length - 2); } // resolve path to full path (relative to project base dir) path = Project.GetFullPath(path); // add path to collection of resource files embeddedResourceFiles.Add(path); } } } // check if embedded resources passed as arguments were updated fileName = FileSet.FindMoreRecentLastWriteTime(embeddedResourceFiles, OutputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } // if we made it here then we don't have to recompile return false; } #endregion Protected Instance Methods } }