Skip to content

Commit

Permalink
Using the None host with binary logs (#120)
Browse files Browse the repository at this point in the history
Before this change using the None host with a binary log required round tripping to a compiler log first. Now it can just be done directly on a binary log.
  • Loading branch information
jaredpar committed Apr 27, 2024
1 parent 82b20ae commit 866649d
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 212 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
<PackageVersion Include="System.IO.Compression" Version="4.3.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageVersion Include="Xunit.Combinatorial" Version="1.6.24" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Xunit.Combinatorial" />
</ItemGroup>

<ItemGroup>
Expand Down
13 changes: 11 additions & 2 deletions src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,26 @@ public void Supported()
[Fact]
public void NoneDispose()
{
var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty);
var host = new BasicAnalyzerHostNone(ImmutableArray<(SourceText, string)>.Empty);
host.Dispose();
Assert.Throws<ObjectDisposedException>(() => { _ = host.AnalyzerReferences; });
}

[Fact]
public void NoneProps()
{
var host = new BasicAnalyzerHostNone(readGeneratedFiles: true, ImmutableArray<(SourceText, string)>.Empty);
var host = new BasicAnalyzerHostNone(ImmutableArray<(SourceText, string)>.Empty);
host.Dispose();
Assert.Equal(BasicAnalyzerKind.None, host.Kind);
Assert.Empty(host.GeneratedSourceTexts);
}

[Fact]
public void Error()
{
var message = "my error message";
var host = new BasicAnalyzerHostNone(message);
var diagnostic = host.GetDiagnostics().Single();
Assert.Contains(message, diagnostic.GetMessage());
}
}
28 changes: 28 additions & 0 deletions src/Basic.CompilerLog.UnitTests/BinaryLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,32 @@ public void VerifyBasicAnalyzerKind(BasicAnalyzerKind basicAnalyzerKind)
var compilationData = reader.ReadCompilationData(compilerCall);
Assert.Equal(basicAnalyzerKind, compilationData.BasicAnalyzerHost.Kind);
}

[Theory]
[CombinatorialData]
public void GetCompilationSimple(BasicAnalyzerKind basicAnalyzerKind)
{
using var reader = BinaryLogReader.Create(Fixture.Console.Value.BinaryLogPath!, basicAnalyzerKind);
var compilerCall = reader.ReadAllCompilerCalls().First();
var compilationData = reader.ReadCompilationData(compilerCall);
Assert.NotNull(compilationData);
var emitResult = compilationData.EmitToMemory();
Assert.True(emitResult.Success);
}

[Fact]
public void ReadDeletedPdb()
{
var dir = Root.NewDirectory();
RunDotNet($"new console --name example --output .", dir);
RunDotNet("build -bl -nr:false", dir);

// Delete the PDB
Directory.EnumerateFiles(dir, "*.pdb", SearchOption.AllDirectories).ForEach(File.Delete);

using var reader = BinaryLogReader.Create(Path.Combine(dir, "msbuild.binlog"), BasicAnalyzerKind.None);
var data = reader.ReadAllCompilationData().Single();
var diagnostic = data.GetDiagnostics().Where(x => x.Severity == DiagnosticSeverity.Error).Single();
Assert.Contains("Can't find portable pdb file for", diagnostic.GetMessage());
}
}
11 changes: 6 additions & 5 deletions src/Basic.CompilerLog.UnitTests/CompilerCallReaderUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ public CompilerCallReaderUtilTests(ITestOutputHelper testOutputHelper, CompilerL
[Fact]
public void CreateBadExtension()
{
Assert.Throws<ArgumentException>(() => CompilerCallReaderUtil.Get("file.bad"));
Assert.Throws<ArgumentException>(() => CompilerCallReaderUtil.Create("file.bad"));
}

[Fact]
public void GetBadArguments()
[Theory]
[CombinatorialData]
public void GetAllAnalyzerKinds(BasicAnalyzerKind basicAnalyzerKind)
{
var binlogPath = Fixture.Console.Value.BinaryLogPath!;
Assert.Throws<ArgumentException>(() => CompilerCallReaderUtil.Get(binlogPath, BasicAnalyzerKind.None));
using var reader = CompilerCallReaderUtil.Create(Fixture.Console.Value.CompilerLogPath!, basicAnalyzerKind);
Assert.Equal(basicAnalyzerKind, reader.BasicAnalyzerKind);
}
}
2 changes: 1 addition & 1 deletion src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public void NoneHostNativePdb()

