Skip to content

Commit 29a2f07

Browse files
authored
Merge pull request #8974 from JanKrivanek/proto/TaskRegistryOrdering
Make TaskRegistry tasks ordering deterministic (FIFO)
2 parents 4989625 + 79152b8 commit 29a2f07

File tree

2 files changed

+57
-51
lines changed

2 files changed

+57
-51
lines changed

src/Build.UnitTests/TestComparers/TaskRegistryComparers.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ internal sealed class TaskRegistryComparer : IEqualityComparer<TaskRegistry>
2020
public bool Equals(TaskRegistry x, TaskRegistry y)
2121
{
2222
Assert.Equal(x.Toolset, y.Toolset, new ToolsetComparer());
23+
Assert.Equal(x.NextRegistrationOrderId, y.NextRegistrationOrderId);
2324

2425
Helpers.AssertDictionariesEqual(
2526
x.TaskRegistrations,

src/Build/Instance/TaskRegistry.cs

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
using System.Collections.ObjectModel;
77
using System.Diagnostics;
88
using System.IO;
9+
using System.Linq;
910
using System.Reflection;
11+
using System.Threading;
1012
using Microsoft.Build.BackEnd;
1113
using Microsoft.Build.Collections;
1214
using Microsoft.Build.Construction;
@@ -122,6 +124,11 @@ internal sealed class TaskRegistry : ITranslatable
122124
/// </summary>
123125
private static string s_potentialTasksCoreLocation = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksCoreFilename);
124126

127+
/// <summary>
128+
/// Monotonically increasing counter for registered tasks.
129+
/// </summary>
130+
private int _nextRegistrationOrderId = 0;
131+
125132
/// <summary>
126133
/// Cache of tasks already found using exact matching,
127134
/// keyed by the task identity requested.
@@ -192,6 +199,12 @@ internal Toolset Toolset
192199
{ return _toolset; }
193200
}
194201

202+
/// <summary>
203+
/// Access the next registration sequence id.
204+
/// FOR UNIT TESTING ONLY.
205+
/// </summary>
206+
internal int NextRegistrationOrderId => _nextRegistrationOrderId;
207+
195208
/// <summary>
196209
/// Access list of task registrations.
197210
/// FOR UNIT TESTING ONLY.
@@ -525,23 +538,11 @@ internal RegisteredTaskRecord GetTaskRegistrationRecord(
525538
}
526539
}
527540

528-
Dictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>> registrations = GetRelevantRegistrations(taskIdentity, exactMatchRequired);
541+
IEnumerable<RegisteredTaskRecord> registrations = GetRelevantOrderedRegistrations(taskIdentity, exactMatchRequired);
529542

530543
// look for the given task name in the registry; if not found, gather all registered task names that partially
531544
// match the given name
532-
foreach (KeyValuePair<RegisteredTaskIdentity, List<RegisteredTaskRecord>> registration in registrations)
533-
{
534-
// if the given task name is longer than the registered task name
535-
// we will use the longer name to help disambiguate between multiple matches
536-
string mostSpecificTaskName = (taskName.Length > registration.Key.Name.Length) ? taskName : registration.Key.Name;
537-
538-
taskRecord = GetMatchingRegistration(mostSpecificTaskName, registration.Value, taskProjectFile, taskIdentityParameters, targetLoggingContext, elementLocation);
539-
540-
if (taskRecord != null)
541-
{
542-
break;
543-
}
544-
}
545+
taskRecord = GetMatchingRegistration(taskName, registrations, taskProjectFile, taskIdentityParameters, targetLoggingContext, elementLocation);
545546
}
546547

