Skip to content

Commit

Permalink
Read TF out of GlobalProperties (#138)
Browse files Browse the repository at this point in the history
Turns out the target framework can be included in `GlobalProperties` as
well. Need to consult that when building up compilations.
  • Loading branch information
jaredpar committed Jun 10, 2024
1 parent 7dba67b commit c310985
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 120 deletions.
20 changes: 14 additions & 6 deletions src/Basic.CompilerLog.UnitTests/BinaryLogUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,34 +96,42 @@ public sealed class MSBuildProjectDataTests
[Fact]
public void MSBuildProjectDataToString()
{
var data = new BinaryLogUtil.MSBuildProjectData(@"example.csproj");
var evalData = new BinaryLogUtil.MSBuildProjectEvaluationData(@"example.csproj");
var data = new BinaryLogUtil.MSBuildProjectContextData(@"example.csproj", 100, 1);
Assert.NotEmpty(data.ToString());
}
}

public sealed class CompilationTaskDataTests
{
internal BinaryLogUtil.MSBuildProjectData ProjectData { get; } = new BinaryLogUtil.MSBuildProjectData(@"example.csproj");
internal BinaryLogUtil.MSBuildProjectEvaluationData EvaluationData { get; }
internal BinaryLogUtil.MSBuildProjectContextData ContextData { get; }

public CompilationTaskDataTests()
{
EvaluationData = new BinaryLogUtil.MSBuildProjectEvaluationData(@"example.csproj");
ContextData = new(@"example.csproj", 100, 1);
}

[Fact]
public void TryCreateCompilerCallBadArguments()
{
var data = new BinaryLogUtil.CompilationTaskData(ProjectData, 1)
var data = new BinaryLogUtil.CompilationTaskData(1, 1, true)
{
CommandLineArguments = "dotnet not a compiler call",
};

Assert.Throws<InvalidOperationException>(() => data.TryCreateCompilerCall(ownerState: null));
Assert.Throws<InvalidOperationException>(() => data.TryCreateCompilerCall(ContextData.ProjectFile, null, CompilerCallKind.Unknown, ownerState: null));
}

[Fact]
public void TryCreateCompilerNoArguments()
{
var data = new BinaryLogUtil.CompilationTaskData(ProjectData, 1)
var data = new BinaryLogUtil.CompilationTaskData(1, 1, true)
{
CommandLineArguments = null,
};

Assert.Null(data.TryCreateCompilerCall(null));
Assert.Null(data.TryCreateCompilerCall(ContextData.ProjectFile, null, CompilerCallKind.Unknown, null));
}
}
158 changes: 65 additions & 93 deletions src/Basic.CompilerLog.Util/BinaryLogUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,39 +32,45 @@ public static class BinaryLogUtil
// Oddities observed
// - There are project start / stop events that have no evaluation id

