// NAnt - A .NET build tool
// Copyright (C) 2001-2002 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
//
// Gerry Shaw (gerry_shaw@yahoo.com)
// Mike Krueger (mike@icsharpcode.net)
// Ian MacLean (ian_maclean@another.com)
// Giuseppe Greco (giuseppe.greco@agamura.com)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Tasks;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.DotNet.Types;
namespace NAnt.DotNet.Tasks {
///
/// Provides the abstract base class for compiler tasks.
///
public abstract class CompilerBase : ExternalProgramBase {
#region Private Instance Fields
private string _responseFileName;
private FileInfo _outputFile;
private string _target;
private bool _debug;
private string _define;
private FileInfo _win32icon;
private bool _warnAsError;
private WarningAsError _warningAsError = new WarningAsError();
private string _noWarn;
private CompilerWarningCollection _suppressWarnings = new CompilerWarningCollection();
private bool _forceRebuild;
private string _mainType;
private string _keyContainer;
private FileInfo _keyFile;
private AssemblyFileSet _references = new AssemblyFileSet();
private FileSet _lib = new FileSet();
private AssemblyFileSet _modules = new AssemblyFileSet();
private FileSet _sources = new FileSet();
private ResourceFileSetCollection _resourcesList = new ResourceFileSetCollection();
private PackageCollection _packages = new PackageCollection();
// framework configuration settings
private bool _supportsPackageReferences;
private bool _supportsWarnAsErrorList;
private bool _supportsNoWarnList;
private bool _supportsKeyContainer;
private bool _supportsKeyFile;
#endregion Private Instance Fields
#region Protected Static Fields
///
/// Contains a list of extensions for all file types that should be treated as
/// 'code-behind' when looking for resources. Ultimately this will determine
/// if we use the "namespace+filename" or "namespace+classname" algorithm, since
/// code-behind will use the "namespace+classname" algorithm.
///
protected static string[] CodebehindExtensions = {".aspx", ".asax", ".ascx", ".asmx"};
///
/// Case-insensitive list of valid culture names for this platform.
///
///
/// The key of the is the culture name and
/// the value is .
///
protected readonly static Hashtable CultureNames;
#endregion Protected Static Fields
#region Static Constructor
///
/// Class constructor for .
///
static CompilerBase() {
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
// initialize hashtable to necessary size
CultureNames = CollectionsUtil.CreateCaseInsensitiveHashtable(
allCultures.Length);
// fill the culture list
foreach (CultureInfo ci in allCultures) {
CultureNames[ci.Name] = null;
}
}
#endregion Static Constructor
#region Public Instance Properties
///
/// Generate debug output. The default is .
///
///
/// Only used for <jsc> tasks, but retained for backward
/// compatibility (Clover.NET).
///
[TaskAttribute("debug")]
[BooleanValidator()]
public virtual bool Debug {
get { return _debug; }
set { _debug = value; }
}
///
/// The output file created by the compiler.
///
[TaskAttribute("output", Required=true)]
public FileInfo OutputFile {
get { return _outputFile; }
set { _outputFile = value; }
}
///
/// Output type. Possible values are exe, winexe,
/// library or module.
///
[TaskAttribute("target", Required=true)]
[StringValidator(AllowEmpty=false)]
public string OutputTarget {
get { return _target; }
set { _target = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Define conditional compilation symbol(s).
///
///
///
/// Corresponds to /d[efine]: flag.
///
///
[TaskAttribute("define")]
public string Define {
get { return _define; }
set { _define = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Icon to associate with the application.
///
///
///
/// Corresponds to /win32icon: flag.
///
///
[TaskAttribute("win32icon")]
public FileInfo Win32Icon {
get { return _win32icon; }
set { _win32icon = value; }
}
///
/// Instructs the compiler to treat all warnings as errors. The default
/// is .
///
///
///
/// Corresponds to the /warnaserror[+|-] flag of the compiler.
///
///
/// When this property is set to , any messages
/// that would ordinarily be reported as warnings will instead be
/// reported as errors.
///
///
[TaskAttribute("warnaserror")]
[BooleanValidator()]
public bool WarnAsError {
get { return _warnAsError; }
set { _warnAsError = value; }
}
///
/// Controls which warnings should be reported as errors.
///
[BuildElement("warnaserror")]
public virtual WarningAsError WarningAsError {
get { return _warningAsError; }
}
///
/// Specifies a comma-separated list of warnings that should be suppressed
/// by the compiler.
///
///
/// Comma-separated list of warnings that should be suppressed by the
/// compiler.
///
///
///
/// Corresponds with the /nowarn flag.
///
///
[TaskAttribute("nowarn")]
[Obsolete("Use the element instead.", false)]
public virtual string NoWarn {
get { return _noWarn; }
set { _noWarn = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Specifies a list of warnings that you want the compiler to suppress.
///
[BuildElementCollection("nowarn", "warning")]
public virtual CompilerWarningCollection SuppressWarnings {
get { return _suppressWarnings; }
}
///
/// Instructs NAnt to recompile the output file regardless of the file timestamps.
///
///
/// When this parameter is to , NAnt will always
/// run the compiler to rebuild the output file, regardless of the file timestamps.
///
[TaskAttribute("rebuild")]
[BooleanValidator()]
public bool ForceRebuild {
get { return _forceRebuild; }
set { _forceRebuild = value; }
}
///
/// Specifies which type contains the Main method that you want to use
/// as the entry point into the program.
///
///
///
/// Corresponds to the /m[ain]: flag of the compiler.
///
///
/// Use this property when creating an executable file. If this property
/// is not set, the compiler searches for a valid Main method in all
/// public classes.
///
///
[TaskAttribute("main")]
public string MainType {
get { return _mainType; }
set { _mainType = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Specifies the key pair container used to strongname the assembly.
///
[TaskAttribute("keycontainer")]
public virtual string KeyContainer {
get { return _keyContainer; }
set { _keyContainer = StringUtils.ConvertEmptyToNull(value); }
}
///
/// Specifies a strong name key file.
///
[TaskAttribute("keyfile")]
public virtual FileInfo KeyFile {
get { return _keyFile; }
set { _keyFile = value; }
}
///
/// Additional directories to search in for assembly references.
///
///
///
/// Corresponds with the /lib[path]: flag.
///
///
[BuildElement("lib")]
[Obsolete("Use the element in and instead.", false)]
public FileSet Lib {
get { return _lib; }
set {_lib = value; }
}
///
/// Reference metadata from the specified assembly files.
///
[BuildElement("references")]
public AssemblyFileSet References {
get { return _references; }
set { _references = value; }
}
///
/// Specifies list of packages to reference.
///
[BuildElementCollection("pkg-references", "package")]
public virtual PackageCollection Packages {
get { return _packages; }
set { _packages = value; }
}
///
/// Resources to embed.
///
///
///
/// This can be a combination of resx files and file resources.
///
///
/// .resx files will be compiled by and then
/// embedded into the resulting executable.
///
///
/// The property is used to make
/// up the resource name added to the assembly manifest for non-resx
/// files.
///
///
/// For .resx files the namespace from the matching source file is used
/// as prefix. This matches the behaviour of Visual Studio.
///
///
/// Multiple resources tags with different namespace prefixes may be
/// specified.
///
///
[BuildElementArray("resources")]
public ResourceFileSetCollection ResourcesList {
get { return _resourcesList; }
}
///
/// Link the specified modules into this assembly.
///
[BuildElement("modules")]
public virtual AssemblyFileSet Modules {
get { return _modules; }
set { _modules = value; }
}
///
/// The set of source files for compilation.
///
[BuildElement("sources", Required=true)]
public FileSet Sources {
get { return _sources; }
set { _sources = value; }
}
///
/// Indicates whether package references are supported by compiler for
/// a given target framework. The default is .
///
[FrameworkConfigurable("supportspackagereferences")]
public virtual bool SupportsPackageReferences {
get { return _supportsPackageReferences; }
set { _supportsPackageReferences = value; }
}
///
/// Indicates whether the compiler for a given target framework supports
/// the "warnaserror" option that takes a list of warnings. The default
/// is .
///
[FrameworkConfigurable("supportswarnaserrorlist")]
public virtual bool SupportsWarnAsErrorList {
get { return _supportsWarnAsErrorList; }
set { _supportsWarnAsErrorList = value; }
}
///
/// Indicates whether the compiler for a given target framework supports
/// a command line option that allows a list of warnings to be
/// suppressed. The default is .
///
[FrameworkConfigurable("supportsnowarnlist")]
public virtual bool SupportsNoWarnList {
get { return _supportsNoWarnList; }
set { _supportsNoWarnList = value; }
}
///
/// Indicates whether the compiler for a given target framework supports
/// the "keycontainer" option. The default is .
///
[FrameworkConfigurable("supportskeycontainer")]
public virtual bool SupportsKeyContainer {
get { return _supportsKeyContainer; }
set { _supportsKeyContainer = value; }
}
///
/// Indicates whether the compiler for a given target framework supports
/// the "keyfile" option. The default is .
///
[FrameworkConfigurable("supportskeyfile")]
public virtual bool SupportsKeyFile {
get { return _supportsKeyFile; }
set { _supportsKeyFile = value; }
}
#endregion Public Instance Properties
#region Protected Instance Properties
///
/// Gets the file extension required by the current compiler.
///
///
/// The file extension required by the current compiler.
///
public abstract string Extension {
get;
}
///
/// Gets the class name regular expression for the language of the current compiler.
///
/// class name regular expression for the language of the current compiler
protected abstract Regex ClassNameRegex {
get;
}
///
/// Gets the namespace regular expression for the language of the current compiler.
///
/// namespace regular expression for the language of the current compiler
protected abstract Regex NamespaceRegex {
get;
}
#endregion Protected Instance Properties
#region Override implementation of ExternalProgramBase
///
/// Gets the command-line arguments for the external program.
///
///
/// The command-line arguments for the external program.
///
public override string ProgramArguments {
get { return "@" + "\"" + _responseFileName + "\""; }
}
///
/// Compiles the sources and resources.
///
protected override void ExecuteTask() {
if (NeedsCompiling()) {
// create temp response file to hold compiler options
_responseFileName = Path.GetTempFileName();
StreamWriter writer = new StreamWriter(_responseFileName);
// culture names are not case-sensitive
Hashtable cultureResources = CollectionsUtil.CreateCaseInsensitiveHashtable();
// will hold temporary compiled resources
StringCollection compiledResourceFiles = new StringCollection();
try {
// ensure base directory is set, even if fileset was not initialized
// from XML
if (References.BaseDirectory == null) {
References.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (Lib.BaseDirectory == null) {
Lib.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (Modules.BaseDirectory == null) {
Modules.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (Sources.BaseDirectory == null) {
Sources.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
// copy lib path details across to the children Assembly filesets
foreach(string directoryName in Lib.DirectoryNames){
References.Lib.DirectoryNames.Add(directoryName);
Modules.Lib.DirectoryNames.Add(directoryName);
}
// rescan to ensure correct assembly resolution
References.Scan();
Modules.Scan();
Log(Level.Info, ResourceUtils.GetString("String_CompilingFiles"),
Sources.FileNames.Count, OutputFile.FullName);
// specific compiler options
WriteOptions(writer);
// suppresses display of the sign-on banner
WriteOption(writer, "nologo");
// specify output file format
WriteOption(writer, "target", OutputTarget);
WriteConditionalCompilationConstants(writer);
// the name of the output file
WriteOption(writer, "out", OutputFile.FullName);
if (Win32Icon != null) {
WriteOption(writer, "win32icon", Win32Icon.FullName);
}
// writes the option that specifies the class containing
// the Main method that should be called when the program
// starts.
if (MainType != null) {
WriteOption(writer, "main", MainType);
}
if (KeyContainer != null) {
if (SupportsKeyContainer) {
WriteOption(writer, "keycontainer", KeyContainer);
} else {
Log(Level.Warning, ResourceUtils.GetString("String_CompilerDoesNotSupportKeyContainer"),
Project.TargetFramework.Description);
}
}
if (KeyFile != null) {
if (SupportsKeyFile) {
WriteOption(writer, "keyfile", KeyFile.FullName);
} else {
Log(Level.Warning, ResourceUtils.GetString("String_CompilerDoesNotSupportKeyFile"),
Project.TargetFramework.Description);
}
}
// writes package references to the response file
WritePackageReferences(writer);
// write warnings to (not) treat as errors to the response file
WriteWarningsAsError(writer);
// write list of warnings to suppress
WriteNoWarnList(writer);
// writes assembly references to the response file
foreach (string fileName in References.FileNames) {
WriteOption(writer, "reference", fileName);
}
// writes module references to the response file
WriteModuleReferences(writer);
// compile resources
foreach (ResourceFileSet resources in ResourcesList) {
// resx files
if (resources.ResxFiles.FileNames.Count > 0) {
// compile the resx files to .resources files in the
// same dir as the input files
CompileResxResources(resources.ResxFiles.FileNames);
// Resx args
foreach (string fileName in resources.ResxFiles.FileNames) {
// determine manifest resource name
string manifestResourceName = this.GetManifestResourceName(
resources, fileName);
// determine the filenames of the .resources file
// generated by the task
string tmpResourcePath = Path.ChangeExtension(fileName, ".resources");
compiledResourceFiles.Add(tmpResourcePath);
// check if resource is localized
CultureInfo resourceCulture = CompilerBase.GetResourceCulture(fileName,
Path.ChangeExtension(fileName, Extension));
if (resourceCulture != null) {
if (!cultureResources.ContainsKey(resourceCulture.Name)) {
// initialize collection for holding
// resource file for this culture
cultureResources.Add(resourceCulture.Name, new Hashtable());
}
// store resulting .resources file for later linking
((Hashtable) cultureResources[resourceCulture.Name])[manifestResourceName] = tmpResourcePath;
} else {
// regular embedded resources (using filename and manifest resource name).
string resourceoption = string.Format(CultureInfo.InvariantCulture, "{0},{1}", tmpResourcePath, manifestResourceName);
// write resource option to response file
WriteOption(writer, "resource", resourceoption);
}
}
}
// other resources
foreach (string fileName in resources.NonResxFiles.FileNames) {
// determine manifest resource name
string manifestResourceName = this.GetManifestResourceName(
resources, fileName);
// check if resource is localized
CultureInfo resourceCulture = CompilerBase.GetResourceCulture(fileName,
Path.ChangeExtension(fileName, Extension));
if (resourceCulture != null) {
if (!cultureResources.ContainsKey(resourceCulture.Name)) {
// initialize collection for holding
// resource file for this culture
cultureResources.Add(resourceCulture.Name, new Hashtable());
}
// store resource filename for later linking
((Hashtable) cultureResources[resourceCulture.Name])[manifestResourceName] = fileName;
} else {
string resourceoption = string.Format(CultureInfo.InvariantCulture,
"{0},{1}",fileName, manifestResourceName);
WriteOption(writer, "resource", resourceoption);
}
}
}
// write sources to compile to response file
foreach (string fileName in Sources.FileNames) {
writer.WriteLine("\"" + fileName + "\"");
}
// 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.Info, ResourceUtils.GetString("String_ContentsOf"), _responseFileName);
StreamReader reader = File.OpenText(_responseFileName);
Log(Level.Info, reader.ReadToEnd());
reader.Close();
}
// call base class to do the work
base.ExecuteTask();
// create a satellite assembly for each culture name
foreach (string culture in cultureResources.Keys) {
// determine directory for satellite assembly
string culturedir = Path.Combine(OutputFile.DirectoryName, culture);
// ensure diretory for satellite assembly exists
Directory.CreateDirectory(culturedir);
// determine filename of satellite assembly
FileInfo outputFile = new FileInfo(Path.Combine(culturedir,
Path.GetFileNameWithoutExtension(OutputFile.Name)
+ ".resources.dll"));
// generate satellite assembly
LinkResourceAssembly((Hashtable)cultureResources[culture],
outputFile, culture);
}
} finally {
// cleanup .resource files
foreach (string compiledResourceFile in compiledResourceFiles) {
File.Delete(compiledResourceFile);
}
// 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 ExternalProgramBase
#region Public Instance Methods
///
/// Determines the manifest resource name of the given resource file.
///
/// The containing information that will used to assemble the manifest resource name.
/// The resource file of which the manifest resource name should be determined.
/// The logical location of the resource file.
/// The source file on which the resource file depends.
///
/// The manifest resource name of the specified resource file.
///
public string GetManifestResourceName(ResourceFileSet resources, string resourcePhysicalFile, string resourceLogicalFile, string dependentFile) {
if (resources == null) {
throw new ArgumentNullException("resources");
}
if (resourcePhysicalFile == null) {
throw new ArgumentNullException("resourcePhysicalFile");
}
if (resourceLogicalFile == null) {
throw new ArgumentNullException("resourceLogicalFile");
}
// make sure the resource file exists
if (!File.Exists(resourcePhysicalFile)) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2009"), resourcePhysicalFile),
Location);
}
// will hold the manifest resource name
string manifestResourceName = null;
// check if we're dealing with a localized resource
CultureInfo resourceCulture = CompilerBase.GetResourceCulture(
resourceLogicalFile, dependentFile);
// determine the resource type
switch (Path.GetExtension(resourcePhysicalFile).ToLower(CultureInfo.InvariantCulture)) {
case ".resx":
// try and get manifest resource name from dependent file
ResourceLinkage resourceLinkage = GetResourceLinkage(
dependentFile, resourceCulture);
if (resourceLinkage == null || !resourceLinkage.IsValid) {
// no resource linkage could be determined (no dependent
// file or dependent file does not exist) or dependent
// file is no (valid) source file
manifestResourceName = Path.ChangeExtension(
resources.GetManifestResourceName(resourcePhysicalFile,
resourceLogicalFile), "resources");
} else {
if (!resourceLinkage.HasClassName) {
// use filename of resource file to determine class name
string className = Path.GetFileNameWithoutExtension(
resourcePhysicalFile);
// cater for asax/aspx special cases. eg. a resource file
// named "WebForm1.aspx(.resx)" will here be transformed to
// "WebForm1"
// we assume that the class name of a codebehind file
// is equal to the file name of that codebehind file
// (without extension)
if (Path.GetExtension(className) != string.Empty) {
string codebehindExtension = Path.GetExtension(
className).ToLower(CultureInfo.InvariantCulture);
foreach (string extension in CodebehindExtensions) {
if (extension == codebehindExtension) {
className = Path.GetFileNameWithoutExtension(
className);
break;
}
}
}
resourceLinkage.ClassName = className;
}
// ensure we have information necessary to determine the
// manifest resource name
if (resourceLinkage.IsValid) {
manifestResourceName = resourceLinkage.ToString()
+ ".resources";
} else {
// we should actually never get here, but just in case ...
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2010"), resourcePhysicalFile), Location);
}
}
break;
case ".resources":
// determine resource name, and leave culture information
// in manifest resource name
manifestResourceName = resources.GetManifestResourceName(
resourcePhysicalFile, resourceLogicalFile);
break;
default:
// VS.NET handles an embedded resource file named licenses.licx
// in the root of the project and without culture in a special
// way
if (Path.GetFileName(resourcePhysicalFile) == "licenses.licx") {
// the manifest resource name will be