| File: AssemblyDependency\Node\RarTaskParameters.cs | Web Access |
| Project: src\msbuild\src\Tasks\Microsoft.Build.Tasks.csproj (Microsoft.Build.Tasks.Core) |
// 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.Collections.Generic; using System.Reflection; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; namespace Microsoft.Build.Tasks.AssemblyDependency { /// <summary> /// Reflection-based discovery of RAR's input and output parameters. /// This is nearly identical to how the engine discovers task parameters, but with support for skipping unset values /// and filtering for any properties we want to handle ourselves for serialization performance. /// </summary> internal static class RarTaskParameters { /// <summary> /// A cache of reflected properties for the ResolveAssemblyReference task. /// </summary> private static ReflectedProperties? s_reflectedProperties; // <summary> // Selector for task input or output parameters. // </summary> internal enum ParameterType { /// <summary> /// Task input. /// </summary> Input, /// <summary> /// Task output. /// </summary> Output, } // <summary> // Initializes cached reflected properties. // </summary> internal static void Init() => s_reflectedProperties ??= new ReflectedProperties(); /// <summary> /// Creates a mapping from each cached parameter name to its current value on the RAR task. /// Only properties that differ from their default values are included. /// </summary> /// <param name="parameterType">The type of parameters to retrieve.</param> /// <param name="rar">The RAR task instance to extract parameters from.</param> /// <returns>A mapping from each set parameter name to a serializable representation of its value.</returns> internal static Dictionary<string, TaskParameter> Get(ParameterType parameterType, ResolveAssemblyReference rar) { s_reflectedProperties ??= new ReflectedProperties(); ReflectedPropertyInfo[] properties = parameterType == ParameterType.Input ? s_reflectedProperties.Inputs : s_reflectedProperties.Outputs; Dictionary<string, TaskParameter> taskParameters = new(properties.Length, StringComparer.Ordinal); foreach (ReflectedPropertyInfo property in properties) { object? value = property.Target.GetValue(rar); if (IsParameterExplicitlySet(value, property.DefaultValue)) { taskParameters[property.Target.Name] = new TaskParameter(value); } } return taskParameters; // Determines if a parameter is explicitly set by comparing its current value against our cached default value. static bool IsParameterExplicitlySet(object? value, object? defaultValue) { // .NET Framework doesn't have an allocation-free SequenceEqual, so manually compare array elements. if (value is string[] stringArray && defaultValue is string[] defaultStringArray && stringArray.Length == defaultStringArray.Length) { for (int i = 0; i < stringArray.Length; i++) { if (!string.Equals(stringArray[i], defaultStringArray[i], StringComparison.Ordinal)) { return true; } } return false; } // Use Equals instead of '==' to correctly handle value equality. return value?.Equals(defaultValue) == false; } } /// <summary> /// Sets each property on the RAR task from a previously extracted mapping of parameters. /// </summary> /// <param name="parameterType">The type of parameters in the dictionary.</param> /// <param name="rar">The RAR task instance to set parameters on.</param> /// <param name="parameters">A previously created mapping from parameter name to its wrapped value.</param> internal static void Set(ParameterType parameterType, ResolveAssemblyReference rar, Dictionary<string, TaskParameter> parameters) { s_reflectedProperties ??= new ReflectedProperties(); ReflectedPropertyInfo[] properties = parameterType == ParameterType.Input ? s_reflectedProperties.Inputs : s_reflectedProperties.Outputs; foreach (ReflectedPropertyInfo property in properties) { if (parameters.TryGetValue(property.Target.Name, out TaskParameter? parameter)) { property.Target.SetValue(rar, parameter.WrappedParameter); } } } private readonly struct ReflectedPropertyInfo(PropertyInfo target, object? defaultValue) { internal PropertyInfo Target { get; } = target; internal object? DefaultValue { get; } = defaultValue; } private class ReflectedProperties { internal ReflectedProperties() { List<ReflectedPropertyInfo> inputs = []; List<ReflectedPropertyInfo> outputs = []; PropertyInfo[] properties = typeof(ResolveAssemblyReference) .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); // Create a throwaway instance to capture the default values for each property. ResolveAssemblyReference rar = new(); foreach (PropertyInfo property in properties) { if (property.SetMethod == null) { // This may happen if a new output property is added to RAR without a setter. // Ignore to avoid failing at runtime. // TODO: Consider adding a UT to validate at build time and remove the check. continue; } ReflectedPropertyInfo reflectedProperty = new(property, property.GetValue(rar)); // Outputs - must be annotated with OutputAttribute. // Inputs - everything else. if (property.GetCustomAttribute<OutputAttribute>() != null) { // Exclude CopyLocalFiles since it is a list of references - otherwise we'll end up with duplicated task item instances. // Also exclude FilesWritten since we can't externally set it due to visibility. RAR will derive it before returning our result. if (!string.Equals(property.Name, nameof(ResolveAssemblyReference.CopyLocalFiles), StringComparison.Ordinal) && !string.Equals(property.Name, nameof(ResolveAssemblyReference.FilesWritten), StringComparison.Ordinal)) { outputs.Add(reflectedProperty); } } else { // Exclude TaskEnvironment since it is not a serializable parameter type for cross-process communication. // It should be set to each task instance after deserialization. if (!string.Equals(property.Name, nameof(ResolveAssemblyReference.TaskEnvironment), StringComparison.Ordinal)) { inputs.Add(reflectedProperty); } } } Inputs = [.. inputs]; Outputs = [.. outputs]; } internal ReflectedPropertyInfo[] Inputs { get; } internal ReflectedPropertyInfo[] Outputs { get; } } } }