internal sealed class MSBuildProjectData
internal sealed class MSBuildProjectContextData(string projectFile, int contextId, int evaluationId)
{
private readonly Dictionary<int, CompilationTaskData> _targetMap = new();
public readonly string ProjectFile;
private readonly Dictionary<int, CompilationTaskData> _taskMap = new(capacity: 4);
private readonly Dictionary<int, CompilerCallKind> _targetMap = new(capacity: 4);
public readonly int ContextId = contextId;
public int EvaluationId = evaluationId;
public string? TargetFramework;
public int? EvaluationId;
public readonly string ProjectFile = projectFile;

public MSBuildProjectData(string projectFile)
public bool TryGetTaskData(BuildEventContext context, [NotNullWhen(true)] out CompilationTaskData? data) =>
_taskMap.TryGetValue(context.TaskId, out data);

public void SetCompilerCallKind(int targetId, CompilerCallKind kind)
{
ProjectFile = projectFile;
Debug.Assert(targetId != BuildEventContext.InvalidTargetId);
_targetMap[targetId] = kind;
}

public bool TryGetTaskData(int targetId, [NotNullWhen(true)] out CompilationTaskData? data) =>
_targetMap.TryGetValue(targetId, out data);

public CompilationTaskData GetOrCreateTaskData(int targetId)
public CompilationTaskData CreateTaskData(BuildEventContext context, bool isCSharp)
{
if (!_targetMap.TryGetValue(targetId, out var data))
{
data = new CompilationTaskData(this, targetId);
_targetMap[targetId] = data;
}

Debug.Assert(!_taskMap.ContainsKey(context.TaskId));
var data = new CompilationTaskData(context.TargetId, context.TaskId, isCSharp);
_taskMap[context.TaskId] = data;
return data;
}

public List<CompilerCall> GetAllCompilerCalls(object? ownerState)
public List<CompilerCall> GetAllCompilerCalls(MSBuildProjectEvaluationData? evaluationData, object? ownerState)
{
var targetFramework = TargetFramework ?? evaluationData?.TargetFramework;
var list = new List<CompilerCall>();

foreach (var data in _targetMap.Values)
foreach (var data in _taskMap.Values)
{
if (data.TryCreateCompilerCall(ownerState) is { } compilerCall)
if (!_targetMap.TryGetValue(data.TargetId, out var compilerCallKind))
{
compilerCallKind = CompilerCallKind.Unknown;
}

if (data.TryCreateCompilerCall(ProjectFile, targetFramework, compilerCallKind, ownerState) is { } compilerCall)
{
if (compilerCall.Kind == CompilerCallKind.Regular)
{
Expand All @@ -83,60 +89,44 @@ public List<CompilerCall> GetAllCompilerCalls(object? ownerState)
public override string ToString() => $"{Path.GetFileName(ProjectFile)}({TargetFramework})";
}

internal sealed class CompilationTaskData
internal sealed class CompilationTaskData(int targetId, int taskId, bool isCSharp)
{
public readonly MSBuildProjectData ProjectData;
public int TargetId;
public readonly int TargetId = targetId;
public readonly int TaskId = taskId;
public readonly bool IsCSharp = isCSharp;
public string? CommandLineArguments;
public CompilerCallKind? Kind;
public int? CompileTaskId;
public bool IsCSharp;

public string ProjectFile => ProjectData.ProjectFile;
public string? TargetFramework => ProjectData.TargetFramework;

public CompilationTaskData(MSBuildProjectData projectData, int targetId)
{
ProjectData = projectData;
TargetId = targetId;
}

public override string ToString() => $"{ProjectData} {TargetId}";
[ExcludeFromCodeCoverage]
public override string ToString() => TaskId.ToString();

internal CompilerCall? TryCreateCompilerCall(object? ownerState)
internal CompilerCall? TryCreateCompilerCall(string projectFile, string? targetFramework, CompilerCallKind kind, object? ownerState)
{
if (CommandLineArguments is null)
{
// An evaluation of the project that wasn't actually a compilation
return null;
}

var kind = Kind ?? CompilerCallKind.Unknown;
var (compilerFilePath, args) = IsCSharp
? ParseTaskForCompilerAndArguments(CommandLineArguments, "csc.exe", "csc.dll")
: ParseTaskForCompilerAndArguments(CommandLineArguments, "vbc.exe", "vbc.dll");

return new CompilerCall(
compilerFilePath,
ProjectFile,
projectFile,
kind,
TargetFramework,
targetFramework,
isCSharp: IsCSharp,
new Lazy<IReadOnlyCollection<string>>(() => args),
ownerState: ownerState);
}
}

private sealed class MSBuildEvaluationData
internal sealed class MSBuildProjectEvaluationData(string projectFile)
{
public string ProjectFile;
public string ProjectFile = projectFile;
public string? TargetFramework;

public MSBuildEvaluationData(string projectFile)
{
ProjectFile = projectFile;
}

[ExcludeFromCodeCoverage]
public override string ToString() => $"{Path.GetFileName(ProjectFile)}({TargetFramework})";
}
Expand All @@ -150,8 +140,8 @@ public static List<CompilerCall> ReadAllCompilerCalls(Stream stream, Func<Compil
var list = new List<CompilerCall>();
var records = BinaryLog.ReadRecords(stream);

var contextMap = new Dictionary<int, MSBuildProjectData>();
var evaluationMap = new Dictionary<int, MSBuildEvaluationData>();
var contextMap = new Dictionary<int, MSBuildProjectContextData>();
var evaluationMap = new Dictionary<int, MSBuildProjectEvaluationData>();

foreach (var record in records)
{
Expand All @@ -164,24 +154,17 @@ public static List<CompilerCall> ReadAllCompilerCalls(Stream stream, Func<Compil
{
case ProjectStartedEventArgs { ProjectFile: not null } e:
{
var data = GetOrCreateData(context, e.ProjectFile);
data.EvaluationId = GetEvaluationId(e);
SetTargetFramework(ref data.TargetFramework, e.Properties);
var contextData = GetOrCreateContextData(context, e.ProjectFile);
SetTargetFramework(ref contextData.TargetFramework, e.GlobalProperties);
SetTargetFramework(ref contextData.TargetFramework, e.Properties);
break;
}
case ProjectFinishedEventArgs e:
{
if (contextMap.TryGetValue(context.ProjectContextId, out var data))
if (contextMap.TryGetValue(context.ProjectContextId, out var contextData))
{
if (string.IsNullOrEmpty(data.TargetFramework) &&
data.EvaluationId is { } evaluationId &&
evaluationMap.TryGetValue(evaluationId, out var evaluationData) &&
!string.IsNullOrEmpty(evaluationData.TargetFramework))
{
data.TargetFramework = evaluationData.TargetFramework;
}

foreach (var compilerCall in data.GetAllCompilerCalls(ownerState))
_ = evaluationMap.TryGetValue(context.EvaluationId, out var evaluationData);
foreach (var compilerCall in contextData.GetAllCompilerCalls(evaluationData, ownerState))
{
if (predicate(compilerCall))
{
Expand All @@ -193,20 +176,23 @@ data.EvaluationId is { } evaluationId &&
}
case ProjectEvaluationStartedEventArgs { ProjectFile: not null } e:
{
var data = new MSBuildEvaluationData(e.ProjectFile);
Debug.Assert(context.EvaluationId != BuildEventContext.InvalidEvaluationId);
var data = new MSBuildProjectEvaluationData(e.ProjectFile);
evaluationMap[context.EvaluationId] = data;
break;
}
case ProjectEvaluationFinishedEventArgs e:
{
if (evaluationMap.TryGetValue(context.EvaluationId, out var data))
if (evaluationMap.TryGetValue(context.EvaluationId, out var evaluationData))
{
SetTargetFramework(ref data.TargetFramework, e.Properties);
SetTargetFramework(ref evaluationData.TargetFramework, e.Properties);
}
break;
}
case TargetStartedEventArgs e:
{
Debug.Assert(context.TargetId != BuildEventContext.InvalidTargetId);

var callKind = e.TargetName switch
{
"CoreCompile" when e.ParentTarget == "_CompileTemporaryAssembly" => CompilerCallKind.WpfTemporaryCompile,
Expand All @@ -216,32 +202,27 @@ data.EvaluationId is { } evaluationId &&
_ => (CompilerCallKind?)null
};

if (callKind is { } ck &&
context.TargetId != BuildEventContext.InvalidTargetId &&
contextMap.TryGetValue(context.ProjectContextId, out var data))
if (callKind is { } ck && contextMap.TryGetValue(context.ProjectContextId, out var contextData))
{
data.GetOrCreateTaskData(context.TargetId).Kind = ck;
contextData.SetCompilerCallKind(context.TargetId, ck);
}

break;
}
case TaskStartedEventArgs e:
{
if ((e.TaskName == "Csc" || e.TaskName == "Vbc") &&
context.TargetId != BuildEventContext.InvalidTargetId &&
contextMap.TryGetValue(context.ProjectContextId, out var data))
contextMap.TryGetValue(context.ProjectContextId, out var contextData))
{
var taskData = data.GetOrCreateTaskData(context.TargetId);
taskData.IsCSharp = e.TaskName == "Csc";
taskData.CompileTaskId = context.TaskId;
var isCSharp = e.TaskName == "Csc";
_ = contextData.CreateTaskData(context, isCSharp);
}
break;
}
case TaskCommandLineEventArgs e:
{
if (context.TargetId != BuildEventContext.InvalidTargetId &&
contextMap.TryGetValue(context.ProjectContextId, out var data) &&
data.TryGetTaskData(context.TargetId, out var taskData))
if (contextMap.TryGetValue(context.ProjectContextId, out var contextData) &&
contextData.TryGetTaskData(context, out var taskData))
{
taskData.CommandLineArguments = e.CommandLine;
}
Expand All @@ -253,30 +234,20 @@ data.EvaluationId is { } evaluationId &&

return list;

static int? GetEvaluationId(ProjectStartedEventArgs e)
MSBuildProjectContextData GetOrCreateContextData(BuildEventContext context, string projecFile)
{
if (e.BuildEventContext is { EvaluationId: > BuildEventContext.InvalidEvaluationId })
if (!contextMap.TryGetValue(context.ProjectContextId, out var contextData))
{
return e.BuildEventContext.EvaluationId;
contextData = new MSBuildProjectContextData(projecFile, context.ProjectContextId, context.EvaluationId);
contextMap[context.ProjectContextId] = contextData;
}

if (e.ParentProjectBuildEventContext is { EvaluationId: > BuildEventContext.InvalidEvaluationId })
if (contextData.EvaluationId == BuildEventContext.InvalidEvaluationId)
{
return e.ParentProjectBuildEventContext.EvaluationId;
contextData.EvaluationId = context.EvaluationId;
}

return null;
}

MSBuildProjectData GetOrCreateData(BuildEventContext context, string projectFile)
{
if (!contextMap.TryGetValue(context.ProjectContextId, out var data))
{
data = new MSBuildProjectData(projectFile);
contextMap[context.ProjectContextId] = data;
}

return data;
return contextData;
}

void SetTargetFramework(ref string? targetFramework, IEnumerable? rawProperties)
Expand All @@ -291,6 +262,7 @@ void SetTargetFramework(ref string? targetFramework, IEnumerable? rawProperties)
switch (property.Key)
{
case "TargetFramework":
Debug.Assert(!string.IsNullOrEmpty(property.Value));
targetFramework = property.Value;
break;
case "TargetFrameworks":
Expand Down
2 changes: 1 addition & 1 deletion src/Basic.CompilerLog/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"CompilerLogger": {
"commandName": "Project",
"commandLineArgs": "print -c \"C:\\Users\\jaredpar\\Downloads\\out_no_analyzers.binlog\"",
"commandLineArgs": "print -c \"C:\\Users\\jaredpar\\Downloads\\out_no_analyzers.binlog\" -p MiddleTier.Provider.csproj",
"workingDirectory": "C:\\users\\jaredpar\\temp"
}
}
Expand Down
Loading

0 comments on commit c310985

Please sign in to comment.