diff --git a/README.md b/README.md index 9359081..4a1ef01 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Compiler Logs -=== +# Compiler Logs [![codecov](https://codecov.io/gh/jaredpar/complog/graph/badge.svg?token=MIM7Y2JZ5G)](https://codecov.io/gh/jaredpar/complog) @@ -70,7 +69,9 @@ When trying to get a compiler log from a build that occurs in a GitHub action yo ``` ## Debugging Compiler Logs + ### Running locally + To re-run all of the compilations in a compiler log use the `replay` command ```cmd @@ -83,7 +84,8 @@ Microsoft.CodeAnalysis.XunitHook.csproj (net472) ...Success Passing the `-export` argument will cause all failed compilations to be exported to the local disk for easy analysis. ### Debugging in Visual Studio -To debug a compilation in Visual Studio first export it to disk: + +To debug a compilation in Visual Studio first export it to disk: ```cmd > complog export build.complog diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..298fc3a --- /dev/null +++ b/notes.txt @@ -0,0 +1,16 @@ + Converting looks like this + + public static PortableExecutableReference EmitToPortableExecutableReference( + this Compilation comp, + EmitOptions options = null, + bool embedInteropTypes = false, + ImmutableArray aliases = default, + DiagnosticDescription[] expectedWarnings = null) + { + var image = comp.EmitToArray(options, expectedWarnings: expectedWarnings); + if (comp.Options.OutputKind == OutputKind.NetModule) + { + return ModuleMetadata.CreateFromImage(image).GetReference(display: comp.MakeSourceModuleName()); + } + else + { diff --git a/src/Basic.CompilerLog.UnitTests/Basic.CompilerLog.UnitTests.csproj b/src/Basic.CompilerLog.UnitTests/Basic.CompilerLog.UnitTests.csproj index 68d6319..b7172a9 100644 --- a/src/Basic.CompilerLog.UnitTests/Basic.CompilerLog.UnitTests.csproj +++ b/src/Basic.CompilerLog.UnitTests/Basic.CompilerLog.UnitTests.csproj @@ -42,6 +42,9 @@ MetadataVersion1.console.complog + + MetadataVersion2.console.complog + linux-console.complog diff --git a/src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs b/src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs index 47b8ae0..e153153 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs @@ -57,6 +57,11 @@ public sealed class CompilerLogFixture : FixtureBase, IDisposable internal Lazy ConsoleNoGenerator { get; } + /// + /// A console project that has a reference to a library + /// + internal Lazy ConsoleWithReference { get; } + /// /// This is a console project that has every nasty feature that can be thought of /// like resources, line directives, embeds, etc ... Rather than running a @@ -336,6 +341,45 @@ partial class Util { RunDotnetCommand("build -bl -nr:false", scratchPath); }); + ConsoleWithReference = WithBuild("console-with-project-ref..complog", void (string scratchPath) => + { + RunDotnetCommand("new sln -n ConsoleWithProjectRef", scratchPath); + + // Create a class library for referencing + var classLibPath = Path.Combine(scratchPath, "classlib"); + _ = Directory.CreateDirectory(classLibPath); + RunDotnetCommand("new classlib -o . -n util", classLibPath); + File.WriteAllText( + Path.Combine(classLibPath, "Class1.cs"), + """ + using System; + namespace Util; + public static class NameInfo + { + public static string GetName() => "Hello World"; + } + """, + TestBase.DefaultEncoding); + RunDotnetCommand($@"sln add ""{classLibPath}""", scratchPath); + + // Create a console project that references the class library + var consolePath = Path.Combine(scratchPath, "console"); + _ = Directory.CreateDirectory(consolePath); + RunDotnetCommand("new console -o . -n console-with-reference", consolePath); + File.WriteAllText( + Path.Combine(consolePath, "Program.cs"), + """ + using System; + using Util; + Console.WriteLine(NameInfo.GetName()); + """, + TestBase.DefaultEncoding); + RunDotnetCommand($@"add . reference ""{classLibPath}""", consolePath); + RunDotnetCommand($@"sln add ""{consolePath}""", scratchPath); + + RunDotnetCommand("build -bl -nr:false", scratchPath); + }); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { WpfApp = WithBuild("wpfapp.complog", void (string scratchPath) => diff --git a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs index 083a82a..51384f8 100644 --- a/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs +++ b/src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs @@ -440,7 +440,7 @@ void Core(string contentFilePath) [Theory] [InlineData("MetadataVersion1.console.complog")] - public void MetadataCompat(string resourceName) + public void MetadataCompatV1(string resourceName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -449,6 +449,18 @@ public void MetadataCompat(string resourceName) } } + [Theory] + [InlineData("MetadataVersion2.console.complog")] + public void MetadataCompatV2(string resourceName) + { + using var stream = ResourceLoader.GetResourceStream(resourceName); + using var reader = CompilerLogReader.Create(stream); + foreach (var compilerCall in reader.ReadAllCompilerCalls()) + { + Assert.NotNull(compilerCall.ProjectFileName); + } + } + [Fact] public void Disposed() { @@ -465,4 +477,81 @@ public void VisualBasic() Assert.True(data.IsVisualBasic); Assert.True(data.CompilerCall.IsVisualBasic); } + + [Fact] + public void ProjectReferences_Simple() + { + using var reader = CompilerLogReader.Create(Fixture.Console.Value.CompilerLogPath); + var compilerCall = reader.ReadCompilerCall(0); + var arguments = BinaryLogUtil.ReadCommandLineArgumentsUnsafe(compilerCall); + var (assemblyFilePath, refAssemblyFilePath) = RoslynUtil.GetAssemblyOutputFilePaths(arguments); + AssertProjectRef(assemblyFilePath); + AssertProjectRef(refAssemblyFilePath); + + void AssertProjectRef(string? filePath) + { + Assert.NotNull(filePath); + var mvid = RoslynUtil.ReadMvid(filePath); + Assert.True(reader.TryGetCompilerCallIndex(mvid, out var callIndex)); + Assert.Equal(reader.GetIndex(compilerCall), callIndex); + } + } + + [Fact] + public void ProjectReferences_ReadReference() + { + using var reader = CompilerLogReader.Create(Fixture.ConsoleWithReference.Value.CompilerLogPath); + var classLibCompilerCall = reader + .ReadAllCompilerCalls(cc => cc.ProjectFileName == "util.csproj") + .Single(); + var consoleCompilerCall = reader + .ReadAllCompilerCalls(cc => cc.ProjectFileName == "console-with-reference.csproj") + .Single(); + var count = 0; + foreach (var rawReferenceData in reader.ReadAllReferenceData(consoleCompilerCall)) + { + if (reader.TryGetCompilerCallIndex(rawReferenceData.Mvid, out var callIndex)) + { + Assert.Equal(reader.GetIndex(classLibCompilerCall), callIndex); + count++; + } + } + Assert.Equal(1, count); + } + + [Fact] + public void ProjectReferences_Corrupted() + { + RunDotNet($"new console --name example --output .", Root.DirectoryPath); + RunDotNet("build -bl -nr:false", Root.DirectoryPath); + var binlogFilePath = Path.Combine(Root.DirectoryPath, "msbuild.binlog"); + + var mvidList = CorruptAssemblies(); + Assert.NotEmpty(mvidList); + using var reader = CompilerLogReader.Create(binlogFilePath); + foreach (var mvid in mvidList) + { + Assert.False(reader.TryGetCompilerCallIndex(mvid, out _)); + } + + List CorruptAssemblies() + { + var list = new List(); + using var binlogReader = BinaryLogReader.Create(binlogFilePath); + foreach (var compilerCall in binlogReader.ReadAllCompilerCalls()) + { + var (assemblyPath, refAssemblyPath) = RoslynUtil.GetAssemblyOutputFilePaths(BinaryLogUtil.ReadCommandLineArgumentsUnsafe(compilerCall)); + Assert.NotNull(assemblyPath); + Assert.NotNull(refAssemblyPath); + list.Add(RoslynUtil.ReadMvid(assemblyPath)); + list.Add(RoslynUtil.ReadMvid(refAssemblyPath)); + + File.WriteAllText(assemblyPath, "hello"); + File.WriteAllText(refAssemblyPath, "hello ref"); + + } + + return list; + } + } } diff --git a/src/Basic.CompilerLog.UnitTests/FixtureBase.cs b/src/Basic.CompilerLog.UnitTests/FixtureBase.cs index b6d5f45..ffabd46 100644 --- a/src/Basic.CompilerLog.UnitTests/FixtureBase.cs +++ b/src/Basic.CompilerLog.UnitTests/FixtureBase.cs @@ -30,6 +30,9 @@ protected void RunDotnetCommand(string args, string workingDirectory) diagnosticBuilder.AppendLine($"Standard Error: {result.StandardError}"); diagnosticBuilder.AppendLine($"Finished: {(DateTime.UtcNow - start).TotalSeconds:F2}s"); MessageSink.OnMessage(new DiagnosticMessage(diagnosticBuilder.ToString())); - Assert.True(result.Succeeded); + if (!result.Succeeded) + { + Assert.Fail($"Command failed: {diagnosticBuilder.ToString()}"); + } } } \ No newline at end of file diff --git a/src/Basic.CompilerLog.UnitTests/Resources/MetadataVersion2/console.complog b/src/Basic.CompilerLog.UnitTests/Resources/MetadataVersion2/console.complog new file mode 100644 index 0000000..fe4603b Binary files /dev/null and b/src/Basic.CompilerLog.UnitTests/Resources/MetadataVersion2/console.complog differ diff --git a/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs b/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs index 652479b..6c5cf5a 100644 --- a/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs +++ b/src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs @@ -14,6 +14,7 @@ namespace Basic.CompilerLog.UnitTests; [Collection(CompilerLogCollection.Name)] public sealed class SolutionReaderTests : TestBase { + public List ReaderList { get; } = new(); public CompilerLogFixture Fixture { get; } public SolutionReaderTests(ITestOutputHelper testOutputHelper, CompilerLogFixture fixture) @@ -22,13 +23,29 @@ public SolutionReaderTests(ITestOutputHelper testOutputHelper, CompilerLogFixtur Fixture = fixture; } + public override void Dispose() + { + base.Dispose(); + foreach (var reader in ReaderList) + { + reader.Dispose(); + } + } + + private Solution GetSolution(string compilerLogFilePath, BasicAnalyzerKind basicAnalyzerKind) + { + var reader = SolutionReader.Create(compilerLogFilePath, basicAnalyzerKind); + ReaderList.Add(reader); + var workspace = new AdhocWorkspace(); + var solution = workspace.AddSolution(reader.ReadSolutionInfo()); + return solution; + } + [Theory] [CombinatorialData] public async Task DocumentsGeneratedDefaultHost(BasicAnalyzerKind basicAnalyzerKind) { - using var reader = SolutionReader.Create(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind); - var workspace = new AdhocWorkspace(); - var solution = workspace.AddSolution(reader.ReadSolutionInfo()); + var solution = GetSolution(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind); var project = solution.Projects.Single(); Assert.NotEmpty(project.AnalyzerReferences); var docs = project.Documents.ToList(); @@ -48,4 +65,21 @@ public void CreateRespectLeaveOpen() // Throws if the underlying stream is disposed stream.Seek(0, SeekOrigin.Begin); } + + [Fact] + public async Task ProjectReference_Simple() + { + var solution = GetSolution(Fixture.ConsoleWithReference.Value.CompilerLogPath, BasicAnalyzerKind.None); + var consoleProject = solution.Projects + .Where(x => x.Name == "console-with-reference.csproj") + .Single(); + var projectReference = consoleProject.ProjectReferences.Single(); + var utilProject = solution.GetProject(projectReference.ProjectId); + Assert.NotNull(utilProject); + Assert.Equal("util.csproj", utilProject.Name); + var compilation = await consoleProject.GetCompilationAsync(); + Assert.NotNull(compilation); + var result = compilation.EmitToMemory(); + Assert.True(result.Success); + } } diff --git a/src/Basic.CompilerLog.Util/CommonUtil.cs b/src/Basic.CompilerLog.Util/CommonUtil.cs index 43d8dee..1c049c4 100644 --- a/src/Basic.CompilerLog.Util/CommonUtil.cs +++ b/src/Basic.CompilerLog.Util/CommonUtil.cs @@ -17,6 +17,7 @@ internal static class CommonUtil { internal const string MetadataFileName = "metadata.txt"; internal const string AssemblyInfoFileName = "assemblyinfo.txt"; + internal const string LogInfoFileName = "loginfo.txt"; internal static readonly Encoding ContentEncoding = Encoding.UTF8; internal static readonly MessagePackSerializerOptions SerializerOptions = MessagePackSerializerOptions.Standard.WithAllowAssemblyVersionMismatch(true); diff --git a/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs b/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs index 5ef4ab2..68b1c77 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs @@ -34,10 +34,11 @@ public bool Return(MemoryStream stream) } } - private readonly Dictionary _mvidToRefInfoMap = new(); + private readonly Dictionary _mvidToRefInfoMap = new(); private readonly Dictionary _assemblyPathToMvidMap = new(PathUtil.Comparer); private readonly HashSet _contentHashMap = new(PathUtil.Comparer); private readonly Dictionary _compilerInfoMap = new(PathUtil.Comparer); + private readonly List<(int CompilerCallIndex, bool IsRefAssembly, Guid Mvid)> _compilerCallMvidList = new(); private readonly DefaultObjectPool _memoryStreamPool = new(new MemoryStreamPoolPolicy(), maximumRetained: 5); private int _compilationCount; @@ -103,6 +104,7 @@ string AddCompilationDataPack(CommandLineArguments commandLineArguments) AddContentIf(dataPack, RawContentKind.Win32Icon, commandLineArguments.Win32Icon); AddContentIf(dataPack, RawContentKind.Win32Manifest, commandLineArguments.Win32Manifest); AddContentIf(dataPack, RawContentKind.CryptoKeyFile, commandLineArguments.CompilationOptions.CryptoKeyFile); + AddAssemblyMvid(commandLineArguments); return WriteContentMessagePack(dataPack); } @@ -174,6 +176,28 @@ void AddCompilationOptions(CompilationInfoPack infoPack, CommandLineArguments ar MessagePackUtil.CreateVisualBasicCompilationOptionsPack((VisualBasicCompilationOptions)args.CompilationOptions)); } } + + void AddAssemblyMvid(CommandLineArguments args) + { + var (assemblyFilePath, refAssemblyFilePath) = RoslynUtil.GetAssemblyOutputFilePaths(args); + AddIf(assemblyFilePath, false); + AddIf(refAssemblyFilePath, false); + void AddIf(string? filePath, bool isRefAssembly) + { + if (filePath is not null && File.Exists(filePath)) + { + try + { + var mvid = RoslynUtil.ReadMvid(filePath); + _compilerCallMvidList.Add((_compilationCount, isRefAssembly, mvid)); + } + catch (Exception ex) + { + Diagnostics.Add($"Could not read emit assembly MVID for {filePath}: {ex.Message}"); + } + } + } + } } /// @@ -243,7 +267,7 @@ public void Close() try { WriteMetadata(); - WriteAssemblyInfo(); + WriteLogInfo(); ZipArchive.Dispose(); ZipArchive = null!; } @@ -259,14 +283,17 @@ void WriteMetadata() Metadata.Create(_compilationCount, MetadataVersion).Write(writer); } - void WriteAssemblyInfo() + void WriteLogInfo() { - var entry = ZipArchive.CreateEntry(AssemblyInfoFileName, CompressionLevel.Fastest); - using var writer = Polyfill.NewStreamWriter(entry.Open(), ContentEncoding, leaveOpen: false); - foreach (var kvp in _mvidToRefInfoMap.OrderBy(x => x.Value.FileName).ThenBy(x => x.Key)) + var pack = new LogInfoPack() { - writer.WriteLine($"{kvp.Value.FileName}:{kvp.Key:N}:{kvp.Value.AssemblyName}"); - } + CompilerCallMvidList = _compilerCallMvidList, + MvidToReferenceInfoMap = _mvidToRefInfoMap + }; + var contentHash = WriteContentMessagePack(pack);; + var entry = ZipArchive.CreateEntry(LogInfoFileName, CompressionLevel.Fastest); + using var writer = Polyfill.NewStreamWriter(entry.Open(), ContentEncoding, leaveOpen: false); + writer.WriteLine(contentHash); } } @@ -574,7 +601,7 @@ private void AddAnalyzers(CompilationDataPack dataPack, CommandLineArguments arg // // Example: .nuget\packages\microsoft.visualstudio.interop\17.2.32505.113\lib\net472\Microsoft.VisualStudio.Interop.dll var assemblyName = AssemblyName.GetAssemblyName(filePath); - _mvidToRefInfoMap[info.Mvid] = (Path.GetFileName(filePath), assemblyName); + _mvidToRefInfoMap[info.Mvid] = (Path.GetFileName(filePath), assemblyName.ToString()); return info; } diff --git a/src/Basic.CompilerLog.Util/CompilerLogReader.cs b/src/Basic.CompilerLog.Util/CompilerLogReader.cs index 7a3d12e..196b548 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogReader.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogReader.cs @@ -41,6 +41,12 @@ private readonly struct CompilerCallState(CompilerLogReader reader, int index) private readonly Dictionary _mvidToRefInfoMap = new(); private readonly Dictionary _compilationInfoMap = new(); + /// + /// This stores the map between an assembly MVID and the that + /// produced it. This is useful for building up items like a project reference map. + /// + private readonly Dictionary _mvidToCompilerCallIndexMap = new(); + /// /// Is this reader responsible for disposing the instance /// @@ -63,7 +69,6 @@ private CompilerLogReader(ZipArchive zipArchive, Metadata metadata, BasicAnalyze OwnsLogReaderState = state is null; LogReaderState = state ?? new LogReaderState(); Metadata = metadata; - ReadAssemblyInfo(); PathNormalizationUtil = (Metadata.IsWindows, RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) switch { @@ -73,6 +78,15 @@ private CompilerLogReader(ZipArchive zipArchive, Metadata metadata, BasicAnalyze (false, false) => PathNormalizationUtil.Empty, }; + if (metadata.MetadataVersion == 2) + { + ReadAssemblyInfo(); + } + else + { + ReadLogInfo(); + } + void ReadAssemblyInfo() { using var reader = Polyfill.NewStreamReader(ZipArchive.OpenEntryOrThrow(AssemblyInfoFileName), ContentEncoding, leaveOpen: false); @@ -84,6 +98,21 @@ void ReadAssemblyInfo() _mvidToRefInfoMap[mvid] = (items[0], assemblyName); } } + + void ReadLogInfo() + { + using var reader = Polyfill.NewStreamReader(ZipArchive.OpenEntryOrThrow(LogInfoFileName), ContentEncoding, leaveOpen: false); + var hash = reader.ReadLine(); + var pack = GetContentPack(hash!); + foreach (var kvp in pack.MvidToReferenceInfoMap) + { + _mvidToRefInfoMap[kvp.Key] = (kvp.Value.FileName, new AssemblyName(kvp.Value.AssemblyName)); + } + foreach (var tuple in pack.CompilerCallMvidList) + { + _mvidToCompilerCallIndexMap[tuple.Mvid] = tuple.CompilerCallIndex; + } + } } public static CompilerLogReader Create( @@ -98,7 +127,7 @@ public static CompilerLogReader Create( var metadata = ReadMetadata(); return metadata.MetadataVersion switch { 1 => throw new CompilerLogException("Version 1 compiler logs are no longer supported"), - 2 => new CompilerLogReader(zipArchive, metadata, basicAnalyzerKind, state), + 2 or 3 => new CompilerLogReader(zipArchive, metadata, basicAnalyzerKind, state), _ => throw new CompilerLogException($"Version {metadata.MetadataVersion} is higher than the max supported version {Metadata.LatestMetadataVersion}"), }; @@ -305,7 +334,7 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall) { var pack = GetOrReadCompilationInfo(GetIndex(compilerCall)); var rawCompilationData = ReadRawCompilationData(compilerCall); - var referenceList = RenameMetadataReferences(rawCompilationData.References); + var referenceList = ReadMetadataReferences(rawCompilationData.References); var (emitOptions, rawParseOptions, compilationOptions) = ReadCompilerOptions(pack); var hashAlgorithm = rawCompilationData.ChecksumAlgorithm; @@ -548,6 +577,10 @@ internal string GetMetadataReferenceFileName(Guid mvid) throw new ArgumentException($"{mvid} is not a valid MVID"); } + /// + /// Reads a with the given . This + /// does not include extra metadata properties like alias, embedded interop types, etc ... + /// internal MetadataReference ReadMetadataReference(Guid mvid) { if (_refMap.TryGetValue(mvid, out var metadataReference)) @@ -562,7 +595,11 @@ internal MetadataReference ReadMetadataReference(Guid mvid) return metadataReference; } - internal MetadataReference RenameMetadataReference(in RawReferenceData data) + /// + /// Reads a from the given and applies + /// all of the properties like alias, embedded interop types, etc ... + /// + internal MetadataReference ReadMetadataReference(in RawReferenceData data) { var reference = ReadMetadataReference(data.Mvid); if (data.EmbedInteropTypes) @@ -578,12 +615,12 @@ internal MetadataReference RenameMetadataReference(in RawReferenceData data) return reference; } - internal List RenameMetadataReferences(List referenceDataList) + internal List ReadMetadataReferences(List referenceDataList) { var list = new List(capacity: referenceDataList.Count); foreach (var referenceData in referenceDataList) { - list.Add(RenameMetadataReference(referenceData)); + list.Add(ReadMetadataReference(referenceData)); } return list; } @@ -645,6 +682,9 @@ internal void CopyAssemblyBytes(Guid mvid, Stream destination) stream.CopyTo(destination); } + internal bool TryGetCompilerCallIndex(Guid mvid, out int compilerCallIndex) => + _mvidToCompilerCallIndexMap.TryGetValue(mvid, out compilerCallIndex); + [return: NotNullIfNotNull("path")] private string? NormalizePath(string? path) => PathNormalizationUtil.NormalizePath(path); diff --git a/src/Basic.CompilerLog.Util/Metadata.cs b/src/Basic.CompilerLog.Util/Metadata.cs index 67e30be..32e8401 100644 --- a/src/Basic.CompilerLog.Util/Metadata.cs +++ b/src/Basic.CompilerLog.Util/Metadata.cs @@ -9,7 +9,7 @@ namespace Basic.CompilerLog.Util; internal sealed class Metadata { - internal static readonly int LatestMetadataVersion = 2; + internal static readonly int LatestMetadataVersion = 3; internal int MetadataVersion { get; } internal int Count { get; } diff --git a/src/Basic.CompilerLog.Util/RoslynUtil.cs b/src/Basic.CompilerLog.Util/RoslynUtil.cs index d65c851..4575a0f 100644 --- a/src/Basic.CompilerLog.Util/RoslynUtil.cs +++ b/src/Basic.CompilerLog.Util/RoslynUtil.cs @@ -301,6 +301,24 @@ internal static string GetAssemblyFileName(CommandLineArguments arguments) }; } + internal static (string? AssemblyFilePath, string? RefAssemblyFilePath) GetAssemblyOutputFilePaths(CommandLineArguments arguments) + { + var assemblyFileName = RoslynUtil.GetAssemblyFileName(arguments); + string? assemblyFilePath = null; + if (arguments.OutputDirectory is { } outputDirectory) + { + assemblyFilePath = Path.Combine(outputDirectory, assemblyFileName); + } + + string? refAssemblyFilePath = null; + if (arguments.OutputRefFilePath is { }) + { + refAssemblyFilePath = arguments.OutputRefFilePath; + } + + return (assemblyFilePath, refAssemblyFilePath); + } + /// /// This checks determines if this compilation should have the files from source generators /// embedded in the PDB. This does not look at anything on disk, it makes a determination diff --git a/src/Basic.CompilerLog.Util/Serialize/MessagePackTypes.cs b/src/Basic.CompilerLog.Util/Serialize/MessagePackTypes.cs index 7ef7e19..4b094b7 100644 --- a/src/Basic.CompilerLog.Util/Serialize/MessagePackTypes.cs +++ b/src/Basic.CompilerLog.Util/Serialize/MessagePackTypes.cs @@ -214,6 +214,10 @@ public class ResourcePack public bool IsPublic { get; set; } } +/// +/// This is used to serialize information around the . Information +/// that isn't necessary for that type should go to instead. +/// [MessagePackObject] public class CompilationInfoPack { @@ -263,3 +267,15 @@ public class CompilationDataPack [Key(7)] public bool? HasGeneratedFilesInPdb { get; set; } } + +/// +/// This stores information that is relevant to all compiler calls. +/// +[MessagePackObject] +public class LogInfoPack +{ + [Key(0)] + public List<(int CompilerCallIndex, bool IsRefAssembly, Guid Mvid)> CompilerCallMvidList { get; set; } + [Key(1)] + public Dictionary MvidToReferenceInfoMap { get; set; } +} diff --git a/src/Basic.CompilerLog.Util/SolutionReader.cs b/src/Basic.CompilerLog.Util/SolutionReader.cs index 6144fbe..3b2a2c6 100644 --- a/src/Basic.CompilerLog.Util/SolutionReader.cs +++ b/src/Basic.CompilerLog.Util/SolutionReader.cs @@ -13,13 +13,13 @@ namespace Basic.CompilerLog.Util; public sealed class SolutionReader : IDisposable { - private readonly ImmutableArray<(CompilerCall CompilerCall, ProjectId ProjectId)> _projectDataList; + private readonly SortedDictionary _indexToProjectDataMap; internal CompilerLogReader Reader { get; } internal VersionStamp VersionStamp { get; } internal SolutionId SolutionId { get; } = SolutionId.CreateNewId(); - public int ProjectCount => _projectDataList.Length; + public int ProjectCount => _indexToProjectDataMap.Count; internal SolutionReader(CompilerLogReader reader, Func? predicate = null, VersionStamp? versionStamp = null) { @@ -27,17 +27,18 @@ internal SolutionReader(CompilerLogReader reader, Func? pred VersionStamp = versionStamp ?? VersionStamp.Default; predicate ??= static c => c.Kind == CompilerCallKind.Regular; - var builder = ImmutableArray.CreateBuilder<(CompilerCall, ProjectId)>(); + var map = new SortedDictionary(); for (int i = 0; i < reader.Count; i++) { var call = reader.ReadCompilerCall(i); if (predicate(call)) { var projectId = ProjectId.CreateNewId(debugName: i.ToString()); - builder.Add((call, projectId)); + map[i] = (call, projectId); } } - _projectDataList = builder.ToImmutableArray(); + + _indexToProjectDataMap = map; } public void Dispose() @@ -56,18 +57,17 @@ public static SolutionReader Create(string filePath, BasicAnalyzerKind? basicAna public SolutionInfo ReadSolutionInfo() { - var projectInfoList = new List(); - for (var i = 0; i < ProjectCount; i++) + var projectInfoList = new List(capacity: ProjectCount); + foreach (var kvp in _indexToProjectDataMap) { - projectInfoList.Add(ReadProjectInfo(i)); + projectInfoList.Add(ReadProjectInfo(kvp.Value.CompilerCall, kvp.Value.ProjectId)); } return SolutionInfo.Create(SolutionId, VersionStamp, projects: projectInfoList); } - public ProjectInfo ReadProjectInfo(int index) + private ProjectInfo ReadProjectInfo(CompilerCall compilerCall, ProjectId projectId) { - var (compilerCall, projectId) = _projectDataList[index]; var rawCompilationData = Reader.ReadRawCompilationData(compilerCall); var documents = new List(); var additionalDocuments = new List(); @@ -115,12 +115,7 @@ void Add(List list) } } - // https://github.com/jaredpar/complog/issues/24 - // Need to store project reference information at the builder point so they can be properly repacked - // here and setup in the Workspace - var projectReferences = new List(); - - var referenceList = Reader.RenameMetadataReferences(FilterToUnique(rawCompilationData.References)); + var refTuple = ReadReferences(rawCompilationData.References); var analyzers = Reader.ReadAnalyzers(rawCompilationData); var options = Reader.ReadCompilerOptions(compilerCall); var projectInfo = ProjectInfo.Create( @@ -134,8 +129,8 @@ void Add(List list) compilationOptions: options.CompilationOptions, parseOptions: options.ParseOptions, documents, - projectReferences, - referenceList, + refTuple.Item1, + refTuple.Item2, analyzerReferences: analyzers.AnalyzerReferences, additionalDocuments, isSubmission: false, @@ -143,22 +138,34 @@ void Add(List list) return projectInfo.WithAnalyzerConfigDocuments(analyzerConfigDocuments); - // The command line compiler supports having the same reference added multiple times. It's actually - // not uncommon for Microsoft.VisualBasic.dll to be passed twice when working on Visual Basic projects. - // The workspaces layer though cannot handle duplicates hence we need to run a de-dupe pass here. - static List FilterToUnique(List referenceList) + (List, List) ReadReferences(List rawReferenceDataList) { + // The command line compiler supports having the same reference added multiple times. It's actually + // not uncommon for Microsoft.VisualBasic.dll to be passed twice when working on Visual Basic projects. + // The workspaces layer though cannot handle duplicates hence we need to run a de-dupe pass here. var hashSet = new HashSet(); - var list = new List(capacity: referenceList.Count); - foreach (var data in referenceList) + var projectReferences = new List(); + var metadataReferences = new List(rawReferenceDataList.Count); + foreach (var rawReferenceData in rawCompilationData.References) { - if (hashSet.Add(data.Mvid)) + if (!hashSet.Add(rawReferenceData.Mvid)) + { + continue; + } + + if (Reader.TryGetCompilerCallIndex(rawReferenceData.Mvid, out var refCompilerCallIndex)) + { + var refProjectId = _indexToProjectDataMap[refCompilerCallIndex].ProjectId; + projectReferences.Add(new ProjectReference(refProjectId, rawReferenceData.Aliases, rawReferenceData.EmbedInteropTypes)); + } + else { - list.Add(data); + var metadataReference = Reader.ReadMetadataReference(rawReferenceData); + metadataReferences.Add(metadataReference); } } - return list; + return (projectReferences, metadataReferences); } } } diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 9b0fd6d..75dbec4 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -7,4 +7,12 @@ $(MSBuildThisFileDirectory)key.snk true + + + + +