From 2506bc68fd86098ecda8181e762cf6693cbee4be Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Tue, 20 Feb 2024 09:08:14 -0800 Subject: [PATCH] Change command line format (#113) * Initial options refactoring code done * Fixed it up * More * Fix tests * Clean up API * More --- .vscode/settings.json | 1 + TODO.txt | 8 ++ .../BasicAnalyzerHostOptionsTests.cs | 31 ------ .../BasicAnalyzerHostTests.cs | 8 +- .../CompilationDataTests.cs | 10 +- .../CompilerLogReaderExTests.cs | 6 +- .../CompilerLogReaderTests.cs | 37 ++++--- .../CompilerLogStateTests.cs | 14 +++ .../FilterOptionSetTests.cs | 18 +++ .../ProgramTests.cs | 104 +++++++++++++----- .../SolutionReaderTests.cs | 15 ++- src/Basic.CompilerLog.UnitTests/TestBase.cs | 4 +- .../UsingAllCompilerLogTests.cs | 10 +- .../BasicAnalyzerHost.cs | 104 ++++-------------- .../CompilerLogReader.cs | 51 +++++---- .../CompilerLogReaderOptions.cs | 43 ++++++++ .../CompilerLogState.cs | 35 +++++- .../Impl/BasicAnalyzerHostInMemory.cs | 12 +- .../Impl/BasicAnalyzerHostNone.cs | 4 +- .../Impl/BasicAnalyzerHostOnDisk.cs | 12 +- src/Basic.CompilerLog.Util/SolutionReader.cs | 10 +- src/Basic.CompilerLog/Constants.cs | 4 + src/Basic.CompilerLog/FilterOptionSet.cs | 40 ++++++- src/Basic.CompilerLog/Program.cs | 22 ++-- src/Scratch/CompilerBenchmarks.cs | 2 +- src/Scratch/Scratch.cs | 4 +- 26 files changed, 371 insertions(+), 238 deletions(-) create mode 100644 TODO.txt delete mode 100644 src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostOptionsTests.cs create mode 100644 src/Basic.CompilerLog.UnitTests/FilterOptionSetTests.cs create mode 100644 src/Basic.CompilerLog.Util/CompilerLogReaderOptions.cs diff --git a/.vscode/settings.json b/.vscode/settings.json index ebfe157..2bf467f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,6 +14,7 @@ "jaredpar", "msbuild", "Mvid", + "NETCOREAPP", "Relogger", "ruleset", "Xunit" diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..912e020 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,8 @@ +unify the analyzer options on command line + - Move the CompilerLogState to FilterOptionSet, make that disposable +switch default to run analyzers +simplify the options, kind, etc ... for loading analyzers + - Move caching to the reader as it conttrols that + - Move ALC to the reader as it controls that + - Host options are just about what host is created, now how the host is managed +better test hooks to see what program actually ran \ No newline at end of file diff --git a/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostOptionsTests.cs b/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostOptionsTests.cs deleted file mode 100644 index ac2805f..0000000 --- a/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostOptionsTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Basic.CompilerLog.Util; -using Basic.CompilerLog.Util.Impl; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; - -#if NETCOREAPP -using System.Runtime.Loader; -#endif - -namespace Basic.CompilerLog.UnitTests; - -public sealed class BasicAnalyzerHostOptionsTests -{ -#if NETCOREAPP - [Fact] - public void CustomAssemblyLoadContext() - { - var alc = new AssemblyLoadContext("Custom", isCollectible: true); - var options = new BasicAnalyzerHostOptions(alc, BasicAnalyzerKind.Default, cacheable: true); - Assert.Same(alc, options.CompilerLoadContext); - alc.Unload(); - } -#endif -} diff --git a/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs b/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs index 96573d6..5660c97 100644 --- a/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs +++ b/src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs @@ -21,7 +21,6 @@ public sealed class BasicAnalyzerHostTests [Fact] public void Supported() { - Assert.True(BasicAnalyzerHost.IsSupported(BasicAnalyzerKind.Default)); Assert.True(BasicAnalyzerHost.IsSupported(BasicAnalyzerKind.OnDisk)); Assert.True(BasicAnalyzerHost.IsSupported(BasicAnalyzerKind.None)); #if NETCOREAPP @@ -31,13 +30,13 @@ public void Supported() #endif // To make sure this test is updated every time a new value is added - Assert.Equal(4, Enum.GetValues(typeof(BasicAnalyzerKind)).Length); + Assert.Equal(3, Enum.GetValues(typeof(BasicAnalyzerKind)).Length); } [Fact] public void NoneDispose() { - var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty, BasicAnalyzerHostOptions.None); + var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty); host.Dispose(); Assert.Throws(() => { _ = host.AnalyzerReferences; }); } @@ -45,10 +44,9 @@ public void NoneDispose() [Fact] public void NoneProps() { - var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty, BasicAnalyzerHostOptions.None); + var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty); host.Dispose(); Assert.Equal(BasicAnalyzerKind.None, host.Kind); - Assert.Same(BasicAnalyzerHostOptions.None, host.Options); Assert.Empty(host.GeneratedSourceTexts); } } diff --git a/src/Basic.CompilerLog.UnitTests/CompilationDataTests.cs b/src/Basic.CompilerLog.UnitTests/CompilationDataTests.cs index c085727..b8abae0 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilationDataTests.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilationDataTests.cs @@ -79,7 +79,7 @@ public void GetAnalyzersNormal() [Fact] public void GetAnalyzersNoHosting() { - using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, CompilerLogReaderOptions.None); var data = reader.ReadCompilationData(0); Assert.Empty(data.GetAnalyzers()); } @@ -87,7 +87,7 @@ public void GetAnalyzersNoHosting() [Fact] public void GetDiagnostics() { - using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, BasicAnalyzerHostOptions.Default); + using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, CompilerLogReaderOptions.Default); var data = reader.ReadCompilationData(0); Assert.NotEmpty(data.GetDiagnostics()); } @@ -95,7 +95,7 @@ public void GetDiagnostics() [Fact] public async Task GetAllDiagnostics() { - using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, BasicAnalyzerHostOptions.Default); + using var reader = CompilerLogReader.Create(Fixture.ClassLibComplogPath.Value, CompilerLogReaderOptions.Default); var data = reader.ReadCompilationData(0); Assert.NotEmpty(await data.GetAllDiagnosticsAsync()); } @@ -103,13 +103,13 @@ public async Task GetAllDiagnostics() [Fact] public void GetCompilationAfterGeneratorsDiagnostics() { - var options = new BasicAnalyzerHostOptions(BasicAnalyzerKind.InMemory, cacheable: true); + var options = new CompilerLogReaderOptions(BasicAnalyzerKind.InMemory, cacheAnalyzers: true); using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, options); var rawData = reader.ReadRawCompilationData(0).Item2; var analyzers = rawData.Analyzers .Where(x => x.FileName != "Microsoft.CodeAnalysis.NetAnalyzers.dll") .ToList(); - var host = new BasicAnalyzerHostInMemory(reader, analyzers, options); + var host = new BasicAnalyzerHostInMemory(reader, analyzers); var data = (CSharpCompilationData)reader.ReadCompilationData(0); data = new CSharpCompilationData( data.CompilerCall, diff --git a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderExTests.cs b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderExTests.cs index 34db9a7..442685c 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderExTests.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderExTests.cs @@ -39,7 +39,7 @@ public CompilerLogReaderExTests(ITestOutputHelper testOutputHelper, SolutionFixt /// /// Convert the console binary log and return a reader over it /// - private CompilerLogReader ConvertConsole(Func func, BasicAnalyzerHostOptions? options = null) + private CompilerLogReader ConvertConsole(Func func, CompilerLogReaderOptions? options = null) { var diagnostics = new List(); @@ -57,10 +57,10 @@ private CompilerLogReader ConvertConsole(Func func, builder.Add(compilerCall); builder.Close(); stream.Position = 0; - return CompilerLogReader.Create(stream, leaveOpen: false, options, State); + return CompilerLogReader.Create(stream, options, State, leaveOpen: false); } - private CompilerLogReader ConvertConsoleArgs(Func func, BasicAnalyzerHostOptions? options = null) => + private CompilerLogReader ConvertConsoleArgs(Func func, CompilerLogReaderOptions? options = null) => ConvertConsole(x => { var args = func(x.GetArguments()); diff --git a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs index 5cfdbd6..328c050 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs @@ -62,7 +62,18 @@ public void CreateInvalidZipFile() using var stream = new MemoryStream(); stream.Write([1, 2, 3, 4, 5], 0, 0); stream.Position = 0; - Assert.Throws(() => CompilerLogReader.Create(stream, leaveOpen: true, BasicAnalyzerHostOptions.None)); + Assert.Throws(() => CompilerLogReader.Create(stream, CompilerLogReaderOptions.None, leaveOpen: true)); + } + + [Fact] + public void CreateRespectLeaveOpen() + { + using var stream = new FileStream(Fixture.ConsoleComplexComplogPath.Value, FileMode.Open, FileAccess.Read, FileShare.Read); + var reader = CompilerLogReader.Create(stream, leaveOpen: true); + reader.Dispose(); + + // Throws if the underlying stream is disposed + stream.Seek(0, SeekOrigin.Begin); } [Fact] @@ -114,7 +125,7 @@ public void KeyFileCustomState() using var state = new CompilerLogState(tempDir.DirectoryPath); var keyBytes = ResourceLoader.GetResourceBlob("Key.snk"); - using var reader = CompilerLogReader.Create(Fixture.ConsoleComplexComplogPath.Value, state: state); + using var reader = CompilerLogReader.Create(Fixture.ConsoleComplexComplogPath.Value, options: null, state: state); var data = reader.ReadCompilationData(0); Assert.NotNull(data.CompilationOptions.CryptoKeyFile); @@ -158,7 +169,7 @@ public void AnalyzerLoadOptions() } any = true; - var options = new BasicAnalyzerHostOptions(kind); + var options = new CompilerLogReaderOptions(kind); using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, options: options); var data = reader.ReadCompilationData(0); var compilation = data.GetCompilationAfterGenerators(out var diagnostics); @@ -189,7 +200,7 @@ public void AnalyzerLoadCaching(BasicAnalyzerKind kind) return; } - var options = new BasicAnalyzerHostOptions(kind, cacheable: true); + var options = new CompilerLogReaderOptions(kind, cacheAnalyzers: true); using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, options: options); var data = reader.ReadRawCompilationData(0).Item2; @@ -211,7 +222,7 @@ public void AnalyzerLoadDispose(BasicAnalyzerKind kind) return; } - var options = new BasicAnalyzerHostOptions(kind, cacheable: true); + var options = new CompilerLogReaderOptions(kind, cacheAnalyzers: true); using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, options: options); var data = reader.ReadCompilationData(0); Assert.False(data.BasicAnalyzerHost.IsDisposed); @@ -227,13 +238,13 @@ public void AnalyzerLoadDispose(BasicAnalyzerKind kind) [Fact] public void AnalyzerDiagnostics() { - var options = new BasicAnalyzerHostOptions(BasicAnalyzerKind.InMemory, cacheable: true); + var options = new CompilerLogReaderOptions(BasicAnalyzerKind.InMemory, cacheAnalyzers: true); using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, options); var data = reader.ReadRawCompilationData(0).Item2; var analyzers = data.Analyzers .Where(x => x.FileName != "Microsoft.CodeAnalysis.NetAnalyzers.dll") .ToList(); - var host = new BasicAnalyzerHostInMemory(reader, analyzers, options); + var host = new BasicAnalyzerHostInMemory(reader, analyzers); foreach (var analyzer in host.AnalyzerReferences) { analyzer.GetAnalyzersForAllLanguages(); @@ -264,7 +275,7 @@ public void ProjectMultiTarget() [Fact] public void NoneHostGeneratedFilesInRaw() { - using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, CompilerLogReaderOptions.None); var (_, data) = reader.ReadRawCompilationData(0); Assert.Equal(1, data.Contents.Count(x => x.Kind == RawContentKind.GeneratedText)); } @@ -272,7 +283,7 @@ public void NoneHostGeneratedFilesInRaw() [Fact] public void NoneHostGeneratedFilesShouldBeLast() { - using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, CompilerLogReaderOptions.None); var data = reader.ReadCompilationData(0); var tree = data.GetCompilationAfterGenerators().SyntaxTrees.Last(); var decls = tree.GetRoot().DescendantNodes().OfType().ToList(); @@ -284,7 +295,7 @@ public void NoneHostGeneratedFilesShouldBeLast() [Fact] public void NoneHostHasNoGenerators() { - using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Fixture.ConsoleComplogPath.Value, CompilerLogReaderOptions.None); var data = reader.ReadCompilationData(0); var compilation1 = data.Compilation; var compilation2 = data.GetCompilationAfterGenerators(); @@ -295,7 +306,7 @@ public void NoneHostHasNoGenerators() [Fact] public void NoneHostAddsNoGeneratorIfNoGeneratedSource() { - using var reader = CompilerLogReader.Create(Fixture.ConsoleNoGeneratorComplogPath.Value, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Fixture.ConsoleNoGeneratorComplogPath.Value, CompilerLogReaderOptions.None); var data = reader.ReadCompilationData(0); var compilation1 = data.Compilation; var compilation2 = data.GetCompilationAfterGenerators(); @@ -326,7 +337,7 @@ public void NoneHostNativePdb() File.WriteAllText(Path.Combine(RootDirectory, "example.csproj"), projectFileContent, DefaultEncoding); RunDotNet("build -bl -nr:false"); - using var reader = CompilerLogReader.Create(Path.Combine(RootDirectory, "msbuild.binlog"), BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(Path.Combine(RootDirectory, "msbuild.binlog"), CompilerLogReaderOptions.None); var rawData = reader.ReadRawCompilationData(0).Item2; Assert.False(rawData.ReadGeneratedFiles); var data = reader.ReadCompilationData(0); @@ -407,7 +418,7 @@ public void MetadataCompat(string resourceName) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { using var stream = ResourceLoader.GetResourceStream(resourceName); - Assert.Throws(() => CompilerLogReader.Create(stream, leaveOpen: true, BasicAnalyzerHostOptions.None)); + Assert.Throws(() => CompilerLogReader.Create(stream, CompilerLogReaderOptions.None, leaveOpen: true)); } } diff --git a/src/Basic.CompilerLog.UnitTests/CompilerLogStateTests.cs b/src/Basic.CompilerLog.UnitTests/CompilerLogStateTests.cs index 1278b23..1c9550a 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilerLogStateTests.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilerLogStateTests.cs @@ -3,6 +3,10 @@ using Xunit; using Xunit.Abstractions; +#if NETCOREAPP +using System.Runtime.Loader; +#endif + namespace Basic.CompilerLog.UnitTests; public class CompilerLogStateTests : TestBase @@ -34,4 +38,14 @@ public void DisposeDirectoryLocked() fileStream.Dispose(); } +#if NETCOREAPP + [Fact] + public void CustomAssemblyLoadContext() + { + var alc = new AssemblyLoadContext("Custom", isCollectible: true); + var options = new CompilerLogState(alc); + Assert.Same(alc, options.CompilerLoadContext); + alc.Unload(); + } +#endif } \ No newline at end of file diff --git a/src/Basic.CompilerLog.UnitTests/FilterOptionSetTests.cs b/src/Basic.CompilerLog.UnitTests/FilterOptionSetTests.cs new file mode 100644 index 0000000..2655d54 --- /dev/null +++ b/src/Basic.CompilerLog.UnitTests/FilterOptionSetTests.cs @@ -0,0 +1,18 @@ +#if NETCOREAPP +using Xunit; + +namespace Basic.CompilerLog.UnitTests; + +public sealed class FilterOptionSetTest +{ + [Fact] + public void CheckForAnalyzers() + { + var options = new FilterOptionSet(analyzers: false); + Assert.Throws(() => options.IncludeAnalyzers); + + options = new FilterOptionSet(analyzers: true); + Assert.True(options.IncludeAnalyzers); + } +} +#endif \ No newline at end of file diff --git a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs index 8c76607..ebb43aa 100644 --- a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs +++ b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs @@ -20,6 +20,8 @@ namespace Basic.CompilerLog.UnitTests; [Collection(SolutionFixtureCollection.Name)] public sealed class ProgramTests : TestBase { + private Action? _assertCompilerLogReader; + public SolutionFixture Fixture { get; } public ProgramTests(ITestOutputHelper testOutputHelper, SolutionFixture fixture) @@ -28,24 +30,59 @@ public ProgramTests(ITestOutputHelper testOutputHelper, SolutionFixture fixture) Fixture = fixture; } + public override void Dispose() + { + base.Dispose(); + Assert.Null(_assertCompilerLogReader); + } + public int RunCompLog(string args, string? currentDirectory = null) { var (exitCode, _) = RunCompLogEx(args, currentDirectory); return exitCode; } + private void OnCompilerLogReader(CompilerLogReader reader) + { + if (_assertCompilerLogReader is { }) + { + try + { + _assertCompilerLogReader(reader); + } + finally + { + _assertCompilerLogReader = null; + } + } + } + + private void AssertCompilerLogReader(Action action) + { + _assertCompilerLogReader = action; + } + public (int ExitCode, string Output) RunCompLogEx(string args, string? currentDirectory = null) { - var writer = new System.IO.StringWriter(); - currentDirectory ??= RootDirectory; - Constants.CurrentDirectory = currentDirectory; - Constants.Out = writer; - var assembly = typeof(FilterOptionSet).Assembly; - var program = assembly.GetType("Program", throwOnError: true); - var main = program!.GetMethod("
$", BindingFlags.Static | BindingFlags.NonPublic); - Assert.NotNull(main); - var ret = main!.Invoke(null, new[] { args.Split(' ', StringSplitOptions.RemoveEmptyEntries) }); - return ((int)ret!, writer.ToString()); + try + { + var writer = new System.IO.StringWriter(); + currentDirectory ??= RootDirectory; + Constants.CurrentDirectory = currentDirectory; + Constants.Out = writer; + Constants.OnCompilerLogReader = OnCompilerLogReader; + var assembly = typeof(FilterOptionSet).Assembly; + var program = assembly.GetType("Program", throwOnError: true); + var main = program!.GetMethod("
$", BindingFlags.Static | BindingFlags.NonPublic); + Assert.NotNull(main); + var ret = main!.Invoke(null, new[] { args.Split(' ', StringSplitOptions.RemoveEmptyEntries) }); + return ((int)ret!, writer.ToString()); + } + finally + { + Constants.Out = Console.Out; + Constants.OnCompilerLogReader = _ => { }; + } } private void RunWithBoth(Action action) @@ -117,7 +154,7 @@ public void CreateProjectFile() RunDotNet("new console --name console -o ."); Assert.Equal(Constants.ExitSuccess, RunCompLog($"create console.csproj -o msbuild.complog")); var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); - using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(complogPath, CompilerLogReaderOptions.None); Assert.Single(reader.ReadAllCompilerCalls()); } @@ -131,7 +168,7 @@ public void CreateNoopBuild() RunDotNet("build"); Assert.Equal(Constants.ExitFailure, RunCompLog($"create console.csproj -o msbuild.complog -- -t:Build")); var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); - using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(complogPath, CompilerLogReaderOptions.None); Assert.Empty(reader.ReadAllCompilerCalls()); } @@ -144,7 +181,7 @@ void RunCore(string filePath) { Assert.Equal(Constants.ExitSuccess, RunCompLog($"create {filePath} -o msbuild.complog")); var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); - using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(complogPath, CompilerLogReaderOptions.None); Assert.NotEmpty(reader.ReadAllCompilerCalls()); } } @@ -339,20 +376,30 @@ public void ResponseLinuxComplog() } [Theory] - [InlineData("")] - [InlineData("--exclude-analyzers")] - public void ExportCompilerLog(string arg) + [InlineData("", null)] + [InlineData("-a none", BasicAnalyzerKind.None)] + [InlineData("-a ondisk", BasicAnalyzerKind.OnDisk)] + public void ExportCompilerLog(string arg, BasicAnalyzerKind? expectedKind) { + expectedKind ??= BasicAnalyzerHost.DefaultKind; RunWithBoth(logPath => { using var exportDir = new TempDir(); + AssertCompilerLogReader(reader => Assert.Equal(expectedKind.Value, reader.BasicAnalyzerKind)); Assert.Equal(Constants.ExitSuccess, RunCompLog($"export -o {exportDir.DirectoryPath} {arg} {logPath} ", RootDirectory)); // Now run the generated build.cmd and see if it succeeds; var exportPath = Path.Combine(exportDir.DirectoryPath, "console", "export"); var buildResult = RunBuildCmd(exportPath); Assert.True(buildResult.Succeeded); + + // Check that the RSP file matches the analyzer intent + var rspPath = Path.Combine(exportPath, "build.rsp"); + var anyAnalyzers = File.ReadAllLines(rspPath) + .Any(x => x.StartsWith("/analyzer:", StringComparison.Ordinal)); + var expectAnalyzers = expectedKind.Value != BasicAnalyzerKind.None; + Assert.Equal(expectAnalyzers, anyAnalyzers); }); } @@ -392,21 +439,28 @@ public void HelpVerbose() } [Theory] - [InlineData("replay", "")] - [InlineData("replay", "-none")] - [InlineData("replay", "-analyzers")] - [InlineData("replay", "-severity Error")] - [InlineData("emit", "-none")] - [InlineData("diagnostics", "-none")] - public void ReplayWithArgs(string command, string arg) - { + [InlineData("replay", "", null)] + [InlineData("replay", "--none", BasicAnalyzerKind.None)] + [InlineData("replay", "--analyzers inmemory", BasicAnalyzerKind.InMemory)] + [InlineData("replay", "--analyzers ondisk", BasicAnalyzerKind.OnDisk)] + [InlineData("replay", "--analyzers none", BasicAnalyzerKind.None)] + [InlineData("replay", "--severity Error", null)] + [InlineData("emit", "--none", BasicAnalyzerKind.None)] + [InlineData("diagnostics", "--none", BasicAnalyzerKind.None)] + public void ReplayWithArgs(string command, string arg, BasicAnalyzerKind? kind) + { + kind ??= BasicAnalyzerHost.DefaultKind; using var emitDir = new TempDir(); + AssertCompilerLogReader(reader => + { + Assert.Equal(kind.Value, reader.Options.BasicAnalyzerKind); + }); Assert.Equal(Constants.ExitSuccess, RunCompLog($"{command} {arg} {Fixture.SolutionBinaryLogPath}")); } [Theory] [InlineData("")] - [InlineData("-none")] + [InlineData("--none")] public void ReplayConsoleWithEmit(string arg) { using var emitDir = new TempDir(); diff --git a/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs b/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs index b95e385..dc23946 100644 --- a/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs +++ b/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs @@ -25,7 +25,7 @@ public SolutionReaderTests(ITestOutputHelper testOutputHelper, CompilerLogFixtur [Fact] public async Task DocumentsGeneratedDefaultHost() { - var host = new BasicAnalyzerHostOptions(BasicAnalyzerKind.Default); + var host = CompilerLogReaderOptions.Default; using var reader = SolutionReader.Create(Fixture.ConsoleComplogPath.Value, host); var workspace = new AdhocWorkspace(); var solution = workspace.AddSolution(reader.ReadSolutionInfo()); @@ -41,7 +41,7 @@ public async Task DocumentsGeneratedDefaultHost() [Fact] public async Task DocumentsGeneratedNoneHost() { - var host = new BasicAnalyzerHostOptions(BasicAnalyzerKind.None); + var host = CompilerLogReaderOptions.None; using var reader = SolutionReader.Create(Fixture.ConsoleComplogPath.Value, host); var workspace = new AdhocWorkspace(); var solution = workspace.AddSolution(reader.ReadSolutionInfo()); @@ -53,4 +53,15 @@ public async Task DocumentsGeneratedNoneHost() Assert.Equal("RegexGenerator.g.cs", docs.Last().Name); Assert.Empty(generatedDocs); } + + [Fact] + public void CreateRespectLeaveOpen() + { + using var stream = new FileStream(Fixture.ConsoleComplexComplogPath.Value, FileMode.Open, FileAccess.Read, FileShare.Read); + var reader = SolutionReader.Create(stream, leaveOpen: true); + reader.Dispose(); + + // Throws if the underlying stream is disposed + stream.Seek(0, SeekOrigin.Begin); + } } diff --git a/src/Basic.CompilerLog.UnitTests/TestBase.cs b/src/Basic.CompilerLog.UnitTests/TestBase.cs index 2dd70a8..020d1c9 100644 --- a/src/Basic.CompilerLog.UnitTests/TestBase.cs +++ b/src/Basic.CompilerLog.UnitTests/TestBase.cs @@ -28,7 +28,7 @@ protected TestBase(ITestOutputHelper testOutputHelper, string name) State = new CompilerLogState(Root.NewDirectory("state")); } - public void Dispose() + public virtual void Dispose() { TestOutputHelper.WriteLine("Deleting temp directory"); Root.Dispose(); @@ -37,7 +37,7 @@ public void Dispose() public CompilationData GetCompilationData( string complogFilePath, Func? predicate = null, - BasicAnalyzerHostOptions? options = null) + CompilerLogReaderOptions? options = null) { using var reader = CompilerLogReader.Create(complogFilePath, options, State); return reader.ReadAllCompilationData(predicate).Single(); diff --git a/src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs b/src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs index a95b0b2..0667822 100644 --- a/src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs +++ b/src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs @@ -70,7 +70,7 @@ public async Task EmitToMemory() await foreach (var complogPath in Fixture.GetAllCompilerLogs(TestOutputHelper)) { TestOutputHelper.WriteLine(complogPath); - using var reader = CompilerLogReader.Create(complogPath, options: BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(complogPath, options: CompilerLogReaderOptions.None); foreach (var data in reader.ReadAllCompilationData()) { TestOutputHelper.WriteLine($"\t{data.CompilerCall.ProjectFileName} ({data.CompilerCall.TargetFramework})"); @@ -97,7 +97,7 @@ public async Task EmitToMemoryWithSeparateState() static List ReadAll(string complogPath, CompilerLogState state) { - using var reader = CompilerLogReader.Create(complogPath, options: BasicAnalyzerHostOptions.None, state: state); + using var reader = CompilerLogReader.Create(complogPath, options: CompilerLogReaderOptions.None, state: state); return reader.ReadAllCompilationData(); } } @@ -108,7 +108,7 @@ public async Task CommandLineArguments() await foreach (var complogPath in Fixture.GetAllCompilerLogs(TestOutputHelper)) { TestOutputHelper.WriteLine(complogPath); - using var reader = CompilerLogReader.Create(complogPath, options: BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(complogPath, options: CompilerLogReaderOptions.None); foreach (var data in reader.ReadAllCompilerCalls()) { Assert.NotEmpty(data.GetArguments()); @@ -124,7 +124,7 @@ public async Task ClassifyAll() { await foreach (var complogPath in Fixture.GetAllCompilerLogs(TestOutputHelper)) { - using var reader = SolutionReader.Create(complogPath, BasicAnalyzerHostOptions.None); + using var reader = SolutionReader.Create(complogPath, CompilerLogReaderOptions.None); using var workspace = new AdhocWorkspace(); workspace.AddSolution(reader.ReadSolutionInfo()); foreach (var project in workspace.CurrentSolution.Projects) @@ -159,7 +159,7 @@ public async Task ExportAndBuild(bool includeAnalyzers) [InlineData(false)] public async Task LoadAllCore(bool none) { - var options = none ? BasicAnalyzerHostOptions.None : BasicAnalyzerHostOptions.Default; + var options = none ? CompilerLogReaderOptions.None : CompilerLogReaderOptions.Default; await foreach (var complogPath in Fixture.GetAllCompilerLogs(TestOutputHelper)) { using var reader = SolutionReader.Create(complogPath, options); diff --git a/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs b/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs index 804e4d7..fd40221 100644 --- a/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs +++ b/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs @@ -13,84 +13,22 @@ using System.Diagnostics; using System.Collections.Concurrent; -#if NETCOREAPP -using System.Runtime.Loader; -#endif - namespace Basic.CompilerLog.Util; -public sealed class BasicAnalyzerHostOptions -{ - public static BasicAnalyzerHostOptions Default { get; } = new BasicAnalyzerHostOptions(BasicAnalyzerKind.Default, cacheable: true); - - public static BasicAnalyzerHostOptions None { get; } = new BasicAnalyzerHostOptions(BasicAnalyzerKind.None, cacheable: true); - - public static BasicAnalyzerKind RuntimeDefaultKind - { - get - { -#if NETCOREAPP - return BasicAnalyzerKind.InMemory; -#else - return BasicAnalyzerKind.OnDisk; -#endif - } - - } - - public BasicAnalyzerKind Kind { get; } - - /// - /// When true requests for the exact same set of analyzers will return - /// the same instance. - /// - public bool Cacheable { get; } - - internal BasicAnalyzerKind ResolvedKind => Kind switch - { - BasicAnalyzerKind.Default => RuntimeDefaultKind, - _ => Kind - }; - -#if NETCOREAPP - - public AssemblyLoadContext CompilerLoadContext { get; } - - public BasicAnalyzerHostOptions( - AssemblyLoadContext compilerLoadContext, - BasicAnalyzerKind kind, - bool cacheable = true) - { - Kind = kind; - CompilerLoadContext = compilerLoadContext; - Cacheable = cacheable; - } - -#endif - - public BasicAnalyzerHostOptions( - BasicAnalyzerKind kind, - string? analyzerDirectory = null, - bool cacheable = true) - { - Kind = kind; - Cacheable = cacheable; - -#if NETCOREAPP - CompilerLoadContext = CommonUtil.GetAssemblyLoadContext(null); -#endif - } -} - /// /// Controls how analyzers (and generators) are loaded /// public enum BasicAnalyzerKind { /// - /// Default for the current runtime + /// Analyzers and generators from the original are not loaded at all. In the case + /// the original build had generated files they are just added directly to the + /// compilation. /// - Default = 0, + /// + /// This option avoids loading third party analyzers and generators. + /// + None = 0, /// /// Analyzers are loaded in memory and disk is not used. @@ -102,16 +40,6 @@ public enum BasicAnalyzerKind /// side effect instances. /// OnDisk = 2, - - /// - /// Analyzers and generators from the original are not loaded at all. In the case - /// the original build had generated files they are just added directly to the - /// compilation. - /// - /// - /// This option avoids loading third party analyzers and generators. - /// - None = 3, } /// @@ -119,9 +47,20 @@ public enum BasicAnalyzerKind /// public abstract class BasicAnalyzerHost : IDisposable { + public static BasicAnalyzerKind DefaultKind + { + get + { +#if NETCOREAPP + return BasicAnalyzerKind.InMemory; +#else + return BasicAnalyzerKind.OnDisk; +#endif + } + + } private readonly ConcurrentQueue _diagnostics = new(); - public BasicAnalyzerHostOptions Options { get; } public BasicAnalyzerKind Kind { get; } public ImmutableArray AnalyzerReferences { @@ -136,10 +75,9 @@ public ImmutableArray AnalyzerReferences public bool IsDisposed { get; private set; } - protected BasicAnalyzerHost(BasicAnalyzerKind kind, BasicAnalyzerHostOptions options) + protected BasicAnalyzerHost(BasicAnalyzerKind kind) { Kind = kind; - Options = options; } public void Dispose() @@ -182,7 +120,7 @@ public static bool IsSupported(BasicAnalyzerKind kind) #if NETCOREAPP return true; #else - return kind is BasicAnalyzerKind.OnDisk or BasicAnalyzerKind.Default or BasicAnalyzerKind.None; + return kind is BasicAnalyzerKind.OnDisk or BasicAnalyzerKind.None; #endif } } diff --git a/src/Basic.CompilerLog.Util/CompilerLogReader.cs b/src/Basic.CompilerLog.Util/CompilerLogReader.cs index e1f7865..ac86d19 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogReader.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogReader.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.VisualBasic; +using Microsoft.VisualBasic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO.Compression; @@ -36,22 +37,23 @@ public sealed class CompilerLogReader : IDisposable /// public bool OwnsCompilerLogState { get; } - public BasicAnalyzerHostOptions BasicAnalyzerHostOptions { get; } + public CompilerLogReaderOptions Options { get; } internal CompilerLogState CompilerLogState { get; } internal Metadata Metadata { get; } internal PathNormalizationUtil PathNormalizationUtil { get; } internal int Count => Metadata.Count; + public BasicAnalyzerKind BasicAnalyzerKind => Options.BasicAnalyzerKind; public int MetadataVersion => Metadata.MetadataVersion; public bool IsWindowsLog => Metadata.IsWindows; public bool IsDisposed => _zipArchiveCore is null; internal ZipArchive ZipArchive => !IsDisposed ? _zipArchiveCore : throw new ObjectDisposedException(nameof(CompilerLogReader)); - private CompilerLogReader(ZipArchive zipArchive, Metadata metadata, BasicAnalyzerHostOptions basicAnalyzersOptions, CompilerLogState? state) + private CompilerLogReader(CompilerLogReaderOptions? options, CompilerLogState? state, ZipArchive zipArchive, Metadata metadata) { _zipArchiveCore = zipArchive; + Options = options ?? CompilerLogReaderOptions.Default; OwnsCompilerLogState = state is null; CompilerLogState = state ?? new CompilerLogState(); - BasicAnalyzerHostOptions = basicAnalyzersOptions; Metadata = metadata; ReadAssemblyInfo(); @@ -78,18 +80,17 @@ void ReadAssemblyInfo() public static CompilerLogReader Create( Stream stream, - bool leaveOpen = false, - BasicAnalyzerHostOptions? options = null, - CompilerLogState? state = null) + CompilerLogReaderOptions? options, + CompilerLogState? state, + bool leaveOpen) { - options ??= BasicAnalyzerHostOptions.Default; try { var zipArchive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen); var metadata = ReadMetadata(); return metadata.MetadataVersion switch { 1 => throw new CompilerLogException("Version 1 compiler logs are no longer supported"), - 2 => new CompilerLogReader(zipArchive, metadata, options, state), + 2 => new CompilerLogReader(options, state, zipArchive, metadata), _ => throw new CompilerLogException($"Version {metadata.MetadataVersion} is higher than the max supported version {Metadata.LatestMetadataVersion}"), }; @@ -108,15 +109,27 @@ Metadata ReadMetadata() } static Exception GetInvalidCompilerLogFileException() => new CompilerLogException("Provided stream is not a compiler log file"); - } + } + + public static CompilerLogReader Create( + Stream stream, + CompilerLogReaderOptions? options = null, + bool leaveOpen = true) => + Create(stream, options, state: null, leaveOpen); + + public static CompilerLogReader Create( + Stream stream, + CompilerLogState? state, + bool leaveOpen = true) => + Create(stream, options: null, state: state, leaveOpen); public static CompilerLogReader Create( string filePath, - BasicAnalyzerHostOptions? options = null, + CompilerLogReaderOptions? options = null, CompilerLogState? state = null) { var stream = CompilerLogUtil.GetOrCreateCompilerLogStream(filePath); - return Create(stream, leaveOpen: false, options, state); + return Create(stream, options, state: state, leaveOpen: false); } private CompilationInfoPack ReadCompilationInfo(int index) @@ -270,7 +283,7 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall) sourceTextList.Add((GetSourceText(rawContent.ContentHash, hashAlgorithm), rawContent.FilePath)); break; case RawContentKind.GeneratedText: - if (BasicAnalyzerHostOptions.ResolvedKind == BasicAnalyzerKind.None) + if (Options.BasicAnalyzerKind == BasicAnalyzerKind.None) { generatedTextList.Add((GetSourceText(rawContent.ContentHash, hashAlgorithm), rawContent.FilePath)); } @@ -521,7 +534,7 @@ internal BasicAnalyzerHost ReadAnalyzers(RawCompilationData rawCompilationData) var analyzers = rawCompilationData.Analyzers; string? key = null; BasicAnalyzerHost? basicAnalyzerHost; - if (BasicAnalyzerHostOptions.Cacheable) + if (Options.CacheAnalyzers) { key = GetKey(); if (_analyzersMap.TryGetValue(key, out basicAnalyzerHost) && !basicAnalyzerHost.IsDisposed) @@ -530,17 +543,17 @@ internal BasicAnalyzerHost ReadAnalyzers(RawCompilationData rawCompilationData) } } - basicAnalyzerHost = BasicAnalyzerHostOptions.ResolvedKind switch + basicAnalyzerHost = Options.BasicAnalyzerKind switch { - BasicAnalyzerKind.OnDisk => new BasicAnalyzerHostOnDisk(this, analyzers, BasicAnalyzerHostOptions), - BasicAnalyzerKind.InMemory => new BasicAnalyzerHostInMemory(this, analyzers, BasicAnalyzerHostOptions), - BasicAnalyzerKind.None => new BasicAnalyzerHostNone(rawCompilationData.ReadGeneratedFiles, ReadGeneratedSourceTexts(), BasicAnalyzerHostOptions), + BasicAnalyzerKind.OnDisk => new BasicAnalyzerHostOnDisk(this, analyzers), + BasicAnalyzerKind.InMemory => new BasicAnalyzerHostInMemory(this, analyzers), + BasicAnalyzerKind.None => new BasicAnalyzerHostNone(rawCompilationData.ReadGeneratedFiles, ReadGeneratedSourceTexts()), _ => throw new InvalidOperationException() }; CompilerLogState.BasicAnalyzerHosts.Add(basicAnalyzerHost); - if (BasicAnalyzerHostOptions.Cacheable) + if (Options.CacheAnalyzers) { _analyzersMap[key!] = basicAnalyzerHost; } @@ -555,7 +568,7 @@ string GetKey() builder.AppendLine($"{analyzer.Mvid}"); } - if (BasicAnalyzerHostOptions.ResolvedKind == BasicAnalyzerKind.None) + if (Options.BasicAnalyzerKind == BasicAnalyzerKind.None) { foreach (var tuple in rawCompilationData.Contents.Where(static x => x.Kind == RawContentKind.GeneratedText)) { diff --git a/src/Basic.CompilerLog.Util/CompilerLogReaderOptions.cs b/src/Basic.CompilerLog.Util/CompilerLogReaderOptions.cs new file mode 100644 index 0000000..a04ac41 --- /dev/null +++ b/src/Basic.CompilerLog.Util/CompilerLogReaderOptions.cs @@ -0,0 +1,43 @@ +using Basic.CompilerLog.Util.Impl; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.ComponentModel; +using System.Diagnostics; +using System.Collections.Concurrent; + +#if NETCOREAPP +using System.Runtime.Loader; +#endif + +namespace Basic.CompilerLog.Util; + +public sealed class CompilerLogReaderOptions +{ + public static CompilerLogReaderOptions Default { get; } = new CompilerLogReaderOptions(BasicAnalyzerHost.DefaultKind, cacheAnalyzers: true); + + public static CompilerLogReaderOptions None { get; } = new CompilerLogReaderOptions(BasicAnalyzerKind.None, cacheAnalyzers: true); + + public BasicAnalyzerKind BasicAnalyzerKind { get; } + + /// + /// When true requests for the exact same set of analyzers will return + /// the same instance. + /// + public bool CacheAnalyzers { get; } + + public CompilerLogReaderOptions( + BasicAnalyzerKind basicAnalyzerKind, + bool cacheAnalyzers = true) + { + BasicAnalyzerKind = basicAnalyzerKind; + CacheAnalyzers = cacheAnalyzers; + } +} diff --git a/src/Basic.CompilerLog.Util/CompilerLogState.cs b/src/Basic.CompilerLog.Util/CompilerLogState.cs index 9ca4913..046daf5 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogState.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogState.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CSharp.Syntax; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Basic.CompilerLog.Util; @@ -17,6 +14,10 @@ namespace Basic.CompilerLog.Util; /// to the lifetime of a but this can be explicitly /// managed in cases where live longer than the /// underlying reader. +/// +/// This differs from in that it holds actual +/// state. Nothing here is really serializable between compilations. It must be +/// created and managed by the caller. /// public sealed class CompilerLogState : IDisposable { @@ -37,6 +38,25 @@ public sealed class CompilerLogState : IDisposable internal List BasicAnalyzerHosts { get; } = new(); +#if NETCOREAPP + + public AssemblyLoadContext CompilerLoadContext { get; } + + /// + /// Create a new instance of the compiler log state + /// + /// The base path that should be used to create + /// and paths + /// The that should be used to load + /// analyzers + public CompilerLogState(AssemblyLoadContext? compilerLoadContext, string? baseDir = null) + : this(baseDir) + { + CompilerLoadContext = CommonUtil.GetAssemblyLoadContext(compilerLoadContext); + } + +#endif + /// /// Create a new instance of the compiler log state /// @@ -47,6 +67,9 @@ public CompilerLogState(string? baseDir = null) BaseDirectory = baseDir ?? Path.Combine(Path.GetTempPath(), "Basic.CompilerLog", Guid.NewGuid().ToString("N")); CryptoKeyFileDirectory = Path.Combine(BaseDirectory, "CryptoKeys"); AnalyzerDirectory = Path.Combine(BaseDirectory, "Analyzers"); +#if NETCOREAPP + CompilerLoadContext = CommonUtil.GetAssemblyLoadContext(null); +#endif } public void Dispose() diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs index 832a1a1..660d672 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs @@ -20,11 +20,11 @@ internal sealed class BasicAnalyzerHostInMemory : BasicAnalyzerHost internal InMemoryLoader Loader { get; } protected override ImmutableArray AnalyzerReferencesCore => Loader.AnalyzerReferences; - internal BasicAnalyzerHostInMemory(CompilerLogReader reader, List analyzers, BasicAnalyzerHostOptions options) - :base(BasicAnalyzerKind.InMemory, options) + internal BasicAnalyzerHostInMemory(CompilerLogReader reader, List analyzers) + :base(BasicAnalyzerKind.InMemory) { var name = $"{nameof(BasicAnalyzerHostInMemory)} - {Guid.NewGuid().ToString("N")}"; - Loader = new InMemoryLoader(name, options, reader, analyzers, AddDiagnostic); + Loader = new InMemoryLoader(name, reader, analyzers, AddDiagnostic); } protected override void DisposeCore() @@ -41,10 +41,10 @@ internal sealed class InMemoryLoader : AssemblyLoadContext internal ImmutableArray AnalyzerReferences { get; } internal AssemblyLoadContext CompilerLoadContext { get; } - internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers, Action onDiagnostic) + internal InMemoryLoader(string name, CompilerLogReader reader, List analyzers, Action onDiagnostic) :base(name, isCollectible: true) { - CompilerLoadContext = options.CompilerLoadContext; + CompilerLoadContext = reader.CompilerLogState.CompilerLoadContext; var builder = ImmutableArray.CreateBuilder(analyzers.Count); foreach (var analyzer in analyzers) { @@ -92,7 +92,7 @@ internal sealed class InMemoryLoader { internal ImmutableArray AnalyzerReferences { get; } - internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers, Action onDiagnostic) + internal InMemoryLoader(string name, CompilerLogReader reader, List analyzers, Action onDiagnostic) { throw new PlatformNotSupportedException(); } diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs index fc4f8c1..206e262 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs @@ -24,8 +24,8 @@ internal sealed class BasicAnalyzerHostNone : BasicAnalyzerHost internal ImmutableArray<(SourceText SourceText, string Path)> GeneratedSourceTexts { get; } protected override ImmutableArray AnalyzerReferencesCore { get; } - internal BasicAnalyzerHostNone(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts, BasicAnalyzerHostOptions options) - : base(BasicAnalyzerKind.None, options) + internal BasicAnalyzerHostNone(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts) + : base(BasicAnalyzerKind.None) { ReadGeneratedFiles = readGeneratedFiles; GeneratedSourceTexts = generatedSourceTexts; diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs index 09f4dda..468405c 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs @@ -25,15 +25,15 @@ internal sealed class BasicAnalyzerHostOnDisk : BasicAnalyzerHost internal string AnalyzerDirectory { get; } - internal BasicAnalyzerHostOnDisk(CompilerLogReader reader, List analyzers, BasicAnalyzerHostOptions options) - : base(BasicAnalyzerKind.OnDisk, options) + internal BasicAnalyzerHostOnDisk(CompilerLogReader reader, List analyzers) + : base(BasicAnalyzerKind.OnDisk) { var dirName = Guid.NewGuid().ToString("N"); var name = $"{nameof(BasicAnalyzerHostOnDisk)} {dirName}"; AnalyzerDirectory = Path.Combine(reader.CompilerLogState.AnalyzerDirectory, dirName); Directory.CreateDirectory(AnalyzerDirectory); - Loader = new OnDiskLoader(name, AnalyzerDirectory, options); + Loader = new OnDiskLoader(name, AnalyzerDirectory, reader.CompilerLogState); // Now create the AnalyzerFileReference. This won't actually pull on any assembly loading // until later so it can be done at the same time we're building up the files. @@ -71,10 +71,10 @@ internal sealed class OnDiskLoader : AssemblyLoadContext, IAnalyzerAssemblyLoade internal AssemblyLoadContext CompilerLoadContext { get; set; } internal string AnalyzerDirectory { get; } - internal OnDiskLoader(string name, string analyzerDirectory, BasicAnalyzerHostOptions options) + internal OnDiskLoader(string name, string analyzerDirectory, CompilerLogState state) : base(name, isCollectible: true) { - CompilerLoadContext = options.CompilerLoadContext; + CompilerLoadContext = state.CompilerLoadContext; AnalyzerDirectory = analyzerDirectory; } @@ -128,7 +128,7 @@ internal sealed class OnDiskLoader : IAnalyzerAssemblyLoader, IDisposable internal string Name { get; } internal string AnalyzerDirectory { get; } - internal OnDiskLoader(string name, string analyzerDirectory, BasicAnalyzerHostOptions options) + internal OnDiskLoader(string name, string analyzerDirectory, CompilerLogState state) { Name = name; AnalyzerDirectory = analyzerDirectory; diff --git a/src/Basic.CompilerLog.Util/SolutionReader.cs b/src/Basic.CompilerLog.Util/SolutionReader.cs index 82d0359..ac0aa02 100644 --- a/src/Basic.CompilerLog.Util/SolutionReader.cs +++ b/src/Basic.CompilerLog.Util/SolutionReader.cs @@ -45,13 +45,13 @@ public void Dispose() Reader.Dispose(); } - public static SolutionReader Create(Stream stream, bool leaveOpen = false, BasicAnalyzerHostOptions? options = null, Func? predicate = null) => - new (CompilerLogReader.Create(stream, leaveOpen, options), predicate); + public static SolutionReader Create(Stream stream, CompilerLogReaderOptions? options = null, CompilerLogState? state = null, bool leaveOpen = false, Func? predicate = null) => + new (CompilerLogReader.Create(stream, options, state, leaveOpen), predicate); - public static SolutionReader Create(string filePath, BasicAnalyzerHostOptions? options = null, Func? predicate = null) + public static SolutionReader Create(string filePath, CompilerLogReaderOptions? options = null, CompilerLogState? state = null, Func? predicate = null) { var stream = CompilerLogUtil.GetOrCreateCompilerLogStream(filePath); - return new(CompilerLogReader.Create(stream, leaveOpen: false, options), predicate); + return new(CompilerLogReader.Create(stream, options, state, leaveOpen: false), predicate); } public SolutionInfo ReadSolutionInfo() @@ -84,7 +84,7 @@ public ProjectInfo ReadProjectInfo(int index) case RawContentKind.GeneratedText: // When the host has generators these will be added as generators are run. If the host // doesn't have generators then we need to add them as documents now. - if (Reader.BasicAnalyzerHostOptions.Kind == BasicAnalyzerKind.None) + if (Reader.Options.BasicAnalyzerKind == BasicAnalyzerKind.None) { Add(generatedFiles); } diff --git a/src/Basic.CompilerLog/Constants.cs b/src/Basic.CompilerLog/Constants.cs index 540cd25..37783ac 100644 --- a/src/Basic.CompilerLog/Constants.cs +++ b/src/Basic.CompilerLog/Constants.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Basic.CompilerLog.Util; internal static class Constants { @@ -11,4 +12,7 @@ internal static class Constants internal static string CurrentDirectory { get; set; } = Environment.CurrentDirectory; internal static TextWriter Out { get; set; } = Console.Out; + + internal static Action OnCompilerLogReader = _ => { }; + } diff --git a/src/Basic.CompilerLog/FilterOptionSet.cs b/src/Basic.CompilerLog/FilterOptionSet.cs index d384a9f..308c9f8 100644 --- a/src/Basic.CompilerLog/FilterOptionSet.cs +++ b/src/Basic.CompilerLog/FilterOptionSet.cs @@ -1,15 +1,36 @@ using Basic.CompilerLog.Util; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Mono.Options; internal sealed class FilterOptionSet : OptionSet { + private bool _hasAnalyzerOptions; + private BasicAnalyzerKind _basicAnalyzerKind; + internal List TargetFrameworks { get; } = new(); internal bool IncludeAllKinds { get; set; } internal List ProjectNames { get; } = new(); internal bool Help { get; set; } - internal bool UseNoneHost { get; set; } - internal FilterOptionSet(bool includeNoneHost = false) + internal CompilerLogReaderOptions Options + { + get + { + CheckHasAnalyzerOptions(); + return new CompilerLogReaderOptions(_basicAnalyzerKind); + } + } + + internal bool IncludeAnalyzers + { + get + { + CheckHasAnalyzerOptions(); + return _basicAnalyzerKind != BasicAnalyzerKind.None; + } + } + + internal FilterOptionSet(bool analyzers = false) { Add("include", "include all compilation kinds", i => { if (i is not null) IncludeAllKinds = true; }); Add("targetframework=", "", TargetFrameworks.Add, hidden: true); @@ -18,9 +39,20 @@ internal FilterOptionSet(bool includeNoneHost = false) Add("n|projectName=", "include only compilations for the project", ProjectNames.Add, hidden: true); Add("h|help", "print help", h => { if (h != null) Help = true; }); - if (includeNoneHost) + if (analyzers) + { + _hasAnalyzerOptions = true; + _basicAnalyzerKind = BasicAnalyzerHost.DefaultKind; + Add("a|analyzers=", "analyzer load strategy: none, ondisk, inmemory", void (BasicAnalyzerKind k) => _basicAnalyzerKind = k); + Add("none", "Do not run analyzers", i => { if (i is not null) _basicAnalyzerKind = BasicAnalyzerKind.None; }, hidden: true); + } + } + + private void CheckHasAnalyzerOptions() + { + if (!_hasAnalyzerOptions) { - Add("none", "do not use original analyzers / generators", n => { if (n != null) UseNoneHost = true; }); + throw new InvalidOperationException(); } } diff --git a/src/Basic.CompilerLog/Program.cs b/src/Basic.CompilerLog/Program.cs index d7ef78c..b573cb0 100644 --- a/src/Basic.CompilerLog/Program.cs +++ b/src/Basic.CompilerLog/Program.cs @@ -273,11 +273,9 @@ void PrintUsage() int RunExport(IEnumerable args) { var baseOutputPath = ""; - var excludeAnalyzers = false; - var options = new FilterOptionSet() + var options = new FilterOptionSet(analyzers: true) { { "o|out=", "path to export build content", o => baseOutputPath = o }, - { "e|exclude-analyzers", "emit the compilation without analyzers / generators", e => excludeAnalyzers = e is not null }, }; try @@ -290,9 +288,9 @@ int RunExport(IEnumerable args) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true, options.Options); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); - var exportUtil = new ExportUtil(reader, includeAnalyzers: !excludeAnalyzers); + var exportUtil = new ExportUtil(reader, includeAnalyzers: options.IncludeAnalyzers); baseOutputPath = GetBaseOutputPath(baseOutputPath); WriteLine($"Exporting to {baseOutputPath}"); @@ -396,13 +394,11 @@ int RunReplay(IEnumerable args) var severity = DiagnosticSeverity.Warning; var export = false; var emit = false; - var analyzers = false; - var options = new FilterOptionSet(includeNoneHost: true) + var options = new FilterOptionSet(analyzers: true) { { "severity=", "minimum severity to display (default Warning)", (DiagnosticSeverity s) => severity = s }, { "export", "export failed compilation", e => export = e is not null }, { "emit", "emit the compilation(s) to disk", e => emit = e is not null }, - { "analyzers", "use actual analyzers / generators (default no)", a => analyzers = a is not null }, { "o|out=", "path to export to ", b => baseOutputPath = b }, }; @@ -427,11 +423,10 @@ int RunReplay(IEnumerable args) WriteLine($"Outputting to {baseOutputPath}"); } - var analyzerHostOptions = analyzers ? BasicAnalyzerHostOptions.Default : BasicAnalyzerHostOptions.None; using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true, analyzerHostOptions); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true, options.Options); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); - var exportUtil = new ExportUtil(reader, includeAnalyzers: analyzerHostOptions.Kind != BasicAnalyzerKind.None); + var exportUtil = new ExportUtil(reader, includeAnalyzers: options.IncludeAnalyzers); var sdkDirs = SdkUtil.GetSdkDirectories(); var success = true; @@ -540,14 +535,15 @@ passed after --. return ExitSuccess; } -CompilerLogReader GetCompilerLogReader(Stream compilerLogStream, bool leaveOpen, BasicAnalyzerHostOptions? options = null) +CompilerLogReader GetCompilerLogReader(Stream compilerLogStream, bool leaveOpen, CompilerLogReaderOptions? options = null) { - var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen, options); + var reader = CompilerLogReader.Create(compilerLogStream, options, leaveOpen); if (reader.IsWindowsLog != RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { WriteLine($"Compiler log generated on different operating system"); } + OnCompilerLogReader(reader); return reader; } diff --git a/src/Scratch/CompilerBenchmarks.cs b/src/Scratch/CompilerBenchmarks.cs index 03081a3..474c0e0 100644 --- a/src/Scratch/CompilerBenchmarks.cs +++ b/src/Scratch/CompilerBenchmarks.cs @@ -61,7 +61,7 @@ public void Emit() [Benchmark] public void LoadAnalyzers() { - using var reader = CompilerLogReader.Create(CompilerLogPath, options: new BasicAnalyzerHostOptions(Kind)); + using var reader = CompilerLogReader.Create(CompilerLogPath, options: new CompilerLogReaderOptions(Kind)); var analyzers = reader.ReadAnalyzers(reader.ReadRawCompilationData(0).Item2); foreach (var analyzer in analyzers.AnalyzerReferences) { diff --git a/src/Scratch/Scratch.cs b/src/Scratch/Scratch.cs index 339b11d..d9ffb6c 100644 --- a/src/Scratch/Scratch.cs +++ b/src/Scratch/Scratch.cs @@ -124,7 +124,7 @@ static void PrintGeneratedFiles() static async Task WorkspaceScratch() { var filePath = @"/mnt/c/Users/jaredpar/temp/console/msbuild.complog"; - using var reader = SolutionReader.Create(filePath, BasicAnalyzerHostOptions.None); + using var reader = SolutionReader.Create(filePath, CompilerLogReaderOptions.None); using var workspace = new AdhocWorkspace(); workspace.AddSolution(reader.ReadSolutionInfo()); foreach (var project in workspace.CurrentSolution.Projects) @@ -147,7 +147,7 @@ static void ExportScratch() Directory.Delete(dest, recursive: true); } - using var reader = CompilerLogReader.Create(filePath, BasicAnalyzerHostOptions.None); + using var reader = CompilerLogReader.Create(filePath, CompilerLogReaderOptions.None); var exportUtil = new ExportUtil(reader); exportUtil.ExportAll(dest, SdkUtil.GetSdkDirectories()); }