Skip to content

Commit 50eebae

Browse files
committed
Fix TaskRegistry mutability
1 parent 4598629 commit 50eebae

File tree

6 files changed

+200
-153
lines changed

6 files changed

+200
-153
lines changed

src/Build.UnitTests/BackEnd/TaskRegistry_Tests.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,18 +2182,15 @@ internal TaskRegistry CreateTaskRegistryAndRegisterTasks(List<ProjectUsingTaskEl
21822182
? new TaskRegistry(toolset, ProjectCollection.GlobalProjectCollection.ProjectRootElementCache)
21832183
: new TaskRegistry(ProjectCollection.GlobalProjectCollection.ProjectRootElementCache);
21842184

2185-
foreach (ProjectUsingTaskElement projectUsingTaskElement in usingTaskElements)
2186-
{
2187-
TaskRegistry.RegisterTasksFromUsingTaskElement(
2188-
_loggingService,
2189-
_loggerContext,
2190-
Directory.GetCurrentDirectory(),
2191-
projectUsingTaskElement,
2192-
registry,
2193-
RegistryExpander,
2194-
ExpanderOptions.ExpandPropertiesAndItems,
2195-
FileSystems.Default);
2196-
}
2185+
string currentDir = Directory.GetCurrentDirectory();
2186+
TaskRegistry.InitializeTaskRegistryFromUsingTaskElements(
2187+
_loggingService,
2188+
_loggerContext,
2189+
usingTaskElements.Select(el => (el, currentDir)),
2190+
registry,
2191+
RegistryExpander,
2192+
ExpanderOptions.ExpandPropertiesAndItems,
2193+
FileSystems.Default);
21972194

21982195
return registry;
21992196
}

src/Build/Definition/ProjectImportPathMatch.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ namespace Microsoft.Build.Evaluation
1111
{
1212
/// <summary>
1313
/// Class representing a reference to a project import path with property fall-back
14+
/// This class is immutable.
15+
/// If mutability would be needed in the future, it should be implemented via copy-on-write or
16+
/// a DeepClone would need to be added (and called from DeepClone methods of owning types)
1417
/// </summary>
1518
internal class ProjectImportPathMatch : ITranslatable
1619
{
@@ -19,14 +22,19 @@ internal class ProjectImportPathMatch : ITranslatable
1922
/// </summary>
2023
public static readonly ProjectImportPathMatch None = new ProjectImportPathMatch(string.Empty, new List<string>());
2124

25+
// Those are effectively readonly and should stay so. Cannot be marked readonly due to ITranslatable
26+
private string _propertyName;
27+
private string _msBuildPropertyFormat;
28+
private List<string> _searchPaths;
29+
2230
internal ProjectImportPathMatch(string propertyName, List<string> searchPaths)
2331
{
2432
ErrorUtilities.VerifyThrowArgumentNull(propertyName, nameof(propertyName));
2533
ErrorUtilities.VerifyThrowArgumentNull(searchPaths, nameof(searchPaths));
2634

27-
PropertyName = propertyName;
28-
SearchPaths = searchPaths;
29-
MsBuildPropertyFormat = $"$({PropertyName})";
35+
_propertyName = propertyName;
36+
_searchPaths = searchPaths;
37+
_msBuildPropertyFormat = $"$({PropertyName})";
3038
}
3139

3240
public ProjectImportPathMatch(ITranslator translator)
@@ -37,23 +45,23 @@ public ProjectImportPathMatch(ITranslator translator)
3745
/// <summary>
3846
/// String representation of the property reference - eg. "MSBuildExtensionsPath32"
3947
/// </summary>
40-
public string PropertyName;
48+
public string PropertyName => _propertyName;
4149

4250
/// <summary>
4351
/// Returns the corresponding property name - eg. "$(MSBuildExtensionsPath32)"
4452
/// </summary>
45-
public string MsBuildPropertyFormat;
53+
public string MsBuildPropertyFormat => _msBuildPropertyFormat;
4654

4755
/// <summary>
4856
/// Enumeration of the search paths for the property.
4957
/// </summary>
50-
public List<string> SearchPaths;
58+
public List<string> SearchPaths => _searchPaths;
5159

5260
public void Translate(ITranslator translator)
5361
{
54-
translator.Translate(ref PropertyName);
55-
translator.Translate(ref MsBuildPropertyFormat);
56-
translator.Translate(ref SearchPaths);
62+
translator.Translate(ref _propertyName);
63+
translator.Translate(ref _msBuildPropertyFormat);
64+
translator.Translate(ref _searchPaths);
5765
}
5866

5967
/// <summary>

src/Build/Definition/Toolset.cs

Lines changed: 77 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
using System.IO;
99
using System.Linq;
1010
using System.Xml;
11-
1211
using Microsoft.Build.BackEnd;
1312
using Microsoft.Build.Collections;
1413
using Microsoft.Build.Construction;
@@ -190,11 +189,6 @@ public class Toolset : ITranslatable
190189
/// </summary>
191190
private Expander<ProjectPropertyInstance, ProjectItemInstance> _expander;
192191

193-
/// <summary>
194-
/// Bag of properties for the expander to expand the properties and items in the using tasks files
195-
/// </summary>
196-
private PropertyDictionary<ProjectPropertyInstance> _propertyBag;
197-
198192
/// <summary>
199193
/// SubToolsets that map to this toolset.
200194
/// </summary>
@@ -901,79 +895,79 @@ private void RegisterDefaultTasks(ILoggingService loggingServices, BuildEventCon
901895
/// </summary>
902896
private void InitializeProperties(ILoggingService loggingServices, BuildEventContext buildEventContext)
903897
{
898+
if (_expander != null)
899+
{
900+
return;
901+
}
902+
904903
try
905904
{
906-
if (_propertyBag == null)
907-
{
908-
List<ProjectPropertyInstance> reservedProperties = new List<ProjectPropertyInstance>();
905+
906+
List<ProjectPropertyInstance> reservedProperties = new List<ProjectPropertyInstance>();
909907

910-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.binPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
911-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsVersion, ToolsVersion, mayBeReserved: true));
908+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.binPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
909+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsVersion, ToolsVersion, mayBeReserved: true));
912910

913-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
914-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion, mayBeReserved: true));
915-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild, mayBeReserved: true));
911+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.toolsPath, EscapingUtilities.Escape(ToolsPath), mayBeReserved: true));
912+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.assemblyVersion, Constants.AssemblyVersion, mayBeReserved: true));
913+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.version, MSBuildAssemblyFileVersion.Instance.MajorMinorBuild, mayBeReserved: true));
916914

