From 04ec6dbd70ecd8a96cdb828851f6e703be2b4820 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Thu, 6 Jun 2024 18:52:24 -0700 Subject: [PATCH] Fix parsing of compiler path from binlog (#136) closes #135 --- .../BinaryLogReaderTests.cs | 13 ++ .../BinaryLogUtilTests.cs | 44 +++++- .../ConditionalFacts.cs | 23 +++ .../ProgramTests.cs | 13 +- .../SolutionFixture.cs | 3 + src/Basic.CompilerLog.Util/BinaryLogReader.cs | 34 +++++ src/Basic.CompilerLog.Util/BinaryLogUtil.cs | 133 +++++++++++++++--- .../CompilerAssemblyData.cs | 16 +++ .../CompilerLogReader.cs | 18 ++- .../ICompilerCallReader.cs | 7 + src/Basic.CompilerLog/Program.cs | 5 +- .../Properties/launchSettings.json | 2 +- src/Scratch/Scratch.cs | 61 +++++++- 13 files changed, 335 insertions(+), 37 deletions(-) create mode 100644 src/Basic.CompilerLog.Util/CompilerAssemblyData.cs diff --git a/src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs b/src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs index 15b8161..2143f58 100644 --- a/src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs +++ b/src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs @@ -77,6 +77,19 @@ public void CreateFilePathLogReaderState() state.Dispose(); } + /// + /// Make sure the underlying stream is managed properly so we can read the compiler calls twice. + /// + [Fact] + public void ReadAllCompilerCallsTwice() + { + using var state = new LogReaderState(); + using var reader = BinaryLogReader.Create(Fixture.Console.Value.BinaryLogPath!, BasicAnalyzerKind.OnDisk, state); + Assert.Single(reader.ReadAllCompilerCalls()); + Assert.Single(reader.ReadAllCompilerCalls()); + state.Dispose(); + } + [Theory] [InlineData(BasicAnalyzerKind.InMemory, true)] [InlineData(BasicAnalyzerKind.OnDisk, true)] diff --git a/src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs b/src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs index a416603..74e1e18 100644 --- a/src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs +++ b/src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs @@ -27,7 +27,33 @@ public sealed class BinaryLogUtilTests [InlineData("csc.exe a.cs b.cs", "csc.exe", "a.cs b.cs")] public void ParseCompilerAndArgumentsCsc(string inputArgs, string? expectedCompilerFilePath, string expectedArgs) { - var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(ToArray(inputArgs), "csc.exe", "csc.dll"); + var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(inputArgs, "csc.exe", "csc.dll"); + Assert.Equal(ToArray(expectedArgs), actualArgs); + Assert.Equal(expectedCompilerFilePath, actualCompilerFilePath); + static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); + } + + [WindowsTheory] + [InlineData(@" C:\Program Files\dotnet\dotnet.exe exec ""C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll"" a.cs", @"C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll", "a.cs")] + [InlineData(@"C:\Program Files\dotnet\dotnet.exe exec ""C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll"" a.cs", @"C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll", "a.cs")] + [InlineData(@"""C:\Program Files\dotnet\dotnet.exe"" exec ""C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll"" a.cs", @"C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll", "a.cs")] + [InlineData(@"'C:\Program Files\dotnet\dotnet.exe' exec ""C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll"" a.cs", @"C:\Program Files\dotnet\sdk\8.0.301\Roslyn\bincore\csc.dll", "a.cs")] + [InlineData(@"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe a.cs b.cs", @"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe", "a.cs b.cs")] + [InlineData(@"""C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe"" a.cs b.cs", @"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\Roslyn\csc.exe", "a.cs b.cs")] + public void ParseCompilerAndArgumentsCscWindows(string inputArgs, string? expectedCompilerFilePath, string expectedArgs) + { + var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(inputArgs, "csc.exe", "csc.dll"); + Assert.Equal(ToArray(expectedArgs), actualArgs); + Assert.Equal(expectedCompilerFilePath, actualCompilerFilePath); + static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); + } + + [UnixTheory] + [InlineData(@"/dotnet/dotnet exec /dotnet/sdk/bincore/csc.dll a.cs", "/dotnet/sdk/bincore/csc.dll", "a.cs")] + [InlineData(@"/dotnet/dotnet exec ""/dotnet/sdk/bincore/csc.dll"" a.cs", "/dotnet/sdk/bincore/csc.dll", "a.cs")] + public void ParseCompilerAndArgumentsCscUnix(string inputArgs, string? expectedCompilerFilePath, string expectedArgs) + { + var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(inputArgs, "csc.exe", "csc.dll"); Assert.Equal(ToArray(expectedArgs), actualArgs); Assert.Equal(expectedCompilerFilePath, actualCompilerFilePath); static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); @@ -39,21 +65,29 @@ public void ParseCompilerAndArgumentsCsc(string inputArgs, string? expectedCompi [InlineData("vbc.exe a.cs b.cs", "vbc.exe", "a.cs b.cs")] public void ParseCompilerAndArgumentsVbc(string inputArgs, string? expectedCompilerFilePath, string expectedArgs) { - var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(ToArray(inputArgs), "vbc.exe", "vbc.dll"); + var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(inputArgs, "vbc.exe", "vbc.dll"); Assert.Equal(ToArray(expectedArgs), actualArgs); Assert.Equal(expectedCompilerFilePath, actualCompilerFilePath); static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); } - [Theory] [InlineData("dotnet not what we expect a.cs")] [InlineData("dotnet csc2 what we expect a.cs")] [InlineData("dotnet exec vbc.dll what we expect a.cs")] + [InlineData("empty")] + [InlineData(" ")] public void ParseCompilerAndArgumentsBad(string inputArgs) { - Assert.Throws(() => BinaryLogUtil.ParseTaskForCompilerAndArguments(ToArray(inputArgs), "csc.exe", "csc.dll")); - static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries); + Assert.Throws(() => BinaryLogUtil.ParseTaskForCompilerAndArguments(inputArgs, "csc.exe", "csc.dll")); + } + + [Fact] + public void ParseCompilerAndArgumentsNull() + { + var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseTaskForCompilerAndArguments(null, "csc.exe", "csc.dll"); + Assert.Null(actualCompilerFilePath); + Assert.Empty(actualArgs); } } diff --git a/src/Basic.CompilerLog.UnitTests/ConditionalFacts.cs b/src/Basic.CompilerLog.UnitTests/ConditionalFacts.cs index 084d3f5..1903992 100644 --- a/src/Basic.CompilerLog.UnitTests/ConditionalFacts.cs +++ b/src/Basic.CompilerLog.UnitTests/ConditionalFacts.cs @@ -14,3 +14,26 @@ public WindowsFactAttribute() } } } + +public sealed class WindowsTheoryAttribute : TheoryAttribute +{ + public WindowsTheoryAttribute() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Skip = "This test is only supported on Windows"; + } + } +} + +public sealed class UnixTheoryAttribute : TheoryAttribute +{ + public UnixTheoryAttribute() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Skip = "This test is only supported on Windows"; + } + } +} + diff --git a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs index c1a4776..415c0ab 100644 --- a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs +++ b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs @@ -717,12 +717,23 @@ public void PrintCompilers() var tuple = reader.ReadAllCompilerAssemblies().Single(); Assert.Contains($""" Compilers - {'\t'}File Path: {tuple.CompilerFilePath} + {'\t'}File Path: {tuple.FilePath} {'\t'}Assembly Name: {tuple.AssemblyName} {'\t'}Commit Hash: {tuple.CommitHash} """, output); } + /// + /// Ensure that print can run without the code being present + /// + [Fact] + public void PrintWithoutProject() + { + var (exitCode, output) = RunCompLogEx($"print {Fixture.RemovedBinaryLogPath} -c"); + Assert.Equal(Constants.ExitSuccess, exitCode); + Assert.StartsWith("Projects", output); + } + /// /// Engage the code to find files in the specified directory /// diff --git a/src/Basic.CompilerLog.UnitTests/SolutionFixture.cs b/src/Basic.CompilerLog.UnitTests/SolutionFixture.cs index fc84e9a..58c3bea 100644 --- a/src/Basic.CompilerLog.UnitTests/SolutionFixture.cs +++ b/src/Basic.CompilerLog.UnitTests/SolutionFixture.cs @@ -40,6 +40,9 @@ public sealed class SolutionFixture : FixtureBase, IDisposable internal string ConsoleWithDiagnosticsProjectName => Path.GetFileName(ConsoleWithDiagnosticsProjectPath); + /// + /// The binary log for a project that has been removed from disk + /// internal string RemovedBinaryLogPath { get; } /// diff --git a/src/Basic.CompilerLog.Util/BinaryLogReader.cs b/src/Basic.CompilerLog.Util/BinaryLogReader.cs index f6ecfe2..3e177c3 100644 --- a/src/Basic.CompilerLog.Util/BinaryLogReader.cs +++ b/src/Basic.CompilerLog.Util/BinaryLogReader.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Reflection; using Basic.CompilerLog.Util.Impl; using MessagePack.Formatters; using Microsoft.Build.Logging.StructuredLogger; @@ -82,6 +83,7 @@ public List ReadAllCompilerCalls(Func? predica { predicate ??= static _ => true; + _stream.Position = 0; return BinaryLogUtil.ReadAllCompilerCalls(_stream, predicate, ownerState: this); } @@ -299,6 +301,38 @@ public List ReadAllReferenceData(CompilerCall compilerCall) return ReadAllReferenceDataCore(args.MetadataReferences.Select(x => x.Reference), args.MetadataReferences.Length); } + public List ReadAllCompilerAssemblies() + { + var list = new List<(string CompilerFilePath, AssemblyName AssemblyName)>(); + var map = new Dictionary(PathUtil.Comparer); + foreach (var compilerCall in ReadAllCompilerCalls()) + { + if (compilerCall.CompilerFilePath is string compilerFilePath && + !map.ContainsKey(compilerFilePath)) + { + AssemblyName name; + string? commitHash; + try + { + name = AssemblyName.GetAssemblyName(compilerFilePath); + commitHash = RoslynUtil.ReadCompilerCommitHash(compilerFilePath); + } + catch + { + name = new AssemblyName(Path.GetFileName(compilerFilePath)); + commitHash = null; + } + + map[compilerCall.CompilerFilePath] = (name, commitHash); + } + } + + return map + .OrderBy(x => x.Key, PathUtil.Comparer) + .Select(x => new CompilerAssemblyData(x.Key, x.Value.Item1, x.Value.Item2)) + .ToList(); + } + /// /// Attempt to add all the generated files from generators. When successful the generators /// don't need to be run when re-hydrating the compilation. diff --git a/src/Basic.CompilerLog.Util/BinaryLogUtil.cs b/src/Basic.CompilerLog.Util/BinaryLogUtil.cs index ce09447..6f29d0f 100644 --- a/src/Basic.CompilerLog.Util/BinaryLogUtil.cs +++ b/src/Basic.CompilerLog.Util/BinaryLogUtil.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Web; using Microsoft.Build.Framework; using Microsoft.Build.Logging.StructuredLogger; using Microsoft.CodeAnalysis; @@ -92,10 +93,9 @@ public CompilationTaskData(MSBuildProjectData projectData, int targetId) } var kind = Kind ?? CompilerCallKind.Unknown; - var rawArgs = CommandLineParser.SplitCommandLineIntoArguments(CommandLineArguments, removeHashComments: true); var (compilerFilePath, args) = IsCSharp - ? ParseTaskForCompilerAndArguments(rawArgs, "csc.exe", "csc.dll") - : ParseTaskForCompilerAndArguments(rawArgs, "vbc.exe", "vbc.dll"); + ? ParseTaskForCompilerAndArguments(CommandLineArguments, "csc.exe", "csc.dll") + : ParseTaskForCompilerAndArguments(CommandLineArguments, "vbc.exe", "vbc.dll"); return new CompilerCall( compilerFilePath, @@ -288,37 +288,52 @@ void SetTargetFramework(ref string? targetFramework, IEnumerable? rawProperties) /// The argument list is going to include either `dotnet exec csc.dll` or `csc.exe`. Need /// to skip past that to get to the real command line. /// - internal static (string? CompilerFilePath, string[] Arguments) ParseTaskForCompilerAndArguments(IEnumerable args, string exeName, string dllName) + internal static (string? CompilerFilePath, string[] Arguments) ParseTaskForCompilerAndArguments(string? args, string exeName, string dllName) { - using var e = args.GetEnumerator(); + if (args is null) + { + return (null, []); + } + + var argsStart = 0; + var appFilePath = FindApplication(args.AsSpan(), ref argsStart, out bool isDotNet); + if (appFilePath.IsEmpty) + { + throw InvalidCommandLine(); + } + + var rawArgs = CommandLineParser.SplitCommandLineIntoArguments(args.Substring(argsStart), removeHashComments: true); + using var e = rawArgs.GetEnumerator(); // The path to the executable is not escaped like the other command line arguments. Need // to skip until we see an exec or a path with the exe as the file name. string? compilerFilePath = null; - var found = false; - while (e.MoveNext()) + if (isDotNet) { - if (PathUtil.Comparer.Equals(e.Current, "exec")) + // The path to the executable is not escaped like the other command line arguments. Need + // to skip until we see an exec or a path with the exe as the file name. + while (e.MoveNext()) { - if (e.MoveNext() && PathUtil.Comparer.Equals(Path.GetFileName(e.Current), dllName)) + if (PathUtil.Comparer.Equals(e.Current, "exec")) { - compilerFilePath = e.Current; - found = true; + if (e.MoveNext() && PathUtil.Comparer.Equals(Path.GetFileName(e.Current), dllName)) + { + compilerFilePath = e.Current; + } + + break; } - break; } - else if (e.Current.EndsWith(exeName, PathUtil.Comparison)) + + if (compilerFilePath is null) { - compilerFilePath = e.Current; - found = true; - break; + throw InvalidCommandLine(); } } - - if (!found) + else { - var cmdLine = string.Join(" ", args); - throw new InvalidOperationException($"Could not parse command line arguments: {cmdLine}"); + // Direct call to the compiler so we already have the compiler file path in hand + compilerFilePath = appFilePath.Trim('"').ToString(); } var list = new List(); @@ -328,6 +343,84 @@ internal static (string? CompilerFilePath, string[] Arguments) ParseTaskForCompi } return (compilerFilePath, list.ToArray()); + + // This search is tricky because there is no attempt by MSBuild to properly quote the + ReadOnlySpan FindApplication(ReadOnlySpan args, ref int index, out bool isDotNet) + { + isDotNet = false; + while (index < args.Length && char.IsWhiteSpace(args[index])) + { + index++; + } + + if (index >= args.Length) + { + return Span.Empty; + } + + if (args[index] is '"' or '\'') + { + // Quote based parsing, just move to the next quote and return. + var start = index + 1; + var quote = args[index]; + do + { + index++; + } + while (index < args.Length && args[index] != quote); + + index++; // move past the quote + var span = args.Slice(start, index - start - 1); + isDotNet = CheckDotNet(span); + return span; + } + else + { + // Move forward until we see a signal that we've reached the compiler + // executable. + // + // Note: Specifically don't need to handle the case of the application ending at the + // exact end of the string. There is always at least one argument to the compiler. + while (index < args.Length) + { + if (char.IsWhiteSpace(args[index])) + { + var span = args.Slice(0, index); + if (span.EndsWith(exeName.AsSpan())) + { + isDotNet = false; + return span; + } + + if (CheckDotNet(span)) + { + isDotNet = true; + return span; + } + + if (span.EndsWith(" exec".AsSpan())) + { + // This can happen when the dotnet host is not called dotnet. Need to back + // up to the path before that. + index -= 5; + span = args.Slice(0, index); + isDotNet = true; + return span; + } + } + + index++; + } + } + + return Span.Empty; + + bool CheckDotNet(ReadOnlySpan span) => + span.EndsWith("dotnet".AsSpan()) || + span.EndsWith("dotnet.exe".AsSpan()); + } + + Exception InvalidCommandLine() => new InvalidOperationException($"Could not parse command line arguments: {args}"); } /// diff --git a/src/Basic.CompilerLog.Util/CompilerAssemblyData.cs b/src/Basic.CompilerLog.Util/CompilerAssemblyData.cs new file mode 100644 index 0000000..2f8ade2 --- /dev/null +++ b/src/Basic.CompilerLog.Util/CompilerAssemblyData.cs @@ -0,0 +1,16 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace Basic.CompilerLog.Util; + +public sealed class CompilerAssemblyData(string filePath, AssemblyName assemblyName, string? commitHash) +{ + public string FilePath { get; } = filePath; + public AssemblyName AssemblyName { get; } = assemblyName; + public string? CommitHash { get; } = commitHash; + + [ExcludeFromCodeCoverage] + public override string ToString() => $"{FilePath} {CommitHash}"; +} + diff --git a/src/Basic.CompilerLog.Util/CompilerLogReader.cs b/src/Basic.CompilerLog.Util/CompilerLogReader.cs index c35da60..8f0a0a7 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogReader.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogReader.cs @@ -110,10 +110,20 @@ Metadata ReadMetadata() return metadata; } } - catch (InvalidDataException) + catch (Exception ex) { + if (!leaveOpen) + { + stream.Dispose(); + } + // Happens when this is not a valid zip file - throw GetInvalidCompilerLogFileException(); + if (ex is not CompilerLogException) + { + throw GetInvalidCompilerLogFileException(); + } + + throw; } static Exception GetInvalidCompilerLogFileException() => new CompilerLogException("Provided stream is not a compiler log file"); @@ -234,7 +244,7 @@ public List ReadAllCompilerCalls(Func? predica return list; } - public List<(string CompilerFilePath, AssemblyName AssemblyName, string? CommitHash)> ReadAllCompilerAssemblies() + public List ReadAllCompilerAssemblies() { var list = new List<(string CompilerFilePath, AssemblyName AssemblyName)>(); var map = new Dictionary(PathUtil.Comparer); @@ -252,7 +262,7 @@ pack.CompilerAssemblyName is not null && return map .OrderBy(x => x.Key, PathUtil.Comparer) - .Select(x => (x.Key, x.Value.Item1, x.Value.Item2)) + .Select(x => new CompilerAssemblyData(x.Key, x.Value.Item1, x.Value.Item2)) .ToList(); } diff --git a/src/Basic.CompilerLog.Util/ICompilerCallReader.cs b/src/Basic.CompilerLog.Util/ICompilerCallReader.cs index d2cca01..58d911d 100644 --- a/src/Basic.CompilerLog.Util/ICompilerCallReader.cs +++ b/src/Basic.CompilerLog.Util/ICompilerCallReader.cs @@ -1,3 +1,5 @@ +using System.Reflection; + namespace Basic.CompilerLog.Util; public interface ICompilerCallReader : IDisposable @@ -18,4 +20,9 @@ public interface ICompilerCallReader : IDisposable /// Read all of the for analyzers passed to the compilation /// public List ReadAllAnalyzerData(CompilerCall compilerCall); + + /// + /// Read all of the compilers used in this build. + /// + public List ReadAllCompilerAssemblies(); } \ No newline at end of file diff --git a/src/Basic.CompilerLog/Program.cs b/src/Basic.CompilerLog/Program.cs index e783ebe..b6e1b35 100644 --- a/src/Basic.CompilerLog/Program.cs +++ b/src/Basic.CompilerLog/Program.cs @@ -165,8 +165,7 @@ int RunPrint(IEnumerable args) return ExitSuccess; } - using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerCallReader(extra, BasicAnalyzerKind.None); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); WriteLine("Projects"); @@ -180,7 +179,7 @@ int RunPrint(IEnumerable args) WriteLine("Compilers"); foreach (var tuple in reader.ReadAllCompilerAssemblies()) { - WriteLine($"\tFile Path: {tuple.CompilerFilePath}"); + WriteLine($"\tFile Path: {tuple.FilePath}"); WriteLine($"\tAssembly Name: {tuple.AssemblyName}"); WriteLine($"\tCommit Hash: {tuple.CommitHash}"); } diff --git a/src/Basic.CompilerLog/Properties/launchSettings.json b/src/Basic.CompilerLog/Properties/launchSettings.json index 9523064..64d94b8 100644 --- a/src/Basic.CompilerLog/Properties/launchSettings.json +++ b/src/Basic.CompilerLog/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "CompilerLogger": { "commandName": "Project", - "commandLineArgs": "export -o c:\\users\\jaredpar\\temp\\export \"C:\\Users\\jaredpar\\Downloads\\msbuild.complog\"", + "commandLineArgs": "print -c \"C:\\Users\\jaredpar\\Downloads\\out_no_analyzers.binlog\"", "workingDirectory": "C:\\users\\jaredpar\\temp" } } diff --git a/src/Scratch/Scratch.cs b/src/Scratch/Scratch.cs index 05dcb08..1a56316 100644 --- a/src/Scratch/Scratch.cs +++ b/src/Scratch/Scratch.cs @@ -8,6 +8,9 @@ using Basic.CompilerLog; using Basic.CompilerLog.Util; using BenchmarkDotNet.Environments; +using Microsoft.Build.Framework; +using Microsoft.Build.Logging.StructuredLogger; + //using Microsoft.Build.Logging.StructuredLogger; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; @@ -42,7 +45,8 @@ // Profile(); -ReadAttribute(); +DarkArtOfBuild(); +// ReadAttribute(); // ExportScratch(); // await WorkspaceScratch(); // RoslynScratch(); @@ -93,6 +97,56 @@ } */ +void DarkArtOfBuild() +{ + var filePath = @"c:\users\jaredpar\temp\console\msbuild.binlog"; + const string targetProjectFile = @"C:\Users\jaredpar\temp\console\console.csproj"; + using var stream = File.OpenRead(filePath); + var records = BinaryLog.ReadRecords(stream); + foreach (var record in records) + { + if (record.Args is not { BuildEventContext: { } context }) + { + continue; + } + + var suffix = $"eval: {context.EvaluationId}, context: {context.ProjectContextId}, instance: {context.ProjectInstanceId}"; + + switch (record.Args) + { + case ProjectStartedEventArgs { ProjectFile: targetProjectFile } e: + { + Console.WriteLine($"ProjectStarted: {suffix}"); + break; + } + case ProjectFinishedEventArgs {ProjectFile: targetProjectFile } e: + { + Console.WriteLine($"ProjectFinished: {suffix}"); + break; + } + case ProjectEvaluationStartedEventArgs { ProjectFile: targetProjectFile } e: + { + Console.WriteLine($"EvaluationStarted: {suffix}"); + break; + } + case ProjectEvaluationFinishedEventArgs { ProjectFile: targetProjectFile } e: + { + Console.WriteLine($"EvaluationFinished: {suffix}"); + break; + } + case TaskStartedEventArgs { ProjectFile: targetProjectFile } e: + { + if ((e.TaskName == "Csc" || e.TaskName == "Vbc")) + { + Console.WriteLine($"CompileStarted: {suffix}"); + } + break; + } + } + } + +} + void ReadAttribute() { var assemblyPath = @"c:\Program Files\dotnet\sdk\8.0.204\Roslyn\bincore\csc.dll"; @@ -145,7 +199,7 @@ void PrintCompilers(string filePath) using var reader = CompilerLogReader.Create(filePath); foreach (var info in reader.ReadAllCompilerAssemblies()) { - Console.WriteLine(info.CompilerFilePath); + Console.WriteLine(info.FilePath); Console.WriteLine(info.AssemblyName); } } @@ -183,7 +237,7 @@ static void PrintGeneratedFiles() } } -static async Task WorkspaceScratch() +/*static async Task WorkspaceScratch() { var filePath = @"/mnt/c/Users/jaredpar/temp/console/msbuild.complog"; using var reader = SolutionReader.Create(filePath, BasicAnalyzerKind.None); @@ -199,6 +253,7 @@ static async Task WorkspaceScratch() } } } +*/ static void ExportScratch() {