| File: BackEnd\BuildManager\BuildSubmission.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; namespace Microsoft.Build.Execution { /// <summary> /// A callback used to receive notification that a build has completed. /// </summary> /// <remarks> /// When this delegate is invoked, the WaitHandle on the BuildSubmission will have been be signalled and the OverallBuildResult will be valid. /// </remarks> internal delegate void BuildSubmissionCompleteCallbackInternal<TRequestData, TResultData>( BuildSubmissionBase<TRequestData, TResultData> submission) where TRequestData : BuildRequestDataBase where TResultData : BuildResultBase; public abstract class BuildSubmissionBase<TRequestData, TResultData> : BuildSubmissionBase where TRequestData : BuildRequestDataBase where TResultData : BuildResultBase { /// <summary> /// The callback to invoke when the submission is complete. /// </summary> private BuildSubmissionCompleteCallbackInternal<TRequestData, TResultData>? _completionCallback; /// <summary> /// Constructor /// </summary> protected internal BuildSubmissionBase(BuildManager buildManager, int submissionId, TRequestData requestData) : base(buildManager, submissionId) { ArgumentNullException.ThrowIfNull(requestData); BuildRequestData = requestData; } // // Unfortunately covariant overrides are not available for .NET 472, // so we have to use two set of properties for derived classes. internal override BuildResultBase? BuildResultBase => BuildResult; internal override BuildRequestDataBase BuildRequestDataBase => BuildRequestData; /// <summary> /// The results of the build per graph node. Valid only after WaitHandle has become signalled. /// </summary> public TResultData? BuildResult { get; set; } /// <summary> /// The BuildRequestData being used for this submission. /// </summary> internal TRequestData BuildRequestData { get; } /// <summary> /// Starts the request and blocks until results are available. /// </summary> /// <exception cref="InvalidOperationException">The request has already been started or is already complete.</exception> [RequiresUnreferencedCode("Initializes project cache plugins, which load plugin assemblies from disk and reflect over their types; incompatible with trimming.")] public abstract TResultData Execute(); [RequiresUnreferencedCode("Initializes project cache plugins, which load plugin assemblies from disk and reflect over their types; incompatible with trimming.")] private protected void ExecuteAsync( BuildSubmissionCompleteCallbackInternal<TRequestData, TResultData>? callback, object? context, bool allowMainThreadBuild) { ErrorUtilities.VerifyThrowInvalidOperation(!IsCompleted, "SubmissionAlreadyComplete"); _completionCallback = callback; AsyncContext = context; BuildManager.ExecuteSubmission(this, allowMainThreadBuild); } /// <summary> /// Sets the event signaling that the build is complete. /// </summary> internal void CompleteResults(TResultData result) { ArgumentNullException.ThrowIfNull(result); CheckResultValidForCompletion(result); BuildResult ??= result; CheckForCompletion(); } protected internal abstract void CheckResultValidForCompletion(TResultData result); protected internal abstract TResultData CreateFailedResult(Exception exception); internal override BuildResultBase CompleteResultsWithException(Exception exception) => CompleteResults(exception); private TResultData CompleteResults(Exception exception) { TResultData result = CreateFailedResult(exception); CompleteResults(result); return result; } /// <summary> /// Determines if we are completely done with this submission and can complete it so the user may access results. /// </summary> protected internal override void CheckForCompletion() { if (BuildResult != null && LoggingCompleted) { bool hasCompleted = (Interlocked.Exchange(ref CompletionInvoked, 1) == 1); if (!hasCompleted) { OnCompletition(); CompletionEvent.Set(); if (_completionCallback != null) { void Callback(object? state) { _completionCallback(this); } ThreadPoolExtensions.QueueThreadPoolWorkItemWithCulture(Callback, CultureInfo.CurrentCulture, CultureInfo.CurrentUICulture); } } } } } /// <summary> /// A callback used to receive notification that a build has completed. /// </summary> /// <remarks> /// When this delegate is invoked, the WaitHandle on the BuildSubmission will have been be signalled and the OverallBuildResult will be valid. /// </remarks> public delegate void BuildSubmissionCompleteCallback(BuildSubmission submission); /// <summary> /// A BuildSubmission represents a build request which has been submitted to the BuildManager for processing. It may be used to /// execute synchronous or asynchronous build requests and provides access to the results upon completion. /// </summary> /// <remarks> /// This class is thread-safe. /// </remarks> public class BuildSubmission : BuildSubmissionBase<BuildRequestData, BuildResult> { /// <summary> /// Flag indicating whether synchronous wait should support legacy threading semantics. /// </summary> private readonly bool _legacyThreadingSemantics; /// <summary> /// The build request for execution. /// </summary> internal BuildRequest? BuildRequest { get; set; } internal BuildSubmission(BuildManager buildManager, int submissionId, BuildRequestData requestData, bool legacyThreadingSemantics) : base(buildManager, submissionId, requestData) { _legacyThreadingSemantics = legacyThreadingSemantics; } /// <summary> /// Starts the request asynchronously and immediately returns control to the caller. /// </summary> /// <exception cref="InvalidOperationException">The request has already been started or is already complete.</exception> [RequiresUnreferencedCode("Initializes project cache plugins, which load plugin assemblies from disk and reflect over their types; incompatible with trimming.")] public void ExecuteAsync(BuildSubmissionCompleteCallback? callback, object? context) { void Clb(BuildSubmissionBase<BuildRequestData, BuildResult> submission) { callback?.Invoke((BuildSubmission)submission); } ExecuteAsync(Clb, context, allowMainThreadBuild: false); } /// <summary> /// Starts the request and blocks until results are available. /// </summary> /// <exception cref="System.InvalidOperationException">The request has already been started or is already complete.</exception> [RequiresUnreferencedCode("Initializes project cache plugins, which load plugin assemblies from disk and reflect over their types; incompatible with trimming.")] public override BuildResult Execute() { LegacyThreadingData legacyThreadingData = ((IBuildComponentHost)BuildManager).LegacyThreadingData; legacyThreadingData.RegisterSubmissionForLegacyThread(SubmissionId); ExecuteAsync(null, null, _legacyThreadingSemantics); if (_legacyThreadingSemantics) { RequestBuilder.WaitWithBuilderThreadStart([WaitHandle], false, legacyThreadingData, SubmissionId); } else { WaitHandle.WaitOne(); } legacyThreadingData.UnregisterSubmissionForLegacyThread(SubmissionId); Assumed.NotNull(BuildResult, "BuildResult is not populated after Execute is done."); return BuildResult!; } /// <summary> /// Whether the build has started. /// </summary> internal override bool IsStarted { get => BuildRequest != null; // Ignore the set - the submission is started once the BuildRequest is set. set { } } protected internal override BuildResult CreateFailedResult(Exception exception) { Assumed.NotNull(BuildRequest, "BuildRequest is not populated while reporting failed result."); return new(BuildRequest!, exception); } protected internal override void CheckResultValidForCompletion(BuildResult result) { // We verify that we got results from the same configuration, but not necessarily the same request, because we are // rather flexible in how users are allowed to submit multiple requests for the same configuration. In this case, the // request id of the result will match the first request, even though it will contain results for all requests (including // this one.) if (result.ConfigurationId != BuildRequest?.ConfigurationId) { InternalError.Throw($"BuildResult configuration ({result.ConfigurationId}) doesn't match BuildRequest configuration ({BuildRequest?.ConfigurationId})"); } } protected internal override void OnCompletition() { // Did this submission have warnings elevated to errors? If so, mark it as // failed even though it succeeded (with warnings--but they're errors). if (BuildResult != null && ((IBuildComponentHost)BuildManager).LoggingService.HasBuildSubmissionLoggedErrors(BuildResult.SubmissionId)) { BuildResult.SetOverallResult(overallResult: false); } } // WARNING!: Do not remove the below proxy properties. // They are required to make the OM forward compatible // (code built against this OM should run against binaries with previous version of OM). /// <inheritdoc cref="BuildSubmissionBase{BuildRequestData, BuildResult}.BuildResult"/> public new BuildResult? BuildResult => base.BuildResult; /// <inheritdoc cref="BuildSubmissionBase.BuildManager"/> public new BuildManager BuildManager => base.BuildManager; /// <inheritdoc cref="BuildSubmissionBase.SubmissionId"/> public new int SubmissionId => base.SubmissionId; /// <inheritdoc cref="BuildSubmissionBase.AsyncContext"/> public new object? AsyncContext => base.AsyncContext; /// <inheritdoc cref="BuildSubmissionBase.WaitHandle"/> public new WaitHandle WaitHandle => base.WaitHandle; /// <inheritdoc cref="BuildSubmissionBase.IsCompleted"/> public new bool IsCompleted => base.IsCompleted; } }