917-
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.msbuildRuntimeType,
915+
reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.msbuildRuntimeType,
918916
#if RUNTIME_TYPE_NETCORE
919-
Traits.Instance.ForceEvaluateAsFullFramework ? "Full" : "Core",
917+
Traits.Instance.ForceEvaluateAsFullFramework ? "Full" : "Core",
920918
#elif MONO
921-
NativeMethodsShared.IsMono ? "Mono" : "Full");
919+
NativeMethodsShared.IsMono ? "Mono" : "Full");
922920
#else
923-
"Full",
921+
"Full",
924922
#endif
925-
mayBeReserved: true));
923+
mayBeReserved: true));
926924

927925

928-
// Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the
929-
// environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small.
930-
int count = _environmentProperties.Count + reservedProperties.Count + Properties.Values.Count + _globalProperties.Count + 1;
926+
// Add one for the subtoolset version property -- it may or may not be set depending on whether it has already been set by the
927+
// environment or global properties, but it's better to create a dictionary that's one too big than one that's one too small.
928+
int count = _environmentProperties.Count + reservedProperties.Count + Properties.Values.Count + _globalProperties.Count + 1;
931929

932-
// GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the
933-
// subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version.
934-
string subToolsetVersion = this.GenerateSubToolsetVersion();
935-
SubToolset subToolset;
936-
ICollection<ProjectPropertyInstance> subToolsetProperties = null;
930+
// GenerateSubToolsetVersion checks the environment and global properties, so it's safe to go ahead and gather the
931+
// subtoolset properties here without fearing that we'll have somehow come up with the wrong subtoolset version.
932+
string subToolsetVersion = this.GenerateSubToolsetVersion();
933+
SubToolset subToolset;
934+
ICollection<ProjectPropertyInstance> subToolsetProperties = null;
937935

938-
if (subToolsetVersion != null)
936+
if (subToolsetVersion != null)
937+
{
938+
if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset))
939939
{
940-
if (SubToolsets.TryGetValue(subToolsetVersion, out subToolset))
941-
{
942-
subToolsetProperties = subToolset.Properties.Values;
943-
count += subToolsetProperties.Count;
944-
}
940+
subToolsetProperties = subToolset.Properties.Values;
941+
count += subToolsetProperties.Count;
945942
}
943+
}
946944

947-
_propertyBag = new PropertyDictionary<ProjectPropertyInstance>(count);
948-
949-
// Should be imported in the same order as in the evaluator:
950-
// - Environment
951-
// - Toolset
952-
// - Subtoolset (if any)
953-
// - Global
954-
_propertyBag.ImportProperties(_environmentProperties);
945+
PropertyDictionary<ProjectPropertyInstance> propertyBag = new PropertyDictionary<ProjectPropertyInstance>(count);
955946

