| File: FilteredBinlogReplay.cs | Web Access |
| Project: MSBuild.csproj (MSBuild) |
// 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.IO; using System.Reflection; using System.Threading; using Microsoft.Build.CommandLine.Experimental; using Microsoft.Build.Framework; using Microsoft.Build.Logging; using Microsoft.Build.Shared; #if NETFRAMEWORK using Directory = Microsoft.IO.Directory; using File = Microsoft.IO.File; using Path = Microsoft.IO.Path; #endif namespace Microsoft.Build.CommandLine; internal sealed class FilteredBinlogReplay { // As with TerminalLogger initialization, shared source types prevent InternalsVisibleTo. // A delegate avoids wrapping initialization failures in TargetInvocationException. private static readonly Action<BinaryLogger, IEventSource, Stream, BinaryLoggerParameters, string> s_initializeBinaryLogger = (Action<BinaryLogger, IEventSource, Stream, BinaryLoggerParameters, string>)typeof(BinaryLogger) .GetMethod(nameof(BinaryLogger.Initialize), BindingFlags.Instance | BindingFlags.NonPublic, binder: null, types: [typeof(IEventSource), typeof(Stream), typeof(BinaryLoggerParameters), typeof(string)], modifiers: null)! .CreateDelegate(typeof(Action<BinaryLogger, IEventSource, Stream, BinaryLoggerParameters, string>)); private readonly string _inputPath; private sealed class Output(string path, BinaryLoggerParameters parameters) { internal string Path { get; } = path; internal BinaryLoggerParameters Parameters { get; } = parameters; internal string? StagingPath { get; set; } internal FileStream? Stream { get; set; } internal bool OwnsStagingFile { get; set; } } private FilteredBinlogReplay(string inputPath) { _inputPath = inputPath; } internal static bool IsRequested(string inputPath, CommandLineSwitches switches) { if (FileUtilities.IsBinaryLogFilename(inputPath)) { var processed = BinaryLogger.ProcessParameters(switches[CommandLineSwitches.ParameterizedSwitch.BinaryLogger]); foreach (string parameter in processed.DistinctParameterSets) { if (BinaryLogger.ParseParameters(parameter).ExcludedEventKinds.Count != 0) { return true; } } } return false; } internal static FilteredBinlogReplay Create(string inputPath, CommandLineSwitches switches) { ValidateSwitches(switches); var processed = BinaryLogger.ProcessParameters(switches[CommandLineSwitches.ParameterizedSwitch.BinaryLogger]); foreach (string parameter in processed.DistinctParameterSets) { CreateOutput(parameter); } return new FilteredBinlogReplay(inputPath); } private static Output CreateOutput(string parameter) { var parameters = BinaryLogger.ParseParameters(parameter); CommandLineSwitchException.VerifyThrow( parameters.ProjectImportsCollectionMode != BinaryLogger.ProjectImportsCollectionMode.ZipFile, "ReplayFilterZipNotSupported", parameter); string outputPath; try { outputPath = Path.GetFullPath(parameters.LogFilePath ?? BinaryLogger.ExtractFilePathFromParameters(parameter)); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { CommandLineSwitchException.Throw("ReplayFilterInvalidOutput", parameter, ex.Message); throw; } CommandLineSwitchException.VerifyThrow(!File.Exists(outputPath) && !Directory.Exists(outputPath), "ReplayFilterOutputExists", outputPath); return new Output(outputPath, parameters); } private static void ValidateSwitches(CommandLineSwitches switches) { foreach (CommandLineSwitches.ParameterizedSwitch kind in Enum.GetValues(typeof(CommandLineSwitches.ParameterizedSwitch))) { if (kind is CommandLineSwitches.ParameterizedSwitch.Invalid or CommandLineSwitches.ParameterizedSwitch.NumberOfParameterizedSwitches || !switches.IsParameterizedSwitchSet(kind)) { continue; } CommandLineSwitchException.VerifyThrow( kind is CommandLineSwitches.ParameterizedSwitch.Project or CommandLineSwitches.ParameterizedSwitch.Logger or CommandLineSwitches.ParameterizedSwitch.DistributedLogger or >= CommandLineSwitches.ParameterizedSwitch.FileLoggerParameters and <= CommandLineSwitches.ParameterizedSwitch.FileLoggerParameters9 or CommandLineSwitches.ParameterizedSwitch.BinaryLogger or CommandLineSwitches.ParameterizedSwitch.Verbosity or CommandLineSwitches.ParameterizedSwitch.ConsoleLoggerParameters or CommandLineSwitches.ParameterizedSwitch.TerminalLogger or CommandLineSwitches.ParameterizedSwitch.TerminalLoggerParameters or CommandLineSwitches.ParameterizedSwitch.NoLogo or CommandLineSwitches.ParameterizedSwitch.MaxCPUCount or CommandLineSwitches.ParameterizedSwitch.NodeReuse or CommandLineSwitches.ParameterizedSwitch.LowPriority, "ReplayFilterUnsupportedSwitch", switches.GetParameterizedSwitchCommandLineArg(kind)); } foreach (CommandLineSwitches.ParameterlessSwitch kind in Enum.GetValues(typeof(CommandLineSwitches.ParameterlessSwitch))) { if (kind is CommandLineSwitches.ParameterlessSwitch.Invalid or CommandLineSwitches.ParameterlessSwitch.NumberOfParameterlessSwitches || !switches.IsParameterlessSwitchSet(kind)) { continue; } CommandLineSwitchException.VerifyThrow( kind is CommandLineSwitches.ParameterlessSwitch.NoAutoResponse or CommandLineSwitches.ParameterlessSwitch.NoConsoleLogger or >= CommandLineSwitches.ParameterlessSwitch.FileLogger and <= CommandLineSwitches.ParameterlessSwitch.DistributedFileLogger, "ReplayFilterUnsupportedSwitch", switches.GetParameterlessSwitchCommandLineArg(kind)); } } internal static bool IsSdkLogger(string[] parameters) { if (parameters.Length != 1) { return false; } string assemblyPath = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, "dotnet.dll"); string logger = $"Microsoft.DotNet.Cli.Commands.MSBuild.MSBuildLogger,{assemblyPath}" + $"*Microsoft.DotNet.Cli.Commands.MSBuild.MSBuildForwardingLogger,{assemblyPath}"; return string.Equals(QuotingUtilities.Unquote(parameters[0]), logger, NativeMethodsShared.IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal); } internal bool Replay(ILogger[] loggers, int cpuCount, CancellationToken cancellationToken) { Dictionary<BinaryLogger, Output> outputs = []; try { using var binaryReader = BinaryLogReplayEventSource.OpenReader(_inputPath); using var reader = BinaryLogReplayEventSource.OpenBuildEventsReader(binaryReader, closeInput: false); cancellationToken.ThrowIfCancellationRequested(); HashSet<string> outputPaths = new(NativeMethodsShared.IsWindows ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); foreach (ILogger logger in loggers) { if (logger is BinaryLogger binaryLogger) { Output output = CreateOutput(binaryLogger.Parameters); CommandLineSwitchException.VerifyThrow(outputPaths.Add(output.Path), "ReplayFilterOutputExists", output.Path); outputs.Add(binaryLogger, output); } } var source = new BinaryLogReplayEventSource(); List<ILogger> initializedLoggers = []; bool finalized = true; try { foreach (ILogger logger in loggers) { if (logger is BinaryLogger binaryLogger) { Output output = outputs[binaryLogger]; string directory = Path.GetDirectoryName(output.Path)!; Directory.CreateDirectory(directory); output.StagingPath = Path.Combine(directory, $".msbuild-filter-{Guid.NewGuid():N}.binlog"); output.Stream = new FileStream(output.StagingPath, FileMode.CreateNew, FileAccess.Write, FileShare.None); output.OwnsStagingFile = true; // Keep the temporary path out of the logger's metadata. initializedLoggers.Add(logger); s_initializeBinaryLogger(binaryLogger, source, output.Stream, output.Parameters, output.Path); } else { initializedLoggers.Add(logger); if (logger is INodeLogger nodeLogger) { nodeLogger.Initialize(source, cpuCount); } else { logger.Initialize(source); } } } source.Replay(reader, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); } finally { try { for (int i = initializedLoggers.Count - 1; i >= 0; i--) { try { initializedLoggers[i].Shutdown(); } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { ReportFailure(ex); finalized = false; } } } finally { foreach (Output output in outputs.Values) { try { output.Stream?.Dispose(); } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { ReportFailure(ex); finalized = false; } } } } if (!finalized) { return false; } cancellationToken.ThrowIfCancellationRequested(); // Same-directory publication must not overwrite a destination created during replay. foreach (Output output in outputs.Values) { File.Move(output.StagingPath!, output.Path); output.OwnsStagingFile = false; } return true; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { Console.WriteLine(ResourceUtilities.GetResourceString("ReplayFilterCanceled")); return false; } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { ReportFailure(ex); return false; } finally { foreach (Output output in outputs.Values) { if (output.OwnsStagingFile) { try { File.Delete(output.StagingPath!); } catch (Exception ex) when (ExceptionHandling.IsIoRelatedException(ex)) { ReportFailure(ex); } } } } } private static void ReportFailure(Exception exception) => Console.WriteLine(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ReplayFilterFailed", exception.ToString())); }