|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared.FileSystem;
using Microsoft.Build.Utilities;
#nullable disable
namespace Microsoft.Build.Tasks
{
/// <summary>
/// Common abstract base for aximp and tlbimp COM reference wrapper classes.
/// They share the resolution method and only differ in constructing the wrapper file name.
/// </summary>
internal abstract class AxTlbBaseReference : ComReference
{
#region Constructors
/// <summary>
/// internal constructor
/// </summary>
/// <param name="taskLoggingHelper">task logger instance used for logging</param>
/// <param name="silent">true if this task should log only errors, no warnings or messages; false otherwise</param>
/// <param name="resolverCallback">callback interface for resolving dependent COM refs/NET assemblies</param>
/// <param name="referenceInfo">cached reference information (typelib pointer, original task item, typelib name etc.)</param>
/// <param name="itemName">reference name (for better logging experience)</param>
/// <param name="outputDirectory">directory we should write the wrapper to</param>
/// <param name="delaySign">delay sign wrappers?</param>
/// <param name="keyFile">file containing public/private keys</param>
/// <param name="keyContainer">container name for public/private keys</param>
/// <param name="includeTypeLibVersionInName">True if the interop name should include the typelib's version</param>
/// <param name="executeAsTool">True if GenerateWrapper() should generate the wrapper out-of-proc using aximp.exe or tlbimp.exe</param>
/// <param name="toolPath">Path to the SDK tools directory where aximp.exe or tlbimp.exe can be found</param>
/// <param name="buildEngine">BuildEngine of parent task; needed for logging purposes when generating wrapper out-of-proc</param>
/// <param name="environmentVariables">Array of equals-separated pairs of environment variables that should be passed to the spawned executable, in addition to (or selectively overriding) the regular environment block.</param>
internal AxTlbBaseReference(TaskLoggingHelper taskLoggingHelper, bool silent, IComReferenceResolver resolverCallback, ComReferenceInfo referenceInfo, string itemName, string outputDirectory, bool delaySign, string keyFile, string keyContainer, bool includeTypeLibVersionInName, bool executeAsTool, string toolPath, IBuildEngine buildEngine, string[] environmentVariables)
: base(taskLoggingHelper, silent, referenceInfo, itemName)
{
ResolverCallback = resolverCallback;
OutputDirectory = outputDirectory;
IncludeTypeLibVersionInName = includeTypeLibVersionInName;
BuildEngine = buildEngine;
EnvironmentVariables = environmentVariables;
DelaySign = delaySign;
ExecuteAsTool = executeAsTool;
KeyFile = keyFile;
KeyContainer = keyContainer;
ToolPath = toolPath;
}
#endregion
#region Properties
/// <summary>
/// directory we should write the wrapper to
/// </summary>
protected virtual string OutputDirectory { get; }
/// <summary>
/// callback interface for resolving dependent COM refs/NET assemblies
/// </summary>
protected IComReferenceResolver ResolverCallback { get; }
/// <summary>
/// container name for public/private keys
/// </summary>
protected string KeyContainer { get; set; }
/// <summary>
/// file containing public/private keys
/// </summary>
protected string KeyFile { get; set; }
/// <summary>
/// True if generated wrappers should be delay signed
/// </summary>
protected bool DelaySign { get; set; }
/// <summary>
/// Property to allow multitargeting of ResolveComReferences: If true, tlbimp.exe and
/// aximp.exe from the appropriate target framework will be run out-of-proc to generate
/// the necessary wrapper assemblies.
/// </summary>
protected bool ExecuteAsTool { get; set; }
/// <summary>
/// The BuildEngine of the ResolveComReference instance that created this instance
/// of the class: necessary for passing to the AxImp or TlbImp task that is spawned
/// when ExecuteAsTool is set to true
/// </summary>
protected IBuildEngine BuildEngine { get; set; }
/// <summary>
/// Environment variables to pass to the tool.
/// </summary>
protected string[] EnvironmentVariables { get; set; }
/// <summary>
/// If ExecuteAsTool is true, this must be set to the SDK
/// tools path for the framework version being targeted.
/// </summary>
protected string ToolPath { get; set; }
/// <summary>
/// When true, we include the typelib version number in the name.
/// </summary>
protected bool IncludeTypeLibVersionInName { get; set; }
#endregion
#region Methods
/// <summary>
/// Checks if there's a preexisting wrapper for this reference.
/// </summary>
internal override bool FindExistingWrapper(out ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp)
{
wrapperInfo = null;
string wrapperPath = GetWrapperPath();
// now see if the wrapper assembly actually exists
if (!FileSystems.Default.FileExists(wrapperPath))
{
return false;
}
wrapperInfo = new ComReferenceWrapperInfo { path = wrapperPath };
return IsWrapperUpToDate(wrapperInfo, componentTimestamp);
}
/// <summary>
/// Checks if the existing wrapper is up to date.
/// </summary>
protected virtual bool IsWrapperUpToDate(ComReferenceWrapperInfo wrapperInfo, DateTime componentTimestamp)
{
Debug.Assert(!string.IsNullOrEmpty(ReferenceInfo.strippedTypeLibPath), "ReferenceInfo.path should be valid if we got here");
if (string.IsNullOrEmpty(ReferenceInfo.strippedTypeLibPath))
{
throw new ComReferenceResolutionException();
}
// if wrapper doesn't exist, wrapper is obviously not up to date
if (!FileSystems.Default.FileExists(wrapperInfo.path))
{
return false;
}
// if typelib file has a DIFFERENT last write time, wrapper is not up to date
// the reason we're comparing write times in an unusual way is that type libraries are unusual
// "source files" for wrappers. If you upgrade/downgrade a system component, its write
// time may be earlier than before but we should still regenerate the wrapper.
if (DateTime.Compare(File.GetLastWriteTime(ReferenceInfo.strippedTypeLibPath), componentTimestamp) != 0)
{
return false;
}
// Compare our the existing wrapper's strong name state to the one we are requesting.
if (!SigningRequirementsMatchExistingWrapper(wrapperInfo))
{
return false;
}
// ok, everything's looking fine, now just verify the assembly file is valid
try
{
wrapperInfo.assembly = Assembly.UnsafeLoadFrom(wrapperInfo.path);
}
catch (BadImageFormatException)
{
// ouch, this assembly is malformed... need to regenerate the wrapper.
wrapperInfo.assembly = null;
}
return wrapperInfo.assembly != null;
}
/// <summary>
/// Constructs the wrapper file path.
/// </summary>
internal string GetWrapperPath()
{
// combine with the specified output directory
return Path.Combine(OutputDirectory, GetWrapperFileName());
}
/// <summary>
/// Helper method for constructing wrapper file name.
/// </summary>
internal string GetWrapperFileName()
{
return GetWrapperFileNameInternal(ReferenceInfo.typeLibName);
}
/*
* Method: GetWrapperFileName
*
*
*/
/// <summary>
/// Constructs the wrapper file name from a type library name. Specialized wrappers must override it if
/// they want to use the Resolve method from this class.
/// </summary>
protected abstract string GetWrapperFileNameInternal(string typeLibName);
/// <summary>
/// Static version of GetWrapperFileName for use when calling from the outside.
/// This version need only be used if the interop DLL needs to include the typelib version in the name
/// Default implementation
/// </summary>
/// <param name="interopDllHeader">XXX, when the interop DLL is of the form XXX.typeLibName.[Y.Z.]dll</param>
/// <param name="typeLibName">The typelib to generate the wrapper name for</param>
/// <param name="includeTypeLibVersionInName">True if the interop name should include the typelib's version</param>
/// <param name="majorVerNum">Major version number to append to the interop DLL's name</param>
/// <param name="minorVerNum">Minor version number to append to the interop DLL's name</param>
internal static string GetWrapperFileName(string interopDllHeader, string typeLibName, bool includeTypeLibVersionInName, short majorVerNum, short minorVerNum)
{
// create wrapper name of the format XXX.YYY[.Z.W].dll, where
// XXX = the header ("Interop." or the like)
// YYY = typeLibName
// Z.W = optional TLB version number
var builder = new StringBuilder(interopDllHeader);
builder.Append(typeLibName);
if (includeTypeLibVersionInName)
{
builder.Append('.');
builder.Append(majorVerNum);
builder.Append('.');
builder.Append(minorVerNum);
}
builder.Append(".dll");
return builder.ToString();
}
/// <summary>
/// Given our KeyFile, KeyContainer, and DelaySign parameters, generate the public / private
/// key pair and validate that it exists to the extent needed.
/// </summary>
internal void GetAndValidateStrongNameKey(out StrongNameKeyPair keyPair, out byte[] publicKey)
{
// get key pair/public key
StrongNameUtils.GetStrongNameKey(Log, KeyFile, KeyContainer, out keyPair, out publicKey);
// make sure we give as much data to the typelib converter as necessary but not more, or we might end up
// with something we didn't want
if (DelaySign)
{
keyPair = null;
if (publicKey == null)
{
Log.LogErrorWithCodeFromResources(null, ReferenceInfo.SourceItemSpec, 0, 0, 0, 0, "StrongNameUtils.NoPublicKeySpecified");
throw new StrongNameException();
}
}
else
{
publicKey = null;
// If the user did not specify delay sign and we didn't get a public/private
// key pair then we have an error since a public key by itself is not enough
// to fully sign the assembly. (only if either KeyContainer or KeyFile was specified though)
if (keyPair == null)
{
if (!string.IsNullOrEmpty(KeyContainer))
{
Log.LogErrorWithCodeFromResources(null, ReferenceInfo.SourceItemSpec, 0, 0, 0, 0, "ResolveComReference.StrongNameUtils.NoKeyPairInContainer", KeyContainer);
throw new StrongNameException();
}
if (!string.IsNullOrEmpty(KeyFile))
{
Log.LogErrorWithCodeFromResources(null, ReferenceInfo.SourceItemSpec, 0, 0, 0, 0, "ResolveComReference.StrongNameUtils.NoKeyPairInFile", KeyFile);
throw new StrongNameException();
}
}
}
}
/// <summary>
/// Compare the strong name signing state of the existing wrapper to the signing
/// state we are requesting in this run of the task. Return true if they match (e.g.
/// from a signing perspective, the wrapper is up-to-date) or false otherwise.
/// </summary>
private bool SigningRequirementsMatchExistingWrapper(ComReferenceWrapperInfo wrapperInfo)
{
StrongNameLevel desiredStrongNameLevel = StrongNameLevel.None;
if (!string.IsNullOrEmpty(KeyFile) || !string.IsNullOrEmpty(KeyContainer))
{
desiredStrongNameLevel = DelaySign ? StrongNameLevel.DelaySigned : StrongNameLevel.FullySigned;
}
// ...and see what we have already
StrongNameLevel currentStrongNameLevel = StrongNameUtils.GetAssemblyStrongNameLevel(wrapperInfo.path);
// if not matching, need to regenerate wrapper
if (desiredStrongNameLevel != currentStrongNameLevel)
{
return false;
}
// if the wrapper needs a strong name, see if the public keys match
if (desiredStrongNameLevel == StrongNameLevel.DelaySigned ||
desiredStrongNameLevel == StrongNameLevel.FullySigned)
{
// get desired public key
StrongNameUtils.GetStrongNameKey(Log, KeyFile, KeyContainer, out _, out byte[] desiredPublicKey);
// get current public key
AssemblyName assemblyName = AssemblyName.GetAssemblyName(wrapperInfo.path);
if (assemblyName == null)
{
return false;
}
byte[] currentPublicKey = assemblyName.GetPublicKey();
if (currentPublicKey.Length != desiredPublicKey.Length)
{
return false;
}
// compare public keys byte by byte
for (int i = 0; i < currentPublicKey.Length; i++)
{
if (currentPublicKey[i] != desiredPublicKey[i])
{
return false;
}
}
}
return true;
}
#endregion
}
}
|