File: BackEnd\Shared\ConfigurationMetadata.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// 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.Diagnostics;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
 
#nullable disable
 
namespace Microsoft.Build.BackEnd
{
    /// <summary>
    /// A struct representing the uniquely-identifying portion of a BuildRequestConfiguration.  Used for lookups.
    /// </summary>
    [DebuggerDisplay(@"{DebugString()}")]
    internal class ConfigurationMetadata : IEquatable<ConfigurationMetadata>, ITranslatable
    {
        /// <summary>
        /// Constructor over a BuildRequestConfiguration.
        /// </summary>
        public ConfigurationMetadata(BuildRequestConfiguration configuration)
        {
            ErrorUtilities.VerifyThrowArgumentNull(configuration);
            _globalProperties = new PropertyDictionary<ProjectPropertyInstance>(configuration.GlobalProperties);
            _projectFullPath = FileUtilities.NormalizePath(configuration.ProjectFullPath);
            _toolsVersion = configuration.ToolsVersion;
        }
 
        /// <summary>
        /// Constructor over a Project.
        /// </summary>
        public ConfigurationMetadata(Project project)
        {
            ErrorUtilities.VerifyThrowArgumentNull(project);
            _globalProperties = new PropertyDictionary<ProjectPropertyInstance>(project.GlobalPropertiesCount);
            foreach (KeyValuePair<string, string> entry in project.GlobalPropertiesEnumerable)
            {
                _globalProperties[entry.Key] = ProjectPropertyInstance.Create(entry.Key, entry.Value);
            }
 
            _toolsVersion = project.ToolsVersion;
            _projectFullPath = FileUtilities.NormalizePath(project.FullPath);
        }
 
        /// <summary>
        /// Constructor taking individual arguments.
        /// </summary>
        public ConfigurationMetadata(string projectFullPath, PropertyDictionary<ProjectPropertyInstance> globalProperties)
        {
            ErrorUtilities.VerifyThrowArgumentLength(projectFullPath);
            ErrorUtilities.VerifyThrowArgumentNull(globalProperties);
 
            _projectFullPath = projectFullPath;
            _toolsVersion = MSBuildConstants.CurrentToolsVersion;
            _globalProperties = globalProperties;
        }
 
        public ConfigurationMetadata(ITranslator translator)
        {
            Translate(translator);
        }
 
        private string _projectFullPath;
 
        /// <summary>
        /// The full path to the project to build.
        /// </summary>
        public string ProjectFullPath => _projectFullPath;
 
        private string _toolsVersion;
 
        /// <summary>
        /// The tools version specified for the configuration.
        /// Always specified.
        /// May have originated from a /tv switch, or an MSBuild task,
        /// or a Project tag, or the default.
        /// </summary>
        public string ToolsVersion => _toolsVersion;
 
        private PropertyDictionary<ProjectPropertyInstance> _globalProperties;
 
        /// <summary>
        /// The set of global properties which should be used when building this project.
        /// </summary>
        public PropertyDictionary<ProjectPropertyInstance> GlobalProperties => _globalProperties;
 
        /// <summary>
        /// This override is used to provide a hash code for storage in dictionaries and the like.
        /// </summary>
        /// <remarks>
        /// If two objects are Equal, they must have the same hash code, for dictionaries to work correctly.
        /// Two configurations are Equal if their global properties are equivalent, not necessary reference equals.
        /// So only include filename and tools version in the hashcode.
        /// </remarks>
        /// <returns>A hash code</returns>
        public override int GetHashCode()
        {
            return StringComparer.OrdinalIgnoreCase.GetHashCode(ProjectFullPath) ^ StringComparer.OrdinalIgnoreCase.GetHashCode(ToolsVersion);
        }
 
        public void Translate(ITranslator translator)
        {
            translator.Translate(ref _projectFullPath);
            translator.Translate(ref _toolsVersion);
            translator.TranslateDictionary(ref _globalProperties, ProjectPropertyInstance.FactoryForDeserialization);
        }
 
        public static ConfigurationMetadata FactoryForDeserialization(ITranslator translator)
        {
            return new ConfigurationMetadata(translator);
        }
 
        /// <summary>
        /// Determines object equality
        /// </summary>
        /// <param name="obj">The object to compare with</param>
        /// <returns>True if they contain the same data, false otherwise</returns>
        public override bool Equals(object obj)
        {
            if (obj is null)
            {
                return false;
            }
 
            if (GetType() != obj.GetType())
            {
                return false;
            }
 
            return InternalEquals((ConfigurationMetadata)obj);
        }
 
        #region IEquatable<ConfigurationMetadata> Members
 
        /// <summary>
        /// Equality of the configuration is the product of the equality of its members.
        /// </summary>
        /// <param name="other">The other configuration to which we will compare ourselves.</param>
        /// <returns>True if equal, false otherwise.</returns>
        public bool Equals(ConfigurationMetadata other)
        {
            if (other is null)
            {
                return false;
            }
 
            return InternalEquals(other);
        }
 
        #endregion
 
        /// <summary>
        /// Compares this object with another for equality
        /// </summary>
        /// <param name="other">The object with which to compare this one.</param>
        /// <returns>True if the objects contain the same data, false otherwise.</returns>
        private bool InternalEquals(ConfigurationMetadata other)
        {
            if (ReferenceEquals(this, other))
            {
                return true;
            }
            return ProjectFullPath.Equals(other.ProjectFullPath, StringComparison.OrdinalIgnoreCase) &&
                ToolsVersion.Equals(other.ToolsVersion, StringComparison.OrdinalIgnoreCase) &&
                GlobalProperties.Equals(other.GlobalProperties);
        }
 
        private string DebugString()
        {
            var truncatedProjectFile = FileUtilities.TruncatePathToTrailingSegments(ProjectFullPath, 2);
 
            return
                $"{truncatedProjectFile}, #GlobalProps={GlobalProperties.Count}";
        }
    }
}