using var reader = CompilerLogReader.Create(Path.Combine(RootDirectory, "msbuild.binlog"), BasicAnalyzerKind.None);
var rawData = reader.ReadRawCompilationData(0).Item2;
Assert.False(rawData.ReadGeneratedFiles);
Assert.False(rawData.HasAllGeneratedFileContent);
var data = reader.ReadCompilationData(0);
var compilation = data.GetCompilationAfterGenerators(out var diagnostics);
Assert.Single(diagnostics);
Expand Down
2 changes: 1 addition & 1 deletion src/Basic.CompilerLog.UnitTests/CompilerLogUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public CompilerLogUtilTests(ITestOutputHelper testOutputHelper, CompilerLogFixtu
[Fact]
public void CreateBadExtension()
{
Assert.Throws<ArgumentException>(() => CompilerCallReaderUtil.Get("file.bad"));
Assert.Throws<ArgumentException>(() => CompilerCallReaderUtil.Create("file.bad"));
}
}
6 changes: 2 additions & 4 deletions src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ public async Task EmitToDisk()
}

[Theory]
[InlineData(BasicAnalyzerKind.None)]
[InlineData(BasicAnalyzerKind.InMemory)]
[InlineData(BasicAnalyzerKind.OnDisk)]
[CombinatorialData]
public async Task EmitToMemory(BasicAnalyzerKind basicAnalyzerKind)
{
TestOutputHelper.WriteLine($"BasicAnalyzerKind: {basicAnalyzerKind}");
Expand All @@ -77,7 +75,7 @@ public async Task EmitToMemory(BasicAnalyzerKind basicAnalyzerKind)
{
count++;
TestOutputHelper.WriteLine(logPath);
using var reader = CompilerCallReaderUtil.GetOrCreate(logPath, basicAnalyzerKind);
using var reader = CompilerCallReaderUtil.Create(logPath, basicAnalyzerKind);
foreach (var data in reader.ReadAllCompilationData())
{
TestOutputHelper.WriteLine($"\t{data.CompilerCall.ProjectFileName} ({data.CompilerCall.TargetFramework})");
Expand Down
42 changes: 34 additions & 8 deletions src/Basic.CompilerLog.Util/BinaryLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ public sealed class BinaryLogReader : ICompilerCallReader, IBasicAnalyzerHostDat

private BinaryLogReader(Stream stream, bool leaveOpen, BasicAnalyzerKind? basicAnalyzerKind, LogReaderState? state)
{
if (basicAnalyzerKind == BasicAnalyzerKind.None)
{
throw new ArgumentException($"{nameof(BasicAnalyzerKind)}.None is not supported on binary logs");
}

_stream = stream;
BasicAnalyzerKind = basicAnalyzerKind ?? BasicAnalyzerHost.DefaultKind;
OwnsLogReaderState = state is null;
Expand Down Expand Up @@ -58,7 +53,7 @@ public static BinaryLogReader Create(
BasicAnalyzerKind? basicAnalyzerKind = null,
LogReaderState? state = null)
{
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
var stream = RoslynUtil.OpenBuildFileForRead(filePath);
return Create(stream, basicAnalyzerKind, state: state, leaveOpen: false);
}

Expand Down Expand Up @@ -130,6 +125,12 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall)
var emitData = GetEmitData();
var basicAnalyzerHost = CreateAnalyzerHost();

if (basicAnalyzerHost is BasicAnalyzerHostNone none)
{
// Generated source code should appear last to match the compiler behavior.
sourceTexts.AddRange(none.GeneratedSourceTexts);
}

return compilerCall.IsCSharp ? GetCSharp() : GetVisualBasic();

CSharpCompilationData GetCSharp()
Expand Down Expand Up @@ -180,11 +181,36 @@ BasicAnalyzerHost CreateAnalyzerHost()
list,
(kind, analyzers) => kind switch
{
BasicAnalyzerKind.None => throw new ArgumentException("Cannot create a host for None"),
BasicAnalyzerKind.None => CreateNoneHost(),
BasicAnalyzerKind.OnDisk => new BasicAnalyzerHostOnDisk(this, analyzers),
BasicAnalyzerKind.InMemory => new BasicAnalyzerHostInMemory(this, analyzers),
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
});

BasicAnalyzerHostNone CreateNoneHost()
{
if (!RoslynUtil.HasGeneratedFilesInPdb(args))
{
return new BasicAnalyzerHostNone("Compilation does not have a PDB compatible with generated files");
}

try
{
var generatedFiles = RoslynUtil.ReadGeneratedFiles(compilerCall, args);
var builder = ImmutableArray.CreateBuilder<(SourceText SourceText, string Path)>(generatedFiles.Count);
foreach (var tuple in generatedFiles)
{
var sourceText = RoslynUtil.GetSourceText(tuple.Stream, args.ChecksumAlgorithm, canBeEmbedded: false);
builder.Add((sourceText, tuple.FilePath));
}

return new BasicAnalyzerHostNone(builder.MoveToImmutable());
}
catch (Exception ex)
{
return new BasicAnalyzerHostNone(ex.Message);
}
}
}

List<(SourceText SourceText, string Path)> GetAnalyzerConfigs() =>
Expand Down Expand Up @@ -282,7 +308,7 @@ private void CheckOwnership(CompilerCall compilerCall)

void IBasicAnalyzerHostDataProvider.CopyAssemblyBytes(RawAnalyzerData data, Stream stream)
{
using var fileStream = new FileStream(data.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
using var fileStream = RoslynUtil.OpenBuildFileForRead(data.FilePath);
fileStream.CopyTo(stream);
}

Expand Down
22 changes: 1 addition & 21 deletions src/Basic.CompilerLog.Util/CompilerCallReaderUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public static class CompilerCallReaderUtil
/// <summary>
/// Create an <see cref="ICompilerCallReader"/> directly over the provided file path
/// </summary>
public static ICompilerCallReader Get(string filePath, BasicAnalyzerKind? basicAnalyzerKind = null, LogReaderState? logReaderState = null)
public static ICompilerCallReader Create(string filePath, BasicAnalyzerKind? basicAnalyzerKind = null, LogReaderState? logReaderState = null)
{
var ext = Path.GetExtension(filePath);
if (ext is ".binlog")
Expand All @@ -20,24 +20,4 @@ public static ICompilerCallReader Get(string filePath, BasicAnalyzerKind? basicA

throw new ArgumentException($"Unrecognized extension: {ext}");
}

/// <summary>
/// Get or create an <see cref="ICompilerCallReader"/> over the provided file path. The implementation
/// may convert binary logs to compiler logs if the provided arguments aren't compatible with
/// a binary log. For example if <see cref="BasicAnalyzerKind.None"/> is provided
/// </summary>
public static ICompilerCallReader GetOrCreate(string filePath, BasicAnalyzerKind? basicAnalyzerKind = null, LogReaderState? logReaderState = null)
{
var ext = Path.GetExtension(filePath);
if (ext is ".binlog")
{
if (basicAnalyzerKind is BasicAnalyzerKind.None)
{
var stream = CompilerLogUtil.GetOrCreateCompilerLogStream(filePath);
return CompilerLogReader.Create(stream, basicAnalyzerKind, logReaderState, leaveOpen: false);
}
}

return Get(filePath, basicAnalyzerKind, logReaderState);
}
}
Loading

0 comments on commit 866649d

Please sign in to comment.