// NAnt - A .NET build tool
// Copyright (C) 2002-2003 Gordon Weakliem
//
// 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
//
// Gordon Weakliem (gweakliem@oddpost.com)
// Gert Driesen (gert.driesen@ardatis.com)
// Ian MacLean (ian_maclean@another.com)
// Giuseppe Greco (giuseppe.greco@agamura.com)
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting.Lifetime;
using System.Security.Cryptography;
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 {
///
/// Generates an AssemblyInfo file using the attributes given.
///
///
///
/// Create a C# AssemblyInfo file containing the specified assembly-level
/// attributes.
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
///
///
/// Create a C# AssemblyInfo file containing an attribute with multiple
/// named properties by setting the
/// attribute on the element to
/// .
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("asminfo")]
[Serializable()]
public class AssemblyInfoTask : Task {
#region Private Instance Fields
private FileInfo _output;
private CodeLanguage _language = CodeLanguage.CSharp;
private AssemblyAttributeCollection _attributes = new AssemblyAttributeCollection();
private NamespaceImportCollection _imports = new NamespaceImportCollection();
private AssemblyFileSet _references = new AssemblyFileSet();
#endregion Private Instance Fields
#region Public Instance Properties
///
/// Name of the AssemblyInfo file to generate.
///
///
/// The name of the AssemblyInfo file to generate.
///
[TaskAttribute("output", Required=true)]
public FileInfo Output {
get { return _output; }
set { _output = value; }
}
///
/// The code language in which the AssemblyInfo file should be
/// generated.
///
[TaskAttribute("language", Required=true)]
[StringValidator(AllowEmpty=false)]
public CodeLanguage Language {
get { return _language; }
set {
if (!Enum.IsDefined(typeof(CodeLanguage), value)) {
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2002"), value));
} else {
_language = value;
}
}
}
///
/// The assembly-level attributes to generate.
///
///
/// The assembly-level attributes to generate.
///
[BuildElementCollection("attributes", "attribute")]
public AssemblyAttributeCollection AssemblyAttributes {
get { return _attributes; }
}
///
/// The namespaces to import.
///
///
/// The namespaces to import.
///
[BuildElement("imports")]
public NamespaceImportCollection Imports {
get { return _imports; }
set { _imports = value; }
}
///
/// Assembly files used to locate the types of the specified attributes.
///
[BuildElement("references")]
public AssemblyFileSet References {
get { return _references; }
set { _references = value; }
}
#endregion Public Instance Properties
#region Override implementation of Task
///
/// Generates an AssemblyInfo file.
///
protected override void ExecuteTask() {
try {
StringCollection imports = new StringCollection();
foreach (NamespaceImport import in Imports) {
if (import.IfDefined && !import.UnlessDefined) {
imports.Add(import.Namespace);
}
}
// ensure base directory is set, even if fileset was not initialized
// from XML
if (References.BaseDirectory == null) {
References.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
// write out code to memory stream, so we can compare it later
// to what is already present (if necessary)
MemoryStream generatedAsmInfoStream = new MemoryStream();
using (StreamWriter writer = new StreamWriter(generatedAsmInfoStream, Encoding.Default)) {
// create new instance of CodeProviderInfo for specified CodeLanguage
CodeProvider codeProvider = new CodeProvider(this, Language);
// only generate imports here for C#, for VB we create the
// imports as part of the assembly attributes compile unit
if (Language == CodeLanguage.CSharp) {
// generate imports code
codeProvider.GenerateImportCode(imports, writer);
}
// generate code for assembly attributes
codeProvider.GenerateAssemblyAttributesCode(AssemblyAttributes,
imports, References.FileNames, writer);
// flush
writer.Flush();
// check whether generated source should be persisted
if (NeedsPersisting(generatedAsmInfoStream)) {
using (FileStream fs = new FileStream(Output.FullName, FileMode.Create, FileAccess.Write)) {
byte[] buffer = generatedAsmInfoStream.ToArray();
fs.Write(buffer, 0, buffer.Length);
fs.Flush();
fs.Close();
generatedAsmInfoStream.Close();
}
Log(Level.Info, ResourceUtils.GetString("String_GeneratedFile"),
Output.FullName);
} else {
Log(Level.Verbose, ResourceUtils.GetString("String_FileUpToDate"),
Output.FullName);
}
}
} catch (Exception ex) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2004"), Output.FullName), Location, ex);
}
}
#endregion Override implementation of Task
#region Private Instance Methods
///
/// Determines whether the specified AssemblyInfo file in the given
/// needs to be persisted.
///
/// holding the newly generated AssemblyInfo source.
///
/// if the generated AssemblyInfo source needs
/// to be persisted; otherwise, .
///
private bool NeedsPersisting(Stream generatedAsmInfoStream) {
// if output file doesn't exist, the stream will always need to be
// persisted to the filesystem.
if (!Output.Exists) {
Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileDoesNotExist"),
Output.FullName);
return true;
}
byte[] existingAssemblyInfoHash = null;
byte[] generatedAssemblyInfoHash = null;
SHA1 hasher = new SHA1CryptoServiceProvider();
// hash existing AssemblyInfo source
using (FileStream fs = new FileStream(Output.FullName, FileMode.Open, FileAccess.Read)) {
existingAssemblyInfoHash = hasher.ComputeHash(fs);
}
// hash generated AssemblyInfo source
generatedAsmInfoStream.Position = 0;
generatedAssemblyInfoHash = hasher.ComputeHash(generatedAsmInfoStream);
// release all resources
hasher.Clear();
//compare hash of generated source with of existing source
if (Convert.ToBase64String(generatedAssemblyInfoHash) != Convert.ToBase64String(existingAssemblyInfoHash)) {
Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileNotUpToDate"),
Output.FullName);
return true;
} else {
return false;
}
}
#endregion Private Instance Methods
///
/// Defines the supported code languages for generating an AssemblyInfo
/// file.
///
public enum CodeLanguage : int {
///
/// A value for generating C# code.
///
CSharp = 0,
///
/// A value for generating JScript code.
///
JScript = 1,
///
/// A value for generating Visual Basic code.
///
VB = 2,
}
///
/// Encapsulates functionality to generate a code file with imports
/// and assembly-level attributes.
///
internal class CodeProvider {
#region Private Instance Fields
private readonly CodeLanguage _language;
private readonly ICodeGenerator _generator;
#endregion Private Instance Fields
#region Public Instance Constructors
///
/// Initializes a new instance of the
/// for the specified .
///
/// The for which an instance of the class should be initialized.
/// The for which an instance of the class should be initialized.
public CodeProvider(AssemblyInfoTask assemblyInfoTask, CodeLanguage codeLanguage) {
CodeDomProvider provider = null;
switch (codeLanguage) {
case CodeLanguage.CSharp:
provider = new Microsoft.CSharp.CSharpCodeProvider();
break;
case CodeLanguage.JScript:
throw new NotSupportedException(ResourceUtils.GetString("NA2008"));
case CodeLanguage.VB:
provider = new Microsoft.VisualBasic.VBCodeProvider();
break;
default:
throw new NotSupportedException(ResourceUtils.GetString("NA2007"));
}
_generator = provider.CreateGenerator();
_language = codeLanguage;
}
#endregion Public Instance Constructors
#region Private Instance Properties
///
/// Gets the in which the AssemblyInfo
/// code will be generated.
///
private CodeLanguage Language {
get { return _language; }
}
///
/// Gets the that will be used to
/// generate the AssemblyInfo code.
///
private ICodeGenerator Generator {
get { return _generator; }
}
#endregion Private Instance Properties
#region Public Instance Methods
///
/// Generates code for the specified imports.
///
/// The imports for which code should be generated.
/// The to which the generated code will be written.
public void GenerateImportCode(StringCollection imports, TextWriter writer) {
CodeNamespace codeNamespace = new CodeNamespace();
foreach (string import in imports) {
codeNamespace.Imports.Add(new CodeNamespaceImport(import));
}
Generator.GenerateCodeFromNamespace(codeNamespace, writer, new CodeGeneratorOptions());
}
///
/// Generates code for the specified assembly attributes.
///
/// The assembly attributes for which code should be generated.
/// Imports used to resolve the assembly attribute names to fully qualified type names.
/// Assembly that will be used to resolve the attribute names to instances.
/// The to which the generated code will be written.
public void GenerateAssemblyAttributesCode(AssemblyAttributeCollection assemblyAttributes, StringCollection imports, StringCollection assemblies, TextWriter writer) {
CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
// for C# the imports were already generated, as the # generator
// will otherwise output the imports after the assembly attributes
if (Language == CodeLanguage.VB) {
CodeNamespace codeNamespace = new CodeNamespace();
foreach (string import in imports) {
codeNamespace.Imports.Add(new CodeNamespaceImport(import));
}
codeCompileUnit.Namespaces.Add(codeNamespace);
}
foreach (AssemblyAttribute assemblyAttribute in assemblyAttributes) {
if (assemblyAttribute.IfDefined && !assemblyAttribute.UnlessDefined) {
// create new assembly-level attribute
CodeAttributeDeclaration codeAttributeDeclaration = new CodeAttributeDeclaration(assemblyAttribute.TypeName);
if (assemblyAttribute.AsIs) {
codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodeSnippetExpression(assemblyAttribute.Value)));
} else {
// convert string value to type expected by attribute constructor
object typedValue = GetTypedValue(assemblyAttribute, assemblies, imports);
if (typedValue != null) {
// add typed value to attribute arguments
codeAttributeDeclaration.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(typedValue)));
}
}
// add assembly-level argument to code compile unit
codeCompileUnit.AssemblyCustomAttributes.Add(codeAttributeDeclaration);
}
}
Generator.GenerateCodeFromCompileUnit(codeCompileUnit, writer, new CodeGeneratorOptions());
}
#endregion Public Instance Methods
#region Private Instance Methods
private object GetTypedValue(AssemblyAttribute attribute, StringCollection assemblies, StringCollection imports) {
// locate type assuming TypeName is fully qualified typename
AppDomain newDomain = AppDomain.CreateDomain("TypeGatheringDomain",
AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation);
TypedValueGatherer typedValueGatherer = (TypedValueGatherer)
newDomain.CreateInstanceAndUnwrap(typeof(TypedValueGatherer).Assembly.FullName,
typeof(TypedValueGatherer).FullName, false, BindingFlags.Public | BindingFlags.Instance,
null, new object[0], CultureInfo.InvariantCulture, new object[0],
AppDomain.CurrentDomain.Evidence);
object typedValue = typedValueGatherer.GetTypedValue(
assemblies, imports, attribute.TypeName, attribute.Value);
// unload newly created AppDomain
AppDomain.Unload(newDomain);
return typedValue;
}
#endregion Private Instance Methods
}
///
/// Responsible for returning the specified value converted to a
/// accepted by a constructor for a given
/// .
///
private class TypedValueGatherer : MarshalByRefObject {
#region Override implementation of MarshalByRefObject
///
/// Obtains a lifetime service object to control the lifetime policy for
/// this instance.
///
///
/// An object of type used to control the lifetime
/// policy for this instance. This is the current lifetime service object
/// for this instance if one exists; otherwise, a new lifetime service
/// object initialized with a lease that will never time out.
///
public override Object InitializeLifetimeService() {
ILease lease = (ILease) base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial) {
lease.InitialLeaseTime = TimeSpan.Zero;
}
return lease;
}
#endregion Override implementation of MarshalByRefObject
#region Public Instance Methods
///
/// Retrieves the specified corresponding with the specified
/// type name from a list of assemblies.
///
/// The collection of assemblies that the type should tried to be instantiated from.
/// The list of imports that can be used to resolve the typename to a full typename.
/// The typename that should be used to determine the type to which the specified value should be converted.
/// The value that should be converted to a typed value.
///
///
/// is and the identified by has no default public constructor.
/// -or-
/// cannot be converted to a value that's suitable for one of the constructors of the identified by .
/// -or-
/// The identified by has no suitable constructor.
/// -or-
/// A identified by could not be located or loaded.
///
public object GetTypedValue(StringCollection assemblies, StringCollection imports, string typename, string value) {
// create assembly resolver
AssemblyResolver assemblyResolver = new AssemblyResolver();
// attach assembly resolver to the current domain
assemblyResolver.Attach();
try {
Type type = null;
// load each assembly and try to get type from it
foreach (string assemblyFileName in assemblies) {
// load assembly from filesystem
Assembly assembly = Assembly.LoadFrom(assemblyFileName);
// try to load type from assembly
type = assembly.GetType(typename, false, false);
if (type == null) {
foreach (string import in imports) {
type = assembly.GetType(import + "." + typename, false, false);
if (type != null) {
break;
}
}
}
if (type != null) {
break;
}
}
// try to load type from all assemblies loaded from disk, if
// it was not loaded from the references assemblies
if (type == null) {
type = Type.GetType(typename, false, false);
if (type == null) {
foreach (string import in imports) {
type = Type.GetType(import + "." + typename, false, false);
if (type != null) {
break;
}
}
}
}
if (type != null) {
object typedValue = null;
if (value == null) {
ConstructorInfo defaultConstructor = type.GetConstructor(
BindingFlags.Public | BindingFlags.Instance, null,
new Type[0], new ParameterModifier[0]);
if (defaultConstructor != null) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2005"), type.FullName), Location.UnknownLocation);
}
typedValue = null;
} else {
ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
for (int counter = 0; counter < constructors.Length; counter++) {
ParameterInfo[] parameters = constructors[counter].GetParameters();
if (parameters.Length == 1) {
if (parameters[0].ParameterType.IsPrimitive || parameters[0].ParameterType == typeof(string)) {
try {
// convert value to type of constructor parameter
typedValue = Convert.ChangeType(value, parameters[0].ParameterType, CultureInfo.InvariantCulture);
break;
} catch (Exception ex) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture, ResourceUtils.GetString("NA2006"),
value, parameters[0].ParameterType.FullName, type.FullName),
Location.UnknownLocation, ex);
}
}
}
}
if (typedValue == null) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2003"), typename), Location.UnknownLocation);
}
}
return typedValue;
} else {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2001"),typename), Location.UnknownLocation);
}
} finally {
// detach assembly resolver from the current domain
assemblyResolver.Detach();
}
}
#endregion Public Instance Methods
}
}
}