Skip to content

Commit

Permalink
Store compiler information (#114)
Browse files Browse the repository at this point in the history
* Store compiler path

* round trip versions

* Compiler info in the output

* Warn when newer compiler is used

* more

* mor

* more
  • Loading branch information
jaredpar authored Feb 27, 2024
1 parent 2506bc6 commit ecbac51
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 57 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
"Complogs",
"cryptodir",
"cryptokeyfile",
"inmemory",
"jaredpar",
"msbuild",
"Mvid",
"NETCOREAPP",
"netstandard",
"ondisk",
"Relogger",
"ruleset",
"Xunit"
Expand Down
13 changes: 7 additions & 6 deletions src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ namespace Basic.CompilerLog.UnitTests;
public sealed class BinaryLogUtilTests
{
[Theory]
[InlineData("dotnet exec csc.dll a.cs", "a.cs")]
[InlineData("dotnet not what we expect a.cs", "")]
[InlineData("csc.exe a.cs b.cs", "a.cs b.cs")]
public void SkipCompilerExecutableTests(string args, string expected)
[InlineData("dotnet exec csc.dll a.cs", "csc.dll", "a.cs")]
[InlineData("dotnet not what we expect a.cs", null, "")]
[InlineData("csc.exe a.cs b.cs", "csc.exe", "a.cs b.cs")]
public void ParseCompilerAndArguments(string inputArgs, string? expectedCompilerFilePath, string expectedArgs)
{
var realArgs = BinaryLogUtil.SkipCompilerExecutable(ToArray(args), "csc.exe", "csc.dll");
Assert.Equal(ToArray(expected), realArgs);
var (actualCompilerFilePath, actualArgs) = BinaryLogUtil.ParseCompilerAndArguments(ToArray(inputArgs), "csc.exe", "csc.dll");
Assert.Equal(ToArray(expectedArgs), actualArgs);
Assert.Equal(expectedCompilerFilePath, actualCompilerFilePath);
static string[] ToArray(string arg) => arg.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries);
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/Basic.CompilerLog.UnitTests/CompilerLogBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,33 @@ public void AddMissingFile()

var compilerCall = BinaryLogUtil.ReadAllCompilerCalls(binlogStream, new()).First(x => x.IsCSharp);
compilerCall = new CompilerCall(
compilerCall.CompilerFilePath,
compilerCall.ProjectFilePath,
CompilerCallKind.Regular,
compilerCall.TargetFramework,
isCSharp: true,
new Lazy<string[]>(() => ["/sourcelink:does-not-exist.txt"]),
null);
Assert.Throws<Exception>(() => builder.Add(compilerCall));
}

[Fact]
public void AddWithMissingCompilerFilePath()
{
using var stream = new MemoryStream();
using var builder = new CompilerLogBuilder(stream, new());
using var binlogStream = new FileStream(Fixture.ConsoleWithDiagnosticsBinaryLogPath, FileMode.Open, FileAccess.Read, FileShare.Read);

var compilerCall = BinaryLogUtil.ReadAllCompilerCalls(binlogStream, new()).First(x => x.IsCSharp);
var args = compilerCall.GetArguments();
compilerCall = new CompilerCall(
compilerFilePath: null,
compilerCall.ProjectFilePath,
CompilerCallKind.Regular,
compilerCall.TargetFramework,
isCSharp: true,
new Lazy<string[]>(() => args),
null);
builder.Add(compilerCall);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private CompilerLogReader ConvertConsoleArgs(Func<string[], string[]> func, Comp
{
var args = func(x.GetArguments());
return new CompilerCall(
x.CompilerFilePath,
x.ProjectFilePath,
x.Kind,
x.TargetFramework,
Expand Down
58 changes: 56 additions & 2 deletions src/Basic.CompilerLog.UnitTests/ProgramTests.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#if NETCOREAPP
using Basic.CompilerLog.Util;
using Basic.CompilerLog.Util.Serialize;
using MessagePack;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -490,6 +495,40 @@ public void ReplayHelp()
Assert.StartsWith("complog replay [OPTIONS]", output);
}

[Fact]
public void ReplayNewCompiler()
{
string logFilePath = CreateBadLog();
var (exitCode, output) = RunCompLogEx($"replay {logFilePath}");
Assert.Equal(Constants.ExitSuccess, exitCode);
Assert.Contains("Compiler in log is newer than complog: 99.99.99.99 >", output);

string CreateBadLog()
{
var logFilePath = Path.Combine(RootDirectory, "mutated.complog");
CompilerLogUtil.ConvertBinaryLog(
Fixture.SolutionBinaryLogPath,
logFilePath,
cc => cc.ProjectFileName == "console.csproj");
MutateArchive(logFilePath);
return logFilePath;
}

static void MutateArchive(string complogFilePath)
{
using var fileStream = new FileStream(complogFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
using var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Update, leaveOpen: true);
using var entryStream = zipArchive.OpenEntryOrThrow(CommonUtil.GetCompilerEntryName(0));
var infoPack = MessagePackSerializer.Deserialize<CompilationInfoPack>(entryStream, CommonUtil.SerializerOptions);
infoPack.CompilerAssemblyName = Regex.Replace(
infoPack.CompilerAssemblyName!,
@"\d+\.\d+\.\d+\.\d+",
"99.99.99.99");
entryStream.Position = 0;
MessagePackSerializer.Serialize(entryStream, infoPack, CommonUtil.SerializerOptions);
}
}

[Fact]
public void ReplayBadOption()
{
Expand All @@ -499,7 +538,7 @@ public void ReplayBadOption()
}

[Fact]
public void RelpayBadOptionCombination()
public void ReplayBadOptionCombination()
{
var (exitCode, output) = RunCompLogEx($"replay -o example");
Assert.Equal(Constants.ExitFailure, exitCode);
Expand Down Expand Up @@ -534,8 +573,23 @@ public void PrintOne()
Assert.Contains("classlib.csproj (net7.0)", output);
}

[Fact]
public void PrintCompilers()
{
var (exitCode, output) = RunCompLogEx($"print {Fixture.SolutionBinaryLogPath} -c");
Assert.Equal(Constants.ExitSuccess, exitCode);

using var reader = CompilerLogReader.Create(Fixture.ConsoleWithDiagnosticsBinaryLogPath);
var tuple = reader.ReadAllCompilerAssemblies().Single();
Assert.Contains($"""
Compilers
{tuple.CompilerFilePath}
{tuple.AssemblyName}
""", output);
}

/// <summary>
/// Engage the code to find files in the specidied directory
/// Engage the code to find files in the specified directory
/// </summary>
[Fact]
public void PrintDirectory()
Expand Down
11 changes: 11 additions & 0 deletions src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ public async Task CommandLineArguments()
using var reader = CompilerLogReader.Create(complogPath, options: CompilerLogReaderOptions.None);
foreach (var data in reader.ReadAllCompilerCalls())
{
var fileName = Path.GetFileName(complogPath);
if (fileName is "windows-console.complog" ||
fileName is "linux-console.complog")
{
Assert.Null(data.CompilerFilePath);
}
else
{
Assert.NotNull(data.CompilerFilePath);
}

Assert.NotEmpty(data.GetArguments());
}
}
Expand Down
25 changes: 16 additions & 9 deletions src/Basic.CompilerLog.Util/BinaryLogUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,23 @@ public CompilationTaskData(MSBuildProjectData projectData, int targetId)
}

var kind = Kind ?? CompilerCallKind.Unknown;
var args = CommandLineParser.SplitCommandLineIntoArguments(CommandLineArguments, removeHashComments: true);
var rawArgs = IsCSharp
? SkipCompilerExecutable(args, "csc.exe", "csc.dll").ToArray()
: SkipCompilerExecutable(args, "vbc.exe", "vbc.dll").ToArray();
if (rawArgs.Length == 0)
var rawArgs = CommandLineParser.SplitCommandLineIntoArguments(CommandLineArguments, removeHashComments: true);
var (compilerFilePath, args) = IsCSharp
? ParseCompilerAndArguments(rawArgs, "csc.exe", "csc.dll")
: ParseCompilerAndArguments(rawArgs, "vbc.exe", "vbc.dll");
if (args.Length == 0)
{
diagnosticList.Add($"Project {ProjectFile} ({TargetFramework}): bad argument list");
return null;
}

return new CompilerCall(
compilerFilePath,
ProjectFile,
kind,
TargetFramework,
isCSharp: IsCSharp,
new Lazy<string[]>(() => rawArgs),
new Lazy<string[]>(() => args),
index: null);
}
}
Expand Down Expand Up @@ -292,38 +293,44 @@ void SetTargetFramework(ref string? targetFramework, IEnumerable? rawProperties)
/// The argument list is going to include either `dotnet exec csc.dll` or `csc.exe`. Need
/// to skip past that to get to the real command line.
/// </summary>
internal static IEnumerable<string> SkipCompilerExecutable(IEnumerable<string> args, string exeName, string dllName)
internal static (string? CompilerFilePath, string[] Arguments) ParseCompilerAndArguments(IEnumerable<string> args, string exeName, string dllName)
{
using var e = args.GetEnumerator();

// The path to the executable is not escaped like the other command line arguments. Need
// to skip until we see an exec or a path with the exe as the file name.
string? compilerFilePath = null;
var found = false;
while (e.MoveNext())
{
if (PathUtil.Comparer.Equals(e.Current, "exec"))
{
if (e.MoveNext() && PathUtil.Comparer.Equals(Path.GetFileName(e.Current), dllName))
{
compilerFilePath = e.Current;
found = true;
}
break;
}
else if (e.Current.EndsWith(exeName, PathUtil.Comparison))
{
compilerFilePath = e.Current;
found = true;
break;
}
}

if (!found)
{
yield break;
return (null, Array.Empty<string>());
}

var list = new List<string>();
while (e.MoveNext())
{
yield return e.Current;
list.Add(e.Current);
}

return (compilerFilePath, list.ToArray());
}
}
14 changes: 11 additions & 3 deletions src/Basic.CompilerLog.Util/CompilerCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ public enum CompilerCallKind
Unknown
}

