diff --git a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs index ffd6ed0068e..6fa40a03d33 100644 --- a/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/AssemblyTaskFactory_Tests.cs @@ -32,11 +32,6 @@ public class AssemblyTaskFactory_Tests /// private AssemblyLoadInfo _loadInfo; - /// - /// The loaded type from the initialized task factory. - /// - private LoadedType _loadedType; - /// /// Initialize a task factory /// @@ -55,7 +50,7 @@ public void NullLoadInfo() { Assert.Throws(() => { - AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); + AssemblyTaskFactory taskFactory = new(); taskFactory.InitializeFactory(null, "TaskToTestFactories", new Dictionary(), string.Empty, null, false, null, ElementLocation.Create("NONE"), String.Empty); } ); @@ -702,8 +697,8 @@ private void SetupTaskFactory(IDictionary factoryParameters, boo #else _loadInfo = AssemblyLoadInfo.Create(typeof(TaskToTestFactories).GetTypeInfo().Assembly.FullName, null); #endif - _loadedType = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, null, ElementLocation.Create("NONE"), String.Empty); - Assert.True(_loadedType.Assembly.Equals(_loadInfo)); // "Expected the AssemblyLoadInfo to be equal" + TypeInformation typeInfo = _taskFactory.InitializeFactory(_loadInfo, "TaskToTestFactories", new Dictionary(), string.Empty, factoryParameters, explicitlyLaunchTaskHost, null, ElementLocation.Create("NONE"), String.Empty); + typeInfo.LoadInfo.ShouldBe(_loadInfo, "Expected the AssemblyLoadInfo to be equal"); } #endregion diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index 4482dda9bfa..1567868e60e 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -1170,7 +1170,7 @@ private void InitializeHost(bool throwOnExecute) TaskBuilderTestTask.TaskBuilderTestTaskFactory taskFactory = new TaskBuilderTestTask.TaskBuilderTestTaskFactory(); taskFactory.ThrowOnExecute = throwOnExecute; string taskName = "TaskBuilderTestTask"; - (_host as TaskExecutionHost)._UNITTESTONLY_TaskFactoryWrapper = new TaskFactoryWrapper(taskFactory, loadedType, taskName, null); + (_host as TaskExecutionHost)._UNITTESTONLY_TaskFactoryWrapper = new TaskFactoryWrapper(taskFactory, new TypeInformation(loadedType), taskName, null); _host.InitializeForTask ( this, diff --git a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs index fd733207795..600eb2a81c5 100644 --- a/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs +++ b/src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs @@ -257,12 +257,12 @@ void ITaskExecutionHost.InitializeForTask(IBuildEngine2 buildEngine, TargetLoggi TaskRequirements requirements = TaskRequirements.None; - if (_taskFactoryWrapper.TaskFactoryLoadedType.HasSTAThreadAttribute()) + if (_taskFactoryWrapper.TaskFactoryTypeInformation.HasSTAThreadAttribute) { requirements |= TaskRequirements.RequireSTAThread; } - if (_taskFactoryWrapper.TaskFactoryLoadedType.HasLoadInSeparateAppDomainAttribute()) + if (_taskFactoryWrapper.TaskFactoryTypeInformation.HasLoadInSeparateAppDomainAttribute) { requirements |= TaskRequirements.RequireSeparateAppDomain; @@ -299,7 +299,7 @@ bool ITaskExecutionHost.InitializeForBatch(TaskLoggingContext loggingContext, It if (_resolver == null) { _resolver = new TaskEngineAssemblyResolver(); - _resolver.Initialize(_taskFactoryWrapper.TaskFactoryLoadedType.Assembly.AssemblyFile); + _resolver.Initialize(_taskFactoryWrapper.TaskFactoryTypeInformation.LoadInfo.AssemblyFile); _resolver.InstallHandler(); } #endif @@ -899,15 +899,18 @@ private TaskFactoryWrapper FindTaskInRegistry(IDictionary taskId } } + string taskFactoryFullName = returnClass.TaskFactory is AssemblyTaskFactory atf ? atf.TaskName : returnClass.TaskFactory.TaskType.FullName; // Map to an intrinsic task, if necessary. - if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.MSBuild", StringComparison.OrdinalIgnoreCase)) + if (String.Equals(taskFactoryFullName, "Microsoft.Build.Tasks.MSBuild", StringComparison.OrdinalIgnoreCase)) { - returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new LoadedType(typeof(MSBuild), AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null)), _taskName, null); + AssemblyLoadInfo loadInfo = AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null); + returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(MSBuild)), new TypeInformation(new LoadedType(typeof(MSBuild), loadInfo)), _taskName, null); _intrinsicTasks[_taskName] = returnClass; } - else if (String.Equals(returnClass.TaskFactory.TaskType.FullName, "Microsoft.Build.Tasks.CallTarget", StringComparison.OrdinalIgnoreCase)) + else if (String.Equals(taskFactoryFullName, "Microsoft.Build.Tasks.CallTarget", StringComparison.OrdinalIgnoreCase)) { - returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new LoadedType(typeof(CallTarget), AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null)), _taskName, null); + AssemblyLoadInfo loadInfo = AssemblyLoadInfo.Create(typeof(TaskExecutionHost).GetTypeInfo().Assembly.FullName, null); + returnClass = new TaskFactoryWrapper(new IntrinsicTaskFactory(typeof(CallTarget)), new TypeInformation(new LoadedType(typeof(CallTarget), loadInfo)), _taskName, null); _intrinsicTasks[_taskName] = returnClass; } } @@ -1073,30 +1076,15 @@ out parameterSet else { // flag an error if we find a parameter that has no .NET property equivalent - if (_taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly is null) - { - _taskLoggingContext.LogError - ( - new BuildEventFileInfo( parameterLocation ), - "UnexpectedTaskAttribute", - parameterName, - _taskName, - _taskFactoryWrapper.TaskFactoryLoadedType.Type.Assembly.FullName, - _taskFactoryWrapper.TaskFactoryLoadedType.Type.Assembly.Location - ); - } - else - { - _taskLoggingContext.LogError - ( - new BuildEventFileInfo( parameterLocation ), - "UnexpectedTaskAttribute", - parameterName, - _taskName, - _taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly.FullName, - _taskFactoryWrapper.TaskFactoryLoadedType.LoadedAssembly.Location - ); - } + _taskLoggingContext.LogError + ( + new BuildEventFileInfo( parameterLocation ), + "UnexpectedTaskAttribute", + parameterName, + _taskName, + _taskFactoryWrapper.TaskFactoryTypeInformation.LoadInfo.AssemblyName, + _taskFactoryWrapper.TaskFactoryTypeInformation.LoadInfo.AssemblyLocation + ); } } catch (AmbiguousMatchException) diff --git a/src/Build/Instance/ReflectableTaskPropertyInfo.cs b/src/Build/Instance/ReflectableTaskPropertyInfo.cs index 571ba866933..6518e901fdd 100644 --- a/src/Build/Instance/ReflectableTaskPropertyInfo.cs +++ b/src/Build/Instance/ReflectableTaskPropertyInfo.cs @@ -17,14 +17,19 @@ namespace Microsoft.Build.Execution internal class ReflectableTaskPropertyInfo : TaskPropertyInfo { /// - /// The reflection-produced PropertyInfo. + /// The name of the generated tasks. /// - private PropertyInfo _propertyInfo; + private readonly string taskName; /// - /// The type of the generated tasks. + /// Function for accessing information about a property on a task via its name. /// - private Type _taskType; + private readonly Func getProperty; + + /// + /// The reflection-produced PropertyInfo. + /// + private PropertyInfo _propertyInfo; /// /// Initializes a new instance of the class. @@ -35,7 +40,16 @@ internal ReflectableTaskPropertyInfo(TaskPropertyInfo taskPropertyInfo, Type tas : base(taskPropertyInfo.Name, taskPropertyInfo.PropertyType, taskPropertyInfo.Output, taskPropertyInfo.Required) { ErrorUtilities.VerifyThrowArgumentNull(taskType, nameof(taskType)); - _taskType = taskType; + getProperty = taskType.GetProperty; + taskName = taskType.FullName; + } + + internal ReflectableTaskPropertyInfo(TaskPropertyInfo taskPropertyInfo, TypeInformation typeInformation) + : base(taskPropertyInfo.Name, taskPropertyInfo.PropertyType, taskPropertyInfo.Output, taskPropertyInfo.Required) + { + ErrorUtilities.VerifyThrowArgumentNull(typeInformation, nameof(typeInformation)); + getProperty = typeInformation.GetProperty; + taskName = typeInformation.TypeName; } /// @@ -52,6 +66,15 @@ internal ReflectableTaskPropertyInfo(PropertyInfo propertyInfo) _propertyInfo = propertyInfo; } + internal ReflectableTaskPropertyInfo(TypeInformation.PropertyInfo propertyInfo) : + base( + propertyInfo.Name, + propertyInfo.PropertyType, + propertyInfo.OutputAttribute, + propertyInfo.RequiredAttribute) + { + } + /// /// Gets or sets the reflection-produced PropertyInfo. /// @@ -61,8 +84,8 @@ internal PropertyInfo Reflection { if (_propertyInfo == null) { - _propertyInfo = _taskType.GetProperty(Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); - ErrorUtilities.VerifyThrow(_propertyInfo != null, "Could not find property {0} on type {1} that the task factory indicated should exist.", Name, _taskType.FullName); + _propertyInfo = getProperty(Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); + ErrorUtilities.VerifyThrow(_propertyInfo != null, "Could not find property {0} on type {1} that the task factory indicated should exist.", Name, taskName); } return _propertyInfo; diff --git a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs index 97d721d4352..cbe5cfbf727 100644 --- a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs +++ b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Threading.Tasks; - using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -38,7 +38,7 @@ internal class AssemblyTaskFactory : ITaskFactory2 /// /// The loaded type (type, assembly name / file) of the task wrapped by the factory /// - private LoadedType _loadedType; + private TypeInformation _typeInformation; #if FEATURE_APPDOMAIN /// @@ -84,18 +84,39 @@ public string FactoryName { get { - return _loadedType.Assembly.AssemblyLocation; + return _typeInformation.LoadInfo.AssemblyLocation; } } /// /// Gets the type of task this factory creates. + /// This is only actually used in finding the TaskName immediately below this if LoadedType is not null. + /// The extra null checks are to avoid throwing, though it will if it cannot find the type. /// public Type TaskType { - get { return _loadedType.Type; } + get { return _typeInformation.LoadedType?.Type ?? Type.GetType(_typeInformation.TypeName, true, true); } + } + + /// + /// The name of the task. + /// + public string TaskName + { + get { return _typeInformation.LoadedType is null ? $"{_typeInformation.Namespace}.{_typeInformation.TypeName}" : TaskType.FullName; } } + /// + /// All information known about a type. If it's loaded, that information mostly comes from the LoadedType object contained within. + /// If not, the information was collected in TypeLoader via System.Reflection.Metadata. + /// + public TypeInformation TypeInformation { get { return _typeInformation; } } + + /// + /// Indicates whether this task implements IGeneratedTask. IGeneratedTask has useful methods for getting and setting properties. + /// + public bool ImplementsIGeneratedTask { get { return _typeInformation?.ImplementsIGeneratedTask ?? false; } } + /// /// Initializes this factory for instantiating tasks with a particular inline task block. /// @@ -147,14 +168,9 @@ public bool Initialize(string taskName, IDictionary factoryIdent /// public TaskPropertyInfo[] GetTaskParameters() { - PropertyInfo[] infos = _loadedType.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public); - var propertyInfos = new TaskPropertyInfo[infos.Length]; - for (int i = 0; i < infos.Length; i++) - { - propertyInfos[i] = new ReflectableTaskPropertyInfo(infos[i]); - } - - return propertyInfos; + return _typeInformation.LoadedType is null ? + _typeInformation.Properties.Select(prop => new ReflectableTaskPropertyInfo(prop)).ToArray() : + _typeInformation.GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(prop => new ReflectableTaskPropertyInfo(prop)).ToArray(); } /// @@ -250,7 +266,7 @@ public void CleanupTask(ITask task) /// /// Initialize the factory from the task registry /// - internal LoadedType InitializeFactory + internal TypeInformation InitializeFactory ( AssemblyLoadInfo loadInfo, string taskName, @@ -277,8 +293,18 @@ string taskProjectFile { ErrorUtilities.VerifyThrowArgumentLength(taskName, nameof(taskName)); _taskName = taskName; - _loadedType = _typeLoader.Load(taskName, loadInfo); - ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty); + + // If the user requested a task host but provided us with an assembly name rather than an assembly file, pretend they didn't. + // Finding the path to the assembly file the runtime would load without actually loading the assembly would likely be a bug farm. + // Also, this should be a very unusual case. + _typeInformation = _typeLoader.Load(taskName, loadInfo, taskHostFactoryExplicitlyRequested && (loadInfo.AssemblyFile is not null || loadInfo.AssemblyName.StartsWith("Microsoft.Build"))); + + // If the user specifically requests a code task factory, and the type wasn't already loaded, we need a way to verify that it really found a matching type. Properties is an array, so it should never be null, + // though it could be an empty array. + ProjectErrorUtilities.VerifyThrowInvalidProject(_typeInformation is not null && (_typeInformation.LoadedType != null || _typeInformation.Properties != null), elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty); + + _typeInformation.LoadInfo = loadInfo; + _typeInformation.TypeName ??= taskName; } catch (TargetInvocationException e) { @@ -309,7 +335,7 @@ string taskProjectFile ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message); } - return _loadedType; + return _typeInformation; } /// @@ -362,7 +388,7 @@ internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingConte mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture(); } - TaskHostTask task = new TaskHostTask(taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _loadedType + TaskHostTask task = new TaskHostTask(taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _typeInformation #if FEATURE_APPDOMAIN , appDomainSetup #endif @@ -371,17 +397,13 @@ internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingConte } else { -#if FEATURE_APPDOMAIN - AppDomain taskAppDomain = null; -#endif - - ITask taskInstance = TaskLoader.CreateTask(_loadedType, _taskName, taskLocation.File, taskLocation.Line, taskLocation.Column, new TaskLoader.LogError(ErrorLoggingDelegate) + ITask taskInstance = TaskLoader.CreateTask(_typeInformation, _taskName, taskLocation.File, taskLocation.Line, taskLocation.Column, new TaskLoader.LogError(ErrorLoggingDelegate) #if FEATURE_APPDOMAIN , appDomainSetup #endif , isOutOfProc #if FEATURE_APPDOMAIN - , out taskAppDomain + , out AppDomain taskAppDomain #endif ); @@ -411,13 +433,13 @@ internal bool TaskNameCreatableByFactory(string taskName, IDictionary /// The type of the task that we are wrapping. /// - private LoadedType _taskType; + private TypeInformation _taskType; #if FEATURE_APPDOMAIN /// @@ -124,7 +124,7 @@ internal class TaskHostTask : IGeneratedTask, ICancelableTask, INodePacketFactor /// /// Constructor /// - public TaskHostTask(IElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, IDictionary taskHostParameters, LoadedType taskType + public TaskHostTask(IElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, IDictionary taskHostParameters, TypeInformation taskType #if FEATURE_APPDOMAIN , AppDomainSetup appDomainSetup #endif @@ -203,16 +203,28 @@ public object GetPropertyValue(TaskPropertyInfo property) { // If we returned an exception, then we want to throw it when we // do the get. - if (value is Exception) + if (value is Exception eVal) { - throw (Exception)value; + throw eVal; } return value; } + else if (_taskType.LoadedType is null) + { + switch (property.Name) + { + case "HostObject": + return this.HostObject; + case "BuildEngine": + return this.BuildEngine; + default: + throw new InternalErrorException($"{property.Name} is not a property on TaskHostTask, or else it needs to be added to its registered list of properties."); + } + } else { - PropertyInfo parameter = _taskType.Type.GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public); + PropertyInfo parameter = _taskType.GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public); return parameter.GetValue(this, null); } } @@ -244,7 +256,7 @@ public bool Execute() // log that we are about to spawn the task host string runtime = _taskHostParameters[XMakeAttributes.runtime]; string architecture = _taskHostParameters[XMakeAttributes.architecture]; - _taskLoggingContext.LogComment(MessageImportance.Low, "ExecutingTaskInTaskHost", _taskType.Type.Name, _taskType.Assembly.AssemblyLocation, runtime, architecture); + _taskLoggingContext.LogComment(MessageImportance.Low, "ExecutingTaskInTaskHost", _taskType.TypeName, _taskType.LoadInfo.AssemblyLocation ?? _taskType.LoadedType.LoadedAssembly.Location, runtime, architecture); // set up the node lock (_taskHostLock) @@ -268,8 +280,8 @@ public bool Execute() BuildEngine.ColumnNumberOfTaskNode, BuildEngine.ProjectFileOfTaskNode, BuildEngine.ContinueOnError, - _taskType.Type.FullName, - AssemblyUtilities.GetAssemblyLocation(_taskType.Type.GetTypeInfo().Assembly), + _taskType.TypeName, + _taskType.Path, _buildComponentHost.BuildParameters.LogTaskInputs, _setParameters, new Dictionary(_buildComponentHost.BuildParameters.GlobalProperties), @@ -465,8 +477,8 @@ private void HandleTaskHostTaskComplete(TaskHostTaskComplete taskHostTaskComplet } else { - exceptionMessageArgs = new string[] { _taskType.Type.Name, - AssemblyUtilities.GetAssemblyLocation(_taskType.Type.GetTypeInfo().Assembly), + exceptionMessageArgs = new string[] { _taskType.TypeName, + _taskType.LoadInfo.AssemblyLocation ?? _taskType.LoadedType.LoadedAssembly.Location, string.Empty }; } @@ -558,11 +570,11 @@ private void LogErrorUnableToCreateTaskHost(HandshakeOptions requiredContext, st if (e == null) { - _taskLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "TaskHostAcquireFailed", _taskType.Type.Name, runtime, architecture, msbuildLocation); + _taskLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "TaskHostAcquireFailed", _taskType.TypeName, runtime, architecture, msbuildLocation); } else { - _taskLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "TaskHostNodeFailedToLaunch", _taskType.Type.Name, runtime, architecture, msbuildLocation, e.ErrorCode, e.Message); + _taskLoggingContext.LogError(new BuildEventFileInfo(_taskLocation), "TaskHostNodeFailedToLaunch", _taskType.TypeName, runtime, architecture, msbuildLocation, e.ErrorCode, e.Message); } } } diff --git a/src/Build/Instance/TaskFactoryWrapper.cs b/src/Build/Instance/TaskFactoryWrapper.cs index 72bf3ec5624..dea1f5c7cdb 100644 --- a/src/Build/Instance/TaskFactoryWrapper.cs +++ b/src/Build/Instance/TaskFactoryWrapper.cs @@ -7,6 +7,8 @@ using Microsoft.Build.Collections; using Microsoft.Build.Framework; using Microsoft.Build.Shared; +using Microsoft.Build.BackEnd; +using System.Linq; #nullable disable @@ -62,13 +64,13 @@ internal sealed class TaskFactoryWrapper /// /// Creates an instance of this class for the given type. /// - internal TaskFactoryWrapper(ITaskFactory taskFactory, LoadedType taskFactoryLoadInfo, string taskName, IDictionary factoryIdentityParameters) + internal TaskFactoryWrapper(ITaskFactory taskFactory, TypeInformation taskFactoryLoadInfo, string taskName, IDictionary factoryIdentityParameters) { ErrorUtilities.VerifyThrowArgumentNull(taskFactory, nameof(taskFactory)); ErrorUtilities.VerifyThrowArgumentLength(taskName, nameof(taskName)); _taskFactory = taskFactory; _taskName = taskName; - TaskFactoryLoadedType = taskFactoryLoadInfo; + TaskFactoryTypeInformation = taskFactoryLoadInfo; _factoryIdentityParameters = factoryIdentityParameters; } @@ -79,11 +81,7 @@ internal TaskFactoryWrapper(ITaskFactory taskFactory, LoadedType taskFactoryLoad /// /// Load information about the task factory itself /// - public LoadedType TaskFactoryLoadedType - { - get; - private set; - } + public TypeInformation TaskFactoryTypeInformation { get; private set; } /// /// The task factory wrapped by the wrapper @@ -187,15 +185,14 @@ internal void SetPropertyValue(ITask task, TaskPropertyInfo property, object val ErrorUtilities.VerifyThrowArgumentNull(task, nameof(task)); ErrorUtilities.VerifyThrowArgumentNull(property, nameof(property)); - IGeneratedTask generatedTask = task as IGeneratedTask; - if (generatedTask != null) + if (task is IGeneratedTask generatedTask) { generatedTask.SetPropertyValue(property, value); } else { - ReflectableTaskPropertyInfo propertyInfo = (ReflectableTaskPropertyInfo)property; - propertyInfo.Reflection.SetValue(task, value, null); + PropertyInfo prop = task.GetType().GetProperty(property.Name); + prop.SetValue(task, value); } } @@ -207,23 +204,23 @@ internal object GetPropertyValue(ITask task, TaskPropertyInfo property) ErrorUtilities.VerifyThrowArgumentNull(task, nameof(task)); ErrorUtilities.VerifyThrowArgumentNull(property, nameof(property)); - IGeneratedTask generatedTask = task as IGeneratedTask; - if (generatedTask != null) + if (task is IGeneratedTask generatedTask) { return generatedTask.GetPropertyValue(property); } else { - ReflectableTaskPropertyInfo propertyInfo = property as ReflectableTaskPropertyInfo; - if (propertyInfo != null) - { - return propertyInfo.Reflection.GetValue(task, null); - } - else + if (property is ReflectableTaskPropertyInfo propertyInfo) { - ErrorUtilities.ThrowInternalError("Task does not implement IGeneratedTask and we don't have {0} either.", typeof(ReflectableTaskPropertyInfo).Name); - throw new InternalErrorException(); // unreachable + try + { + return propertyInfo.Reflection.GetValue(task, null); + } + // If the type was not loaded, we may end up with a NotImplementedException. Ignore it. + catch (NotImplementedException) { } } + + return task.GetType().GetTypeInfo().GetProperty(property.Name); } } @@ -246,7 +243,9 @@ private void PopulatePropertyInfoCacheIfNecessary() { if (_propertyInfoCache == null) { - bool taskTypeImplementsIGeneratedTask = typeof(IGeneratedTask).IsAssignableFrom(_taskFactory.TaskType); + bool taskTypeImplementsIGeneratedTask = _taskFactory is AssemblyTaskFactory assemblyTaskFactory ? + assemblyTaskFactory.ImplementsIGeneratedTask : + typeof(IGeneratedTask).IsAssignableFrom(_taskFactory.TaskType); TaskPropertyInfo[] propertyInfos = _taskFactory.GetTaskParameters(); for (int i = 0; i < propertyInfos.Length; i++) @@ -257,7 +256,9 @@ private void PopulatePropertyInfoCacheIfNecessary() TaskPropertyInfo propertyInfo = propertyInfos[i]; if (!taskTypeImplementsIGeneratedTask) { - propertyInfo = new ReflectableTaskPropertyInfo(propertyInfo, _taskFactory.TaskType); + propertyInfo = _taskFactory is AssemblyTaskFactory assemblyTaskFactory2 ? + new ReflectableTaskPropertyInfo(propertyInfo, assemblyTaskFactory2.TypeInformation) : + new ReflectableTaskPropertyInfo(propertyInfo, _taskFactory.TaskType); } try diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs index 390f8e29f92..d12c45e98e6 100644 --- a/src/Build/Instance/TaskRegistry.cs +++ b/src/Build/Instance/TaskRegistry.cs @@ -436,7 +436,7 @@ ElementLocation elementLocation targetLoggingContext.LogComment(MessageImportance.Low, "TaskFoundFromFactory", taskName, taskFactory.Name); } - if (taskFactory.TaskFactoryLoadedType.HasSTAThreadAttribute()) + if (taskFactory.TaskFactoryTypeInformation.HasSTAThreadAttribute) { targetLoggingContext.LogComment(MessageImportance.Low, "TaskNeedsSTA", taskName); } @@ -1284,7 +1284,7 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo AssemblyLoadInfo taskFactoryLoadInfo = TaskFactoryAssemblyLoadInfo; ErrorUtilities.VerifyThrow(taskFactoryLoadInfo != null, "TaskFactoryLoadInfo should never be null"); ITaskFactory factory = null; - LoadedType loadedType = null; + TypeInformation typeInformation = null; bool isAssemblyTaskFactory = String.Equals(TaskFactoryAttributeName, AssemblyTaskFactory, StringComparison.OrdinalIgnoreCase); bool isTaskHostFactory = String.Equals(TaskFactoryAttributeName, TaskHostFactory, StringComparison.OrdinalIgnoreCase); @@ -1300,8 +1300,8 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo ); // Create an instance of the internal assembly task factory, it has the error handling built into its methods. - AssemblyTaskFactory taskFactory = new AssemblyTaskFactory(); - loadedType = taskFactory.InitializeFactory(taskFactoryLoadInfo, RegisteredName, ParameterGroupAndTaskBody.UsingTaskParameters, ParameterGroupAndTaskBody.InlineTaskXmlBody, TaskFactoryParameters, explicitlyLaunchTaskHost, targetLoggingContext, elementLocation, taskProjectFile); + AssemblyTaskFactory taskFactory = new(); + typeInformation = taskFactory.InitializeFactory(taskFactoryLoadInfo, RegisteredName, ParameterGroupAndTaskBody.UsingTaskParameters, ParameterGroupAndTaskBody.InlineTaskXmlBody, TaskFactoryParameters, explicitlyLaunchTaskHost, targetLoggingContext, elementLocation, taskProjectFile); factory = taskFactory; } else @@ -1327,9 +1327,9 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo } // Make sure we only look for task factory classes when loading based on the name - loadedType = s_taskFactoryTypeLoader.Load(TaskFactoryAttributeName, taskFactoryLoadInfo); + typeInformation = s_taskFactoryTypeLoader.Load(TaskFactoryAttributeName, taskFactoryLoadInfo, false); - if (loadedType == null) + if (typeInformation == null) { // We could not find the type (this is what null means from the Load method) but there is no reason given so we can only log the fact that // we could not find the name given in the task factory attribute in the class specified in the assembly File or assemblyName fields. @@ -1367,9 +1367,9 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo // We have loaded the type, lets now try and construct it // Any exceptions from the constructor of the task factory will be caught lower down and turned into an InvalidProjectFileExceptions #if FEATURE_APPDOMAIN - factory = (ITaskFactory)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName); + factory = (ITaskFactory)AppDomain.CurrentDomain.CreateInstanceAndUnwrap(typeInformation.LoadInfo.AssemblyName ?? typeInformation.LoadedType.Type.GetTypeInfo().Assembly.FullName, typeInformation.TypeName); #else - factory = (ITaskFactory) Activator.CreateInstance(loadedType.Type); + factory = (ITaskFactory)Activator.CreateInstance(typeInformation.LoadInfo.AssemblyName ?? typeInformation.LoadedType.LoadedAssembly.FullName, typeInformation.TypeName)?.Unwrap(); #endif TaskFactoryLoggingHost taskFactoryLoggingHost = new TaskFactoryLoggingHost(true /*I dont have the data at this point, the safest thing to do is make sure events are serializable*/, elementLocation, targetLoggingContext); @@ -1388,15 +1388,14 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo // TaskFactoryParameters will always be null unless specifically created to have runtime and architecture parameters. if (TaskFactoryParameters != null) { - targetLoggingContext.LogWarning - ( + targetLoggingContext.LogWarning( null, - new BuildEventFileInfo(elementLocation), - "TaskFactoryWillIgnoreTaskFactoryParameters", - factory.FactoryName, - XMakeAttributes.runtime, - XMakeAttributes.architecture, - RegisteredName); + new BuildEventFileInfo(elementLocation), + "TaskFactoryWillIgnoreTaskFactoryParameters", + factory.FactoryName, + XMakeAttributes.runtime, + XMakeAttributes.architecture, + RegisteredName); } } @@ -1460,7 +1459,7 @@ private bool GetTaskFactory(TargetLoggingContext targetLoggingContext, ElementLo } } - _taskFactoryWrapperInstance = new TaskFactoryWrapper(factory, loadedType, RegisteredName, TaskFactoryParameters); + _taskFactoryWrapperInstance = new TaskFactoryWrapper(factory, typeInformation, RegisteredName, TaskFactoryParameters); } return true; diff --git a/src/Build/Logging/LoggerDescription.cs b/src/Build/Logging/LoggerDescription.cs index dc7950123bb..bf1d727c41d 100644 --- a/src/Build/Logging/LoggerDescription.cs +++ b/src/Build/Logging/LoggerDescription.cs @@ -202,7 +202,7 @@ private ILogger CreateLogger(bool forwardingLogger) if (forwardingLogger) { // load the logger from its assembly - LoadedType loggerClass = (new TypeLoader(s_forwardingLoggerClassFilter)).Load(_loggerClassName, _loggerAssembly); + LoadedType loggerClass = (new TypeLoader(s_forwardingLoggerClassFilter)).Load(_loggerClassName, _loggerAssembly, false).LoadedType; if (loggerClass != null) { @@ -213,7 +213,7 @@ private ILogger CreateLogger(bool forwardingLogger) else { // load the logger from its assembly - LoadedType loggerClass = (new TypeLoader(s_loggerClassFilter)).Load(_loggerClassName, _loggerAssembly); + LoadedType loggerClass = (new TypeLoader(s_loggerClassFilter)).Load(_loggerClassName, _loggerAssembly, false).LoadedType; if (loggerClass != null) { diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index cd99bc84721..b05dc8c9617 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -35,7 +35,7 @@ - + @@ -47,7 +47,6 @@ - @@ -717,6 +716,10 @@ SharedUtilities\LoadedType.cs true + + SharedUtilities\TypeInformation.cs + true + InprocTrackingNativeMethods.cs true diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj index 803083dd1fc..82122c476ea 100644 --- a/src/MSBuild/MSBuild.csproj +++ b/src/MSBuild/MSBuild.csproj @@ -135,8 +135,9 @@ + - true + true true diff --git a/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs index 8405d0474a0..c9ebdbb44fb 100644 --- a/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs +++ b/src/MSBuild/OutOfProcTaskAppDomainWrapperBase.cs @@ -10,6 +10,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; +using System.IO; #if FEATURE_APPDOMAIN using System.Runtime.Remoting; #endif @@ -113,11 +114,11 @@ IDictionary taskParams #endif wrappedTask = null; - LoadedType taskType = null; + TypeInformation taskType = null; try { TypeLoader typeLoader = new TypeLoader(TaskLoader.IsTaskClass); - taskType = typeLoader.Load(taskName, AssemblyLoadInfo.Create(null, taskLocation)); + taskType = typeLoader.Load(taskName, AssemblyLoadInfo.Create(null, taskLocation), false); } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) { @@ -135,10 +136,10 @@ IDictionary taskParams } OutOfProcTaskHostTaskResult taskResult; - if (taskType.HasSTAThreadAttribute()) + if (taskType.HasSTAThreadAttribute) { #if FEATURE_APARTMENT_STATE - taskResult = InstantiateAndExecuteTaskInSTAThread(oopTaskHostNode, taskType, taskName, taskLocation, taskFile, taskLine, taskColumn, + taskResult = InstantiateAndExecuteTaskInSTAThread(oopTaskHostNode, taskType.LoadedType, taskName, taskLocation, taskFile, taskLine, taskColumn, #if FEATURE_APPDOMAIN appDomainSetup, #endif @@ -155,7 +156,7 @@ IDictionary taskParams } else { - taskResult = InstantiateAndExecuteTask(oopTaskHostNode, taskType, taskName, taskLocation, taskFile, taskLine, taskColumn, + taskResult = InstantiateAndExecuteTask(oopTaskHostNode, taskType.LoadedType, taskName, taskLocation, taskFile, taskLine, taskColumn, #if FEATURE_APPDOMAIN appDomainSetup, #endif @@ -296,7 +297,7 @@ IDictionary taskParams try { - wrappedTask = TaskLoader.CreateTask(taskType, taskName, taskFile, taskLine, taskColumn, new TaskLoader.LogError(LogErrorDelegate), + wrappedTask = TaskLoader.CreateTask(new TypeInformation(taskType), taskName, taskFile, taskLine, taskColumn, new TaskLoader.LogError(LogErrorDelegate), #if FEATURE_APPDOMAIN appDomainSetup, #endif diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index 6b54a4ec089..a5d72babc54 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -895,7 +895,7 @@ private void RunTask(object state) taskResult = _taskWrapper.ExecuteTask ( - this as IBuildEngine, + this, taskName, taskLocation, taskConfiguration.ProjectFileOfTask, diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj index f56435ec284..9a454bde581 100644 --- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj +++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj @@ -176,7 +176,8 @@ - + + diff --git a/src/MSBuildTaskHost/TypeLoader.cs b/src/MSBuildTaskHost/TypeLoader.cs index 5b4833472c4..00153e95b0a 100644 --- a/src/MSBuildTaskHost/TypeLoader.cs +++ b/src/MSBuildTaskHost/TypeLoader.cs @@ -127,10 +127,11 @@ internal static bool IsPartialTypeNameMatch(string typeName1, string typeName2) /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - internal LoadedType Load + internal TypeInformation Load ( string typeName, - AssemblyLoadInfo assembly + AssemblyLoadInfo assembly, + bool taskHostFactoryExplicitlyRequested ) { return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly); @@ -148,7 +149,7 @@ internal LoadedType ReflectionOnlyLoad AssemblyLoadInfo assembly ) { - return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly); + return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly).LoadedType; } /// @@ -156,7 +157,7 @@ AssemblyLoadInfo assembly /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - private LoadedType GetLoadedType(Concurrent.ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) + private TypeInformation GetLoadedType(Concurrent.ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) { // A given type filter have been used on a number of assemblies, Based on the type filter we will get another dictionary which // will map a specific AssemblyLoadInfo to a AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. @@ -233,12 +234,11 @@ internal AssemblyInfoToLoadedTypes(TypeFilter typeFilter, AssemblyLoadInfo loadI /// /// Determine if a given type name is in the assembly or not. Return null if the type is not in the assembly /// - internal LoadedType GetLoadedTypeByTypeName(string typeName) + internal TypeInformation GetLoadedTypeByTypeName(string typeName) { ErrorUtilities.VerifyThrowArgumentNull(typeName, nameof(typeName)); // Only one thread should be doing operations on this instance of the object at a time. - Type type = _typeNameToType.GetOrAdd(typeName, (key) => { if ((_assemblyLoadInfo.AssemblyName != null) && (typeName.Length > 0)) @@ -284,7 +284,7 @@ internal LoadedType GetLoadedTypeByTypeName(string typeName) return null; }); - return type != null ? new LoadedType(type, _assemblyLoadInfo, _loadedAssembly) : null; + return type != null ? new TypeInformation(new LoadedType(type, _assemblyLoadInfo, _loadedAssembly)) : null; } /// diff --git a/src/Shared/AssemblyLoadInfo.cs b/src/Shared/AssemblyLoadInfo.cs index 271a077efaa..0400d847b4e 100644 --- a/src/Shared/AssemblyLoadInfo.cs +++ b/src/Shared/AssemblyLoadInfo.cs @@ -176,7 +176,7 @@ private sealed class AssemblyLoadInfoWithFile : AssemblyLoadInfo /// internal AssemblyLoadInfoWithFile(string assemblyFile) { - ErrorUtilities.VerifyThrow(Path.IsPathRooted(assemblyFile), "Assembly file path should be rooted"); + ErrorUtilities.VerifyThrow(Path.IsPathRooted(assemblyFile), $"Assembly file path should be rooted: {assemblyFile}"); _assemblyFile = assemblyFile; } diff --git a/src/Shared/LoadedType.cs b/src/Shared/LoadedType.cs index eeae7eb79ab..cd29aacc43e 100644 --- a/src/Shared/LoadedType.cs +++ b/src/Shared/LoadedType.cs @@ -92,7 +92,7 @@ private void CheckForHardcodedSTARequirement() { AssemblyName assemblyName = _type.GetTypeInfo().Assembly.GetName(); Version lastVersionToForce = new Version(3, 5); - if (assemblyName.Version.CompareTo(lastVersionToForce) > 0) + if (assemblyName.Version > lastVersionToForce) { if (String.Equals(assemblyName.Name, "PresentationBuildTasks", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Shared/TaskLoader.cs b/src/Shared/TaskLoader.cs index c553d8e36ac..82e5bcb39b6 100644 --- a/src/Shared/TaskLoader.cs +++ b/src/Shared/TaskLoader.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +#if !NETFRAMEWORK using System.Linq; +#endif using System.Reflection; using Microsoft.Build.Framework; @@ -21,7 +23,7 @@ internal static class TaskLoader /// For saving the assembly that was loaded by the TypeLoader /// We only use this when the assembly failed to load properly into the appdomain /// - private static LoadedType s_resolverLoadedType; + private static TypeInformation s_resolverTypeInformation; #endif /// @@ -47,7 +49,7 @@ internal static bool IsTaskClass(Type type, object unused) /// /// Creates an ITask instance and returns it. /// - internal static ITask CreateTask(LoadedType loadedType, string taskName, string taskLocation, int taskLine, int taskColumn, LogError logError + internal static ITask CreateTask(TypeInformation typeInformation, string taskName, string taskLocation, int taskLine, int taskColumn, LogError logError #if FEATURE_APPDOMAIN , AppDomainSetup appDomainSetup #endif @@ -58,8 +60,8 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string ) { #if FEATURE_APPDOMAIN - bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute(); - s_resolverLoadedType = null; + bool separateAppDomain = typeInformation.HasLoadInSeparateAppDomainAttribute; + s_resolverTypeInformation = null; taskAppDomain = null; ITask taskInstanceInOtherAppDomain = null; #endif @@ -69,7 +71,7 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string #if FEATURE_APPDOMAIN if (separateAppDomain) { - if (!loadedType.Type.GetTypeInfo().IsMarshalByRef) + if (!typeInformation.IsMarshalByRef) { logError ( @@ -106,13 +108,13 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string } AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolver; - s_resolverLoadedType = loadedType; + s_resolverTypeInformation = typeInformation; taskAppDomain = AppDomain.CreateDomain(isOutOfProc ? "taskAppDomain (out-of-proc)" : "taskAppDomain (in-proc)", null, appDomainInfo); - if (loadedType.LoadedAssembly != null) + if (typeInformation.LoadedType?.LoadedAssembly != null) { - taskAppDomain.Load(loadedType.LoadedAssembly.GetName()); + taskAppDomain.Load(typeInformation.AssemblyName); } #if FEATURE_APPDOMAIN_UNHANDLED_EXCEPTION @@ -121,18 +123,18 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string #endif } } - else + else if (typeInformation.LoadedType is not null) #endif { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. - return (ITask)Activator.CreateInstance(loadedType.Type); + return (ITask)Activator.CreateInstance(typeInformation.LoadedType.Type); } #if FEATURE_APPDOMAIN - if (loadedType.Assembly.AssemblyFile != null) + if ((typeInformation.LoadInfo.AssemblyFile ?? typeInformation.LoadedType.Assembly.AssemblyFile) != null) { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(loadedType.Assembly.AssemblyFile, loadedType.Type.FullName); + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceFromAndUnwrap(typeInformation.LoadInfo.AssemblyFile ?? typeInformation.LoadedType.Assembly.AssemblyFile, typeInformation.TypeName); // this will force evaluation of the task class type and try to load the task assembly Type taskType = taskInstanceInOtherAppDomain.GetType(); @@ -140,24 +142,24 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string // If the types don't match, we have a problem. It means that our AppDomain was able to load // a task assembly using Load, and loaded a different one. I don't see any other choice than // to fail here. - if (taskType != loadedType.Type) + if (((typeInformation.LoadedType is not null) && taskType != typeInformation.LoadedType.Type) || + ((typeInformation.LoadedType is null) && + (!taskType.Assembly.Location.Equals(typeInformation.LoadInfo.AssemblyLocation) || !taskType.Name.Equals(typeInformation.TypeName)))) { - logError - ( - taskLocation, - taskLine, - taskColumn, - "ConflictingTaskAssembly", - loadedType.Assembly.AssemblyFile, - loadedType.Type.GetTypeInfo().Assembly.Location - ); + logError( + taskLocation, + taskLine, + taskColumn, + "ConflictingTaskAssembly", + typeInformation.LoadInfo.AssemblyFile ?? typeInformation.LoadedType.Assembly.AssemblyFile, + typeInformation.LoadInfo.AssemblyLocation ?? typeInformation.LoadedType.Type.GetTypeInfo().Assembly.Location); taskInstanceInOtherAppDomain = null; } } else { - taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName); + taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(typeInformation.LoadedType.Type.GetTypeInfo().Assembly.FullName, typeInformation.TypeName); } return taskInstanceInOtherAppDomain; @@ -183,12 +185,12 @@ internal static ITask CreateTask(LoadedType loadedType, string taskName, string /// internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) { - if ((s_resolverLoadedType?.LoadedAssembly != null)) + if ((s_resolverTypeInformation?.LoadedType?.LoadedAssembly != null)) { // Match the name being requested by the resolver with the FullName of the assembly we have loaded - if (args.Name.Equals(s_resolverLoadedType.LoadedAssembly.FullName, StringComparison.Ordinal)) + if (args.Name.Equals(s_resolverTypeInformation.LoadedType.LoadedAssembly.FullName, StringComparison.Ordinal)) { - return s_resolverLoadedType.LoadedAssembly; + return s_resolverTypeInformation.LoadedType.LoadedAssembly; } } @@ -200,10 +202,10 @@ internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) /// internal static void RemoveAssemblyResolver() { - if (s_resolverLoadedType != null) + if (s_resolverTypeInformation != null) { AppDomain.CurrentDomain.AssemblyResolve -= AssemblyResolver; - s_resolverLoadedType = null; + s_resolverTypeInformation = null; } } #endif diff --git a/src/Shared/TypeInformation.cs b/src/Shared/TypeInformation.cs new file mode 100644 index 00000000000..67d3d05862d --- /dev/null +++ b/src/Shared/TypeInformation.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; +#if !TASKHOST +using System.Reflection.Metadata; +#endif +using Microsoft.Build.Framework; + +#nullable disable + +namespace Microsoft.Build.Shared +{ + internal sealed class TypeInformation + { + internal string Path { get; set; } + internal AssemblyLoadInfo LoadInfo { get; set; } + internal string TypeName { get; set; } + + internal LoadedType LoadedType { get; set; } + + internal bool HasSTAThreadAttribute { get; set; } + internal bool HasLoadInSeparateAppDomainAttribute { get; set; } + internal bool IsMarshalByRef { get; set; } + internal bool ImplementsIGeneratedTask { get; set; } + internal AssemblyName AssemblyName { get; set; } + internal string Namespace { get; set; } +#if !TASKHOST + internal TypeInformation.PropertyInfo[] Properties { get; set; } +#endif + + internal TypeInformation() + { + } + + internal TypeInformation(LoadedType baseType) + { + LoadedType = baseType; + HasSTAThreadAttribute = LoadedType.HasSTAThreadAttribute(); + HasLoadInSeparateAppDomainAttribute = LoadedType.HasLoadInSeparateAppDomainAttribute(); + IsMarshalByRef = LoadedType.Type.GetTypeInfo().IsMarshalByRef; +#if TASKHOST + ImplementsIGeneratedTask = false; +#else + ImplementsIGeneratedTask = LoadedType.Type is IGeneratedTask; +#endif + AssemblyName = baseType.LoadedAssembly?.GetName(); + Namespace = LoadedType.Type.Namespace; + LoadInfo = LoadedType.Assembly; + TypeName = LoadedType.Type.FullName; + Path = baseType.Assembly.AssemblyFile; + } + + public System.Reflection.PropertyInfo[] GetProperties(BindingFlags flags) + { + if (LoadedType is null) + { + throw new NotImplementedException(); + } + else + { + return LoadedType.Type.GetProperties(flags); + } + } + + public System.Reflection.PropertyInfo GetProperty(string name, BindingFlags flags) + { + if (LoadedType is null) + { + throw new NotImplementedException(); + } + else + { + return LoadedType.Type.GetProperty(name, flags); + } + } + + internal struct PropertyInfo + { + public PropertyInfo(string name, Type propertyType, bool outputAttribute, bool requiredAttribute) + { + Name = name; + PropertyType = propertyType; + OutputAttribute = outputAttribute; + RequiredAttribute = requiredAttribute; + } + + public string Name { get; set; } + public Type PropertyType { get; set; } + public bool OutputAttribute { get; set; } + public bool RequiredAttribute { get; set; } + } + } +} diff --git a/src/Shared/TypeLoader.cs b/src/Shared/TypeLoader.cs index d296c8c7547..2769c2e9f24 100644 --- a/src/Shared/TypeLoader.cs +++ b/src/Shared/TypeLoader.cs @@ -4,11 +4,20 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +#if !NETFRAMEWORK +using System.Runtime.Loader; +#endif using System.Threading; +using Microsoft.Build.Framework; #nullable disable @@ -185,13 +194,14 @@ private static Assembly LoadAssembly(AssemblyLoadInfo assemblyLoadInfo) /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - internal LoadedType Load + internal TypeInformation Load ( string typeName, - AssemblyLoadInfo assembly + AssemblyLoadInfo assembly, + bool taskHostFactoryExplicitlyRequested ) { - return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly); + return GetLoadedType(s_cacheOfLoadedTypesByFilter, typeName, assembly, taskHostFactoryExplicitlyRequested); } /// @@ -206,7 +216,7 @@ internal LoadedType ReflectionOnlyLoad AssemblyLoadInfo assembly ) { - return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly); + return GetLoadedType(s_cacheOfReflectionOnlyLoadedTypesByFilter, typeName, assembly, false)?.LoadedType; } /// @@ -214,7 +224,11 @@ AssemblyLoadInfo assembly /// any) is unambiguous; otherwise, if there are multiple types with the same name in different namespaces, the first type /// found will be returned. /// - private LoadedType GetLoadedType(ConcurrentDictionary, ConcurrentDictionary> cache, string typeName, AssemblyLoadInfo assembly) + private TypeInformation GetLoadedType( + ConcurrentDictionary, ConcurrentDictionary> cache, + string typeName, + AssemblyLoadInfo assembly, + bool taskHostFactoryExplicitlyRequested) { // A given type filter have been used on a number of assemblies, Based on the type filter we will get another dictionary which // will map a specific AssemblyLoadInfo to a AssemblyInfoToLoadedTypes class which knows how to find a typeName in a given assembly. @@ -225,7 +239,7 @@ private LoadedType GetLoadedType(ConcurrentDictionary, AssemblyInfoToLoadedTypes typeNameToType = loadInfoToType.GetOrAdd(assembly, (_) => new AssemblyInfoToLoadedTypes(_isDesiredType, _)); - return typeNameToType.GetLoadedTypeByTypeName(typeName); + return typeNameToType.GetLoadedTypeByTypeName(typeName, taskHostFactoryExplicitlyRequested); } /// @@ -256,7 +270,12 @@ private class AssemblyInfoToLoadedTypes /// /// What is the type for the given type name, this may be null if the typeName does not map to a type. /// - private ConcurrentDictionary _typeNameToType; + private ConcurrentDictionary _typeNameToTypeInformation; + + /// + /// What is the type for the given type name, this may be null if the typeName does not map to a type. + /// + private ConcurrentDictionary _typeNameToTypeInformationTaskHost; /// /// List of public types in the assembly which match the type filter and their corresponding types @@ -285,65 +304,352 @@ internal AssemblyInfoToLoadedTypes(Func typeFilter, Assembly _isDesiredType = typeFilter; _assemblyLoadInfo = loadInfo; - _typeNameToType = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _typeNameToTypeInformation = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + _typeNameToTypeInformationTaskHost = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); _publicTypeNameToType = new Dictionary(StringComparer.OrdinalIgnoreCase); } /// /// Determine if a given type name is in the assembly or not. Return null if the type is not in the assembly /// - internal LoadedType GetLoadedTypeByTypeName(string typeName) + internal TypeInformation GetLoadedTypeByTypeName(string typeName, bool taskHostFactoryExplicitlyRequested) { ErrorUtilities.VerifyThrowArgumentNull(typeName, nameof(typeName)); // Only one thread should be doing operations on this instance of the object at a time. + TypeInformation typeInfo = taskHostFactoryExplicitlyRequested ? + _typeNameToTypeInformationTaskHost.GetOrAdd(typeName, key => FindTypeInformationUsingSystemReflectionMetadata(typeName)) : + _typeNameToTypeInformation.GetOrAdd(typeName, key => FindTypeInformationUsingLoadedType(typeName) + ); + + return typeInfo; + } - Type type = _typeNameToType.GetOrAdd(typeName, (key) => + /// + /// The user has not explicitly requested a TaskHost; load the type and use it to find relevant information. + /// + /// The type to find. + /// A with a LoadedType indicating relevant information. + private TypeInformation FindTypeInformationUsingLoadedType(string typeName) + { + if ((_assemblyLoadInfo.AssemblyName != null) && (typeName.Length > 0)) { - if ((_assemblyLoadInfo.AssemblyName != null) && (typeName.Length > 0)) + try { - try + // try to load the type using its assembly qualified name + Type t2 = Type.GetType(typeName + "," + _assemblyLoadInfo.AssemblyName, false /* don't throw on error */, true /* case-insensitive */); + if (t2 != null) { - // try to load the type using its assembly qualified name - Type t2 = Type.GetType(typeName + "," + _assemblyLoadInfo.AssemblyName, false /* don't throw on error */, true /* case-insensitive */); - if (t2 != null) - { - return !_isDesiredType(t2, null) ? null : t2; - } + return _isDesiredType(t2, null) ? new TypeInformation(new LoadedType(t2, _assemblyLoadInfo, _loadedAssembly)) : null; } - catch (ArgumentException) + } + catch (ArgumentException) + { + // Type.GetType() will throw this exception if the type name is invalid -- but we have no idea if it's the + // type or the assembly name that's the problem -- so just ignore the exception, because we're going to + // check the existence/validity of the assembly and type respectively, below anyway + } + } + + if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) + { + lock (_lockObject) + { + if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) { - // Type.GetType() will throw this exception if the type name is invalid -- but we have no idea if it's the - // type or the assembly name that's the problem -- so just ignore the exception, because we're going to - // check the existence/validity of the assembly and type respectively, below anyway + ScanAssemblyForPublicTypes(); + Interlocked.Exchange(ref _haveScannedPublicTypes, ~0); } } + } + + foreach (KeyValuePair desiredTypeInAssembly in _publicTypeNameToType) + { + // if type matches partially on its name + if (typeName.Length == 0 || TypeLoader.IsPartialTypeNameMatch(desiredTypeInAssembly.Key, typeName)) + { + return new TypeInformation(new LoadedType(desiredTypeInAssembly.Value, _assemblyLoadInfo, _loadedAssembly)); + } + } + + return null; + } - if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) + /// + /// Find type information using System.Reflection.Metadata to avoid loading (and locking) its containing assembly. + /// + /// The type to find. + /// A indicating relevant information about typeName. + private TypeInformation FindTypeInformationUsingSystemReflectionMetadata(string typeName) + { + string path = _assemblyLoadInfo.AssemblyFile; + + // This should only be true for Microsoft.Build assemblies. We use this for testing. + if (path is null) + { +#if NETFRAMEWORK + AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation; + setup.LoaderOptimization = LoaderOptimization.SingleDomain; + AppDomain appDomain = AppDomain.CreateDomain("appDomainToFindPath", null, setup); + path = appDomain.Load(new AssemblyName(_assemblyLoadInfo.AssemblyName)).Location; + AppDomain.Unload(appDomain); +#else + AssemblyLoadContext alc = new("loadContextToFindPath", true); + path = alc.LoadFromAssemblyName(new AssemblyName(_assemblyLoadInfo.AssemblyName)).Location; + alc.Unload(); +#endif + } + + using (FileStream stream = File.OpenRead(path)) + using (PEReader peFile = new(stream)) + { + MetadataReader metadataReader = peFile.GetMetadataReader(); + AssemblyDefinition assemblyDef = metadataReader.GetAssemblyDefinition(); + foreach (TypeDefinitionHandle typeDefHandle in metadataReader.TypeDefinitions) + { + TypeDefinition typeDef = metadataReader.GetTypeDefinition(typeDefHandle); + if (TryGetTypeInformationFromDefinition(metadataReader, typeDef, typeName, out TypeInformation typeInformation)) + { + typeInformation.Path = path; + return typeInformation; + } + } + } + + return null; + } + + /// + /// Tries to find information about the type. + /// + /// for the assembly containing the type. + /// indicating the type currently under consideration. + /// The name of the task type to find. + /// The information, if we find it. + /// True if this type or one of its children matches typeName. False otherwise. + private bool TryGetTypeInformationFromDefinition(MetadataReader metadataReader, TypeDefinition typeDef, string typeName, out TypeInformation typeInformation) + { + typeInformation = null; + string currentTypeName = metadataReader.GetString(typeDef.Name); + + if (!(typeDef.Attributes.HasFlag(TypeAttributes.Public) || typeDef.Attributes.HasFlag(TypeAttributes.NestedPublic)) || !typeDef.Attributes.HasFlag(TypeAttributes.Class)) + { + return false; + } + + if (currentTypeName.Length != 0 && !TypeLoader.IsPartialTypeNameMatch(currentTypeName, typeName)) + { + foreach (TypeDefinitionHandle typeDefHandle in typeDef.GetNestedTypes()) + { + TypeDefinition childTypeDef = metadataReader.GetTypeDefinition(typeDefHandle); + if (TryGetTypeInformationFromDefinition(metadataReader, childTypeDef, typeName, out typeInformation)) + { + return true; + } + } + return false; + } + + // We found the right type! Now get its information. + typeInformation = new(); + + foreach (CustomAttributeHandle customAttrHandle in typeDef.GetCustomAttributes()) + { + CustomAttribute customAttribute = metadataReader.GetCustomAttribute(customAttrHandle); + MemberReference constructorReference = metadataReader.GetMemberReference((MemberReferenceHandle)customAttribute.Constructor); + if (constructorReference.Parent.Kind == HandleKind.TypeReference) { - lock (_lockObject) + TypeReference typeReference = metadataReader.GetTypeReference((TypeReferenceHandle)constructorReference.Parent); + string customAttributeName = metadataReader.GetString(typeReference.Name); + switch (customAttributeName) { - if (Interlocked.Read(ref _haveScannedPublicTypes) == 0) + case "RunInSTAAttribute": + typeInformation.HasSTAThreadAttribute = true; + break; + case "LoadInSeparateAppDomainAttribute": + typeInformation.HasLoadInSeparateAppDomainAttribute = true; + break; + } + } + } + + IEnumerable propertyDefinitions = typeDef.GetProperties().Select(prop => metadataReader.GetPropertyDefinition(prop)); + List typePropertyInfos = new(); + foreach (PropertyDefinition propertyDefinition in propertyDefinitions) + { + TypeInformation.PropertyInfo toAdd = new(); + toAdd.Name = metadataReader.GetString(propertyDefinition.Name); + SignatureDecoder decoder = new(ConstantSignatureVisualizer.Instance, metadataReader, genericContext: null); + BlobReader blob = metadataReader.GetBlobReader(propertyDefinition.Signature); + MethodSignature signature = decoder.DecodeMethodSignature(ref blob); + toAdd.PropertyType = StringToType(signature.ReturnType); + toAdd.OutputAttribute = false; + toAdd.RequiredAttribute = false; + foreach (CustomAttributeHandle attr in propertyDefinition.GetCustomAttributes()) + { + EntityHandle referenceHandle = metadataReader.GetMemberReference((MemberReferenceHandle)metadataReader.GetCustomAttribute(attr).Constructor).Parent; + if (referenceHandle.Kind == HandleKind.TypeReference) + { + string name = metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)referenceHandle).Name); + if (name.Equals("OutputAttribute", StringComparison.OrdinalIgnoreCase)) + { + toAdd.OutputAttribute = true; + } + else if (name.Equals("RequiredAttribute", StringComparison.OrdinalIgnoreCase)) { - ScanAssemblyForPublicTypes(); - Interlocked.Exchange(ref _haveScannedPublicTypes, ~0); + toAdd.RequiredAttribute = true; } } } + typePropertyInfos.Add(toAdd); + } + typeInformation.Properties = typePropertyInfos.ToArray(); - foreach (KeyValuePair desiredTypeInAssembly in _publicTypeNameToType) + TypeDefinition parentTypeDefinition = typeDef; + while (true) + { + foreach (InterfaceImplementationHandle interfaceHandle in parentTypeDefinition.GetInterfaceImplementations()) { - // if type matches partially on its name - if (typeName.Length == 0 || TypeLoader.IsPartialTypeNameMatch(desiredTypeInAssembly.Key, typeName)) + if (metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)metadataReader.GetInterfaceImplementation(interfaceHandle).Interface).Name).Equals("IGeneratedTask")) { - return desiredTypeInAssembly.Value; + typeInformation.ImplementsIGeneratedTask = true; } } - return null; - }); + if (parentTypeDefinition.BaseType.IsNil) + { + break; + } + + // If the baseType is not a TypeDefinitionHandle, we won't be able to chase it without actually loading the assembly. We would need to find the assembly containing the base type + // and load it using System.Reflection.Metdata just as we're doing here, but we don't know its path without loading this assembly. Just assume it didn't implement IGeneratedTask. + bool shouldBreakLoop = false; + switch (parentTypeDefinition.BaseType.Kind) + { + case HandleKind.TypeDefinition: + parentTypeDefinition = metadataReader.GetTypeDefinition((TypeDefinitionHandle)parentTypeDefinition.BaseType); + break; + case HandleKind.TypeReference: + string parentName = metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)parentTypeDefinition.BaseType).Name); + if (parentName.Equals("IGeneratedTask")) + { + typeInformation.ImplementsIGeneratedTask = true; + } + else if (parentName.Equals("MarshalByRefObject")) + { + typeInformation.IsMarshalByRef = true; + } + shouldBreakLoop = true; + break; + case HandleKind.TypeSpecification: + shouldBreakLoop = true; + break; + } + + string typeDefinitionName = metadataReader.GetString(parentTypeDefinition.Name); + if (typeDefinitionName.Equals("MarshalByRefObject")) + { + typeInformation.IsMarshalByRef = true; + } + if (shouldBreakLoop || typeDefinitionName.Equals("object")) + { + break; + } + } + + foreach (InterfaceImplementationHandle interfaceHandle in typeDef.GetInterfaceImplementations()) + { + if (metadataReader.GetString(metadataReader.GetTypeReference((TypeReferenceHandle)metadataReader.GetInterfaceImplementation(interfaceHandle).Interface).Name).Equals("IGeneratedTask")) + { + typeInformation.ImplementsIGeneratedTask = true; + } + } + + typeInformation.AssemblyName = _assemblyLoadInfo.AssemblyName is null ? new AssemblyName(Path.GetFileNameWithoutExtension(_assemblyLoadInfo.AssemblyFile)) : new AssemblyName(_assemblyLoadInfo.AssemblyName); + + typeInformation.Namespace = metadataReader.GetString(metadataReader.GetNamespaceDefinition(metadataReader.GetNamespaceDefinitionRoot().NamespaceDefinitions.First()).Name); - return type != null ? new LoadedType(type, _assemblyLoadInfo, _loadedAssembly) : null; + return true; + } + + private Type StringToType(string s) + { + // return Type.GetType(s, false, true) ?? typeof(object); + // would be a much cleaner implementation of StringToType, but it unfortunately + // expects not just the type name but also its namespace like "System,Int32" + // rather than just "Int32" as we get from decoding the TypeDefinition's signature. + return s switch + { + "String" => typeof(String), + "Microsoft.Build.Framework.ITaskItem" => typeof(ITaskItem), + "Boolean" => typeof(Boolean), + "Int32" => typeof(Int32), + "Char" => typeof(Char), + "Single" => typeof(Single), + "Int64" => typeof(Int64), + "Double" => typeof(Double), + "Byte" => typeof(Byte), + "SByte" => typeof(SByte), + "Decimal" => typeof(Decimal), + "UInt32" => typeof(UInt32), + "IntPtr" => typeof(IntPtr), + "UIntPtr" => typeof(UIntPtr), + "UInt64" => typeof(UInt64), + "Int16" => typeof(Int16), + "UInt16" => typeof(UInt16), + "String[]" => typeof(String[]), + "Microsoft.Build.Framework.ITaskItem[]" => typeof(ITaskItem[]), + "Boolean[]" => typeof(Boolean[]), + "Int32[]" => typeof(Int32[]), + "Char[]" => typeof(Char[]), + "Single[]" => typeof(Single[]), + "Int64[]" => typeof(Int64[]), + "Double[]" => typeof(Double[]), + "Byte[]" => typeof(Byte[]), + "SByte[]" => typeof(SByte[]), + "Decimal[]" => typeof(Decimal[]), + "UInt32[]" => typeof(UInt32[]), + "IntPtr[]" => typeof(IntPtr[]), + "UIntPtr[]" => typeof(UIntPtr[]), + "UInt64[]" => typeof(UInt64[]), + "Int16[]" => typeof(Int16[]), + "UInt16[]" => typeof(UInt16[]), + "String?" => typeof(String), + "Microsoft.Build.Framework.ITaskItem?" => typeof(ITaskItem), + "Boolean?" => typeof(Boolean?), + "Int32?" => typeof(Int32?), + "Char?" => typeof(Char?), + "Single?" => typeof(Single?), + "Int64?" => typeof(Int64?), + "Double?" => typeof(Double?), + "Byte?" => typeof(Byte?), + "SByte?" => typeof(SByte?), + "Decimal?" => typeof(Decimal?), + "UInt32?" => typeof(UInt32?), + "IntPtr?" => typeof(IntPtr?), + "UIntPtr?" => typeof(UIntPtr?), + "UInt64?" => typeof(UInt64?), + "Int16?" => typeof(Int16?), + "UInt16?" => typeof(UInt16?), + "String?[]" => typeof(String[]), + "Microsoft.Build.Framework.ITaskItem?[]" => typeof(ITaskItem[]), + "Boolean?[]" => typeof(Boolean?[]), + "Int32?[]" => typeof(Int32?[]), + "Char?[]" => typeof(Char?[]), + "Single?[]" => typeof(Single?[]), + "Int64?[]" => typeof(Int64?[]), + "Double?[]" => typeof(Double?[]), + "Byte?[]" => typeof(Byte?[]), + "SByte?[]" => typeof(SByte?[]), + "Decimal?[]" => typeof(Decimal?[]), + "UInt32?[]" => typeof(UInt32?[]), + "IntPtr?[]" => typeof(IntPtr?[]), + "UIntPtr?[]" => typeof(UIntPtr?[]), + "UInt64?[]" => typeof(UInt64?[]), + "Int16?[]" => typeof(Int16?[]), + "UInt16?[]" => typeof(UInt16?[]), + _ => typeof(object), + }; } /// @@ -367,5 +673,64 @@ private void ScanAssemblyForPublicTypes() } } } + + // Copied from https://github.com/dotnet/roslyn/blob/a9027f3d3bddcd77eb3c97bf0caba61335c08426/src/Compilers/Test/Core/Metadata/MetadataReaderUtils.cs#L405 + private sealed class ConstantSignatureVisualizer : ISignatureTypeProvider + { + public static readonly ConstantSignatureVisualizer Instance = new(); + + public string GetArrayType(string elementType, ArrayShape shape) + => elementType + "[" + new string(',', shape.Rank) + "]"; + + public string GetByReferenceType(string elementType) + => elementType + "&"; + + public string GetFunctionPointerType(MethodSignature signature) + => "method-ptr"; + + public string GetGenericInstantiation(string genericType, ImmutableArray typeArguments) + => genericType + "{" + string.Join(", ", typeArguments) + "}"; + + public string GetGenericMethodParameter(object genericContext, int index) + => "!!" + index; + + public string GetGenericTypeParameter(object genericContext, int index) + => "!" + index; + + public string GetModifiedType(string modifier, string unmodifiedType, bool isRequired) + => (isRequired ? "modreq" : "modopt") + "(" + modifier + ") " + unmodifiedType; + + public string GetPinnedType(string elementType) + => "pinned " + elementType; + + public string GetPointerType(string elementType) + => elementType + "*"; + + public string GetPrimitiveType(PrimitiveTypeCode typeCode) + => typeCode.ToString(); + + public string GetSZArrayType(string elementType) + => elementType + "[]"; + + public string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + var typeDef = reader.GetTypeDefinition(handle); + var name = reader.GetString(typeDef.Name); + return typeDef.Namespace.IsNil ? name : reader.GetString(typeDef.Namespace) + "." + name; + } + + public string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + var typeRef = reader.GetTypeReference(handle); + var name = reader.GetString(typeRef.Name); + return typeRef.Namespace.IsNil ? name : reader.GetString(typeRef.Namespace) + "." + name; + } + + public string GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + var sigReader = reader.GetBlobReader(reader.GetTypeSpecification(handle).Signature); + return new SignatureDecoder(Instance, reader, genericContext).DecodeType(ref sigReader); + } + } } } diff --git a/src/Shared/UnitTests/TestEnvironment.cs b/src/Shared/UnitTests/TestEnvironment.cs index 33ed94cc123..d4a4e38926e 100644 --- a/src/Shared/UnitTests/TestEnvironment.cs +++ b/src/Shared/UnitTests/TestEnvironment.cs @@ -473,7 +473,7 @@ public override void AssertInvariant(ITestOutputHelper output) } // Assert file count is equal minus any files that were OK - Assert.Equal(_originalFiles.Length, newFilesCount); + newFilesCount.ShouldBe(_originalFiles.Length, "Files to check: " + string.Join(" ", newFiles.Except(_originalFiles))); } } diff --git a/src/Shared/UnitTests/TypeLoader_Tests.cs b/src/Shared/UnitTests/TypeLoader_Tests.cs index 9fb112d8c03..bbb55d19105 100644 --- a/src/Shared/UnitTests/TypeLoader_Tests.cs +++ b/src/Shared/UnitTests/TypeLoader_Tests.cs @@ -200,13 +200,13 @@ public void Regress640476PartialName() { string forwardingLoggerLocation = typeof(Microsoft.Build.Logging.ConfigurableForwardingLogger).Assembly.Location; TypeLoader loader = new TypeLoader(IsForwardingLoggerClass); - LoadedType loadedType = loader.Load("ConfigurableForwardingLogger", AssemblyLoadInfo.Create(null, forwardingLoggerLocation)); + LoadedType loadedType = loader.Load("ConfigurableForwardingLogger", AssemblyLoadInfo.Create(null, forwardingLoggerLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(forwardingLoggerLocation, loadedType.Assembly.AssemblyLocation); string fileLoggerLocation = typeof(Microsoft.Build.Logging.FileLogger).Assembly.Location; loader = new TypeLoader(IsLoggerClass); - loadedType = loader.Load("FileLogger", AssemblyLoadInfo.Create(null, fileLoggerLocation)); + loadedType = loader.Load("FileLogger", AssemblyLoadInfo.Create(null, fileLoggerLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(fileLoggerLocation, loadedType.Assembly.AssemblyLocation); } @@ -221,14 +221,14 @@ public void Regress640476FullyQualifiedName() Type forwardingLoggerType = typeof(Microsoft.Build.Logging.ConfigurableForwardingLogger); string forwardingLoggerLocation = forwardingLoggerType.Assembly.Location; TypeLoader loader = new TypeLoader(IsForwardingLoggerClass); - LoadedType loadedType = loader.Load(forwardingLoggerType.FullName, AssemblyLoadInfo.Create(null, forwardingLoggerLocation)); + LoadedType loadedType = loader.Load(forwardingLoggerType.FullName, AssemblyLoadInfo.Create(null, forwardingLoggerLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(forwardingLoggerLocation, loadedType.Assembly.AssemblyLocation); Type fileLoggerType = typeof(Microsoft.Build.Logging.FileLogger); string fileLoggerLocation = fileLoggerType.Assembly.Location; loader = new TypeLoader(IsLoggerClass); - loadedType = loader.Load(fileLoggerType.FullName, AssemblyLoadInfo.Create(null, fileLoggerLocation)); + loadedType = loader.Load(fileLoggerType.FullName, AssemblyLoadInfo.Create(null, fileLoggerLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(fileLoggerLocation, loadedType.Assembly.AssemblyLocation); } @@ -248,7 +248,7 @@ public void NoTypeNamePicksFirstType() Type firstPublicType = FirstPublicDesiredType(forwardingLoggerfilter, forwardingLoggerAssemblyLocation); TypeLoader loader = new TypeLoader(forwardingLoggerfilter); - LoadedType loadedType = loader.Load(String.Empty, AssemblyLoadInfo.Create(null, forwardingLoggerAssemblyLocation)); + LoadedType loadedType = loader.Load(String.Empty, AssemblyLoadInfo.Create(null, forwardingLoggerAssemblyLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(forwardingLoggerAssemblyLocation, loadedType.Assembly.AssemblyLocation); Assert.Equal(firstPublicType, loadedType.Type); @@ -260,7 +260,7 @@ public void NoTypeNamePicksFirstType() firstPublicType = FirstPublicDesiredType(fileLoggerfilter, fileLoggerAssemblyLocation); loader = new TypeLoader(fileLoggerfilter); - loadedType = loader.Load(String.Empty, AssemblyLoadInfo.Create(null, fileLoggerAssemblyLocation)); + loadedType = loader.Load(String.Empty, AssemblyLoadInfo.Create(null, fileLoggerAssemblyLocation), false).LoadedType; Assert.NotNull(loadedType); Assert.Equal(fileLoggerAssemblyLocation, loadedType.Assembly.AssemblyLocation); Assert.Equal(firstPublicType, loadedType.Type); diff --git a/src/Tasks.UnitTests/PortableTasks_Tests.cs b/src/Tasks.UnitTests/PortableTasks_Tests.cs index 20353efc583..b8c4cc9e3a8 100644 --- a/src/Tasks.UnitTests/PortableTasks_Tests.cs +++ b/src/Tasks.UnitTests/PortableTasks_Tests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -54,7 +55,7 @@ private void RunMSBuildOnProjectWithPortableTaskAndAssertOutput(bool useDesktopM // "Debug", "netstandard1.3" DirectoryInfo ProjectFileFolder = - new DirectoryInfo(PortableTaskFolderPath).EnumerateDirectories().First().EnumerateDirectories().First(); + new DirectoryInfo(PortableTaskFolderPath).EnumerateDirectories().First().EnumerateDirectories().First(n => n.Name.Equals("netstandard2.0", StringComparison.OrdinalIgnoreCase)); foreach (var file in ProjectFileFolder.GetFiles()) {