547548
// If we didn't find the task but we have a fallback registry in the toolset state, try that one.
@@ -603,36 +604,24 @@ private static bool IsTaskFactoryClass(Type type, object unused)
603604
/// If no exact match is found, looks for partial matches.
604605
/// A task name that is not fully qualified may produce several partial matches.
605606
/// </summary>
606-
private Dictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>> GetRelevantRegistrations(RegisteredTaskIdentity taskIdentity, bool exactMatchRequired)
607+
private IEnumerable<RegisteredTaskRecord> GetRelevantOrderedRegistrations(RegisteredTaskIdentity taskIdentity, bool exactMatchRequired)
607608
{
608-
Dictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>> relevantTaskRegistrations =
609-
new Dictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>>(RegisteredTaskIdentity.RegisteredTaskIdentityComparer.Exact);
610-
611-
List<RegisteredTaskRecord> taskAssemblies;
612-
613-
// if we find an exact match
614-
if (_taskRegistrations.TryGetValue(taskIdentity, out taskAssemblies))
609+
if (_taskRegistrations.TryGetValue(taskIdentity, out List<RegisteredTaskRecord> taskAssemblies))
615610
{
616-
// we're done
617-
relevantTaskRegistrations[taskIdentity] = taskAssemblies;
618-
return relevantTaskRegistrations;
611+
// (records for single key should be ordered by order of registrations - as they are inserted into the list)
612+
return taskAssemblies;
619613
}
620614

621615
if (exactMatchRequired)
622616
{
623-
return relevantTaskRegistrations;
617+
return Enumerable.Empty<RegisteredTaskRecord>();
624618
}
625619

626620
// look through all task declarations for partial matches
627-
foreach (KeyValuePair<RegisteredTaskIdentity, List<RegisteredTaskRecord>> taskRegistration in _taskRegistrations)
628-
{
629-
if (RegisteredTaskIdentity.RegisteredTaskIdentityComparer.IsPartialMatch(taskIdentity, taskRegistration.Key))
630-
{
631-
relevantTaskRegistrations[taskRegistration.Key] = taskRegistration.Value;
632-
}
633-
}
634-
635-
return relevantTaskRegistrations;
621+
return _taskRegistrations
622+
.Where(tp => RegisteredTaskIdentity.RegisteredTaskIdentityComparer.IsPartialMatch(taskIdentity, tp.Key))
623+
.SelectMany(tp => tp.Value)
624+
.OrderBy(r => r.RegistrationOrderId);
636625
}
637626

638627
// Create another set containing architecture-specific task entries.
@@ -673,7 +662,13 @@ private void RegisterTask(
673662
_taskRegistrations[taskIdentity] = registeredTaskEntries;
674663
}
675664

676-
RegisteredTaskRecord newRecord = new RegisteredTaskRecord(taskName, assemblyLoadInfo, taskFactory, taskFactoryParameters, inlineTaskRecord);
665+
RegisteredTaskRecord newRecord = new RegisteredTaskRecord(
666+
taskName,
667+
assemblyLoadInfo,
668+
taskFactory,
669+
taskFactoryParameters,
670+
inlineTaskRecord,
671+
Interlocked.Increment(ref _nextRegistrationOrderId));
677672