/// <summary>
/// Represents a call to the compiler. The file paths and arguments provided here are correct
/// for the machine on which the compiler was run. They cannot be relied on to be correct on
/// machines where a compiler log is rehydrated.
/// </summary>
public sealed class CompilerCall
{
private readonly Lazy<string[]> _lazyArguments;
private readonly Lazy<CommandLineArguments> _lazyParsedArgumets;
private readonly Lazy<CommandLineArguments> _lazyParsedArguments;

public string? CompilerFilePath { get; }
public string ProjectFilePath { get; }
public CompilerCallKind Kind { get; }
public string? TargetFramework { get; }
Expand All @@ -50,20 +56,22 @@ public sealed class CompilerCall
public string ProjectDirectory => Path.GetDirectoryName(ProjectFilePath)!;

internal CompilerCall(
string? compilerFilePath,
string projectFilePath,
CompilerCallKind kind,
string? targetFramework,
bool isCSharp,
Lazy<string[]> arguments,
int? index)
{
CompilerFilePath = compilerFilePath;
ProjectFilePath = projectFilePath;
Kind = kind;
TargetFramework = targetFramework;
IsCSharp = isCSharp;
Index = index;
_lazyArguments = arguments;
_lazyParsedArgumets = new Lazy<CommandLineArguments>(ParseArgumentsCore);
_lazyParsedArguments = new Lazy<CommandLineArguments>(ParseArgumentsCore);
}

public string GetDiagnosticName()
Expand All @@ -81,7 +89,7 @@ public string GetDiagnosticName()

public string[] GetArguments() => _lazyArguments.Value;

internal CommandLineArguments ParseArguments() => _lazyParsedArgumets.Value;
internal CommandLineArguments ParseArguments() => _lazyParsedArguments.Value;

private CommandLineArguments ParseArgumentsCore()
{
Expand Down
Loading

0 comments on commit ecbac51

Please sign in to comment.