-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
TaskBuilder.cs
1217 lines (1058 loc) · 59.5 KB
/
TaskBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// 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;
#if FEATURE_APARTMENT_STATE
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Reflection;
#if FEATURE_APARTMENT_STATE
using System.Runtime.ExceptionServices;
#endif
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.BackEnd.Components.RequestBuilder;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Eventing;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Shared.FileSystem;
using ElementLocation = Microsoft.Build.Construction.ElementLocation;
using ProjectItemInstanceFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.ProjectItemInstanceFactory;
using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext;
using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext;
#nullable disable
namespace Microsoft.Build.BackEnd
{
/// <summary>
/// The possible values for a task's ContinueOnError attribute.
/// </summary>
internal enum ContinueOnError
{
/// <summary>
/// If the task fails, error and stop.
/// </summary>
ErrorAndStop,
/// <summary>
/// If the task fails, error and continue.
/// </summary>
ErrorAndContinue,
/// <summary>
/// If the task fails, warn and continue.
/// </summary>
WarnAndContinue
}
/// <summary>
/// The TaskBuilder is one of two components related to building tasks, the other being the TaskExecutionHost. The TaskBuilder is
/// responsible for all parts dealing with the XML/task declaration. It determines if the task is intrinsic or extrinsic,
/// looks up the task in the task registry, determines the task parameters and requests them to be set, and requests outputs
/// when task execution has been completed. It is not responsible for reflection over the task instance or anything which
/// requires dealing with the task instance directly - those actions are handled by the TaskExecutionHost.
/// </summary>
internal class TaskBuilder : ITaskBuilder, IBuildComponent
{
/// <summary>
/// The Build Request Entry for which this task is executing.
/// </summary>
private BuildRequestEntry _buildRequestEntry;
/// <summary>
/// The cancellation token
/// </summary>
private CancellationToken _cancellationToken;
/// <summary>
/// The build component host.
/// </summary>
private IBuildComponentHost _componentHost;
/// <summary>
/// The original target child instance
/// </summary>
private ProjectTargetInstanceChild _targetChildInstance;
/// <summary>
/// The task instance for extrinsic tasks
/// </summary>
private ProjectTaskInstance _taskNode;
/// <summary>
/// Host callback for host-aware tasks.
/// </summary>
private ITaskHost _taskHostObject;
/// <summary>
/// indicates whether to ignore task execution failures
/// </summary>
private ContinueOnError _continueOnError;
/// <summary>
/// The logging context for the target in which we are executing.
/// </summary>
private TargetLoggingContext _targetLoggingContext;
/// <summary>
/// Full path to the project, for errors
/// </summary>
private string _projectFullPath;
/// <summary>
/// The target builder callback.
/// </summary>
private ITargetBuilderCallback _targetBuilderCallback;
/// <summary>
/// The task execution host for in-proc tasks.
/// </summary>
private TaskExecutionHost _taskExecutionHost;
/// <summary>
/// The object used to synchronize access to the task execution host.
/// </summary>
private Object _taskExecutionHostSync = new Object();
/// <summary>
/// Constructor
/// </summary>
internal TaskBuilder()
{
}
/// <summary>
/// Builds the task specified by the XML.
/// </summary>
/// <param name="loggingContext">The logging context of the target</param>
/// <param name="requestEntry">The build request entry being built</param>
/// <param name="targetBuilderCallback">The target builder callback.</param>
/// <param name="taskInstance">The task instance.</param>
/// <param name="mode">The mode in which to execute tasks.</param>
/// <param name="inferLookup">The lookup to be used for inference.</param>
/// <param name="executeLookup">The lookup to be used during execution.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use when executing the task.</param>
/// <returns>The result of running the task batch.</returns>
/// <remarks>
/// The ExecuteTask method takes a task as specified by XML and executes it. This procedure is comprised
/// of the following steps:
/// 1. Loading the Task from its containing assembly by looking it up in the task registry
/// 2. Determining if the task is batched. If it is, create the batches and execute each as if it were a non-batched task
/// 3. If the task is not batched, execute it.
/// 4. If the task was batched, hold on to its Lookup until all of the natches are done, then merge them.
/// </remarks>
public async Task<WorkUnitResult> ExecuteTask(TargetLoggingContext loggingContext, BuildRequestEntry requestEntry, ITargetBuilderCallback targetBuilderCallback, ProjectTargetInstanceChild taskInstance, TaskExecutionMode mode, Lookup inferLookup, Lookup executeLookup, CancellationToken cancellationToken)
{
ErrorUtilities.VerifyThrow(taskInstance != null, "Need to specify the task instance.");
_buildRequestEntry = requestEntry;
_targetBuilderCallback = targetBuilderCallback;
_cancellationToken = cancellationToken;
_targetChildInstance = taskInstance;
// In the case of Intrinsic tasks, taskNode will end up null. Currently this is how we distinguish
// intrinsic from extrinsic tasks.
_taskNode = taskInstance as ProjectTaskInstance;
if (_taskNode != null && requestEntry.Request.HostServices != null)
{
_taskHostObject = requestEntry.Request.HostServices.GetHostObject(requestEntry.RequestConfiguration.Project.FullPath, loggingContext.Target.Name, _taskNode.Name);
}
_projectFullPath = requestEntry.RequestConfiguration.Project.FullPath;
// this.handleId = handleId; No handles
// this.parentModule = parentModule; No task execution module
_continueOnError = ContinueOnError.ErrorAndStop;
_targetLoggingContext = loggingContext;
WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
if ((mode & TaskExecutionMode.InferOutputsOnly) == TaskExecutionMode.InferOutputsOnly)
{
taskResult = await ExecuteTask(TaskExecutionMode.InferOutputsOnly, inferLookup);
}
if ((mode & TaskExecutionMode.ExecuteTaskAndGatherOutputs) == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
{
taskResult = await ExecuteTask(TaskExecutionMode.ExecuteTaskAndGatherOutputs, executeLookup);
}
return taskResult;
}
#region IBuildComponent Members
/// <summary>
/// Sets the build component host.
/// </summary>
/// <param name="host">The component host.</param>
public void InitializeComponent(IBuildComponentHost host)
{
_componentHost = host;
_taskExecutionHost = new TaskExecutionHost(host);
}
/// <summary>
/// Shuts down the component.
/// </summary>
public void ShutdownComponent()
{
lock (_taskExecutionHostSync)
{
ErrorUtilities.VerifyThrow(_taskExecutionHost != null, "taskExecutionHost not initialized.");
_componentHost = null;
IDisposable disposable = _taskExecutionHost as IDisposable;
disposable?.Dispose();
_taskExecutionHost = null;
}
}
#endregion
/// <summary>
/// Class factory for component creation.
/// </summary>
internal static IBuildComponent CreateComponent(BuildComponentType type)
{
ErrorUtilities.VerifyThrow(type == BuildComponentType.TaskBuilder, "Cannot create components of type {0}", type);
return new TaskBuilder();
}
#region Methods
/// <summary>
/// Build up a list of all parameters on the task, including those in any Output tags,
/// in order to find batchable metadata references
/// </summary>
/// <returns>The list of parameter values</returns>
private List<string> CreateListOfParameterValues()
{
if (_taskNode == null)
{
// This is an intrinsic task. Batching is not handled here.
return new List<string>();
}
List<string> taskParameters = new List<string>(_taskNode.ParametersForBuild.Count + _taskNode.Outputs.Count);
foreach (KeyValuePair<string, (string, ElementLocation)> taskParameter in _taskNode.ParametersForBuild)
{
taskParameters.Add(taskParameter.Value.Item1);
}
// Add parameters on any output tags
foreach (ProjectTaskInstanceChild taskOutputSpecification in _taskNode.Outputs)
{
ProjectTaskOutputItemInstance outputItemInstance = taskOutputSpecification as ProjectTaskOutputItemInstance;
if (outputItemInstance != null)
{
taskParameters.Add(outputItemInstance.TaskParameter);
taskParameters.Add(outputItemInstance.ItemType);
}
ProjectTaskOutputPropertyInstance outputPropertyInstance = taskOutputSpecification as ProjectTaskOutputPropertyInstance;
if (outputPropertyInstance != null)
{
taskParameters.Add(outputPropertyInstance.TaskParameter);
taskParameters.Add(outputPropertyInstance.PropertyName);
}
if (!String.IsNullOrEmpty(taskOutputSpecification.Condition))
{
taskParameters.Add(taskOutputSpecification.Condition);
}
}
if (!String.IsNullOrEmpty(_taskNode.Condition))
{
taskParameters.Add(_taskNode.Condition);
}
if (!String.IsNullOrEmpty(_taskNode.ContinueOnError))
{
taskParameters.Add(_taskNode.ContinueOnError);
}
return taskParameters;
}
/// <summary>
/// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it.
/// </summary>
/// <returns>true, if successful</returns>
private async Task<WorkUnitResult> ExecuteTask(TaskExecutionMode mode, Lookup lookup)
{
ErrorUtilities.VerifyThrowArgumentNull(lookup, nameof(lookup));
WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
TaskHost taskHost = null;
List<ItemBucket> buckets = null;
try
{
if (_taskNode != null)
{
taskHost = new TaskHost(_componentHost, _buildRequestEntry, _targetChildInstance.Location, _targetBuilderCallback);
_taskExecutionHost.InitializeForTask(taskHost, _targetLoggingContext, _buildRequestEntry.RequestConfiguration.Project, _taskNode.Name, _taskNode.Location, _taskHostObject, _continueOnError != ContinueOnError.ErrorAndStop,
#if FEATURE_APPDOMAIN
taskHost.AppDomainSetup,
#endif
taskHost.IsOutOfProc, _cancellationToken);
}
List<string> taskParameterValues = CreateListOfParameterValues();
buckets = BatchingEngine.PrepareBatchingBuckets(taskParameterValues, lookup, _targetChildInstance.Location, _targetLoggingContext);
Dictionary<string, string> lookupHash = null;
// Only create a hash table if there are more than one bucket as this is the only time a property can be overridden
if (buckets.Count > 1)
{
lookupHash ??= new Dictionary<string, string>(MSBuildNameIgnoreCaseComparer.Default);
}
WorkUnitResult aggregateResult = new WorkUnitResult();
// Loop through each of the batch buckets and execute them one at a time
for (int i = 0; i < buckets.Count; i++)
{
// Execute the batch bucket, pass in which bucket we are executing so that we know when to get a new taskId for the bucket.
taskResult = await ExecuteBucket(taskHost, (ItemBucket)buckets[i], mode, lookupHash);
aggregateResult = aggregateResult.AggregateResult(taskResult);
if (aggregateResult.ActionCode == WorkUnitActionCode.Stop)
{
break;
}
}
taskResult = aggregateResult;
}
finally
{
_taskExecutionHost.CleanupForTask();
taskHost?.MarkAsInactive();
// Now all task batches are done, apply all item adds to the outer
// target batch; we do this even if the task wasn't found (in that case,
// no items or properties will have been added to the scope)
if (buckets != null)
{
foreach (ItemBucket bucket in buckets)
{
bucket.LeaveScope();
}
}
}
return taskResult;
}
/// <summary>
/// Execute a single bucket
/// </summary>
/// <returns>true if execution succeeded</returns>
private async Task<WorkUnitResult> ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary<string, string> lookupHash)
{
// On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded.
ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll;
WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
bool condition = ConditionEvaluator.EvaluateCondition(
_targetChildInstance.Condition,
parserOptions,
bucket.Expander,
ExpanderOptions.ExpandAll,
_buildRequestEntry.ProjectRootDirectory,
_targetChildInstance.ConditionLocation,
FileSystems.Default,
loggingContext: _targetLoggingContext);
if (!condition)
{
LogSkippedTask(bucket, howToExecuteTask);
return new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null);
}
// Some tests do not provide an actual taskNode; checking if _taskNode == null prevents those tests from failing.
// If this is an Intrinsic task, it gets handled in a special fashion.
if (_taskNode == null)
{
try
{
ExecuteIntrinsicTask(bucket);
taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null);
}
catch (InvalidProjectFileException e)
{
// Make sure the Invalid Project error gets logged *before* TaskFinished. Otherwise,
// the log is confusing.
_targetLoggingContext.LogInvalidProjectFileError(e);
_continueOnError = ContinueOnError.ErrorAndStop;
taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, e);
}
}
else
{
if (_componentHost.BuildParameters.SaveOperatingEnvironment)
{
// Change to the project root directory.
// If that directory does not exist, do nothing. (Do not check first as it is almost always there and it is slow)
// This is because if the project has not been saved, this directory may not exist, yet it is often useful to still be able to build the project.
// No errors are masked by doing this: errors loading the project from disk are reported at load time, if necessary.
NativeMethodsShared.SetCurrentDirectory(_buildRequestEntry.ProjectRootDirectory);
}
if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
{
// We need to find the task before logging the task started event so that the using task statement comes before the task started event
IDictionary<string, string> taskIdentityParameters = GatherTaskIdentityParameters(bucket.Expander);
(TaskRequirements? requirements, TaskFactoryWrapper taskFactoryWrapper) = _taskExecutionHost.FindTask(taskIdentityParameters);
string taskAssemblyLocation = taskFactoryWrapper?.TaskFactoryLoadedType?.Path;
if (requirements != null)
{
TaskLoggingContext taskLoggingContext = _targetLoggingContext.LogTaskBatchStarted(_projectFullPath, _targetChildInstance, taskAssemblyLocation);
MSBuildEventSource.Log.ExecuteTaskStart(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId);
_buildRequestEntry.Request.CurrentTaskContext = taskLoggingContext.BuildEventContext;
try
{
if (
(requirements.Value & TaskRequirements.RequireSTAThread) == TaskRequirements.RequireSTAThread
#if FEATURE_APARTMENT_STATE
&& (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
#endif
)
{
#if FEATURE_APARTMENT_STATE
taskResult = ExecuteTaskInSTAThread(bucket, taskLoggingContext, taskIdentityParameters, taskHost, howToExecuteTask);
#else
throw new PlatformNotSupportedException(TaskRequirements.RequireSTAThread.ToString());
#endif
}
else
{
taskResult = await InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask);
}
if (lookupHash != null)
{
List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash);
if (overrideMessages != null)
{
foreach (string s in overrideMessages)
{
taskLoggingContext.LogCommentFromText(MessageImportance.Low, s);
}
}
}
}
catch (InvalidProjectFileException e)
{
// Make sure the Invalid Project error gets logged *before* TaskFinished. Otherwise,
// the log is confusing.
taskLoggingContext.LogInvalidProjectFileError(e);
_continueOnError = ContinueOnError.ErrorAndStop;
taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, e);
}
finally
{
_buildRequestEntry.Request.CurrentTaskContext = null;
// Flag the completion of the task.
taskLoggingContext.LogTaskBatchFinished(_projectFullPath, taskResult.ResultCode == WorkUnitResultCode.Success || taskResult.ResultCode == WorkUnitResultCode.Skipped);
if (taskResult.ResultCode == WorkUnitResultCode.Failed && _continueOnError == ContinueOnError.WarnAndContinue)
{
// We coerce the failing result to a successful result.
taskResult = new WorkUnitResult(WorkUnitResultCode.Success, taskResult.ActionCode, taskResult.Exception);
}
MSBuildEventSource.Log.ExecuteTaskStop(_taskNode?.Name, taskLoggingContext.BuildEventContext.TaskId);
}
}
}
else
{
ErrorUtilities.VerifyThrow(howToExecuteTask == TaskExecutionMode.InferOutputsOnly, "should be inferring");
ErrorUtilities.VerifyThrow(
GatherTaskOutputs(null, howToExecuteTask, bucket),
"The method GatherTaskOutputs() should never fail when inferring task outputs.");
if (lookupHash != null)
{
List<string> overrideMessages = bucket.Lookup.GetPropertyOverrideMessages(lookupHash);
if (overrideMessages != null)
{
foreach (string s in overrideMessages)
{
_targetLoggingContext.LogCommentFromText(MessageImportance.Low, s);
}
}
}
taskResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null);
}
}
return taskResult;
}
/// <summary>
/// Returns the set of parameters that can contribute to a task's identity, and their values for this particular task.
/// </summary>
private IDictionary<string, string> GatherTaskIdentityParameters(Expander<ProjectPropertyInstance, ProjectItemInstance> expander)
{
ErrorUtilities.VerifyThrowInternalNull(_taskNode, "taskNode"); // taskNode should never be null when we're calling this method.
string msbuildArchitecture = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildArchitecture ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildArchitectureLocation ?? ElementLocation.EmptyLocation);
string msbuildRuntime = expander.ExpandIntoStringAndUnescape(_taskNode.MSBuildRuntime ?? String.Empty, ExpanderOptions.ExpandAll, _taskNode.MSBuildRuntimeLocation ?? ElementLocation.EmptyLocation);
IDictionary<string, string> taskIdentityParameters = null;
// only bother to create a task identity parameter set if we're putting anything in there -- otherwise,
// a null set will be treated as equivalent to all parameters being "don't care".
if (msbuildRuntime != String.Empty || msbuildArchitecture != String.Empty)
{
taskIdentityParameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
msbuildArchitecture = msbuildArchitecture == String.Empty ? XMakeAttributes.MSBuildArchitectureValues.any : msbuildArchitecture.Trim();
msbuildRuntime = msbuildRuntime == String.Empty ? XMakeAttributes.MSBuildRuntimeValues.any : msbuildRuntime.Trim();
taskIdentityParameters.Add(XMakeAttributes.runtime, msbuildRuntime);
taskIdentityParameters.Add(XMakeAttributes.architecture, msbuildArchitecture);
}
return taskIdentityParameters;
}
#if FEATURE_APARTMENT_STATE
/// <summary>
/// Executes the task using an STA thread.
/// </summary>
/// <comment>
/// STA thread launching also being used in XMakeCommandLine\OutOfProcTaskAppDomainWrapperBase.cs, InstantiateAndExecuteTaskInSTAThread method.
/// Any bug fixes made to this code, please ensure that you also fix that code.
/// </comment>
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception is caught and rethrown in the correct thread.")]
private WorkUnitResult ExecuteTaskInSTAThread(ItemBucket bucket, TaskLoggingContext taskLoggingContext, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask)
{
WorkUnitResult taskResult = new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null);
Thread staThread = null;
ExceptionDispatchInfo exceptionFromExecution = null;
ManualResetEvent taskRunnerFinished = new ManualResetEvent(false);
try
{
ThreadStart taskRunnerDelegate = delegate ()
{
Lookup.Scope scope = bucket.Lookup.EnterScope("STA Thread for Task");
try
{
taskResult = InitializeAndExecuteTask(taskLoggingContext, bucket, taskIdentityParameters, taskHost, howToExecuteTask).Result;
}
catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
{
exceptionFromExecution = ExceptionDispatchInfo.Capture(e);
}
finally
{
scope.LeaveScope();
taskRunnerFinished.Set();
}
};
staThread = new Thread(taskRunnerDelegate);
staThread.SetApartmentState(ApartmentState.STA);
staThread.Name = "MSBuild STA task runner thread";
staThread.CurrentCulture = _componentHost.BuildParameters.Culture;
staThread.CurrentUICulture = _componentHost.BuildParameters.UICulture;
staThread.Start();
// TODO: Why not just Join on the thread???
taskRunnerFinished.WaitOne();
}
finally
{
taskRunnerFinished.Close();
taskRunnerFinished = null;
}
exceptionFromExecution?.Throw();
return taskResult;
}
#endif
/// <summary>
/// Logs a task skipped message if necessary.
/// </summary>
private void LogSkippedTask(ItemBucket bucket, TaskExecutionMode howToExecuteTask)
{
// If this is an Intrinsic task, it does not log skips.
if (_taskNode != null)
{
if (howToExecuteTask == TaskExecutionMode.ExecuteTaskAndGatherOutputs)
{
if (!_targetLoggingContext.LoggingService.OnlyLogCriticalEvents)
{
// Expand the expression for the Log. Since we know the condition evaluated to false, leave unexpandable properties in the condition so as not to cause an error
string expanded = bucket.Expander.ExpandIntoStringAndUnescape(_targetChildInstance.Condition, ExpanderOptions.ExpandAll | ExpanderOptions.LeavePropertiesUnexpandedOnError | ExpanderOptions.Truncate, _targetChildInstance.ConditionLocation);
// Whilst we are within the processing of the task, we haven't actually started executing it, so
// our skip task message needs to be in the context of the target. However any errors should be reported
// at the point where the task appears in the project.
_targetLoggingContext.LogComment(
MessageImportance.Low,
"TaskSkippedFalseCondition",
_taskNode.Name,
_targetChildInstance.Condition,
expanded);
}
}
}
}
/// <summary>
/// Runs an intrinsic task.
/// </summary>
private void ExecuteIntrinsicTask(ItemBucket bucket)
{
IntrinsicTask task = IntrinsicTask.InstantiateTask(
_targetChildInstance,
_targetLoggingContext,
_buildRequestEntry.RequestConfiguration.Project,
_taskExecutionHost.LogTaskInputs);
task.ExecuteTask(bucket.Lookup);
}
/// <summary>
/// Initializes and executes the task.
/// </summary>
private async Task<WorkUnitResult> InitializeAndExecuteTask(TaskLoggingContext taskLoggingContext, ItemBucket bucket, IDictionary<string, string> taskIdentityParameters, TaskHost taskHost, TaskExecutionMode howToExecuteTask)
{
if (!_taskExecutionHost.InitializeForBatch(taskLoggingContext, bucket, taskIdentityParameters))
{
ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskDeclarationOrUsageError", _taskNode.Name);
}
using var assemblyLoadsTracker = AssemblyLoadsTracker.StartTracking(taskLoggingContext, AssemblyLoadingContext.TaskRun, _taskExecutionHost?.TaskInstance?.GetType());
try
{
// UNDONE: Move this and the task host.
taskHost.LoggingContext = taskLoggingContext;
WorkUnitResult executionResult = await ExecuteInstantiatedTask(_taskExecutionHost, taskLoggingContext, taskHost, bucket, howToExecuteTask);
ErrorUtilities.VerifyThrow(executionResult != null, "Unexpected null execution result");
return executionResult;
}
finally
{
_taskExecutionHost.CleanupForBatch();
}
}
/// <summary>
/// Recomputes the task's "ContinueOnError" setting.
/// </summary>
/// <param name="bucket">The bucket being executed.</param>
/// <param name="taskHost">The task host to use.</param>
/// <remarks>
/// There are four possible values:
/// false - Error and stop if the task fails.
/// true - Warn and continue if the task fails.
/// ErrorAndContinue - Error and continue if the task fails.
/// WarnAndContinue - Same as true.
/// </remarks>
private void UpdateContinueOnError(ItemBucket bucket, TaskHost taskHost)
{
string continueOnErrorAttribute = _taskNode.ContinueOnError;
_continueOnError = ContinueOnError.ErrorAndStop;
if (_taskNode.ContinueOnErrorLocation != null)
{
string expandedValue = bucket.Expander.ExpandIntoStringAndUnescape(continueOnErrorAttribute, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata
try
{
if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase))
{
_continueOnError = ContinueOnError.ErrorAndContinue;
}
else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.warnAndContinue, expandedValue, StringComparison.OrdinalIgnoreCase))
{
_continueOnError = ContinueOnError.WarnAndContinue;
}
else if (String.Equals(XMakeAttributes.ContinueOnErrorValues.errorAndStop, expandedValue, StringComparison.OrdinalIgnoreCase))
{
_continueOnError = ContinueOnError.ErrorAndStop;
}
else
{
// if attribute doesn't exist, default to "false"
// otherwise, convert its value to a boolean
bool value = ConversionUtilities.ConvertStringToBool(expandedValue);
_continueOnError = value ? ContinueOnError.WarnAndContinue : ContinueOnError.ErrorAndStop;
}
}
catch (ArgumentException e)
{
// handle errors in string-->bool conversion
ProjectErrorUtilities.ThrowInvalidProject(_taskNode.ContinueOnErrorLocation, "InvalidContinueOnErrorAttribute", _taskNode.Name, e.Message);
}
}
// We need to access an internal method of the EngineProxy in order to update the value
// of continueOnError that will be returned to the task when the task queries IBuildEngine for it
taskHost.ContinueOnError = (_continueOnError != ContinueOnError.ErrorAndStop);
taskHost.ConvertErrorsToWarnings = (_continueOnError == ContinueOnError.WarnAndContinue);
}
/// <summary>
/// Execute a task object for a given bucket.
/// </summary>
/// <param name="taskExecutionHost">The host used to execute the task.</param>
/// <param name="taskLoggingContext">The logging context.</param>
/// <param name="taskHost">The task host for the task.</param>
/// <param name="bucket">The batching bucket</param>
/// <param name="howToExecuteTask">The task execution mode</param>
/// <returns>The result of running the task.</returns>
private async Task<WorkUnitResult> ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask)
{
UpdateContinueOnError(bucket, taskHost);
bool taskResult = false;
WorkUnitResultCode resultCode = WorkUnitResultCode.Success;
WorkUnitActionCode actionCode = WorkUnitActionCode.Continue;
if (!taskExecutionHost.SetTaskParameters(_taskNode.ParametersForBuild))
{
// The task cannot be initialized.
ProjectErrorUtilities.ThrowInvalidProject(_targetChildInstance.Location, "TaskParametersError", _taskNode.Name, String.Empty);
}
else
{
bool taskReturned = false;
Exception taskException = null;
// If this is the MSBuild task, we need to execute it's special internal method.
try
{
if (taskExecutionHost.TaskInstance is MSBuild msbuildTask)
{
var undeclaredProjects = GetUndeclaredProjects(msbuildTask);
if (undeclaredProjects?.Count > 0)
{
_continueOnError = ContinueOnError.ErrorAndStop;
taskException = new InvalidProjectFileException(
taskHost.ProjectFileOfTaskNode,
taskHost.LineNumberOfTaskNode,
taskHost.ColumnNumberOfTaskNode,
0,
0,
ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword(
"UndeclaredMSBuildTasksNotAllowedInIsolatedGraphBuilds",
string.Join(";", undeclaredProjects.Select(p => $"\"{p}\"")),
taskExecutionHost.ProjectInstance.FullPath),
null,
null,
null);
}
else
{
_targetBuilderCallback.EnterMSBuildCallbackState();
try
{
taskResult = await msbuildTask.ExecuteInternal();
}
finally
{
_targetBuilderCallback.ExitMSBuildCallbackState();
}
}
}
else if (taskExecutionHost.TaskInstance is CallTarget callTargetTask)
{
taskResult = await callTargetTask.ExecuteInternal();
}
else
{
#if FEATURE_FILE_TRACKER
using (FullTracking.Track(taskLoggingContext.TargetLoggingContext.Target.Name, _taskNode.Name, _buildRequestEntry.ProjectRootDirectory, _buildRequestEntry.RequestConfiguration.Project.PropertiesToBuildWith))
#endif
{
taskResult = taskExecutionHost.Execute();
}
}
}
catch (Exception ex)
{
if (ExceptionHandling.IsCriticalException(ex) || Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1")
{
taskLoggingContext.LogFatalTaskError(
ex,
new BuildEventFileInfo(_targetChildInstance.Location),
_taskNode.Name);
throw new CriticalTaskException(ex);
}
taskException = ex;
}
if (taskException == null)
{
taskReturned = true;
// Set the property "MSBuildLastTaskResult" to reflect whether the task succeeded or not.
// The main use of this is if ContinueOnError is true -- so that the next task can consult the result.
// So we want it to be "false" even if ContinueOnError is true.
// The constants "true" and "false" should NOT be localized. They become property values.
bucket.Lookup.SetProperty(ProjectPropertyInstance.Create(ReservedPropertyNames.lastTaskResult, taskResult ? "true" : "false", true/* may be reserved */, _buildRequestEntry.RequestConfiguration.Project.IsImmutable));
}
else
{
var type = taskException.GetType();
if (type == typeof(LoggerException))
{
// if a logger has failed, abort immediately
// Polite logger failure
_continueOnError = ContinueOnError.ErrorAndStop;
// Rethrow wrapped in order to avoid losing the callstack
throw new LoggerException(taskException.Message, taskException);
}
else if (type == typeof(InternalLoggerException))
{
// Logger threw arbitrary exception
_continueOnError = ContinueOnError.ErrorAndStop;
InternalLoggerException ex = taskException as InternalLoggerException;
// Rethrow wrapped in order to avoid losing the callstack
throw new InternalLoggerException(taskException.Message, taskException, ex.BuildEventArgs, ex.ErrorCode, ex.HelpKeyword, ex.InitializationException);
}
else if (type == typeof(ThreadAbortException))
{
#if !NET6_0_OR_GREATER && !NET6_0 // This is redundant but works around https://github.com/dotnet/sdk/issues/20700
Thread.ResetAbort();
#endif
_continueOnError = ContinueOnError.ErrorAndStop;
// Cannot rethrow wrapped as ThreadAbortException is sealed and has no appropriate constructor
// Stack will be lost
throw taskException;
}
else if (type == typeof(BuildAbortedException))
{
_continueOnError = ContinueOnError.ErrorAndStop;
// Rethrow wrapped in order to avoid losing the callstack
throw new BuildAbortedException(taskException.Message, (BuildAbortedException)taskException);
}
else if (type == typeof(CircularDependencyException))
{
_continueOnError = ContinueOnError.ErrorAndStop;
ProjectErrorUtilities.ThrowInvalidProject(taskLoggingContext.Task.Location, "CircularDependency", taskLoggingContext.TargetLoggingContext.Target.Name);
}
else if (type == typeof(InvalidProjectFileException))
{
// Just in case this came out of a task, make sure it's not
// marked as having been logged.
InvalidProjectFileException ipex = (InvalidProjectFileException)taskException;
ipex.HasBeenLogged = false;
if (_continueOnError != ContinueOnError.ErrorAndStop)
{
taskLoggingContext.LogInvalidProjectFileError(ipex);
taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
}
else
{
// Rethrow wrapped in order to avoid losing the callstack
throw new InvalidProjectFileException(ipex.Message, ipex);
}
}
else if (type == typeof(Exception) || type.GetTypeInfo().IsSubclassOf(typeof(Exception)))
{
// Occasionally, when debugging a very uncommon task exception, it is useful to loop the build with
// a debugger attached to break on 2nd chance exceptions.
// That requires that there needs to be a way to not catch here, by setting an environment variable.
if (ExceptionHandling.IsCriticalException(taskException) || (Environment.GetEnvironmentVariable("MSBUILDDONOTCATCHTASKEXCEPTIONS") == "1"))
{
// Wrapping in an Exception will unfortunately mean that this exception would fly through any IsCriticalException above.
// However, we should not have any, also we should not have stashed such an exception anyway.
throw new Exception(taskException.Message, taskException);
}
Exception exceptionToLog = taskException;
if (exceptionToLog is TargetInvocationException)
{
exceptionToLog = exceptionToLog.InnerException;
}
// handle any exception thrown by the task during execution
// NOTE: We catch ALL exceptions here, to attempt to completely isolate the Engine
// from failures in the task.
if (_continueOnError == ContinueOnError.WarnAndContinue)
{
taskLoggingContext.LogTaskWarningFromException(
exceptionToLog,
new BuildEventFileInfo(_targetChildInstance.Location),
_taskNode.Name);
// Log a message explaining why we converted the previous error into a warning.
taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
}
else
{
taskLoggingContext.LogFatalTaskError(
exceptionToLog,
new BuildEventFileInfo(_targetChildInstance.Location),
_taskNode.Name);
}
}
else
{
ErrorUtilities.ThrowInternalErrorUnreachable();
}
}
// When a task fails it must log an error. If a task fails to do so,
// that is logged as an error. MSBuild tasks are an exception because
// errors are not logged directly from them, but the tasks spawned by them.
IBuildEngine be = taskExecutionHost.TaskInstance.BuildEngine;
if (taskReturned // if the task returned
&& !taskResult // and it returned false
&& !taskLoggingContext.HasLoggedErrors // and it didn't log any errors
&& (be is TaskHost th ? th.BuildRequestsSucceeded : false)
&& !(_cancellationToken.CanBeCanceled && _cancellationToken.IsCancellationRequested)) // and it wasn't cancelled
{
// Then decide how to log MSB4181
if (be is IBuildEngine7 be7 && be7.AllowFailureWithoutError)
{
// If it's allowed to fail without error, log as a message
taskLoggingContext.LogComment(MessageImportance.Normal, "TaskReturnedFalseButDidNotLogError", _taskNode.Name);
}
else if (_continueOnError == ContinueOnError.WarnAndContinue)
{
taskLoggingContext.LogWarning(null,
new BuildEventFileInfo(_targetChildInstance.Location),
"TaskReturnedFalseButDidNotLogError",
_taskNode.Name);
taskLoggingContext.LogComment(MessageImportance.Normal, "ErrorConvertedIntoWarning");
}
else
{
taskLoggingContext.LogError(new BuildEventFileInfo(_targetChildInstance.Location),
"TaskReturnedFalseButDidNotLogError",
_taskNode.Name);
}
}
// If the task returned attempt to gather its outputs. If gathering outputs fails set the taskResults
// to false
if (taskReturned)
{
taskResult = GatherTaskOutputs(taskExecutionHost, howToExecuteTask, bucket) && taskResult;
}
// If the taskResults are false look at ContinueOnError. If ContinueOnError=false (default)
// mark the taskExecutedSuccessfully=false. Otherwise let the task succeed but log a normal
// pri message that says this task is continuing because ContinueOnError=true
resultCode = taskResult ? WorkUnitResultCode.Success : WorkUnitResultCode.Failed;
actionCode = WorkUnitActionCode.Continue;
if (resultCode == WorkUnitResultCode.Failed)
{
if (_continueOnError == ContinueOnError.ErrorAndStop)
{
actionCode = WorkUnitActionCode.Stop;
}
else
{
// This is the ErrorAndContinue or WarnAndContinue case...
string settingString = "true";
if (_taskNode.ContinueOnErrorLocation != null)
{
settingString = bucket.Expander.ExpandIntoStringAndUnescape(_taskNode.ContinueOnError, ExpanderOptions.ExpandAll, _taskNode.ContinueOnErrorLocation); // expand embedded item vectors after expanding properties and item metadata