├── icon.png ├── Docs └── TeamCityBuildLog.png ├── test.cmd ├── global.json ├── TeamCity.MSBuild.Logger ├── IStatistics.cs ├── IInitializable.cs ├── StatisticsMode.cs ├── ColorThemeMode.cs ├── TeamCityMode.cs ├── IConsole.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── Resources.resx ├── IDiagnostics.cs ├── IEventContext.cs ├── IPathService.cs ├── ColorMode.cs ├── IColorStorage.cs ├── IEventRegistry.cs ├── DefaultStatistics.cs ├── IParametersParser.cs ├── IHierarchicalMessageWriter.cs ├── IColorTheme.cs ├── ILogFormatter.cs ├── ILogWriter.cs ├── IEnvironment.cs ├── Property.cs ├── ColorStorage.cs ├── IPerformanceCounterFactory.cs ├── IStringService.cs ├── EventHandlers │ ├── IBuildEventHandler.cs │ ├── TargetStartedHandler.cs │ ├── CustomEventHandler.cs │ ├── ErrorHandler.cs │ ├── WarningHandler.cs │ ├── TaskFinishedHandler.cs │ ├── TaskStartedHandler.cs │ ├── BuildStartedHandler.cs │ ├── MessageHandler.cs │ ├── ProjectFinishedHandler.cs │ ├── TargetFinishedHandler.cs │ └── ProjectStartedHandler.cs ├── PathService.cs ├── IDeferredMessageWriter.cs ├── Color.cs ├── DefaultHierarchicalMessageWriter.cs ├── DictionaryEntryKeyComparer.cs ├── TaskItem.cs ├── IEventFormatter.cs ├── LogFormatter.cs ├── TaskItemItemSpecComparer.cs ├── ComparerContextNodeIdTargetId.cs ├── IPerformanceCounter.cs ├── ComparerContextNodeId.cs ├── EventContext.cs ├── DescendingByElapsedTime.cs ├── FlowIdGenerator.cs ├── NoColorLogWriter.cs ├── IBuildEventManager.cs ├── HierarchicalContext.cs ├── PatchedServiceMessage.cs ├── TeamCityMSBuildLogger.cs ├── TeamCityStatistics.cs ├── ErrorWarningSummaryDictionaryKey.cs ├── Environment.cs ├── IMessageWriter.cs ├── StringBuilderCache.cs ├── PerformanceCounterFactory.cs ├── TeamCityColorTheme.cs ├── Statistics.cs ├── ProjectFullKey.cs ├── Diagnostics.cs ├── AnsiLogWriter.cs ├── DefaultConsole.cs ├── ColorTheme.cs ├── TargetStartedEventMinimumFields.cs ├── TeamCity.MSBuild.Logger.csproj ├── ILoggerContext.cs ├── HierarchicalMessageWriter.cs ├── ProjectStartedEventMinimumFields.cs ├── LogWriter.cs ├── BuildErrorMessageUpdater.cs ├── BuildWarningMessageUpdater.cs ├── BuildMessageMessageUpdater.cs ├── DefaultColorTheme.cs ├── Parameters.cs ├── StringService.cs ├── Disposable.cs ├── DefaultLogWriter.cs ├── LoggerContext.cs ├── PerformanceCounter.cs ├── Composition.cs ├── EventFormatter.cs ├── BuildEventManager.cs └── ParametersParser.cs ├── .gitignore ├── IntegrationTests └── Console │ ├── Console.csproj │ └── Program.cs ├── TeamCity.MSBuild.Logger.sln.DotSettings ├── TeamCity.MSBuild.Logger.Tests ├── TeamCity.MSBuild.Logger.Tests.csproj ├── Helpers │ ├── CommandLineResult.cs │ ├── IntegrationTests.cs │ └── CommandLine.cs ├── ColorStorageTests.cs ├── PatchedServiceMessageTests.cs ├── MSBuildIntegrationTests.cs └── DotnetIntegrationTests.cs ├── tools ├── dotnet-sdk └── dotnet-sdk.cmd ├── TeamCity.Integration.nuspec ├── samples.cmd ├── TeamCity.MSBuild.Logger.sln └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/teamcity-msbuild-logger/HEAD/icon.png -------------------------------------------------------------------------------- /Docs/TeamCityBuildLog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JetBrains/teamcity-msbuild-logger/HEAD/Docs/TeamCityBuildLog.png -------------------------------------------------------------------------------- /test.cmd: -------------------------------------------------------------------------------- 1 | dotnet restore TeamCity.MSBuild.Logger.sln 2 | dotnet msbuild build.proj /t:Build;Publish;Test /r /p:Configuration=Release -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal interface IStatistics 4 | { 5 | void Publish(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IInitializable.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | // ReSharper disable once IdentifierTypo 4 | internal interface IInitializable { } 5 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/StatisticsMode.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal enum StatisticsMode 4 | { 5 | Default, 6 | 7 | TeamCity 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ColorThemeMode.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal enum ColorThemeMode 4 | { 5 | Default = 1, 6 | 7 | TeamCity = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TeamCityMode.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal enum TeamCityMode 4 | { 5 | Off = 1, 6 | 7 | SupportHierarchy = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IConsole.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IConsole 6 | { 7 | void Write([CanBeNull] string text); 8 | } 9 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 4 | [assembly: InternalsVisibleTo("TeamCity.MSBuild.Logger.Tests")] 5 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IDiagnostics.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | 5 | internal interface IDiagnostics 6 | { 7 | void Send(Func diagnosticsBuilder); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | _ReSharper.Caches 3 | **/obj/ 4 | **/bin/ 5 | **/*.lock.json 6 | **/*.TMP 7 | **/*.DotSettings.user 8 | **/*.csproj.user 9 | *.suo 10 | .vs/ 11 | *.user 12 | IntegrationTests/Console/global.json 13 | .idea/ 14 | -------------------------------------------------------------------------------- /IntegrationTests/Console/Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net452;netcoreapp1.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /IntegrationTests/Console/Program.cs: -------------------------------------------------------------------------------- 1 | namespace Console 2 | { 3 | class Program 4 | { 5 | static void Main(string[] args) 6 | { 7 | System.Console.WriteLine("Hello World!"); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IEventContext.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using Microsoft.Build.Framework; 4 | 5 | internal interface IEventContext 6 | { 7 | bool TryGetEvent(out BuildEventArgs buildEventArgs); 8 | } 9 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IPathService.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IPathService 6 | { 7 | [NotNull] string GetFileName([NotNull] string path); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ColorMode.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal enum ColorMode 4 | { 5 | Default = 1, 6 | 7 | TeamCity = 2, 8 | 9 | NoColor = 3, 10 | 11 | AnsiColor = 4 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IColorStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal interface IColorStorage 4 | { 5 | Color? Color { get;} 6 | 7 | void SetColor(Color color); 8 | 9 | void ResetColor(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IEventRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using Microsoft.Build.Framework; 5 | 6 | internal interface IEventRegistry 7 | { 8 | IDisposable Register(BuildEventArgs buildEventArgs); 9 | } 10 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DefaultStatistics.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | // ReSharper disable once ClassNeverInstantiated.Global 4 | internal class DefaultStatistics : IStatistics 5 | { 6 | public void Publish() 7 | { 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IParametersParser.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IParametersParser 6 | { 7 | bool TryParse([CanBeNull] string parametersString, [NotNull] Parameters parameters, out string error); 8 | } 9 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IHierarchicalMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IHierarchicalMessageWriter 6 | { 7 | void StartBlock([NotNull] string name); 8 | 9 | void FinishBlock(); 10 | } 11 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IColorTheme.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TeamCity.MSBuild.Logger 4 | { 5 | using JetBrains.Annotations; 6 | 7 | internal interface IColorTheme 8 | { 9 | ConsoleColor GetConsoleColor(Color color); 10 | 11 | [NotNull] string GetAnsiColor(Color color); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ILogFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | 6 | internal interface ILogFormatter 7 | { 8 | [NotNull] string FormatLogTimeStamp(DateTime timeStamp); 9 | 10 | [NotNull] string FormatTimeSpan(TimeSpan timeSpan); 11 | } 12 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ILogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface ILogWriter 6 | { 7 | void Write([CanBeNull] string message, [CanBeNull] IConsole console = null); 8 | 9 | void SetColor(Color color); 10 | 11 | void ResetColor(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IEnvironment.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IEnvironment 6 | { 7 | string GetEnvironmentVariable(string name); 8 | 9 | bool TargetOutputLogging { get; } 10 | 11 | [NotNull] string DiagnosticsFile { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Property.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal readonly struct Property 4 | { 5 | public readonly string Name; 6 | 7 | public readonly string Value; 8 | 9 | public Property(string name, string value) 10 | { 11 | Name = name; 12 | Value = value; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ColorStorage.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | // ReSharper disable once ClassNeverInstantiated.Global 4 | internal class ColorStorage : IColorStorage 5 | { 6 | public Color? Color { get; private set; } 7 | 8 | public void SetColor(Color color) => Color = color; 9 | 10 | public void ResetColor() => Color = default; 11 | } 12 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IPerformanceCounterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using JetBrains.Annotations; 5 | 6 | internal interface IPerformanceCounterFactory 7 | { 8 | [NotNull] 9 | IPerformanceCounter GetOrCreatePerformanceCounter([NotNull] string scopeName, IDictionary performanceCounters); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IStringService.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.Annotations; 4 | 5 | internal interface IStringService 6 | { 7 | [NotNull] string FormatResourceString([NotNull] string resourceName, [NotNull] params object[] args); 8 | 9 | // ReSharper disable once IdentifierTypo 10 | [CanBeNull] string UnescapeAll([CanBeNull] string escapedString); 11 | } 12 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/IBuildEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using JetBrains.Annotations; 4 | using Microsoft.Build.Framework; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | internal interface IBuildEventHandler where TBuildEventArgs : BuildEventArgs 8 | { 9 | void Handle([NotNull] TBuildEventArgs e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/PathService.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.IO; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | internal class PathService: IPathService 8 | { 9 | public string GetFileName(string path) 10 | { 11 | if (path == null) throw new ArgumentNullException(nameof(path)); 12 | return Path.GetFileName(path); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IDeferredMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using Microsoft.Build.Framework; 4 | 5 | internal interface IDeferredMessageWriter 6 | { 7 | void DisplayDeferredProjectStartedEvent(BuildEventContext e); 8 | 9 | void DisplayDeferredStartedEvents(BuildEventContext e); 10 | 11 | void DisplayDeferredTargetStartedEvent(BuildEventContext e); 12 | 13 | void ShownBuildEventContext(BuildEventContext e); 14 | } 15 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Color.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | internal enum Color 4 | { 5 | BuildStage, 6 | 7 | Success, 8 | 9 | Warning, 10 | 11 | WarningSummary, 12 | 13 | Error, 14 | 15 | ErrorSummary, 16 | 17 | Details, 18 | 19 | Task, 20 | 21 | SummaryHeader, 22 | 23 | SummaryInfo, 24 | 25 | PerformanceHeader, 26 | 27 | PerformanceCounterInfo, 28 | 29 | Items 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DefaultHierarchicalMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | 5 | // ReSharper disable once ClassNeverInstantiated.Global 6 | internal class DefaultHierarchicalMessageWriter : IHierarchicalMessageWriter 7 | { 8 | public void StartBlock(string name) 9 | { 10 | if (name == null) throw new ArgumentNullException(nameof(name)); 11 | } 12 | 13 | public void FinishBlock() 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DictionaryEntryKeyComparer.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | internal class DictionaryEntryKeyComparer : IComparer 7 | { 8 | public static readonly IComparer Shared = new DictionaryEntryKeyComparer(); 9 | 10 | private DictionaryEntryKeyComparer() 11 | { 12 | } 13 | 14 | public int Compare(Property x, Property y) => string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase); 15 | } 16 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TaskItem.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | internal readonly struct TaskItem 8 | { 9 | public readonly string Name; 10 | 11 | public readonly ITaskItem Item; 12 | 13 | public TaskItem([NotNull] string name, [NotNull] ITaskItem item) 14 | { 15 | Name = name ?? throw new ArgumentNullException(nameof(name)); 16 | Item = item ?? throw new ArgumentNullException(nameof(item)); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IEventFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using Microsoft.Build.Framework; 4 | using JetBrains.Annotations; 5 | 6 | internal interface IEventFormatter 7 | { 8 | [NotNull] string FormatEventMessage([NotNull] BuildErrorEventArgs e, bool removeCarriageReturn, bool showProjectFile); 9 | 10 | [NotNull] string FormatEventMessage([NotNull] BuildMessageEventArgs e, bool removeCarriageReturn, bool showProjectFile); 11 | 12 | [NotNull] string FormatEventMessage([NotNull] BuildWarningEventArgs e, bool removeCarriageReturn, bool showProjectFile); 13 | } 14 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/LogFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Globalization; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | internal class LogFormatter: ILogFormatter 8 | { 9 | public string FormatLogTimeStamp(DateTime timeStamp) 10 | { 11 | return timeStamp.ToString("HH:mm:ss.fff", CultureInfo.CurrentCulture); 12 | } 13 | 14 | public string FormatTimeSpan(TimeSpan timeSpan) 15 | { 16 | var length = Math.Min(11, timeSpan.ToString().Length); 17 | return timeSpan.ToString().Substring(0, length); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | FileSystem 9 | Release 10 | netstandard1.3 11 | bin\Release\PublishOutput 12 | 13 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TaskItemItemSpecComparer.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.Build.Framework; 6 | 7 | internal class TaskItemItemSpecComparer : IComparer 8 | { 9 | public static readonly IComparer Shared = new TaskItemItemSpecComparer(); 10 | 11 | private TaskItemItemSpecComparer() 12 | { 13 | } 14 | 15 | public int Compare(ITaskItem x, ITaskItem y) 16 | { 17 | if (x == null || y == null) 18 | { 19 | return 0; 20 | } 21 | 22 | return string.Compare(x.ItemSpec, y.ItemSpec, StringComparison.CurrentCultureIgnoreCase); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | C:\Users\Nikol\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_44d69b55\SolutionCaches -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ComparerContextNodeIdTargetId.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using Microsoft.Build.Framework; 5 | 6 | internal class ComparerContextNodeIdTargetId : IEqualityComparer 7 | { 8 | public static readonly IEqualityComparer Shared = new ComparerContextNodeIdTargetId(); 9 | 10 | public bool Equals(BuildEventContext x, BuildEventContext y) 11 | { 12 | if (x == null || y == null || x.NodeId != y.NodeId || x.ProjectContextId != y.ProjectContextId) 13 | { 14 | return false; 15 | } 16 | 17 | return x.TargetId == y.TargetId; 18 | } 19 | 20 | public int GetHashCode(BuildEventContext x) 21 | { 22 | return x.ProjectContextId + (x.NodeId << 24); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IPerformanceCounter.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedMemberInSuper.Global 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | using JetBrains.Annotations; 7 | using Microsoft.Build.Framework; 8 | 9 | internal interface IPerformanceCounter 10 | { 11 | string ScopeName { get; set; } 12 | 13 | TimeSpan ElapsedTime { get; } 14 | 15 | bool ReenteredScope { get; } 16 | 17 | int MessageIdentLevel { set; } 18 | 19 | void AddEventFinished(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp); 20 | 21 | void AddEventStarted([CanBeNull] string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp, [CanBeNull] IEqualityComparer comparer); 22 | 23 | void PrintCounterMessage(); 24 | } 25 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ComparerContextNodeId.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using Microsoft.Build.Framework; 5 | 6 | internal class ComparerContextNodeId : IEqualityComparer 7 | { 8 | public static readonly IEqualityComparer Shared = new ComparerContextNodeId(); 9 | 10 | private ComparerContextNodeId() 11 | { 12 | } 13 | 14 | public bool Equals(BuildEventContext x, BuildEventContext y) 15 | { 16 | if (x == null || y == null || x.NodeId != y.NodeId) 17 | { 18 | return false; 19 | } 20 | 21 | return x.ProjectContextId == y.ProjectContextId; 22 | } 23 | 24 | public int GetHashCode(BuildEventContext x) 25 | { 26 | return x.ProjectContextId + (x.NodeId << 24); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventContext.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable ClassNeverInstantiated.Global 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using System; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | internal class EventContext : IEventRegistry, IEventContext 9 | { 10 | [CanBeNull] private BuildEventArgs _event; 11 | 12 | public IDisposable Register(BuildEventArgs buildEventArgs) 13 | { 14 | var prevEvent = _event; 15 | _event = buildEventArgs; 16 | return Disposable.Create(() => { _event = prevEvent; }); 17 | } 18 | 19 | public bool TryGetEvent(out BuildEventArgs buildEventArgs) 20 | { 21 | if (_event != null) 22 | { 23 | buildEventArgs = _event; 24 | return true; 25 | } 26 | 27 | buildEventArgs = default; 28 | return false; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/TeamCity.MSBuild.Logger.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DescendingByElapsedTime.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | internal class DescendingByElapsedTime : IComparer 7 | { 8 | public static readonly IComparer Shared = new DescendingByElapsedTime(); 9 | 10 | private DescendingByElapsedTime() 11 | { 12 | } 13 | 14 | public int Compare(IPerformanceCounter x, IPerformanceCounter y) 15 | { 16 | if (x == null || y == null) 17 | { 18 | return 0; 19 | } 20 | 21 | if (!x.ReenteredScope && !y.ReenteredScope) 22 | { 23 | return TimeSpan.Compare(x.ElapsedTime, y.ElapsedTime); 24 | } 25 | 26 | if (x.Equals(y)) 27 | { 28 | return 0; 29 | } 30 | 31 | return x.ReenteredScope ? -1 : 1; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/FlowIdGenerator.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable ClassNeverInstantiated.Global 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using System; 5 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 6 | 7 | internal class FlowIdGenerator: IFlowIdGenerator 8 | { 9 | private readonly Parameters _parameters; 10 | private bool _isFirst = true; 11 | 12 | public FlowIdGenerator(Parameters parameters) => 13 | _parameters = parameters; 14 | 15 | public string NewFlowId() 16 | { 17 | // ReSharper disable once InvertIf 18 | if (_isFirst) 19 | { 20 | _isFirst = false; 21 | var flowId = _parameters.FlowId; 22 | if (!string.IsNullOrWhiteSpace(flowId)) 23 | { 24 | return flowId; 25 | } 26 | } 27 | 28 | return Guid.NewGuid().ToString().Replace("-", string.Empty); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/NoColorLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | internal class NoColorLogWriter : ILogWriter 8 | { 9 | [NotNull] private readonly IConsole _defaultConsole; 10 | 11 | public NoColorLogWriter( 12 | [NotNull] IConsole defaultConsole) 13 | { 14 | _defaultConsole = defaultConsole ?? throw new ArgumentNullException(nameof(defaultConsole)); 15 | } 16 | 17 | public void Write(string message, IConsole console = null) 18 | { 19 | if (string.IsNullOrEmpty(message)) 20 | { 21 | return; 22 | } 23 | 24 | (console ?? _defaultConsole).Write(message); 25 | } 26 | 27 | public void SetColor(Color color) 28 | { 29 | } 30 | 31 | public void ResetColor() 32 | { 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IBuildEventManager.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using Microsoft.Build.Framework; 5 | using JetBrains.Annotations; 6 | 7 | internal interface IBuildEventManager 8 | { 9 | void AddProjectStartedEvent([NotNull] ProjectStartedEventArgs e, bool requireTimestamp); 10 | 11 | void AddTargetStartedEvent([NotNull] TargetStartedEventArgs e, bool requireTimeStamp); 12 | 13 | [CanBeNull] ProjectStartedEventMinimumFields GetProjectStartedEvent([NotNull] BuildEventContext e); 14 | 15 | [CanBeNull] TargetStartedEventMinimumFields GetTargetStartedEvent([NotNull] BuildEventContext e); 16 | 17 | [NotNull] IEnumerable ProjectCallStackFromProject([NotNull] BuildEventContext e); 18 | 19 | void RemoveProjectStartedEvent([NotNull] BuildEventContext e); 20 | 21 | void RemoveTargetStartedEvent([NotNull] BuildEventContext e); 22 | 23 | void SetErrorWarningFlagOnCallStack([NotNull] BuildEventContext e); 24 | 25 | void Reset(); 26 | } 27 | } -------------------------------------------------------------------------------- /tools/dotnet-sdk: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function sdk_help(){ 4 | echo ".NET Command Line SDK Switcher (1.0.0) 5 | 6 | Usage: dotnet sdk [command] 7 | Usage: dotnet sdk [version] 8 | 9 | Commands: 10 | latest Swtiches to the latest .NET Core SDK version 11 | list Lists all installed .NET Core SDKs 12 | help Display help 13 | 14 | Versions: 15 | An installed version number of a .NET Core SDK" 16 | } 17 | 18 | function sdk_list(){ 19 | echo "The installed .NET Core SDKs are:" 20 | ls -1 "/usr/local/share/dotnet/sdk" 21 | } 22 | 23 | function sdk_latest(){ 24 | if [ -e global.json ]; then 25 | rm global.json 26 | fi 27 | 28 | echo ".NET Core SDK version switched to latest version." 29 | dotnet --version 30 | } 31 | 32 | case "$1" in 33 | "help") 34 | sdk_help 35 | ;; 36 | "") 37 | sdk_help 38 | ;; 39 | "list") 40 | sdk_list 41 | ;; 42 | "latest") 43 | sdk_latest 44 | ;; 45 | *) 46 | echo "Switching .NET Core SDK version to $1" 47 | echo "{ 48 | \"sdk\": { 49 | \"version\": \"$1\" 50 | } 51 | }" >> global.json 52 | ;; 53 | esac 54 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/Helpers/CommandLineResult.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable All 2 | namespace TeamCity.MSBuild.Logger.Tests.Helpers 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | using JetBrains.Annotations; 7 | 8 | public class CommandLineResult 9 | { 10 | public CommandLineResult( 11 | [NotNull] CommandLine commandLine, 12 | int exitCode, 13 | [NotNull] IList stdOut, 14 | [NotNull] IList stdError) 15 | { 16 | CommandLine = commandLine ?? throw new ArgumentNullException(nameof(commandLine)); 17 | ExitCode = exitCode; 18 | StdOut = stdOut ?? throw new ArgumentNullException(nameof(stdOut)); 19 | StdError = stdError ?? throw new ArgumentNullException(nameof(stdError)); 20 | } 21 | 22 | public CommandLine CommandLine { [NotNull] get; } 23 | 24 | public int ExitCode { get; } 25 | 26 | public IEnumerable StdOut { [NotNull] get; } 27 | 28 | public IEnumerable StdError { [NotNull] get; } 29 | } 30 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/HierarchicalContext.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | 6 | internal class HierarchicalContext: IDisposable 7 | { 8 | public const int DefaultFlowId = 0; 9 | private static readonly HierarchicalContext Default = new HierarchicalContext(0); 10 | 11 | [CanBeNull][ThreadStatic] private static HierarchicalContext _currentHierarchicalContext; 12 | private readonly HierarchicalContext _prevHierarchicalContext; 13 | 14 | public HierarchicalContext([CanBeNull] int? flowId) 15 | { 16 | FlowId = flowId ?? DefaultFlowId; 17 | _prevHierarchicalContext = _currentHierarchicalContext; 18 | _currentHierarchicalContext = this; 19 | } 20 | 21 | [NotNull] 22 | public static HierarchicalContext Current => _currentHierarchicalContext ?? Default; 23 | 24 | public int FlowId { get; } 25 | 26 | public void Dispose() 27 | { 28 | _currentHierarchicalContext = _prevHierarchicalContext; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/PatchedServiceMessage.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using JetBrains.Annotations; 5 | using JetBrains.TeamCity.ServiceMessages; 6 | 7 | internal class PatchedServiceMessage : IServiceMessage 8 | { 9 | [NotNull] private readonly IServiceMessage _message; 10 | [NotNull] private readonly Dictionary _values = new Dictionary(); 11 | 12 | public PatchedServiceMessage([NotNull] IServiceMessage message) 13 | { 14 | _message = message; 15 | foreach (var key in _message.Keys) 16 | { 17 | _values[key] = message.GetValue(key); 18 | } 19 | } 20 | 21 | public string GetValue(string key) => _values.TryGetValue(key, out var value) ? value : default; 22 | 23 | public string Name => _message.Name; 24 | 25 | public string DefaultValue => _message.DefaultValue; 26 | 27 | public IEnumerable Keys => _values.Keys; 28 | 29 | public void Add(string name, string value) => _values[name] = value; 30 | } 31 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TeamCityMSBuildLogger.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable UnusedType.Global 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using Microsoft.Build.Framework; 5 | 6 | // ReSharper disable once UnusedMember.Global 7 | public class TeamCityMsBuildLogger : INodeLogger 8 | { 9 | private readonly Composition _composition = new(); 10 | 11 | private INodeLogger Logger => _composition.Logger; 12 | 13 | public string Parameters 14 | { 15 | get => Logger.Parameters; 16 | set => Logger.Parameters = value; 17 | } 18 | 19 | public LoggerVerbosity Verbosity 20 | { 21 | get => Logger.Verbosity; 22 | set => Logger.Verbosity = value; 23 | } 24 | 25 | public void Initialize(IEventSource eventSource, int nodeCount) => Logger.Initialize(eventSource, nodeCount); 26 | 27 | public void Initialize(IEventSource eventSource) => Logger.Initialize(eventSource); 28 | 29 | public void Shutdown() 30 | { 31 | Logger.Shutdown(); 32 | _composition.Dispose(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TeamCity.Integration.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TeamCity.Dotnet.Integration 5 | 1.0.0 6 | JetBrains TeamCity dotnet integration package 7 | Provides the TeamCity integration 8 | NikolayP 9 | JetBrains 10 | false 11 | https://github.com/JetBrains/TeamCity.MSBuild.Logger/raw/master/LICENSE 12 | https://github.com/JetBrains/TeamCity.MSBuild.Logger 13 | https://github.com/JetBrains/TeamCity.MSBuild.Logger/raw/master/icon.png 14 | true 15 | JetBrains 16 | TeamCity integration 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TeamCityStatistics.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable NotAccessedField.Local 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using System; 5 | using JetBrains.Annotations; 6 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class TeamCityStatistics : IStatistics 10 | { 11 | [NotNull] private readonly ITeamCityWriter _writer; 12 | [NotNull] private readonly ILoggerContext _context; 13 | 14 | public TeamCityStatistics( 15 | [NotNull] ILoggerContext context, 16 | [NotNull] ITeamCityWriter writer) 17 | { 18 | _writer = writer ?? throw new ArgumentNullException(nameof(writer)); 19 | _context = context ?? throw new ArgumentNullException(nameof(context)); 20 | } 21 | 22 | public void Publish() 23 | { 24 | // _writer.WriteBuildStatistics("BuildStatsW", _context.WarningCount.ToString(CultureInfo.InvariantCulture)); 25 | // _writer.WriteBuildStatistics("BuildStatsE", _context.ErrorCount.ToString(CultureInfo.InvariantCulture)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ErrorWarningSummaryDictionaryKey.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using Microsoft.Build.Framework; 5 | 6 | internal class ErrorWarningSummaryDictionaryKey 7 | { 8 | internal ErrorWarningSummaryDictionaryKey(BuildEventContext entryPoint, string targetName) 9 | { 10 | EntryPointContext = entryPoint; 11 | TargetName = targetName ?? string.Empty; 12 | } 13 | 14 | public BuildEventContext EntryPointContext { get; } 15 | 16 | public string TargetName { get; } 17 | 18 | public override bool Equals(object obj) 19 | { 20 | if (!(obj is ErrorWarningSummaryDictionaryKey summaryDictionaryKey) || !ComparerContextNodeId.Shared.Equals(EntryPointContext, summaryDictionaryKey.EntryPointContext)) 21 | { 22 | return false; 23 | } 24 | 25 | return string.Compare(TargetName, summaryDictionaryKey.TargetName, StringComparison.OrdinalIgnoreCase) == 0; 26 | } 27 | 28 | public override int GetHashCode() 29 | { 30 | return EntryPointContext.GetHashCode() + TargetName.GetHashCode(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Environment.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class Environment : IEnvironment 10 | { 11 | private static readonly Dictionary Envs = new Dictionary(StringComparer.OrdinalIgnoreCase); 12 | 13 | static Environment() 14 | { 15 | foreach (var entry in System.Environment.GetEnvironmentVariables().OfType()) 16 | { 17 | var key = entry.Key?.ToString()?.Trim() ?? string.Empty; 18 | var value = entry.Value?.ToString()?.Trim() ?? string.Empty; 19 | Envs[key] = value; 20 | } 21 | } 22 | 23 | public string GetEnvironmentVariable(string name) => Envs.TryGetValue(name, out var val) ? val : string.Empty; 24 | 25 | public string DiagnosticsFile => System.Environment.GetEnvironmentVariable("MSBUILDDIAGNOSTICSFILE") ?? string.Empty; 26 | 27 | public bool TargetOutputLogging => !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("MSBUILDTARGETOUTPUTLOGGING")); 28 | } 29 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/IMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.Build.Framework; 6 | using JetBrains.Annotations; 7 | 8 | internal interface IMessageWriter 9 | { 10 | void DisplayCounters(IDictionary counters); 11 | 12 | void PrintMessage(BuildMessageEventArgs e, bool lightenText); 13 | 14 | void WriteLinePrefix(BuildEventContext e, DateTime eventTimeStamp, bool isMessagePrefix); 15 | 16 | void WriteLinePrefix(string key, DateTime eventTimeStamp, bool isMessagePrefix); 17 | 18 | void WriteLinePretty(int indentLevel, [NotNull] string formattedString); 19 | 20 | void WriteLinePretty([NotNull] string formattedString); 21 | 22 | void WriteLinePrettyFromResource(int indentLevel, [NotNull] string resourceString, [NotNull] params object[] args); 23 | 24 | void WriteLinePrettyFromResource([NotNull] string resourceString, [NotNull] params object[] args); 25 | 26 | void WriteMessageAligned(string message, bool prefixAlreadyWritten, int prefixAdjustment = 0); 27 | 28 | void WriteNewLine(); 29 | 30 | bool WriteTargetMessagePrefix(BuildEventArgs e, BuildEventContext context, DateTime timeStamp); 31 | } 32 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/StringBuilderCache.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Text; 6 | 7 | internal static class StringBuilderCache 8 | { 9 | [ThreadStatic] private static StringBuilder _cachedInstance; 10 | 11 | [SuppressMessage("ReSharper", "InvertIf")] 12 | public static StringBuilder Acquire(int capacity = 16) 13 | { 14 | if (capacity <= 360) 15 | { 16 | var tCachedInstance = _cachedInstance; 17 | if (tCachedInstance != null && capacity <= tCachedInstance.Capacity) 18 | { 19 | _cachedInstance = null; 20 | tCachedInstance.Length = 0; 21 | return tCachedInstance; 22 | } 23 | } 24 | 25 | return new StringBuilder(capacity); 26 | } 27 | 28 | public static string GetStringAndRelease(StringBuilder sb) 29 | { 30 | var str = sb.ToString(); 31 | Release(sb); 32 | return str; 33 | } 34 | 35 | private static void Release(StringBuilder sb) 36 | { 37 | if (sb.Capacity > 360) 38 | { 39 | return; 40 | } 41 | 42 | _cachedInstance = sb; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/PerformanceCounterFactory.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class PerformanceCounterFactory: IPerformanceCounterFactory 9 | { 10 | private readonly Func _performanceCounterFactory; 11 | 12 | public PerformanceCounterFactory( 13 | [NotNull] Func performanceCounterFactory) 14 | { 15 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 16 | } 17 | 18 | public IPerformanceCounter GetOrCreatePerformanceCounter(string scopeName, IDictionary performanceCounters) 19 | { 20 | if (scopeName == null) throw new ArgumentNullException(nameof(scopeName)); 21 | if (performanceCounters.TryGetValue(scopeName, out var performanceCounter)) 22 | { 23 | return performanceCounter; 24 | } 25 | 26 | performanceCounter = _performanceCounterFactory(); 27 | performanceCounter.ScopeName = scopeName; 28 | performanceCounters.Add(scopeName, performanceCounter); 29 | 30 | return performanceCounter; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TeamCityColorTheme.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Pure.DI; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class TeamCityColorTheme : IColorTheme 9 | { 10 | [NotNull] private readonly IColorTheme _defaultColorTheme; 11 | 12 | public TeamCityColorTheme( 13 | [NotNull][Tag(ColorThemeMode.Default)] IColorTheme defaultColorTheme) 14 | { 15 | _defaultColorTheme = defaultColorTheme ?? throw new ArgumentNullException(nameof(defaultColorTheme)); 16 | } 17 | 18 | public ConsoleColor GetConsoleColor(Color color) 19 | { 20 | return _defaultColorTheme.GetConsoleColor(color); 21 | } 22 | 23 | public string GetAnsiColor(Color color) 24 | { 25 | // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault 26 | switch (color) 27 | { 28 | case Color.SummaryInfo: 29 | case Color.PerformanceCounterInfo: 30 | return "35"; 31 | case Color.Details: 32 | return "34;1"; 33 | case Color.Task: 34 | return "36"; 35 | default: 36 | return _defaultColorTheme.GetAnsiColor(color); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Statistics.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Pure.DI; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class Statistics : IStatistics 10 | { 11 | [NotNull] private readonly ILoggerContext _context; 12 | private readonly Dictionary _statistics; 13 | 14 | public Statistics( 15 | [NotNull] ILoggerContext context, 16 | [NotNull][Tag(StatisticsMode.Default)] IStatistics defaultStatistics, 17 | [NotNull][Tag(StatisticsMode.TeamCity)] IStatistics teamcityStatistics) 18 | { 19 | _context = context ?? throw new ArgumentNullException(nameof(context)); 20 | _statistics = new Dictionary 21 | { 22 | { StatisticsMode.Default, defaultStatistics ?? throw new ArgumentNullException(nameof(defaultStatistics))}, 23 | { StatisticsMode.TeamCity, teamcityStatistics ?? throw new ArgumentNullException(nameof(teamcityStatistics))} 24 | }; 25 | } 26 | 27 | private IStatistics CurrentStatistics => _statistics[_context.Parameters?.StatisticsMode ?? StatisticsMode.Default]; 28 | 29 | public void Publish() 30 | { 31 | CurrentStatistics.Publish(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ProjectFullKey.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Globalization; 4 | using Microsoft.Build.Framework; 5 | 6 | internal class ProjectFullKey 7 | { 8 | public ProjectFullKey(int projectKey, int entryPointKey) 9 | { 10 | ProjectKey = projectKey; 11 | EntryPointKey = entryPointKey; 12 | } 13 | 14 | public int ProjectKey { get; } 15 | 16 | public int EntryPointKey { get; } 17 | 18 | public string ToString(LoggerVerbosity verbosity) 19 | { 20 | return verbosity <= LoggerVerbosity.Normal ? string.Format(CultureInfo.InvariantCulture, "{0}", ProjectKey) : ToString(); 21 | } 22 | 23 | public override string ToString() 24 | { 25 | return EntryPointKey <= 1 ? string.Format(CultureInfo.InvariantCulture, "{0}", ProjectKey) : string.Format(CultureInfo.InvariantCulture, "{0}:{1}", ProjectKey, EntryPointKey); 26 | } 27 | 28 | public override bool Equals(object obj) 29 | { 30 | if (obj is ProjectFullKey projectFullKey && projectFullKey.ProjectKey == ProjectKey) 31 | { 32 | return projectFullKey.EntryPointKey == EntryPointKey; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | return ProjectKey + (EntryPointKey << 16); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples.cmd: -------------------------------------------------------------------------------- 1 | rem msbuild IntegrationTests\Console\Console.csproj /t:build /v:diag /noconsolelogger /logger:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,C:\Projects\TeamCity\TeamCity.MSBuild.Logger\TeamCity.MSBuild.Logger\bin\Debug\net452\TeamCity.MSBuild.Logger.dll 2 | 3 | rem msbuild IntegrationTests\Console\Console.csproj /t:build /noconsolelogger /logger:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,C:\Projects\TeamCity\TeamCity.MSBuild.Logger\TeamCity.MSBuild.Logger\bin\Debug\net45\TeamCity.MSBuild.Logger.dll;teamcity /verbosity:diag 4 | rem dotnet "restore" "C:\Projects\TeamCity\TeamCity.MSBuild.Logger\IntegrationTests\Console\Console.csproj" "--verbosity" "detailed" "/noconsolelogger" "/m:10" "/l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,C:\Projects\TeamCity\TeamCity.MSBuild.Logger\TeamCity.MSBuild.Logger\bin\Debug\netcoreapp1.0\publish\TeamCity.MSBuild.Logger.dll" 5 | rem dotnet "restore" "C:\Projects\TeamCity\TeamCity.MSBuild.Logger\IntegrationTests\Console\Console.csproj" "--verbosity" "detailed" 6 | 7 | SET MSBUILDDIAGNOSTICSFILE=C:\Projects\TeamCity\TeamCity.MSBuild.Logger\aaa.txt 8 | rem dotnet build C:\Projects\TeamCity\TeamCity.MSBuild.Logger\IntegrationTests\Console\Console.csproj --verbosity d /noconsolelogger /l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,bin\publish\msbuild15\TeamCity.MSBuild.Logger.dll;teamcity 9 | dotnet build C:\Projects\TeamCity\TeamCity.MSBuild.Logger\IntegrationTests\Console\Console.csproj /noconsolelogger /l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,bin\publish\msbuild15\TeamCity.MSBuild.Logger.dll;teamcity 10 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Diagnostics.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.IO; 5 | using JetBrains.Annotations; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class Diagnostics : IDiagnostics 9 | { 10 | private readonly bool _isEnabled; 11 | [NotNull] private readonly string _diagnosticsFile; 12 | [NotNull] private readonly object _lockObject = new object(); 13 | 14 | public Diagnostics([NotNull] IEnvironment environment) 15 | { 16 | if (environment == null) throw new ArgumentNullException(nameof(environment)); 17 | _diagnosticsFile = environment.DiagnosticsFile; 18 | _isEnabled = !string.IsNullOrWhiteSpace(_diagnosticsFile); 19 | } 20 | 21 | public void Send(Func diagnosticsBuilder) 22 | { 23 | if (!_isEnabled) 24 | { 25 | return; 26 | } 27 | 28 | try 29 | { 30 | var diagnosticsInfo = GetPrefix() + diagnosticsBuilder() + System.Environment.NewLine; 31 | lock (_lockObject) 32 | { 33 | File.AppendAllText(_diagnosticsFile, diagnosticsInfo); 34 | } 35 | } 36 | // ReSharper disable once EmptyGeneralCatchClause 37 | catch 38 | { 39 | } 40 | } 41 | 42 | private static string GetPrefix() => $"{System.Threading.Thread.CurrentThread.ManagedThreadId:0000}: "; 43 | } 44 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/AnsiLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | 6 | // ReSharper disable once ClassNeverInstantiated.Global 7 | internal class AnsiLogWriter : ILogWriter 8 | { 9 | [NotNull] private readonly IColorStorage _colorStorage; 10 | [NotNull] private readonly IColorTheme _colorTheme; 11 | [NotNull] private readonly IConsole _defaultConsole; 12 | 13 | public AnsiLogWriter( 14 | [NotNull] IConsole defaultConsole, 15 | [NotNull] IColorTheme colorTheme, 16 | [NotNull] IColorStorage colorStorage) 17 | { 18 | _colorStorage = colorStorage ?? throw new ArgumentNullException(nameof(colorStorage)); 19 | _colorTheme = colorTheme ?? throw new ArgumentNullException(nameof(colorTheme)); 20 | _defaultConsole = defaultConsole ?? throw new ArgumentNullException(nameof(defaultConsole)); 21 | } 22 | 23 | public void Write(string message, IConsole console = null) 24 | { 25 | if (string.IsNullOrEmpty(message)) 26 | { 27 | return; 28 | } 29 | 30 | (console ?? _defaultConsole).Write(_colorStorage.Color.HasValue ? $"\x001B[{_colorTheme.GetAnsiColor(_colorStorage.Color.Value)}m{message}" : message); 31 | } 32 | 33 | public void SetColor(Color color) 34 | { 35 | _colorStorage.SetColor(color); 36 | } 37 | 38 | public void ResetColor() 39 | { 40 | _colorStorage.ResetColor(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DefaultConsole.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Threading; 6 | using JetBrains.Annotations; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class DefaultConsole : IConsole, IInitializable 10 | { 11 | [NotNull] private readonly IDiagnostics _diagnostics; 12 | [NotNull] private readonly TextWriter _out; 13 | // ReSharper disable once IdentifierTypo 14 | private int _reentrancy; 15 | 16 | public DefaultConsole([NotNull] IDiagnostics diagnostics) 17 | { 18 | _diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); 19 | // https://youtrack.jetbrains.com/issue/TW-72330 20 | _out = Console.Out; 21 | } 22 | 23 | public void Write(string text) 24 | { 25 | if (string.IsNullOrEmpty(text)) 26 | { 27 | return; 28 | } 29 | 30 | // ReSharper disable once IdentifierTypo 31 | var reentrancy = Interlocked.Increment(ref _reentrancy) - 1; 32 | // ReSharper disable once AccessToModifiedClosure 33 | _diagnostics.Send(() => $"[{reentrancy} +] Write({text.Trim()})"); 34 | try 35 | { 36 | _out.Write(text); 37 | } 38 | finally 39 | { 40 | reentrancy = Interlocked.Decrement(ref _reentrancy); 41 | _diagnostics.Send(() => $"[{reentrancy} -] Write({text.Trim()})"); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ColorTheme.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Pure.DI; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class ColorTheme : IColorTheme 10 | { 11 | [NotNull] private readonly Dictionary _colorThemes; 12 | [NotNull] private readonly ILoggerContext _context; 13 | 14 | public ColorTheme( 15 | [NotNull] ILoggerContext context, 16 | [NotNull][Tag(ColorThemeMode.Default)] IColorTheme defaultColorTheme, 17 | [NotNull][Tag(ColorThemeMode.TeamCity)] IColorTheme teamCityColorTheme) 18 | { 19 | if (defaultColorTheme == null) throw new ArgumentNullException(nameof(defaultColorTheme)); 20 | if (teamCityColorTheme == null) throw new ArgumentNullException(nameof(teamCityColorTheme)); 21 | _colorThemes = new Dictionary 22 | { 23 | { ColorThemeMode.Default, defaultColorTheme }, 24 | { ColorThemeMode.TeamCity, teamCityColorTheme } 25 | }; 26 | 27 | _context = context ?? throw new ArgumentNullException(nameof(context)); 28 | } 29 | 30 | private IColorTheme CurrentColorTheme => _colorThemes[_context.Parameters?.ColorThemeMode ?? ColorThemeMode.Default]; 31 | 32 | public ConsoleColor GetConsoleColor(Color color) 33 | { 34 | return CurrentColorTheme.GetConsoleColor(color); 35 | } 36 | 37 | public string GetAnsiColor(Color color) 38 | { 39 | return CurrentColorTheme.GetAnsiColor(color); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TargetStartedEventMinimumFields.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using Microsoft.Build.Framework; 6 | 7 | [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] 8 | [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] 9 | internal class TargetStartedEventMinimumFields 10 | { 11 | public DateTime TimeStamp { get; } 12 | 13 | public string TargetName { get; } 14 | 15 | public string TargetFile { get; } 16 | 17 | public string ProjectFile { get; } 18 | 19 | public string Message { get; } 20 | 21 | public bool ShowTargetFinishedEvent { get; set; } 22 | 23 | public bool ErrorInTarget { get; set; } 24 | 25 | public BuildEventContext TargetBuildEventContext { get; } 26 | 27 | public string ParentTarget { get; } 28 | 29 | public string FullTargetKey { get; } 30 | 31 | public TargetStartedEventMinimumFields( 32 | TargetStartedEventArgs startedEvent, 33 | bool requireTimeStamp) 34 | { 35 | TargetName = startedEvent.TargetName; 36 | TargetFile = startedEvent.TargetFile; 37 | ProjectFile = startedEvent.ProjectFile; 38 | ShowTargetFinishedEvent = false; 39 | ErrorInTarget = false; 40 | Message = startedEvent.Message; 41 | TargetBuildEventContext = startedEvent.BuildEventContext; 42 | if (requireTimeStamp) 43 | { 44 | TimeStamp = startedEvent.Timestamp; 45 | } 46 | 47 | ParentTarget = startedEvent.ParentTarget; 48 | FullTargetKey = $"{TargetFile}.{TargetName}"; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/TeamCity.MSBuild.Logger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6;net452;net45 5 | false 6 | latest 7 | disable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ..\Libs\MSBuild.14\Microsoft.Build.Framework.dll 21 | 22 | 23 | 24 | 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | 32 | 33 | 34 | 35 | True 36 | True 37 | Resources.resx 38 | 39 | 40 | 41 | 42 | 43 | ResXFileCodeGenerator 44 | Resources.Designer.cs 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamCity.MSBuild.Logger", "TeamCity.MSBuild.Logger\TeamCity.MSBuild.Logger.csproj", "{71557836-EE1B-4027-B196-F70AEAE3ECBB}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeamCity.MSBuild.Logger.Tests", "TeamCity.MSBuild.Logger.Tests\TeamCity.MSBuild.Logger.Tests.csproj", "{7ED24502-C598-4388-B775-D29E6F496010}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{07E124E1-8B41-45BF-8FA4-6EFD47927E4E}" 11 | ProjectSection(SolutionItems) = preProject 12 | build.proj = build.proj 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {71557836-EE1B-4027-B196-F70AEAE3ECBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {71557836-EE1B-4027-B196-F70AEAE3ECBB}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {71557836-EE1B-4027-B196-F70AEAE3ECBB}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {71557836-EE1B-4027-B196-F70AEAE3ECBB}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {7ED24502-C598-4388-B775-D29E6F496010}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {7ED24502-C598-4388-B775-D29E6F496010}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {7ED24502-C598-4388-B775-D29E6F496010}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {7ED24502-C598-4388-B775-D29E6F496010}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ILoggerContext.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | internal interface ILoggerContext 9 | { 10 | DateTime BuildStarted { get; set; } 11 | 12 | int CurrentIndentLevel { get; } 13 | 14 | IDictionary> DeferredMessages { [NotNull] get; } 15 | 16 | int ErrorCount { get; set; } 17 | 18 | IList ErrorList { [CanBeNull] get; } 19 | 20 | bool HasBuildStarted { get; set; } 21 | 22 | [CanBeNull] BuildEventContext LastDisplayedBuildEventContext { get; set; } 23 | 24 | ProjectFullKey LastProjectFullKey { get; set; } 25 | 26 | int NumberOfProcessors { get; } 27 | 28 | Parameters Parameters { [NotNull] get; } 29 | 30 | int PrefixWidth { get; set; } 31 | 32 | bool SkipProjectStartedText { get; } 33 | 34 | IDictionary ProjectPerformanceCounters { [NotNull] get; } 35 | 36 | IDictionary TargetPerformanceCounters { [NotNull] get; } 37 | 38 | IDictionary TaskPerformanceCounters { [NotNull] get; } 39 | 40 | LoggerVerbosity Verbosity { get; } 41 | 42 | int WarningCount { get; set; } 43 | 44 | IList WarningList { [CanBeNull] get; } 45 | 46 | [NotNull] ProjectFullKey GetFullProjectKey([CanBeNull] BuildEventContext e); 47 | 48 | void Initialize( 49 | int numberOfProcessors, 50 | bool skipProjectStartedText, 51 | [NotNull] Parameters parameters); 52 | 53 | bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity); 54 | 55 | void ResetConsoleLoggerState(); 56 | } 57 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/TargetStartedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class TargetStartedHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IBuildEventManager _buildEventManager; 11 | [NotNull] private readonly ILoggerContext _context; 12 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 13 | 14 | public TargetStartedHandler( 15 | [NotNull] ILoggerContext context, 16 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 17 | [NotNull] IBuildEventManager buildEventManager) 18 | { 19 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 20 | _context = context ?? throw new ArgumentNullException(nameof(context)); 21 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 22 | } 23 | 24 | public void Handle(TargetStartedEventArgs e) 25 | { 26 | if (e == null) throw new ArgumentNullException(nameof(e)); 27 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 28 | _buildEventManager.AddTargetStartedEvent(e, _context.Parameters.ShowTimeStamp || _context.IsVerbosityAtLeast(LoggerVerbosity.Detailed)); 29 | if (_context.Parameters.ShowPerfSummary) 30 | { 31 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.TargetName, _context.TargetPerformanceCounters).AddEventStarted(null, e.BuildEventContext, e.Timestamp, ComparerContextNodeIdTargetId.Shared); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/HierarchicalMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Pure.DI; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class HierarchicalMessageWriter : IHierarchicalMessageWriter 10 | { 11 | [NotNull] private readonly Dictionary _hierarchicalMessageWriter; 12 | [NotNull] private readonly ILoggerContext _context; 13 | 14 | public HierarchicalMessageWriter( 15 | [NotNull] ILoggerContext context, 16 | [NotNull][Tag(TeamCityMode.Off)] IHierarchicalMessageWriter defaultHierarchicalMessageWriter, 17 | [NotNull][Tag(TeamCityMode.SupportHierarchy)] IHierarchicalMessageWriter teamcityHierarchicalMessageWriter) 18 | { 19 | _context = context ?? throw new ArgumentNullException(nameof(context)); 20 | _hierarchicalMessageWriter = new Dictionary 21 | { 22 | { TeamCityMode.Off, defaultHierarchicalMessageWriter ?? throw new ArgumentNullException(nameof(defaultHierarchicalMessageWriter))}, 23 | { TeamCityMode.SupportHierarchy, teamcityHierarchicalMessageWriter ?? throw new ArgumentNullException(nameof(teamcityHierarchicalMessageWriter))} 24 | }; 25 | } 26 | 27 | private IHierarchicalMessageWriter CurrentHierarchicalMessageWriter => _hierarchicalMessageWriter[_context.Parameters?.TeamCityMode ?? TeamCityMode.Off]; 28 | 29 | public void StartBlock(string name) 30 | { 31 | if (name == null) throw new ArgumentNullException(nameof(name)); 32 | CurrentHierarchicalMessageWriter.StartBlock(name); 33 | } 34 | 35 | public void FinishBlock() 36 | { 37 | CurrentHierarchicalMessageWriter.FinishBlock(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ProjectStartedEventMinimumFields.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using Microsoft.Build.Framework; 5 | 6 | internal class ProjectStartedEventMinimumFields 7 | { 8 | private readonly ProjectFullKey _projectFullKey; 9 | 10 | public DateTime TimeStamp { get; } 11 | 12 | public int ProjectKey => _projectFullKey.ProjectKey; 13 | 14 | public int EntryPointKey => _projectFullKey.EntryPointKey; 15 | 16 | public string FullProjectKey => _projectFullKey.ToString(); 17 | 18 | public ProjectStartedEventMinimumFields ParentProjectStartedEvent { get; } 19 | 20 | public string TargetNames { get; } 21 | 22 | // ReSharper disable once MemberCanBePrivate.Global 23 | // ReSharper disable once UnusedAutoPropertyAccessor.Global 24 | public int ProjectId { get; } 25 | 26 | public string ProjectFile { get; } 27 | 28 | public bool ShowProjectFinishedEvent { get; set; } 29 | 30 | public bool ErrorInProject { get; set; } 31 | 32 | public BuildEventContext ProjectBuildEventContext { get; } 33 | 34 | public ProjectStartedEventMinimumFields( 35 | int projectKey, 36 | int entryPointKey, 37 | ProjectStartedEventArgs startedEvent, 38 | ProjectStartedEventMinimumFields parentProjectStartedEvent, 39 | bool requireTimeStamp) 40 | { 41 | TargetNames = startedEvent.TargetNames; 42 | ProjectFile = startedEvent.ProjectFile; 43 | ShowProjectFinishedEvent = false; 44 | ErrorInProject = false; 45 | ProjectId = startedEvent.ProjectId; 46 | ProjectBuildEventContext = startedEvent.BuildEventContext; 47 | ParentProjectStartedEvent = parentProjectStartedEvent; 48 | _projectFullKey = new ProjectFullKey(projectKey, entryPointKey); 49 | if (requireTimeStamp) 50 | { 51 | TimeStamp = startedEvent.Timestamp; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/LogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System.Collections.Generic; 4 | using System; 5 | using JetBrains.Annotations; 6 | using Pure.DI; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class LogWriter : ILogWriter 10 | { 11 | [NotNull] private readonly Dictionary _logWriters; 12 | [NotNull] private readonly ILoggerContext _context; 13 | 14 | public LogWriter( 15 | [NotNull] ILoggerContext context, 16 | [NotNull][Tag(ColorMode.Default)] ILogWriter defaultLogWriter, 17 | [NotNull][Tag(ColorMode.TeamCity)] ILogWriter ansiLogWriter, 18 | [NotNull][Tag(ColorMode.NoColor)] ILogWriter noColorLogWriter, 19 | [NotNull][Tag(ColorMode.AnsiColor)] ILogWriter ansiColorLogWriter) 20 | { 21 | _logWriters = new Dictionary 22 | { 23 | { ColorMode.Default, defaultLogWriter ?? throw new ArgumentNullException(nameof(defaultLogWriter))}, 24 | { ColorMode.TeamCity, ansiLogWriter ?? throw new ArgumentNullException(nameof(ansiLogWriter))}, 25 | { ColorMode.NoColor, noColorLogWriter ?? throw new ArgumentNullException(nameof(noColorLogWriter))}, 26 | { ColorMode.AnsiColor, ansiColorLogWriter ?? throw new ArgumentNullException(nameof(ansiColorLogWriter))} 27 | }; 28 | 29 | _context = context ?? throw new ArgumentNullException(nameof(context)); 30 | } 31 | 32 | private ILogWriter CurrentLogWriter => _logWriters[_context.Parameters?.ColorMode ?? ColorMode.Default]; 33 | 34 | public void Write(string message, IConsole console = null) 35 | { 36 | CurrentLogWriter.Write(message, console); 37 | } 38 | 39 | public void SetColor(Color color) 40 | { 41 | CurrentLogWriter.SetColor(color); 42 | } 43 | 44 | public void ResetColor() 45 | { 46 | CurrentLogWriter.ResetColor(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/ColorStorageTests.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.Tests 2 | { 3 | using Shouldly; 4 | using Xunit; 5 | 6 | public class ColorStorageTests 7 | { 8 | [Fact] 9 | public void ShouldReturnNullColorByDefault() 10 | { 11 | // Given 12 | var storage = new ColorStorage(); 13 | 14 | // When 15 | 16 | // Then 17 | storage.Color.ShouldBe(default); 18 | } 19 | 20 | [Fact] 21 | public void ShouldStoreColor() 22 | { 23 | // Given 24 | var storage = new ColorStorage(); 25 | 26 | // When 27 | storage.SetColor(Color.Error); 28 | 29 | // Then 30 | storage.Color.ShouldBe(Color.Error); 31 | } 32 | 33 | [Fact] 34 | public void ShouldResetColor() 35 | { 36 | // Given 37 | var storage = new ColorStorage(); 38 | 39 | // When 40 | storage.SetColor(Color.Error); 41 | storage.SetColor(Color.Warning); 42 | storage.ResetColor(); 43 | 44 | // Then 45 | storage.Color.ShouldBe(default); 46 | } 47 | 48 | [Fact] 49 | public void ShouldResetToNullWhenLastReset() 50 | { 51 | // Given 52 | var storage = new ColorStorage(); 53 | 54 | // When 55 | storage.SetColor(Color.Error); 56 | storage.ResetColor(); 57 | 58 | // Then 59 | storage.Color.ShouldBe(default); 60 | } 61 | 62 | [Fact] 63 | public void ShouldResetToNullWhenTooManyReset() 64 | { 65 | // Given 66 | var storage = new ColorStorage(); 67 | 68 | // When 69 | storage.SetColor(Color.Error); 70 | storage.ResetColor(); 71 | storage.ResetColor(); 72 | storage.ResetColor(); 73 | storage.ResetColor(); 74 | 75 | // Then 76 | storage.Color.ShouldBe(default); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/CustomEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class CustomEventHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 11 | [NotNull] private readonly IMessageWriter _messageWriter; 12 | [NotNull] private readonly ILoggerContext _context; 13 | 14 | public CustomEventHandler( 15 | [NotNull] ILoggerContext context, 16 | [NotNull] IMessageWriter messageWriter, 17 | [NotNull] IDeferredMessageWriter deferredMessageWriter) 18 | { 19 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 20 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 21 | _context = context ?? throw new ArgumentNullException(nameof(context)); 22 | } 23 | 24 | public void Handle(CustomBuildEventArgs e) 25 | { 26 | if (e == null) throw new ArgumentNullException(nameof(e)); 27 | if (_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 28 | { 29 | return; 30 | } 31 | 32 | if (e == null) throw new ArgumentNullException(nameof(e)); 33 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 34 | 35 | if (!_context.IsVerbosityAtLeast(LoggerVerbosity.Detailed) || e.Message == null) 36 | { 37 | return; 38 | } 39 | 40 | _deferredMessageWriter.DisplayDeferredStartedEvents(e.BuildEventContext); 41 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 42 | _messageWriter.WriteMessageAligned(e.Message, true); 43 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/BuildErrorMessageUpdater.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.TeamCity.ServiceMessages; 4 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 5 | using Microsoft.Build.Framework; 6 | 7 | internal class BuildErrorMessageUpdater: IServiceMessageUpdater 8 | { 9 | private readonly IEventContext _eventContext; 10 | 11 | public BuildErrorMessageUpdater(IEventContext eventContext) => 12 | _eventContext = eventContext; 13 | 14 | public IServiceMessage UpdateServiceMessage(IServiceMessage message) 15 | { 16 | if (_eventContext.TryGetEvent(out var buildEventManager) 17 | && buildEventManager is BuildErrorEventArgs error) 18 | { 19 | var newMessage = new PatchedServiceMessage(message); 20 | if (!string.IsNullOrWhiteSpace(error.Code)) 21 | { 22 | newMessage.Add("code", error.Code); 23 | } 24 | 25 | if (!string.IsNullOrWhiteSpace(error.File)) 26 | { 27 | newMessage.Add("file", error.File); 28 | } 29 | 30 | if (!string.IsNullOrWhiteSpace(error.Subcategory)) 31 | { 32 | newMessage.Add("subcategory", error.Subcategory); 33 | } 34 | 35 | if (!string.IsNullOrWhiteSpace(error.ProjectFile)) 36 | { 37 | newMessage.Add("projectFile", error.ProjectFile); 38 | } 39 | 40 | if (!string.IsNullOrWhiteSpace(error.SenderName)) 41 | { 42 | newMessage.Add("senderName", error.SenderName); 43 | } 44 | 45 | newMessage.Add("columnNumber", error.ColumnNumber.ToString()); 46 | newMessage.Add("endColumnNumber", error.EndColumnNumber.ToString()); 47 | newMessage.Add("lineNumber", error.LineNumber.ToString()); 48 | newMessage.Add("endLineNumber", error.EndLineNumber.ToString()); 49 | return newMessage; 50 | } 51 | 52 | return message; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/BuildWarningMessageUpdater.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.TeamCity.ServiceMessages; 4 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 5 | using Microsoft.Build.Framework; 6 | 7 | internal class BuildWarningMessageUpdater: IServiceMessageUpdater 8 | { 9 | private readonly IEventContext _eventContext; 10 | 11 | public BuildWarningMessageUpdater(IEventContext eventContext) => 12 | _eventContext = eventContext; 13 | 14 | public IServiceMessage UpdateServiceMessage(IServiceMessage message) 15 | { 16 | if (_eventContext.TryGetEvent(out var buildEventManager) 17 | && buildEventManager is BuildWarningEventArgs warning) 18 | { 19 | var newMessage = new PatchedServiceMessage(message); 20 | if (!string.IsNullOrWhiteSpace(warning.Code)) 21 | { 22 | newMessage.Add("code", warning.Code); 23 | } 24 | 25 | if (!string.IsNullOrWhiteSpace(warning.File)) 26 | { 27 | newMessage.Add("file", warning.File); 28 | } 29 | 30 | if (!string.IsNullOrWhiteSpace(warning.Subcategory)) 31 | { 32 | newMessage.Add("subcategory", warning.Subcategory); 33 | } 34 | 35 | if (!string.IsNullOrWhiteSpace(warning.ProjectFile)) 36 | { 37 | newMessage.Add("projectFile", warning.ProjectFile); 38 | } 39 | 40 | if (!string.IsNullOrWhiteSpace(warning.SenderName)) 41 | { 42 | newMessage.Add("senderName", warning.SenderName); 43 | } 44 | 45 | newMessage.Add("columnNumber", warning.ColumnNumber.ToString()); 46 | newMessage.Add("endColumnNumber", warning.EndColumnNumber.ToString()); 47 | newMessage.Add("lineNumber", warning.LineNumber.ToString()); 48 | newMessage.Add("endLineNumber", warning.EndLineNumber.ToString()); 49 | return newMessage; 50 | } 51 | 52 | return message; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/BuildMessageMessageUpdater.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using JetBrains.TeamCity.ServiceMessages; 4 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 5 | using Microsoft.Build.Framework; 6 | 7 | internal class BuildMessageMessageUpdater: IServiceMessageUpdater 8 | { 9 | private readonly IEventContext _eventContext; 10 | 11 | public BuildMessageMessageUpdater(IEventContext eventContext) => 12 | _eventContext = eventContext; 13 | 14 | public IServiceMessage UpdateServiceMessage(IServiceMessage message) 15 | { 16 | if (_eventContext.TryGetEvent(out var buildEventManager) 17 | && buildEventManager is BuildMessageEventArgs msg) 18 | { 19 | var newMessage = new PatchedServiceMessage(message); 20 | if (!string.IsNullOrWhiteSpace(msg.Code)) 21 | { 22 | newMessage.Add("code", msg.Code); 23 | } 24 | 25 | if (!string.IsNullOrWhiteSpace(msg.File)) 26 | { 27 | newMessage.Add("file", msg.File); 28 | } 29 | 30 | if (!string.IsNullOrWhiteSpace(msg.Subcategory)) 31 | { 32 | newMessage.Add("subcategory", msg.Subcategory); 33 | } 34 | 35 | if (!string.IsNullOrWhiteSpace(msg.ProjectFile)) 36 | { 37 | newMessage.Add("projectFile", msg.ProjectFile); 38 | } 39 | 40 | if (!string.IsNullOrWhiteSpace(msg.SenderName)) 41 | { 42 | newMessage.Add("senderName", msg.SenderName); 43 | } 44 | 45 | newMessage.Add("columnNumber", msg.ColumnNumber.ToString()); 46 | newMessage.Add("endColumnNumber", msg.EndColumnNumber.ToString()); 47 | newMessage.Add("lineNumber", msg.LineNumber.ToString()); 48 | newMessage.Add("endLineNumber", msg.EndLineNumber.ToString()); 49 | newMessage.Add("importance", msg.Importance.ToString()); 50 | 51 | return newMessage; 52 | } 53 | 54 | return message; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/PatchedServiceMessageTests.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.Tests 2 | { 3 | using System.Linq; 4 | using JetBrains.TeamCity.ServiceMessages; 5 | using JetBrains.TeamCity.ServiceMessages.Write; 6 | using Moq; 7 | using Shouldly; 8 | using Xunit; 9 | 10 | public class PatchedServiceMessageTests 11 | { 12 | [Fact] 13 | public void ShouldUpdateValue() 14 | { 15 | // Given 16 | var baseMessage = new ServiceMessage("Abc") 17 | { 18 | { "Name1", "Val1" } 19 | }; 20 | 21 | var patchedMessage = CreateInstance(baseMessage); 22 | 23 | // When 24 | patchedMessage.Add("Name1", "Val 1"); 25 | 26 | // Then 27 | patchedMessage.GetValue("Name1").ShouldBe("Val 1"); 28 | patchedMessage.Name.ShouldBe("Abc"); 29 | patchedMessage.Keys.Count().ShouldBe(1); 30 | patchedMessage.Keys.ShouldContain("Name1"); 31 | } 32 | 33 | [Fact] 34 | public void ShouldAddValue() 35 | { 36 | // Given 37 | var baseMessage = new ServiceMessage("Abc") 38 | { 39 | { "Name1", "Val1" } 40 | }; 41 | 42 | var patchedMessage = CreateInstance(baseMessage); 43 | 44 | // When 45 | patchedMessage.Add("Name2", "Val 2"); 46 | 47 | // Then 48 | patchedMessage.GetValue("Name1").ShouldBe("Val1"); 49 | patchedMessage.GetValue("Name2").ShouldBe("Val 2"); 50 | patchedMessage.Name.ShouldBe("Abc"); 51 | patchedMessage.Keys.Count().ShouldBe(2); 52 | patchedMessage.Keys.ShouldContain("Name1"); 53 | patchedMessage.Keys.ShouldContain("Name2"); 54 | } 55 | 56 | [Fact] 57 | public void ShouldCopyDefaultValue() 58 | { 59 | // Given 60 | var baseMessage = new Mock(); 61 | var patchedMessage = CreateInstance(baseMessage.Object); 62 | baseMessage.SetupGet(i => i.Keys).Returns(Enumerable.Empty()); 63 | 64 | // When 65 | baseMessage.SetupGet(i => i.DefaultValue).Returns("Default Val"); 66 | 67 | // Then 68 | patchedMessage.DefaultValue.ShouldBe("Default Val"); 69 | } 70 | 71 | private static PatchedServiceMessage CreateInstance(IServiceMessage baseMessage) => 72 | new PatchedServiceMessage(baseMessage); 73 | } 74 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DefaultColorTheme.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | 5 | // ReSharper disable once ClassNeverInstantiated.Global 6 | internal class DefaultColorTheme : IColorTheme 7 | { 8 | public ConsoleColor GetConsoleColor(Color color) 9 | { 10 | switch (color) 11 | { 12 | case Color.BuildStage: 13 | return ConsoleColor.Cyan; 14 | case Color.SummaryHeader: 15 | case Color.PerformanceHeader: 16 | case Color.Items: 17 | return ConsoleColor.Blue; 18 | case Color.Success: 19 | return ConsoleColor.Green; 20 | case Color.Warning: 21 | case Color.WarningSummary: 22 | return ConsoleColor.Yellow; 23 | case Color.Error: 24 | case Color.ErrorSummary: 25 | return ConsoleColor.Red; 26 | case Color.SummaryInfo: 27 | return ConsoleColor.Gray; 28 | case Color.Details: 29 | return ConsoleColor.DarkGray; 30 | case Color.Task: 31 | return ConsoleColor.DarkCyan; 32 | case Color.PerformanceCounterInfo: 33 | return ConsoleColor.White; 34 | default: 35 | throw new ArgumentException($"Unknown color \"{color}\""); 36 | } 37 | 38 | 39 | } 40 | 41 | public string GetAnsiColor(Color color) 42 | { 43 | // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault 44 | switch (color) 45 | { 46 | case Color.Task: 47 | return "36"; 48 | case Color.SummaryInfo: 49 | return "37"; 50 | case Color.Details: 51 | return "30;1"; 52 | case Color.Success: 53 | return "32;1"; 54 | case Color.SummaryHeader: 55 | case Color.PerformanceHeader: 56 | case Color.Items: 57 | return "34;1"; 58 | case Color.BuildStage: 59 | return "36;1"; 60 | case Color.Error: 61 | return "31;1"; 62 | case Color.Warning: 63 | return "33;1"; 64 | case Color.PerformanceCounterInfo: 65 | return "37;1"; 66 | default: 67 | throw new ArgumentException($"Unknown color \"{color}\""); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Parameters.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | internal class Parameters 8 | { 9 | private readonly IEnvironment _environment; 10 | 11 | public Parameters([NotNull] IEnvironment environment) => 12 | _environment = environment ?? throw new ArgumentNullException(nameof(environment)); 13 | 14 | public bool Debug { get; set; } 15 | 16 | public bool ShowOnlyWarnings { get; set; } 17 | 18 | public bool ShowEnvironment { get; set; } 19 | 20 | public LoggerVerbosity Verbosity { get; set; } 21 | 22 | public bool ShowPerfSummary { get; set; } 23 | 24 | public bool ShowItemAndPropertyList { get; set; } 25 | 26 | public bool? ShowSummary { get; set; } 27 | 28 | public bool ShowOnlyErrors { get; set; } 29 | 30 | public bool ShowProjectFile { get; set; } 31 | 32 | public bool? ShowCommandLine { get; set; } 33 | 34 | public bool ShowTimeStamp { get; set; } 35 | 36 | public bool? ShowEventId { get; set; } 37 | 38 | public bool ForceNoAlign { get; set; } 39 | 40 | public bool AlignMessages { get; set; } 41 | 42 | public bool ShowTargetOutputs { get; set; } 43 | 44 | public int BufferWidth { get; set; } 45 | 46 | public ColorMode ColorMode { get; set; } = ColorMode.Default; 47 | 48 | public TeamCityMode TeamCityMode { get; set; } = TeamCityMode.Off; 49 | 50 | public StatisticsMode StatisticsMode { get; set; } = StatisticsMode.Default; 51 | 52 | public ColorThemeMode ColorThemeMode { get; set; } = ColorThemeMode.Default; 53 | 54 | public bool PlainServiceMessage { get; set; } 55 | 56 | // ReSharper disable once MemberCanBeMadeStatic.Global 57 | public string FlowId => _environment.GetEnvironmentVariable("TEAMCITY_PROCESS_FLOW_ID") ?? string.Empty; 58 | 59 | public override string ToString() => $"{nameof(Debug)}: {Debug}, {nameof(ShowOnlyWarnings)}: {ShowOnlyWarnings}, {nameof(ShowEnvironment)}: {ShowEnvironment}, {nameof(Verbosity)}: {Verbosity}, {nameof(ShowPerfSummary)}: {ShowPerfSummary}, {nameof(ShowItemAndPropertyList)}: {ShowItemAndPropertyList}, {nameof(ShowSummary)}: {ShowSummary}, {nameof(ShowOnlyErrors)}: {ShowOnlyErrors}, {nameof(ShowProjectFile)}: {ShowProjectFile}, {nameof(ShowCommandLine)}: {ShowCommandLine}, {nameof(ShowTimeStamp)}: {ShowTimeStamp}, {nameof(ShowEventId)}: {ShowEventId}, {nameof(ForceNoAlign)}: {ForceNoAlign}, {nameof(AlignMessages)}: {AlignMessages}, {nameof(ShowTargetOutputs)}: {ShowTargetOutputs}, {nameof(BufferWidth)}: {BufferWidth}, {nameof(ColorMode)}: {ColorMode}, {nameof(TeamCityMode)}: {TeamCityMode}, {nameof(StatisticsMode)}: {StatisticsMode}, {nameof(ColorThemeMode)}: {ColorThemeMode}, {nameof(PlainServiceMessage)}: {PlainServiceMessage}"; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/StringService.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Globalization; 5 | using JetBrains.Annotations; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class StringService : IStringService 9 | { 10 | // ReSharper disable once IdentifierTypo 11 | public string UnescapeAll(string escapedString) 12 | { 13 | return UnescapeAll(escapedString, out _); 14 | } 15 | 16 | public string FormatResourceString(string resourceName, params object[] args) 17 | { 18 | if (args == null) throw new ArgumentNullException(nameof(args)); 19 | var formatString = Properties.Resources.ResourceManager.GetString(resourceName, CultureInfo.InvariantCulture); 20 | return string.IsNullOrEmpty(formatString) ? string.Empty : FormatString(formatString, args); 21 | } 22 | 23 | [CanBeNull] 24 | // ReSharper disable once IdentifierTypo 25 | private static string UnescapeAll([CanBeNull] string escapedString, out bool escapingWasNecessary) 26 | { 27 | escapingWasNecessary = false; 28 | if (string.IsNullOrEmpty(escapedString)) 29 | { 30 | return escapedString; 31 | } 32 | 33 | var num = escapedString.IndexOf('%'); 34 | if (num == -1) 35 | { 36 | return escapedString; 37 | } 38 | 39 | var sb = StringBuilderCache.Acquire(escapedString.Length); 40 | var startIndex = 0; 41 | for (; num != -1; num = escapedString.IndexOf('%', num + 1)) 42 | { 43 | // ReSharper disable once InvertIf 44 | if (num <= escapedString.Length - 3 && IsHexDigit(escapedString[num + 1]) && IsHexDigit(escapedString[num + 2])) 45 | { 46 | sb.Append(escapedString, startIndex, num - startIndex); 47 | var ch = (char)int.Parse(escapedString.Substring(num + 1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); 48 | sb.Append(ch); 49 | startIndex = num + 3; 50 | escapingWasNecessary = true; 51 | } 52 | } 53 | 54 | sb.Append(escapedString, startIndex, escapedString.Length - startIndex); 55 | return StringBuilderCache.GetStringAndRelease(sb); 56 | } 57 | 58 | private static bool IsHexDigit(char character) 59 | { 60 | if (character >= 48 && character <= 57 || character >= 65 && character <= 70) 61 | { 62 | return true; 63 | } 64 | 65 | if (character >= 97) 66 | { 67 | return character <= 102; 68 | } 69 | 70 | return false; 71 | } 72 | 73 | private static string FormatString([NotNull] string formatString, [NotNull] params object[] args) 74 | { 75 | if (formatString == null) throw new ArgumentNullException(nameof(formatString)); 76 | if (args == null) throw new ArgumentNullException(nameof(args)); 77 | return string.Format(formatString, args); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Disposable.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable RedundantUsingDirective 2 | // ReSharper disable UnusedMember.Global 3 | namespace TeamCity.MSBuild.Logger 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Runtime.CompilerServices; 9 | using System.Threading; 10 | using JetBrains.Annotations; 11 | 12 | internal static class Disposable 13 | { 14 | [NotNull] public static readonly IDisposable Empty = EmptyDisposable.Shared; 15 | 16 | [MethodImpl((MethodImplOptions)0x100)] 17 | [NotNull] 18 | public static IDisposable Create([NotNull] Action action) 19 | { 20 | #if DEBUG 21 | if (action == null) throw new ArgumentNullException(nameof(action)); 22 | #endif 23 | return new DisposableAction(action); 24 | } 25 | 26 | [MethodImpl((MethodImplOptions)0x100)] 27 | [NotNull] 28 | public static IDisposable Create([NotNull][ItemCanBeNull] IEnumerable disposables) 29 | { 30 | #if DEBUG 31 | if (disposables == null) throw new ArgumentNullException(nameof(disposables)); 32 | #endif 33 | return new CompositeDisposable(disposables); 34 | } 35 | 36 | private sealed class DisposableAction : IDisposable 37 | { 38 | [NotNull] private readonly Action _action; 39 | [CanBeNull] private readonly object _key; 40 | private int _counter; 41 | 42 | public DisposableAction([NotNull] Action action, [CanBeNull] object key = null) 43 | { 44 | _action = action; 45 | _key = key ?? action; 46 | } 47 | 48 | public void Dispose() 49 | { 50 | if (Interlocked.Increment(ref _counter) != 1) return; 51 | _action(); 52 | } 53 | 54 | public override bool Equals(object obj) 55 | { 56 | if (ReferenceEquals(null, obj)) return false; 57 | if (ReferenceEquals(this, obj)) return true; 58 | return obj is DisposableAction other && Equals(_key, other._key); 59 | } 60 | 61 | public override int GetHashCode() => 62 | _key != null ? _key.GetHashCode() : 0; 63 | } 64 | 65 | private sealed class CompositeDisposable : IDisposable 66 | { 67 | private readonly IEnumerable _disposables; 68 | private int _counter; 69 | 70 | public CompositeDisposable(IEnumerable disposables) 71 | => _disposables = disposables; 72 | 73 | public void Dispose() 74 | { 75 | if (Interlocked.Increment(ref _counter) != 1) return; 76 | foreach (var disposable in _disposables) 77 | { 78 | disposable?.Dispose(); 79 | } 80 | } 81 | } 82 | 83 | private sealed class EmptyDisposable : IDisposable 84 | { 85 | [NotNull] public static readonly IDisposable Shared = new EmptyDisposable(); 86 | 87 | private EmptyDisposable() { } 88 | 89 | public void Dispose() { } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## MSBuild logger for [](https://www.jetbrains.com/teamcity/) 2 | 3 | [](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![NuGet TeamCity.Dotnet.Integration](https://buildstats.info/nuget/TeamCity.Dotnet.Integration?includePreReleases=false)](https://www.nuget.org/packages/TeamCity.Dotnet.Integration) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [](http://teamcity.jetbrains.com/viewType.html?buildTypeId=TeamCityPluginsByJetBrains_TeamCityDotnetIntegration_TeamCityMSBuildLogger&guest=1) 4 | 5 | Provides the TeamCity integration with [__*.NET CLI*__](https://www.microsoft.com/net/core)/[__*MSBuild*__](https://msdn.microsoft.com/en-US/library/0k6kkbsd.aspx) tools. 6 | 7 | 8 | 9 | ## Supported platforms: 10 | 11 | * [.NET CLI](https://www.microsoft.com/net/core) 12 | 13 | ``` 14 | dotnet build my.csproj /noconsolelogger /l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,path_to_logger\TeamCity.MSBuild.Logger.dll;teamcity 15 | ``` 16 | 17 | * [MSBuild 12+](https://msdn.microsoft.com/en-US/library/0k6kkbsd.aspx) 18 | 19 | ``` 20 | msbuild.exe my.csproj /t:build /noconsolelogger /l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,path_to_logger\TeamCity.MSBuild.Logger.dll;teamcity 21 | ``` 22 | 23 | ## Download 24 | 25 | * [Stable version](http://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:TeamCityPluginsByJetBrains_TeamCityDotnetIntegration_TeamCityMSBuildLogger,pinned:true,status:SUCCESS,tags:release/artifacts/content/TeamCity.MSBuild.Logger.zip ) 26 | * [Nightly build](http://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:TeamCityPluginsByJetBrains_TeamCityDotnetIntegration_TeamCityMSBuildLogger,status:SUCCESS/artifacts/content/TeamCity.MSBuild.Logger.zip) 27 | 28 | ## TeamCity integration 29 | 30 | TeamCity Integration is working from-the-box while you are using [TeamCity dotnet plugin](https://github.com/JetBrains/teamcity-dotnet-plugin). Also it is possible to use TeamCity logger manually, see more details in the [Wiki](https://github.com/JetBrains/TeamCity.MSBuild.Logger/wiki/How-to-use). 31 | 32 | ## Contribution 33 | 34 | We would be a grateful contribution. 35 | 36 | - clone this repo 37 | - open the solution _TeamCity.MSBuild.Logger.sln_ in IDE 38 | - configure debugging for _TeamCity.MSBuild.Logger_ 39 | - specify the executable to _dotnet.exe_ for instance like _C:\Program Files\dotnet\dotnet.exe_ 40 | - specify command-line arguments for your case, for instance _build \MyProject.csproj --verbosity normal /noconsolelogger /l:TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,\TeamCity.MSBuild.Logger\bin\Debug\net452\TeamCity.MSBuild.Logger.dll;teamcity;DEBUG_ - the last option _DEBUG_ is needed to debug this logger. 41 | - set breakpoints 42 | - run debugging for _TeamCity.MSBuild.Logger_ 43 | - attach to the process _dotnet.exe_ with a number from stdOut 44 | - _test.cmd_ to run integration tests 45 | 46 | ## License 47 | 48 | It is under the [Apache License](LICENSE). 49 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/ErrorHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class ErrorHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IEventFormatter _eventFormatter; 11 | [NotNull] private readonly IBuildEventManager _buildEventManager; 12 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 13 | [NotNull] private readonly IMessageWriter _messageWriter; 14 | [NotNull] private readonly ILoggerContext _context; 15 | [NotNull] private readonly ILogWriter _logWriter; 16 | 17 | public ErrorHandler( 18 | [NotNull] ILoggerContext context, 19 | [NotNull] ILogWriter logWriter, 20 | [NotNull] IMessageWriter messageWriter, 21 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 22 | [NotNull] IBuildEventManager buildEventManager, 23 | [NotNull] IEventFormatter eventFormatter) 24 | { 25 | _eventFormatter = eventFormatter ?? throw new ArgumentNullException(nameof(eventFormatter)); 26 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 27 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 28 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 29 | _context = context ?? throw new ArgumentNullException(nameof(context)); 30 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 31 | } 32 | 33 | public void Handle(BuildErrorEventArgs e) 34 | { 35 | if (e == null) throw new ArgumentNullException(nameof(e)); 36 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 37 | 38 | _context.ErrorCount += 1; 39 | _buildEventManager.SetErrorWarningFlagOnCallStack(e.BuildEventContext); 40 | var targetStartedEvent = _buildEventManager.GetTargetStartedEvent(e.BuildEventContext); 41 | if (targetStartedEvent != null) 42 | { 43 | targetStartedEvent.ErrorInTarget = true; 44 | } 45 | 46 | _deferredMessageWriter.DisplayDeferredStartedEvents(e.BuildEventContext); 47 | if (_context.Parameters.ShowOnlyWarnings && !_context.Parameters.ShowOnlyErrors) 48 | { 49 | return; 50 | } 51 | 52 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Normal)) 53 | { 54 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 55 | } 56 | 57 | _logWriter.SetColor(Color.Error); 58 | _messageWriter.WriteMessageAligned(_eventFormatter.FormatEventMessage(e, false, _context.Parameters.ShowProjectFile), true); 59 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 60 | if (_context.ErrorList != null && (_context.Parameters.ShowSummary ?? false) && !_context.ErrorList.Contains(e)) 61 | { 62 | _context.ErrorList.Add(e); 63 | } 64 | 65 | _logWriter.ResetColor(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/WarningHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class WarningHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IEventFormatter _eventFormatter; 11 | [NotNull] private readonly IBuildEventManager _buildEventManager; 12 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWrite; 13 | [NotNull] private readonly IMessageWriter _messageWriter; 14 | [NotNull] private readonly ILoggerContext _context; 15 | [NotNull] private readonly ILogWriter _logWriter; 16 | 17 | public WarningHandler( 18 | [NotNull] ILoggerContext context, 19 | [NotNull] ILogWriter logWriter, 20 | [NotNull] IMessageWriter messageWriter, 21 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 22 | [NotNull] IBuildEventManager buildEventManager, 23 | [NotNull] IEventFormatter eventFormatter) 24 | { 25 | _eventFormatter = eventFormatter ?? throw new ArgumentNullException(nameof(eventFormatter)); 26 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 27 | _deferredMessageWrite = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 28 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 29 | _context = context ?? throw new ArgumentNullException(nameof(context)); 30 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 31 | } 32 | 33 | public void Handle(BuildWarningEventArgs e) 34 | { 35 | if (e == null) throw new ArgumentNullException(nameof(e)); 36 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 37 | _context.WarningCount += 1; 38 | _buildEventManager.SetErrorWarningFlagOnCallStack(e.BuildEventContext); 39 | var targetStartedEvent = _buildEventManager.GetTargetStartedEvent(e.BuildEventContext); 40 | if (targetStartedEvent != null) 41 | { 42 | targetStartedEvent.ErrorInTarget = true; 43 | } 44 | 45 | _deferredMessageWrite.DisplayDeferredStartedEvents(e.BuildEventContext); 46 | if (!_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 47 | { 48 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Normal)) 49 | { 50 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 51 | } 52 | 53 | _logWriter.SetColor(Color.Warning); 54 | _messageWriter.WriteMessageAligned(_eventFormatter.FormatEventMessage(e, false, _context.Parameters.ShowProjectFile), true); 55 | } 56 | 57 | _deferredMessageWrite.ShownBuildEventContext(e.BuildEventContext); 58 | if (_context.WarningList != null && (_context.Parameters.ShowSummary ?? false) && !_context.WarningList.Contains(e)) 59 | { 60 | _context.WarningList.Add(e); 61 | } 62 | 63 | _logWriter.ResetColor(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/TaskFinishedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class TaskFinishedHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IStringService _stringService; 11 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 12 | [NotNull] private readonly IMessageWriter _messageWriter; 13 | [NotNull] private readonly ILoggerContext _context; 14 | [NotNull] private readonly ILogWriter _logWriter; 15 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 16 | 17 | public TaskFinishedHandler( 18 | [NotNull] ILoggerContext context, 19 | [NotNull] ILogWriter logWriter, 20 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 21 | [NotNull] IMessageWriter messageWriter, 22 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 23 | [NotNull] IStringService stringService) 24 | { 25 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 26 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 27 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 28 | _context = context ?? throw new ArgumentNullException(nameof(context)); 29 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 30 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 31 | } 32 | 33 | public void Handle(TaskFinishedEventArgs e) 34 | { 35 | if (e == null) throw new ArgumentNullException(nameof(e)); 36 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 37 | if (_context.Parameters.ShowPerfSummary) 38 | { 39 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.TaskName, _context.TaskPerformanceCounters).AddEventFinished(null, e.BuildEventContext, e.Timestamp); 40 | } 41 | 42 | if (!_context.IsVerbosityAtLeast(LoggerVerbosity.Detailed)) 43 | { 44 | return; 45 | } 46 | 47 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 48 | { 49 | var prefixAlreadyWritten = _messageWriter.WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp); 50 | _logWriter.SetColor(Color.Task); 51 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || (_context.Parameters.ShowEventId ?? false)) 52 | { 53 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("TaskMessageWithId", e.Message, e.BuildEventContext.TaskId), prefixAlreadyWritten); 54 | } 55 | else 56 | { 57 | _messageWriter.WriteMessageAligned(e.Message, prefixAlreadyWritten); 58 | } 59 | 60 | _logWriter.ResetColor(); 61 | } 62 | 63 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/DefaultLogWriter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.IO; 5 | using JetBrains.Annotations; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class DefaultLogWriter : ILogWriter 9 | { 10 | [NotNull] private readonly IColorTheme _colorTheme; 11 | [NotNull] private readonly IConsole _defaultConsole; 12 | private static bool _supportReadingBackgroundColor = true; 13 | private readonly bool _hasBackgroundColor; 14 | 15 | public DefaultLogWriter( 16 | [NotNull] IConsole defaultConsole, 17 | [NotNull] IColorTheme colorTheme) 18 | { 19 | _colorTheme = colorTheme ?? throw new ArgumentNullException(nameof(colorTheme)); 20 | _defaultConsole = defaultConsole ?? throw new ArgumentNullException(nameof(defaultConsole)); 21 | _hasBackgroundColor = true; 22 | try 23 | { 24 | // ReSharper disable once UnusedVariable 25 | var backgroundColor = BackgroundColor; 26 | } 27 | catch (IOException) 28 | { 29 | _hasBackgroundColor = false; 30 | } 31 | } 32 | 33 | private static ConsoleColor BackgroundColor 34 | { 35 | get 36 | { 37 | // ReSharper disable once InvertIf 38 | if (_supportReadingBackgroundColor) 39 | { 40 | try 41 | { 42 | return Console.BackgroundColor; 43 | } 44 | catch (PlatformNotSupportedException) 45 | { 46 | _supportReadingBackgroundColor = false; 47 | } 48 | } 49 | 50 | return ConsoleColor.Black; 51 | } 52 | } 53 | 54 | public void Write(string message, IConsole console = null) 55 | { 56 | if (string.IsNullOrEmpty(message)) 57 | { 58 | return; 59 | } 60 | 61 | (console ?? _defaultConsole).Write(message); 62 | } 63 | 64 | public void SetColor(Color color) 65 | { 66 | if (!_hasBackgroundColor) 67 | { 68 | return; 69 | } 70 | 71 | try 72 | { 73 | Console.ForegroundColor = TransformColor(_colorTheme.GetConsoleColor(color), BackgroundColor); 74 | } 75 | catch (IOException) 76 | { 77 | } 78 | } 79 | 80 | public void ResetColor() 81 | { 82 | if (!_hasBackgroundColor) 83 | { 84 | return; 85 | } 86 | 87 | try 88 | { 89 | Console.ResetColor(); 90 | } 91 | catch (IOException) 92 | { 93 | } 94 | } 95 | 96 | private static ConsoleColor TransformColor(ConsoleColor foreground, ConsoleColor background) 97 | { 98 | var consoleColor = foreground; 99 | if (foreground == background) 100 | { 101 | consoleColor = background == ConsoleColor.Black ? ConsoleColor.Gray : ConsoleColor.Black; 102 | } 103 | 104 | return consoleColor; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/TaskStartedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class TaskStartedHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IStringService _stringService; 11 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 12 | [NotNull] private readonly IMessageWriter _messageWriter; 13 | [NotNull] private readonly ILoggerContext _context; 14 | [NotNull] private readonly ILogWriter _logWriter; 15 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 16 | 17 | public TaskStartedHandler( 18 | [NotNull] ILoggerContext context, 19 | [NotNull] ILogWriter logWriter, 20 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 21 | [NotNull] IMessageWriter messageWriter, 22 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 23 | [NotNull] IStringService stringService) 24 | { 25 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 26 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 27 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 28 | _context = context ?? throw new ArgumentNullException(nameof(context)); 29 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 30 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 31 | } 32 | 33 | public void Handle(TaskStartedEventArgs e) 34 | { 35 | if (e == null) throw new ArgumentNullException(nameof(e)); 36 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 37 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Detailed)) 38 | { 39 | _deferredMessageWriter.DisplayDeferredStartedEvents(e.BuildEventContext); 40 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 41 | { 42 | var prefixAlreadyWritten = _messageWriter.WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp); 43 | _logWriter.SetColor(Color.Task); 44 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || (_context.Parameters.ShowEventId ?? false)) 45 | { 46 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("TaskMessageWithId", e.Message, e.BuildEventContext.TaskId), prefixAlreadyWritten); 47 | } 48 | else 49 | { 50 | _messageWriter.WriteMessageAligned(e.Message, prefixAlreadyWritten); 51 | } 52 | 53 | _logWriter.ResetColor(); 54 | } 55 | 56 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 57 | } 58 | 59 | if (!_context.Parameters.ShowPerfSummary) 60 | { 61 | return; 62 | } 63 | 64 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.TaskName, _context.TaskPerformanceCounters).AddEventStarted(null, e.BuildEventContext, e.Timestamp, null); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/BuildStartedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using Microsoft.Build.Framework; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using JetBrains.Annotations; 8 | 9 | // ReSharper disable once ClassNeverInstantiated.Global 10 | internal class BuildStartedHandler : IBuildEventHandler 11 | { 12 | [NotNull] private readonly IStringService _stringService; 13 | [NotNull] private readonly IHierarchicalMessageWriter _hierarchicalMessageWriter; 14 | [NotNull] private readonly IMessageWriter _messageWriter; 15 | [NotNull] private readonly ILoggerContext _context; 16 | [NotNull] private readonly ILogWriter _logWriter; 17 | 18 | public BuildStartedHandler( 19 | [NotNull] ILoggerContext context, 20 | [NotNull] ILogWriter logWriter, 21 | [NotNull] IMessageWriter messageWriter, 22 | [NotNull] IHierarchicalMessageWriter hierarchicalMessageWriter, 23 | [NotNull] IStringService stringService) 24 | { 25 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 26 | _hierarchicalMessageWriter = hierarchicalMessageWriter ?? throw new ArgumentNullException(nameof(hierarchicalMessageWriter)); 27 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 28 | _context = context ?? throw new ArgumentNullException(nameof(context)); 29 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 30 | } 31 | 32 | public void Handle(BuildStartedEventArgs e) 33 | { 34 | if (e == null) throw new ArgumentNullException(nameof(e)); 35 | _context.BuildStarted = e.Timestamp; 36 | _context.HasBuildStarted = true; 37 | if (_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 38 | { 39 | return; 40 | } 41 | 42 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Normal)) 43 | { 44 | _messageWriter.WriteLinePrettyFromResource("BuildStartedWithTime", e.Timestamp); 45 | } 46 | 47 | WriteEnvironment(e.BuildEnvironment); 48 | } 49 | 50 | private void WriteEnvironment([CanBeNull] IDictionary environment) 51 | { 52 | if (environment == null || environment.Count == 0 || _context.Verbosity != LoggerVerbosity.Diagnostic && !_context.Parameters.ShowEnvironment) 53 | { 54 | return; 55 | } 56 | 57 | OutputEnvironment(environment); 58 | _messageWriter.WriteNewLine(); 59 | } 60 | 61 | private void OutputEnvironment(IDictionary environment) 62 | { 63 | if (environment == null) throw new ArgumentNullException(nameof(environment)); 64 | _logWriter.SetColor(Color.SummaryHeader); 65 | _hierarchicalMessageWriter.StartBlock("Environment"); 66 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("EnvironmentHeader"), true); 67 | foreach (var keyValuePair in environment) 68 | { 69 | _logWriter.SetColor(Color.SummaryInfo); 70 | _messageWriter.WriteMessageAligned(string.Format(CultureInfo.CurrentCulture, "{0} = {1}", keyValuePair.Key, keyValuePair.Value), false); 71 | } 72 | 73 | _hierarchicalMessageWriter.FinishBlock(); 74 | _logWriter.ResetColor(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /tools/dotnet-sdk.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | SET dotnet_releases_url=https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json 4 | SET script_path=%~dp0 5 | SET tools_path=%script_path%tools 6 | 7 | if [%1]==[help] goto help 8 | if [%1]==[] goto help 9 | if [%1]==[list] goto sdk_list 10 | if [%1]==[latest] goto sdk_latest 11 | if [%1]==[releases] goto sdk_releases 12 | if [%1]==[get] goto sdk_download 13 | 14 | for /f %%f in ('dir /b "%programfiles%\dotnet\sdk"') do ( 15 | if %1==%%f goto switch 16 | ) 17 | echo The %1 version of .Net Core SDK was not found 18 | echo Please, run "dotnet sdk list" to make sure you have it installed in "%programfiles%\dotnet\sdk" 19 | goto end 20 | 21 | :switch 22 | echo Switching .NET Core SDK version to %1 23 | ( 24 | echo { 25 | echo "sdk": { 26 | echo "version": "%1" 27 | echo } 28 | echo } 29 | ) > global.json 30 | goto end 31 | 32 | :sdk_list 33 | echo The installed .NET Core SDKs are: 34 | dir /b "%programfiles%\dotnet\sdk" | find /i "." 35 | goto end 36 | 37 | :sdk_latest 38 | if exist global.json del global.json 39 | if exist ..\global.json ( 40 | set /p choice= There's a global.json in your parent directory. Do you want to delete it? (N/y) 41 | if /I "%choice%"=="y" ( 42 | del ..\global.json 43 | ) else ( 44 | SETLOCAL ENABLEDELAYEDEXPANSION 45 | set dotnetVersion= 46 | for /f "delims=" %%a in ('dotnet --version') do set dotnetVersion=%%a 47 | echo .NET Core SDK current version: !dotnetVersion! 48 | goto end 49 | ) 50 | ) 51 | echo .NET Core SDK version switched to latest version. 52 | dotnet --version 53 | 54 | goto end 55 | 56 | :help 57 | echo .NET Core SDK Switcher 58 | echo. 59 | echo Usage: .net sdk [command] 60 | echo Usage: .net sdk [version] 61 | echo Usage: .net sdk get [version] [platform] 62 | echo. 63 | echo Commands: 64 | echo latest Switches to the latest .NET Core SDK version 65 | echo list Lists all installed .NET Core SDKs 66 | echo releases Lists all available releases of .NET Core SDKs 67 | echo get Downloads the provided release version. ('' or 'latest' for the latest release) 68 | echo help Display help 69 | echo. 70 | echo version: 71 | echo An installed version number of a .NET Core SDK 72 | echo. 73 | 74 | goto end 75 | 76 | :sdk_releases 77 | echo Releases available for the .NET Core SDK are: 78 | "%tools_path%\curl" %dotnet_releases_url% -H "Accept: application/json" -s | "%tools_path%\jq" "map({date: .date,sdk: .\"version-sdk\"}) | unique_by(.sdk) | .[] | \"\(.date)\t\(.sdk)\" " -r 79 | echo. 80 | 81 | goto end 82 | 83 | :sdk_download 84 | SETLOCAL 85 | SET version=%2 86 | if [%version%]==[] SET version=latest 87 | if "%version%"=="latest" ( 88 | "%tools_path%\curl" %dotnet_releases_url% -H "Accept: application/json" -s | "%tools_path%\jq" "map({sdk: .\"version-sdk\"}) | unique_by(.sdk) | .[-1] | .sdk " -r > version.dat 89 | set /p version= download.dat 97 | 98 | SET /p url= 0).ShouldBe(producesTeamCityServiceMessages.Value); 24 | } 25 | 26 | ServiceMessages.ResultShouldContainCorrectStructureAndSequence(actualResult.StdOut); 27 | ServiceMessages.ResultShouldContainCorrectStructureAndSequence(actualResult.StdError); 28 | } 29 | 30 | private static void CheckOutput([NotNull] this IEnumerable actualLines) 31 | { 32 | if (actualLines == null) throw new ArgumentNullException(nameof(actualLines)); 33 | // ReSharper disable once PossibleMultipleEnumeration 34 | var filteredActualLines = ServiceMessages.FilterTeamCityServiceMessages(actualLines).ToList(); 35 | // ReSharper disable once PossibleMultipleEnumeration 36 | var curExpectedLines = ServiceMessages.FilterTeamCityServiceMessages(actualLines).ToList(); 37 | filteredActualLines.Count.ShouldBe(curExpectedLines.Count); 38 | foreach (var pair in filteredActualLines.Zip(curExpectedLines, (actualLine, expectedLine) => new { actualLine, expectedLine })) 39 | { 40 | CheckLines(pair.actualLine, pair.expectedLine); 41 | } 42 | } 43 | 44 | private static void CheckLines([CanBeNull] this string actualLine, [CanBeNull] string expectedLine) 45 | { 46 | var modifiedActualLine = ReplaceChangeableItems(actualLine); 47 | var modifiedExpectedLine = ReplaceChangeableItems(expectedLine); 48 | if (modifiedActualLine != modifiedExpectedLine) 49 | { 50 | Assert.Equal(modifiedActualLine, modifiedExpectedLine); 51 | } 52 | } 53 | 54 | private static string ReplaceChangeableItems([CanBeNull] string line) => string.IsNullOrWhiteSpace(line) ? line : new string(ExcludeChangeableChars(line).ToArray()); 55 | 56 | private static IEnumerable ExcludeChangeableChars([NotNull] IEnumerable chars) 57 | { 58 | if (chars == null) throw new ArgumentNullException(nameof(chars)); 59 | foreach (var c in chars) 60 | { 61 | if (char.IsDigit(c)) 62 | { 63 | continue; 64 | } 65 | 66 | yield return c; 67 | } 68 | } 69 | 70 | public static string CreateLoggerString( 71 | [NotNull] this string framework, 72 | [CanBeNull] string parameters = "") 73 | { 74 | if (framework == null) throw new ArgumentNullException(nameof(framework)); 75 | #if DEBUG 76 | const string configuration = "Debug"; 77 | #else 78 | const string configuration = "Release"; 79 | #endif 80 | 81 | var loggerPath = Path.GetFullPath(Path.Combine(CommandLine.WorkingDirectory, $@"TeamCity.MSBuild.Logger\bin\{configuration}\{framework}\publish\TeamCity.MSBuild.Logger.dll")); 82 | if (!string.IsNullOrWhiteSpace(parameters)) 83 | { 84 | parameters = ";" + parameters; 85 | } 86 | 87 | return $"TeamCity.MSBuild.Logger.TeamCityMSBuildLogger,{loggerPath}{parameters}"; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/MessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class MessageHandler : IBuildEventHandler 10 | { 11 | [NotNull] private readonly IBuildEventManager _buildEventManager; 12 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 13 | [NotNull] private readonly IMessageWriter _messageWriter; 14 | [NotNull] private readonly ILoggerContext _context; 15 | 16 | public MessageHandler( 17 | [NotNull] ILoggerContext context, 18 | [NotNull] IMessageWriter messageWriter, 19 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 20 | [NotNull] IBuildEventManager buildEventManager) 21 | { 22 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 23 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 24 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 25 | _context = context ?? throw new ArgumentNullException(nameof(context)); 26 | } 27 | 28 | public void Handle(BuildMessageEventArgs e) 29 | { 30 | if (e == null) throw new ArgumentNullException(nameof(e)); 31 | if (_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 32 | { 33 | return; 34 | } 35 | 36 | if (e == null) throw new ArgumentNullException(nameof(e)); 37 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 38 | bool showMessages; 39 | var lightenText = false; 40 | if (e is TaskCommandLineEventArgs) 41 | { 42 | if (_context.Parameters.ShowCommandLine != true && !_context.IsVerbosityAtLeast(LoggerVerbosity.Normal)) 43 | { 44 | return; 45 | } 46 | 47 | showMessages = true; 48 | } 49 | else 50 | { 51 | switch (e.Importance) 52 | { 53 | case MessageImportance.High: 54 | showMessages = _context.IsVerbosityAtLeast(LoggerVerbosity.Minimal); 55 | break; 56 | case MessageImportance.Normal: 57 | showMessages = _context.IsVerbosityAtLeast(LoggerVerbosity.Normal); 58 | lightenText = true; 59 | break; 60 | case MessageImportance.Low: 61 | showMessages = _context.IsVerbosityAtLeast(LoggerVerbosity.Detailed); 62 | lightenText = true; 63 | break; 64 | default: 65 | throw new InvalidOperationException("Impossible"); 66 | } 67 | } 68 | 69 | if (!showMessages) 70 | { 71 | return; 72 | } 73 | 74 | if (_context.HasBuildStarted && e.BuildEventContext.ProjectContextId != -2 && _buildEventManager.GetProjectStartedEvent(e.BuildEventContext) == null && _context.IsVerbosityAtLeast(LoggerVerbosity.Normal)) 75 | { 76 | if (!_context.DeferredMessages.TryGetValue(e.BuildEventContext, out var messageEventArgsList)) 77 | { 78 | messageEventArgsList = new List(); 79 | _context.DeferredMessages.Add(e.BuildEventContext, messageEventArgsList); 80 | } 81 | 82 | messageEventArgsList.Add(e); 83 | } 84 | else 85 | { 86 | _deferredMessageWriter.DisplayDeferredStartedEvents(e.BuildEventContext); 87 | _messageWriter.PrintMessage(e, lightenText); 88 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/LoggerContext.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class LoggerContext: ILoggerContext 10 | { 11 | [NotNull] private readonly IBuildEventManager _buildEventManager; 12 | 13 | public LoggerContext( 14 | [NotNull] IBuildEventManager buildEventManager) 15 | { 16 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 17 | ResetConsoleLoggerState(); 18 | } 19 | 20 | public LoggerVerbosity Verbosity => Parameters.Verbosity; 21 | 22 | public Parameters Parameters { get; private set; } 23 | 24 | public int CurrentIndentLevel => 0; 25 | 26 | public int NumberOfProcessors { get; private set; } 27 | 28 | public bool SkipProjectStartedText { get; private set; } 29 | 30 | [CanBeNull] public IList ErrorList { get; private set; } 31 | 32 | [CanBeNull] public IList WarningList { get; private set; } 33 | 34 | public IDictionary> DeferredMessages { get; } = new Dictionary>(ComparerContextNodeId.Shared); 35 | 36 | public IDictionary ProjectPerformanceCounters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 37 | 38 | public IDictionary TargetPerformanceCounters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 39 | 40 | public IDictionary TaskPerformanceCounters { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 41 | 42 | public bool HasBuildStarted { get; set; } 43 | 44 | public DateTime BuildStarted { get; set; } 45 | 46 | public int ErrorCount { get; set; } 47 | 48 | public int WarningCount { get; set; } 49 | 50 | public BuildEventContext LastDisplayedBuildEventContext { get; set; } 51 | 52 | public int PrefixWidth { get; set; } 53 | 54 | public ProjectFullKey LastProjectFullKey { get; set; } 55 | 56 | public void Initialize( 57 | int numberOfProcessors, 58 | bool skipProjectStartedText, 59 | Parameters parameters) 60 | { 61 | NumberOfProcessors = numberOfProcessors; 62 | SkipProjectStartedText = skipProjectStartedText; 63 | Parameters = parameters; 64 | if (parameters.ShowSummary ?? false) 65 | { 66 | ErrorList = new List(); 67 | WarningList = new List(); 68 | } 69 | else 70 | { 71 | ErrorList = null; 72 | WarningList = null; 73 | } 74 | } 75 | 76 | public bool IsVerbosityAtLeast(LoggerVerbosity checkVerbosity) 77 | { 78 | return Verbosity >= checkVerbosity; 79 | } 80 | 81 | public void ResetConsoleLoggerState() 82 | { 83 | ErrorCount = 0; 84 | WarningCount = 0; 85 | _buildEventManager.Reset(); 86 | PrefixWidth = 0; 87 | LastDisplayedBuildEventContext = null; 88 | LastProjectFullKey = new ProjectFullKey(-1, -1); 89 | HasBuildStarted = false; 90 | BuildStarted = default; 91 | PrefixWidth = 0; 92 | ProjectPerformanceCounters.Clear(); 93 | TargetPerformanceCounters.Clear(); 94 | TaskPerformanceCounters.Clear(); 95 | } 96 | 97 | public ProjectFullKey GetFullProjectKey(BuildEventContext e) 98 | { 99 | ProjectStartedEventMinimumFields eventMinimumFields = null; 100 | if (e != null) 101 | { 102 | eventMinimumFields = _buildEventManager.GetProjectStartedEvent(e); 103 | } 104 | 105 | // ReSharper disable once ConvertIfStatementToReturnStatement 106 | if (eventMinimumFields == null) 107 | { 108 | return new ProjectFullKey(0, 0); 109 | } 110 | 111 | return new ProjectFullKey(eventMinimumFields.ProjectKey, eventMinimumFields.EntryPointKey); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/PerformanceCounter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using JetBrains.Annotations; 7 | using Microsoft.Build.Framework; 8 | 9 | // ReSharper disable once ClassNeverInstantiated.Global 10 | internal class PerformanceCounter: IPerformanceCounter 11 | { 12 | [NotNull] private readonly IMessageWriter _messageWriter; 13 | private readonly IDictionary _internalPerformanceCounters = new Dictionary(StringComparer.OrdinalIgnoreCase); 14 | private Dictionary _startedEvent; 15 | [NotNull] private readonly ILogWriter _logWriter; 16 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 17 | private int _calls; 18 | 19 | public PerformanceCounter( 20 | [NotNull] ILogWriter logWriter, 21 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 22 | [NotNull] IMessageWriter messageWriter) 23 | { 24 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 25 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 26 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 27 | } 28 | 29 | public string ScopeName { get; set; } = string.Empty; 30 | 31 | public TimeSpan ElapsedTime { get; private set; } = new TimeSpan(0L); 32 | 33 | public bool ReenteredScope => false; 34 | 35 | public int MessageIdentLevel { private get; set; } = 2; 36 | 37 | public void AddEventStarted(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp, IEqualityComparer comparer) 38 | { 39 | if (!string.IsNullOrEmpty(projectTargetNames)) 40 | { 41 | var performanceCounter = _performanceCounterFactory.GetOrCreatePerformanceCounter(projectTargetNames, _internalPerformanceCounters); 42 | performanceCounter.AddEventStarted(null, buildEventContext, eventTimeStamp, ComparerContextNodeIdTargetId.Shared); 43 | performanceCounter.MessageIdentLevel = 7; 44 | } 45 | 46 | if (_startedEvent == null) 47 | { 48 | _startedEvent = comparer != null ? new Dictionary(comparer) : new Dictionary(); 49 | } 50 | 51 | if (_startedEvent.ContainsKey(buildEventContext)) 52 | { 53 | return; 54 | } 55 | 56 | _startedEvent.Add(buildEventContext, eventTimeStamp.Ticks); 57 | _calls += 1; 58 | } 59 | 60 | public void AddEventFinished(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp) 61 | { 62 | if (!string.IsNullOrEmpty(projectTargetNames)) 63 | { 64 | _performanceCounterFactory.GetOrCreatePerformanceCounter(projectTargetNames, _internalPerformanceCounters).AddEventFinished(null, buildEventContext, eventTimeStamp); 65 | } 66 | 67 | if (_startedEvent == null) 68 | { 69 | throw new InvalidOperationException("Cannot have finished counter without started counter."); 70 | } 71 | 72 | if (!_startedEvent.TryGetValue(buildEventContext, out var ticks)) 73 | { 74 | return; 75 | } 76 | 77 | ElapsedTime += TimeSpan.FromTicks(eventTimeStamp.Ticks - ticks); 78 | _startedEvent.Remove(buildEventContext); 79 | } 80 | 81 | public void PrintCounterMessage() 82 | { 83 | var str = string.Format(CultureInfo.CurrentCulture, "{0,5}", Math.Round(ElapsedTime.TotalMilliseconds, 0)); 84 | _messageWriter.WriteLinePrettyFromResource(MessageIdentLevel, "PerformanceLine", str, string.Format(CultureInfo.CurrentCulture, "{0,-40}", ScopeName), string.Format(CultureInfo.CurrentCulture, "{0,3}", _calls)); 85 | if (_internalPerformanceCounters == null || _internalPerformanceCounters.Count <= 0) 86 | { 87 | return; 88 | } 89 | 90 | foreach (var performanceCounter in _internalPerformanceCounters.Values) 91 | { 92 | _logWriter.SetColor(Color.PerformanceCounterInfo); 93 | performanceCounter.PrintCounterMessage(); 94 | _logWriter.ResetColor(); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/MSBuildIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable StringLiteralTypo 2 | namespace TeamCity.MSBuild.Logger.Tests 3 | { 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using Helpers; 7 | using JetBrains.Annotations; 8 | using Shouldly; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | 12 | [Collection("Integration")] 13 | // ReSharper disable once InconsistentNaming 14 | public class MSBuildIntegrationTests 15 | { 16 | private readonly ITestOutputHelper _testOutputHelper; 17 | 18 | public MSBuildIntegrationTests(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; 19 | 20 | [Theory] 21 | [InlineData("net452", 10, "minimal", null, false)] 22 | [InlineData("net452", 1, "m", null, false)] 23 | [InlineData("net452", 1, "quiet", null, false)] 24 | [InlineData("net452", 10, "quiet", null, false)] 25 | [InlineData("net452", 1, "q", null, false)] 26 | [InlineData("net452", 10, "q", null, false)] 27 | [InlineData("net452", 10, "normal", null, false)] 28 | [InlineData("net452", 10, "normal", "TEAMcity", true)] 29 | [InlineData("net452", 10, "n", null, false)] 30 | [InlineData("net452", 10, "detailed", null, false)] 31 | [InlineData("net452", 10, "d", null, false)] 32 | [InlineData("net452", 10, "diagnostic", null, false)] 33 | [InlineData("net452", 10, "diag", null, false)] 34 | [InlineData("net452", 10, "deTailed", null, false)] 35 | [InlineData("net452", 10, "diag", "teamcity", true)] 36 | [InlineData("net452", 10, "deTailed", "teamcity", true)] 37 | // ReSharper disable once InconsistentNaming 38 | public void ShouldProduceSameMessagesAsConsoleLoggerWhenMSBuild( 39 | string framework, 40 | int processCount, 41 | [NotNull] string verbosity, 42 | [CanBeNull] string parameters, 43 | bool producesTeamCityServiceMessages) 44 | { 45 | // Given 46 | WriteLine(); 47 | WriteLine($@"Run: framework={framework}, processCount={processCount}, verbosity={verbosity}"); 48 | 49 | var environmentVariables = new Dictionary(); 50 | var loggerString = framework.CreateLoggerString(parameters); 51 | var projectPath = Path.GetFullPath(Path.Combine(CommandLine.WorkingDirectory, @"IntegrationTests\Console\Console.csproj")); 52 | var restoreWithLoggerCommandLine = new CommandLine( 53 | "msbuild.exe", 54 | environmentVariables, 55 | "/t:restore", 56 | projectPath, 57 | "/noconsolelogger", 58 | $"/m:{processCount}", 59 | $@"/logger:{loggerString}"); 60 | 61 | var buildWithLoggerCommandLine = new CommandLine( 62 | "msbuild.exe", 63 | environmentVariables, 64 | "/t:build", 65 | projectPath, 66 | "/noconsolelogger", 67 | $"/verbosity:{verbosity}", 68 | $"/m:{processCount}", 69 | $@"/l:{loggerString}"); 70 | 71 | var restoreCommandLine = new CommandLine( 72 | "msbuild.exe", 73 | environmentVariables, 74 | "/t:restore", 75 | projectPath, 76 | $"/m:{processCount}"); 77 | 78 | var buildCommandLine = new CommandLine( 79 | "msbuild.exe", 80 | environmentVariables, 81 | "/t:build", 82 | projectPath, 83 | $"/verbosity:{verbosity}", 84 | $"/m:{processCount}"); 85 | 86 | // When 87 | WriteLine(); 88 | WriteLine(@"Without TeamCity logger"); 89 | 90 | restoreWithLoggerCommandLine.TryExecute(out var restoreWithLoggerResult).ShouldBe(true); 91 | buildCommandLine.TryExecute(out var buildResult).ShouldBe(true); 92 | 93 | WriteLine(); 94 | WriteLine(@"With TeamCity logger"); 95 | 96 | restoreCommandLine.TryExecute(out var restoreResult).ShouldBe(true); 97 | buildWithLoggerCommandLine.TryExecute(out var buildWithLoggerResult).ShouldBe(true); 98 | 99 | // Then 100 | restoreWithLoggerResult.ResultShouldBe(restoreResult, producesTeamCityServiceMessages); 101 | buildWithLoggerResult.ResultShouldBe(buildResult); 102 | } 103 | 104 | private void WriteLine(string message = "") => _testOutputHelper.WriteLine(message); 105 | } 106 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/Helpers/CommandLine.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable MemberCanBePrivate.Global 2 | namespace TeamCity.MSBuild.Logger.Tests.Helpers 3 | { 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using Shouldly; 11 | using JetBrains.Annotations; 12 | 13 | public class CommandLine 14 | { 15 | private readonly IDictionary _environmentVariables; 16 | 17 | public CommandLine([NotNull] string executableFile, [NotNull] IDictionary environmentVariables, [NotNull] params string[] args) 18 | { 19 | ExecutableFile = executableFile ?? throw new ArgumentNullException(nameof(executableFile)); 20 | Args = args ?? throw new ArgumentNullException(nameof(args)); 21 | _environmentVariables = environmentVariables ?? throw new ArgumentNullException(nameof(environmentVariables)); 22 | } 23 | 24 | public static string WorkingDirectory => Path.GetFullPath(Path.Combine(typeof(CommandLine).GetTypeInfo().Assembly.Location, "../../../../../")); 25 | 26 | public string ExecutableFile { [NotNull] get; } 27 | 28 | public string[] Args { [NotNull] get; } 29 | 30 | public bool TryExecute(out CommandLineResult result) 31 | { 32 | var process = new Process 33 | { 34 | StartInfo = new ProcessStartInfo 35 | { 36 | WorkingDirectory = WorkingDirectory, 37 | FileName = ExecutableFile, 38 | Arguments = string.Join(" ", Args.Select(NormalizeArg).ToArray()), 39 | RedirectStandardError = true, 40 | RedirectStandardOutput = true, 41 | CreateNoWindow = true, 42 | UseShellExecute = false 43 | } 44 | }; 45 | 46 | foreach (var (key, value) in _environmentVariables) 47 | { 48 | #if NET45 49 | if (value == null) 50 | { 51 | process.StartInfo.EnvironmentVariables.Remove(key); 52 | } 53 | else 54 | { 55 | process.StartInfo.EnvironmentVariables[key] = value; 56 | } 57 | #else 58 | if (value == null) 59 | { 60 | process.StartInfo.Environment.Remove(key); 61 | } 62 | else 63 | { 64 | process.StartInfo.Environment[key] = value; 65 | } 66 | #endif 67 | } 68 | 69 | IList stdOut = new List(); 70 | IList stdError = new List(); 71 | process.OutputDataReceived += (sender, args) => 72 | { 73 | if (args.Data != null) 74 | { 75 | stdOut.Add(args.Data); 76 | //Console.Write("."); 77 | } 78 | }; 79 | 80 | process.ErrorDataReceived += (sender, args) => 81 | { 82 | if (args.Data == null) 83 | { 84 | return; 85 | } 86 | 87 | stdError.Add(args.Data); 88 | Console.Error.WriteLine(args.Data); 89 | }; 90 | 91 | // ReSharper disable once LocalizableElement 92 | Console.WriteLine($"Run: {process.StartInfo.FileName} {process.StartInfo.Arguments}"); 93 | var stopwatch = new Stopwatch(); 94 | if (!process.Start()) 95 | { 96 | result = default; 97 | return false; 98 | } 99 | 100 | stopwatch.Start(); 101 | Console.WriteLine($@"Process Id: {process.Id}"); 102 | 103 | process.BeginOutputReadLine(); 104 | process.BeginErrorReadLine(); 105 | 106 | process.WaitForExit(20000).ShouldBe(true, "Timeout"); 107 | stopwatch.Stop(); 108 | Console.WriteLine($@"Elapsed Milliseconds: {stopwatch.ElapsedMilliseconds}"); 109 | 110 | result = new CommandLineResult(this, process.ExitCode, stdOut, stdError); 111 | return true; 112 | } 113 | 114 | public override string ToString() 115 | { 116 | return string.Join(" ", Enumerable.Repeat(ExecutableFile, 1).Concat(Args).Select(NormalizeArg)); 117 | } 118 | 119 | private static string NormalizeArg(string arg) 120 | { 121 | return arg.Contains(" ") ? "\"{arg}\"" : arg; 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/ProjectFinishedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using JetBrains.Annotations; 5 | using Microsoft.Build.Framework; 6 | 7 | // ReSharper disable once ClassNeverInstantiated.Global 8 | internal class ProjectFinishedHandler : IBuildEventHandler 9 | { 10 | [NotNull] private readonly IStringService _stringService; 11 | [NotNull] private readonly IHierarchicalMessageWriter _hierarchicalMessageWriter; 12 | [NotNull] private readonly IBuildEventManager _buildEventManager; 13 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 14 | [NotNull] private readonly IMessageWriter _messageWriter; 15 | [NotNull] private readonly ILoggerContext _context; 16 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 17 | [NotNull] private readonly ILogWriter _logWriter; 18 | 19 | public ProjectFinishedHandler( 20 | [NotNull] ILoggerContext context, 21 | [NotNull] ILogWriter logWriter, 22 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 23 | [NotNull] IMessageWriter messageWriter, 24 | [NotNull] IHierarchicalMessageWriter hierarchicalMessageWriter, 25 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 26 | [NotNull] IBuildEventManager buildEventManager, 27 | [NotNull] IStringService stringService) 28 | { 29 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 30 | _hierarchicalMessageWriter = hierarchicalMessageWriter ?? throw new ArgumentNullException(nameof(hierarchicalMessageWriter)); 31 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 32 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 33 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 34 | _context = context ?? throw new ArgumentNullException(nameof(context)); 35 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 36 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 37 | } 38 | 39 | public void Handle(ProjectFinishedEventArgs e) 40 | { 41 | if (e == null) throw new ArgumentNullException(nameof(e)); 42 | var projectStartedEvent = _buildEventManager.GetProjectStartedEvent(e.BuildEventContext); 43 | // ReSharper disable once LocalizableElement 44 | if (projectStartedEvent == null) throw new ArgumentException($"Project finished event for {e.ProjectFile} received without matching start event", nameof(e)); 45 | if (_context.Parameters.ShowPerfSummary) 46 | { 47 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.ProjectFile, _context.ProjectPerformanceCounters).AddEventFinished(projectStartedEvent.TargetNames, e.BuildEventContext, e.Timestamp); 48 | } 49 | 50 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Normal) && projectStartedEvent.ShowProjectFinishedEvent) 51 | { 52 | _context.LastProjectFullKey = _context.GetFullProjectKey(e.BuildEventContext); 53 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 54 | { 55 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 56 | _logWriter.SetColor(Color.BuildStage); 57 | var targetNames = projectStartedEvent.TargetNames; 58 | var projectFile = projectStartedEvent.ProjectFile ?? string.Empty; 59 | if (string.IsNullOrEmpty(targetNames)) 60 | { 61 | if (e.Succeeded) 62 | { 63 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("ProjectFinishedPrefixWithDefaultTargetsMultiProc", projectFile), true); 64 | _hierarchicalMessageWriter.FinishBlock(); 65 | } 66 | else 67 | { 68 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("ProjectFinishedPrefixWithDefaultTargetsMultiProcFailed", projectFile), true); 69 | _hierarchicalMessageWriter.FinishBlock(); 70 | } 71 | } 72 | else 73 | { 74 | if (e.Succeeded) 75 | { 76 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("ProjectFinishedPrefixWithTargetNamesMultiProc", projectFile, targetNames), true); 77 | _hierarchicalMessageWriter.FinishBlock(); 78 | } 79 | else 80 | { 81 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("ProjectFinishedPrefixWithTargetNamesMultiProcFailed", projectFile, targetNames), true); 82 | _hierarchicalMessageWriter.FinishBlock(); 83 | } 84 | } 85 | } 86 | 87 | _deferredMessageWriter.ShownBuildEventContext(projectStartedEvent.ProjectBuildEventContext); 88 | _logWriter.ResetColor(); 89 | } 90 | 91 | _buildEventManager.RemoveProjectStartedEvent(e.BuildEventContext); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Composition.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable PartialTypeWithSinglePart 2 | namespace TeamCity.MSBuild.Logger 3 | { 4 | using System; 5 | using Microsoft.Build.Framework; 6 | using EventHandlers; 7 | using JetBrains.TeamCity.ServiceMessages.Read; 8 | using JetBrains.TeamCity.ServiceMessages.Write; 9 | using JetBrains.TeamCity.ServiceMessages.Write.Special; 10 | using JetBrains.TeamCity.ServiceMessages.Write.Special.Impl.Updater; 11 | using Pure.DI; 12 | using static Pure.DI.Lifetime; 13 | 14 | internal partial class Composition 15 | { 16 | // ReSharper disable once UnusedMember.Local 17 | private static void Setup() => 18 | DI.Setup(nameof(Composition)) 19 | .DefaultLifetime(Singleton) 20 | .Bind().To().Root("Logger") 21 | .Bind().To() 22 | .Bind().To() 23 | .Bind().To() 24 | .Bind().Bind().To() 25 | .Bind().To() 26 | .Bind().To() 27 | .Bind().To() 28 | .Bind().To() 29 | .Bind().To() 30 | .Bind().To() 31 | .Bind().To() 32 | .Bind().To() 33 | .Bind().To() 34 | .Bind().To() 35 | .Bind().As(Transient).To() 36 | .Bind().As(Transient).To() 37 | .Bind().Bind().To() 38 | 39 | // Colors 40 | .Bind().To() 41 | .Bind(ColorThemeMode.Default).To() 42 | .Bind(ColorThemeMode.TeamCity).To() 43 | 44 | // IStatistics 45 | .Bind().To() 46 | .Bind(StatisticsMode.Default).To() 47 | .Bind(StatisticsMode.TeamCity).To() 48 | 49 | // ILogWriter 50 | .Bind().To() 51 | .Bind(ColorMode.Default).To() 52 | .Bind().Bind().Tags(ColorMode.TeamCity, TeamCityMode.SupportHierarchy).To() 53 | .Bind(ColorMode.NoColor).To() 54 | .Bind(ColorMode.AnsiColor).To() 55 | 56 | // IHierarchicalMessageWriter 57 | .Bind().To() 58 | .Bind(TeamCityMode.Off).To() 59 | 60 | // Build event handlers 61 | .Bind>().To() 62 | .Bind>().To() 63 | .Bind>().To() 64 | .Bind>().To() 65 | .Bind>().To() 66 | .Bind>().To() 67 | .Bind>().To() 68 | .Bind>().To() 69 | .Bind>().To() 70 | .Bind>().To() 71 | .Bind>().To() 72 | .Bind>().To() 73 | 74 | // Service messages 75 | .Bind().To() 76 | .Bind().To() 77 | .Bind().To() 78 | .Bind().As(Transient).To(_ => DateTime.Now) 79 | .Bind(typeof(TimestampUpdater)).To() 80 | .Bind(typeof(BuildErrorMessageUpdater)).To() 81 | .Bind(typeof(BuildWarningMessageUpdater)).To() 82 | .Bind(typeof(BuildMessageMessageUpdater)).To() 83 | .Bind().To( 84 | ctx => 85 | { 86 | ctx.Inject(out var teamCityServiceMessages); 87 | ctx.Inject(ColorMode.NoColor, out var logWriter); 88 | return teamCityServiceMessages.CreateWriter( 89 | str => { logWriter.Write(str + "\n"); }); 90 | }) 91 | .Bind().To(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/TargetFinishedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using System.Collections; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class TargetFinishedHandler : IBuildEventHandler 10 | { 11 | [NotNull] private readonly IStringService _stringService; 12 | [NotNull] private readonly IHierarchicalMessageWriter _hierarchicalMessageWriter; 13 | [NotNull] private readonly IBuildEventManager _buildEventManager; 14 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 15 | [NotNull] private readonly IMessageWriter _messageWriter; 16 | [NotNull] private readonly ILoggerContext _context; 17 | [NotNull] private readonly ILogWriter _logWriter; 18 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 19 | 20 | public TargetFinishedHandler( 21 | [NotNull] ILoggerContext context, 22 | [NotNull] ILogWriter logWriter, 23 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 24 | [NotNull] IMessageWriter messageWriter, 25 | [NotNull] IHierarchicalMessageWriter hierarchicalMessageWriter, 26 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 27 | [NotNull] IBuildEventManager buildEventManager, 28 | [NotNull] IStringService stringService) 29 | { 30 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 31 | _hierarchicalMessageWriter = hierarchicalMessageWriter ?? throw new ArgumentNullException(nameof(hierarchicalMessageWriter)); 32 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 33 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 34 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 35 | _context = context ?? throw new ArgumentNullException(nameof(context)); 36 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 37 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 38 | } 39 | 40 | public void Handle(TargetFinishedEventArgs e) 41 | { 42 | if (e == null) throw new ArgumentNullException(nameof(e)); 43 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 44 | if (_context.Parameters.ShowPerfSummary) 45 | { 46 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.TargetName, _context.TargetPerformanceCounters).AddEventFinished(null, e.BuildEventContext, e.Timestamp); 47 | } 48 | 49 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Detailed)) 50 | { 51 | _deferredMessageWriter.DisplayDeferredTargetStartedEvent(e.BuildEventContext); 52 | var targetStartedEvent = _buildEventManager.GetTargetStartedEvent(e.BuildEventContext); 53 | // ReSharper disable once NotResolvedInText 54 | if (targetStartedEvent == null) throw new ArgumentNullException("Started event should not be null in the finished event handler"); 55 | if (targetStartedEvent.ShowTargetFinishedEvent) 56 | { 57 | if (_context.Parameters.ShowTargetOutputs) 58 | { 59 | var targetOutputs = e.TargetOutputs; 60 | if (targetOutputs != null) 61 | { 62 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("TargetOutputItemsHeader"), false); 63 | foreach (ITaskItem taskItem in targetOutputs) 64 | { 65 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("TargetOutputItem", taskItem.ItemSpec), false); 66 | foreach (DictionaryEntry dictionaryEntry in taskItem.CloneCustomMetadata()) 67 | { 68 | _messageWriter.WriteMessageAligned(new string(' ', 8) + dictionaryEntry.Key + " = " + taskItem.GetMetadata((string)dictionaryEntry.Key), false); 69 | } 70 | } 71 | } 72 | } 73 | 74 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 75 | { 76 | _context.LastProjectFullKey = _context.GetFullProjectKey(e.BuildEventContext); 77 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 78 | _logWriter.SetColor(Color.BuildStage); 79 | if (_context.IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || (_context.Parameters.ShowEventId ?? false)) 80 | { 81 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("TargetMessageWithId", e.Message, e.BuildEventContext.TargetId), true); 82 | } 83 | else 84 | { 85 | _messageWriter.WriteMessageAligned(e.Message, true); 86 | } 87 | 88 | _logWriter.ResetColor(); 89 | } 90 | 91 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 92 | _hierarchicalMessageWriter.FinishBlock(); 93 | } 94 | } 95 | 96 | _buildEventManager.RemoveTargetStartedEvent(e.BuildEventContext); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Globalization; 5 | using System.Text; 6 | using Microsoft.Build.Framework; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class EventFormatter : IEventFormatter 10 | { 11 | private static readonly string[] NewLines = {"\r\n", "\n"}; 12 | 13 | public string FormatEventMessage( 14 | BuildErrorEventArgs e, 15 | bool removeCarriageReturn, 16 | bool showProjectFile) 17 | { 18 | if (e == null) throw new ArgumentNullException(nameof(e)); 19 | return FormatEventMessage( 20 | "error", 21 | e.Subcategory, 22 | removeCarriageReturn ? EscapeCarriageReturn(e.Message) : e.Message, 23 | e.Code, 24 | e.File, 25 | showProjectFile ? e.ProjectFile : null, 26 | e.LineNumber, 27 | e.EndLineNumber, 28 | e.ColumnNumber, 29 | e.EndColumnNumber, 30 | e.ThreadId); 31 | } 32 | 33 | public string FormatEventMessage( 34 | BuildWarningEventArgs e, 35 | bool removeCarriageReturn, 36 | bool showProjectFile) 37 | { 38 | if (e == null) throw new ArgumentNullException(nameof(e)); 39 | return FormatEventMessage( 40 | "warning", 41 | e.Subcategory, 42 | removeCarriageReturn ? EscapeCarriageReturn(e.Message) : e.Message, 43 | e.Code, 44 | e.File, 45 | showProjectFile ? e.ProjectFile : null, e.LineNumber, 46 | e.EndLineNumber, 47 | e.ColumnNumber, 48 | e.EndColumnNumber, 49 | e.ThreadId); 50 | } 51 | 52 | public string FormatEventMessage(BuildMessageEventArgs e, bool removeCarriageReturn, bool showProjectFile) 53 | { 54 | if (e == null) throw new ArgumentNullException(nameof(e)); 55 | return FormatEventMessage( 56 | "message", 57 | e.Subcategory, 58 | removeCarriageReturn ? EscapeCarriageReturn(e.Message) : e.Message, 59 | e.Code, 60 | e.File, 61 | showProjectFile ? e.ProjectFile : null, 62 | e.LineNumber, 63 | e.EndLineNumber, 64 | e.ColumnNumber, 65 | e.EndColumnNumber, 66 | e.ThreadId); 67 | } 68 | 69 | private static string FormatEventMessage( 70 | string category, 71 | string subcategory, 72 | string message, 73 | string code, 74 | string file, 75 | string projectFile, 76 | int lineNumber, 77 | int endLineNumber, 78 | int columnNumber, 79 | int endColumnNumber, 80 | int threadId) 81 | { 82 | var sb = new StringBuilder(); 83 | if (string.IsNullOrEmpty(file)) 84 | { 85 | sb.Append("MSBUILD : "); 86 | } 87 | else 88 | { 89 | sb.Append("{1}"); 90 | if (lineNumber == 0) 91 | { 92 | sb.Append(" : "); 93 | } 94 | else if (columnNumber == 0) 95 | { 96 | sb.Append(endLineNumber == 0 ? "({2}): " : "({2}-{7}): "); 97 | } 98 | else if (endLineNumber == 0) 99 | { 100 | sb.Append(endColumnNumber == 0 ? "({2},{3}): " : "({2},{3}-{8}): "); 101 | } 102 | else if (endColumnNumber == 0) 103 | { 104 | sb.Append("({2}-{7},{3}): "); 105 | } 106 | else 107 | { 108 | sb.Append("({2},{3},{7},{8}): "); 109 | } 110 | } 111 | 112 | if (!string.IsNullOrEmpty(subcategory)) 113 | { 114 | sb.Append("{9} "); 115 | } 116 | 117 | sb.Append("{4} "); 118 | sb.Append(code == null ? ": " : "{5}: "); 119 | 120 | if (message != null) 121 | { 122 | sb.Append("{6}"); 123 | } 124 | 125 | if (projectFile != null && !string.Equals(projectFile, file)) 126 | { 127 | sb.Append(" [{10}]"); 128 | } 129 | 130 | if (message == null) 131 | { 132 | message = string.Empty; 133 | } 134 | 135 | var format = sb.ToString(); 136 | var strArray = SplitStringOnNewLines(message); 137 | var stringBuilder2 = new StringBuilder(); 138 | for (var index = 0; index < strArray.Length; ++index) 139 | { 140 | stringBuilder2.Append(string.Format(CultureInfo.CurrentCulture, format, threadId, 141 | file, lineNumber, columnNumber, category, code, 142 | strArray[index], endLineNumber, endColumnNumber, subcategory, 143 | projectFile)); 144 | 145 | if (index < strArray.Length - 1) 146 | { 147 | stringBuilder2.AppendLine(); 148 | } 149 | } 150 | return stringBuilder2.ToString(); 151 | } 152 | 153 | private static string[] SplitStringOnNewLines(string str) 154 | { 155 | if (str == null) throw new ArgumentNullException(nameof(str)); 156 | return str.Split(NewLines, StringSplitOptions.None); 157 | } 158 | 159 | private static string EscapeCarriageReturn(string stringWithCarriageReturn) 160 | { 161 | return !string.IsNullOrEmpty(stringWithCarriageReturn) ? stringWithCarriageReturn.Replace("\r", "\\r") : stringWithCarriageReturn; 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/BuildEventManager.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using JetBrains.Annotations; 7 | using Microsoft.Build.Framework; 8 | 9 | // ReSharper disable once ClassNeverInstantiated.Global 10 | internal class BuildEventManager: IBuildEventManager 11 | { 12 | [NotNull] private readonly IStringService _stringService; 13 | private readonly IDictionary _projectTargetKey = new Dictionary(StringComparer.OrdinalIgnoreCase); 14 | private readonly IDictionary _projectKey = new Dictionary(StringComparer.OrdinalIgnoreCase); 15 | private readonly IDictionary _projectStartedEvents = new Dictionary(ComparerContextNodeId.Shared); 16 | private readonly IDictionary _targetStartedEvents = new Dictionary(ComparerContextNodeIdTargetId.Shared); 17 | private int _projectIncrementKey; 18 | 19 | public BuildEventManager( 20 | [NotNull] IStringService stringService) 21 | { 22 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 23 | } 24 | 25 | public void AddProjectStartedEvent(ProjectStartedEventArgs e, bool requireTimestamp) 26 | { 27 | if (e == null) throw new ArgumentNullException(nameof(e)); 28 | var projectStartedEvent = GetProjectStartedEvent(e.ParentProjectBuildEventContext); 29 | int projectIncrementKey; 30 | int entryPointKey; 31 | lock (_projectStartedEvents) 32 | { 33 | if (_projectStartedEvents.ContainsKey(e.BuildEventContext)) 34 | { 35 | return; 36 | } 37 | 38 | if (!_projectKey.TryGetValue(e.ProjectFile, out projectIncrementKey)) 39 | { 40 | _projectIncrementKey += 1; 41 | _projectKey.Add(e.ProjectFile, _projectIncrementKey); 42 | projectIncrementKey = _projectIncrementKey; 43 | } 44 | 45 | if (!_projectTargetKey.TryGetValue(e.ProjectFile, out entryPointKey)) 46 | { 47 | _projectTargetKey.Add(e.ProjectFile, 1); 48 | } 49 | else 50 | { 51 | _projectTargetKey[e.ProjectFile] = entryPointKey + 1; 52 | } 53 | } 54 | 55 | _projectStartedEvents.Add(e.BuildEventContext, new ProjectStartedEventMinimumFields(projectIncrementKey, entryPointKey, e, projectStartedEvent, requireTimestamp)); 56 | } 57 | 58 | public void AddTargetStartedEvent(TargetStartedEventArgs e, bool requireTimeStamp) 59 | { 60 | if (e == null) throw new ArgumentNullException(nameof(e)); 61 | if (_targetStartedEvents.ContainsKey(e.BuildEventContext)) 62 | { 63 | return; 64 | } 65 | 66 | _targetStartedEvents.Add(e.BuildEventContext, new TargetStartedEventMinimumFields(e, requireTimeStamp)); 67 | } 68 | 69 | public void SetErrorWarningFlagOnCallStack(BuildEventContext e) 70 | { 71 | if (e == null) throw new ArgumentNullException(nameof(e)); 72 | foreach (var projectCall in GetProjectCallStack(e)) 73 | { 74 | if (projectCall != null) 75 | { 76 | projectCall.ErrorInProject = true; 77 | } 78 | } 79 | } 80 | 81 | public IEnumerable ProjectCallStackFromProject(BuildEventContext e) 82 | { 83 | if (e == null) throw new ArgumentNullException(nameof(e)); 84 | var projectStartedEvent = GetProjectStartedEvent(e); 85 | if (projectStartedEvent == null) 86 | { 87 | return Enumerable.Empty(); 88 | } 89 | 90 | return ( 91 | from projectCall in GetProjectCallStack(e) 92 | select string.IsNullOrEmpty(projectCall.TargetNames) 93 | ? _stringService.FormatResourceString("ProjectStackWithTargetNames", projectCall.ProjectFile, projectCall.TargetNames, projectCall.FullProjectKey) 94 | : _stringService.FormatResourceString("ProjectStackWithDefaultTargets", projectCall.ProjectFile, projectCall.FullProjectKey)) 95 | .Reverse(); 96 | } 97 | 98 | public ProjectStartedEventMinimumFields GetProjectStartedEvent(BuildEventContext e) 99 | { 100 | if (e == null) throw new ArgumentNullException(nameof(e)); 101 | return _projectStartedEvents.TryGetValue(e, out var result) ? result : null; 102 | } 103 | 104 | public TargetStartedEventMinimumFields GetTargetStartedEvent(BuildEventContext e) 105 | { 106 | if (e == null) throw new ArgumentNullException(nameof(e)); 107 | return _targetStartedEvents.TryGetValue(e, out var result) ? result : null; 108 | } 109 | 110 | public void RemoveProjectStartedEvent(BuildEventContext e) 111 | { 112 | if (e == null) throw new ArgumentNullException(nameof(e)); 113 | var projectStartedEvent = GetProjectStartedEvent(e); 114 | if (projectStartedEvent == null || projectStartedEvent.ErrorInProject) 115 | { 116 | return; 117 | } 118 | 119 | _projectStartedEvents.Remove(e); 120 | } 121 | 122 | public void RemoveTargetStartedEvent(BuildEventContext e) 123 | { 124 | if (e == null) throw new ArgumentNullException(nameof(e)); 125 | var targetStartedEvent = GetTargetStartedEvent(e); 126 | if (targetStartedEvent == null || targetStartedEvent.ErrorInTarget) 127 | { 128 | return; 129 | } 130 | 131 | _targetStartedEvents.Remove(e); 132 | } 133 | 134 | public void Reset() 135 | { 136 | _projectTargetKey.Clear(); 137 | _projectKey.Clear(); 138 | _projectStartedEvents.Clear(); 139 | _targetStartedEvents.Clear(); 140 | _projectIncrementKey = 0; 141 | } 142 | 143 | [NotNull] 144 | private IEnumerable GetProjectCallStack([NotNull] BuildEventContext e) 145 | { 146 | if (e == null) throw new ArgumentNullException(nameof(e)); 147 | var projectStartedEvent = GetProjectStartedEvent(e); 148 | if (projectStartedEvent == null) 149 | { 150 | yield break; 151 | } 152 | 153 | yield return projectStartedEvent; 154 | while (projectStartedEvent.ParentProjectStartedEvent != null) 155 | { 156 | projectStartedEvent = projectStartedEvent.ParentProjectStartedEvent; 157 | yield return projectStartedEvent; 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger.Tests/DotnetIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable StringLiteralTypo 2 | namespace TeamCity.MSBuild.Logger.Tests 3 | { 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using Helpers; 9 | using JetBrains.Annotations; 10 | using Shouldly; 11 | using Xunit; 12 | using Xunit.Abstractions; 13 | 14 | [Collection("Integration")] 15 | public class DotnetIntegrationTests 16 | { 17 | private readonly ITestOutputHelper _testOutputHelper; 18 | 19 | public DotnetIntegrationTests(ITestOutputHelper testOutputHelper) => _testOutputHelper = testOutputHelper; 20 | 21 | [Theory] 22 | [ClassData(typeof(TestDataGenerator))] 23 | public void ShouldProduceSameMessagesAsConsoleLoggerViaDotnet( 24 | string framework, 25 | int processCount, 26 | [NotNull] string verbosity, 27 | [CanBeNull] string parameters, 28 | bool producesTeamCityServiceMessages, 29 | string dotnetVersion) 30 | { 31 | // Given 32 | WriteLine(); 33 | WriteLine($@"Run: framework={framework}, sdk={dotnetVersion}, processCount={processCount}, verbosity={verbosity}"); 34 | 35 | var environmentVariables = new Dictionary(); 36 | var loggerString = framework.CreateLoggerString(parameters); 37 | var projectDir = Path.GetFullPath(Path.Combine(CommandLine.WorkingDirectory, @"IntegrationTests\Console")); 38 | var projectPath = Path.Combine(projectDir, "Console.csproj"); 39 | var globalJsonPath = Path.Combine(projectDir, "global.json"); 40 | 41 | using(var json = File.CreateText(globalJsonPath)) 42 | { 43 | json.WriteLine("{"); 44 | json.WriteLine("\"sdk\": {"); 45 | json.WriteLine($"\"version\": \"{dotnetVersion}\""); 46 | json.WriteLine("}"); 47 | json.WriteLine("}"); 48 | } 49 | 50 | var restoreWithLoggerCommandLine = new CommandLine( 51 | @"dotnet", 52 | environmentVariables, 53 | "restore", 54 | projectPath, 55 | "--verbosity", 56 | verbosity, 57 | "/noconsolelogger", 58 | $"/m:{processCount}", 59 | $@"/l:{loggerString}"); 60 | 61 | var buildWithLoggerCommandLine = new CommandLine( 62 | @"dotnet", 63 | environmentVariables, 64 | "build", 65 | projectPath, 66 | "--verbosity", 67 | verbosity, 68 | "/noconsolelogger", 69 | $"/m:{processCount}", 70 | $@"/l:{loggerString}"); 71 | 72 | var restoreCommandLine = new CommandLine( 73 | @"dotnet", 74 | environmentVariables, 75 | "restore", 76 | projectPath, 77 | "--verbosity", 78 | verbosity, 79 | $"/m:{processCount}"); 80 | 81 | var buildCommandLine = new CommandLine( 82 | @"dotnet", 83 | environmentVariables, 84 | "build", 85 | projectPath, 86 | "--verbosity", 87 | verbosity, 88 | $"/m:{processCount}"); 89 | 90 | // When 91 | WriteLine(); 92 | WriteLine(@"Without TeamCity logger"); 93 | 94 | restoreCommandLine.TryExecute(out var restoreResult).ShouldBe(true); 95 | buildCommandLine.TryExecute(out var buildResult).ShouldBe(true); 96 | 97 | WriteLine(); 98 | WriteLine(@"With TeamCity logger"); 99 | 100 | restoreWithLoggerCommandLine.TryExecute(out var restoreWithLoggerResult).ShouldBe(true); 101 | buildWithLoggerCommandLine.TryExecute(out var buildWithLoggerResult).ShouldBe(true); 102 | 103 | // Then 104 | restoreWithLoggerResult.ResultShouldBe(restoreResult); 105 | buildWithLoggerResult.ResultShouldBe(buildResult, producesTeamCityServiceMessages); 106 | 107 | try 108 | { 109 | File.Delete(globalJsonPath); 110 | } 111 | catch 112 | { 113 | // ignored 114 | } 115 | } 116 | 117 | private void WriteLine(string message = "") => _testOutputHelper.WriteLine(message); 118 | 119 | private class TestDataGenerator : IEnumerable 120 | { 121 | private static readonly object[][] Cases = 122 | { 123 | new object[] { "netstandard1.6", 10, "minimal", null, false }, 124 | new object[] { "netstandard1.6", 1, "m", null, false }, 125 | new object[] { "netstandard1.6", 1, "quiet", null, false }, 126 | new object[] { "netstandard1.6", 10, "quiet", null, false }, 127 | new object[] { "netstandard1.6", 1, "q", null, false }, 128 | new object[] { "netstandard1.6", 10, "q", null, false }, 129 | new object[] { "netstandard1.6", 10, "normal", null, false }, 130 | new object[] { "netstandard1.6", 10, "normal", "TEAMcity", true }, 131 | new object[] { "netstandard1.6", 10, "n", null, false }, 132 | new object[] { "netstandard1.6", 10, "detailed", null, false }, 133 | new object[] { "netstandard1.6", 10, "d", null, false }, 134 | new object[] { "netstandard1.6", 10, "diagnostic", null, false }, 135 | new object[] { "netstandard1.6", 10, "diag", null, false }, 136 | new object[] { "netstandard1.6", 10, "deTailed", null, false }, 137 | new object[] { "netstandard1.6", 10, "diag", "teamcity", true }, 138 | new object[] { "netstandard1.6", 10, "deTailed", "teamcity", true } 139 | }; 140 | 141 | public IEnumerator GetEnumerator() => CreateCases().GetEnumerator(); 142 | 143 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 144 | 145 | private static IEnumerable CreateCases() 146 | { 147 | var cmd = Path.Combine(CommandLine.WorkingDirectory, @"tools\dotnet-sdk.cmd"); 148 | var listCommandLine = new CommandLine(cmd, new Dictionary(), "list"); 149 | listCommandLine.TryExecute(out var listCommandLineResult).ShouldBe(true); 150 | var dotnetVersions = listCommandLineResult.StdOut.Skip(1); 151 | 152 | return 153 | from dotnetVersion in dotnetVersions 154 | from caseData in Cases 155 | select CreateCase(caseData, dotnetVersion); 156 | } 157 | 158 | private static object[] CreateCase(object[] caseData, string dotNetVersion) 159 | { 160 | var data = new object[caseData.Length + 1]; 161 | caseData.CopyTo(data, 0); 162 | data[^1] = dotNetVersion; 163 | return data; 164 | } 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/ParametersParser.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger 2 | { 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using JetBrains.Annotations; 6 | using Microsoft.Build.Framework; 7 | 8 | // ReSharper disable once ClassNeverInstantiated.Global 9 | internal class ParametersParser : IParametersParser 10 | { 11 | private static readonly char[] ParameterDelimiters = { ';' }; 12 | private static readonly char[] ParameterValueSplitCharacter = { '=' }; 13 | 14 | public bool TryParse(string parametersString, Parameters parameters, out string error) 15 | { 16 | if (parameters == null) throw new ArgumentNullException(nameof(parameters)); 17 | 18 | error = null; 19 | if (parametersString == null) 20 | { 21 | return true; 22 | } 23 | 24 | foreach (var parameter in parametersString.Split(ParameterDelimiters)) 25 | { 26 | if (parameter.Length <= 0) 27 | { 28 | continue; 29 | } 30 | 31 | var paramList = parameter.Split(ParameterValueSplitCharacter); 32 | if(!ApplyParameter(parameters, paramList[0], paramList.Length > 1 ? paramList[1] : null, out error)) 33 | { 34 | return false; 35 | } 36 | } 37 | 38 | parameters.AlignMessages = false; 39 | parameters.BufferWidth = -1; 40 | if (parameters.ForceNoAlign) 41 | { 42 | return true; 43 | } 44 | 45 | try 46 | { 47 | parameters.BufferWidth = Console.BufferWidth; 48 | parameters.AlignMessages = true; 49 | } 50 | catch (Exception) 51 | { 52 | parameters.AlignMessages = false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | [SuppressMessage("ReSharper", "StringLiteralTypo")] 59 | private static bool ApplyParameter([NotNull] Parameters parameters, [NotNull] string parameterName, [CanBeNull] string parameterValue, out string error) 60 | { 61 | if (parameters == null) throw new ArgumentNullException(nameof(parameters)); 62 | if (parameterName == null) throw new ArgumentNullException(nameof(parameterName)); 63 | var parameterNameUpper = parameterName.ToUpperInvariant(); 64 | error = null; 65 | switch (parameterNameUpper) 66 | { 67 | case "DEBUG": 68 | parameters.Debug = true; 69 | return true; 70 | 71 | case "WARNINGSONLY": 72 | parameters.ShowOnlyWarnings = true; 73 | return true; 74 | 75 | case "SHOWENVIRONMENT": 76 | parameters.ShowEnvironment = true; 77 | return true; 78 | 79 | case "DISABLECONSOLECOLOR": 80 | parameters.ColorMode = ColorMode.NoColor; 81 | return true; 82 | 83 | case "FORCECONSOLECOLOR": 84 | parameters.ColorMode = ColorMode.AnsiColor; 85 | return true; 86 | 87 | case "TEAMCITY": 88 | parameters.TeamCityMode = TeamCityMode.SupportHierarchy; 89 | parameters.StatisticsMode = StatisticsMode.TeamCity; 90 | parameters.ColorMode = ColorMode.TeamCity; 91 | parameters.ColorThemeMode = ColorThemeMode.TeamCity; 92 | parameters.ForceNoAlign = true; 93 | parameters.AlignMessages = false; 94 | parameters.ShowSummary = true; 95 | return true; 96 | 97 | case "V": 98 | case "VERBOSITY": 99 | // ReSharper disable once InvertIf 100 | if (TryApplyVerbosityParameter(parameterValue, out error, out var verbosity)) 101 | { 102 | parameters.Verbosity = verbosity; 103 | return true; 104 | } 105 | return false; 106 | 107 | case "PERFORMANCESUMMARY": 108 | parameters.ShowPerfSummary = true; 109 | return true; 110 | 111 | case "NOITEMANDPROPERTYLIST": 112 | parameters.ShowItemAndPropertyList = false; 113 | return true; 114 | 115 | case "NOSUMMARY": 116 | parameters.ShowSummary = false; 117 | return true; 118 | 119 | case "ERRORSONLY": 120 | parameters.ShowOnlyErrors = true; 121 | return true; 122 | 123 | // Do not use nested TeamCity service messages 124 | case "PLAIN": 125 | parameters.PlainServiceMessage = true; 126 | return true; 127 | 128 | case "SHOWPROJECTFILE": 129 | parameters.ShowProjectFile = string.IsNullOrEmpty(parameterValue) || parameterValue.ToUpperInvariant() == "TRUE"; 130 | return true; 131 | 132 | case "SUMMARY": 133 | parameters.ShowSummary = true; 134 | return true; 135 | 136 | case "SHOWCOMMANDLINE": 137 | parameters.ShowCommandLine = true; 138 | return true; 139 | 140 | case "SHOWTIMESTAMP": 141 | parameters.ShowTimeStamp = true; 142 | return true; 143 | 144 | case "SHOWEVENTID": 145 | parameters.ShowEventId = true; 146 | return true; 147 | 148 | case "FORCENOALIGN": 149 | parameters.ForceNoAlign = true; 150 | parameters.AlignMessages = false; 151 | return true; 152 | 153 | default: 154 | error = $"Invalid parameter \"{parameterName}\"=\"{parameterValue ?? "null"}\""; 155 | return false; 156 | } 157 | } 158 | 159 | private static bool TryApplyVerbosityParameter(string parameterValue, out string error, out LoggerVerbosity verbosity) 160 | { 161 | var parameterValueUpper = parameterValue.ToUpperInvariant(); 162 | error = null; 163 | switch (parameterValueUpper) 164 | { 165 | case "N": 166 | case "NORMAL": 167 | verbosity = LoggerVerbosity.Normal; 168 | return true; 169 | 170 | case "M": 171 | case "MINIMAL": 172 | verbosity = LoggerVerbosity.Minimal; 173 | return true; 174 | 175 | case "DIAG": 176 | case "DIAGNOSTIC": 177 | verbosity = LoggerVerbosity.Diagnostic; 178 | return true; 179 | 180 | case "D": 181 | case "DETAILED": 182 | verbosity = LoggerVerbosity.Detailed; 183 | return true; 184 | 185 | case "Q": 186 | case "QUIET": 187 | verbosity = LoggerVerbosity.Quiet; 188 | return true; 189 | 190 | default: 191 | error = $"Invalid verbosity \"{parameterValue}\""; 192 | verbosity = default; 193 | return false; 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/EventHandlers/ProjectStartedHandler.cs: -------------------------------------------------------------------------------- 1 | namespace TeamCity.MSBuild.Logger.EventHandlers 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Linq; 6 | using Microsoft.Build.Framework; 7 | using System.Collections.Generic; 8 | using System.Text; 9 | using System.Globalization; 10 | using JetBrains.Annotations; 11 | 12 | // ReSharper disable once ClassNeverInstantiated.Global 13 | internal class ProjectStartedHandler : IBuildEventHandler 14 | { 15 | [NotNull] private readonly IStringService _stringService; 16 | [NotNull] private readonly IBuildEventManager _buildEventManager; 17 | [NotNull] private readonly IDeferredMessageWriter _deferredMessageWriter; 18 | [NotNull] private readonly IMessageWriter _messageWriter; 19 | [NotNull] private readonly ILoggerContext _context; 20 | [NotNull] private readonly IBuildEventHandler _messageHandler; 21 | [NotNull] private readonly IPerformanceCounterFactory _performanceCounterFactory; 22 | [NotNull] private readonly ILogWriter _logWriter; 23 | 24 | public ProjectStartedHandler( 25 | [NotNull] ILoggerContext context, 26 | [NotNull] ILogWriter logWriter, 27 | [NotNull] IPerformanceCounterFactory performanceCounterFactory, 28 | [NotNull] IBuildEventHandler messageHandler, 29 | [NotNull] IMessageWriter messageWriter, 30 | [NotNull] IDeferredMessageWriter deferredMessageWriter, 31 | [NotNull] IBuildEventManager buildEventManager, 32 | [NotNull] IStringService stringService) 33 | { 34 | _stringService = stringService ?? throw new ArgumentNullException(nameof(stringService)); 35 | _buildEventManager = buildEventManager ?? throw new ArgumentNullException(nameof(buildEventManager)); 36 | _deferredMessageWriter = deferredMessageWriter ?? throw new ArgumentNullException(nameof(deferredMessageWriter)); 37 | _messageWriter = messageWriter ?? throw new ArgumentNullException(nameof(messageWriter)); 38 | _context = context ?? throw new ArgumentNullException(nameof(context)); 39 | _messageHandler = messageHandler ?? throw new ArgumentNullException(nameof(messageHandler)); 40 | _performanceCounterFactory = performanceCounterFactory ?? throw new ArgumentNullException(nameof(performanceCounterFactory)); 41 | _logWriter = logWriter ?? throw new ArgumentNullException(nameof(logWriter)); 42 | } 43 | 44 | public void Handle(ProjectStartedEventArgs e) 45 | { 46 | if (e == null) throw new ArgumentNullException(nameof(e)); 47 | if (e.BuildEventContext == null) throw new ArgumentException(nameof(e)); 48 | 49 | _buildEventManager.AddProjectStartedEvent(e, _context.Parameters.ShowTimeStamp || _context.IsVerbosityAtLeast(LoggerVerbosity.Detailed)); 50 | if (_context.Parameters.ShowPerfSummary) 51 | { 52 | _performanceCounterFactory.GetOrCreatePerformanceCounter(e.ProjectFile, _context.ProjectPerformanceCounters).AddEventStarted(e.TargetNames, e.BuildEventContext, e.Timestamp, ComparerContextNodeId.Shared); 53 | } 54 | 55 | if (_context.DeferredMessages.TryGetValue(e.BuildEventContext, out var messages)) 56 | { 57 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 58 | { 59 | foreach (var message in messages) 60 | { 61 | _messageHandler.Handle(message); 62 | } 63 | } 64 | 65 | _context.DeferredMessages.Remove(e.BuildEventContext); 66 | } 67 | 68 | if (_context.Verbosity != LoggerVerbosity.Diagnostic || !_context.Parameters.ShowItemAndPropertyList) 69 | { 70 | return; 71 | } 72 | 73 | if (!_context.Parameters.ShowOnlyErrors && !_context.Parameters.ShowOnlyWarnings) 74 | { 75 | _deferredMessageWriter.DisplayDeferredProjectStartedEvent(e.BuildEventContext); 76 | } 77 | 78 | if (e.Properties != null) 79 | { 80 | WriteProperties(e, e.Properties.Cast().Select(i => new Property((string)i.Key, (string)i.Value)).ToList()); 81 | } 82 | 83 | if (e.Items != null) 84 | { 85 | WriteItems(e, e.Items.Cast().Select(i => new TaskItem((string)i.Key, (ITaskItem)i.Value)).ToList()); 86 | } 87 | } 88 | 89 | private void WriteItems([NotNull] BuildEventArgs e, [NotNull] IList items) 90 | { 91 | if (e == null) throw new ArgumentNullException(nameof(e)); 92 | if (items == null) throw new ArgumentNullException(nameof(items)); 93 | if (_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 94 | { 95 | return; 96 | } 97 | 98 | if (items.Count == 0) 99 | { 100 | return; 101 | } 102 | 103 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 104 | WriteItems(items); 105 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 106 | } 107 | 108 | private void WriteItems([NotNull] IList items) 109 | { 110 | if (items == null) throw new ArgumentNullException(nameof(items)); 111 | if (_context.Verbosity != LoggerVerbosity.Diagnostic || !_context.Parameters.ShowItemAndPropertyList || items.Count == 0) 112 | { 113 | return; 114 | } 115 | 116 | _logWriter.SetColor(Color.Items); 117 | _messageWriter.WriteLinePretty(_context.CurrentIndentLevel, _stringService.FormatResourceString("ItemListHeader")); 118 | 119 | var groupedItems = from item in items 120 | group item by item.Name 121 | into groupedByName 122 | orderby groupedByName.Key.ToLowerInvariant() 123 | select new { ItemType = groupedByName.Key, Items = groupedByName.Select(i => i.Item).OrderBy(i => i, TaskItemItemSpecComparer.Shared) }; 124 | 125 | foreach (var groupedItem in groupedItems) 126 | { 127 | OutputItems(groupedItem.ItemType, groupedItem.Items); 128 | } 129 | 130 | _messageWriter.WriteNewLine(); 131 | } 132 | 133 | private void OutputItems([NotNull] string itemType, [NotNull] IEnumerable items) 134 | { 135 | if (itemType == null) throw new ArgumentNullException(nameof(itemType)); 136 | if (items == null) throw new ArgumentNullException(nameof(items)); 137 | var isFirst = true; 138 | foreach (var item in items) 139 | { 140 | if (isFirst) 141 | { 142 | _logWriter.SetColor(Color.Details); 143 | _messageWriter.WriteMessageAligned(itemType, false); 144 | isFirst = false; 145 | } 146 | 147 | _logWriter.SetColor(Color.SummaryInfo); 148 | var stringBuilder = new StringBuilder(); 149 | stringBuilder.Append(' ', 4).Append(item.ItemSpec); 150 | _messageWriter.WriteMessageAligned(stringBuilder.ToString(), false); 151 | foreach (DictionaryEntry dictionaryEntry in item.CloneCustomMetadata()) 152 | { 153 | _messageWriter.WriteMessageAligned(new string(' ', 8) + dictionaryEntry.Key + " = " + item.GetMetadata((string)dictionaryEntry.Key), false); 154 | } 155 | } 156 | 157 | _logWriter.ResetColor(); 158 | } 159 | 160 | private void WriteProperties([NotNull] IEnumerable properties) 161 | { 162 | if (properties == null) throw new ArgumentNullException(nameof(properties)); 163 | if (_context.Verbosity != LoggerVerbosity.Diagnostic || !_context.Parameters.ShowItemAndPropertyList) 164 | { 165 | return; 166 | } 167 | 168 | OutputProperties(properties); 169 | _messageWriter.WriteNewLine(); 170 | } 171 | 172 | private void WriteProperties(BuildEventArgs e, ICollection properties) 173 | { 174 | if (_context.Parameters.ShowOnlyErrors || _context.Parameters.ShowOnlyWarnings) 175 | { 176 | return; 177 | } 178 | 179 | if (properties.Count == 0) 180 | { 181 | return; 182 | } 183 | 184 | _messageWriter.WriteLinePrefix(e.BuildEventContext, e.Timestamp, false); 185 | WriteProperties(properties); 186 | _deferredMessageWriter.ShownBuildEventContext(e.BuildEventContext); 187 | } 188 | 189 | private void OutputProperties(IEnumerable properties) 190 | { 191 | if (properties == null) throw new ArgumentNullException(nameof(properties)); 192 | _logWriter.SetColor(Color.Items); 193 | _messageWriter.WriteMessageAligned(_stringService.FormatResourceString("PropertyListHeader"), true); 194 | foreach (var property in properties.OrderBy(i => i, DictionaryEntryKeyComparer.Shared)) 195 | { 196 | _logWriter.SetColor(Color.SummaryInfo); 197 | _messageWriter.WriteMessageAligned(string.Format(CultureInfo.CurrentCulture, "{0} = {1}", property.Name, _stringService.UnescapeAll(property.Value)), false); 198 | } 199 | 200 | _logWriter.ResetColor(); 201 | } 202 | 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /TeamCity.MSBuild.Logger/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | "{0}" ({1} target) ({2}) -> 122 | 123 | 124 | "{0}" (default target) ({1}) -> 125 | 126 | 127 | Project "{0}" on node {1} (default targets). 128 | 129 | 130 | Project "{0}" on node {1} ({2} target(s)). 131 | 132 | 133 | Project "{0}" ({1}) is building "{2}" ({3}) on node {4} (default targets). 134 | 135 | 136 | Project "{0}" ({1}) is building "{2}" ({3}) on node {4} ({5} target(s)). 137 | 138 | 139 | {0}: (TargetId:{1}) 140 | 141 | 142 | Target "{0}" in project "{1}" (target "{2}" depends on it): 143 | 144 | 145 | Target "{0}" in project "{1}" (entry point): 146 | 147 | 148 | Target "{0}" in file "{1}" from project "{2}" (target "{3}" depends on it): 149 | 150 | 151 | Target "{0}" in file "{1}" from project "{2}" (entry point): 152 | 153 | 154 | ({0} target) -> 155 | 156 | 157 | Environment at start of build: 158 | 159 | 160 | Done Building Project "{0}" (default targets). 161 | 162 | 163 | Done Building Project "{0}" (default targets) -- FAILED. 164 | 165 | 166 | Done Building Project "{0}" ({1} target(s)). 167 | 168 | 169 | Done Building Project "{0}" ({1} target(s)) -- FAILED. 170 | 171 | 172 | Initial Items: 173 | 174 | 175 | Initial Properties: 176 | 177 | 178 | Target output items: 179 | 180 | 181 | {0} 182 | 183 | 184 | {0} (TaskId:{1}) 185 | 186 | 187 | {0} {1,5} 188 | 189 | 190 | Deferred Messages 191 | 192 | 193 | Time Elapsed {0} 194 | 195 | 196 | Project Performance Summary: 197 | 198 | 199 | Target Performance Summary: 200 | 201 | 202 | Task Performance Summary: 203 | 204 | 205 | Build started {0}. 206 | 207 | 208 | {0} Warning(s) 209 | 210 | 211 | {0} Error(s) 212 | 213 | 214 | (* = timing was not recorded because of reentrancy) 215 | 216 | 217 | {0} ms {1} {2} calls 218 | 219 | --------------------------------------------------------------------------------