├── 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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------