// 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)
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
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.
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
///
[TaskName("asminfo")]
public class AssemblyInfoTask : Task {
#region Private Instance Fields
private string _output;
private CodeLanguage _language = CodeLanguage.CSharp;
private AssemblyAttributeCollection _attributes = new AssemblyAttributeCollection();
private NamespaceImportCollection _imports = new NamespaceImportCollection();
private FileSet _references = new FileSet();
#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)]
[StringValidator(AllowEmpty=false)]
public string Output {
get { return (_output != null) ? Project.GetFullPath(_output) : null; }
set { _output = StringUtils.ConvertEmptyToNull(value); }
}
///
/// The code language in which the AssemblyInfo file should be
/// generated - either or
/// .
///
///
/// 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,
"An invalid type {0} was specified.", 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.
///
[BuildElementCollection("imports", "import")]
public NamespaceImportCollection Imports {
get { return _imports; }
}
///
/// Assembly files used to locate the types of the specified attributes.
///
[BuildElement("references")]
public FileSet References {
get { return _references; }
set { _references = value; }
}
#endregion Public Instance Properties
#region Override implementation of Task
///
/// Generates an AssemblyInfo file.
///
protected override void ExecuteTask() {
if (References.BaseDirectory == null) {
References.BaseDirectory = Project.BaseDirectory;
}
try
{
StringCollection imports = new StringCollection();
foreach (NamespaceImport import in Imports)
{
if (import.IfDefined && !import.UnlessDefined)
{
imports.Add(import.Namespace);
}
}
// fix references to system assemblies
if (Project.CurrentFramework != null)
{
foreach (string pattern in References.Includes)
{
if (Path.GetFileName(pattern) == pattern)
{
string frameworkDir = Project.CurrentFramework.FrameworkAssemblyDirectory.FullName;
string localPath = Path.Combine(References.BaseDirectory, pattern);
string fullPath = Path.Combine(frameworkDir, pattern);
if (!File.Exists(localPath) && File.Exists(fullPath))
{
// found a system reference
References.FileNames.Add(fullPath);
}
}
}
}
//write out code to memory stream
//so we can compare it later to what is already present if necessary
MemoryStream ms = new MemoryStream();
StreamWriter writer = new StreamWriter(ms,System.Text.Encoding.Default);
// create new instance of CodeProviderInfo for specified CodeLanguage
CodeProvider codeProvider = new CodeProvider(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);
writer.Flush();
byte[] previousAsmHash = null;
byte[] genAsmHash = null;
if(File.Exists(Output))
{
SHA1 hasher = new SHA1CryptoServiceProvider();
//hash previous
using(FileStream fs = new FileStream(Output,FileMode.Open,FileAccess.Read))
{
previousAsmHash = hasher.ComputeHash(fs);
}
//hash generated
ms.Position = 0;
genAsmHash = hasher.ComputeHash(ms);
}
//compare what is generated with what is present
if(previousAsmHash != null
&& Convert.ToBase64String(genAsmHash) != Convert.ToBase64String(previousAsmHash))
{
using (FileStream fs = new FileStream(Output, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] buf = ms.ToArray();
fs.Write(buf,0,buf.Length);
fs.Flush();
fs.Close();
ms.Close();
}
}
else
{
Log(Level.Verbose, LogPrefix + "AssemblyInfo generated was not different than the file already present.");
}
}
catch (Exception ex)
{
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
"AssemblyInfo file '{0}' could not be generated.",
Output), Location, ex);
}
}
#endregion Override implementation of Task
///
/// 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 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.
public CodeProvider(CodeLanguage codeLanguage) {
CodeDomProvider provider = null;
switch (codeLanguage) {
case CodeLanguage.CSharp:
provider = new Microsoft.CSharp.CSharpCodeProvider();
break;
case CodeLanguage.JScript:
throw new NotSupportedException("Generating a JSCript AssemblyInfo file is not supported at this moment.");
case CodeLanguage.VB:
provider = new Microsoft.VisualBasic.VBCodeProvider();
break;
default:
throw new NotSupportedException("The specified code language is not supported.");
}
_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, new AppDomainSetup());
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 {
///
/// 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, AppDomain.CurrentDomain.Evidence);
// 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,
"Assembly attribute {0} has no default public constructor.",
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,
"Value '{0}' cannot be converted to type '{1}' of assembly attribute {2}.",
value, parameters[0].ParameterType.FullName, type.FullName),
Location.UnknownLocation, ex);
}
}
}
}
if (typedValue == null) {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
"Value of assembly attribute {0} cannot be set as it has no constructor accepting a primitive type or string.",
typename), Location.UnknownLocation);
}
}
return typedValue;
} else {
throw new BuildException(string.Format(
CultureInfo.InvariantCulture,
"Assembly attribute with type {0} could not be loaded.",
typename), Location.UnknownLocation);
}
} finally {
// detach assembly resolver from the current domain
assemblyResolver.Detach();
}
}
}
}
}