diff --git a/src/Basic.CompilerLog.UnitTests/MetadataTests.cs b/src/Basic.CompilerLog.UnitTests/MetadataTests.cs new file mode 100644 index 0000000..a172b07 --- /dev/null +++ b/src/Basic.CompilerLog.UnitTests/MetadataTests.cs @@ -0,0 +1,44 @@ +using Basic.CompilerLog.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Basic.CompilerLog.UnitTests; + +public sealed class MetadataTests +{ + private static Metadata Parse(string content) + { + using var reader = new StringReader(content); + return Metadata.Read(reader); + } + + [Fact] + public void ParseVersion0() + { + var content = """ + count:50 + """; + var metadata = Parse(content); + Assert.Equal(0, metadata.MetadataVersion); + Assert.Equal(50, metadata.Count); + Assert.Null(metadata.IsWindows); + } + + [Fact] + public void ParseVersion1() + { + var content = """ + version:1 + count:50 + windows:true + """; + var metadata = Parse(content); + Assert.Equal(1, metadata.MetadataVersion); + Assert.Equal(50, metadata.Count); + Assert.True(metadata.IsWindows); + } +} diff --git a/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs b/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs index e50a049..3f459d1 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogBuilder.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using static Basic.CompilerLog.Util.CommonUtil; @@ -166,7 +167,7 @@ void WriteMetadata() { var entry = ZipArchive.CreateEntry(MetadataFileName, CompressionLevel.Optimal); using var writer = Polyfill.NewStreamWriter(entry.Open(), ContentEncoding, leaveOpen: false); - writer.WriteLine($"count:{_compilationCount}"); + Metadata.Create(_compilationCount).Write(writer); } void WriteAssemblyInfo() diff --git a/src/Basic.CompilerLog.Util/CompilerLogReader.cs b/src/Basic.CompilerLog.Util/CompilerLogReader.cs index 6961ca0..a9448c0 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogReader.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogReader.cs @@ -13,6 +13,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using static Basic.CompilerLog.Util.CommonUtil; @@ -20,15 +21,19 @@ namespace Basic.CompilerLog.Util; public sealed class CompilerLogReader : IDisposable { - private readonly Dictionary _refMap = new (); + public static int LatestMetadataVersion => Metadata.LatestMetadataVersion; + + private readonly Dictionary _refMap = new(); private readonly Dictionary _mvidToRefInfoMap = new(); - private readonly Dictionary _analyzersMap = new (); + private readonly Dictionary _analyzersMap = new(); private readonly bool _ownsCompilerLogState; public BasicAnalyzerHostOptions BasicAnalyzerHostOptions { get; } internal CompilerLogState CompilerLogState { get; } - internal ZipArchive ZipArchive { get; set; } - internal int Count { get; } + internal ZipArchive ZipArchive { get; private set; } + internal Metadata Metadata { get; } + internal int Count => Metadata.Count; + public int MetadataVersion => Metadata.MetadataVersion; private CompilerLogReader(Stream stream, bool leaveOpen, BasicAnalyzerHostOptions? basicAnalyzersOptions = null, CompilerLogState? state = null) { @@ -45,18 +50,25 @@ private CompilerLogReader(Stream stream, bool leaveOpen, BasicAnalyzerHostOption } BasicAnalyzerHostOptions = basicAnalyzersOptions ?? BasicAnalyzerHostOptions.Default; - Count = ReadMetadata(); + Metadata = ReadMetadata(); ReadAssemblyInfo(); - int ReadMetadata() + Metadata ReadMetadata() { var entry = ZipArchive.GetEntry(MetadataFileName) ?? throw GetInvalidCompilerLogFileException(); using var reader = Polyfill.NewStreamReader(entry.Open(), ContentEncoding, leaveOpen: false); - var line = reader.ReadLineOrThrow(); - var items = line.Split(':', StringSplitOptions.RemoveEmptyEntries); - if (items.Length != 2 || !int.TryParse(items[1], out var count)) - throw new InvalidOperationException(); - return count; + var metadata = Metadata.Read(reader); + + if (metadata.IsWindows is { } isWindows && isWindows != RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var produced = GetName(isWindows); + var current = GetName(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + throw new Exception("Compiler log created on {produced} cannot be consumed on {current}"); + + string GetName(bool isWindows) => isWindows ? "Windows" : "Unix"; + } + + return metadata; } void ReadAssemblyInfo() diff --git a/src/Basic.CompilerLog.Util/Extensions.cs b/src/Basic.CompilerLog.Util/Extensions.cs index d2ebf4a..981bd80 100644 --- a/src/Basic.CompilerLog.Util/Extensions.cs +++ b/src/Basic.CompilerLog.Util/Extensions.cs @@ -35,7 +35,7 @@ internal static byte[] ReadAllBytes(this ZipArchive zipArchive, string name) return bytes; } - internal static string ReadLineOrThrow(this StreamReader reader) + internal static string ReadLineOrThrow(this TextReader reader) { if (reader.ReadLine() is { } line) { diff --git a/src/Basic.CompilerLog.Util/Metadata.cs b/src/Basic.CompilerLog.Util/Metadata.cs new file mode 100644 index 0000000..6dbea51 --- /dev/null +++ b/src/Basic.CompilerLog.Util/Metadata.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Basic.CompilerLog.Util; + +internal sealed class Metadata +{ + internal static readonly int LatestMetadataVersion = 1; + + internal int MetadataVersion { get; } + internal int Count { get; } + internal bool? IsWindows { get; } + + private Metadata( + int metadataVersion, + int count, + bool? isWindows) + { + MetadataVersion = metadataVersion; + Count = count; + IsWindows = isWindows; + } + + internal static Metadata Create(int count) => + new Metadata( + LatestMetadataVersion, + count, + RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); + + internal static Metadata Read(TextReader reader) + { + try + { + var line = reader.ReadLineOrThrow(); + if (line.StartsWith("count", StringComparison.Ordinal)) + { + // This is a version 0, there is just a count method + var count = ParseLine(line, "count", int.Parse); + return new Metadata(metadataVersion: 0, count, isWindows: null); + } + else + { + var metadataVersion = ParseLine(line, "version", int.Parse); + var count = ParseLine(reader.ReadLineOrThrow(), "count", int.Parse); + var isWindows = ParseLine(reader.ReadLineOrThrow(), "windows", bool.Parse); + return new Metadata( + metadataVersion, + count, + isWindows); + } + } + catch (Exception ex) + { + throw new InvalidOperationException("Cannot parse metadata", ex); + } + + T ParseLine(string line, string label, Func func) + { + var items = line.Split(':', StringSplitOptions.RemoveEmptyEntries); + if (items.Length != 2 || items[0] != label) + throw new InvalidOperationException("Line has wrong format"); + + return func(items[1]); + } + } + + internal void Write(StreamWriter writer) + { + writer.WriteLine($"version:{MetadataVersion}"); + writer.WriteLine($"count:{Count}"); + writer.WriteLine($"windows:{RuntimeInformation.IsOSPlatform(OSPlatform.Windows)}"); + } +} diff --git a/src/Basic.CompilerLog/Program.cs b/src/Basic.CompilerLog/Program.cs index a0000ff..6755393 100644 --- a/src/Basic.CompilerLog/Program.cs +++ b/src/Basic.CompilerLog/Program.cs @@ -2,6 +2,7 @@ using Basic.CompilerLog.Util; using Microsoft.CodeAnalysis; using Mono.Options; +using System.Runtime.InteropServices; using System.Runtime.Loader; using System.Text; using static Constants; @@ -130,7 +131,7 @@ int RunAnalyzers(IEnumerable args) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); foreach (var compilerCall in compilerCalls) @@ -215,7 +216,7 @@ int RunReferences(IEnumerable args) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); baseOutputPath = GetBaseOutputPath(baseOutputPath); @@ -301,7 +302,7 @@ int RunExport(IEnumerable args) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); var exportUtil = new ExportUtil(reader, includeAnalyzers: !excludeAnalyzers); @@ -401,7 +402,7 @@ int RunEmit(IEnumerable args, CancellationToken cancellationToken) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); var compilerCalls = reader.ReadAllCompilerCalls(options.FilterCompilerCalls); var allSucceeded = true; @@ -467,7 +468,7 @@ int RunDiagnostics(IEnumerable args) } using var compilerLogStream = GetOrCreateCompilerLogStream(extra); - using var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen: true); + using var reader = GetCompilerLogReader(compilerLogStream, leaveOpen: true); var compilationDatas = reader.ReadAllCompilationData(options.FilterCompilerCalls); foreach (var compilationData in compilationDatas) @@ -534,6 +535,19 @@ List GetCompilerCalls(List extra, Func } } +CompilerLogReader GetCompilerLogReader(Stream compilerLogStream, bool leaveOpen) +{ + var reader = CompilerLogReader.Create(compilerLogStream, leaveOpen); + if (reader.MetadataVersion > CompilerLogReader.LatestMetadataVersion) + { + WriteLine($"Compiler log version newer than toolset: {reader.MetadataVersion}"); + WriteLine($"Consider upgrading to latest compiler log toolset"); + WriteLine("dotnet tool update --global Basic.CompilerLog"); + } + + return reader; +} + Stream GetOrCreateCompilerLogStream(List extra) { var logFilePath = GetLogFilePath(extra);