956-
_propertyBag.ImportProperties(reservedProperties);
947+
// Should be imported in the same order as in the evaluator:
948+
// - Environment
949+
// - Toolset
950+
// - Subtoolset (if any)
951+
// - Global
952+
propertyBag.ImportProperties(_environmentProperties);
957953

958-
_propertyBag.ImportProperties(Properties.Values);
954+
propertyBag.ImportProperties(reservedProperties);
959955

960-
if (subToolsetVersion != null)
961-
{
962-
_propertyBag.Set(ProjectPropertyInstance.Create(Constants.SubToolsetVersionPropertyName, subToolsetVersion));
963-
}
964-
965-
if (subToolsetProperties != null)
966-
{
967-
_propertyBag.ImportProperties(subToolsetProperties);
968-
}
956+
propertyBag.ImportProperties(Properties.Values);
969957

970-
_propertyBag.ImportProperties(_globalProperties);
958+
if (subToolsetVersion != null)
959+
{
960+
propertyBag.Set(ProjectPropertyInstance.Create(Constants.SubToolsetVersionPropertyName, subToolsetVersion));
971961
}
972962

973-
if (_expander == null)
963+
if (subToolsetProperties != null)
974964
{
975-
_expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(_propertyBag, FileSystems.Default);
965+
propertyBag.ImportProperties(subToolsetProperties);
976966
}
967+
968+
propertyBag.ImportProperties(_globalProperties);
969+
970+
_expander = new Expander<ProjectPropertyInstance, ProjectItemInstance>(propertyBag, FileSystems.Default);
977971
}
978972
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
979973
{
@@ -1044,10 +1038,35 @@ private void RegisterOverrideTasks(ILoggingService loggingServices, BuildEventCo
10441038
/// </summary>
10451039
private void LoadAndRegisterFromTasksFile(string[] defaultTaskFiles, ILoggingService loggingServices, BuildEventContext buildEventContext, string taskFileError, ProjectRootElementCacheBase projectRootElementCache, TaskRegistry registry)
10461040
{
1047-
foreach (string defaultTasksFile in defaultTaskFiles)
1041+
string currentTasksFile = null;
1042+
try
10481043
{
1049-
try
1044+
TaskRegistry.InitializeTaskRegistryFromUsingTaskElements<ProjectPropertyInstance, ProjectItemInstance>(
1045+
loggingServices,
1046+
buildEventContext,
1047+
EnumerateTasksRegistrations(),
1048+
registry,
1049+
_expander,
1050+
ExpanderOptions.ExpandProperties,
1051+
FileSystems.Default);
1052+
}
1053+
catch (XmlException e)
1054+
{
1055+
// handle XML errors in the default tasks file
1056+
ProjectFileErrorUtilities.ThrowInvalidProjectFile(new BuildEventFileInfo(currentTasksFile, e),
1057+
taskFileError, e.Message);
1058+
}
1059+
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
1060+
{
1061+
loggingServices.LogError(buildEventContext, new BuildEventFileInfo(currentTasksFile),
1062+
taskFileError, e.Message);
1063+
}
1064+
1065+
IEnumerable<(ProjectUsingTaskElement projectUsingTaskXml, string directoryOfImportingFile)> EnumerateTasksRegistrations()
1066+
{
1067+
foreach (string defaultTasksFile in defaultTaskFiles)
10501068
{
1069+
currentTasksFile = defaultTasksFile;
10511070
// Important to keep the following line since unit tests use the delegate.
10521071
ProjectRootElement projectRootElement;
10531072
if (_loadXmlFromPath != null)
@@ -1074,27 +1093,9 @@ private void LoadAndRegisterFromTasksFile(string[] defaultTaskFiles, ILoggingSer
10741093
elementXml.XmlElement.Name);
10751094
}
10761095

1077-
TaskRegistry.RegisterTasksFromUsingTaskElement<ProjectPropertyInstance, ProjectItemInstance>(
1078-
loggingServices,
1079-
buildEventContext,
1080-
Path.GetDirectoryName(defaultTasksFile),
1081-
usingTask,
1082-
registry,
1083-
_expander,
1084-
ExpanderOptions.ExpandProperties,
1085-
FileSystems.Default);
1096+
yield return (usingTask, Path.GetDirectoryName(defaultTasksFile));
10861097
}
10871098
}
1088-
catch (XmlException e)
1089-
{
1090-
// handle XML errors in the default tasks file
1091-
ProjectFileErrorUtilities.ThrowInvalidProjectFile(new BuildEventFileInfo(defaultTasksFile, e), taskFileError, e.Message);
1092-
}
1093-
catch (Exception e) when (ExceptionHandling.IsIoRelatedException(e))
1094-
{
1095-
loggingServices.LogError(buildEventContext, new BuildEventFileInfo(defaultTasksFile), taskFileError, e.Message);
1096-
break;
1097-
}
10981099
}
10991100
}
11001101
}

