├── cache └── writecache.json ├── docs ├── input │ ├── index.md │ ├── _Footer.cshtml │ ├── _Navbar.cshtml │ └── assets │ │ └── css │ │ └── override.less └── config.wyam ├── .gitmodules ├── marvin.cmd ├── global.json ├── src ├── MsBuildPipeLogger.Logger │ ├── IPipeWriter.cs │ ├── AnonymousPipeWriter.cs │ ├── TypeExtensions.cs │ ├── BinaryLogger │ │ ├── BinaryLogRecordKind.cs │ │ ├── BuildEventArgsFieldFlags.cs │ │ ├── TargetBuiltReason.cs │ │ ├── ProjectEvaluationStartedEventArgs.cs │ │ ├── ProjectEvaluationFinishedEventArgs.cs │ │ ├── EvaluationIdProvider.cs │ │ ├── TargetSkippedEventArgs.cs │ │ ├── ProjectImportedEventArgs.cs │ │ ├── ProfilerResult.cs │ │ ├── Reflector.cs │ │ ├── EvaluationLocation.cs │ │ └── BuildEventArgsWriter.cs │ ├── NamedPipeWriter.cs │ ├── MsBuildPipeLogger.Logger.csproj │ ├── PipeLogger.cs │ ├── ParameterParser.cs │ └── PipeWriter.cs └── MsBuildPipeLogger.Server │ ├── MsBuildPipeLogger.Server.csproj │ ├── IPipeLoggerServer.cs │ ├── InterlockedBool.cs │ ├── BuildEventArgsReaderProxy.cs │ ├── CancellationTokenExtensions.cs │ ├── NamedPipeLoggerServer.cs │ ├── AnonymousPipeLoggerServer.cs │ ├── PipeLoggerServer.cs │ └── PipeBuffer.cs ├── tests ├── .editorconfig ├── MsBuildPipeLogger.Tests.Client │ ├── MsBuildPipeLogger.Tests.Client.csproj │ └── Program.cs ├── MsBuildPipeLogger.Tests │ ├── MsBuildPipeLogger.Tests.csproj │ └── IntegrationFixture.cs └── MsBuildPipeLogger.Logger.Tests │ ├── MsBuildPipeLogger.Logger.Tests.csproj │ └── ParameterParserFixture.cs ├── stylecop.json ├── marvin.yml ├── .github └── workflows │ ├── test-report.yml │ └── build.yml ├── LICENSE ├── RELEASE.md ├── Directory.Build.props ├── MsBuildPipeLogger.sln ├── .gitignore ├── README.md └── .editorconfig /cache/writecache.json: -------------------------------------------------------------------------------- 1 | {"Writes":{},"Content":{}} -------------------------------------------------------------------------------- /docs/input/index.md: -------------------------------------------------------------------------------- 1 | Title: Home 2 | NoTitle: true 3 | --- 4 | ^"../../README.md" -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "marvin"] 2 | path = marvin 3 | url = https://github.com/daveaglick/Marvin.git 4 | -------------------------------------------------------------------------------- /marvin.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cd "marvin" 3 | dotnet run -- %* 4 | set exitcode=%errorlevel% 5 | cd %~dp0 6 | exit /b %exitcode% -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.100", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /docs/config.wyam: -------------------------------------------------------------------------------- 1 | Settings[Keys.Host] = "msbuildpipelogger.netlify.com"; 2 | Settings[DocsKeys.Title] = "MsBuildPipeLogger"; 3 | Settings[DocsKeys.SourceFiles] = "../../src/**/*.cs"; 4 | Settings[DocsKeys.ProcessIncludes] = true; -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/IPipeWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Build.Framework; 3 | 4 | namespace MsBuildPipeLogger 5 | { 6 | public interface IPipeWriter : IDisposable 7 | { 8 | void Write(BuildEventArgs e); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/.editorconfig: -------------------------------------------------------------------------------- 1 | # Test Rules 2 | # These rules apply to all test projects and relax some severity levels as appropriate 3 | [*.{cs,vb}] 4 | dotnet_diagnostic.RCS1046.severity = none 5 | dotnet_diagnostic.VSTHRD200.severity = none 6 | dotnet_diagnostic.VSTHRD103.severity = suggestion -------------------------------------------------------------------------------- /stylecop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", 3 | "settings": { 4 | "orderingRules": { 5 | "usingDirectivesPlacement": "outsideNamespace" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /marvin.yml: -------------------------------------------------------------------------------- 1 | ProjectSets: 2 | - Name: MsBuildPipeLogger 3 | SourceProjects: "src/*/*.csproj" 4 | TestProjects: "tests/*/*.csproj" 5 | GitHubOwner: "daveaglick" 6 | GitHubName: "MsBuildPipeLogger" 7 | GitHubToken: => GetString("GITHUB_TOKEN") 8 | NuGetApiKey: => GetString("DAVEAGLICK_NUGET_API_KEY") -------------------------------------------------------------------------------- /docs/input/_Footer.cshtml: -------------------------------------------------------------------------------- 1 | MsBuildPipeLogger is crafted by Dave Glick. Code on GitHub. Generated by Wyam and hosted by Netlify on a free open source plan. -------------------------------------------------------------------------------- /docs/input/_Navbar.cshtml: -------------------------------------------------------------------------------- 1 |
  • API
  • 2 |
  • GitHub
  • 3 |
  • Buy Me A Coffee
  • -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/AnonymousPipeWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | 3 | namespace MsBuildPipeLogger 4 | { 5 | public class AnonymousPipeWriter : PipeWriter 6 | { 7 | public string Handle { get; } 8 | 9 | public AnonymousPipeWriter(string pipeHandleAsString) 10 | : base(new AnonymousPipeClientStream(PipeDirection.Out, pipeHandleAsString)) 11 | { 12 | Handle = pipeHandleAsString; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/test-report.yml: -------------------------------------------------------------------------------- 1 | name: 'Test Report' 2 | on: 3 | workflow_run: 4 | workflows: [ 'Build' ] 5 | types: 6 | - completed 7 | jobs: 8 | report: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | os: [windows-latest, ubuntu-latest, macos-latest] 13 | steps: 14 | - name: Process Test Results 15 | uses: dorny/test-reporter@v1 16 | with: 17 | artifact: test-results-${{ matrix.os }} 18 | name: 'Test Results (${{ matrix.os }})' 19 | path: '**/*.trx' 20 | reporter: dotnet-trx -------------------------------------------------------------------------------- /docs/input/assets/css/override.less: -------------------------------------------------------------------------------- 1 | @import "bootstrap/variables.less"; 2 | @import "bootstrap/mixins.less"; 3 | @import "adminlte/variables.less"; 4 | @import "adminlte/mixins.less"; 5 | 6 | @media (min-width: 768px) { 7 | .buymeacoffee { 8 | position: absolute !important; 9 | right: 0 !important; 10 | top: -8px !important; 11 | 12 | & a { 13 | height: 58px !important; 14 | 15 | & img { 16 | height: auto !important; 17 | width: auto !important; 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace MsBuildPipeLogger 7 | { 8 | internal static class TypeExtensions 9 | { 10 | public static PropertyInfo GetProperty(this Type type, string name) => type.GetTypeInfo().GetDeclaredProperty(name); 11 | 12 | public static MethodInfo GetGetMethod(this PropertyInfo property) => property.GetMethod; 13 | 14 | public static FieldInfo GetField(this Type type, string name) => type.GetTypeInfo().GetDeclaredField(name); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Tests.Client/MsBuildPipeLogger.Tests.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/MsBuildPipeLogger.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | MsBuildPipeLogger 5 | A logger for MSBuild that sends event data over anonymous or named pipes. 6 | 7 | 8 | 9 | 10 | 11 | 12 | <_Parameter1>MsBuildPipeLogger.Tests 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/BinaryLogRecordKind.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Build.Logging 2 | { 3 | public enum BinaryLogRecordKind 4 | { 5 | EndOfFile = 0, 6 | BuildStarted, 7 | BuildFinished, 8 | ProjectStarted, 9 | ProjectFinished, 10 | TargetStarted, 11 | TargetFinished, 12 | TaskStarted, 13 | TaskFinished, 14 | Error, 15 | Warning, 16 | Message, 17 | TaskCommandLine, 18 | CriticalBuildMessage, 19 | ProjectEvaluationStarted, 20 | ProjectEvaluationFinished, 21 | ProjectImported, 22 | ProjectImportArchive, 23 | TargetSkipped 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Tests/MsBuildPipeLogger.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/IPipeLoggerServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Build.Framework; 3 | 4 | namespace MsBuildPipeLogger 5 | { 6 | public interface IPipeLoggerServer : IDisposable 7 | { 8 | /// 9 | /// Reads a single event from the pipe. This method blocks until an event is received, 10 | /// there are no more events, or the pipe is closed. 11 | /// 12 | /// The read event or null if there are no more events or the pipe is closed. 13 | BuildEventArgs Read(); 14 | 15 | /// 16 | /// Reads all events from the pipe and blocks until there are no more events or the pipe is closed. 17 | /// 18 | void ReadAll(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Logger.Tests/MsBuildPipeLogger.Logger.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/BuildEventArgsFieldFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Build.Logging 4 | { 5 | /// 6 | /// A bitmask to specify which fields on a BuildEventArgs object are present; used in serialization. 7 | /// 8 | [Flags] 9 | internal enum BuildEventArgsFieldFlags 10 | { 11 | None = 0, 12 | BuildEventContext = 1 << 0, 13 | HelpHeyword = 1 << 1, 14 | Message = 1 << 2, 15 | SenderName = 1 << 3, 16 | ThreadId = 1 << 4, 17 | Timestamp = 1 << 5, 18 | Subcategory = 1 << 6, 19 | Code = 1 << 7, 20 | File = 1 << 8, 21 | ProjectFile = 1 << 9, 22 | LineNumber = 1 << 10, 23 | ColumnNumber = 1 << 11, 24 | EndLineNumber = 1 << 12, 25 | EndColumnNumber = 1 << 13 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/NamedPipeWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | 3 | namespace MsBuildPipeLogger 4 | { 5 | public class NamedPipeWriter : PipeWriter 6 | { 7 | public string ServerName { get; } 8 | 9 | public string PipeName { get; } 10 | 11 | public NamedPipeWriter(string pipeName) 12 | : this(".", pipeName) 13 | { 14 | } 15 | 16 | public NamedPipeWriter(string serverName, string pipeName) 17 | : base(InitializePipe(serverName, pipeName)) 18 | { 19 | ServerName = serverName; 20 | PipeName = pipeName; 21 | } 22 | 23 | private static PipeStream InitializePipe(string serverName, string pipeName) 24 | { 25 | NamedPipeClientStream pipeStream = new NamedPipeClientStream(serverName, pipeName, PipeDirection.Out); 26 | pipeStream.Connect(); 27 | return pipeStream; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/TargetBuiltReason.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | namespace Microsoft.Build.Framework 5 | { 6 | /// 7 | /// The reason that a target was built by its parent target. 8 | /// 9 | public enum TargetBuiltReason 10 | { 11 | /// 12 | /// This wasn't built on because of a parent. 13 | /// 14 | None, 15 | 16 | /// 17 | /// The target was part of the parent's BeforeTargets list. 18 | /// 19 | BeforeTargets, 20 | 21 | /// 22 | /// The target was part of the parent's DependsOn list. 23 | /// 24 | DependsOn, 25 | 26 | /// 27 | /// The target was part of the parent's AfterTargets list. 28 | /// 29 | AfterTargets 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [windows-latest, ubuntu-latest, macos-latest] 14 | env: 15 | # https://github.com/NuGet/Home/issues/11548 16 | # https://twitter.com/xoofx/status/1488617114940452872?s=20&t=BKSN4j9rP6fOyg8l7aW0eg 17 | NUGET_CERT_REVOCATION_MODE: offline 18 | steps: 19 | - name: Get Source 20 | uses: actions/checkout@v2 21 | with: 22 | submodules: recursive 23 | - name: Install .NET Core SDK 24 | uses: actions/setup-dotnet@v1 25 | with: 26 | dotnet-version: | 27 | 3.1.x 28 | 6.0.x 29 | - name: Build and Test 30 | run: dotnet test --logger "trx;LogFileName=test-results.trx" 31 | - name: Upload Test Results 32 | uses: actions/upload-artifact@v2 33 | if: success() || failure() 34 | with: 35 | name: test-results-${{ matrix.os }} 36 | path: '**/test-results.trx' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dave Glick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Tests.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Microsoft.Build.Framework; 4 | 5 | namespace MsBuildPipeLogger.Tests.Client 6 | { 7 | internal class Program 8 | { 9 | public static int Main(string[] args) 10 | { 11 | Console.WriteLine(string.Join("; ", args)); 12 | int messages = int.Parse(args[1]); 13 | try 14 | { 15 | using (IPipeWriter writer = ParameterParser.GetPipeFromParameters(args[0])) 16 | { 17 | writer.Write(new BuildStartedEventArgs($"Testing", "help")); 18 | for (int c = 0; c < messages; c++) 19 | { 20 | writer.Write(new BuildMessageEventArgs($"Testing {c}", "help", "sender", MessageImportance.Normal)); 21 | } 22 | } 23 | } 24 | catch (Exception ex) 25 | { 26 | Console.WriteLine(ex.ToString()); 27 | return 1; 28 | } 29 | return 0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/InterlockedBool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipes; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace MsBuildPipeLogger 7 | { 8 | internal class InterlockedBool 9 | { 10 | private volatile int _set; 11 | 12 | public InterlockedBool() 13 | { 14 | _set = 0; 15 | } 16 | 17 | public InterlockedBool(bool initialState) 18 | { 19 | _set = initialState ? 1 : 0; 20 | } 21 | 22 | // Returns the previous switch state of the switch 23 | public bool Set() 24 | { 25 | #pragma warning disable 420 26 | return Interlocked.Exchange(ref _set, 1) != 0; 27 | #pragma warning restore 420 28 | } 29 | 30 | // Returns the previous switch state of the switch 31 | public bool Unset() 32 | { 33 | #pragma warning disable 420 34 | return Interlocked.Exchange(ref _set, 0) != 0; 35 | #pragma warning restore 420 36 | } 37 | 38 | // Returns the current state 39 | public static implicit operator bool(InterlockedBool interlockedBool) 40 | { 41 | return interlockedBool._set != 0; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/MsBuildPipeLogger.Logger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | MsBuildPipeLogger 5 | A logger for MSBuild that sends event data over anonymous or named pipes. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <_Parameter1>MsBuildPipeLogger.Logger.Tests 16 | 17 | 18 | <_Parameter1>MsBuildPipeLogger.Tests 19 | 20 | 21 | <_Parameter1>MsBuildPipeLogger.Tests.Client 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/ProjectEvaluationStartedEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Build.Framework 7 | { 8 | /// 9 | /// Arguments for the project evaluation started event. 10 | /// 11 | #if FEATURE_BINARY_SERIALIZATION 12 | [Serializable] 13 | #endif 14 | public class ProjectEvaluationStartedEventArgs : BuildStatusEventArgs 15 | { 16 | /// 17 | /// Initializes a new instance of the ProjectEvaluationStartedEventArgs class. 18 | /// 19 | public ProjectEvaluationStartedEventArgs() 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the ProjectEvaluationStartedEventArgs class. 25 | /// 26 | public ProjectEvaluationStartedEventArgs(string message, params object[] messageArgs) 27 | : base(message, null, null, DateTime.UtcNow, messageArgs) 28 | { 29 | } 30 | 31 | /// 32 | /// Gets or sets the full path of the project that started evaluation. 33 | /// 34 | public string ProjectFile { get; set; } 35 | } 36 | } -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/BuildEventArgsReaderProxy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using Microsoft.Build.Framework; 5 | using Microsoft.Build.Logging; 6 | 7 | namespace MsBuildPipeLogger 8 | { 9 | internal class BuildEventArgsReaderProxy 10 | { 11 | private readonly Func _read; 12 | 13 | public BuildEventArgsReaderProxy(BinaryReader reader) 14 | { 15 | // Use reflection to get the Microsoft.Build.Logging.BuildEventArgsReader.Read() method 16 | object argsReader; 17 | Type buildEventArgsReader = typeof(BinaryLogger).GetTypeInfo().Assembly.GetType("Microsoft.Build.Logging.BuildEventArgsReader"); 18 | ConstructorInfo readerCtor = buildEventArgsReader.GetConstructor(new[] { typeof(BinaryReader) }); 19 | if (readerCtor != null) 20 | { 21 | argsReader = readerCtor.Invoke(new[] { reader }); 22 | } 23 | else 24 | { 25 | readerCtor = buildEventArgsReader.GetConstructor(new[] { typeof(BinaryReader), typeof(int) }); 26 | argsReader = readerCtor.Invoke(new object[] { reader, 7 }); 27 | } 28 | MethodInfo readMethod = buildEventArgsReader.GetMethod("Read"); 29 | _read = (Func)readMethod.CreateDelegate(typeof(Func), argsReader); 30 | } 31 | 32 | public BuildEventArgs Read() => _read(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/PipeLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | 5 | namespace MsBuildPipeLogger 6 | { 7 | /// 8 | /// Logger to send messages from the MSBuild logging system over an anonymous pipe. 9 | /// 10 | /// 11 | /// Heavily based on the work of Kirill Osenkov and the MSBuildStructuredLog project. 12 | /// 13 | public class PipeLogger : Logger 14 | { 15 | protected IPipeWriter Pipe { get; private set; } 16 | 17 | public override void Initialize(IEventSource eventSource) 18 | { 19 | InitializeEnvironmentVariables(); 20 | Pipe = InitializePipeWriter(); 21 | InitializeEvents(eventSource); 22 | } 23 | 24 | protected virtual void InitializeEnvironmentVariables() 25 | { 26 | Environment.SetEnvironmentVariable("MSBUILDTARGETOUTPUTLOGGING", "true"); 27 | Environment.SetEnvironmentVariable("MSBUILDLOGIMPORTS", "1"); 28 | } 29 | 30 | protected virtual IPipeWriter InitializePipeWriter() => ParameterParser.GetPipeFromParameters(Parameters); 31 | 32 | protected virtual void InitializeEvents(IEventSource eventSource) 33 | { 34 | eventSource.AnyEventRaised += (_, e) => Pipe.Write(e); 35 | } 36 | 37 | public override void Shutdown() 38 | { 39 | base.Shutdown(); 40 | if (Pipe != null) 41 | { 42 | Pipe.Dispose(); 43 | Pipe = null; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/CancellationTokenExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace MsBuildPipeLogger 6 | { 7 | internal static class CancellationTokenExtensions 8 | { 9 | public static TResult Try(this CancellationToken cancellationToken, Func action, Func cancelled) 10 | { 11 | if (cancellationToken.IsCancellationRequested) 12 | { 13 | return cancelled == null ? default(TResult) : cancelled(); 14 | } 15 | try 16 | { 17 | return action(); 18 | } 19 | catch (TaskCanceledException) 20 | { 21 | // Thrown if the task itself was canceled from inside the read method 22 | return cancelled == null ? default(TResult) : cancelled(); 23 | } 24 | catch (OperationCanceledException) 25 | { 26 | // Thrown if the operation was canceled (I.e., the task didn't deal with cancellation) 27 | return cancelled == null ? default(TResult) : cancelled(); 28 | } 29 | catch (AggregateException ex) 30 | { 31 | // Sometimes the cancellation exceptions are thrown in aggregate 32 | if (!(ex.InnerException is TaskCanceledException) 33 | && !(ex.InnerException is OperationCanceledException)) 34 | { 35 | throw; 36 | } 37 | return cancelled == null ? default(TResult) : cancelled(); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # 1.1.6 2 | 3 | - Fix for unhandled interrupted system call in `PipeLoggerServer` (#8, #9, thanks @xoofx). 4 | 5 | # 1.1.5 6 | 7 | - Added SourceLink support. 8 | 9 | # 1.1.4 10 | 11 | - Fix to avoid calling `PipeStream.WaitForPipeDrain()` on non-Windows platforms where it's not supported (#3, #7, thanks @xoofx). 12 | - Updated target of `MsBuildPipeLogger.Logger` and `MsBuildPipeLogger.Server` to .NET Standard 2.0 (#5, thanks @xoofx). 13 | - Fix for lack of async availability on Windows with anonymous pipes (#4, thanks @xoofx). 14 | - Fix to detect `BuildFinishedEventArgs` and stop reading from the pipe when found (#2, #6, thanks @ltcmelo and @xoofx). 15 | 16 | # 1.1.3 17 | 18 | - [Fix] Fixed a race condition when fetching a log buffer (#1, thanks @duncanawoods) 19 | 20 | # 1.1.2 21 | 22 | - [Fix] No longer catches certain error exceptions that actually indicate a problem 23 | 24 | # 1.1.1 25 | 26 | - [Fix] Handles premature stream termination when reading events in the server 27 | 28 | # 1.1.0 29 | 30 | - [Feature] Support for server read cancellation with a `CancellationToken` 31 | - [Refactoring] Added a `IPipeLoggerServer` interface 32 | - [Feature] Added `IPipeLoggerServer.ReadAll()` to read all events in one call 33 | - [Refactoring] Changes `IPipeLoggerServer.Read()` to return the event that was read instead of a `bool` 34 | 35 | # 1.0.3 36 | 37 | - [Refactoring] Made it easier to override the logger and use it as the basis for custom pipe loggers (I.e., filtering events) 38 | 39 | # 1.0.2 40 | 41 | - [Fix] No longer crashes when caller disposes the server 42 | 43 | # 1.0.1 44 | 45 | - [Refactoring] Greatly improved performance by using buffers and threads for pipe I/O 46 | 47 | # 1.0.0 48 | 49 | - Initial release -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/ProjectEvaluationFinishedEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | using Microsoft.Build.Framework.Profiler; 6 | 7 | namespace Microsoft.Build.Framework 8 | { 9 | /// 10 | /// Arguments for the project evaluation finished event. 11 | /// 12 | #if FEATURE_BINARY_SERIALIZATION 13 | [Serializable] 14 | #endif 15 | public sealed class ProjectEvaluationFinishedEventArgs : BuildStatusEventArgs 16 | { 17 | /// 18 | /// Initializes a new instance of the ProjectEvaluationFinishedEventArgs class. 19 | /// 20 | public ProjectEvaluationFinishedEventArgs() 21 | { 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the ProjectEvaluationFinishedEventArgs class. 26 | /// 27 | public ProjectEvaluationFinishedEventArgs(string message, params object[] messageArgs) 28 | : base(message, null, null, DateTime.UtcNow, messageArgs) 29 | { 30 | } 31 | 32 | /// 33 | /// Gets or sets the full path of the project that started evaluation. 34 | /// 35 | public string ProjectFile { get; set; } 36 | 37 | /// 38 | /// The result of profiling a project. 39 | /// 40 | /// 41 | /// Null if profiling is not turned on. 42 | /// 43 | public ProfilerResult? ProfilerResult { get; set; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/EvaluationIdProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | //----------------------------------------------------------------------- 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Diagnostics; 9 | using System.Threading; 10 | 11 | namespace Microsoft.Build.Framework.Profiler 12 | { 13 | /// 14 | /// Assigns unique evaluation ids. Thread safe. 15 | /// 16 | internal static class EvaluationIdProvider 17 | { 18 | private static readonly long ProcessId = Process.GetCurrentProcess().Id; 19 | private static long _sAssignedId = -1; 20 | 21 | /// 22 | /// Returns a unique evaluation id. 23 | /// 24 | /// 25 | /// The id is guaranteed to be unique across all running processes. 26 | /// Additionally, it is monotonically increasing for callers on the same process id. 27 | /// 28 | public static long GetNextId() 29 | { 30 | checked 31 | { 32 | long nextId = Interlocked.Increment(ref _sAssignedId); 33 | 34 | // Returns a unique number based on nextId (a unique number for this process) and the current process Id 35 | // Uses the Cantor pairing function (https://en.wikipedia.org/wiki/Pairing_function) to guarantee uniqueness 36 | return (((nextId + ProcessId) * (nextId + ProcessId + 1)) / 2) + ProcessId; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/TargetSkippedEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Build.Framework 7 | { 8 | /// 9 | /// Arguments for the target skipped event. 10 | /// 11 | #if FEATURE_BINARY_SERIALIZATION 12 | [Serializable] 13 | #endif 14 | public class TargetSkippedEventArgs : BuildMessageEventArgs 15 | { 16 | /// 17 | /// Initializes a new instance of the TargetSkippedEventArgs class. 18 | /// 19 | public TargetSkippedEventArgs() 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the TargetSkippedEventArgs class. 25 | /// 26 | public TargetSkippedEventArgs( 27 | string message, 28 | params object[] messageArgs) 29 | : base(null, null, null, 0, 0, 0, 0, message, null, null, MessageImportance.Low, DateTime.UtcNow, messageArgs) 30 | { 31 | } 32 | 33 | /// 34 | /// Gets or sets the name of the target being skipped. 35 | /// 36 | public string TargetName { get; set; } 37 | 38 | /// 39 | /// Gets or sets the parent target of the target being skipped. 40 | /// 41 | public string ParentTarget { get; set; } 42 | 43 | /// 44 | /// File where this target was declared. 45 | /// 46 | public string TargetFile { get; set; } 47 | 48 | /// 49 | /// Why the parent target built this target. 50 | /// 51 | public TargetBuiltReason BuildReason { get; set; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.0.0 4 | $(MsBuildPipeLoggerVersion) 5 | $(Version.Split('-')[0]) 6 | $(Version.Split('-')[0]) 7 | Dave Glick and contributors 8 | Dave Glick and contributors 9 | https://msbuildpipelogger.netlify.com/ 10 | https://github.com/daveaglick/MsBuildPipeLogger.git 11 | git 12 | MIT 13 | false 14 | true 15 | true 16 | snupkg 17 | true 18 | true 19 | true 20 | true 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/ProjectImportedEventArgs.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Build.Framework 7 | { 8 | /// 9 | /// Arguments for the project imported event. 10 | /// 11 | #if FEATURE_BINARY_SERIALIZATION 12 | [Serializable] 13 | #endif 14 | public class ProjectImportedEventArgs : BuildMessageEventArgs 15 | { 16 | /// 17 | /// Initializes a new instance of the ProjectImportedEventArgs class. 18 | /// 19 | public ProjectImportedEventArgs() 20 | { 21 | } 22 | 23 | /// 24 | /// Initializes a new instance of the ProjectImportedEventArgs class. 25 | /// 26 | public ProjectImportedEventArgs( 27 | int lineNumber, 28 | int columnNumber, 29 | string message, 30 | params object[] messageArgs) 31 | : base(null, null, null, lineNumber, columnNumber, 0, 0, message, null, null, MessageImportance.Low, DateTime.UtcNow, messageArgs) 32 | { 33 | } 34 | 35 | /// 36 | /// Gets or sets the original value of the Project attribute. 37 | /// 38 | public string UnexpandedProject { get; set; } 39 | 40 | /// 41 | /// Gets or sets the full path to the project file that was imported. Will be. null 42 | /// if the import statement was a glob and no files matched, or the condition (if any) evaluated 43 | /// to false. 44 | /// 45 | public string ImportedProjectFile { get; set; } 46 | 47 | /// 48 | /// Gets or sets if this import was ignored. Ignoring imports is controlled by. 49 | /// ProjectLoadSettings. This is only set when an import would have been included 50 | /// but was ignored to due being invalid. This does not include when a globbed import returned 51 | /// no matches, or a conditioned import that evaluated to false. 52 | /// 53 | public bool ImportIgnored { get; set; } 54 | } 55 | } -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/NamedPipeLoggerServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipes; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace MsBuildPipeLogger 7 | { 8 | /// 9 | /// A server for receiving MSBuild logging events over a named pipe. 10 | /// 11 | public class NamedPipeLoggerServer : PipeLoggerServer 12 | { 13 | private readonly InterlockedBool _connected = new InterlockedBool(false); 14 | 15 | public string PipeName { get; } 16 | 17 | /// 18 | /// Creates a named pipe server for receiving MSBuild logging events. 19 | /// 20 | /// The name of the pipe to create. 21 | public NamedPipeLoggerServer(string pipeName) 22 | : this(pipeName, CancellationToken.None) 23 | { 24 | } 25 | 26 | /// 27 | /// Creates a named pipe server for receiving MSBuild logging events. 28 | /// 29 | /// The name of the pipe to create. 30 | /// A that will cancel read operations if triggered. 31 | public NamedPipeLoggerServer(string pipeName, CancellationToken cancellationToken) 32 | : base(new NamedPipeServerStream(pipeName, PipeDirection.In), cancellationToken) 33 | { 34 | PipeName = pipeName; 35 | CancellationToken.Register(CancelConnectionWait); 36 | } 37 | 38 | protected override void Connect() 39 | { 40 | PipeStream.WaitForConnection(); 41 | _connected.Set(); 42 | } 43 | 44 | private void CancelConnectionWait() 45 | { 46 | if (!_connected.Set()) 47 | { 48 | // This is a crazy hack that stops the WaitForConnection by connecting a dummy client 49 | // We have to do it this way instead of checking for .IsConnected because if we connect 50 | // and then disconnect very quickly, .IsConnected will never observe as true and we'll lock 51 | using (NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", PipeName, PipeDirection.Out)) 52 | { 53 | pipeStream.Connect(); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/AnonymousPipeLoggerServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace MsBuildPipeLogger 8 | { 9 | /// 10 | /// A server for receiving MSBuild logging events over an anonymous pipe. 11 | /// 12 | public class AnonymousPipeLoggerServer : PipeLoggerServer 13 | { 14 | private string _clientHandle; 15 | 16 | /// 17 | /// Creates an anonymous pipe server for receiving MSBuild logging events. 18 | /// 19 | public AnonymousPipeLoggerServer() 20 | : this(CancellationToken.None) 21 | { 22 | } 23 | 24 | /// 25 | /// Creates an anonymous pipe server for receiving MSBuild logging events. 26 | /// 27 | /// A that will cancel read operations if triggered. 28 | public AnonymousPipeLoggerServer(CancellationToken cancellationToken) 29 | : base(new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable), cancellationToken) 30 | { 31 | } 32 | 33 | /// 34 | /// Gets the client handle as a string. The local copy of the handle will be automatically disposed on the first call to Read. 35 | /// 36 | /// The client handle as a string. 37 | public string GetClientHandle() => _clientHandle ?? (_clientHandle = PipeStream.GetClientHandleAsString()); 38 | 39 | protected override void Connect() 40 | { 41 | // Wait for the first write, there's a chicken-and-egg problem with the pipe handle 42 | // I can only dispose the local handle after the first pipe read, which blocks 43 | // But I can only catch the pipe disposal from cancellation after the handle has been disposed 44 | Buffer.FillFromStream(PipeStream, CancellationToken); 45 | 46 | // Dispose the client handle if we asked for one 47 | // If we don't do this we won't get notified when the stream closes, see https://stackoverflow.com/q/39682602/807064 48 | if (_clientHandle != null) 49 | { 50 | PipeStream.DisposeLocalCopyOfClientHandle(); 51 | _clientHandle = null; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Logger.Tests/ParameterParserFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.Build.Framework; 5 | using MsBuildPipeLogger; 6 | using NUnit.Framework; 7 | using Shouldly; 8 | 9 | namespace MsBuildPipeLogger.Logger.Tests 10 | { 11 | [TestFixture] 12 | public class ParameterParserFixture 13 | { 14 | [TestCase("1234")] 15 | [TestCase(" 1234 ")] 16 | [TestCase("handle=1234")] 17 | [TestCase("HANDLE=1234")] 18 | [TestCase(" handle = 1234 ")] 19 | [TestCase("\"1234\"")] 20 | [TestCase("\"handle=1234\"")] 21 | [TestCase("\" handle = 1234 \"")] 22 | public void GetsAnonymousPipe(string parameters) 23 | { 24 | // Given, When 25 | KeyValuePair[] parts = ParameterParser.ParseParameters(parameters); 26 | 27 | // Then 28 | parts.ShouldBe(new KeyValuePair[] 29 | { 30 | new KeyValuePair(ParameterParser.ParameterType.Handle, "1234") 31 | }); 32 | } 33 | 34 | [TestCase("name=Foo")] 35 | [TestCase("\"name=Foo\"")] 36 | [TestCase("NAME=Foo")] 37 | [TestCase(" name = Foo ")] 38 | public void GetsNamedPipe(string parameters) 39 | { 40 | // Given, When 41 | KeyValuePair[] parts = ParameterParser.ParseParameters(parameters); 42 | 43 | // Then 44 | parts.ShouldBe(new KeyValuePair[] 45 | { 46 | new KeyValuePair(ParameterParser.ParameterType.Name, "Foo") 47 | }); 48 | } 49 | 50 | [TestCase("name=Foo;server=Bar")] 51 | [TestCase("\"name=Foo;server=Bar\"")] 52 | [TestCase("NAME=Foo;SERVER=Bar")] 53 | [TestCase(" name = Foo ; server = Bar")] 54 | public void GetsNamedPipeWithServer(string parameters) 55 | { 56 | // Given, When 57 | KeyValuePair[] parts = ParameterParser.ParseParameters(parameters); 58 | 59 | // Then 60 | parts.ShouldBe( 61 | new KeyValuePair[] 62 | { 63 | new KeyValuePair(ParameterParser.ParameterType.Name, "Foo"), 64 | new KeyValuePair(ParameterParser.ParameterType.Server, "Bar") 65 | }, 66 | true); 67 | } 68 | 69 | [TestCase("")] 70 | [TestCase(" ")] 71 | [TestCase("123;foo;baz")] 72 | [TestCase("handle=1234;foo")] 73 | [TestCase("handle=1234;name=bar")] 74 | [TestCase("foo=bar")] 75 | [TestCase("123;name=bar")] 76 | [TestCase("server=foo")] 77 | public void ThrowsForInvalidParameters(string parameters) 78 | { 79 | // Given, When, Then 80 | Should.Throw(() => ParameterParser.GetPipeFromParameters(parameters)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/ParameterParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Build.Framework; 5 | 6 | namespace MsBuildPipeLogger 7 | { 8 | internal static class ParameterParser 9 | { 10 | internal enum ParameterType 11 | { 12 | Handle, 13 | Name, 14 | Server 15 | } 16 | 17 | public static IPipeWriter GetPipeFromParameters(string parameters) 18 | { 19 | KeyValuePair[] segments = ParseParameters(parameters); 20 | 21 | if (segments.Any(x => string.IsNullOrWhiteSpace(x.Value))) 22 | { 23 | throw new LoggerException($"Invalid or empty parameter value"); 24 | } 25 | 26 | // Anonymous pipe 27 | if (segments[0].Key == ParameterType.Handle) 28 | { 29 | if (segments.Length > 1) 30 | { 31 | throw new LoggerException("Handle can only be specified as a single parameter"); 32 | } 33 | return new AnonymousPipeWriter(segments[0].Value); 34 | } 35 | 36 | // Named pipe 37 | if (segments[0].Key == ParameterType.Name) 38 | { 39 | if (segments.Length == 1) 40 | { 41 | return new NamedPipeWriter(segments[0].Value); 42 | } 43 | if (segments[1].Key != ParameterType.Server) 44 | { 45 | throw new LoggerException("Only server and name can be specified for a named pipe"); 46 | } 47 | return new NamedPipeWriter(segments[1].Value, segments[0].Value); 48 | } 49 | if (segments.Length == 1 || segments[1].Key != ParameterType.Name) 50 | { 51 | throw new LoggerException("Pipe name must be specified for a named pipe"); 52 | } 53 | return new NamedPipeWriter(segments[0].Value, segments[1].Value); 54 | } 55 | 56 | internal static KeyValuePair[] ParseParameters(string parameters) 57 | { 58 | string[] segments = parameters.Split(';'); 59 | if (segments.Length < 1 || segments.Length > 2) 60 | { 61 | throw new LoggerException("Unexpected number of parameters"); 62 | } 63 | return segments.Select(x => ParseParameter(x)).ToArray(); 64 | } 65 | 66 | private static KeyValuePair ParseParameter(string parameter) 67 | { 68 | string[] parts = parameter.Trim().Trim('"').Split('='); 69 | 70 | // No parameter name specified 71 | if (parts.Length == 1) 72 | { 73 | return new KeyValuePair(ParameterType.Handle, parts[0].Trim()); 74 | } 75 | 76 | // Parse the parameter name 77 | if (!Enum.TryParse(parts[0].Trim(), true, out ParameterType parameterType)) 78 | { 79 | throw new LoggerException($"Invalid parameter name {parts[0]}"); 80 | } 81 | return new KeyValuePair(parameterType, string.Join("=", parts.Skip(1)).Trim()); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/PipeWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Threading; 6 | using Microsoft.Build.Framework; 7 | using Microsoft.Build.Logging; 8 | 9 | namespace MsBuildPipeLogger 10 | { 11 | public abstract class PipeWriter : IPipeWriter 12 | { 13 | private readonly BlockingCollection _queue = 14 | new BlockingCollection(new ConcurrentQueue()); 15 | 16 | private readonly AutoResetEvent _doneProcessing = new AutoResetEvent(false); 17 | 18 | private readonly PipeStream _pipeStream; 19 | private readonly BinaryWriter _binaryWriter; 20 | private readonly BuildEventArgsWriter _argsWriter; 21 | 22 | // Buffer writes through a memory stream since the args writer does a bunch of small writes 23 | private readonly MemoryStream _memoryStream = new MemoryStream(); 24 | 25 | protected PipeWriter(PipeStream pipeStream) 26 | { 27 | _pipeStream = pipeStream ?? throw new ArgumentNullException(nameof(pipeStream)); 28 | _binaryWriter = new BinaryWriter(_memoryStream); 29 | _argsWriter = new BuildEventArgsWriter(_binaryWriter); 30 | 31 | Thread writerThread = new Thread(() => 32 | { 33 | BuildEventArgs eventArgs; 34 | while ((eventArgs = TakeEventArgs()) != null) 35 | { 36 | // Reset the memory stream (but reuse the memory) 37 | _memoryStream.Seek(0, SeekOrigin.Begin); 38 | _memoryStream.SetLength(0); 39 | 40 | // Buffer to the memory stream 41 | _argsWriter.Write(eventArgs); 42 | _binaryWriter.Flush(); 43 | 44 | // ...then write that to the pipe 45 | _memoryStream.WriteTo(_pipeStream); 46 | _pipeStream.Flush(); 47 | } 48 | _doneProcessing.Set(); 49 | }) 50 | { 51 | IsBackground = true, 52 | }; 53 | writerThread.Start(); 54 | } 55 | 56 | private BuildEventArgs TakeEventArgs() 57 | { 58 | if (!_queue.IsCompleted) 59 | { 60 | try 61 | { 62 | return _queue.Take(); 63 | } 64 | catch (InvalidOperationException) 65 | { 66 | } 67 | } 68 | return null; 69 | } 70 | 71 | public void Dispose() 72 | { 73 | if (!_queue.IsAddingCompleted) 74 | { 75 | try 76 | { 77 | _queue.CompleteAdding(); 78 | _doneProcessing.WaitOne(); 79 | if (IsWindows) 80 | { 81 | _pipeStream.WaitForPipeDrain(); 82 | } 83 | _pipeStream.Dispose(); 84 | } 85 | catch 86 | { 87 | } 88 | } 89 | } 90 | 91 | public void Write(BuildEventArgs e) => _queue.Add(e); 92 | 93 | private static readonly bool IsWindows = Environment.OSVersion.Platform == PlatformID.Win32NT || 94 | Environment.OSVersion.Platform == PlatformID.Win32S || 95 | Environment.OSVersion.Platform == PlatformID.Win32Windows || 96 | Environment.OSVersion.Platform == PlatformID.WinCE || 97 | Environment.OSVersion.Platform == PlatformID.Xbox; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/ProfilerResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | //----------------------------------------------------------------------- 4 | // 5 | // The result profiling an evaluation. 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using System.Linq; 12 | 13 | namespace Microsoft.Build.Framework.Profiler 14 | { 15 | /// 16 | /// Result of profiling an evaluation. 17 | /// 18 | #if FEATURE_BINARY_SERIALIZATION 19 | [Serializable] 20 | #endif 21 | public struct ProfilerResult 22 | { 23 | /// 24 | public IReadOnlyDictionary ProfiledLocations { get; } 25 | 26 | /// 27 | public ProfilerResult(IDictionary profiledLocations) 28 | { 29 | ProfiledLocations = new ReadOnlyDictionary(profiledLocations); 30 | } 31 | 32 | /// 33 | public override bool Equals(object obj) 34 | { 35 | if (!(obj is ProfilerResult)) 36 | { 37 | return false; 38 | } 39 | 40 | ProfilerResult result = (ProfilerResult)obj; 41 | 42 | return (ProfiledLocations == result.ProfiledLocations) 43 | || (ProfiledLocations.Count == result.ProfiledLocations.Count 44 | && !ProfiledLocations.Except(result.ProfiledLocations).Any()); 45 | } 46 | 47 | /// 48 | public override int GetHashCode() 49 | { 50 | return ProfiledLocations.Keys.Aggregate(0, (acum, location) => acum + location.GetHashCode()); 51 | } 52 | } 53 | 54 | /// 55 | /// Result of timing the evaluation of a given element at a given location. 56 | /// 57 | #if FEATURE_BINARY_SERIALIZATION 58 | [Serializable] 59 | #endif 60 | public struct ProfiledLocation 61 | { 62 | /// 63 | public TimeSpan InclusiveTime { get; } 64 | 65 | /// 66 | public TimeSpan ExclusiveTime { get; } 67 | 68 | /// 69 | public int NumberOfHits { get; } 70 | 71 | /// 72 | public ProfiledLocation(TimeSpan inclusiveTime, TimeSpan exclusiveTime, int numberOfHits) 73 | { 74 | InclusiveTime = inclusiveTime; 75 | ExclusiveTime = exclusiveTime; 76 | NumberOfHits = numberOfHits; 77 | } 78 | 79 | /// 80 | public override bool Equals(object obj) 81 | { 82 | if (!(obj is ProfiledLocation)) 83 | { 84 | return false; 85 | } 86 | 87 | ProfiledLocation location = (ProfiledLocation)obj; 88 | return InclusiveTime.Equals(location.InclusiveTime) 89 | && ExclusiveTime.Equals(location.ExclusiveTime) 90 | && NumberOfHits == location.NumberOfHits; 91 | } 92 | 93 | /// 94 | public override int GetHashCode() 95 | { 96 | int hashCode = -2131368567; 97 | hashCode = (hashCode * -1521134295) + base.GetHashCode(); 98 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(InclusiveTime); 99 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ExclusiveTime); 100 | return (hashCode * -1521134295) + NumberOfHits.GetHashCode(); 101 | } 102 | 103 | /// 104 | public override string ToString() 105 | { 106 | return $"[{InclusiveTime} - {ExclusiveTime}]: {NumberOfHits} hits"; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /MsBuildPipeLogger.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CC6B5A3-CF3A-463D-8116-D02280B6F6A2}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{812B67C9-1716-4583-B2FF-25C1715D0C58}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsBuildPipeLogger.Logger", "src\MsBuildPipeLogger.Logger\MsBuildPipeLogger.Logger.csproj", "{F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsBuildPipeLogger.Server", "src\MsBuildPipeLogger.Server\MsBuildPipeLogger.Server.csproj", "{19286807-751C-43CE-891A-6E9B9CB7200C}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsBuildPipeLogger.Tests", "tests\MsBuildPipeLogger.Tests\MsBuildPipeLogger.Tests.csproj", "{F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1}" 15 | ProjectSection(ProjectDependencies) = postProject 16 | {2C6D9150-3CA3-4499-A08C-98A12F76757F} = {2C6D9150-3CA3-4499-A08C-98A12F76757F} 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsBuildPipeLogger.Tests.Client", "tests\MsBuildPipeLogger.Tests.Client\MsBuildPipeLogger.Tests.Client.csproj", "{2C6D9150-3CA3-4499-A08C-98A12F76757F}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MsBuildPipeLogger.Logger.Tests", "tests\MsBuildPipeLogger.Logger.Tests\MsBuildPipeLogger.Logger.Tests.csproj", "{65721127-A8F7-4DA0-9FAF-1D028C4A6900}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {19286807-751C-43CE-891A-6E9B9CB7200C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {19286807-751C-43CE-891A-6E9B9CB7200C}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {19286807-751C-43CE-891A-6E9B9CB7200C}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {19286807-751C-43CE-891A-6E9B9CB7200C}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {2C6D9150-3CA3-4499-A08C-98A12F76757F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {2C6D9150-3CA3-4499-A08C-98A12F76757F}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {2C6D9150-3CA3-4499-A08C-98A12F76757F}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {2C6D9150-3CA3-4499-A08C-98A12F76757F}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {65721127-A8F7-4DA0-9FAF-1D028C4A6900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {65721127-A8F7-4DA0-9FAF-1D028C4A6900}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {65721127-A8F7-4DA0-9FAF-1D028C4A6900}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {65721127-A8F7-4DA0-9FAF-1D028C4A6900}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(NestedProjects) = preSolution 54 | {F7DEEDA0-B1EB-4A4C-B102-367A73AB5F4A} = {5CC6B5A3-CF3A-463D-8116-D02280B6F6A2} 55 | {19286807-751C-43CE-891A-6E9B9CB7200C} = {5CC6B5A3-CF3A-463D-8116-D02280B6F6A2} 56 | {F70DD8B5-DAE2-4D6F-9BBC-A0E64378EAA1} = {812B67C9-1716-4583-B2FF-25C1715D0C58} 57 | {2C6D9150-3CA3-4499-A08C-98A12F76757F} = {812B67C9-1716-4583-B2FF-25C1715D0C58} 58 | {65721127-A8F7-4DA0-9FAF-1D028C4A6900} = {812B67C9-1716-4583-B2FF-25C1715D0C58} 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {A3E97697-C563-4DFF-9360-F7E48E991421} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/PipeLoggerServer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Text; 6 | using System.Threading; 7 | using Microsoft.Build.Framework; 8 | using Microsoft.Build.Logging; 9 | 10 | namespace MsBuildPipeLogger 11 | { 12 | /// 13 | /// Receives MSBuild logging events over a pipe. This is the base class for 14 | /// and . 15 | /// 16 | public abstract class PipeLoggerServer : EventArgsDispatcher, IPipeLoggerServer 17 | where TPipeStream : PipeStream 18 | { 19 | private readonly BinaryReader _binaryReader; 20 | private readonly BuildEventArgsReaderProxy _buildEventArgsReader; 21 | 22 | internal PipeBuffer Buffer { get; } = new PipeBuffer(); 23 | 24 | protected TPipeStream PipeStream { get; } 25 | 26 | protected CancellationToken CancellationToken { get; } 27 | 28 | /// 29 | /// Creates a server that receives MSBuild events over a specified pipe. 30 | /// 31 | /// The pipe to receive events from. 32 | protected PipeLoggerServer(TPipeStream pipeStream) 33 | : this(pipeStream, CancellationToken.None) 34 | { 35 | } 36 | 37 | /// 38 | /// Creates a server that receives MSBuild events over a specified pipe. 39 | /// 40 | /// The pipe to receive events from. 41 | /// A that will cancel read operations if triggered. 42 | protected PipeLoggerServer(TPipeStream pipeStream, CancellationToken cancellationToken) 43 | { 44 | PipeStream = pipeStream; 45 | _binaryReader = new BinaryReader(Buffer); 46 | _buildEventArgsReader = new BuildEventArgsReaderProxy(_binaryReader); 47 | CancellationToken = cancellationToken; 48 | 49 | Thread readerThread = new Thread(() => 50 | { 51 | try 52 | { 53 | Connect(); 54 | while (Buffer.FillFromStream(PipeStream, CancellationToken)) 55 | { 56 | } 57 | } 58 | catch (IOException) 59 | { 60 | // The client broke the stream so we're done 61 | } 62 | catch (ObjectDisposedException) 63 | { 64 | // The pipe was disposed 65 | } 66 | 67 | // Add a final 0 (BinaryLogRecordKind.EndOfFile) into the stream in case the BuildEventArgsReader is waiting for a read 68 | Buffer.Write(new byte[1] { 0 }, 0, 1); 69 | 70 | Buffer.CompleteAdding(); 71 | }) 72 | { 73 | IsBackground = true 74 | }; 75 | 76 | readerThread.Start(); 77 | } 78 | 79 | protected abstract void Connect(); 80 | 81 | /// 82 | public BuildEventArgs Read() 83 | { 84 | if (Buffer.IsCompleted) 85 | { 86 | return null; 87 | } 88 | 89 | try 90 | { 91 | BuildEventArgs args = _buildEventArgsReader.Read(); 92 | if (args != null) 93 | { 94 | Dispatch(args); 95 | return args; 96 | } 97 | } 98 | catch (EndOfStreamException) 99 | { 100 | // The stream may have been closed or otherwise stopped 101 | } 102 | 103 | return null; 104 | } 105 | 106 | /// 107 | public void ReadAll() 108 | { 109 | BuildEventArgs args = Read(); 110 | while (args != null) 111 | { 112 | if (args is BuildFinishedEventArgs) 113 | { 114 | return; 115 | } 116 | args = Read(); 117 | } 118 | } 119 | 120 | /// 121 | public void Dispose() 122 | { 123 | _binaryReader.Dispose(); 124 | Buffer.Dispose(); 125 | PipeStream.Dispose(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | [Bb]uild/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # .NET Core 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | **/Properties/launchSettings.json 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # Visual Studio code coverage results 118 | *.coverage 119 | *.coveragexml 120 | 121 | # NCrunch 122 | _NCrunch_* 123 | .*crunch*.local.xml 124 | nCrunchTemp_* 125 | 126 | # MightyMoose 127 | *.mm.* 128 | AutoTest.Net/ 129 | 130 | # Web workbench (sass) 131 | .sass-cache/ 132 | 133 | # Installshield output folder 134 | [Ee]xpress/ 135 | 136 | # DocProject is a documentation generator add-in 137 | DocProject/buildhelp/ 138 | DocProject/Help/*.HxT 139 | DocProject/Help/*.HxC 140 | DocProject/Help/*.hhc 141 | DocProject/Help/*.hhk 142 | DocProject/Help/*.hhp 143 | DocProject/Help/Html2 144 | DocProject/Help/html 145 | 146 | # Click-Once directory 147 | publish/ 148 | 149 | # Publish Web Output 150 | *.[Pp]ublish.xml 151 | *.azurePubxml 152 | # TODO: Comment the next line if you want to checkin your web deploy settings 153 | # but database connection strings (with potential passwords) will be unencrypted 154 | *.pubxml 155 | *.publishproj 156 | 157 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 158 | # checkin your Azure Web App publish settings, but sensitive information contained 159 | # in these scripts will be unencrypted 160 | PublishScripts/ 161 | 162 | # NuGet Packages 163 | *.nupkg 164 | # The packages folder can be ignored because of Package Restore 165 | **/packages/* 166 | # except build/, which is used as an MSBuild target. 167 | !**/packages/build/ 168 | # Uncomment if necessary however generally it will be regenerated when needed 169 | #!**/packages/repositories.config 170 | # NuGet v3's project.json files produces more ignorable files 171 | *.nuget.props 172 | *.nuget.targets 173 | 174 | # Microsoft Azure Build Output 175 | csx/ 176 | *.build.csdef 177 | 178 | # Microsoft Azure Emulator 179 | ecf/ 180 | rcf/ 181 | 182 | # Windows Store app package directories and files 183 | AppPackages/ 184 | BundleArtifacts/ 185 | Package.StoreAssociation.xml 186 | _pkginfo.txt 187 | 188 | # Visual Studio cache files 189 | # files ending in .cache can be ignored 190 | *.[Cc]ache 191 | # but keep track of directories ending in .cache 192 | !*.[Cc]ache/ 193 | 194 | # Others 195 | ClientBin/ 196 | ~$* 197 | *~ 198 | *.dbmdl 199 | *.dbproj.schemaview 200 | *.jfm 201 | *.pfx 202 | *.publishsettings 203 | orleans.codegen.cs 204 | .idea 205 | .vscode 206 | 207 | # Since there are multiple workflows, uncomment next line to ignore bower_components 208 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 209 | #bower_components/ 210 | 211 | # RIA/Silverlight projects 212 | Generated_Code/ 213 | 214 | # Backup & report files from converting an old project file 215 | # to a newer Visual Studio version. Backup files are not needed, 216 | # because we have git ;-) 217 | _UpgradeReport_Files/ 218 | Backup*/ 219 | UpgradeLog*.XML 220 | UpgradeLog*.htm 221 | 222 | # SQL Server files 223 | *.mdf 224 | *.ldf 225 | *.ndf 226 | 227 | # Business Intelligence projects 228 | *.rdl.data 229 | *.bim.layout 230 | *.bim_*.settings 231 | 232 | # Microsoft Fakes 233 | FakesAssemblies/ 234 | 235 | # GhostDoc plugin setting file 236 | *.GhostDoc.xml 237 | 238 | # Node.js Tools for Visual Studio 239 | .ntvs_analysis.dat 240 | node_modules/ 241 | 242 | # Typescript v1 declaration files 243 | typings/ 244 | 245 | # Visual Studio 6 build log 246 | *.plg 247 | 248 | # Visual Studio 6 workspace options file 249 | *.opt 250 | 251 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 252 | *.vbw 253 | 254 | # Visual Studio LightSwitch build output 255 | **/*.HTMLClient/GeneratedArtifacts 256 | **/*.DesktopClient/GeneratedArtifacts 257 | **/*.DesktopClient/ModelManifest.xml 258 | **/*.Server/GeneratedArtifacts 259 | **/*.Server/ModelManifest.xml 260 | _Pvt_Extensions 261 | 262 | # Paket dependency manager 263 | .paket/paket.exe 264 | paket-files/ 265 | 266 | # FAKE - F# Make 267 | .fake/ 268 | 269 | # JetBrains Rider 270 | .idea/ 271 | *.sln.iml 272 | 273 | # CodeRush 274 | .cr/ 275 | 276 | # Python Tools for Visual Studio (PTVS) 277 | __pycache__/ 278 | *.pyc 279 | 280 | # Cake - Uncomment if you are using it 281 | [tT]ools/ 282 | 283 | # Docs 284 | docs/output/ 285 | docs/config.wyam.packages.xml 286 | docs/config.wyam.hash 287 | docs/config.wyam.dll 288 | 289 | # Telerik's JustMock configuration file 290 | *.jmconfig 291 | 292 | # BizTalk build output 293 | *.btp.cs 294 | *.btm.cs 295 | *.odx.cs 296 | *.xsd.cs 297 | 298 | # Ignore repos cloned in unit tests 299 | tests/repos 300 | 301 | marvin/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A logger for MSBuild that sends event data over anonymous or named pipes. 2 | 3 | **NuGet** 4 | * [MsBuildPipeLogger.Logger](https://www.nuget.org/packages/MsBuildPipeLogger.Logger/) 5 | * [MsBuildPipeLogger.Server](https://www.nuget.org/packages/MsBuildPipeLogger.Server/) 6 | 7 | **MyGet** 8 | * [MsBuildPipeLogger.Logger](https://www.myget.org/feed/msbuildpipelogger/package/nuget/MsBuildPipeLogger.Logger) 9 | * [MsBuildPipeLogger.Server](https://www.myget.org/feed/msbuildpipelogger/package/nuget/MsBuildPipeLogger.Server) 10 | 11 | **GitHub** 12 | * [MsBuildPipeLogger](https://github.com/daveaglick/MsBuildPipeLogger) 13 | 14 | **Donations** 15 | 16 | Buy Me A Coffee 17 | 18 | --- 19 | 20 | ## Say what? 21 | 22 | As a general purpose build tool, MSBuild is really powerful. However, it can be hard to figure out what's going on under the hood. Thankfully, MSBuild provides a nice logging API that consists of sending a sequence of events to one or more attached loggers. This allows the logger to track (almost) everything that happens during the build. 23 | 24 | This project is heavily based on the amazing work done by @KirillOsenkov on [MSBuildStructuredLog](https://github.com/KirillOsenkov/MSBuildStructuredLog), which is a custom logger (and log viewer application) that serializes every logging event to disk for post-build viewing, analysis, and playback. While that logger (which is now built into MSBuild itself by the way) serializes logging events to disk, this logger serializes them across either a named or anonymous pipe to a server that receives the serialized logging events, deserializes them, and issues callbacks using the MSBuild logging API for each event received. It uses the same serialization format as [MSBuildStructuredLog](https://github.com/KirillOsenkov/MSBuildStructuredLog) and lets you write code that responds to MSBuild logging events in much the same way you would if you were writing a custom logger, except your code can respond to events from another process or even another system. 25 | 26 | ## Why would I want to do that? 27 | 28 | I wrote this to address a need I had in [Buildalyzer](https://github.com/daveaglick/Buildalyzer) to access MSBuild properties from out-of-process MSBuild instances. In that use case, you can use `MsBuildPipeLogger` with a forked MSBuild instance to send logging events back to your original process where they can be operated on. I'm sure there are other use cases such as remote log aggregation. Drop me a line if you find this logger helpful and let me know what you're up to. 29 | 30 | ## How do I use it? 31 | 32 | There are two libraries: 33 | * [MsBuildPipeLogger.Logger](https://www.nuget.org/packages/MsBuildPipeLogger.Logger/) - the logger that's given to MSBuild. 34 | * [MsBuildPipeLogger.Server](https://www.nuget.org/packages/MsBuildPipeLogger.Server/) - the server that receives logging events from the pipe and raises them to your code. 35 | 36 | Usage consists of creating a server to receive logging events and then telling MSBuild to use the `MsBuildPipeLogger`. It's slightly different depending on if you want to use an [anonymous pipe](https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-use-anonymous-pipes-for-local-interprocess-communication) or a [named pipe](https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-use-named-pipes-for-network-interprocess-communication). 37 | 38 | ### Anonymous pipe 39 | 40 | ```csharp 41 | // Create the server 42 | AnonymousPipeLoggerServer server = new AnonymousPipeLoggerServer(); 43 | 44 | // Get the pipe handle 45 | string pipeHandle = server.GetClientHandle(); 46 | 47 | // Register an event handler 48 | server.AnyEventRaised += (s, e) => Console.WriteLine(e.Message); 49 | 50 | // Run the MSBuild process, passing it the logger and pipe handle 51 | // Note you can also call MSBuild straight from the CLI, you just need to know the pipe handle to pass it to the logger 52 | Process process = new Process(); 53 | process.StartInfo.FileName = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin"; 54 | process.StartInfo.Arguments = $"/noconlog /logger:MsBuildPipeLogger,\"C:\Path\To\MsBuildPipeLogger.Logger.dll\";{pipeHandle}"; 55 | // ...other process settings like working directory 56 | process.Start(); 57 | 58 | // Wait for the process to exit 59 | while (!process.HasExited) 60 | { 61 | // Read a single logger event (which will trigger the handler above) 62 | server.Read(); 63 | } 64 | process.Close(); 65 | ``` 66 | 67 | ### Named pipe 68 | 69 | ```csharp 70 | // Create the server with a pipe name 71 | NamedPipeLoggerServer server = new NamedPipeLoggerServer("Mario"); 72 | 73 | // Register an event handler 74 | server.AnyEventRaised += (s, e) => Console.WriteLine(e.Message); 75 | 76 | // Run the MSBuild process, passing it the logger, pipe name, and optionally the server 77 | // Note you can also call MSBuild straight from the CLI, you just need to know the pipe handle to pass it to the logger 78 | Process process = new Process(); 79 | process.StartInfo.FileName = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin"; 80 | process.StartInfo.Arguments = $"/noconlog /logger:MsBuildPipeLogger.Logger,\"C:\Path\To\MsBuildPipeLogger.Logger.dll\";name=Mario;server=MyServerName"; 81 | // ...other process settings like working directory 82 | process.Start(); 83 | 84 | // Wait for the process to exit 85 | while (!process.HasExited) 86 | { 87 | // Read a single logger event (which will trigger the handler above) 88 | server.Read(); 89 | } 90 | process.Close(); 91 | ``` 92 | 93 | ### Logger parameters 94 | 95 | [The syntax](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-command-line-reference) for specifying a logger to MSBuild is: 96 | 97 | ``` 98 | [LoggerClass,]LoggerAssembly[;LoggerParameters] 99 | ``` 100 | 101 | The `MsBuildPipeLogger.Logger` recognizes these parameters, separated by a `;` and distinguished as a name and value by `=`: 102 | * A single string is interpreted as an anonymous pipe handle: `MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;1234` 103 | * `handle` indicates the anonymous pipe handle to connect to: `MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;handle=1234` 104 | * `name` indicates the named pipe name: `MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;name=Mario` 105 | * `server` indicates the named pipe server (assumed to be a local pipe if omitted): `MsBuildPipeLogger.Logger,MsBuildPipeLogger.Logger.dll;name=Mario;server=MyServerName` 106 | 107 | ### A note on concurrency 108 | 109 | The `AnonymousPipeLoggerServer.Read()` and `NamedPipeLoggerServer.Read()` methods both block while waiting for events. If you need to support concurrency or cancellation, pass a `CancellationToken` to the server constructors and then cancel it during read operations. 110 | 111 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Server/PipeBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace MsBuildPipeLogger 9 | { 10 | internal class PipeBuffer : Stream 11 | { 12 | private const int BufferSize = 8192; 13 | 14 | private readonly ConcurrentBag _pool = new ConcurrentBag(); 15 | 16 | private readonly BlockingCollection _queue = 17 | new BlockingCollection(new ConcurrentQueue()); 18 | 19 | private Buffer _current; 20 | 21 | public void CompleteAdding() => _queue.CompleteAdding(); 22 | 23 | public bool IsCompleted => _queue.IsCompleted; 24 | 25 | public bool FillFromStream(Stream stream, CancellationToken cancellationToken) 26 | { 27 | if (!_pool.TryTake(out Buffer buffer)) 28 | { 29 | buffer = new Buffer(); 30 | } 31 | if (buffer.FillFromStream(stream, cancellationToken) == 0) 32 | { 33 | // Didn't write anything, return it to the pool 34 | _pool.Add(buffer); 35 | return false; 36 | } 37 | _queue.Add(buffer); 38 | return true; 39 | } 40 | 41 | public override void Write(byte[] buffer, int offset, int count) => 42 | _queue.Add(new Buffer(buffer, offset, count)); 43 | 44 | public override int Read(byte[] buffer, int offset, int count) 45 | { 46 | int read = 0; 47 | while (read < count) 48 | { 49 | // Ensure a buffer is available 50 | if (TakeBuffer()) 51 | { 52 | // Get as much as we can from the current buffer 53 | read += _current.Read(buffer, offset + read, count - read); 54 | if (_current.Count == 0) 55 | { 56 | // Used up this buffer, return to the pool if it's a pool buffer 57 | if (_current.FromPool) 58 | { 59 | _pool.Add(_current); 60 | } 61 | _current = null; 62 | } 63 | } 64 | else 65 | { 66 | break; 67 | } 68 | } 69 | return read; 70 | } 71 | 72 | private bool TakeBuffer() 73 | { 74 | if (_current == null) 75 | { 76 | // Take() can throw when marked as complete from another thread 77 | // https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.blockingcollection-1.take?view=netcore-3.1 78 | try 79 | { 80 | _current = _queue.Take(); 81 | } 82 | catch (ObjectDisposedException) 83 | { 84 | return false; 85 | } 86 | catch (OperationCanceledException) 87 | { 88 | return false; 89 | } 90 | catch (InvalidOperationException) 91 | { 92 | return false; 93 | } 94 | } 95 | return true; 96 | } 97 | 98 | private class Buffer 99 | { 100 | private readonly byte[] _buffer; 101 | 102 | private int _offset; 103 | 104 | public int Count { get; private set; } 105 | 106 | public bool FromPool { get; } 107 | 108 | public Buffer() 109 | { 110 | _buffer = new byte[BufferSize]; 111 | FromPool = true; 112 | } 113 | 114 | public Buffer(byte[] buffer, int offset, int count) 115 | { 116 | _buffer = buffer; 117 | _offset = offset; 118 | Count = count; 119 | } 120 | 121 | public int FillFromStream(Stream stream, CancellationToken cancellationToken) 122 | { 123 | if (stream is AnonymousPipeServerStream || stream is AnonymousPipeClientStream) 124 | { 125 | // We can't use ReadAsync with Anonymous PipeStream 126 | // https://github.com/dotnet/runtime/issues/23638 127 | // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations 128 | // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes 129 | _offset = 0; 130 | Count = cancellationToken.IsCancellationRequested ? 0 : stream.Read(_buffer, _offset, BufferSize); 131 | } 132 | else 133 | { 134 | Count = cancellationToken.Try( 135 | () => 136 | { 137 | _offset = 0; 138 | Task readTask = stream.ReadAsync(_buffer, _offset, BufferSize, cancellationToken); 139 | #pragma warning disable VSTHRD002 // Synchronously waiting on tasks or awaiters may cause deadlocks. Use await or JoinableTaskFactory.Run instead. 140 | readTask.Wait(cancellationToken); 141 | return readTask.Status == TaskStatus.Canceled ? 0 : readTask.Result; 142 | #pragma warning restore VSTHRD002 143 | }, 144 | () => 0); 145 | } 146 | return Count; 147 | } 148 | 149 | public int Read(byte[] buffer, int offset, int count) 150 | { 151 | int available = count > Count ? Count : count; 152 | Array.Copy(_buffer, _offset, buffer, offset, available); 153 | _offset += available; 154 | Count -= available; 155 | return available; 156 | } 157 | } 158 | 159 | // Not implemented 160 | 161 | public override bool CanRead => true; 162 | 163 | public override bool CanSeek => false; 164 | 165 | public override bool CanWrite => false; 166 | 167 | public override long Length => throw new NotImplementedException(); 168 | 169 | public override long Position 170 | { 171 | get => throw new NotImplementedException(); 172 | set => throw new NotImplementedException(); 173 | } 174 | 175 | public override void Flush() => throw new NotImplementedException(); 176 | 177 | public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(); 178 | 179 | public override void SetLength(long value) => throw new NotImplementedException(); 180 | } 181 | } -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/Reflector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Microsoft.Build.Framework; 4 | using MsBuildPipeLogger; 5 | 6 | namespace Microsoft.Build.Logging 7 | { 8 | /// 9 | /// This class accesses the properties on event args that were added in MSBuild 15.3. 10 | /// As the StructuredLogger.dll references MSBuild 14.0 and gracefully degrades 11 | /// when used with MSBuild 14.0, we have to use Reflection to dynamically 12 | /// retrieve the values if present and gracefully degrade if we're running with 13 | /// an earlier MSBuild. 14 | /// 15 | internal class Reflector 16 | { 17 | private static Func projectFileFromEvaluationStarted; 18 | private static Func projectFileFromEvaluationFinished; 19 | private static Func unexpandedProjectGetter; 20 | private static Func importedProjectFileGetter; 21 | private static Func evaluationIdGetter; 22 | private static Func targetNameFromTargetSkipped; 23 | private static Func targetFileFromTargetSkipped; 24 | private static Func parentTargetFromTargetSkipped; 25 | private static Func buildReasonFromTargetSkipped; 26 | 27 | internal static string GetProjectFileFromEvaluationStarted(BuildEventArgs e) 28 | { 29 | if (projectFileFromEvaluationStarted == null) 30 | { 31 | Type type = e.GetType(); 32 | MethodInfo method = type.GetProperty("ProjectFile").GetGetMethod(); 33 | projectFileFromEvaluationStarted = b => method.Invoke(b, null) as string; 34 | } 35 | 36 | return projectFileFromEvaluationStarted(e); 37 | } 38 | 39 | internal static string GetProjectFileFromEvaluationFinished(BuildEventArgs e) 40 | { 41 | if (projectFileFromEvaluationFinished == null) 42 | { 43 | Type type = e.GetType(); 44 | MethodInfo method = type.GetProperty("ProjectFile").GetGetMethod(); 45 | projectFileFromEvaluationFinished = b => method.Invoke(b, null) as string; 46 | } 47 | 48 | return projectFileFromEvaluationFinished(e); 49 | } 50 | 51 | internal static string GetTargetNameFromTargetSkipped(BuildEventArgs e) 52 | { 53 | if (targetNameFromTargetSkipped == null) 54 | { 55 | Type type = e.GetType(); 56 | MethodInfo method = type.GetProperty("TargetName").GetGetMethod(); 57 | targetNameFromTargetSkipped = b => method.Invoke(b, null) as string; 58 | } 59 | 60 | return targetNameFromTargetSkipped(e); 61 | } 62 | 63 | internal static string GetTargetFileFromTargetSkipped(BuildEventArgs e) 64 | { 65 | if (targetFileFromTargetSkipped == null) 66 | { 67 | Type type = e.GetType(); 68 | MethodInfo method = type.GetProperty("TargetFile").GetGetMethod(); 69 | targetFileFromTargetSkipped = b => method.Invoke(b, null) as string; 70 | } 71 | 72 | return targetFileFromTargetSkipped(e); 73 | } 74 | 75 | internal static string GetParentTargetFromTargetSkipped(BuildEventArgs e) 76 | { 77 | if (parentTargetFromTargetSkipped == null) 78 | { 79 | Type type = e.GetType(); 80 | MethodInfo method = type.GetProperty("ParentTarget").GetGetMethod(); 81 | parentTargetFromTargetSkipped = b => method.Invoke(b, null) as string; 82 | } 83 | 84 | return parentTargetFromTargetSkipped(e); 85 | } 86 | 87 | internal static TargetBuiltReason GetBuildReasonFromTargetStarted(BuildEventArgs e) 88 | { 89 | Type type = e.GetType(); 90 | PropertyInfo property = type.GetProperty("BuildReason"); 91 | if (property == null) 92 | { 93 | return TargetBuiltReason.None; 94 | } 95 | 96 | MethodInfo method = property.GetGetMethod(); 97 | return (TargetBuiltReason)method.Invoke(e, null); 98 | } 99 | 100 | internal static TargetBuiltReason GetBuildReasonFromTargetSkipped(BuildEventArgs e) 101 | { 102 | if (buildReasonFromTargetSkipped == null) 103 | { 104 | Type type = e.GetType(); 105 | PropertyInfo property = type.GetProperty("BuildReason"); 106 | if (property == null) 107 | { 108 | return TargetBuiltReason.None; 109 | } 110 | 111 | MethodInfo method = property.GetGetMethod(); 112 | buildReasonFromTargetSkipped = b => (TargetBuiltReason)method.Invoke(b, null); 113 | } 114 | 115 | return buildReasonFromTargetSkipped(e); 116 | } 117 | 118 | internal static string GetUnexpandedProject(BuildEventArgs e) 119 | { 120 | if (unexpandedProjectGetter == null) 121 | { 122 | Type type = e.GetType(); 123 | MethodInfo method = type.GetProperty("UnexpandedProject").GetGetMethod(); 124 | unexpandedProjectGetter = b => method.Invoke(b, null) as string; 125 | } 126 | 127 | return unexpandedProjectGetter(e); 128 | } 129 | 130 | internal static string GetImportedProjectFile(BuildEventArgs e) 131 | { 132 | if (importedProjectFileGetter == null) 133 | { 134 | Type type = e.GetType(); 135 | MethodInfo method = type.GetProperty("ImportedProjectFile").GetGetMethod(); 136 | importedProjectFileGetter = b => method.Invoke(b, null) as string; 137 | } 138 | 139 | return importedProjectFileGetter(e); 140 | } 141 | 142 | internal static int GetEvaluationId(BuildEventContext buildEventContext) 143 | { 144 | if (buildEventContext == null) 145 | { 146 | return -1; 147 | } 148 | 149 | if (evaluationIdGetter == null) 150 | { 151 | Type type = buildEventContext.GetType(); 152 | FieldInfo field = type.GetField("_evaluationId"/*, BindingFlags.Instance | BindingFlags.NonPublic*/); 153 | if (field != null) 154 | { 155 | evaluationIdGetter = b => (int)field.GetValue(b); 156 | } 157 | else 158 | { 159 | evaluationIdGetter = b => b.ProjectContextId <= 0 ? -b.ProjectContextId : -1; 160 | } 161 | } 162 | 163 | return evaluationIdGetter(buildEventContext); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/MsBuildPipeLogger.Tests/IntegrationFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using Microsoft.Build.Framework; 9 | using Microsoft.Build.Logging; 10 | using NUnit.Framework; 11 | using Shouldly; 12 | 13 | namespace MsBuildPipeLogger.Tests 14 | { 15 | [TestFixture] 16 | [NonParallelizable] 17 | public class IntegrationFixture 18 | { 19 | private static readonly int[] MessageCounts = { 0, 1, 100000 }; 20 | 21 | [Test] 22 | public void SerializesData([ValueSource(nameof(MessageCounts))] int messageCount) 23 | { 24 | // Given 25 | Stopwatch sw = new Stopwatch(); 26 | MemoryStream memory = new MemoryStream(); 27 | BinaryWriter binaryWriter = new BinaryWriter(memory); 28 | BuildEventArgsWriter writer = new BuildEventArgsWriter(binaryWriter); 29 | BinaryReader binaryReader = new BinaryReader(memory); 30 | BuildEventArgsReaderProxy reader = new BuildEventArgsReaderProxy(binaryReader); 31 | List eventArgs = new List(); 32 | 33 | // When 34 | sw.Start(); 35 | writer.Write(new BuildStartedEventArgs($"Testing", "help")); 36 | for (int m = 0; m < messageCount; m++) 37 | { 38 | writer.Write(new BuildMessageEventArgs($"Testing {m}", "help", "sender", MessageImportance.Normal)); 39 | } 40 | sw.Stop(); 41 | TestContext.Out.WriteLine($"Serialization completed in {sw.ElapsedMilliseconds} ms"); 42 | 43 | memory.Position = 0; 44 | sw.Restart(); 45 | BuildEventArgs e; 46 | while ((e = reader.Read()) != null) 47 | { 48 | eventArgs.Add(e); 49 | if (memory.Position >= memory.Length) 50 | { 51 | break; 52 | } 53 | } 54 | sw.Stop(); 55 | TestContext.Out.WriteLine($"Deserialization completed in {sw.ElapsedMilliseconds} ms"); 56 | 57 | // Then 58 | eventArgs.Count.ShouldBe(messageCount + 1); 59 | eventArgs[0].ShouldBeOfType(); 60 | eventArgs[0].Message.ShouldBe("Testing"); 61 | int c = 0; 62 | foreach (BuildEventArgs eventArg in eventArgs.Skip(1)) 63 | { 64 | eventArg.ShouldBeOfType(); 65 | eventArg.Message.ShouldBe($"Testing {c++}"); 66 | } 67 | } 68 | 69 | [Test] 70 | public void NamedPipeSupportsCancellation() 71 | { 72 | // Given 73 | BuildEventArgs buildEvent = null; 74 | using (CancellationTokenSource tokenSource = new CancellationTokenSource()) 75 | { 76 | using (NamedPipeLoggerServer server = new NamedPipeLoggerServer("Foo", tokenSource.Token)) 77 | { 78 | // When 79 | tokenSource.CancelAfter(1000); // The call to .Read() below will block so need to set a timeout for cancellation 80 | buildEvent = server.Read(); 81 | } 82 | } 83 | 84 | // Then 85 | buildEvent.ShouldBeNull(); 86 | } 87 | 88 | [Test] 89 | public void SendsDataOverAnonymousPipe([ValueSource(nameof(MessageCounts))] int messageCount) 90 | { 91 | // Given 92 | List eventArgs = new List(); 93 | int exitCode; 94 | using (AnonymousPipeLoggerServer server = new AnonymousPipeLoggerServer()) 95 | { 96 | server.AnyEventRaised += (s, e) => eventArgs.Add(e); 97 | 98 | // When 99 | exitCode = RunClientProcess(server, server.GetClientHandle(), messageCount); 100 | } 101 | 102 | // Then 103 | exitCode.ShouldBe(0); 104 | eventArgs.Count.ShouldBe(messageCount + 1); 105 | eventArgs[0].ShouldBeOfType(); 106 | eventArgs[0].Message.ShouldBe("Testing"); 107 | int c = 0; 108 | foreach (BuildEventArgs eventArg in eventArgs.Skip(1)) 109 | { 110 | eventArg.ShouldBeOfType(); 111 | eventArg.Message.ShouldBe($"Testing {c++}"); 112 | } 113 | } 114 | 115 | [Test] 116 | public void SendsDataOverNamedPipe([ValueSource(nameof(MessageCounts))] int messageCount) 117 | { 118 | // Given 119 | List eventArgs = new List(); 120 | int exitCode; 121 | using (NamedPipeLoggerServer server = new NamedPipeLoggerServer("foo")) 122 | { 123 | server.AnyEventRaised += (s, e) => eventArgs.Add(e); 124 | 125 | // When 126 | exitCode = RunClientProcess(server, "name=foo", messageCount); 127 | } 128 | 129 | // Then 130 | exitCode.ShouldBe(0); 131 | eventArgs.Count.ShouldBe(messageCount + 1); 132 | eventArgs[0].ShouldBeOfType(); 133 | eventArgs[0].Message.ShouldBe("Testing"); 134 | int c = 0; 135 | foreach (BuildEventArgs eventArg in eventArgs.Skip(1)) 136 | { 137 | eventArg.ShouldBeOfType(); 138 | eventArg.Message.ShouldBe($"Testing {c++}"); 139 | } 140 | } 141 | 142 | private int RunClientProcess(IPipeLoggerServer server, string arguments, int messages) 143 | { 144 | Process process = new Process(); 145 | int exitCode; 146 | try 147 | { 148 | process.StartInfo.FileName = "dotnet"; 149 | process.StartInfo.Arguments = $"MsBuildPipeLogger.Tests.Client.dll {arguments} {messages}"; 150 | process.StartInfo.WorkingDirectory = Path.GetDirectoryName(typeof(IntegrationFixture).Assembly.Location).Replace("MsBuildPipeLogger.Tests", "MsBuildPipeLogger.Tests.Client"); 151 | process.StartInfo.CreateNoWindow = true; 152 | process.StartInfo.UseShellExecute = false; 153 | 154 | process.StartInfo.RedirectStandardOutput = true; 155 | process.StartInfo.RedirectStandardError = true; 156 | process.OutputDataReceived += (s, e) => TestContext.WriteLine(e.Data); 157 | process.ErrorDataReceived += (s, e) => TestContext.WriteLine(e.Data); 158 | 159 | process.Start(); 160 | TestContext.WriteLine($"Started process {process.Id}"); 161 | process.BeginOutputReadLine(); 162 | 163 | server.ReadAll(); 164 | } 165 | catch (Exception ex) 166 | { 167 | TestContext.WriteLine($"Process error: {ex}"); 168 | } 169 | finally 170 | { 171 | process.WaitForExit(); 172 | exitCode = process.ExitCode; 173 | TestContext.WriteLine($"Exited process {process.Id} with code {exitCode}"); 174 | process.Close(); 175 | } 176 | return exitCode; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/EvaluationLocation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | //----------------------------------------------------------------------- 4 | // 5 | // Location for different elements tracked by the evaluation profiler. 6 | //----------------------------------------------------------------------- 7 | 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace Microsoft.Build.Framework.Profiler 12 | { 13 | /// 14 | /// Evaluation main phases used by the profiler. 15 | /// 16 | /// 17 | /// Order matters since the profiler pretty printer orders profiled items from top to bottom using 18 | /// the pass they belong to. 19 | /// 20 | public enum EvaluationPass : byte 21 | { 22 | TotalEvaluation = 0, 23 | TotalGlobbing = 1, 24 | InitialProperties = 2, 25 | Properties = 3, 26 | ItemDefinitionGroups = 4, 27 | Items = 5, 28 | LazyItems = 6, 29 | UsingTasks = 7, 30 | Targets = 8 31 | } 32 | 33 | /// 34 | /// The kind of the evaluated location being tracked. 35 | /// 36 | public enum EvaluationLocationKind : byte 37 | { 38 | Element = 0, 39 | Condition = 1, 40 | Glob = 2 41 | } 42 | 43 | /// 44 | /// Represents a location for different evaluation elements tracked by the EvaluationProfiler. 45 | /// 46 | #if FEATURE_BINARY_SERIALIZATION 47 | [Serializable] 48 | #endif 49 | public struct EvaluationLocation 50 | { 51 | /// 52 | /// Default descriptions for locations that are used in case a description is not provided. 53 | /// 54 | private static readonly Dictionary PassDefaultDescription = 55 | new Dictionary 56 | { 57 | { EvaluationPass.TotalEvaluation, "Total evaluation" }, 58 | { EvaluationPass.TotalGlobbing, "Total evaluation for globbing" }, 59 | { EvaluationPass.InitialProperties, "Initial properties (pass 0)" }, 60 | { EvaluationPass.Properties, "Properties (pass 1)" }, 61 | { EvaluationPass.ItemDefinitionGroups, "Item definition groups (pass 2)" }, 62 | { EvaluationPass.Items, "Items (pass 3)" }, 63 | { EvaluationPass.LazyItems, "Lazy items (pass 3.1)" }, 64 | { EvaluationPass.UsingTasks, "Using tasks (pass 4)" }, 65 | { EvaluationPass.Targets, "Targets (pass 5)" }, 66 | }; 67 | 68 | /// 69 | public long Id { get; } 70 | 71 | /// 72 | public long? ParentId { get; } 73 | 74 | /// 75 | public EvaluationPass EvaluationPass { get; } 76 | 77 | /// 78 | public string EvaluationPassDescription { get; } 79 | 80 | /// 81 | public string File { get; } 82 | 83 | /// 84 | public int? Line { get; } 85 | 86 | /// 87 | public string ElementName { get; } 88 | 89 | /// 90 | public string ElementDescription { get; } 91 | 92 | /// 93 | public EvaluationLocationKind Kind { get; } 94 | 95 | /// 96 | public bool IsEvaluationPass => File == null; 97 | 98 | /// 99 | public static EvaluationLocation CreateLocationForCondition( 100 | long? parentId, 101 | EvaluationPass evaluationPass, 102 | string evaluationDescription, 103 | string file, 104 | int? line, 105 | string condition) 106 | { 107 | return new EvaluationLocation(parentId, evaluationPass, evaluationDescription, file, line, "Condition", condition, kind: EvaluationLocationKind.Condition); 108 | } 109 | 110 | /// 111 | public static EvaluationLocation CreateLocationForGlob( 112 | long? parentId, 113 | EvaluationPass evaluationPass, 114 | string evaluationDescription, 115 | string file, 116 | int? line, 117 | string globDescription) 118 | { 119 | return new EvaluationLocation(parentId, evaluationPass, evaluationDescription, file, line, "Glob", globDescription, kind: EvaluationLocationKind.Glob); 120 | } 121 | 122 | /// 123 | public static EvaluationLocation CreateLocationForAggregatedGlob() 124 | { 125 | return new EvaluationLocation( 126 | EvaluationPass.TotalGlobbing, 127 | PassDefaultDescription[EvaluationPass.TotalGlobbing], 128 | file: null, 129 | line: null, 130 | elementName: null, 131 | elementDescription: null, 132 | kind: EvaluationLocationKind.Glob); 133 | } 134 | 135 | /// 136 | /// Constructs a generic evaluation location. 137 | /// 138 | /// 139 | /// Used by serialization/deserialization purposes. 140 | /// 141 | public EvaluationLocation( 142 | long id, 143 | long? parentId, 144 | EvaluationPass evaluationPass, 145 | string evaluationPassDescription, 146 | string file, 147 | int? line, 148 | string elementName, 149 | string elementDescription, 150 | EvaluationLocationKind kind) 151 | { 152 | Id = id; 153 | ParentId = parentId == EmptyLocation.Id ? null : parentId; // The empty location doesn't count as a parent id, since it's just a dummy starting point 154 | EvaluationPass = evaluationPass; 155 | EvaluationPassDescription = evaluationPassDescription; 156 | File = file; 157 | Line = line; 158 | ElementName = elementName; 159 | ElementDescription = elementDescription; 160 | Kind = kind; 161 | } 162 | 163 | /// 164 | /// Constructs a generic evaluation location based on a (possibly null) parent Id. 165 | /// 166 | /// 167 | /// A unique Id gets assigned automatically 168 | /// Used by serialization/deserialization purposes. 169 | /// 170 | public EvaluationLocation(long? parentId, EvaluationPass evaluationPass, string evaluationPassDescription, string file, int? line, string elementName, string elementDescription, EvaluationLocationKind kind) 171 | : this(EvaluationIdProvider.GetNextId(), parentId, evaluationPass, evaluationPassDescription, file, line, elementName, elementDescription, kind) 172 | { 173 | } 174 | 175 | /// 176 | /// Constructs a generic evaluation location with no parent. 177 | /// 178 | /// 179 | /// A unique Id gets assigned automatically 180 | /// Used by serialization/deserialization purposes. 181 | /// 182 | public EvaluationLocation(EvaluationPass evaluationPass, string evaluationPassDescription, string file, int? line, string elementName, string elementDescription, EvaluationLocationKind kind) 183 | : this(null, evaluationPass, evaluationPassDescription, file, line, elementName, elementDescription, kind) 184 | { 185 | } 186 | 187 | /// 188 | /// An empty location, used as the starting instance. 189 | /// 190 | public static EvaluationLocation EmptyLocation { get; } = CreateEmptyLocation(); 191 | 192 | /// 193 | public EvaluationLocation WithEvaluationPass(EvaluationPass evaluationPass, string passDescription = null) 194 | { 195 | return new EvaluationLocation( 196 | Id, 197 | evaluationPass, 198 | passDescription ?? PassDefaultDescription[evaluationPass], 199 | File, 200 | Line, 201 | ElementName, 202 | ElementDescription, 203 | Kind); 204 | } 205 | 206 | /// 207 | public EvaluationLocation WithParentId(long? parentId) 208 | { 209 | // Simple optimization. If the new parent id is the same as the current one, then we just return this 210 | if (parentId == ParentId) 211 | { 212 | return this; 213 | } 214 | 215 | return new EvaluationLocation( 216 | Id, 217 | parentId, 218 | EvaluationPass, 219 | EvaluationPassDescription, 220 | File, 221 | Line, 222 | ElementName, 223 | ElementDescription, 224 | Kind); 225 | } 226 | 227 | /// 228 | public EvaluationLocation WithFile(string file) 229 | { 230 | return new EvaluationLocation(Id, EvaluationPass, EvaluationPassDescription, file, null, null, null, Kind); 231 | } 232 | 233 | /// 234 | public EvaluationLocation WithFileLineAndCondition(string file, int? line, string condition) 235 | { 236 | return CreateLocationForCondition(Id, EvaluationPass, EvaluationPassDescription, file, line, condition); 237 | } 238 | 239 | /// 240 | public EvaluationLocation WithGlob(string globDescription) 241 | { 242 | return CreateLocationForGlob(Id, EvaluationPass, EvaluationPassDescription, File, Line, globDescription); 243 | } 244 | 245 | /// 246 | public override bool Equals(object obj) 247 | { 248 | if (obj is EvaluationLocation evaluationLocation) 249 | { 250 | EvaluationLocation other = evaluationLocation; 251 | return 252 | Id == other.Id 253 | && ParentId == other.ParentId 254 | && EvaluationPass == other.EvaluationPass 255 | && EvaluationPassDescription == other.EvaluationPassDescription 256 | && string.Equals(File, other.File, StringComparison.OrdinalIgnoreCase) 257 | && Line == other.Line 258 | && ElementName == other.ElementName 259 | && ElementDescription == other.ElementDescription 260 | && Kind == other.Kind; 261 | } 262 | return false; 263 | } 264 | 265 | /// 266 | public override string ToString() 267 | { 268 | return 269 | $"{Id}\t{ParentId?.ToString() ?? string.Empty}\t{EvaluationPassDescription ?? string.Empty}\t{File ?? string.Empty}\t{Line?.ToString() ?? string.Empty}\t{ElementName ?? string.Empty}\tDescription:{ElementDescription}\t{EvaluationPassDescription}"; 270 | } 271 | 272 | /// 273 | public override int GetHashCode() 274 | { 275 | int hashCode = 1198539463; 276 | hashCode = (hashCode * -1521134295) + base.GetHashCode(); 277 | hashCode = (hashCode * -1521134295) + Id.GetHashCode(); 278 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ParentId); 279 | hashCode = (hashCode * -1521134295) + EvaluationPass.GetHashCode(); 280 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(EvaluationPassDescription); 281 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(File); 282 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(Line); 283 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ElementName); 284 | hashCode = (hashCode * -1521134295) + EqualityComparer.Default.GetHashCode(ElementDescription); 285 | return (hashCode * -1521134295) + Kind.GetHashCode(); 286 | } 287 | 288 | private static EvaluationLocation CreateEmptyLocation() 289 | { 290 | return new EvaluationLocation( 291 | EvaluationIdProvider.GetNextId(), 292 | null, 293 | default(EvaluationPass), 294 | null, 295 | null, 296 | null, 297 | null, 298 | null, 299 | default(EvaluationLocationKind)); 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome:http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | # Code files 12 | [*.{cs,csx,vb,vbx}] 13 | indent_size = 4 14 | 15 | # Xml project files 16 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 17 | indent_size = 2 18 | 19 | # Xml config files 20 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 21 | indent_size = 2 22 | 23 | # JSON files 24 | [*.json] 25 | indent_size = 2 26 | 27 | # Dotnet code style settings: 28 | [*.{cs,vb}] 29 | # Sort using and Import directives with System.* appearing first 30 | dotnet_sort_system_directives_first = true 31 | # Avoid "this." and "Me." if not necessary 32 | dotnet_style_qualification_for_field = false:suggestion 33 | dotnet_style_qualification_for_property = false:suggestion 34 | dotnet_style_qualification_for_method = false:suggestion 35 | dotnet_style_qualification_for_event = false:suggestion 36 | 37 | # Use language keywords instead of framework type names for type references 38 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 39 | dotnet_style_predefined_type_for_member_access = true:suggestion 40 | 41 | # Suggest more modern language features when available 42 | dotnet_style_object_initializer = true:suggestion 43 | dotnet_style_collection_initializer = true:suggestion 44 | dotnet_style_coalesce_expression = true:suggestion 45 | dotnet_style_null_propagation = true:suggestion 46 | dotnet_style_explicit_tuple_names = true:suggestion 47 | 48 | # CSharp code style settings: 49 | [*.cs] 50 | csharp_style_var_for_built_in_types = false:suggestion 51 | csharp_style_var_when_type_is_apparent = false:suggestion 52 | csharp_style_var_elsewhere = false:suggestion 53 | 54 | # Prefer method-like constructs to have a block body 55 | csharp_style_expression_bodied_methods = false:none 56 | csharp_style_expression_bodied_constructors = false:none 57 | csharp_style_expression_bodied_operators = false:none 58 | 59 | # Prefer property-like constructs to have an expression-body 60 | csharp_style_expression_bodied_properties = true:none 61 | csharp_style_expression_bodied_indexers = true:none 62 | csharp_style_expression_bodied_accessors = true:none 63 | 64 | # Suggest more modern language features when available 65 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 66 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 67 | csharp_style_inlined_variable_declaration = true:suggestion 68 | csharp_style_throw_expression = true:suggestion 69 | csharp_style_conditional_delegate_call = true:suggestion 70 | 71 | # Newline settings 72 | csharp_new_line_before_open_brace = all 73 | csharp_new_line_before_else = true 74 | csharp_new_line_before_catch = true 75 | csharp_new_line_before_finally = true 76 | csharp_new_line_before_members_in_object_initializers = true 77 | csharp_new_line_before_members_in_anonymous_types = true 78 | 79 | # Known validation methods, see https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062?view=vs-2019#null-check-validation-methods 80 | dotnet_code_quality.CA1062.null_check_validation_methods = ThrowIfNull|ThrowIfNullOrEmpty|ThrowIfNullOrWhiteSpace 81 | 82 | # These rules represent Dave Glick's preferences and enforce consistent code quality guidelines. 83 | [*.{cs,vb}] 84 | dotnet_diagnostic.CA1020.severity = warning 85 | dotnet_diagnostic.CA1021.severity = warning 86 | dotnet_diagnostic.CA1040.severity = warning 87 | dotnet_diagnostic.CA1045.severity = warning 88 | dotnet_diagnostic.CA1062.severity = warning 89 | dotnet_diagnostic.CA1501.severity = warning 90 | dotnet_diagnostic.CA1504.severity = warning 91 | dotnet_diagnostic.CA1505.severity = warning 92 | dotnet_diagnostic.CA1506.severity = none 93 | dotnet_diagnostic.CA1700.severity = warning 94 | dotnet_diagnostic.CA1701.severity = warning 95 | dotnet_diagnostic.CA1702.severity = warning 96 | dotnet_diagnostic.CA1703.severity = warning 97 | dotnet_diagnostic.CA1704.severity = warning 98 | dotnet_diagnostic.CA1707.severity = warning 99 | dotnet_diagnostic.CA1709.severity = warning 100 | dotnet_diagnostic.CA1710.severity = warning 101 | dotnet_diagnostic.CA1711.severity = warning 102 | dotnet_diagnostic.CA1712.severity = warning 103 | dotnet_diagnostic.CA1713.severity = warning 104 | dotnet_diagnostic.CA1714.severity = warning 105 | dotnet_diagnostic.CA1715.severity = warning 106 | dotnet_diagnostic.CA1717.severity = warning 107 | dotnet_diagnostic.CA1719.severity = warning 108 | dotnet_diagnostic.CA1720.severity = warning 109 | dotnet_diagnostic.CA1721.severity = warning 110 | dotnet_diagnostic.CA1722.severity = warning 111 | dotnet_diagnostic.CA1724.severity = none 112 | dotnet_diagnostic.CA1725.severity = warning 113 | dotnet_diagnostic.CA1726.severity = warning 114 | dotnet_diagnostic.CA2204.severity = warning 115 | dotnet_diagnostic.CS1591.severity = none 116 | dotnet_diagnostic.IDE0007.severity = none 117 | dotnet_diagnostic.IDE0007WithoutSuggestion.severity = none 118 | dotnet_diagnostic.IDE0008.severity = warning 119 | dotnet_diagnostic.RCS1001.severity = suggestion 120 | dotnet_diagnostic.RCS1002.severity = none 121 | dotnet_diagnostic.RCS1003.severity = suggestion 122 | dotnet_diagnostic.RCS1004.severity = none 123 | dotnet_diagnostic.RCS1005.severity = silent 124 | dotnet_diagnostic.RCS1006.severity = silent 125 | dotnet_diagnostic.RCS1007.severity = none 126 | dotnet_diagnostic.RCS1008.severity = warning 127 | dotnet_diagnostic.RCS1009.severity = warning 128 | dotnet_diagnostic.RCS1010.severity = none 129 | dotnet_diagnostic.RCS1012.severity = warning 130 | dotnet_diagnostic.RCS1013.severity = none 131 | dotnet_diagnostic.RCS1014.severity = none 132 | dotnet_diagnostic.RCS1015.severity = suggestion 133 | dotnet_diagnostic.RCS1016.severity = none 134 | dotnet_diagnostic.RCS1017.severity = none 135 | dotnet_diagnostic.RCS1018.severity = suggestion 136 | dotnet_diagnostic.RCS1019.severity = none 137 | dotnet_diagnostic.RCS1020.severity = suggestion 138 | dotnet_diagnostic.RCS1021.severity = suggestion 139 | dotnet_diagnostic.RCS1022.severity = none 140 | dotnet_diagnostic.RCS1023.severity = none 141 | dotnet_diagnostic.RCS1024.severity = none 142 | dotnet_diagnostic.RCS1025.severity = none 143 | dotnet_diagnostic.RCS1026.severity = none 144 | dotnet_diagnostic.RCS1027.severity = none 145 | dotnet_diagnostic.RCS1028.severity = none 146 | dotnet_diagnostic.RCS1029.severity = suggestion 147 | dotnet_diagnostic.RCS1030.severity = none 148 | dotnet_diagnostic.RCS1031.severity = suggestion 149 | dotnet_diagnostic.RCS1032.severity = suggestion 150 | dotnet_diagnostic.RCS1033.severity = suggestion 151 | dotnet_diagnostic.RCS1034.severity = silent 152 | dotnet_diagnostic.RCS1035.severity = none 153 | dotnet_diagnostic.RCS1036.severity = suggestion 154 | dotnet_diagnostic.RCS1037.severity = suggestion 155 | dotnet_diagnostic.RCS1038.severity = suggestion 156 | dotnet_diagnostic.RCS1039.severity = silent 157 | dotnet_diagnostic.RCS1040.severity = silent 158 | dotnet_diagnostic.RCS1041.severity = suggestion 159 | dotnet_diagnostic.RCS1042.severity = silent 160 | dotnet_diagnostic.RCS1043.severity = silent 161 | dotnet_diagnostic.RCS1044.severity = warning 162 | dotnet_diagnostic.RCS1045.severity = none 163 | dotnet_diagnostic.RCS1046.severity = none 164 | dotnet_diagnostic.RCS1047.severity = suggestion 165 | dotnet_diagnostic.RCS1048.severity = suggestion 166 | dotnet_diagnostic.RCS1049.severity = suggestion 167 | dotnet_diagnostic.RCS1050.severity = none 168 | dotnet_diagnostic.RCS1051.severity = none 169 | dotnet_diagnostic.RCS1052.severity = none 170 | dotnet_diagnostic.RCS1053.severity = none 171 | dotnet_diagnostic.RCS1054.severity = silent 172 | dotnet_diagnostic.RCS1055.severity = silent 173 | dotnet_diagnostic.RCS1056.severity = none 174 | dotnet_diagnostic.RCS1057.severity = suggestion 175 | dotnet_diagnostic.RCS1058.severity = suggestion 176 | dotnet_diagnostic.RCS1059.severity = warning 177 | dotnet_diagnostic.RCS1060.severity = none 178 | dotnet_diagnostic.RCS1061.severity = silent 179 | dotnet_diagnostic.RCS1062.severity = silent 180 | dotnet_diagnostic.RCS1063.severity = suggestion 181 | dotnet_diagnostic.RCS1064.severity = none 182 | dotnet_diagnostic.RCS1065.severity = none 183 | dotnet_diagnostic.RCS1066.severity = silent 184 | dotnet_diagnostic.RCS1067.severity = none 185 | dotnet_diagnostic.RCS1068.severity = suggestion 186 | dotnet_diagnostic.RCS1069.severity = silent 187 | dotnet_diagnostic.RCS1070.severity = silent 188 | dotnet_diagnostic.RCS1071.severity = silent 189 | dotnet_diagnostic.RCS1072.severity = suggestion 190 | dotnet_diagnostic.RCS1073.severity = suggestion 191 | dotnet_diagnostic.RCS1074.severity = silent 192 | dotnet_diagnostic.RCS1075.severity = none 193 | dotnet_diagnostic.RCS1076.severity = silent 194 | dotnet_diagnostic.RCS1077.severity = suggestion 195 | dotnet_diagnostic.RCS1078.severity = none 196 | dotnet_diagnostic.RCS1079.severity = none 197 | dotnet_diagnostic.RCS1080.severity = suggestion 198 | dotnet_diagnostic.RCS1081.severity = none 199 | dotnet_diagnostic.RCS1082.severity = warning 200 | dotnet_diagnostic.RCS1083.severity = warning 201 | dotnet_diagnostic.RCS1084.severity = suggestion 202 | dotnet_diagnostic.RCS1085.severity = suggestion 203 | dotnet_diagnostic.RCS1086.severity = none 204 | dotnet_diagnostic.RCS1087.severity = none 205 | dotnet_diagnostic.RCS1088.severity = none 206 | dotnet_diagnostic.RCS1089.severity = suggestion 207 | dotnet_diagnostic.RCS1090.severity = suggestion 208 | dotnet_diagnostic.RCS1091.severity = silent 209 | dotnet_diagnostic.RCS1092.severity = none 210 | dotnet_diagnostic.RCS1093.severity = suggestion 211 | dotnet_diagnostic.RCS1094.severity = none 212 | dotnet_diagnostic.RCS1095.severity = suggestion 213 | dotnet_diagnostic.RCS1096.severity = suggestion 214 | dotnet_diagnostic.RCS1097.severity = suggestion 215 | dotnet_diagnostic.RCS1098.severity = suggestion 216 | dotnet_diagnostic.RCS1099.severity = suggestion 217 | dotnet_diagnostic.RCS1100.severity = none 218 | dotnet_diagnostic.RCS1101.severity = none 219 | dotnet_diagnostic.RCS1102.severity = none 220 | dotnet_diagnostic.RCS1103.severity = suggestion 221 | dotnet_diagnostic.RCS1104.severity = suggestion 222 | dotnet_diagnostic.RCS1105.severity = suggestion 223 | dotnet_diagnostic.RCS1106.severity = suggestion 224 | dotnet_diagnostic.RCS1107.severity = suggestion 225 | dotnet_diagnostic.RCS1108.severity = suggestion 226 | dotnet_diagnostic.RCS1109.severity = suggestion 227 | dotnet_diagnostic.RCS1110.severity = suggestion 228 | dotnet_diagnostic.RCS1111.severity = none 229 | dotnet_diagnostic.RCS1112.severity = suggestion 230 | dotnet_diagnostic.RCS1113.severity = suggestion 231 | dotnet_diagnostic.RCS1114.severity = suggestion 232 | dotnet_diagnostic.RCS1115.severity = silent 233 | dotnet_diagnostic.RCS1116.severity = silent 234 | dotnet_diagnostic.RCS1117.severity = silent 235 | dotnet_diagnostic.RCS1118.severity = suggestion 236 | dotnet_diagnostic.RCS1119.severity = suggestion 237 | dotnet_diagnostic.RCS1120.severity = suggestion 238 | dotnet_diagnostic.RCS1121.severity = suggestion 239 | dotnet_diagnostic.RCS1122.severity = none 240 | dotnet_diagnostic.RCS1123.severity = suggestion 241 | dotnet_diagnostic.RCS1124.severity = silent 242 | dotnet_diagnostic.RCS1125.severity = silent 243 | dotnet_diagnostic.RCS1126.severity = none 244 | dotnet_diagnostic.RCS1127.severity = suggestion 245 | dotnet_diagnostic.RCS1128.severity = suggestion 246 | dotnet_diagnostic.RCS1129.severity = silent 247 | dotnet_diagnostic.RCS1130.severity = suggestion 248 | dotnet_diagnostic.RCS1131.severity = silent 249 | dotnet_diagnostic.RCS1132.severity = suggestion 250 | dotnet_diagnostic.RCS1133.severity = silent 251 | dotnet_diagnostic.RCS1134.severity = silent 252 | dotnet_diagnostic.RCS1135.severity = suggestion 253 | dotnet_diagnostic.RCS1136.severity = silent 254 | dotnet_diagnostic.RCS1137.severity = silent 255 | dotnet_diagnostic.RCS1138.severity = warning 256 | dotnet_diagnostic.RCS1139.severity = none 257 | dotnet_diagnostic.RCS1140.severity = silent 258 | dotnet_diagnostic.RCS1141.severity = silent 259 | dotnet_diagnostic.RCS1142.severity = silent 260 | dotnet_diagnostic.RCS1143.severity = silent 261 | dotnet_diagnostic.RCS1144.severity = silent 262 | dotnet_diagnostic.RCS1145.severity = silent 263 | dotnet_diagnostic.RCS1146.severity = suggestion 264 | dotnet_diagnostic.RCS1147.severity = silent 265 | dotnet_diagnostic.RCS1148.severity = silent 266 | dotnet_diagnostic.RCS1149.severity = silent 267 | dotnet_diagnostic.RCS1150.severity = suggestion 268 | dotnet_diagnostic.RCS1151.severity = silent 269 | dotnet_diagnostic.RCS1152.severity = silent 270 | dotnet_diagnostic.RCS1153.severity = none 271 | dotnet_diagnostic.RCS1154.severity = suggestion 272 | dotnet_diagnostic.RCS1155.severity = warning 273 | dotnet_diagnostic.RCS1156.severity = suggestion 274 | dotnet_diagnostic.RCS1157.severity = suggestion 275 | dotnet_diagnostic.RCS1158.severity = suggestion 276 | dotnet_diagnostic.RCS1159.severity = suggestion 277 | dotnet_diagnostic.RCS1160.severity = suggestion 278 | dotnet_diagnostic.RCS1161.severity = silent 279 | dotnet_diagnostic.RCS1162.severity = none 280 | dotnet_diagnostic.RCS1163.severity = suggestion 281 | dotnet_diagnostic.RCS1164.severity = suggestion 282 | dotnet_diagnostic.RCS1165.severity = silent 283 | dotnet_diagnostic.RCS1166.severity = suggestion 284 | dotnet_diagnostic.RCS1167.severity = silent 285 | dotnet_diagnostic.RCS1168.severity = none 286 | dotnet_diagnostic.RCS1169.severity = suggestion 287 | dotnet_diagnostic.RCS1170.severity = suggestion 288 | dotnet_diagnostic.RCS1171.severity = suggestion 289 | dotnet_diagnostic.RCS1172.severity = warning 290 | dotnet_diagnostic.RCS1173.severity = suggestion 291 | dotnet_diagnostic.RCS1174.severity = none 292 | dotnet_diagnostic.RCS1175.severity = suggestion 293 | dotnet_diagnostic.RCS1176.severity = none 294 | dotnet_diagnostic.RCS1177.severity = none 295 | dotnet_diagnostic.RCS1178.severity = suggestion 296 | dotnet_diagnostic.RCS1179.severity = suggestion 297 | dotnet_diagnostic.RCS1180.severity = suggestion 298 | dotnet_diagnostic.RCS1181.severity = silent 299 | dotnet_diagnostic.RCS1182.severity = silent 300 | dotnet_diagnostic.RCS1183.severity = silent 301 | dotnet_diagnostic.RCS1184.severity = none 302 | dotnet_diagnostic.RCS1185.severity = none 303 | dotnet_diagnostic.RCS1186.severity = silent 304 | dotnet_diagnostic.RCS1187.severity = suggestion 305 | dotnet_diagnostic.RCS1188.severity = silent 306 | dotnet_diagnostic.RCS1189.severity = silent 307 | dotnet_diagnostic.RCS1190.severity = suggestion 308 | dotnet_diagnostic.RCS1191.severity = suggestion 309 | dotnet_diagnostic.RCS1192.severity = suggestion 310 | dotnet_diagnostic.RCS1193.severity = warning 311 | dotnet_diagnostic.RCS1194.severity = none 312 | dotnet_diagnostic.RCS1195.severity = suggestion 313 | dotnet_diagnostic.RCS1196.severity = suggestion 314 | dotnet_diagnostic.RCS1197.severity = suggestion 315 | dotnet_diagnostic.RCS1198.severity = none 316 | dotnet_diagnostic.RCS1199.severity = suggestion 317 | dotnet_diagnostic.RCS1200.severity = suggestion 318 | dotnet_diagnostic.RCS1201.severity = silent 319 | dotnet_diagnostic.RCS1202.severity = suggestion 320 | dotnet_diagnostic.RCS1203.severity = warning 321 | dotnet_diagnostic.RCS1204.severity = suggestion 322 | dotnet_diagnostic.RCS1205.severity = suggestion 323 | dotnet_diagnostic.RCS1206.severity = suggestion 324 | dotnet_diagnostic.RCS1207.severity = silent 325 | dotnet_diagnostic.RCS1208.severity = none 326 | dotnet_diagnostic.RCS1209.severity = suggestion 327 | dotnet_diagnostic.RCS1210.severity = warning 328 | dotnet_diagnostic.RCS1211.severity = silent 329 | dotnet_diagnostic.RCS1212.severity = suggestion 330 | dotnet_diagnostic.RCS1213.severity = suggestion 331 | dotnet_diagnostic.RCS1214.severity = suggestion 332 | dotnet_diagnostic.RCS1215.severity = warning 333 | dotnet_diagnostic.RCS1216.severity = suggestion 334 | dotnet_diagnostic.RCS1217.severity = silent 335 | dotnet_diagnostic.RCS1218.severity = suggestion 336 | dotnet_diagnostic.RCS1219.severity = none 337 | dotnet_diagnostic.RCS1220.severity = suggestion 338 | dotnet_diagnostic.RCS1221.severity = suggestion 339 | dotnet_diagnostic.RCS1222.severity = suggestion 340 | dotnet_diagnostic.RCS1223.severity = none 341 | dotnet_diagnostic.RCS1224.severity = suggestion 342 | dotnet_diagnostic.RCS1225.severity = suggestion 343 | dotnet_diagnostic.RCS1226.severity = suggestion 344 | dotnet_diagnostic.RCS1227.severity = suggestion 345 | dotnet_diagnostic.RCS1228.severity = silent 346 | dotnet_diagnostic.RCS1229.severity = suggestion 347 | dotnet_diagnostic.RCS1230.severity = suggestion 348 | dotnet_diagnostic.RCS1231.severity = suggestion 349 | dotnet_diagnostic.SA1101.severity = none 350 | dotnet_diagnostic.SA1118.severity = none 351 | dotnet_diagnostic.SA1200.severity = none 352 | dotnet_diagnostic.SA1201.severity = none 353 | dotnet_diagnostic.SA1202.severity = none 354 | dotnet_diagnostic.SA1204.severity = none 355 | dotnet_diagnostic.SA1210.severity = none 356 | dotnet_diagnostic.SA1309.severity = none 357 | dotnet_diagnostic.SA1413.severity = none 358 | dotnet_diagnostic.SA1512.severity = none 359 | dotnet_diagnostic.SA1513.severity = none 360 | dotnet_diagnostic.SA1516.severity = none 361 | dotnet_diagnostic.SA1600.severity = none 362 | dotnet_diagnostic.SA1601.severity = none 363 | dotnet_diagnostic.SA1602.severity = none 364 | dotnet_diagnostic.SA1604.severity = none 365 | dotnet_diagnostic.SA1605.severity = none 366 | dotnet_diagnostic.SA1606.severity = none 367 | dotnet_diagnostic.SA1607.severity = none 368 | dotnet_diagnostic.SA1608.severity = none 369 | dotnet_diagnostic.SA1611.severity = none 370 | dotnet_diagnostic.SA1615.severity = none 371 | dotnet_diagnostic.SA1618.severity = none 372 | dotnet_diagnostic.SA1623.severity = none 373 | dotnet_diagnostic.SA1633.severity = none 374 | dotnet_diagnostic.SA1642.severity = none 375 | dotnet_diagnostic.SA1648.severity = none 376 | dotnet_diagnostic.SX1309.severity = warning -------------------------------------------------------------------------------- /src/MsBuildPipeLogger.Logger/BinaryLogger/BuildEventArgsWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using Microsoft.Build.Framework; 7 | using Microsoft.Build.Framework.Profiler; 8 | 9 | namespace Microsoft.Build.Logging 10 | { 11 | /// 12 | /// Serializes BuildEventArgs-derived objects into a provided BinaryWriter. 13 | /// 14 | internal class BuildEventArgsWriter 15 | { 16 | private readonly BinaryWriter _binaryWriter; 17 | 18 | /// 19 | /// Initializes a new instance of BuildEventArgsWriter with a BinaryWriter. 20 | /// 21 | /// A BinaryWriter to write the BuildEventArgs instances to. 22 | public BuildEventArgsWriter(BinaryWriter binaryWriter) 23 | { 24 | _binaryWriter = binaryWriter; 25 | } 26 | 27 | /// 28 | /// Write a provided instance of BuildEventArgs to the BinaryWriter. 29 | /// 30 | public void Write(BuildEventArgs e) 31 | { 32 | string type = e.GetType().Name; 33 | 34 | // the cases are ordered by most used first for performance 35 | if (e is BuildMessageEventArgs && type != "ProjectImportedEventArgs" && type != "TargetSkippedEventArgs") 36 | { 37 | Write((BuildMessageEventArgs)e); 38 | } 39 | else if (e is TaskStartedEventArgs taskStartedEventArgs) 40 | { 41 | Write(taskStartedEventArgs); 42 | } 43 | else if (e is TaskFinishedEventArgs taskFinishedEventArgs) 44 | { 45 | Write(taskFinishedEventArgs); 46 | } 47 | else if (e is TargetStartedEventArgs targetStartedEventArgs) 48 | { 49 | Write(targetStartedEventArgs); 50 | } 51 | else if (e is TargetFinishedEventArgs targetFinishedEventArgs) 52 | { 53 | Write(targetFinishedEventArgs); 54 | } 55 | else if (e is BuildErrorEventArgs buildErrorEventArgs) 56 | { 57 | Write(buildErrorEventArgs); 58 | } 59 | else if (e is BuildWarningEventArgs buildWarningEventArgs) 60 | { 61 | Write(buildWarningEventArgs); 62 | } 63 | else if (e is ProjectStartedEventArgs projectStartedEventArgs) 64 | { 65 | Write(projectStartedEventArgs); 66 | } 67 | else if (e is ProjectFinishedEventArgs projectFinishedEventArgs) 68 | { 69 | Write(projectFinishedEventArgs); 70 | } 71 | else if (e is BuildStartedEventArgs buildStartedEventArgs) 72 | { 73 | Write(buildStartedEventArgs); 74 | } 75 | else if (e is BuildFinishedEventArgs buildFinishedEventArgs) 76 | { 77 | Write(buildFinishedEventArgs); 78 | } 79 | else if (e is ProjectEvaluationStartedEventArgs projectEvaluationStartedEventArgs) 80 | { 81 | Write(projectEvaluationStartedEventArgs); 82 | } 83 | else if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinishedEventArgs) 84 | { 85 | Write(projectEvaluationFinishedEventArgs); 86 | } 87 | 88 | // The following cases are due to the fact that StructuredLogger.dll 89 | // only references MSBuild 14.0 .dlls. The following BuildEventArgs types 90 | // were only introduced in MSBuild 15.3 so we can't refer to them statically. 91 | // To still provide a good experience to those who are using the BinaryLogger 92 | // from StructuredLogger.dll against MSBuild 15.3 or later we need to preserve 93 | // these new events, so use reflection to create our "equivalents" of those 94 | // and populate them to be binary identical to the originals. Then serialize 95 | // our copies so that it's impossible to tell what wrote these. 96 | else if (type == "ProjectEvaluationStartedEventArgs") 97 | { 98 | ProjectEvaluationStartedEventArgs evaluationStarted = new ProjectEvaluationStartedEventArgs(e.Message) 99 | { 100 | BuildEventContext = e.BuildEventContext, 101 | ProjectFile = Reflector.GetProjectFileFromEvaluationStarted(e) 102 | }; 103 | Write(evaluationStarted); 104 | } 105 | else if (type == "ProjectEvaluationFinishedEventArgs") 106 | { 107 | ProjectEvaluationFinishedEventArgs evaluationFinished = new ProjectEvaluationFinishedEventArgs(e.Message) 108 | { 109 | BuildEventContext = e.BuildEventContext, 110 | ProjectFile = Reflector.GetProjectFileFromEvaluationFinished(e) 111 | }; 112 | Write(evaluationFinished); 113 | } 114 | else if (type == "ProjectImportedEventArgs") 115 | { 116 | BuildMessageEventArgs message = e as BuildMessageEventArgs; 117 | ProjectImportedEventArgs projectImported = new ProjectImportedEventArgs(message.LineNumber, message.ColumnNumber, e.Message) 118 | { 119 | BuildEventContext = e.BuildEventContext, 120 | ProjectFile = message.ProjectFile, 121 | ImportedProjectFile = Reflector.GetImportedProjectFile(e), 122 | UnexpandedProject = Reflector.GetUnexpandedProject(e) 123 | }; 124 | Write(projectImported); 125 | } 126 | else if (type == "TargetSkippedEventArgs") 127 | { 128 | BuildMessageEventArgs message = e as BuildMessageEventArgs; 129 | TargetSkippedEventArgs targetSkipped = new TargetSkippedEventArgs(e.Message) 130 | { 131 | BuildEventContext = e.BuildEventContext, 132 | ProjectFile = message.ProjectFile, 133 | TargetName = Reflector.GetTargetNameFromTargetSkipped(e), 134 | TargetFile = Reflector.GetTargetFileFromTargetSkipped(e), 135 | ParentTarget = Reflector.GetParentTargetFromTargetSkipped(e), 136 | BuildReason = Reflector.GetBuildReasonFromTargetSkipped(e) 137 | }; 138 | Write(targetSkipped); 139 | } 140 | else 141 | { 142 | // convert all unrecognized objects to message 143 | // and just preserve the message 144 | BuildMessageEventArgs buildMessageEventArgs = new BuildMessageEventArgs( 145 | e.Message, 146 | e.HelpKeyword, 147 | e.SenderName, 148 | MessageImportance.Normal, 149 | e.Timestamp) 150 | { 151 | BuildEventContext = e.BuildEventContext ?? BuildEventContext.Invalid 152 | }; 153 | Write(buildMessageEventArgs); 154 | } 155 | } 156 | 157 | public void WriteBlob(BinaryLogRecordKind kind, byte[] bytes) 158 | { 159 | Write(kind); 160 | Write(bytes.Length); 161 | Write(bytes); 162 | } 163 | 164 | private void Write(BuildStartedEventArgs e) 165 | { 166 | Write(BinaryLogRecordKind.BuildStarted); 167 | WriteBuildEventArgsFields(e); 168 | Write(e.BuildEnvironment); 169 | } 170 | 171 | private void Write(BuildFinishedEventArgs e) 172 | { 173 | Write(BinaryLogRecordKind.BuildFinished); 174 | WriteBuildEventArgsFields(e); 175 | Write(e.Succeeded); 176 | } 177 | 178 | private void Write(ProjectEvaluationStartedEventArgs e) 179 | { 180 | Write(BinaryLogRecordKind.ProjectEvaluationStarted); 181 | WriteBuildEventArgsFields(e); 182 | Write(e.ProjectFile); 183 | } 184 | 185 | private void Write(ProjectEvaluationFinishedEventArgs e) 186 | { 187 | Write(BinaryLogRecordKind.ProjectEvaluationFinished); 188 | 189 | WriteBuildEventArgsFields(e); 190 | Write(e.ProjectFile); 191 | 192 | Write(e.ProfilerResult.HasValue); 193 | if (e.ProfilerResult.HasValue) 194 | { 195 | Write(e.ProfilerResult.Value.ProfiledLocations.Count); 196 | 197 | foreach (KeyValuePair item in e.ProfilerResult.Value.ProfiledLocations) 198 | { 199 | Write(item.Key); 200 | Write(item.Value); 201 | } 202 | } 203 | } 204 | 205 | private void Write(ProjectStartedEventArgs e) 206 | { 207 | Write(BinaryLogRecordKind.ProjectStarted); 208 | WriteBuildEventArgsFields(e); 209 | 210 | if (e.ParentProjectBuildEventContext == null) 211 | { 212 | Write(false); 213 | } 214 | else 215 | { 216 | Write(true); 217 | Write(e.ParentProjectBuildEventContext); 218 | } 219 | 220 | WriteOptionalString(e.ProjectFile); 221 | 222 | Write(e.ProjectId); 223 | Write(e.TargetNames); 224 | WriteOptionalString(e.ToolsVersion); 225 | 226 | if (e.GlobalProperties == null) 227 | { 228 | Write(false); 229 | } 230 | else 231 | { 232 | Write(true); 233 | Write(e.GlobalProperties); 234 | } 235 | 236 | WriteProperties(e.Properties); 237 | 238 | WriteItems(e.Items); 239 | } 240 | 241 | private void Write(ProjectFinishedEventArgs e) 242 | { 243 | Write(BinaryLogRecordKind.ProjectFinished); 244 | WriteBuildEventArgsFields(e); 245 | WriteOptionalString(e.ProjectFile); 246 | Write(e.Succeeded); 247 | } 248 | 249 | private void Write(TargetStartedEventArgs e) 250 | { 251 | Write(BinaryLogRecordKind.TargetStarted); 252 | WriteBuildEventArgsFields(e); 253 | WriteOptionalString(e.TargetName); 254 | WriteOptionalString(e.ProjectFile); 255 | WriteOptionalString(e.TargetFile); 256 | WriteOptionalString(e.ParentTarget); 257 | Write((int)Reflector.GetBuildReasonFromTargetStarted(e)); 258 | } 259 | 260 | private void Write(TargetFinishedEventArgs e) 261 | { 262 | Write(BinaryLogRecordKind.TargetFinished); 263 | WriteBuildEventArgsFields(e); 264 | Write(e.Succeeded); 265 | WriteOptionalString(e.ProjectFile); 266 | WriteOptionalString(e.TargetFile); 267 | WriteOptionalString(e.TargetName); 268 | WriteItemList(e.TargetOutputs); 269 | } 270 | 271 | private void Write(TaskStartedEventArgs e) 272 | { 273 | Write(BinaryLogRecordKind.TaskStarted); 274 | WriteBuildEventArgsFields(e); 275 | WriteOptionalString(e.TaskName); 276 | WriteOptionalString(e.ProjectFile); 277 | WriteOptionalString(e.TaskFile); 278 | } 279 | 280 | private void Write(TaskFinishedEventArgs e) 281 | { 282 | Write(BinaryLogRecordKind.TaskFinished); 283 | WriteBuildEventArgsFields(e); 284 | Write(e.Succeeded); 285 | WriteOptionalString(e.TaskName); 286 | WriteOptionalString(e.ProjectFile); 287 | WriteOptionalString(e.TaskFile); 288 | } 289 | 290 | private void Write(BuildErrorEventArgs e) 291 | { 292 | Write(BinaryLogRecordKind.Error); 293 | WriteBuildEventArgsFields(e); 294 | WriteOptionalString(e.Subcategory); 295 | WriteOptionalString(e.Code); 296 | WriteOptionalString(e.File); 297 | WriteOptionalString(e.ProjectFile); 298 | Write(e.LineNumber); 299 | Write(e.ColumnNumber); 300 | Write(e.EndLineNumber); 301 | Write(e.EndColumnNumber); 302 | } 303 | 304 | private void Write(BuildWarningEventArgs e) 305 | { 306 | Write(BinaryLogRecordKind.Warning); 307 | WriteBuildEventArgsFields(e); 308 | WriteOptionalString(e.Subcategory); 309 | WriteOptionalString(e.Code); 310 | WriteOptionalString(e.File); 311 | WriteOptionalString(e.ProjectFile); 312 | Write(e.LineNumber); 313 | Write(e.ColumnNumber); 314 | Write(e.EndLineNumber); 315 | Write(e.EndColumnNumber); 316 | } 317 | 318 | private void Write(BuildMessageEventArgs e) 319 | { 320 | if (e is CriticalBuildMessageEventArgs criticalBuildMessageEventArgs) 321 | { 322 | Write(criticalBuildMessageEventArgs); 323 | return; 324 | } 325 | 326 | if (e is TaskCommandLineEventArgs taskCommandLineEventArgs) 327 | { 328 | Write(taskCommandLineEventArgs); 329 | return; 330 | } 331 | 332 | if (e is ProjectImportedEventArgs projectImportedEventArgs) 333 | { 334 | Write(projectImportedEventArgs); 335 | return; 336 | } 337 | 338 | if (e is TargetSkippedEventArgs targetSkippedEventArgs) 339 | { 340 | Write(targetSkippedEventArgs); 341 | return; 342 | } 343 | 344 | Write(BinaryLogRecordKind.Message); 345 | WriteMessageFields(e); 346 | } 347 | 348 | private void Write(ProjectImportedEventArgs e) 349 | { 350 | Write(BinaryLogRecordKind.ProjectImported); 351 | WriteMessageFields(e); 352 | Write(e.ImportIgnored); 353 | WriteOptionalString(e.ImportedProjectFile); 354 | WriteOptionalString(e.UnexpandedProject); 355 | } 356 | 357 | private void Write(TargetSkippedEventArgs e) 358 | { 359 | Write(BinaryLogRecordKind.TargetSkipped); 360 | WriteMessageFields(e); 361 | WriteOptionalString(e.TargetFile); 362 | WriteOptionalString(e.TargetName); 363 | WriteOptionalString(e.ParentTarget); 364 | Write((int)e.BuildReason); 365 | } 366 | 367 | private void Write(CriticalBuildMessageEventArgs e) 368 | { 369 | Write(BinaryLogRecordKind.CriticalBuildMessage); 370 | WriteMessageFields(e); 371 | } 372 | 373 | private void Write(TaskCommandLineEventArgs e) 374 | { 375 | Write(BinaryLogRecordKind.TaskCommandLine); 376 | WriteMessageFields(e); 377 | WriteOptionalString(e.CommandLine); 378 | WriteOptionalString(e.TaskName); 379 | } 380 | 381 | private void WriteBuildEventArgsFields(BuildEventArgs e) 382 | { 383 | BuildEventArgsFieldFlags flags = GetBuildEventArgsFieldFlags(e); 384 | Write((int)flags); 385 | WriteBaseFields(e, flags); 386 | } 387 | 388 | private void WriteBaseFields(BuildEventArgs e, BuildEventArgsFieldFlags flags) 389 | { 390 | if ((flags & BuildEventArgsFieldFlags.Message) != 0) 391 | { 392 | Write(e.Message); 393 | } 394 | 395 | if ((flags & BuildEventArgsFieldFlags.BuildEventContext) != 0) 396 | { 397 | Write(e.BuildEventContext); 398 | } 399 | 400 | if ((flags & BuildEventArgsFieldFlags.ThreadId) != 0) 401 | { 402 | Write(e.ThreadId); 403 | } 404 | 405 | if ((flags & BuildEventArgsFieldFlags.HelpHeyword) != 0) 406 | { 407 | Write(e.HelpKeyword); 408 | } 409 | 410 | if ((flags & BuildEventArgsFieldFlags.SenderName) != 0) 411 | { 412 | Write(e.SenderName); 413 | } 414 | 415 | if ((flags & BuildEventArgsFieldFlags.Timestamp) != 0) 416 | { 417 | Write(e.Timestamp); 418 | } 419 | } 420 | 421 | private void WriteMessageFields(BuildMessageEventArgs e) 422 | { 423 | BuildEventArgsFieldFlags flags = GetBuildEventArgsFieldFlags(e); 424 | flags = GetMessageFlags(e, flags); 425 | 426 | Write((int)flags); 427 | 428 | WriteBaseFields(e, flags); 429 | 430 | if ((flags & BuildEventArgsFieldFlags.Subcategory) != 0) 431 | { 432 | Write(e.Subcategory); 433 | } 434 | 435 | if ((flags & BuildEventArgsFieldFlags.Code) != 0) 436 | { 437 | Write(e.Code); 438 | } 439 | 440 | if ((flags & BuildEventArgsFieldFlags.File) != 0) 441 | { 442 | Write(e.File); 443 | } 444 | 445 | if ((flags & BuildEventArgsFieldFlags.ProjectFile) != 0) 446 | { 447 | Write(e.ProjectFile); 448 | } 449 | 450 | if ((flags & BuildEventArgsFieldFlags.LineNumber) != 0) 451 | { 452 | Write(e.LineNumber); 453 | } 454 | 455 | if ((flags & BuildEventArgsFieldFlags.ColumnNumber) != 0) 456 | { 457 | Write(e.ColumnNumber); 458 | } 459 | 460 | if ((flags & BuildEventArgsFieldFlags.EndLineNumber) != 0) 461 | { 462 | Write(e.EndLineNumber); 463 | } 464 | 465 | if ((flags & BuildEventArgsFieldFlags.EndColumnNumber) != 0) 466 | { 467 | Write(e.EndColumnNumber); 468 | } 469 | 470 | Write((int)e.Importance); 471 | } 472 | 473 | private static BuildEventArgsFieldFlags GetMessageFlags(BuildMessageEventArgs e, BuildEventArgsFieldFlags flags) 474 | { 475 | if (e.Subcategory != null) 476 | { 477 | flags |= BuildEventArgsFieldFlags.Subcategory; 478 | } 479 | 480 | if (e.Code != null) 481 | { 482 | flags |= BuildEventArgsFieldFlags.Code; 483 | } 484 | 485 | if (e.File != null) 486 | { 487 | flags |= BuildEventArgsFieldFlags.File; 488 | } 489 | 490 | if (e.ProjectFile != null) 491 | { 492 | flags |= BuildEventArgsFieldFlags.ProjectFile; 493 | } 494 | 495 | if (e.LineNumber != 0) 496 | { 497 | flags |= BuildEventArgsFieldFlags.LineNumber; 498 | } 499 | 500 | if (e.ColumnNumber != 0) 501 | { 502 | flags |= BuildEventArgsFieldFlags.ColumnNumber; 503 | } 504 | 505 | if (e.EndLineNumber != 0) 506 | { 507 | flags |= BuildEventArgsFieldFlags.EndLineNumber; 508 | } 509 | 510 | if (e.EndColumnNumber != 0) 511 | { 512 | flags |= BuildEventArgsFieldFlags.EndColumnNumber; 513 | } 514 | 515 | return flags; 516 | } 517 | 518 | private static BuildEventArgsFieldFlags GetBuildEventArgsFieldFlags(BuildEventArgs e) 519 | { 520 | BuildEventArgsFieldFlags flags = BuildEventArgsFieldFlags.None; 521 | if (e.BuildEventContext != null) 522 | { 523 | flags |= BuildEventArgsFieldFlags.BuildEventContext; 524 | } 525 | 526 | if (e.HelpKeyword != null) 527 | { 528 | flags |= BuildEventArgsFieldFlags.HelpHeyword; 529 | } 530 | 531 | if (!string.IsNullOrEmpty(e.Message)) 532 | { 533 | flags |= BuildEventArgsFieldFlags.Message; 534 | } 535 | 536 | // no need to waste space for the default sender name 537 | if (e.SenderName != null && e.SenderName != "MSBuild") 538 | { 539 | flags |= BuildEventArgsFieldFlags.SenderName; 540 | } 541 | 542 | if (e.ThreadId > 0) 543 | { 544 | flags |= BuildEventArgsFieldFlags.ThreadId; 545 | } 546 | 547 | if (e.Timestamp != default(DateTime)) 548 | { 549 | flags |= BuildEventArgsFieldFlags.Timestamp; 550 | } 551 | 552 | return flags; 553 | } 554 | 555 | private void WriteItemList(IEnumerable items) 556 | { 557 | if (items is IEnumerable taskItems) 558 | { 559 | Write(taskItems.Count()); 560 | 561 | foreach (ITaskItem item in taskItems) 562 | { 563 | Write(item); 564 | } 565 | 566 | return; 567 | } 568 | 569 | Write(0); 570 | } 571 | 572 | private void WriteItems(IEnumerable items) 573 | { 574 | if (items == null) 575 | { 576 | Write(0); 577 | return; 578 | } 579 | 580 | DictionaryEntry[] entries = items.OfType() 581 | .Where(e => e.Key is string && e.Value is ITaskItem) 582 | .ToArray(); 583 | Write(entries.Length); 584 | 585 | foreach (DictionaryEntry entry in entries) 586 | { 587 | string key = entry.Key as string; 588 | ITaskItem item = entry.Value as ITaskItem; 589 | Write(key); 590 | Write(item); 591 | } 592 | } 593 | 594 | private void Write(ITaskItem item) 595 | { 596 | Write(item.ItemSpec); 597 | IDictionary customMetadata = item.CloneCustomMetadata(); 598 | Write(customMetadata.Count); 599 | 600 | foreach (string metadataName in customMetadata.Keys) 601 | { 602 | Write(metadataName); 603 | Write(item.GetMetadata(metadataName)); 604 | } 605 | } 606 | 607 | private void WriteProperties(IEnumerable properties) 608 | { 609 | if (properties == null) 610 | { 611 | Write(0); 612 | return; 613 | } 614 | 615 | // there are no guarantees that the properties iterator won't change, so 616 | // take a snapshot and work with the readonly copy 617 | DictionaryEntry[] propertiesArray = properties.OfType().ToArray(); 618 | 619 | Write(propertiesArray.Length); 620 | 621 | foreach (DictionaryEntry entry in propertiesArray) 622 | { 623 | if (entry.Key is string && entry.Value is string) 624 | { 625 | Write((string)entry.Key); 626 | Write((string)entry.Value); 627 | } 628 | else 629 | { 630 | // to keep the count accurate 631 | Write(string.Empty); 632 | Write(string.Empty); 633 | } 634 | } 635 | } 636 | 637 | private void Write(BuildEventContext buildEventContext) 638 | { 639 | Write(buildEventContext.NodeId); 640 | Write(buildEventContext.ProjectContextId); 641 | Write(buildEventContext.TargetId); 642 | Write(buildEventContext.TaskId); 643 | Write(buildEventContext.SubmissionId); 644 | Write(buildEventContext.ProjectInstanceId); 645 | Write(Reflector.GetEvaluationId(buildEventContext)); 646 | } 647 | 648 | private void Write(IEnumerable> keyValuePairs) 649 | { 650 | if (keyValuePairs?.Any() == true) 651 | { 652 | Write(keyValuePairs.Count()); 653 | foreach (KeyValuePair kvp in keyValuePairs) 654 | { 655 | Write(kvp.Key.ToString()); 656 | Write(kvp.Value.ToString()); 657 | } 658 | } 659 | else 660 | { 661 | Write(false); 662 | } 663 | } 664 | 665 | private void Write(BinaryLogRecordKind kind) 666 | { 667 | Write((int)kind); 668 | } 669 | 670 | private void Write(int value) 671 | { 672 | Write7BitEncodedInt(_binaryWriter, value); 673 | } 674 | 675 | private void Write(long value) 676 | { 677 | _binaryWriter.Write(value); 678 | } 679 | 680 | private void Write7BitEncodedInt(BinaryWriter writer, int value) 681 | { 682 | // Write out an int 7 bits at a time. The high bit of the byte, 683 | // when on, tells reader to continue reading more bytes. 684 | uint v = (uint)value; // support negative numbers 685 | while (v >= 0x80) 686 | { 687 | writer.Write((byte)(v | 0x80)); 688 | v >>= 7; 689 | } 690 | writer.Write((byte)v); 691 | } 692 | 693 | private void Write(byte[] bytes) 694 | { 695 | _binaryWriter.Write(bytes); 696 | } 697 | 698 | private void Write(bool boolean) 699 | { 700 | _binaryWriter.Write(boolean); 701 | } 702 | 703 | private void Write(string text) 704 | { 705 | if (text != null) 706 | { 707 | _binaryWriter.Write(text); 708 | } 709 | else 710 | { 711 | _binaryWriter.Write(false); 712 | } 713 | } 714 | 715 | private void WriteOptionalString(string text) 716 | { 717 | if (text == null) 718 | { 719 | Write(false); 720 | } 721 | else 722 | { 723 | Write(true); 724 | Write(text); 725 | } 726 | } 727 | 728 | private void Write(DateTime timestamp) 729 | { 730 | _binaryWriter.Write(timestamp.Ticks); 731 | Write((int)timestamp.Kind); 732 | } 733 | 734 | private void Write(TimeSpan timeSpan) 735 | { 736 | _binaryWriter.Write(timeSpan.Ticks); 737 | } 738 | 739 | private void Write(EvaluationLocation item) 740 | { 741 | WriteOptionalString(item.ElementName); 742 | WriteOptionalString(item.ElementDescription); 743 | WriteOptionalString(item.EvaluationPassDescription); 744 | WriteOptionalString(item.File); 745 | Write((int)item.Kind); 746 | Write((int)item.EvaluationPass); 747 | 748 | Write(item.Line.HasValue); 749 | if (item.Line.HasValue) 750 | { 751 | Write(item.Line.Value); 752 | } 753 | 754 | Write(item.Id); 755 | Write(item.ParentId.HasValue); 756 | if (item.ParentId.HasValue) 757 | { 758 | Write(item.ParentId.Value); 759 | } 760 | } 761 | 762 | private void Write(ProfiledLocation e) 763 | { 764 | Write(e.NumberOfHits); 765 | Write(e.ExclusiveTime); 766 | Write(e.InclusiveTime); 767 | } 768 | } 769 | } 770 | --------------------------------------------------------------------------------