diff --git a/src/Build.UnitTests/Collections/CopyOnWritePropertyDictionary_Tests.cs b/src/Build.UnitTests/Collections/CopyOnWritePropertyDictionary_Tests.cs index 882753a8a50..4866a721238 100644 --- a/src/Build.UnitTests/Collections/CopyOnWritePropertyDictionary_Tests.cs +++ b/src/Build.UnitTests/Collections/CopyOnWritePropertyDictionary_Tests.cs @@ -180,7 +180,7 @@ public void ImportProperties() public void DeepClone() { CopyOnWritePropertyDictionary source = CreateInstance("a", "b", "c"); - CopyOnWritePropertyDictionary clone = source.DeepClone(); + CopyOnWritePropertyDictionary clone = (CopyOnWritePropertyDictionary)source.DeepClone(); source.ShouldBe(clone); source.ShouldNotBeSameAs(clone); diff --git a/src/Build.UnitTests/Instance/TaskItem_Tests.cs b/src/Build.UnitTests/Instance/TaskItem_Tests.cs index 9a9cc0eb4ca..c17a194274c 100644 --- a/src/Build.UnitTests/Instance/TaskItem_Tests.cs +++ b/src/Build.UnitTests/Instance/TaskItem_Tests.cs @@ -194,7 +194,7 @@ public void Metadata() item.MetadataCount.ShouldBe(s_builtInMetadataNames.Length + 2); item.DirectMetadataCount.ShouldBe(1); - CopyOnWritePropertyDictionary metadata = item.MetadataCollection; + ICopyOnWritePropertyDictionary metadata = item.MetadataCollection; metadata.Count.ShouldBe(2); metadata["a"].EvaluatedValue.ShouldBe("override"); metadata["b"].EvaluatedValue.ShouldBe("base"); diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 826fbadef56..9657019dc00 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -231,7 +231,7 @@ public class BuildParameters : ITranslatable /// public BuildParameters() { - Initialize(Utilities.GetEnvironmentProperties(), new ProjectRootElementCache(false), null); + Initialize(Utilities.GetEnvironmentProperties(makeReadOnly: false), new ProjectRootElementCache(false), null); } /// diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs index 08cc3e9f5e0..4bf6e8ef11a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs @@ -459,7 +459,8 @@ private List ExpandItemIntoItems( includeSplit /* before wildcard expansion */, null, null, - originalItem.Location.File)); + originalItem.Location.File, + useItemDefinitionsWithoutModification: false)); } } } diff --git a/src/Build/Collections/CopyOnWritePropertyDictionary.cs b/src/Build/Collections/CopyOnWritePropertyDictionary.cs index 76e975fe3c3..44e7fe20555 100644 --- a/src/Build/Collections/CopyOnWritePropertyDictionary.cs +++ b/src/Build/Collections/CopyOnWritePropertyDictionary.cs @@ -36,18 +36,18 @@ namespace Microsoft.Build.Collections /// /// Property or Metadata class type to store [DebuggerDisplay("#Entries={Count}")] - internal sealed class CopyOnWritePropertyDictionary : IEnumerable, IEquatable>, IDictionary + internal sealed class CopyOnWritePropertyDictionary : ICopyOnWritePropertyDictionary, IEquatable> where T : class, IKeyed, IValued, IEquatable, IImmutable { private static readonly ImmutableDictionary NameComparerDictionaryPrototype = ImmutableDictionary.Create(MSBuildNameIgnoreCaseComparer.Default); /// - /// Backing dictionary + /// Backing dictionary. /// private ImmutableDictionary _backing; /// - /// Creates empty dictionary + /// Creates empty dictionary. /// public CopyOnWritePropertyDictionary() { @@ -55,7 +55,7 @@ public CopyOnWritePropertyDictionary() } /// - /// Cloning constructor, with deferred cloning semantics + /// Cloning constructor, with deferred cloning semantics. /// private CopyOnWritePropertyDictionary(CopyOnWritePropertyDictionary that) { @@ -63,12 +63,12 @@ private CopyOnWritePropertyDictionary(CopyOnWritePropertyDictionary that) } /// - /// Accessor for the list of property names + /// Accessor for the list of property names. /// ICollection IDictionary.Keys => ((IDictionary)_backing).Keys; /// - /// Accessor for the list of properties + /// Accessor for the list of properties. /// ICollection IDictionary.Values => ((IDictionary)_backing).Values; @@ -115,6 +115,16 @@ public T this[string name] /// public bool Contains(string name) => _backing.ContainsKey(name); + public string GetEscapedValue(string name) + { + if (_backing.TryGetValue(name, out T value)) + { + return value?.EscapedValue; + } + + return null; + } + /// /// Empties the collection /// @@ -134,7 +144,7 @@ public void Clear() /// IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - #region IEquatable> Members + #region IEquatable> Members /// /// Compares two property dictionaries for equivalence. They are equal if each contains the same properties with the @@ -180,6 +190,56 @@ public bool Equals(CopyOnWritePropertyDictionary other) #endregion + #region IEquatable> Members + + /// + /// Compares two property dictionaries for equivalence. They are equal if each contains the same properties with the + /// same values as the other, unequal otherwise. + /// + /// The dictionary to which this should be compared + /// True if they are equivalent, false otherwise. + public bool Equals(ICopyOnWritePropertyDictionary other) + { + if (other == null) + { + return false; + } + + ImmutableDictionary thisBacking = _backing; + IDictionary otherDict = other; + + if (other is CopyOnWritePropertyDictionary otherCopyOnWritePropertyDictionary) + { + // If the backing collections are the same, we are equal. + // Note that with this check, we intentionally avoid the common reference + // comparison between 'this' and 'other'. + if (ReferenceEquals(thisBacking, otherCopyOnWritePropertyDictionary._backing)) + { + return true; + } + + otherDict = otherCopyOnWritePropertyDictionary._backing; + } + + if (thisBacking.Count != otherDict.Count) + { + return false; + } + + foreach (T thisProp in thisBacking.Values) + { + if (!otherDict.TryGetValue(thisProp.Key, out T thatProp) || + !EqualityComparer.Default.Equals(thisProp, thatProp)) + { + return false; + } + } + + return true; + } + + #endregion + #region IDictionary Members /// @@ -274,7 +334,7 @@ public bool Remove(string name) /// Overwrites any property with the same name already in the collection. /// To remove a property, use Remove(...) instead. /// - internal void Set(T projectProperty) + public void Set(T projectProperty) { ErrorUtilities.VerifyThrowArgumentNull(projectProperty, nameof(projectProperty)); @@ -285,7 +345,7 @@ internal void Set(T projectProperty) /// Adds the specified properties to this dictionary. /// /// An enumerator over the properties to add. - internal void ImportProperties(IEnumerable other) + public void ImportProperties(IEnumerable other) { _backing = _backing.SetItems(Items()); @@ -302,7 +362,7 @@ IEnumerable> Items() /// Clone. As we're copy on write, this /// should be cheap. /// - internal CopyOnWritePropertyDictionary DeepClone() + public ICopyOnWritePropertyDictionary DeepClone() { return new CopyOnWritePropertyDictionary(this); } diff --git a/src/Build/Collections/ICopyOnWritePropertyDictionary.cs b/src/Build/Collections/ICopyOnWritePropertyDictionary.cs new file mode 100644 index 00000000000..310476fa83b --- /dev/null +++ b/src/Build/Collections/ICopyOnWritePropertyDictionary.cs @@ -0,0 +1,65 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// An interface that represents a dictionary of unordered property or metadata name/value pairs with copy-on-write semantics. + /// + /// + /// The value that this adds over IDictionary<string, T> is: + /// - supports copy on write + /// - enforces that key = T.Name + /// - default enumerator is over values + /// - (marginal) enforces the correct key comparer + /// + /// Really a Dictionary<string, T> where the key (the name) is obtained from IKeyed.Key. + /// Is not observable, so if clients wish to observe modifications they must mediate them themselves and + /// either not expose this collection or expose it through a readonly wrapper. + /// + /// This collection is safe for concurrent readers and a single writer. + /// + /// Property or Metadata class type to store + internal interface ICopyOnWritePropertyDictionary : IEnumerable, IEquatable>, IDictionary + where T : class, IKeyed, IValued, IEquatable, IImmutable + { + /// + /// Returns true if a property with the specified name is present in the collection, otherwise false. + /// + bool Contains(string name); + + /// + /// Add the specified property to the collection. + /// Overwrites any property with the same name already in the collection. + /// To remove a property, use Remove(...) instead. + /// + void Set(T projectProperty); + + /// + /// Adds the specified properties to this dictionary. + /// + /// An enumerator over the properties to add. + void ImportProperties(IEnumerable other); + + /// + /// Clone. As we're copy on write, this should be cheap. + /// + ICopyOnWritePropertyDictionary DeepClone(); + + /// + /// must implement , which means it contains an + /// EscapedValue. This method allows retrieving the EscapedValue of an object in the dictionary + /// directly. + /// + string GetEscapedValue(string name); + } +} diff --git a/src/Build/Collections/IMultiDictionary.cs b/src/Build/Collections/IMultiDictionary.cs new file mode 100644 index 00000000000..1fece50dc23 --- /dev/null +++ b/src/Build/Collections/IMultiDictionary.cs @@ -0,0 +1,24 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Build.Collections +{ + /// + /// Represents a dictionary that can hold more than one distinct value with the same key. + /// All keys must have at least one value: null values are currently rejected. + /// + /// Type of key + /// Type of value + internal interface IMultiDictionary + where K : class + where V : class + { + IEnumerable this[K key] { get; } + } +} diff --git a/src/Build/Collections/MultiDictionary.cs b/src/Build/Collections/MultiDictionary.cs index b2fc7427261..ef8c02c8c8e 100644 --- a/src/Build/Collections/MultiDictionary.cs +++ b/src/Build/Collections/MultiDictionary.cs @@ -22,7 +22,7 @@ namespace Microsoft.Build.Collections /// Type of key /// Type of value [DebuggerDisplay("#Keys={KeyCount} #Values={ValueCount}")] - internal class MultiDictionary + internal class MultiDictionary : IMultiDictionary where K : class where V : class { @@ -86,7 +86,7 @@ internal MultiDictionary(IEqualityComparer keyComparer) /// /// Enumerator over values that have the specified key. /// - internal IEnumerable this[K key] + public IEnumerable this[K key] { get { diff --git a/src/Build/Collections/PropertyDictionary.cs b/src/Build/Collections/PropertyDictionary.cs index 96884a1914d..01411c5792c 100644 --- a/src/Build/Collections/PropertyDictionary.cs +++ b/src/Build/Collections/PropertyDictionary.cs @@ -42,14 +42,14 @@ internal sealed class PropertyDictionary : IEnumerable, IEquatable [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - private readonly IRetrievableEntryHashSet _properties; + private readonly IRetrievableValuedEntryHashSet _properties; /// /// Creates empty dictionary /// public PropertyDictionary() { - _properties = new RetrievableEntryHashSet(MSBuildNameIgnoreCaseComparer.Default); + _properties = new RetrievableValuedEntryHashSet(MSBuildNameIgnoreCaseComparer.Default); } /// @@ -57,7 +57,7 @@ public PropertyDictionary() /// internal PropertyDictionary(int capacity) { - _properties = new RetrievableEntryHashSet(capacity, MSBuildNameIgnoreCaseComparer.Default); + _properties = new RetrievableValuedEntryHashSet(capacity, MSBuildNameIgnoreCaseComparer.Default); } /// @@ -77,7 +77,7 @@ internal PropertyDictionary(IEnumerable elements) /// internal PropertyDictionary(MSBuildNameIgnoreCaseComparer comparer) { - _properties = new RetrievableEntryHashSet(comparer); + _properties = new RetrievableValuedEntryHashSet(comparer); } /// @@ -96,7 +96,7 @@ internal PropertyDictionary(int capacity, IEnumerable elements) /// Initializes a new instance of the class. /// /// The collection of properties to use. - internal PropertyDictionary(IRetrievableEntryHashSet propertiesHashSet) + internal PropertyDictionary(IRetrievableValuedEntryHashSet propertiesHashSet) { _properties = propertiesHashSet; } @@ -333,6 +333,24 @@ public T Get(string keyString, int startIndex, int endIndex) return GetProperty(keyString, startIndex, endIndex); } + /// + /// Gets the unescaped value of a particular property. + /// + /// The name of the property whose value is sought. + /// The out parameter by which a successfully retrieved value is returned. + /// True if a property with a matching name was found. False otherwise. + public bool TryGetPropertyUnescapedValue(string propertyName, out string unescapedValue) + { + if (_properties.TryGetEscapedValue(propertyName, out string escapedValue) && escapedValue != null) + { + unescapedValue = EscapingUtilities.UnescapeAll(escapedValue); + return true; + } + + unescapedValue = null; + return false; + } + #region IDictionary Members /// diff --git a/src/Build/Collections/RetrievableEntryHashSet/IRetrievableEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableEntryHashSet.cs index 3100e7914a8..4d271575848 100644 --- a/src/Build/Collections/RetrievableEntryHashSet/IRetrievableEntryHashSet.cs +++ b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableEntryHashSet.cs @@ -8,6 +8,12 @@ namespace Microsoft.Build.Collections { + /// + /// Represents a hash set mapping string to , with the specialization that + /// value lookup supports using substrings of a provided key without requiring instantiating the substring + /// (in order to avoid the memory usage of string allocation). + /// + /// The type of data the hash set contains (which must be ). internal interface IRetrievableEntryHashSet : ICollection, ISerializable, diff --git a/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs new file mode 100644 index 00000000000..e3b10556772 --- /dev/null +++ b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Runtime.Serialization; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + /// Like , this represents a hash set mapping string to + /// with the specialization that value lookup supports using substrings of a provided key without requiring instantiating + /// the substring (in order to avoid the memory usage of string allocation). + /// + /// This interface extends the functionality of by introducing the ability + /// to directly retrieve the Value of an instance of T instead of retrieving the instance of T itself. Implementations of + /// this interface can avoid the cost of allocating an instance of when the caller requests only + /// the Value. + /// + /// The type of data the hash set contains (which must be + /// and also ). + internal interface IRetrievableValuedEntryHashSet : IRetrievableEntryHashSet + where T : class, IKeyed, IValued + { + /// + /// Gets the of the item whose matches . + /// + /// The key of the item whose value is sought. + /// The out parameter by which a successfully retrieved is returned. + /// True if an item whose matches was found. False otherwise. + bool TryGetEscapedValue(string key, out string escapedValue); + } +} diff --git a/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs similarity index 99% rename from src/Build/Collections/RetrievableEntryHashSet/HashSet.cs rename to src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs index e6160cec90f..dc8d96f9f7c 100644 --- a/src/Build/Collections/RetrievableEntryHashSet/HashSet.cs +++ b/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs @@ -320,7 +320,7 @@ public bool TryGetValue(string key, out T item) /// Gets the item if any with the given name /// /// key to check for containment - /// true if item contained; false if not + /// The item, if it was found. Otherwise, default(T). public T Get(string key) { return GetCore(key, 0, key?.Length ?? 0); diff --git a/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs new file mode 100644 index 00000000000..c45da4e12b6 --- /dev/null +++ b/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs @@ -0,0 +1,53 @@ +// 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; + +#nullable disable + +namespace Microsoft.Build.Collections +{ + /// + [DebuggerTypeProxy(typeof(HashSetDebugView<>))] + [DebuggerDisplay("Count = {Count}")] +#if FEATURE_SECURITY_PERMISSIONS + [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] +#endif + internal class RetrievableValuedEntryHashSet : RetrievableEntryHashSet, IRetrievableValuedEntryHashSet + where T : class, IKeyed, IValued + { + /// + /// Initializes a new instance of the RetrievableValuedEntryHashSet class. + /// + /// A comparer with which the items' key values are compared. + public RetrievableValuedEntryHashSet(IEqualityComparer comparer) + : base(comparer) + { + } + + /// + /// Initializes a new instance of the RetrievableValuedEntryHashSet class. + /// + /// A value suggesting a good approximate minimum size for the initial collection. + /// A comparer with which the items' key values are compared. + public RetrievableValuedEntryHashSet(int suggestedCapacity, IEqualityComparer comparer) + : base(suggestedCapacity, comparer) + { + } + + /// + public bool TryGetEscapedValue(string key, out string escapedValue) + { + if (TryGetValue(key, out T item) && item != null) + { + escapedValue = item.EscapedValue; + return true; + } + + escapedValue = null; + return false; + } + } +} diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index 11dd0a1143d..cf70b8f41fa 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -887,6 +887,21 @@ internal PropertyDictionary GlobalPropertiesCollection /// Returns the property dictionary containing the properties representing the environment. /// internal PropertyDictionary EnvironmentProperties + { + get + { + // Retrieves the environment properties. + // This is only done once, when the project collection is created. Any subsequent + // environment changes will be ignored. Child nodes will be passed this set + // of properties in their build parameters. + return new PropertyDictionary(SharedReadOnlyEnvironmentProperties); + } + } + + /// + /// Returns a shared immutable property dictionary containing the properties representing the environment. + /// + internal PropertyDictionary SharedReadOnlyEnvironmentProperties { get { @@ -898,7 +913,7 @@ internal PropertyDictionary EnvironmentProperties { if (_environmentProperties != null) { - return new PropertyDictionary(_environmentProperties); + return _environmentProperties; } } @@ -906,9 +921,9 @@ internal PropertyDictionary EnvironmentProperties { if (_environmentProperties == null) { - _environmentProperties = Utilities.GetEnvironmentProperties(); + _environmentProperties = Utilities.GetEnvironmentProperties(makeReadOnly: true); } - return new PropertyDictionary(_environmentProperties); + return _environmentProperties; } } } diff --git a/src/Build/Instance/IImmutableInstanceProvider.cs b/src/Build/Instance/IImmutableInstanceProvider.cs index a862a12cf13..4b39fc66288 100644 --- a/src/Build/Instance/IImmutableInstanceProvider.cs +++ b/src/Build/Instance/IImmutableInstanceProvider.cs @@ -9,6 +9,19 @@ namespace Microsoft.Build.Execution /// The Instance type. internal interface IImmutableInstanceProvider { - T ImmutableInstance { get; set; } + /// + /// Gets the Immutable Instance. + /// + T ImmutableInstance { get; } + + /// + /// If the ImmutableInstance has not already been set, then this + /// method sets the ImmutableInstance to the requested value. + /// An already set ImmutableInstance is never replaced. + /// + /// An instance that will be set as the immutable instance, provided that + /// the immutable instance has not already been set. + /// The immutable instance, which may or may not be the supplied . + T GetOrSetImmutableInstance(T instance); } } diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableElementCollectionConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableElementCollectionConverter.cs index ae164108704..f15fd45f63e 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableElementCollectionConverter.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableElementCollectionConverter.cs @@ -6,11 +6,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Data; using System.Runtime.Serialization; using Microsoft.Build.Collections; -using Microsoft.Build.Evaluation; -using Microsoft.Build.Execution; using Microsoft.Build.Shared; namespace Microsoft.Build.Instance @@ -18,20 +15,19 @@ namespace Microsoft.Build.Instance /// /// A specialized collection used when element data originates in an immutable Project. /// - internal sealed class ImmutableElementCollectionConverter : IRetrievableEntryHashSet + internal class ImmutableElementCollectionConverter : IRetrievableEntryHashSet where T : class, IKeyed { - private readonly IDictionary _projectElements; - private readonly IDictionary<(string, int, int), TCached> _constrainedProjectElements; + protected readonly IDictionary _projectElements; private readonly ValuesCollection _values; public ImmutableElementCollectionConverter( IDictionary projectElements, - IDictionary<(string, int, int), TCached> constrainedProjectElements) + IDictionary<(string, int, int), TCached> constrainedProjectElements, + Func convertElement) { _projectElements = projectElements; - _constrainedProjectElements = constrainedProjectElements; - _values = new ValuesCollection(_projectElements, _constrainedProjectElements); + _values = new ValuesCollection(_projectElements, constrainedProjectElements, convertElement); } public T this[string key] @@ -110,13 +106,16 @@ private sealed class ValuesCollection : ICollection { private readonly IDictionary _projectElements; private readonly IDictionary<(string, int, int), TCached> _constrainedProjectElements; + private readonly Func _getElementInstance; public ValuesCollection( IDictionary projectElements, - IDictionary<(string, int, int), TCached> constrainedProjectElements) + IDictionary<(string, int, int), TCached> constrainedProjectElements, + Func getElementInstance) { _projectElements = projectElements; _constrainedProjectElements = constrainedProjectElements; + _getElementInstance = getElementInstance; } public int Count => _projectElements.Count; @@ -155,7 +154,7 @@ public void CopyTo(T[] array, int arrayIndex, int count) int endIndex = arrayIndex + count; foreach (var item in _projectElements.Values) { - array[index] = GetElementInstance(item); + array[index] = _getElementInstance(item); ++index; if (index >= endIndex) { @@ -171,7 +170,7 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) int index = arrayIndex; foreach (var item in _projectElements.Values) { - var itemInstance = GetElementInstance(item); + var itemInstance = _getElementInstance(item); array[index] = new KeyValuePair(itemInstance.Key, itemInstance); ++index; } @@ -181,7 +180,7 @@ public IEnumerator GetEnumerator() { foreach (var item in _projectElements.Values) { - yield return GetElementInstance(item); + yield return _getElementInstance(item); } } @@ -189,7 +188,7 @@ public IEnumerator> GetKvpEnumerator() { foreach (var kvp in _projectElements) { - T instance = GetElementInstance(kvp.Value); + T instance = _getElementInstance(kvp.Value); yield return new KeyValuePair(kvp.Key, instance); } } @@ -200,7 +199,7 @@ IEnumerator IEnumerable.GetEnumerator() { foreach (var item in _projectElements.Values) { - yield return GetElementInstance(item); + yield return _getElementInstance(item); } } @@ -208,7 +207,7 @@ public T Get(string key) { if (_projectElements.TryGetValue(key, out TCached element)) { - return GetElementInstance(element); + return _getElementInstance(element); } return null; @@ -216,9 +215,14 @@ public T Get(string key) public T Get(string keyString, int startIndex, int length) { + if (_constrainedProjectElements == null) + { + return Get(keyString); + } + if (_constrainedProjectElements.TryGetValue((keyString, startIndex, length), out TCached element)) { - return GetElementInstance(element); + return _getElementInstance(element); } return null; @@ -232,19 +236,9 @@ public bool TryGetValue(string key, out T value) return false; } - value = GetElementInstance(element); + value = _getElementInstance(element); return value != null; } - - private T GetElementInstance(TCached element) - { - if (element is IImmutableInstanceProvider instanceProvider) - { - return instanceProvider.ImmutableInstance; - } - - return null; - } } } } diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs new file mode 100644 index 00000000000..b5579adf088 --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs @@ -0,0 +1,251 @@ +// 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.CodeDom; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Collections; +using Microsoft.Build.Execution; +using Microsoft.Build.Shared; + +#nullable disable + +namespace Microsoft.Build.Instance.ImmutableProjectCollections +{ + /// + /// A collection representing the set of Global ProjectPropertyInstance objects. + /// + /// This class is used only when the containing ProjectInstance originates from an + /// immutable linked project source. It's specialized in order to reduce required allocations + /// by instead relying on the linked project source's collection of global properties + /// (the IDictionary _globalProperties) and the ProjectInstance's collection of all + /// properties (the PropertyDictionary _allProperties). When a property is requested, + /// _globalProperties is checked to determine whether the named property is actually + /// a global property and, if it is, then instance is retrieved from _allProperties. + /// + internal class ImmutableGlobalPropertiesCollectionConverter : IRetrievableEntryHashSet + { + private readonly IDictionary _globalProperties; + private readonly PropertyDictionary _allProperties; + private readonly ValuesCollection _values; + + public ImmutableGlobalPropertiesCollectionConverter( + IDictionary globalProperties, + PropertyDictionary allProperties) + { + _globalProperties = globalProperties; + _allProperties = allProperties; + _values = new ValuesCollection(this); + } + + public ProjectPropertyInstance this[string key] + { + set => throw new NotSupportedException(); + get + { + if (_globalProperties.ContainsKey(key)) + { + return _allProperties[key]; + } + + return null; + } + } + + public int Count => _globalProperties.Count; + + public bool IsReadOnly => true; + + public ICollection Keys => _globalProperties.Keys; + + public ICollection Values => _values; + + public void Add(ProjectPropertyInstance item) => throw new NotSupportedException(); + + public void Add(string key, ProjectPropertyInstance value) => throw new NotSupportedException(); + + public void Add(KeyValuePair item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public bool Contains(ProjectPropertyInstance item) => _values.Contains(item); + + public bool Contains(KeyValuePair itemKvp) => _values.Contains(itemKvp.Value); + + public bool ContainsKey(string key) => _globalProperties.ContainsKey(key); + + public void CopyTo(ProjectPropertyInstance[] array) => _values.CopyTo(array, arrayIndex: 0); + + public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex) => _values.CopyTo(array, arrayIndex); + + public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex, int count) => _values.CopyTo(array, arrayIndex, count); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _globalProperties.Count); + + int currentIndex = arrayIndex; + foreach (var itemKey in _globalProperties.Keys) + { + ProjectPropertyInstance instance = _allProperties[itemKey]; + if (instance != null) + { + array[currentIndex] = new KeyValuePair(itemKey, instance); + ++currentIndex; + } + } + } + + public ProjectPropertyInstance Get(string key) + { + return this[key]; + } + + public ProjectPropertyInstance Get(string key, int index, int length) + { + // The PropertyDictionary containing all of the properties can efficiently + // look up the requested property while honoring the specific index and length + // constraints. We then just have to verify that it's one of the global properties. + ProjectPropertyInstance actualProperty = _allProperties.Get(key, index, length); + if (actualProperty != null && _globalProperties.ContainsKey(actualProperty.Name)) + { + return actualProperty; + } + + return null; + } + + public IEnumerator GetEnumerator() => _values.GetEnumerator(); + + public void GetObjectData(SerializationInfo info, StreamingContext context) => throw new NotSupportedException(); + + public void OnDeserialization(object sender) => throw new NotSupportedException(); + + public bool Remove(ProjectPropertyInstance item) => throw new NotSupportedException(); + + public bool Remove(string key) => throw new NotSupportedException(); + + public bool Remove(KeyValuePair item) => throw new NotSupportedException(); + + public void TrimExcess() + { + } + + public bool TryGetValue(string key, out ProjectPropertyInstance value) + { + ProjectPropertyInstance instance = Get(key); + value = instance; + return instance != null; + } + + public void UnionWith(IEnumerable other) => throw new NotSupportedException(); + + IEnumerator> IEnumerable>.GetEnumerator() + { + foreach (var itemKey in _globalProperties.Keys) + { + ProjectPropertyInstance instance = _allProperties[itemKey]; + if (instance != null) + { + yield return new KeyValuePair(itemKey, instance); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() => _values.GetEnumerator(); + + private class ValuesCollection : ICollection + { + private readonly ImmutableGlobalPropertiesCollectionConverter _parent; + + public ValuesCollection(ImmutableGlobalPropertiesCollectionConverter parent) + { + _parent = parent; + } + + public int Count => _parent._globalProperties.Count; + + public bool IsReadOnly => true; + + public void Add(ProjectPropertyInstance item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public bool Remove(ProjectPropertyInstance item) => throw new NotSupportedException(); + + public bool Contains(ProjectPropertyInstance item) + { + if (!_parent._globalProperties.ContainsKey(item.Name)) + { + return false; + } + + ProjectPropertyInstance actualInstance = _parent._allProperties[item.Name]; + + if (actualInstance == null) + { + return false; + } + + return actualInstance.Equals(item); + } + + public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex) + { + CopyTo(array, arrayIndex, _parent._globalProperties.Count); + } + + public void CopyTo(ProjectPropertyInstance[] array, int arrayIndex, int count) + { + ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _parent._globalProperties.Count); + + int currentIndex = arrayIndex; + int currentCount = 0; + foreach (var itemKey in _parent._globalProperties.Keys) + { + if (currentCount >= count) + { + return; + } + + ProjectPropertyInstance instance = _parent._allProperties[itemKey]; + if (instance != null) + { + array[currentIndex] = instance; + ++currentIndex; + ++currentCount; + } + } + } + + public IEnumerator GetEnumerator() + { + foreach (var itemKey in _parent._globalProperties.Keys) + { + ProjectPropertyInstance instance = _parent._allProperties[itemKey]; + if (instance != null) + { + yield return instance; + } + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (var itemKey in _parent._globalProperties.Keys) + { + ProjectPropertyInstance instance = _parent._allProperties[itemKey]; + if (instance != null) + { + yield return instance; + } + } + } + } + } +} diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs new file mode 100644 index 00000000000..1a1d499b0aa --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs @@ -0,0 +1,182 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Collections; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Instance +{ + internal class ImmutableItemDefinitionsListConverter : IList + where T : IKeyed + where TCached : IKeyed + { + private readonly IList? _itemList; + private readonly TCached? _itemTypeDefinition; + private readonly Func _getInstance; + + public ImmutableItemDefinitionsListConverter( + IList? itemList, + TCached? itemTypeDefinition, + Func getInstance) + { + ErrorUtilities.VerifyThrowArgumentNull(getInstance, nameof(getInstance)); + + _itemList = itemList; + _itemTypeDefinition = itemTypeDefinition; + _getInstance = getInstance; + } + + public T this[int index] + { + set => throw new NotSupportedException(); + get + { + if (_itemList == null) + { + if (index != 0 || _itemTypeDefinition == null) + { + throw new IndexOutOfRangeException(); + } + + return _getInstance(_itemTypeDefinition); + } + + if (index > _itemList.Count) + { + throw new IndexOutOfRangeException(); + } + + if (index == _itemList.Count) + { + if (_itemTypeDefinition == null) + { + throw new IndexOutOfRangeException(); + } + + return _getInstance(_itemTypeDefinition); + } + + return _getInstance(_itemList[index]); + } + } + + public int Count => (_itemList == null ? 0 : _itemList.Count) + (_itemTypeDefinition == null ? 0 : 1); + + public bool IsReadOnly => true; + + public void Add(T item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public void Insert(int index, T item) => throw new NotSupportedException(); + + public bool Remove(T item) => throw new NotSupportedException(); + + public void RemoveAt(int index) => throw new NotSupportedException(); + + public bool Contains(T item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(T[] array, int arrayIndex) + { + ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), Count); + + int currentIndex = arrayIndex; + void PutItemIntoArray(TCached item) + { + array[currentIndex] = _getInstance(item); + ++currentIndex; + } + + if (_itemList != null) + { + foreach (var item in _itemList) + { + PutItemIntoArray(item); + } + } + + if (_itemTypeDefinition != null) + { + PutItemIntoArray(_itemTypeDefinition); + } + } + + public IEnumerator GetEnumerator() => GetEnumeratorImpl(); + + public int IndexOf(T item) + { + int currentIndex = 0; + if (_itemList != null) + { + foreach (var cachedItem in _itemList) + { + if (IsMatchingItem(cachedItem, item)) + { + return currentIndex; + } + + ++currentIndex; + } + } + + if (_itemTypeDefinition != null) + { + if (IsMatchingItem(_itemTypeDefinition, item)) + { + return currentIndex; + } + } + + return -1; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumeratorImpl(); + + private bool IsMatchingItem(TCached cachedItem, T item) + { + if (MSBuildNameIgnoreCaseComparer.Default.Equals(cachedItem.Key, item.Key)) + { + T? foundItem = _getInstance(cachedItem); + if (foundItem is not null && foundItem.Equals(item)) + { + return true; + } + } + + return false; + } + + private IEnumerator GetEnumeratorImpl() + { + if (_itemList != null) + { + foreach (var item in _itemList) + { + T? instance = _getInstance(item); + if (instance != null) + { + yield return instance; + } + } + } + + if (_itemTypeDefinition != null) + { + T? instance = _getInstance(_itemTypeDefinition); + if (instance != null) + { + yield return instance; + } + } + } + } +} diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs index 74919a57b97..de2d06522ae 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs @@ -19,29 +19,28 @@ namespace Microsoft.Build.Instance /// internal sealed class ImmutableItemDictionary : IItemDictionary where T : class, IKeyed, IItem + where TCached : IKeyed, IItem { private readonly IDictionary> _itemsByType; - private readonly ICollection _allItems; - - public ImmutableItemDictionary(IDictionary> itemsByType, ICollection allItems) + private readonly ICollection _allCachedItems; + private readonly Func _getInstance; + private readonly Func _getItemType; + + public ImmutableItemDictionary( + ICollection allItems, + IDictionary> itemsByType, + Func getInstance, + Func getItemType) { - _itemsByType = itemsByType ?? throw new ArgumentNullException(nameof(itemsByType)); - if (allItems == null) { throw new ArgumentNullException(nameof(allItems)); } - var convertedItems = new HashSet(allItems.Count); - foreach (var item in allItems) - { - T? instance = GetInstance(item); - if (instance != null) - { - convertedItems.Add(instance); - } - } - _allItems = new ReadOnlyCollection(convertedItems); + _allCachedItems = allItems; + _itemsByType = itemsByType ?? throw new ArgumentNullException(nameof(itemsByType)); + _getInstance = getInstance; + _getItemType = getItemType; } /// @@ -54,12 +53,12 @@ public ICollection this[string itemType] return Array.Empty(); } - return new ListConverter(itemType, _allItems, list); + return new ListConverter(itemType, list, _getInstance); } } /// - public int Count => _allItems.Count; + public int Count => _allCachedItems.Count; /// public ICollection ItemTypes => _itemsByType.Keys; @@ -77,7 +76,22 @@ public ICollection this[string itemType] public void Clear() => throw new NotSupportedException(); /// - public bool Contains(T projectItem) => _allItems.Contains(projectItem); + public bool Contains(T projectItem) + { + if (projectItem == null) + { + return false; + } + + string? itemType = _getItemType(projectItem); + if (itemType == null) + { + return false; + } + + ICollection items = GetItems(itemType); + return items.Contains(projectItem); + } /// public void EnumerateItemsPerType(Action> itemTypeCallback) @@ -90,31 +104,55 @@ public void EnumerateItemsPerType(Action> itemTypeCallbac continue; } - itemTypeCallback(kvp.Key, new ListConverter(kvp.Key, _allItems, kvp.Value)); + itemTypeCallback(kvp.Key, new ListConverter(kvp.Key, kvp.Value, _getInstance)); } } /// public IEnumerable GetCopyOnReadEnumerable(Func selector) { - foreach (var item in _allItems) + foreach (var cachedItem in _allCachedItems) { - yield return selector(item); + T? item = _getInstance(cachedItem); + if (item is not null) + { + yield return selector(item); + } } } /// - public IEnumerator GetEnumerator() => _allItems.GetEnumerator(); + public IEnumerator GetEnumerator() + { + foreach (var cachedItem in _allCachedItems) + { + T? item = _getInstance(cachedItem); + if (item is not null) + { + yield return item; + } + } + } /// - IEnumerator IEnumerable.GetEnumerator() => _allItems.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() + { + foreach (var cachedItem in _allCachedItems) + { + T? item = _getInstance(cachedItem); + if (item is not null) + { + yield return item; + } + } + } /// public ICollection GetItems(string itemType) { if (_itemsByType.TryGetValue(itemType, out ICollection? items)) { - return new ListConverter(itemType, _allItems, items); + return new ListConverter(itemType, items, _getInstance); } return Array.Empty(); @@ -138,27 +176,17 @@ public ICollection GetItems(string itemType) /// public void Replace(T existingItem, T newItem) => throw new NotSupportedException(); - private static T? GetInstance(TCached item) - { - if (item is IImmutableInstanceProvider instanceProvider) - { - return instanceProvider.ImmutableInstance; - } - - return null; - } - private sealed class ListConverter : ICollection { private readonly string _itemType; - private readonly ICollection _allItems; private readonly ICollection _list; + private readonly Func _getInstance; - public ListConverter(string itemType, ICollection allItems, ICollection list) + public ListConverter(string itemType, ICollection list, Func getInstance) { _itemType = itemType; - _allItems = allItems; _list = list; + _getInstance = getInstance; } public int Count => _list.Count; @@ -173,8 +201,20 @@ public ListConverter(string itemType, ICollection allItems, ICollection + { + if (MSBuildNameIgnoreCaseComparer.Default.Equals(cachedItem.EvaluatedIncludeEscaped, item.EvaluatedIncludeEscaped)) + { + T? foundItem = _getInstance(cachedItem); + if (foundItem is not null && foundItem.Equals(item)) + { + return true; + } + } + + return false; + }); } public void CopyTo(T[] array, int arrayIndex) @@ -184,7 +224,7 @@ public void CopyTo(T[] array, int arrayIndex) int currentIndex = arrayIndex; foreach (var item in _list) { - T? instance = GetInstance(item); + T? instance = _getInstance(item); if (instance != null) { array[currentIndex] = instance; @@ -197,7 +237,7 @@ public IEnumerator GetEnumerator() { foreach (var item in _list) { - T? instance = GetInstance(item); + T? instance = _getInstance(item); if (instance != null) { yield return instance; @@ -209,7 +249,7 @@ IEnumerator IEnumerable.GetEnumerator() { foreach (var item in _list) { - T? instance = GetInstance(item); + T? instance = _getInstance(item); if (instance != null) { yield return instance; diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs new file mode 100644 index 00000000000..1b11db2341a --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs @@ -0,0 +1,43 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Build.Collections; +using Microsoft.Build.Execution; + +namespace Microsoft.Build.Instance.ImmutableProjectCollections +{ + internal class ImmutableLinkedMultiDictionaryConverter : IMultiDictionary + where K : class + where V : class + where VCached : class + { + private readonly Func> _getCachedValues; + private readonly Func _getInstance; + + public ImmutableLinkedMultiDictionaryConverter(Func> getCachedValues, Func getInstance) + { + _getCachedValues = getCachedValues; + _getInstance = getInstance; + } + + public IEnumerable this[K key] + { + get + { + IEnumerable cachedValues = _getCachedValues(key); + if (cachedValues != null) + { + foreach (var cachedValue in cachedValues) + { + yield return _getInstance(cachedValue); + } + } + } + } + } +} diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutablePropertyCollectionConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutablePropertyCollectionConverter.cs new file mode 100644 index 00000000000..27610c71543 --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutablePropertyCollectionConverter.cs @@ -0,0 +1,72 @@ +// 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 Microsoft.Build.Collections; + +namespace Microsoft.Build.Instance +{ + internal class ImmutablePropertyCollectionConverter : ImmutableElementCollectionConverter, ICopyOnWritePropertyDictionary + where T : class, IKeyed, IValued, IEquatable, IImmutable + where TCached : class, IValued, IEquatable + { + public ImmutablePropertyCollectionConverter(IDictionary properties, Func convertProperty) + : base(properties, constrainedProjectElements: null, convertProperty) + { + } + + public bool Contains(string name) => ContainsKey(name); + + public string? GetEscapedValue(string name) + { + if (_projectElements.TryGetValue(name, out TCached? value)) + { + return value?.EscapedValue; + } + + return null; + } + + public ICopyOnWritePropertyDictionary DeepClone() => this; + + public void ImportProperties(IEnumerable other) => throw new NotSupportedException(); + + public void Set(T projectProperty) => throw new NotSupportedException(); + + public bool Equals(ICopyOnWritePropertyDictionary? other) + { + if (other == null || Count != other.Count) + { + return false; + } + + if (other is ImmutablePropertyCollectionConverter otherImmutableDict) + { + // When comparing to another CollectionConverter we compare the TCached values + // in order to avoid causing the instantiation of each T instance. + foreach (var propKvp in _projectElements) + { + if (!otherImmutableDict._projectElements.TryGetValue(propKvp.Key, out TCached? otherProperty) || + !EqualityComparer.Default.Equals(propKvp.Value, otherProperty)) + { + return false; + } + } + } + else + { + foreach (T thisProp in Values) + { + if (!other.TryGetValue(thisProp.Key, out T? thatProp) || + !EqualityComparer.Default.Equals(thisProp, thatProp)) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableStringValuedListConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableStringValuedListConverter.cs new file mode 100644 index 00000000000..89e12f7c0a1 --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableStringValuedListConverter.cs @@ -0,0 +1,99 @@ +// 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; +using System.Collections.Generic; +using Microsoft.Build.Collections; +using Microsoft.Build.Shared; + +namespace Microsoft.Build.Instance +{ + internal class ImmutableStringValuedListConverter : IList, IReadOnlyList + { + private readonly IList _itemList; + private readonly Func _getStringValue; + + public ImmutableStringValuedListConverter(IList itemList, Func getStringValue) + { + _itemList = itemList; + _getStringValue = getStringValue; + } + + public string this[int index] + { + set => throw new NotSupportedException(); + get => _getStringValue(_itemList[index]); + } + + public int Count => _itemList.Count; + + public bool IsReadOnly => true; + + public void Add(string item) => throw new NotSupportedException(); + + public void Clear() => throw new NotSupportedException(); + + public void Insert(int index, string item) => throw new NotSupportedException(); + + public bool Remove(string item) => throw new NotSupportedException(); + + public void RemoveAt(int index) => throw new NotSupportedException(); + + public bool Contains(string item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(string[] array, int arrayIndex) + { + ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _itemList.Count); + + int currentIndex = arrayIndex; + foreach (var item in _itemList) + { + array[currentIndex] = _getStringValue(item); + ++currentIndex; + } + } + + public IEnumerator GetEnumerator() + { + foreach (var item in _itemList) + { + string? stringValue = _getStringValue(item); + if (stringValue != null) + { + yield return stringValue; + } + } + } + + public int IndexOf(string item) + { + for (int i = 0; i < _itemList.Count; ++i) + { + T cachedItem = _itemList[i]; + string stringValue = _getStringValue(cachedItem); + if (MSBuildNameIgnoreCaseComparer.Default.Equals(stringValue, item)) + { + return i; + } + } + + return -1; + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (var item in _itemList) + { + string? instance = _getStringValue(item); + if (instance != null) + { + yield return instance; + } + } + } + } +} diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableValuedElementCollectionConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableValuedElementCollectionConverter.cs new file mode 100644 index 00000000000..5477ba57c28 --- /dev/null +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableValuedElementCollectionConverter.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System; +using System.Collections.Generic; +using Microsoft.Build.Collections; + +namespace Microsoft.Build.Instance +{ + /// + internal sealed class ImmutableValuedElementCollectionConverter : ImmutableElementCollectionConverter, IRetrievableValuedEntryHashSet + where T : class, IKeyed, IValued + where TCached : IValued + { + public ImmutableValuedElementCollectionConverter( + IDictionary projectElements, + IDictionary<(string, int, int), TCached> constrainedProjectElements, + Func convertElement) + : base(projectElements, constrainedProjectElements, convertElement) + { + } + + public bool TryGetEscapedValue(string key, out string escapedValue) + { + if (_projectElements.TryGetValue(key, out TCached value) && value != null) + { + escapedValue = value.EscapedValue; + return true; + } + + escapedValue = null; + return false; + } + } +} diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index fe63676c1d2..6b4ecede907 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -22,6 +22,7 @@ using Microsoft.Build.FileSystem; using Microsoft.Build.Framework; using Microsoft.Build.Instance; +using Microsoft.Build.Instance.ImmutableProjectCollections; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -92,9 +93,9 @@ public class ProjectInstance : IPropertyProvider, IItem private List _initialTargets; - private List _importPaths; + private IList _importPaths; - private List _importPathsIncludingDuplicates; + private IList _importPathsIncludingDuplicates; /// /// The global properties evaluation occurred with. @@ -131,7 +132,7 @@ public class ProjectInstance : IPropertyProvider, IItem /// /// Items organized by evaluatedInclude value /// - private MultiDictionary _itemsByEvaluatedInclude; + private IMultiDictionary _itemsByEvaluatedInclude; /// /// The project's root directory, for evaluation of relative paths and @@ -411,41 +412,41 @@ private ProjectInstance(Project linkedProject, bool fastItemLookupNeeded) EvaluationId = linkedProject.EvaluationCounter; // ProjectProperties - InitializeImmutableProjectPropertyInstances(linkedProject.Properties); - var projectPropertiesConverter = GetImmutableElementCollectionConverter(linkedProject.Properties); - _properties = new PropertyDictionary(projectPropertiesConverter); + _properties = GetImmutablePropertyDictionaryFromImmutableProject(linkedProject); // ProjectItemDefinitions - InitializeImmutableProjectItemDefinitionInstances(linkedProject.ItemDefinitions); - _itemDefinitions = GetImmutableElementCollectionConverter(linkedProject.ItemDefinitions); + _itemDefinitions = GetImmutableItemDefinitionsHashSetFromImmutableProject(linkedProject); // ProjectItems - InitializeImmutableProjectItemInstances(linkedProject.Items); - var itemsByType = linkedProject.Items as IDictionary>; - _items = new ImmutableItemDictionary(itemsByType, linkedProject.Items); + _items = GetImmutableItemsDictionaryFromImmutableProject(linkedProject, this); // ItemsByEvaluatedInclude if (fastItemLookupNeeded) { - _itemsByEvaluatedInclude = new MultiDictionary(StringComparer.OrdinalIgnoreCase); - foreach (var item in linkedProject.Items) - { - if (item is IImmutableInstanceProvider immutableInstanceProvider) - { - _itemsByEvaluatedInclude.Add(item.EvaluatedInclude, immutableInstanceProvider.ImmutableInstance); - } - } + _itemsByEvaluatedInclude = new ImmutableLinkedMultiDictionaryConverter( + linkedProject.GetItemsByEvaluatedInclude, + item => ConvertCachedProjectItemToInstance(linkedProject, this, item)); } - _globalProperties = new PropertyDictionary(linkedProject.GlobalPropertiesCount); - foreach (var property in linkedProject.GlobalPropertiesEnumerable) - { - _globalProperties.Set(ProjectPropertyInstance.Create(property.Key, property.Value)); - } + // GlobalProperties + var globalPropertiesRetrievableHashSet = new ImmutableGlobalPropertiesCollectionConverter(linkedProject.GlobalProperties, _properties); + _globalProperties = new PropertyDictionary(globalPropertiesRetrievableHashSet); + + // EnvironmentVariableProperties + _environmentVariableProperties = linkedProject.ProjectCollection.SharedReadOnlyEnvironmentProperties; - CreateEnvironmentVariablePropertiesSnapshot(linkedProject.ProjectCollection.EnvironmentProperties); - CreateTargetsSnapshot(linkedProject.Targets, null, null, null, null); - CreateImportsSnapshot(linkedProject.Imports, linkedProject.ImportsIncludingDuplicates); + // Targets + _targets = linkedProject.Targets; + InitializeTargetsData(null, null, null, null); + + // Imports + var importsListConverter = new ImmutableStringValuedListConverter(linkedProject.Imports, GetImportFullPath); + _importPaths = importsListConverter; + ImportPaths = importsListConverter; + + importsListConverter = new ImmutableStringValuedListConverter(linkedProject.ImportsIncludingDuplicates, GetImportFullPath); + _importPathsIncludingDuplicates = importsListConverter; + ImportPathsIncludingDuplicates = importsListConverter; Toolset = linkedProject.ProjectCollection.GetToolset(linkedProject.ToolsVersion); SubToolsetVersion = linkedProject.SubToolsetVersion; @@ -526,9 +527,9 @@ internal ProjectInstance(string projectFile, ProjectInstance projectToInheritFro this.TaskRegistry = projectToInheritFrom.TaskRegistry; _isImmutable = projectToInheritFrom._isImmutable; _importPaths = projectToInheritFrom._importPaths; - ImportPaths = _importPaths.AsReadOnly(); + ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = projectToInheritFrom._importPathsIncludingDuplicates; - ImportPathsIncludingDuplicates = _importPathsIncludingDuplicates.AsReadOnly(); + ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); this.EvaluatedItemElements = new List(); @@ -729,9 +730,9 @@ private ProjectInstance(ProjectInstance that, bool isImmutable, RequestedProject _itemDefinitions = that._itemDefinitions; _explicitToolsVersionSpecified = that._explicitToolsVersionSpecified; _importPaths = that._importPaths; - ImportPaths = _importPaths.AsReadOnly(); + ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = that._importPathsIncludingDuplicates; - ImportPathsIncludingDuplicates = _importPathsIncludingDuplicates.AsReadOnly(); + ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); this.EvaluatedItemElements = that.EvaluatedItemElements; @@ -874,9 +875,145 @@ public static ProjectInstance FromImmutableProjectSource(Project project, Projec return new ProjectInstance(project, fastItemLookupNeeded); } - private static ImmutableElementCollectionConverter GetImmutableElementCollectionConverter( - ICollection elementsCollection) - where T : class, IKeyed + private static IRetrievableEntryHashSet GetImmutableItemDefinitionsHashSetFromImmutableProject(Project linkedProject) + { + IDictionary linkedProjectItemDefinitions = linkedProject.ItemDefinitions; + VerifyCollectionImplementsRequiredDictionaryInterfaces( + linkedProjectItemDefinitions, + out IDictionary elementsDictionary, + out IDictionary<(string, int, int), ProjectItemDefinition> constrainedElementsDictionary); + + var hashSet = new ImmutableElementCollectionConverter( + elementsDictionary, + constrainedElementsDictionary, + ConvertCachedItemDefinitionToInstance); + + return hashSet; + } + + private static ImmutableItemDictionary GetImmutableItemsDictionaryFromImmutableProject( + Project linkedProject, + ProjectInstance owningProjectInstance) + { + var itemsByType = linkedProject.Items as IDictionary>; + if (itemsByType == null) + { + throw new ArgumentException(nameof(linkedProject)); + } + + Func convertCachedItemToInstance = + projectItem => ConvertCachedProjectItemToInstance(linkedProject, owningProjectInstance, projectItem); + + var itemDictionary = new ImmutableItemDictionary( + linkedProject.Items, + itemsByType, + convertCachedItemToInstance, + projectItemInstance => projectItemInstance.ItemType); + + return itemDictionary; + } + + private static ProjectItemInstance ConvertCachedProjectItemToInstance( + Project linkedProject, + ProjectInstance owningProjectInstance, + ProjectItem projectItem) + { + ProjectItemInstance result = null; + if (projectItem is IImmutableInstanceProvider instanceProvider) + { + result = instanceProvider.ImmutableInstance; + if (result == null) + { + var newInstance = InstantiateProjectItemInstanceFromImmutableProjectSource( + linkedProject, + owningProjectInstance, + projectItem); + + result = instanceProvider.GetOrSetImmutableInstance(newInstance); + } + } + + return result; + } + + private static ProjectItemDefinitionInstance ConvertCachedItemDefinitionToInstance(ProjectItemDefinition projectItemDefinition) + { + ProjectItemDefinitionInstance result = null; + + if (projectItemDefinition is IImmutableInstanceProvider instanceProvider) + { + result = instanceProvider.ImmutableInstance; + if (result == null) + { + IDictionary metadata = null; + if (projectItemDefinition.Metadata is IDictionary linkedMetadataDict) + { + metadata = new ImmutableElementCollectionConverter( + linkedMetadataDict, + constrainedProjectElements: null, + ConvertCachedProjectMetadataToInstance); + } + + result = instanceProvider.GetOrSetImmutableInstance( + new ProjectItemDefinitionInstance(projectItemDefinition.ItemType, metadata)); + } + } + + return result; + } + + private static ProjectMetadataInstance ConvertCachedProjectMetadataToInstance(ProjectMetadata projectMetadata) + { + ProjectMetadataInstance result = null; + + if (projectMetadata is IImmutableInstanceProvider instanceProvider) + { + result = instanceProvider.ImmutableInstance; + if (result == null) + { + result = instanceProvider.GetOrSetImmutableInstance(new ProjectMetadataInstance(projectMetadata)); + } + } + + return result; + } + + private static PropertyDictionary GetImmutablePropertyDictionaryFromImmutableProject(Project linkedProject) + { + ICollection linkedProjectProperties = linkedProject.Properties; + VerifyCollectionImplementsRequiredDictionaryInterfaces( + linkedProjectProperties, + out IDictionary elementsDictionary, + out IDictionary<(string, int, int), ProjectProperty> constrainedElementsDictionary); + + var hashSet = new ImmutableValuedElementCollectionConverter( + elementsDictionary, + constrainedElementsDictionary, + ConvertCachedPropertyToInstance); + + return new PropertyDictionary(hashSet); + } + + private static ProjectPropertyInstance ConvertCachedPropertyToInstance(ProjectProperty property) + { + ProjectPropertyInstance result = null; + + if (property is IImmutableInstanceProvider instanceProvider) + { + result = instanceProvider.ImmutableInstance; + if (result == null) + { + result = instanceProvider.GetOrSetImmutableInstance(InstantiateProjectPropertyInstance(property, isImmutable: true)); + } + } + + return result; + } + + private static void VerifyCollectionImplementsRequiredDictionaryInterfaces( + object elementsCollection, + out IDictionary elementsDictionary, + out IDictionary<(string, int, int), TCached> constrainedElementsDictionary) { // The elementsCollection we receive here is implemented in CPS as a special collection // that is both IDictionary and also IDictionary<(string, int, int), TCached>. @@ -887,25 +1024,14 @@ private static ImmutableElementCollectionConverter GetImmutableEleme // represents the elementsCollection as an IRetrievableEntryHashSet. // That IRetrievableEntryHashSet is then used either directly or as a backing source for // another collection wrapper (e.g. PropertyDictionary). - if (elementsCollection is not IDictionary elementsDictionary || - elementsCollection is not IDictionary<(string, int, int), TCached> constrainedElementsDictionary) + if (elementsCollection is not IDictionary elementsDict || + elementsCollection is not IDictionary<(string, int, int), TCached> constrainedElementsDict) { throw new ArgumentException(nameof(elementsCollection)); } - return new ImmutableElementCollectionConverter(elementsDictionary, constrainedElementsDictionary); - } - - private static ImmutableElementCollectionConverter GetImmutableElementCollectionConverter( - IDictionary elementsDictionary) - where T : class, IKeyed - { - if (elementsDictionary is not IDictionary<(string, int, int), TCached> constrainedElementsDictionary) - { - throw new ArgumentException(nameof(elementsDictionary)); - } - - return new ImmutableElementCollectionConverter(elementsDictionary, constrainedElementsDictionary); + elementsDictionary = elementsDict; + constrainedElementsDictionary = constrainedElementsDict; } /// @@ -1734,10 +1860,12 @@ ProjectPropertyInstance IPropertyProvider.GetProperty(s /// public string GetPropertyValue(string name) { - ProjectPropertyInstance property = _properties[name]; - string value = (property == null) ? String.Empty : property.EvaluatedValue; + if (!_properties.TryGetPropertyUnescapedValue(name, out string unescapedValue)) + { + unescapedValue = String.Empty; + } - return value; + return unescapedValue; } /// @@ -2816,28 +2944,6 @@ private static IDictionary CreateCloneDictionary(IDictio } } - private static void InitializeImmutableProjectItemDefinitionInstances(IDictionary projectItemDefinitions) - { - foreach (var projectItemDefinition in projectItemDefinitions.Values) - { - if (projectItemDefinition is IImmutableInstanceProvider immutableInstanceProvider) - { - immutableInstanceProvider.ImmutableInstance = new ProjectItemDefinitionInstance(projectItemDefinition); - } - } - } - - private static void InitializeImmutableProjectPropertyInstances(ICollection projectProperties) - { - foreach (var projectProperty in projectProperties) - { - if (projectProperty is IImmutableInstanceProvider immutableInstanceProvider) - { - immutableInstanceProvider.ImmutableInstance = InstantiateProjectPropertyInstance(projectProperty, isImmutable: true); - } - } - } - private static ProjectPropertyInstance InstantiateProjectPropertyInstance(ProjectProperty property, bool isImmutable) { // Allow reserved property names, since this is how they are added to the project instance. @@ -2883,9 +2989,9 @@ private void Initialize( _actualTargets = new RetrievableEntryHashSet(StringComparer.OrdinalIgnoreCase); _targets = new ObjectModel.ReadOnlyDictionary(_actualTargets); _importPaths = new List(); - ImportPaths = _importPaths.AsReadOnly(); + ImportPaths = new ObjectModel.ReadOnlyCollection(_importPaths); _importPathsIncludingDuplicates = new List(); - ImportPathsIncludingDuplicates = _importPathsIncludingDuplicates.AsReadOnly(); + ImportPathsIncludingDuplicates = new ObjectModel.ReadOnlyCollection(_importPathsIncludingDuplicates); _globalProperties = new PropertyDictionary((globalProperties == null) ? 0 : globalProperties.Count); _environmentVariableProperties = buildParameters.EnvironmentPropertiesInternal; _itemDefinitions = new RetrievableEntryHashSet(MSBuildNameIgnoreCaseComparer.Default); @@ -3002,8 +3108,16 @@ private void CreateTargetsSnapshot( // ProjectTargetInstances are immutable so only the dictionary must be cloned _targets = CreateCloneDictionary(targets); - this.DefaultTargets = defaultTargets == null ? new List(0) : new List(defaultTargets); - this.InitialTargets = defaultTargets == null ? new List(0) : new List(initialTargets); + InitializeTargetsData(defaultTargets, initialTargets, beforeTargets, afterTargets); + } + + private void InitializeTargetsData(List defaultTargets, + List initialTargets, + IDictionary> beforeTargets, + IDictionary> afterTargets) + { + DefaultTargets = defaultTargets == null ? new List(0) : new List(defaultTargets); + InitialTargets = initialTargets == null ? new List(0) : new List(initialTargets); ((IEvaluatorData)this).BeforeTargets = CreateCloneDictionary(beforeTargets, StringComparer.OrdinalIgnoreCase); ((IEvaluatorData)this).AfterTargets = CreateCloneDictionary(afterTargets, StringComparer.OrdinalIgnoreCase); } @@ -3013,29 +3127,31 @@ private void CreateTargetsSnapshot( /// private void CreateImportsSnapshot(IList importClosure, IList importClosureWithDuplicates) { - _importPaths = new List(Math.Max(0, importClosure.Count - 1) /* outer project */); + var importPaths = new List(Math.Max(0, importClosure.Count - 1) /* outer project */); foreach (var resolvedImport in importClosure) { // Exclude outer project itself if (resolvedImport.ImportingElement != null) { - _importPaths.Add(resolvedImport.ImportedProject.FullPath); + importPaths.Add(resolvedImport.ImportedProject.FullPath); } } - ImportPaths = _importPaths.AsReadOnly(); + _importPaths = importPaths; + ImportPaths = importPaths.AsReadOnly(); - _importPathsIncludingDuplicates = new List(Math.Max(0, importClosureWithDuplicates.Count - 1) /* outer project */); + var importPathsIncludingDuplicates = new List(Math.Max(0, importClosureWithDuplicates.Count - 1) /* outer project */); foreach (var resolvedImport in importClosureWithDuplicates) { // Exclude outer project itself if (resolvedImport.ImportingElement != null) { - _importPathsIncludingDuplicates.Add(resolvedImport.ImportedProject.FullPath); + importPathsIncludingDuplicates.Add(resolvedImport.ImportedProject.FullPath); } } - ImportPathsIncludingDuplicates = _importPathsIncludingDuplicates.AsReadOnly(); + _importPathsIncludingDuplicates = importPathsIncludingDuplicates; + ImportPathsIncludingDuplicates = importPathsIncludingDuplicates.AsReadOnly(); } /// @@ -3074,11 +3190,13 @@ private void CreateEvaluatedIncludeSnapshotIfRequested(bool keepEvaluationCache, return; } - _itemsByEvaluatedInclude = new MultiDictionary(StringComparer.OrdinalIgnoreCase); + var multiDictionary = new MultiDictionary(StringComparer.OrdinalIgnoreCase); foreach (var item in items) { - _itemsByEvaluatedInclude.Add(item.EvaluatedInclude, projectItemToInstanceMap[item]); + multiDictionary.Add(item.EvaluatedInclude, projectItemToInstanceMap[item]); } + + _itemsByEvaluatedInclude = multiDictionary; } /// @@ -3100,22 +3218,9 @@ private Dictionary CreateItemsSnapshot(ICollec return projectItemToInstanceMap; } - private void InitializeImmutableProjectItemInstances(ICollection projectItems) - { - foreach (var projectItem in projectItems) - { - if (projectItem is IImmutableInstanceProvider immutableInstanceProvider) - { - ProjectItemInstance instance = InstantiateProjectItemInstance(projectItem); - immutableInstanceProvider.ImmutableInstance = instance; - } - } - } - private ProjectItemInstance InstantiateProjectItemInstance(ProjectItem item) { List inheritedItemDefinitions = null; - if (item.InheritedItemDefinitions != null) { inheritedItemDefinitions = new List(item.InheritedItemDefinitions.Count); @@ -3129,7 +3234,6 @@ private ProjectItemInstance InstantiateProjectItemInstance(ProjectItem item) } CopyOnWritePropertyDictionary directMetadata = null; - if (item.DirectMetadata != null) { directMetadata = new CopyOnWritePropertyDictionary(); @@ -3138,16 +3242,87 @@ private ProjectItemInstance InstantiateProjectItemInstance(ProjectItem item) directMetadata.ImportProperties(projectMetadataInstances); } + GetEvaluatedIncludesFromProjectItem( + item, + out string evaluatedIncludeEscaped, + out string evaluatedIncludeBeforeWildcardExpansionEscaped); + + var instance = new ProjectItemInstance( + this, + item.ItemType, + evaluatedIncludeEscaped, + evaluatedIncludeBeforeWildcardExpansionEscaped, + directMetadata, + inheritedItemDefinitions, + item.Xml.ContainingProject.EscapedFullPath, + useItemDefinitionsWithoutModification: false); + + return instance; + } + + private static void GetEvaluatedIncludesFromProjectItem( + ProjectItem item, + out string evaluatedIncludeEscaped, + out string evaluatedIncludeBeforeWildcardExpansionEscaped) + { // For externally constructed ProjectItem, fall back to the publicly available EvaluateInclude - var evaluatedIncludeEscaped = ((IItem)item).EvaluatedIncludeEscaped; + evaluatedIncludeEscaped = ((IItem)item).EvaluatedIncludeEscaped; evaluatedIncludeEscaped ??= item.EvaluatedInclude; - var evaluatedIncludeBeforeWildcardExpansionEscaped = item.EvaluatedIncludeBeforeWildcardExpansionEscaped; + evaluatedIncludeBeforeWildcardExpansionEscaped = item.EvaluatedIncludeBeforeWildcardExpansionEscaped; evaluatedIncludeBeforeWildcardExpansionEscaped ??= item.EvaluatedInclude; + } + + private static ProjectItemInstance InstantiateProjectItemInstanceFromImmutableProjectSource( + Project linkedProject, + ProjectInstance projectInstance, + ProjectItem item) + { + linkedProject.ItemDefinitions.TryGetValue(item.ItemType, out ProjectItemDefinition itemTypeDefinition); + + IList inheritedItemDefinitions = + new ImmutableItemDefinitionsListConverter( + item.InheritedItemDefinitions, + itemTypeDefinition, + ConvertCachedItemDefinitionToInstance); + + ICopyOnWritePropertyDictionary directMetadata = null; + if (item.DirectMetadata is not null) + { + if (item.DirectMetadata is IDictionary metadataDict) + { + directMetadata = new ImmutablePropertyCollectionConverter(metadataDict, ConvertCachedProjectMetadataToInstance); + } + else + { + directMetadata = new CopyOnWritePropertyDictionary(); + + IEnumerable projectMetadataInstances = item.DirectMetadata.Select(directMetadatum => new ProjectMetadataInstance(directMetadatum)); + directMetadata.ImportProperties(projectMetadataInstances); + } + } - ProjectItemInstance instance = new ProjectItemInstance(this, item.ItemType, evaluatedIncludeEscaped, evaluatedIncludeBeforeWildcardExpansionEscaped, directMetadata, inheritedItemDefinitions, item.Xml.ContainingProject.EscapedFullPath); + GetEvaluatedIncludesFromProjectItem( + item, + out string evaluatedIncludeEscaped, + out string evaluatedIncludeBeforeWildcardExpansionEscaped); + + ProjectItemInstance instance = new ProjectItemInstance( + projectInstance, + item.ItemType, + evaluatedIncludeEscaped, + evaluatedIncludeBeforeWildcardExpansionEscaped, + directMetadata, + inheritedItemDefinitions, + item.Xml.ContainingProject.EscapedFullPath, + useItemDefinitionsWithoutModification: true); return instance; } + private static string GetImportFullPath(ResolvedImport import) + { + return import.ImportedProject.FullPath; + } + /// /// Create ItemDefinitions snapshot /// diff --git a/src/Build/Instance/ProjectItemDefinitionInstance.cs b/src/Build/Instance/ProjectItemDefinitionInstance.cs index fa41b3882ec..2051107c8bd 100644 --- a/src/Build/Instance/ProjectItemDefinitionInstance.cs +++ b/src/Build/Instance/ProjectItemDefinitionInstance.cs @@ -10,6 +10,7 @@ using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Instance; using Microsoft.Build.Shared; #nullable disable @@ -31,9 +32,8 @@ public class ProjectItemDefinitionInstance : IKeyed, IMetadataTable, IItemDefini /// /// Collection of metadata that link the XML metadata and instance metadata /// Since evaluation has occurred, this is an unordered collection. - /// Is never null or empty. /// - private CopyOnWritePropertyDictionary _metadata; + private IDictionary _metadata; /// /// Constructs an empty project item definition instance. @@ -58,13 +58,25 @@ internal ProjectItemDefinitionInstance(ProjectItemDefinition itemDefinition) { if (itemDefinition.MetadataCount > 0) { - _metadata = new CopyOnWritePropertyDictionary(); - + var copyOnWriteMetadataDictionary = new CopyOnWritePropertyDictionary(); IEnumerable projectMetadataInstances = itemDefinition.Metadata.Select(originalMetadata => new ProjectMetadataInstance(originalMetadata)); - _metadata.ImportProperties(projectMetadataInstances); + copyOnWriteMetadataDictionary.ImportProperties(projectMetadataInstances); + + _metadata = copyOnWriteMetadataDictionary; } } + /// + /// Initializes a new instance of the class. + /// + /// The type of item this definition object represents. + /// A (possibly null) collection of the metadata associated with this item definition. + internal ProjectItemDefinitionInstance(string itemType, IDictionary metadata) + : this(itemType) + { + _metadata = metadata; + } + private ProjectItemDefinitionInstance() { } @@ -95,7 +107,7 @@ public ICollection Metadata return ReadOnlyEmptyCollection.Instance; } - return new ReadOnlyCollection(_metadata); + return new ReadOnlyCollection(_metadata.Values); } } @@ -110,21 +122,7 @@ public int MetadataCount /// /// Names of all metadata on this item definition /// - public IEnumerable MetadataNames - { - get - { - if (_metadata == null) - { - yield break; - } - - foreach (ProjectMetadataInstance metadatum in _metadata) - { - yield return metadatum.Name; - } - } - } + public IEnumerable MetadataNames => _metadata == null ? Enumerable.Empty() : _metadata.Keys; /// /// Implementation of IKeyed exposing the item type, so these @@ -213,8 +211,9 @@ internal ProjectItemDefinitionElement ToProjectItemDefinitionElement(ProjectElem { ProjectItemDefinitionElement element = parent.ContainingProject.CreateItemDefinitionElement(ItemType); parent.AppendChild(element); - foreach (ProjectMetadataInstance metadataInstance in _metadata) + foreach (var kvp in _metadata) { + ProjectMetadataInstance metadataInstance = kvp.Value; element.AddMetadata(metadataInstance.Name, metadataInstance.EvaluatedValue); } @@ -224,7 +223,7 @@ internal ProjectItemDefinitionElement ToProjectItemDefinitionElement(ProjectElem void ITranslatable.Translate(ITranslator translator) { translator.Translate(ref _itemType); - translator.TranslateDictionary(ref _metadata, ProjectMetadataInstance.FactoryForDeserialization); + translator.TranslateDictionary(ref _metadata, ProjectMetadataInstance.FactoryForDeserialization, CreateMetadataCollection); } internal static ProjectItemDefinitionInstance FactoryForDeserialization(ITranslator translator) @@ -236,5 +235,10 @@ internal static ProjectItemDefinitionInstance FactoryForDeserialization(ITransla } string IItemTypeDefinition.ItemType => _itemType; + + private static IDictionary CreateMetadataCollection(int capacity) + { + return new CopyOnWritePropertyDictionary(); + } } } diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index cf6fc186770..5769a0fa12e 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -77,7 +77,7 @@ internal ProjectItemInstance(ProjectInstance project, string itemType, string in /// Mutability follows the project. /// internal ProjectItemInstance(ProjectInstance project, string itemType, string includeEscaped, string includeBeforeWildcardExpansionEscaped, string definingFileEscaped) - : this(project, itemType, includeEscaped, includeBeforeWildcardExpansionEscaped, null /* no direct metadata */, null /* need to add item definition metadata */, definingFileEscaped) + : this(project, itemType, includeEscaped, includeBeforeWildcardExpansionEscaped, null /* no direct metadata */, null /* need to add item definition metadata */, definingFileEscaped, useItemDefinitionsWithoutModification: false) { } @@ -94,9 +94,17 @@ internal ProjectItemInstance(ProjectInstance project, string itemType, string in /// /// Not public since the only creation scenario is setting on a project. /// - internal ProjectItemInstance(ProjectInstance project, string itemType, string includeEscaped, string includeBeforeWildcardExpansionEscaped, CopyOnWritePropertyDictionary directMetadata, List itemDefinitions, string definingFileEscaped) + internal ProjectItemInstance( + ProjectInstance project, + string itemType, + string includeEscaped, + string includeBeforeWildcardExpansionEscaped, + ICopyOnWritePropertyDictionary directMetadata, + IList itemDefinitions, + string definingFileEscaped, + bool useItemDefinitionsWithoutModification) { - CommonConstructor(project, itemType, includeEscaped, includeBeforeWildcardExpansionEscaped, directMetadata, itemDefinitions, definingFileEscaped); + CommonConstructor(project, itemType, includeEscaped, includeBeforeWildcardExpansionEscaped, directMetadata, itemDefinitions, definingFileEscaped, useItemDefinitionsWithoutModification); } /// @@ -121,7 +129,7 @@ internal ProjectItemInstance(ProjectInstance project, string itemType, string in metadata.ImportProperties(directMetadataInstances); } - CommonConstructor(project, itemType, includeEscaped, includeEscaped, metadata, null /* need to add item definition metadata */, definingFileEscaped); + CommonConstructor(project, itemType, includeEscaped, includeEscaped, metadata, null /* need to add item definition metadata */, definingFileEscaped, useItemDefinitionsWithoutModification: false); } /// @@ -615,7 +623,7 @@ internal static ProjectItemInstance FactoryForDeserialization(ITranslator transl /// Add a metadata with the specified names and values. /// Overwrites any metadata with the same name already in the collection. /// - internal void SetMetadata(CopyOnWritePropertyDictionary metadataDictionary) + internal void SetMetadata(ICopyOnWritePropertyDictionary metadataDictionary) { _project.VerifyThrowNotImmutable(); @@ -682,33 +690,53 @@ internal ProjectItemElement ToProjectItemElement(ProjectElementContainer parent) /// Inherited item definition metadata may be null. It is assumed to ALREADY HAVE BEEN CLONED. /// Mutability follows the project. /// - private void CommonConstructor(ProjectInstance projectToUse, string itemTypeToUse, string includeEscaped, string includeBeforeWildcardExpansionEscaped, CopyOnWritePropertyDictionary directMetadata, List itemDefinitions, string definingFileEscaped) + private void CommonConstructor( + ProjectInstance projectToUse, + string itemTypeToUse, + string includeEscaped, + string includeBeforeWildcardExpansionEscaped, + ICopyOnWritePropertyDictionary directMetadata, + IList itemDefinitions, + string definingFileEscaped, + bool useItemDefinitionsWithoutModification) { ErrorUtilities.VerifyThrowArgumentNull(projectToUse, "project"); ErrorUtilities.VerifyThrowArgumentLength(itemTypeToUse, "itemType"); XmlUtilities.VerifyThrowArgumentValidElementName(itemTypeToUse); ErrorUtilities.VerifyThrowArgument(!XMakeElements.ReservedItemNames.Contains(itemTypeToUse), "OM_ReservedName", itemTypeToUse); - // TaskItems don't have an item type. So for their benefit, we have to lookup and add the regular item definition. - List inheritedItemDefinitions = (itemDefinitions == null) ? null : new List(itemDefinitions); + IList inheritedItemDefinitions; + if (itemDefinitions == null || !useItemDefinitionsWithoutModification) + { + // TaskItems don't have an item type. So for their benefit, we have to lookup and add the regular item definition. + inheritedItemDefinitions = (itemDefinitions == null) ? null : new List(itemDefinitions); - ProjectItemDefinitionInstance itemDefinition; - if (projectToUse.ItemDefinitions.TryGetValue(itemTypeToUse, out itemDefinition)) + ProjectItemDefinitionInstance itemDefinition; + if (projectToUse.ItemDefinitions.TryGetValue(itemTypeToUse, out itemDefinition)) + { + inheritedItemDefinitions ??= new List(); + inheritedItemDefinitions.Add(itemDefinition); + } + } + else { - inheritedItemDefinitions ??= new List(); - inheritedItemDefinitions.Add(itemDefinition); + // In this case the caller specifying useItemDefinitionsWithoutModification is guaranteeing that + // the itemDefinitions collection contains all necessary definitions (including the definition + // associated with itemTypeToUse) and, for performance reasons, the provided (immutable) collection + // should be used as is. + inheritedItemDefinitions = itemDefinitions; } _project = projectToUse; _itemType = itemTypeToUse; _taskItem = new TaskItem( - includeEscaped, - includeBeforeWildcardExpansionEscaped, - directMetadata?.DeepClone(), // copy on write! - inheritedItemDefinitions, - _project.Directory, - _project.IsImmutable, - definingFileEscaped); + includeEscaped, + includeBeforeWildcardExpansionEscaped, + directMetadata?.DeepClone(), // copy on write! + inheritedItemDefinitions, + _project.Directory, + _project.IsImmutable, + definingFileEscaped); } /// @@ -751,7 +779,7 @@ internal sealed class TaskItem : /// Lazily created, as there are huge numbers of items generated in /// a build that have no metadata at all. /// - private CopyOnWritePropertyDictionary _directMetadata; + private ICopyOnWritePropertyDictionary _directMetadata; /// /// Cached value of the fullpath metadata. All other metadata are computed on demand. @@ -765,7 +793,7 @@ internal sealed class TaskItem : /// be item definitions inherited from items that were /// used to create this item. /// - private List _itemDefinitions; + private IList _itemDefinitions; /// /// Directory of the associated project. If this is available, @@ -794,8 +822,8 @@ internal TaskItem(string includeEscaped, string definingFileEscaped) internal TaskItem( string includeEscaped, string includeBeforeWildcardExpansionEscaped, - CopyOnWritePropertyDictionary directMetadata, - List itemDefinitions, + ICopyOnWritePropertyDictionary directMetadata, + IList itemDefinitions, string projectDirectory, bool immutable, string definingFileEscaped) // the actual project file (or import) that defines this item. @@ -909,11 +937,11 @@ public ICollection MetadataNames { get { - CopyOnWritePropertyDictionary metadataCollection = MetadataCollection; + ICopyOnWritePropertyDictionary metadataCollection = MetadataCollection; List names = new List(capacity: metadataCollection.Count + FileUtilities.ItemSpecModifiers.All.Length); - foreach (ProjectMetadataInstance metadatum in metadataCollection) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)metadataCollection) { names.Add(metadatum.Name); } @@ -1058,11 +1086,11 @@ public void ImportMetadata(IEnumerable> metadata) /// /// The source list to return metadata from. /// An array of string key-value pairs representing metadata. - private IEnumerable> EnumerateMetadataEager(CopyOnWritePropertyDictionary list) + private IEnumerable> EnumerateMetadataEager(ICopyOnWritePropertyDictionary list) { var result = new List>(list.Count); - foreach (var projectMetadataInstance in list) + foreach (var projectMetadataInstance in list.Values) { if (projectMetadataInstance != null) { @@ -1074,9 +1102,9 @@ private IEnumerable> EnumerateMetadataEager(CopyOnW return result.ToArray(); } - private IEnumerable> EnumerateMetadata(CopyOnWritePropertyDictionary list) + private IEnumerable> EnumerateMetadata(ICopyOnWritePropertyDictionary list) { - foreach (var projectMetadataInstance in list) + foreach (var projectMetadataInstance in list.Values) { if (projectMetadataInstance != null) { @@ -1093,7 +1121,7 @@ private IEnumerable> EnumerateMetadata(CopyOnWriteP /// This is a read-only collection. To modify the metadata, use . /// Computed, not necessarily fast. /// - internal CopyOnWritePropertyDictionary MetadataCollection + internal ICopyOnWritePropertyDictionary MetadataCollection { get { @@ -1137,7 +1165,7 @@ IEnumerable metaData() // Finally any direct metadata win. if (_directMetadata != null) { - foreach (ProjectMetadataInstance metadatum in _directMetadata) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)_directMetadata) { if (metadatum != null) { @@ -1287,16 +1315,16 @@ public string GetMetadataEscaped(string metadataName) ErrorUtilities.VerifyThrowArgumentLength(metadataName, nameof(metadataName)); } - ProjectMetadataInstance metadatum; if (_directMetadata != null) { - metadatum = _directMetadata[metadataName]; - if (metadatum != null) + string escapedValue = _directMetadata.GetEscapedValue(metadataName); + if (escapedValue != null) { - return metadatum.EvaluatedValueEscaped; + return escapedValue; } } + ProjectMetadataInstance metadatum; metadatum = GetItemDefinitionMetadata(metadataName); if (metadatum != null && Expander.ExpressionMayContainExpandableExpressions(metadatum.EvaluatedValueEscaped)) @@ -1404,7 +1432,10 @@ public void CopyMetadataTo(ITaskItem destinationItem, bool addOriginalItemSpec) } else if (_itemDefinitions != null) { - destinationAsTaskItem._itemDefinitions.AddRange(_itemDefinitions); + foreach (var itemDefinition in _itemDefinitions) + { + destinationAsTaskItem._itemDefinitions.Add(itemDefinition); + } } } else if (destinationItem is IMetadataContainer destinationItemAsMetadataContainer) @@ -1428,7 +1459,7 @@ public void CopyMetadataTo(ITaskItem destinationItem, bool addOriginalItemSpec) else { // OK, most likely the destination item was a Microsoft.Build.Utilities.TaskItem. - foreach (ProjectMetadataInstance metadatum in MetadataCollection) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)MetadataCollection) { // When copying metadata, we do NOT overwrite metadata already on the destination item. string destinationValue = destinationItem.GetMetadata(metadatum.Name); @@ -1460,7 +1491,7 @@ public IDictionary CloneCustomMetadata() var metadata = MetadataCollection; Dictionary clonedMetadata = new Dictionary(metadata.Count, MSBuildNameIgnoreCaseComparer.Default); - foreach (ProjectMetadataInstance metadatum in metadata) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)metadata) { clonedMetadata[metadatum.Name] = metadatum.EvaluatedValue; } @@ -1477,7 +1508,7 @@ IDictionary ITaskItem2.CloneCustomMetadataEscaped() { Dictionary clonedMetadata = new Dictionary(MSBuildNameIgnoreCaseComparer.Default); - foreach (ProjectMetadataInstance metadatum in MetadataCollection) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)MetadataCollection) { clonedMetadata[metadatum.Name] = metadatum.EvaluatedValueEscaped; } @@ -1501,8 +1532,17 @@ void ITranslatable.Translate(ITranslator translator) translator.Translate(ref _isImmutable); translator.Translate(ref _definingFileEscaped); - translator.Translate(ref _itemDefinitions, ProjectItemDefinitionInstance.FactoryForDeserialization); - translator.TranslateDictionary(ref _directMetadata, ProjectMetadataInstance.FactoryForDeserialization); + TranslatorHelpers.Translate( + translator, + ref _itemDefinitions, + ProjectItemDefinitionInstance.FactoryForDeserialization, + (capacity) => new List(capacity)); + + TranslatorHelpers.TranslateDictionary( + translator, + ref _directMetadata, + ProjectMetadataInstance.FactoryForDeserialization, + (capacity) => new CopyOnWritePropertyDictionary()); if (_itemDefinitions?.Count == 0) { @@ -1580,7 +1620,8 @@ public bool Equals(TaskItem other) // the set of metadata names on 'this', to avoid computing the full metadata collection // of both 'this' and 'other'. Once we have the names for 'this', we enumerate 'other' // and ensure the names we see there are set-equal to the names we produce here. - var thisNames = new HashSet(MSBuildNameIgnoreCaseComparer.Default); + int capacity = _itemDefinitions?.Count ?? 0 + _directMetadata?.Count ?? 0; + var thisNames = new HashSet(capacity, MSBuildNameIgnoreCaseComparer.Default); if (_itemDefinitions is not null) { @@ -1592,20 +1633,20 @@ public bool Equals(TaskItem other) if (_directMetadata is not null) { - foreach (ProjectMetadataInstance metadatum in _directMetadata) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)_directMetadata) { thisNames.Add(metadatum.Name); } } - CopyOnWritePropertyDictionary otherMetadata = other.MetadataCollection; + ICopyOnWritePropertyDictionary otherMetadata = other.MetadataCollection; if (otherMetadata.Count != thisNames.Count) { return false; } - foreach (ProjectMetadataInstance metadatum in otherMetadata) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)otherMetadata) { string name = metadatum.Name; @@ -1702,7 +1743,11 @@ private void ReadInternString(ITranslator translator, LookasideStringInterner in /// internal void TranslateWithInterning(ITranslator translator, LookasideStringInterner interner) { - translator.Translate(ref _itemDefinitions, ProjectItemDefinitionInstance.FactoryForDeserialization); + TranslatorHelpers.Translate( + translator, + ref _itemDefinitions, + ProjectItemDefinitionInstance.FactoryForDeserialization, + (capacity) => new List(capacity)); translator.Translate(ref _isImmutable); translator.Translate(ref _includeEscaped); @@ -1711,14 +1756,14 @@ internal void TranslateWithInterning(ITranslator translator, LookasideStringInte WriteInternString(translator, interner, ref _includeBeforeWildcardExpansionEscaped); WriteInternString(translator, interner, ref _definingFileEscaped); - CopyOnWritePropertyDictionary temp = MetadataCollection; + ICopyOnWritePropertyDictionary temp = MetadataCollection; // Intern the metadata if (translator.TranslateNullable(temp)) { int count = temp.Count; translator.Writer.Write(count); - foreach (ProjectMetadataInstance metadatum in temp) + foreach (ProjectMetadataInstance metadatum in (IEnumerable)temp) { int key = interner.Intern(metadatum.Name); int value = interner.Intern(metadatum.EvaluatedValueEscaped); @@ -1779,7 +1824,7 @@ internal ProjectMetadataInstance GetMetadataObject(string name) /// Add a metadata with the specified name and value. /// Overwrites any metadata with the same name already in the collection. /// - internal void SetMetadata(CopyOnWritePropertyDictionary metadata) + internal void SetMetadata(ICopyOnWritePropertyDictionary metadata) { ProjectInstance.VerifyThrowNotImmutable(_isImmutable); @@ -2051,7 +2096,7 @@ private ProjectItemInstance CreateItem(string includeEscaped, string includeBefo itemDefinitionsClone.Add(sourceItemDefinition); } - return new ProjectItemInstance(_project, ItemType, includeEscaped, includeBeforeWildcardExpansionEscaped, source._taskItem._directMetadata, itemDefinitionsClone, definingProject); + return new ProjectItemInstance(_project, ItemType, includeEscaped, includeBeforeWildcardExpansionEscaped, source._taskItem._directMetadata, itemDefinitionsClone, definingProject, useItemDefinitionsWithoutModification: false); } } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index dc2d2ed13ca..68be7831145 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -276,14 +276,6 @@ - - - - - - - - @@ -394,6 +386,9 @@ + + + @@ -403,17 +398,25 @@ + + + - + + + + + + + - @@ -528,7 +531,13 @@ + + + + + + diff --git a/src/Build/ObjectModelRemoting/LinkedObjectFactory.cs b/src/Build/ObjectModelRemoting/LinkedObjectFactory.cs index c0165f577a5..4618292cc2d 100644 --- a/src/Build/ObjectModelRemoting/LinkedObjectFactory.cs +++ b/src/Build/ObjectModelRemoting/LinkedObjectFactory.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; @@ -234,18 +235,27 @@ public UsingTaskParameterGroupElement Create(UsingTaskParameterGroupElementLink #region Linked classes helpers // Using the pattern with overloaded classes that provide "Link" object so we ensure we do not increase the - // memory storage of original items (with the Link field) while it is small, some of the MSbuild items can be created + // memory storage of original items (with the Link field) while it is small, some of the MSBuild items can be created // in millions so it does adds up otherwise. private class LinkedProjectItem : ProjectItem, ILinkableObject, IImmutableInstanceProvider { + private ProjectItemInstance _immutableInstance; + internal LinkedProjectItem(ProjectItemElement xml, Project project, ProjectItemLink link) : base(xml, project) { Link = link; } - public ProjectItemInstance ImmutableInstance { get; set; } + public ProjectItemInstance ImmutableInstance => _immutableInstance; + + public ProjectItemInstance GetOrSetImmutableInstance(ProjectItemInstance instance) + { + Interlocked.CompareExchange(ref _immutableInstance, instance, null); + + return _immutableInstance; + } internal override ProjectItemLink Link { get; } @@ -254,32 +264,54 @@ internal LinkedProjectItem(ProjectItemElement xml, Project project, ProjectItemL private class LinkedProjectItemDefinition : ProjectItemDefinition, ILinkableObject, IImmutableInstanceProvider { + private ProjectItemDefinitionInstance _immutableInstance; + internal LinkedProjectItemDefinition(ProjectItemDefinitionLink link, Project project, string itemType) : base(project, itemType) { Link = link; } - public ProjectItemDefinitionInstance ImmutableInstance { get; set; } + public ProjectItemDefinitionInstance ImmutableInstance => _immutableInstance; + + public ProjectItemDefinitionInstance GetOrSetImmutableInstance(ProjectItemDefinitionInstance instance) + { + Interlocked.CompareExchange(ref _immutableInstance, instance, null); + + return _immutableInstance; + } internal override ProjectItemDefinitionLink Link { get; } object ILinkableObject.Link => Link; } - private class LinkedProjectMetadata : ProjectMetadata, ILinkableObject + private class LinkedProjectMetadata : ProjectMetadata, ILinkableObject, IImmutableInstanceProvider { + private ProjectMetadataInstance _immutableInstance; + internal LinkedProjectMetadata(object parent, ProjectMetadataLink link) : base(parent, link.Xml) { Link = link; } + public ProjectMetadataInstance ImmutableInstance => _immutableInstance; + + public ProjectMetadataInstance GetOrSetImmutableInstance(ProjectMetadataInstance instance) + { + Interlocked.CompareExchange(ref _immutableInstance, instance, null); + + return _immutableInstance; + } + internal override ProjectMetadataLink Link { get; } object ILinkableObject.Link => Link; } private class LinkedProjectProperty : ProjectProperty, ILinkableObject, IImmutableInstanceProvider { + private ProjectPropertyInstance _immutableInstance; + internal ProjectPropertyLink Link { get; } object ILinkableObject.Link => Link; @@ -295,7 +327,14 @@ internal LinkedProjectProperty(Project project, ProjectPropertyLink link) Link = link; } - public ProjectPropertyInstance ImmutableInstance { get; set; } + public ProjectPropertyInstance ImmutableInstance => _immutableInstance; + + public ProjectPropertyInstance GetOrSetImmutableInstance(ProjectPropertyInstance instance) + { + Interlocked.CompareExchange(ref _immutableInstance, instance, null); + + return _immutableInstance; + } public override string Name => Link.Name; diff --git a/src/Build/Utilities/Utilities.cs b/src/Build/Utilities/Utilities.cs index 67b454cc023..e8bf893be60 100644 --- a/src/Build/Utilities/Utilities.cs +++ b/src/Build/Utilities/Utilities.cs @@ -462,11 +462,11 @@ private static bool UsingDifferentToolsVersionFromProjectFile(string toolsVersio /// Retrieves properties derived from the current /// environment variables. /// - internal static PropertyDictionary GetEnvironmentProperties() + internal static PropertyDictionary GetEnvironmentProperties(bool makeReadOnly) { IDictionary environmentVariablesBag = CommunicationsUtilities.GetEnvironmentVariables(); - PropertyDictionary environmentProperties = new PropertyDictionary(environmentVariablesBag.Count + 2); + var envPropertiesHashSet = new RetrievableValuedEntryHashSet(environmentVariablesBag.Count + 2, MSBuildNameIgnoreCaseComparer.Default); // We set the MSBuildExtensionsPath variables here because we don't want to make them official // reserved properties; we need the ability for people to override our default in their @@ -483,11 +483,11 @@ internal static PropertyDictionary GetEnvironmentProper ? Path.Combine(programFiles32, ReservedPropertyNames.extensionsPathSuffix) : programFiles32; #endif - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath32, extensionsPath32, true)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath32, extensionsPath32, true)); #if !FEATURE_INSTALLED_MSBUILD string extensionsPath64 = extensionsPath; - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath64, extensionsPath64, true)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath64, extensionsPath64, true)); #else // "MSBuildExtensionsPath64". This points to whatever the value of "Program Files" environment variable is on a // 64-bit machine, and is empty on a 32-bit machine. @@ -500,7 +500,7 @@ internal static PropertyDictionary GetEnvironmentProper FrameworkLocationHelper.programFiles64, ReservedPropertyNames.extensionsPathSuffix) : FrameworkLocationHelper.programFiles64; - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath64, extensionsPath64, true)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath64, extensionsPath64, true)); } #endif @@ -523,13 +523,13 @@ internal static PropertyDictionary GetEnvironmentProper } #endif - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath, extensionsPath, true)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.extensionsPath, extensionsPath, true)); // Windows XP and Windows Server 2003 don't define LocalAppData in their environment. // We'll set it here if the environment doesn't have it so projects can reliably // depend on $(LocalAppData). string localAppData = String.Empty; - ProjectPropertyInstance localAppDataProp = environmentProperties.GetProperty(ReservedPropertyNames.localAppData); + ProjectPropertyInstance localAppDataProp = envPropertiesHashSet.Get(ReservedPropertyNames.localAppData); if (localAppDataProp != null) { localAppData = localAppDataProp.EvaluatedValue; @@ -551,11 +551,11 @@ internal static PropertyDictionary GetEnvironmentProper } - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.localAppData, localAppData)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.localAppData, localAppData)); // Add MSBuildUserExtensionsPath at $(LocalAppData)\Microsoft\MSBuild string userExtensionsPath = Path.Combine(localAppData, ReservedPropertyNames.userExtensionsPathSuffix); - environmentProperties.Set(ProjectPropertyInstance.Create(ReservedPropertyNames.userExtensionsPath, userExtensionsPath)); + envPropertiesHashSet.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.userExtensionsPath, userExtensionsPath)); foreach (KeyValuePair environmentVariable in environmentVariablesBag) { @@ -570,7 +570,7 @@ internal static PropertyDictionary GetEnvironmentProper { ProjectPropertyInstance environmentProperty = ProjectPropertyInstance.Create(environmentVariableName, environmentVariable.Value); - environmentProperties.Set(environmentProperty); + envPropertiesHashSet.Add(environmentProperty); } else { @@ -579,6 +579,12 @@ internal static PropertyDictionary GetEnvironmentProper } } + if (makeReadOnly) + { + envPropertiesHashSet.MakeReadOnly(); + } + + var environmentProperties = new PropertyDictionary(envPropertiesHashSet); return environmentProperties; }