src/Build/Evaluation/Evaluator.cs

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -711,10 +711,15 @@ private void Evaluate()
711711
MSBuildEventSource.Log.EvaluatePass4Start(projectFile);
712712
using (_evaluationProfiler.TrackPass(EvaluationPass.UsingTasks))
713713
{
714-
foreach (var entry in _usingTaskElements)
715-
{
716-
EvaluateUsingTaskElement(entry.Key, entry.Value);
717-
}
714+
// Evaluate the usingtask and add the result into the data passed in
715+
TaskRegistry.InitializeTaskRegistryFromUsingTaskElements<P, I>(
716+
_evaluationLoggingContext.LoggingService,
717+
_evaluationLoggingContext.BuildEventContext,
718+
_usingTaskElements.Select(p => (p.Value, p.Key)),
719+
_data.TaskRegistry,
720+
_expander,
721+
ExpanderOptions.ExpandPropertiesAndItems,
722+
_evaluationContext.FileSystem);
718723
}
719724

720725
// If there was no DefaultTargets attribute found in the depth first pass,
@@ -1015,22 +1020,6 @@ private void EvaluateItemGroupElement(ProjectItemGroupElement itemGroupElement,
10151020
}
10161021
}
10171022

1018-
/// <summary>
1019-
/// Evaluate the usingtask and add the result into the data passed in
1020-
/// </summary>
1021-
private void EvaluateUsingTaskElement(string directoryOfImportingFile, ProjectUsingTaskElement projectUsingTaskElement)
1022-
{
1023-
TaskRegistry.RegisterTasksFromUsingTaskElement<P, I>(
1024-
_evaluationLoggingContext.LoggingService,
1025-
_evaluationLoggingContext.BuildEventContext,
1026-
directoryOfImportingFile,
1027-
projectUsingTaskElement,
1028-
_data.TaskRegistry,
1029-
_expander,
1030-
ExpanderOptions.ExpandPropertiesAndItems,
1031-
_evaluationContext.FileSystem);
1032-
}
1033-
10341023
/// <summary>
10351024
/// Retrieve the matching ProjectTargetInstance from the cache and add it to the provided collection.
10361025
/// If it is not cached already, read it and cache it.

src/Build/Instance/ProjectInstance.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,9 @@ internal ProjectInstance(Evaluation.Project.Data data, string directory, string
561561
this.CreateTargetsSnapshot(data.Targets, data.DefaultTargets, data.InitialTargets, data.BeforeTargets, data.AfterTargets);
562562
this.CreateImportsSnapshot(data.ImportClosure, data.ImportClosureWithDuplicates);
563563

564-
this.Toolset = data.Toolset; // UNDONE: This isn't immutable, should be cloned or made immutable; it currently has a pointer to project collection
564+
// Toolset and task registry are logically immutable after creation, and shareable by project instances
565+
// with same evaluation (global/local properties) - which is guaranteed here (the passed in data is recreated on evaluation if needed)
566+
this.Toolset = data.Toolset;
565567
this.SubToolsetVersion = data.SubToolsetVersion;
566568
this.TaskRegistry = data.TaskRegistry;
567569

@@ -641,10 +643,8 @@ private ProjectInstance(ProjectInstance that, bool isImmutable, RequestedProject
641643
ProjectItemDefinitionInstance>)this).AfterTargets = CreateCloneDictionary(
642644
((IEvaluatorData<ProjectPropertyInstance, ProjectItemInstance, ProjectMetadataInstance,
643645
ProjectItemDefinitionInstance>)that).AfterTargets, StringComparer.OrdinalIgnoreCase);
644-
this.TaskRegistry =
645-
that.TaskRegistry; // UNDONE: This isn't immutable, should be cloned or made immutable; it currently has a pointer to project collection
646-
647-
// These are immutable so we don't need to clone them:
646+
// These are immutable (or logically immutable after creation) so we don't need to clone them:
647+
this.TaskRegistry = that.TaskRegistry;
648648
this.Toolset = that.Toolset;
649649
this.SubToolsetVersion = that.SubToolsetVersion;
650650
_targets = that._targets;

0 commit comments

Comments
 (0)