diff --git a/MSBuildStructuredLog.Avalonia.sln b/MSBuildStructuredLog.Avalonia.sln index bc68ca51..81b5baba 100644 --- a/MSBuildStructuredLog.Avalonia.sln +++ b/MSBuildStructuredLog.Avalonia.sln @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TaskRunner", "src\TaskRunne EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredLogViewer.Core", "src\StructuredLogViewer.Core\StructuredLogViewer.Core.csproj", "{3C655C5D-22C3-4B8D-969C-3FC497294703}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredLogViewer.Avalonia.Desktop", "src\StructuredLogViewer.Avalonia.Desktop\StructuredLogViewer.Avalonia.Desktop.csproj", "{4143B4F2-1FC3-469C-A91C-2721705820AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredLogViewer.Avalonia.Browser", "src\StructuredLogViewer.Avalonia.Browser\StructuredLogViewer.Avalonia.Browser.csproj", "{95215092-37BB-4ED1-91D4-A96535508C12}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Mixed Platforms = Debug|Mixed Platforms @@ -48,6 +52,14 @@ Global {B8DB3798-1636-417D-B57A-25C51C574F1E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {B8DB3798-1636-417D-B57A-25C51C574F1E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {B8DB3798-1636-417D-B57A-25C51C574F1E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4143B4F2-1FC3-469C-A91C-2721705820AC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4143B4F2-1FC3-469C-A91C-2721705820AC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4143B4F2-1FC3-469C-A91C-2721705820AC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4143B4F2-1FC3-469C-A91C-2721705820AC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {95215092-37BB-4ED1-91D4-A96535508C12}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {95215092-37BB-4ED1-91D4-A96535508C12}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {95215092-37BB-4ED1-91D4-A96535508C12}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {95215092-37BB-4ED1-91D4-A96535508C12}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MSBuildStructuredLog.sln b/MSBuildStructuredLog.sln index c77296dd..92a16242 100644 --- a/MSBuildStructuredLog.sln +++ b/MSBuildStructuredLog.sln @@ -28,6 +28,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResourcesGenerator", "src\R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BinlogTool", "src\BinlogTool\BinlogTool.csproj", "{35F44EA6-7259-43CC-8C17-E058F3EB86D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredLogViewer.Avalonia.Desktop", "src\StructuredLogViewer.Avalonia.Desktop\StructuredLogViewer.Avalonia.Desktop.csproj", "{BA82B344-6C38-4449-A318-BF1AEF33D7D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructuredLogViewer.Avalonia.Browser", "src\StructuredLogViewer.Avalonia.Browser\StructuredLogViewer.Avalonia.Browser.csproj", "{E16075FD-60E4-430B-BBCF-61692C6A3157}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Mixed Platforms = Debug|Mixed Platforms @@ -66,6 +70,14 @@ Global {35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU {35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {35F44EA6-7259-43CC-8C17-E058F3EB86D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BA82B344-6C38-4449-A318-BF1AEF33D7D8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BA82B344-6C38-4449-A318-BF1AEF33D7D8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BA82B344-6C38-4449-A318-BF1AEF33D7D8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA82B344-6C38-4449-A318-BF1AEF33D7D8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E16075FD-60E4-430B-BBCF-61692C6A3157}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E16075FD-60E4-430B-BBCF-61692C6A3157}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E16075FD-60E4-430B-BBCF-61692C6A3157}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E16075FD-60E4-430B-BBCF-61692C6A3157}.Release|Mixed Platforms.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/appveyor.yml b/appveyor.yml index ecd26af4..1ff5f1a9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,9 +9,11 @@ install: - choco upgrade chocolatey # Need at least 0.10.8 to avoid packaging error - dotnet tool install --global Cake.Tool --version 1.0.0 - ps: Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile "$env:temp\dotnet-install.ps1" - - ps: '& $env:temp\dotnet-install.ps1 -Version "6.0.303"' -build_script: + - ps: '& $env:temp\dotnet-install.ps1 -Version "7.0.0"' +build_script: - cmd: >- + dotnet workload restore + dotnet msbuild /r /m /p:Configuration=Release /clp:v=m MSBuildStructuredLog.sln /bl /logger:"C:\Program Files\AppVeyor\BuildAgent\dotnetcore\Appveyor.MSBuildLogger.dll" dotnet cake ./build-macos.cake --settings_skippackageversioncheck=true diff --git a/src/StructuredLogViewer.Avalonia.Browser/AppBundle/app.css b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/app.css new file mode 100644 index 00000000..cdf48c14 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/app.css @@ -0,0 +1,52 @@ +/* HTML styles for the splash screen */ + +.highlight { + color: white; + font-size: 2.5rem; + display: block; +} + +.purple { + color: #8b44ac; +} + +#avalonia-splash a { + color: whitesmoke; + text-decoration: none; +} + +.center { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} + +#avalonia-splash { + position: relative; + height: 100%; + width: 100%; + color: whitesmoke; + background-color: #1b2a4e; + font-family: 'Nunito', sans-serif; + background-position: center; + background-size: cover; + background-repeat: no-repeat; + justify-content: center; + align-items: center; +} + +.splash-close { + animation: fadeout 0.25s linear forwards; +} + +@keyframes fadeout { + 0% { + opacity: 100%; + } + + 100% { + opacity: 0; + visibility: collapse; + } +} diff --git a/src/StructuredLogViewer.Avalonia.Browser/AppBundle/favicon.ico b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/favicon.ico new file mode 100644 index 00000000..a7e85269 Binary files /dev/null and b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/favicon.ico differ diff --git a/src/StructuredLogViewer.Avalonia.Browser/AppBundle/index.html b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/index.html new file mode 100644 index 00000000..95865348 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/index.html @@ -0,0 +1,30 @@ + + + + + StructuredLogViewer + + + + + + + + + + + +
+
+
+

+ Powered by + Avalonia UI +

+
+
+
+ + + + \ No newline at end of file diff --git a/src/StructuredLogViewer.Avalonia.Browser/AppBundle/main.js b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/main.js new file mode 100644 index 00000000..b0b10186 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/AppBundle/main.js @@ -0,0 +1,16 @@ +import { dotnet } from './dotnet.js' +import { registerAvaloniaModule } from './avalonia.js'; + +const is_browser = typeof window != "undefined"; +if (!is_browser) throw new Error(`Expected to be running in a browser`); + +const dotnetRuntime = await dotnet + .withDiagnosticTracing(false) + .withApplicationArgumentsFromQuery() + .create(); + +await registerAvaloniaModule(dotnetRuntime); + +const config = dotnetRuntime.getConfig(); + +await dotnetRuntime.runMainAndExit(config.mainAssemblyName, [window.location.search]); \ No newline at end of file diff --git a/src/StructuredLogViewer.Avalonia.Browser/Program.cs b/src/StructuredLogViewer.Avalonia.Browser/Program.cs new file mode 100644 index 00000000..aa4fd0d5 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/Program.cs @@ -0,0 +1,15 @@ +using System.Runtime.Versioning; +using Avalonia; +using Avalonia.Browser; +using StructuredLogViewer.Avalonia; + +[assembly: SupportedOSPlatform("browser")] + +internal partial class Program +{ + private static void Main(string[] args) => BuildAvaloniaApp() + .SetupBrowserApp("out"); + + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure(); +} diff --git a/src/StructuredLogViewer.Avalonia.Browser/StructuredLogViewer.Avalonia.Browser.csproj b/src/StructuredLogViewer.Avalonia.Browser/StructuredLogViewer.Avalonia.Browser.csproj new file mode 100644 index 00000000..e797f4b4 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/StructuredLogViewer.Avalonia.Browser.csproj @@ -0,0 +1,24 @@ + + + net7.0 + browser-wasm + AppBundle\main.js + Exe + + + + + + + + + + + + + + + + + + diff --git a/src/StructuredLogViewer.Avalonia.Browser/runtimeconfig.template.json b/src/StructuredLogViewer.Avalonia.Browser/runtimeconfig.template.json new file mode 100644 index 00000000..cc3f2ae4 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Browser/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} \ No newline at end of file diff --git a/src/StructuredLogViewer.Avalonia/Info.plist b/src/StructuredLogViewer.Avalonia.Desktop/Info.plist similarity index 100% rename from src/StructuredLogViewer.Avalonia/Info.plist rename to src/StructuredLogViewer.Avalonia.Desktop/Info.plist diff --git a/src/StructuredLogViewer.Avalonia.Desktop/Program.cs b/src/StructuredLogViewer.Avalonia.Desktop/Program.cs new file mode 100644 index 00000000..bbcb7294 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Desktop/Program.cs @@ -0,0 +1,20 @@ +using Avalonia; +using System; + +namespace StructuredLogViewer.Avalonia.Desktop; + +class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace(); +} diff --git a/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogViewer.Avalonia.Desktop.csproj b/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogViewer.Avalonia.Desktop.csproj new file mode 100644 index 00000000..bb2fc3ac --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogViewer.Avalonia.Desktop.csproj @@ -0,0 +1,31 @@ + + + WinExe + win7-x64;ubuntu.14.04-x64;osx.10.12-x64;osx-arm64 + net7.0 + enable + true + StructuredLogger.ico + true + + + + + PreserveNewest + + + PreserveNewest + + + + app.manifest + + + + + + + + + + \ No newline at end of file diff --git a/src/StructuredLogViewer.Avalonia/StructuredLogViewer.icns b/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogViewer.icns similarity index 100% rename from src/StructuredLogViewer.Avalonia/StructuredLogViewer.icns rename to src/StructuredLogViewer.Avalonia.Desktop/StructuredLogViewer.icns diff --git a/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogger.ico b/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogger.ico new file mode 100644 index 00000000..a7e85269 Binary files /dev/null and b/src/StructuredLogViewer.Avalonia.Desktop/StructuredLogger.ico differ diff --git a/src/StructuredLogViewer.Avalonia.Desktop/app.manifest b/src/StructuredLogViewer.Avalonia.Desktop/app.manifest new file mode 100644 index 00000000..5eedf0e0 --- /dev/null +++ b/src/StructuredLogViewer.Avalonia.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/StructuredLogViewer.Avalonia/App.xaml b/src/StructuredLogViewer.Avalonia/App.xaml index e5765632..f0ea3db3 100644 --- a/src/StructuredLogViewer.Avalonia/App.xaml +++ b/src/StructuredLogViewer.Avalonia/App.xaml @@ -3,7 +3,8 @@ xmlns:l="clr-namespace:Microsoft.Build.Logging.StructuredLogger;assembly=StructuredLogger" xmlns:common="clr-namespace:Microsoft.Build.Logging.StructuredLogger;assembly=StructuredLogViewer.Core" xmlns:s="clr-namespace:StructuredLogViewer.Avalonia.Controls;assembly=StructuredLogViewer.Avalonia" - x:Class="StructuredLogViewer.Avalonia.App"> + x:Class="StructuredLogViewer.Avalonia.App" + RequestedThemeVariant="Light"> @@ -16,13 +17,12 @@ - + - - + - + @@ -41,15 +41,15 @@ - + - + - + - + diff --git a/src/StructuredLogger/BinaryLogger/BinLogReader.cs b/src/StructuredLogger/BinaryLogger/BinLogReader.cs index 62a77c8f..ec32f260 100644 --- a/src/StructuredLogger/BinaryLogger/BinLogReader.cs +++ b/src/StructuredLogger/BinaryLogger/BinLogReader.cs @@ -74,17 +74,8 @@ public void Replay(Stream stream, Progress progress) throw new NotSupportedException(text); } - // Use a producer-consumer queue so that IO can happen on one thread - // while processing can happen on another thread decoupled. The speed - // up is from 4.65 to 4.15 seconds. - var queue = new BlockingCollection(boundedCapacity: 5000); - var processingTask = System.Threading.Tasks.Task.Run(() => - { - foreach (var args in queue.GetConsumingEnumerable()) - { - Dispatch(args); - } - }); + + var queue = new EventArgsDispatcherQueue(Dispatch); int recordsRead = 0; @@ -126,7 +117,7 @@ public void Replay(Stream stream, Progress progress) } } - processingTask.Wait(); + queue.Wait(); if (fileFormatVersion >= 10) { @@ -313,10 +304,10 @@ public WrapperStream(Stream stream) public override long Length => stream.Length; private long position; - public override long Position + public override long Position { - get => position; - set => throw new NotImplementedException(); + get => position; + set => throw new NotImplementedException(); } public override void Flush() diff --git a/src/StructuredLogger/BinaryLogger/EventArgsDispatcherQueue.cs b/src/StructuredLogger/BinaryLogger/EventArgsDispatcherQueue.cs new file mode 100644 index 00000000..433b3533 --- /dev/null +++ b/src/StructuredLogger/BinaryLogger/EventArgsDispatcherQueue.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using SystemTask = System.Threading.Tasks.Task; + +namespace Microsoft.Build.Logging.StructuredLogger; + +internal class EventArgsDispatcherQueue : EventArgs +{ + private readonly Action dispatch; + private readonly BlockingCollection queue; + private readonly SystemTask processingTask; + + public EventArgsDispatcherQueue(Action dispatch) + { + this.dispatch = dispatch; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) + { + // Use a producer-consumer queue so that IO can happen on one thread + // while processing can happen on another thread decoupled. The speed + // up is from 4.65 to 4.15 seconds. + queue = new BlockingCollection(boundedCapacity: 5000); + processingTask = SystemTask.Run(() => + { + foreach (var args in queue.GetConsumingEnumerable()) + { + dispatch(args); + } + }); + } + } + + public void CompleteAdding() + { + queue?.CompleteAdding(); + } + + public void Add(TEventArgs args) + { + if (queue is not null) + { + queue.Add(args); + } + else + { + dispatch(args); + } + } + + public void Wait() + { + processingTask?.Wait(); + } +} diff --git a/src/StructuredLogger/Serialization/Binary/BuildLogWriter.cs b/src/StructuredLogger/Serialization/Binary/BuildLogWriter.cs index 14415df2..def77807 100644 --- a/src/StructuredLogger/Serialization/Binary/BuildLogWriter.cs +++ b/src/StructuredLogger/Serialization/Binary/BuildLogWriter.cs @@ -1,10 +1,10 @@ using System; +using System.IO; namespace Microsoft.Build.Logging.StructuredLogger { public class BuildLogWriter : IDisposable { - private readonly string filePath; private TreeBinaryWriter writer; public static void Write(Build build, string filePath) @@ -15,12 +15,24 @@ public static void Write(Build build, string filePath) } } + public static void Write(Build build, Stream stream) + { + using (var binaryLogWriter = new BuildLogWriter(stream)) + { + binaryLogWriter.WriteNode(build); + } + } + private BuildLogWriter(string filePath) { - this.filePath = filePath; this.writer = new TreeBinaryWriter(filePath); } + private BuildLogWriter(Stream stream) + { + this.writer = new TreeBinaryWriter(stream); + } + private void WriteNode(BaseNode node) { writer.WriteNode(Serialization.GetNodeName(node)); diff --git a/src/StructuredLogger/Serialization/Binary/TreeBinaryWriter.cs b/src/StructuredLogger/Serialization/Binary/TreeBinaryWriter.cs index 7310b825..ac5fe72c 100644 --- a/src/StructuredLogger/Serialization/Binary/TreeBinaryWriter.cs +++ b/src/StructuredLogger/Serialization/Binary/TreeBinaryWriter.cs @@ -9,10 +9,9 @@ namespace Microsoft.Build.Logging.StructuredLogger { public class TreeBinaryWriter : IDisposable { - private readonly string filePath; private readonly BinaryWriter binaryWriter; private readonly BetterBinaryWriter treeNodesStreamBinaryWriter; - private readonly FileStream fileStream; + private readonly Stream fileStream; private readonly GZipStream gzipStream; /// @@ -27,10 +26,14 @@ public class TreeBinaryWriter : IDisposable private readonly List attributes = new List(10); public TreeBinaryWriter(string filePath) + : this(new FileStream(filePath, FileMode.Create, FileAccess.Write)) + { + } + + public TreeBinaryWriter(Stream stream) { - this.filePath = filePath; this.treeNodesStream = new MemoryStream(); - this.fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); + this.fileStream = stream; WriteVersion(); this.gzipStream = new GZipStream(fileStream, CompressionLevel.Optimal); this.binaryWriter = new BetterBinaryWriter(DestinationStream); diff --git a/src/StructuredLogger/Serialization/Serialization.cs b/src/StructuredLogger/Serialization/Serialization.cs index 4a82aaa2..657ea224 100644 --- a/src/StructuredLogger/Serialization/Serialization.cs +++ b/src/StructuredLogger/Serialization/Serialization.cs @@ -131,6 +131,18 @@ public static void Write(Build build, string filePath) } } + public static void Write(Build build, Stream stream, string fileName = null) + { + if (fileName?.EndsWith(".xml", StringComparison.OrdinalIgnoreCase) == true) + { + XmlLogWriter.WriteToXml(build, stream); + } + else + { + BuildLogWriter.Write(build, stream); + } + } + public static string GetNodeName(BaseNode node) { var folder = node as Folder; diff --git a/src/StructuredLogger/Serialization/XmlLogWriter.cs b/src/StructuredLogger/Serialization/XmlLogWriter.cs index 9b14936f..f020f941 100644 --- a/src/StructuredLogger/Serialization/XmlLogWriter.cs +++ b/src/StructuredLogger/Serialization/XmlLogWriter.cs @@ -14,13 +14,26 @@ public static void WriteToXml(Build build, string logFile) writer.Write(build, logFile); } + public static void WriteToXml(Build build, Stream stream) + { + var writer = new XmlLogWriter(); + writer.Write(build, stream); + } + public void Write(Build build, string logFile) + { + using (FileStream stream = File.Open(logFile, FileMode.Create)) + { + Write(build, stream); + } + } + + public void Write(Build build, Stream stream) { var settings = new XmlWriterSettings() { Indent = true }; - using (FileStream stream = File.Open(logFile, FileMode.Create)) using (xmlWriter = XmlWriter.Create(stream, settings)) { xmlWriter.WriteStartDocument();