678673
if (overrideTask)
679674
{
@@ -721,23 +716,21 @@ private static Dictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>> Cr
721716
/// </summary>
722717
private RegisteredTaskRecord GetMatchingRegistration(
723718
string taskName,
724-
List<RegisteredTaskRecord> taskRecords,
719+
IEnumerable<RegisteredTaskRecord> taskRecords,
725720
string taskProjectFile,
726721
IDictionary<string, string> taskIdentityParameters,
727722
TargetLoggingContext targetLoggingContext,
728723
ElementLocation elementLocation)
729-
{
730-
foreach (RegisteredTaskRecord record in taskRecords)
731-
{
732-
if (record.CanTaskBeCreatedByFactory(taskName, taskProjectFile, taskIdentityParameters, targetLoggingContext, elementLocation))
733-
{
734-
return record;
735-
}
736-
}
737-
738-
// Cannot find the task in any of the records
739-
return null;
740-
}
724+
=>
725+
taskRecords.FirstOrDefault(r =>
726+
r.CanTaskBeCreatedByFactory(
727+
// if the given task name is longer than the registered task name
728+
// we will use the longer name to help disambiguate between multiple matches
729+
(taskName.Length > r.TaskIdentity.Name.Length) ? taskName : r.TaskIdentity.Name,
730+
taskProjectFile,
731+
taskIdentityParameters,
732+
targetLoggingContext,
733+
elementLocation));
741734

742735
/// <summary>
743736
/// An object representing the identity of a task -- not just task name, but also
@@ -1115,17 +1108,23 @@ internal class RegisteredTaskRecord : ITranslatable
11151108
/// </summary>
11161109
private ParameterGroupAndTaskElementRecord _parameterGroupAndTaskBody;
11171110

1111+
/// <summary>
1112+
/// The registration order id for this task. This is used to determine the order in which tasks are registered.
1113+
/// </summary>
1114+
private int _registrationOrderId;
1115+
11181116
/// <summary>
11191117
/// Constructor
11201118
/// </summary>
1121-
internal RegisteredTaskRecord(string registeredName, AssemblyLoadInfo assemblyLoadInfo, string taskFactory, Dictionary<string, string> taskFactoryParameters, ParameterGroupAndTaskElementRecord inlineTask)
1119+
internal RegisteredTaskRecord(string registeredName, AssemblyLoadInfo assemblyLoadInfo, string taskFactory, Dictionary<string, string> taskFactoryParameters, ParameterGroupAndTaskElementRecord inlineTask, int registrationOrderId)
11221120
{
11231121
ErrorUtilities.VerifyThrowArgumentNull(assemblyLoadInfo, "AssemblyLoadInfo");
11241122
_registeredName = registeredName;
11251123
_taskFactoryAssemblyLoadInfo = assemblyLoadInfo;
11261124
_taskFactoryParameters = taskFactoryParameters;
11271125
_taskIdentity = new RegisteredTaskIdentity(registeredName, taskFactoryParameters);
11281126
_parameterGroupAndTaskBody = inlineTask;
1127+
_registrationOrderId = registrationOrderId;
11291128

11301129
if (String.IsNullOrEmpty(taskFactory))
11311130
{
@@ -1206,6 +1205,11 @@ internal ParameterGroupAndTaskElementRecord ParameterGroupAndTaskBody
12061205
/// </summary>
12071206
internal RegisteredTaskIdentity TaskIdentity => _taskIdentity;
12081207

1208+
/// <summary>
1209+
/// The registration order id for this task. This is used to determine the order in which tasks are registered.
1210+
/// </summary>
1211+
internal int RegistrationOrderId => _registrationOrderId;
1212+
12091213
/// <summary>
12101214
/// Ask the question, whether or not the task name can be created by the task factory.
12111215
/// To answer this question we need to instantiate and initialize the task factory and ask it if it can create the given task name.
@@ -1765,6 +1769,7 @@ public void Translate(ITranslator translator)
17651769
translator.Translate(ref _taskFactoryAssemblyLoadInfo, AssemblyLoadInfo.FactoryForTranslation);
17661770
translator.Translate(ref _taskFactory);
17671771
translator.Translate(ref _parameterGroupAndTaskBody);
1772+
translator.Translate(ref _registrationOrderId);
17681773

17691774
IDictionary<string, string> localParameters = _taskFactoryParameters;
17701775
translator.TranslateDictionary(ref localParameters, count => CreateTaskFactoryParametersDictionary(count));
@@ -1787,7 +1792,7 @@ internal static RegisteredTaskRecord FactoryForDeserialization(ITranslator trans
17871792
public void Translate(ITranslator translator)
17881793
{
17891794
translator.Translate(ref _toolset, Toolset.FactoryForDeserialization);
1790-
1795+
translator.Translate(ref _nextRegistrationOrderId);
17911796
IDictionary<RegisteredTaskIdentity, List<RegisteredTaskRecord>> copy = _taskRegistrations;
17921797
translator.TranslateDictionary(ref copy, TranslateTaskRegistrationKey, TranslateTaskRegistrationValue, count => CreateRegisteredTaskDictionary(count));
17931798

0 commit comments

Comments
 (0)