// NAnt - A .NET build tool
// Copyright (C) 2001-2004 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
//
// Matthew Mastracci (matt@aclaro.com)
// Scott Ford (sford@RJKTECH.com)
// Gert Driesen (gert.driesen@ardatis.com)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
#if NET_2_0
using System.Runtime.InteropServices.ComTypes;
#endif
using System.Xml;
using Microsoft.Win32;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Util;
using NAnt.Core.Types;
using NAnt.Win32.Tasks;
namespace NAnt.VSNet {
public abstract class WrapperReferenceBase : FileReferenceBase {
#region Protected Instance Constructors
protected WrapperReferenceBase(XmlElement xmlDefinition, ReferencesResolver referencesResolver, ProjectBase parent, GacCache gacCache) : base(xmlDefinition, referencesResolver, parent, gacCache) {
}
#endregion Protected Instance Constructors
#region Override implementation of ReferenceBase
///
/// Gets a value indicating whether the output file(s) of this reference
/// should be copied locally.
///
///
/// if the reference wraps a Primary Interop
/// Assembly; otherwise, .
///
public override bool CopyLocal {
get { return (WrapperTool != "primary"); }
}
///
/// Gets a value indicating whether this reference represents a system
/// assembly.
///
///
/// as none of the system assemblies are wrappers
/// or Primary Interop Assemblies anyway.
///
protected override bool IsSystem {
get { return false; }
}
///
/// Gets the path of the reference, without taking the "copy local"
/// setting into consideration.
///
/// The solution configuration that is built.
///
/// The output path of the reference.
///
public override string GetPrimaryOutputFile(string solutionConfiguration) {
return WrapperAssembly;
}
///
/// Gets the complete set of output files for the referenced project.
///
/// The solution configuration that is built.
/// The set of output files to be updated.
///
/// The key of the case-insensitive is the
/// full path of the output file and the value is the path relative to
/// the output directory.
///
public override void GetOutputFiles(string solutionConfiguration, Hashtable outputFiles) {
// obtain project configuration (corresponding with solution configuration)
ConfigurationBase config = (ConfigurationBase) Parent.BuildConfigurations[solutionConfiguration];
base.GetAssemblyOutputFiles(CreateWrapper(config), outputFiles);
}
///
/// Gets the complete set of assemblies that need to be referenced when
/// a project references this component.
///
/// The solution configuration that is built.
///
/// The complete set of assemblies that need to be referenced when a
/// project references this component.
///
public override StringCollection GetAssemblyReferences(string solutionConfiguration) {
// obtain project configuration (corresponding with solution configuration)
ConfigurationBase config = (ConfigurationBase) Parent.BuildConfigurations[solutionConfiguration];
// ensure wrapper is actually created
string assemblyFile = CreateWrapper(config);
if (!File.Exists(assemblyFile)) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Couldn't find assembly \"{0}\", referenced by project \"{1}\".",
assemblyFile, Parent.Name), Location.UnknownLocation);
}
// add referenced assembly to list of reference assemblies
StringCollection assemblyReferences = new StringCollection();
assemblyReferences.Add(assemblyFile);
return assemblyReferences;
}
///
/// Gets the timestamp of the reference.
///
/// The solution configuration that is built.
///
/// The timestamp of the reference.
///
public override DateTime GetTimestamp(string solutionConfiguration) {
return GetFileTimestamp(WrapperAssembly);
}
#endregion Override implementation of ReferenceBase
#region Public Instance Properties
///
/// Gets the name of the tool that should be used to create the
/// .
///
///
/// The name of the tool that should be used to create the
/// .
///
public abstract string WrapperTool {
get;
}
///
/// Gets the path of the wrapper assembly.
///
///
/// The path of the wrapper assembly.
///
///
/// The wrapper assembly is stored in the object directory of the
/// project.
///
public abstract string WrapperAssembly {
get;
}
///
/// Gets a value indicating whether the wrapper assembly has already been
/// created.
///
public bool IsCreated {
get { return _isCreated; }
}
#endregion Public Instance Properties
#region Protected Instance Properties
///
/// Gets the path of the Primary Interop Assembly.
///
///
/// The path of the Primary Interop Assembly, or
/// if not available.
///
protected abstract string PrimaryInteropAssembly {
get;
}
///
/// Gets the hex version of the type library as defined in the definition
/// of the reference.
///
///
/// The hex version of the type library.
///
protected abstract string TypeLibVersion {
get;
}
///
/// Gets the GUID of the type library as defined in the definition
/// of the reference.
///
///
/// The GUID of the type library.
///
protected abstract string TypeLibGuid {
get;
}
///
/// Gets the locale of the type library in hex notation.
///
///
/// The locale of the type library.
///
protected abstract string TypeLibLocale {
get;
}
///
/// Gets the name of the type library.
///
///
/// The name of the type library.
///
protected virtual string TypeLibraryName {
get {
return GetTypeLibraryName(GetTypeLibrary());
}
}
#endregion Protected Instance Properties
#region Protected Instance Methods
protected abstract void ImportTypeLibrary();
protected abstract void ImportActiveXLibrary();
protected string ResolveWrapperAssembly() {
string wrapperAssembly = null;
switch (WrapperTool) {
case "primary":
wrapperAssembly = PrimaryInteropAssembly;
if (wrapperAssembly == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Couldn't find Primary Interop Assembly \"{0}\","
+ " referenced by project \"{1}\".", Name, Parent.Name),
Location.UnknownLocation);
}
return wrapperAssembly;
case "aximp":
wrapperAssembly = "AxInterop." + TypeLibraryName + ".dll";
break;
default:
wrapperAssembly = "Interop." + TypeLibraryName + ".dll";
break;
}
// resolve to full path
return FileUtils.CombinePaths(Parent.ObjectDir.FullName,
wrapperAssembly);
}
protected string GetPrimaryInteropAssembly() {
string typeLibVersionKey = string.Format(CultureInfo.InvariantCulture,
@"TYPELIB\{0}\{1}", TypeLibGuid, TypeLibVersion);
string assemblyFile = null;
using (RegistryKey registryKey = Registry.ClassesRoot.OpenSubKey(typeLibVersionKey)) {
if (registryKey != null && registryKey.GetValue("PrimaryInteropAssemblyName") != null) {
string primaryInteropAssemblyName = (string) registryKey.GetValue("PrimaryInteropAssemblyName");
try {
// get filename of primary interop assembly
assemblyFile = ReferencesResolver.GetAssemblyFileName(
primaryInteropAssemblyName);
} catch (Exception ex) {
// only have build fail if we're actually dealing with a
// reference to a primary interop assembly
//
// certain tools (such as Office) register the name of
// the primary interop assembly of the typelib, but the
// actual primary interop assembly is not always installed
if (WrapperTool == "primary") {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Primary Interop Assembly \"{0}\", referenced by project"
+ " \"{1}\", could not be loaded.", primaryInteropAssemblyName,
Parent.Name), Location.UnknownLocation, ex);
}
}
}
}
return assemblyFile;
}
protected string GetTypeLibrary() {
string typeLibKey = string.Format(CultureInfo.InvariantCulture,
@"TYPELIB\{0}\{1}\{2}\win32", TypeLibGuid, TypeLibVersion,
TypeLibLocale);
using (RegistryKey registryKey = Registry.ClassesRoot.OpenSubKey(typeLibKey)) {
// TODO: if there's no direct match, then use a type library
// with the same major version, and the highest minor version
// TODO: check if the library identifier matches the one of the
// reference
if (registryKey == null) {
throw CreateTypeLibraryNotRegisteredException();
}
string typeLibValue = (string) registryKey.GetValue(null);
if (StringUtils.IsNullOrEmpty(typeLibValue)) {
throw CreateInvalidTypeLibraryRegistrationException();
}
// extract path to type library from reg value
string typeLibPath = TlbImpTask.ExtractTypeLibPath(typeLibValue);
// check if the typelib actually exists
if (!File.Exists(typeLibPath)) {
throw CreateTypeLibraryPathDoesNotExistException(typeLibPath);
}
return typeLibValue;
}
}
protected string GetTypeLibraryName(string typeLibraryPath) {
Object typeLib;
try {
LoadTypeLibEx(typeLibraryPath, 0, out typeLib);
} catch (COMException ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Type library \"{0}\" could not be loaded.", typeLibraryPath),
Location.UnknownLocation, ex);
}
if (typeLib == null) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Type library \"{0}\" could not be loaded.", typeLibraryPath),
Location.UnknownLocation);
}
#if NET_2_0
return Marshal.GetTypeLibName((ITypeLib) typeLib);
#else
return Marshal.GetTypeLibName((UCOMITypeLib) typeLib);
#endif
}
#endregion Protected Instance Methods
#region Private Instance Methods
private string CreateWrapper(ConfigurationBase config) {
// if wrapper assembly was created during the current build, then
// there's no need to create it again
if (IsCreated) {
return WrapperAssembly;
}
// synchronize build and output directory
Sync(config);
switch (WrapperTool) {
case "primary":
// nothing to do for Primary Interop Assembly
break;
case "tlbimp":
if (PrimaryInteropAssembly != null) {
// if tlbimp is defined as import tool, but a primary
// interop assembly is available, then output a
// warning
Log(Level.Warning, "The component \"{0}\", referenced by"
+ " project \"{1}\" has an updated custom wrapper"
+ " available.", Name, Parent.Name);
}
ImportTypeLibrary();
break;
case "aximp":
ImportActiveXLibrary();
break;
default:
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Wrapper tool \"{0}\" for reference \"{1}\" in project"
+ " \"{2}\" is not supported.", WrapperTool, Name,
Parent.Name), Location.UnknownLocation);
}
// mark wrapper as completed
_isCreated = true;
return WrapperAssembly;
}
///
/// Removes wrapper assembly from build directory, if wrapper assembly
/// no longer exists in output directory or is not in sync with build
/// directory, to force rebuild.
///
/// The project configuration.
private void Sync(ConfigurationBase config) {
if (!CopyLocal || !File.Exists(WrapperAssembly)) {
// nothing to synchronize
return;
}
// determine path where wrapper assembly should be deployed to
string outputFile = FileUtils.CombinePaths(config.OutputDir.FullName,
Path.GetFileName(WrapperAssembly));
// determine last modification date/time of built wrapper assembly
DateTime wrapperModTime = File.GetLastWriteTime(WrapperAssembly);
// rebuild wrapper assembly if output assembly is more recent,
// or have been removed (by the user) to force a rebuild
if (FileSet.FindMoreRecentLastWriteTime(outputFile, wrapperModTime) != null) {
// remove wrapper assembly to ensure a rebuild is performed
DeleteTask deleteTask = new DeleteTask();
deleteTask.Project = SolutionTask.Project;
deleteTask.Parent = SolutionTask;
deleteTask.InitializeTaskConfiguration();
deleteTask.File = new FileInfo(WrapperAssembly);
deleteTask.Threshold = Level.None; // no output in build log
deleteTask.Execute();
}
}
private BuildException CreateTypeLibraryNotRegisteredException() {
string msg = null;
if (StringUtils.IsNullOrEmpty(Name)) {
msg = string.Format(CultureInfo.InvariantCulture, "Couldn't" +
" find type library \"{0}\" with version {1}, referenced" +
" by project \"{2}\".", TypeLibGuid, TypeLibVersion,
Parent.Name);
} else {
msg = string.Format(CultureInfo.InvariantCulture, "Couldn't" +
" find type library \"{0}\" ({1} with version {2}), referenced" +
" by project \"{3}\".", Name, TypeLibGuid, TypeLibVersion,
Parent.Name);
}
return new BuildException(msg, Location.UnknownLocation);
}
private BuildException CreateInvalidTypeLibraryRegistrationException() {
string msg = null;
if (StringUtils.IsNullOrEmpty(Name)) {
msg = string.Format(CultureInfo.InvariantCulture, "Couldn't" +
" find path of type library \"{0}\" with version {1}, referenced" +
" by project \"{2}\". Ensure the type library is registered" +
"correctly.", TypeLibGuid, TypeLibVersion, Parent.Name);
} else {
msg = string.Format(CultureInfo.InvariantCulture, "Couldn't" +
" find path of type library \"{0}\" ({1} with version {2})," +
" referenced by project \"{3}\". Ensure the type library is" +
" registered correctly.", Name, TypeLibGuid, TypeLibVersion,
Parent.Name);
}
return new BuildException(msg, Location.UnknownLocation);
}
private BuildException CreateTypeLibraryPathDoesNotExistException(string typeLibraryPath) {
string msg = null;
if (StringUtils.IsNullOrEmpty(Name)) {
msg = string.Format(CultureInfo.InvariantCulture, "Type library" +
" \"{0}\" with version {1}, referenced by project \"{2}\"," +
" no longer exists at registered path \"{3}\".", TypeLibGuid,
TypeLibVersion, Parent.Name, typeLibraryPath);
} else {
msg = string.Format(CultureInfo.InvariantCulture, "Type library" +
" \"{0}\" ({1} with version {2}), referenced by project \"{3}\"," +
" no longer exists at registered path \"{4}\".", Name,
TypeLibGuid, TypeLibVersion, Parent.Name, typeLibraryPath);
}
return new BuildException(msg, Location.UnknownLocation);
}
#endregion Private Instance Methods
#region Private Static Methods
[DllImport( "oleaut32.dll", CharSet=CharSet.Unicode, PreserveSig=false)]
private static extern void LoadTypeLibEx(string strTypeLibName, int regKind,
[MarshalAs(UnmanagedType.Interface)] out Object typeLib);
#endregion Private Static Methods
#region Private Instance Fields
private bool _isCreated;
#endregion Private Instance Fields
}
}