From 6e7e0b6b827c7bd1cb78d03a3205d2cedf9ed1fa Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Fri, 1 Sep 2023 12:19:31 -0700 Subject: [PATCH] Support project files for command targets (#56) * really basic build support * more fixup * More --- .vscode/launch.json | 2 +- .../ProgramTests.cs | 44 ++++- src/Basic.CompilerLog/FilterOptionSet.cs | 2 +- src/Basic.CompilerLog/Program.cs | 186 ++++++++++++------ 4 files changed, 170 insertions(+), 64 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index ccf4ea1..74f1e79 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/src/Basic.CompilerLog/bin/Debug/net7.0/Basic.CompilerLog.dll", - "args": [], + "args": ["rsp", "c:\\users\\jaredpar\\temp\\console\\console.csproj"], "cwd": "${workspaceFolder}/src/Basic.CompilerLog", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", diff --git a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs index d052ae6..97a1926 100644 --- a/src/Basic.CompilerLog.UnitTests/ProgramTests.cs +++ b/src/Basic.CompilerLog.UnitTests/ProgramTests.cs @@ -1,4 +1,5 @@ #if NETCOREAPP +using Basic.CompilerLog.Util; using Microsoft.CodeAnalysis.CSharp; using System; using System.Collections.Generic; @@ -49,6 +50,45 @@ public void Create(string extra, string fileName) Assert.True(File.Exists(complogPath)); } + [Fact] + public void CreateProjectFile() + { + RunDotNet("new console --name console -o ."); + Assert.Equal(0, RunCompLog($"create console.csproj -o msbuild.complog")); + var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); + using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + Assert.Single(reader.ReadAllCompilerCalls()); + } + + /// + /// Explicit build target overrides the implicit -t:Rebuild + /// + [Fact] + public void CreateNoopBuild() + { + RunDotNet("new console --name console -o ."); + RunDotNet("build"); + Assert.Equal(0, RunCompLog($"create console.csproj -o msbuild.complog -- -t:Build")); + var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); + using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + Assert.Empty(reader.ReadAllCompilerCalls()); + } + + [Theory] + [InlineData("console.sln")] + [InlineData("console.csproj")] + [InlineData("")] + public void CreateSolution(string target) + { + RunDotNet("new console --name console -o ."); + RunDotNet("new sln --name console"); + RunDotNet("sln add console.csproj"); + Assert.Equal(0, RunCompLog($"create {target} -o msbuild.complog")); + var complogPath = Path.Combine(RootDirectory, "msbuild.complog"); + using var reader = CompilerLogReader.Create(complogPath, BasicAnalyzerHostOptions.None); + Assert.Single(reader.ReadAllCompilerCalls()); + } + [Fact] public void CreateFullPath() { @@ -62,7 +102,7 @@ public void CreateFullPath() [Fact] public void References() { - Assert.Equal(0, RunCompLog($"ref -o {RootDirectory} {Path.Combine(Fixture.ComplogDirectory, "console.complog")}")); + Assert.Equal(0, RunCompLog($"ref -o {RootDirectory} {Fixture.ConsoleComplogPath.Value}")); Assert.NotEmpty(Directory.EnumerateFiles(Path.Combine(RootDirectory, "console", "refs"), "*.dll")); Assert.NotEmpty(Directory.EnumerateFiles(Path.Combine(RootDirectory, "console", "analyzers"), "*.dll", SearchOption.AllDirectories)); } @@ -90,7 +130,7 @@ public void ExportHelloWorld(string template) public void EmitConsole(string arg) { using var emitDir = new TempDir(); - RunCompLog($"emit {arg} -o {emitDir.DirectoryPath} {Fixture.ConsoleComplogPath}"); + RunCompLog($"emit {arg} -o {emitDir.DirectoryPath} {Fixture.ConsoleComplogPath.Value}"); AssertOutput(@"console\emit\console.dll"); AssertOutput(@"console\emit\console.pdb"); diff --git a/src/Basic.CompilerLog/FilterOptionSet.cs b/src/Basic.CompilerLog/FilterOptionSet.cs index f146551..485dfa4 100644 --- a/src/Basic.CompilerLog/FilterOptionSet.cs +++ b/src/Basic.CompilerLog/FilterOptionSet.cs @@ -11,7 +11,7 @@ internal sealed class FilterOptionSet : OptionSet internal FilterOptionSet(bool includeNoneHost = false) { - Add("include", "include all compilation kinds", i => { if (i != null) IncludeAllKinds = true; }); + Add("include", "include all compilation kinds", i => { if (i is not null) IncludeAllKinds = true; }); Add("targetframework=", "", TargetFrameworks.Add, hidden: true); Add("framework=", "include only compilations for the target framework (allows multiple)", TargetFrameworks.Add); Add("n|projectName=", "include only compilations with the project name", (string n) => ProjectName = n); diff --git a/src/Basic.CompilerLog/Program.cs b/src/Basic.CompilerLog/Program.cs index 6755393..15b7463 100644 --- a/src/Basic.CompilerLog/Program.cs +++ b/src/Basic.CompilerLog/Program.cs @@ -33,13 +33,13 @@ "emit" => RunEmit(rest, cts.Token), "analyzers" => RunAnalyzers(rest), "print" => RunPrint(rest), - "help" => RunHelp(), - _ => RunHelp() + "help" => RunHelp(rest), + _ => RunHelp(null) }; } catch (Exception e) { - RunHelp(); + RunHelp(null); WriteLine("Unexpected error"); WriteLine(e.Message); return ExitFailure; @@ -62,22 +62,11 @@ int RunCreate(IEnumerable args) return ExitFailure; } - string? binlogFilePath = null; - if (extra.Count == 1) + // todo use the standard get or build code + string binlogFilePath = GetLogFilePath(extra); + if (PathUtil.Comparer.Equals(".complog", Path.GetExtension(binlogFilePath))) { - binlogFilePath = extra[0]; - } - else if (extra.Count == 0) - { - binlogFilePath = Directory - .EnumerateFiles(CurrentDirectory, "*.binlog") - .OrderBy(x => Path.GetFileName(x), PathUtil.Comparer) - .FirstOrDefault(); - } - - if (binlogFilePath is null) - { - PrintUsage(); + WriteLine($"Already a .complog file: {binlogFilePath}"); return ExitFailure; } @@ -86,11 +75,7 @@ int RunCreate(IEnumerable args) complogFilePath = Path.ChangeExtension(binlogFilePath, ".complog"); } - if (!Path.IsPathRooted(complogFilePath)) - { - complogFilePath = Path.Combine(CurrentDirectory, complogFilePath); - } - + complogFilePath = GetResolvedPath(CurrentDirectory, complogFilePath); var diagnosticList = CompilerLogUtil.ConvertBinaryLog( binlogFilePath, complogFilePath, @@ -112,7 +97,7 @@ int RunCreate(IEnumerable args) void PrintUsage() { - WriteLine("complog create [OPTIONS] binlog"); + WriteLine("complog create [OPTIONS] msbuild.binlog"); options.WriteOptionDescriptions(Out); } } @@ -154,7 +139,7 @@ int RunAnalyzers(IEnumerable args) void PrintUsage() { - WriteLine("complog analyzers [OPTIONS] build.complog"); + WriteLine("complog analyzers [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -193,7 +178,7 @@ int RunPrint(IEnumerable args) void PrintUsage() { - WriteLine("complog print [OPTIONS] build.complog"); + WriteLine("complog print [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -277,7 +262,7 @@ string GetGroupDirectoryPath() void PrintUsage() { - WriteLine("complog rsp [OPTIONS] build.complog"); + WriteLine("complog rsp [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -329,7 +314,7 @@ int RunExport(IEnumerable args) void PrintUsage() { - WriteLine("complog export [OPTIONS] build.complog"); + WriteLine("complog export [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -379,7 +364,7 @@ int RunResponseFile(IEnumerable args) void PrintUsage() { - WriteLine("complog rsp [OPTIONS] build.complog"); + WriteLine("complog rsp [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -445,7 +430,7 @@ int RunEmit(IEnumerable args, CancellationToken cancellationToken) void PrintUsage() { - WriteLine("complog rsp [OPTIONS] build.complog"); + WriteLine("complog rsp [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } @@ -496,17 +481,27 @@ int RunDiagnostics(IEnumerable args) void PrintUsage() { - WriteLine("complog diagnostics [OPTIONS] compilerlog"); + WriteLine("complog diagnostics [OPTIONS] msbuild.complog"); options.WriteOptionDescriptions(Out); } } -int RunHelp() +int RunHelp(IEnumerable? args) { + var verbose = false; + if (args is not null) + { + var options = new OptionSet() + { + { "v|verbose", "verbose output", o => { if (o is not null) verbose = true; } }, + }; + options.Parse(args); + } + WriteLine(""" complog [command] [args] Commands - create Create a compilerlog file + create Create a compiler log file diagnostics Print diagnostics for a compilation export Export compilation contents, rsp and build files to disk rsp Generate compiler response file projects on this machine @@ -516,6 +511,19 @@ analyzers Print analyzers used by a compilation print Print summary of entries in the log help Print help """); + + if (verbose) + { + WriteLine(""" + Commands can be passed a .complog, .binlog, .sln or .csproj file. In the case of build + files a 'dotnet build' will be used to create a binlog file. Extra build args can be + passed after --. + + For example: complog create console.csproj -- -p:Configuration=Release + + """); + } + return ExitFailure; } @@ -554,54 +562,107 @@ Stream GetOrCreateCompilerLogStream(List extra) return CompilerLogUtil.GetOrCreateCompilerLogStream(logFilePath); } +/// +/// Returns a path to a .complog or .binlog to be used for processing +/// string GetLogFilePath(List extra) { - if (extra.Count > 1) - { - throw CreateOptionException(); - } - - string? path; + string? logFilePath; + IEnumerable args = Array.Empty(); + string baseDirectory = CurrentDirectory; + var printFile = false; if (extra.Count == 0) { - path = GetLogFilePath(CurrentDirectory); + logFilePath = FindLogFilePath(baseDirectory); + printFile = true; } else { - path = extra[0]; - if (string.IsNullOrEmpty(Path.GetExtension(path))) + logFilePath = extra[0]; + args = extra.Skip(1); + if (string.IsNullOrEmpty(Path.GetExtension(logFilePath))) { - path = GetLogFilePath(path); + baseDirectory = logFilePath; + logFilePath = FindLogFilePath(baseDirectory); + printFile = true; } } - return path; + if (logFilePath is null) + { + throw CreateOptionException(); + } - static string GetLogFilePath(string baseDirectory) + // If the file wasn't explicitly specified let the user know what file we are using + if (printFile) { - // Search the directory for valid log files - var path = Directory - .EnumerateFiles(baseDirectory, "*.complog") - .OrderBy(x => Path.GetFileName(x), PathUtil.Comparer) - .FirstOrDefault(); - if (path is not null) + WriteLine($"Using {logFilePath}"); + } + + switch (Path.GetExtension(logFilePath)) + { + case ".complog": + case ".binlog": + if (args.Any()) + { + throw new OptionException($"Extra arguments: {string.Join(' ', args.Skip(1))}", "log"); + } + + return GetResolvedPath(CurrentDirectory, logFilePath); + case ".sln": + case ".csproj": + case ".vbproj": + return GetLogFilePathAfterBuild(baseDirectory, logFilePath, args); + default: + throw new OptionException($"Not a valid log file {logFilePath}", "log"); + } + + static string? FindLogFilePath(string baseDirectory) => + FindFirstFileWithPattern(baseDirectory, "*.complog", "*.binlog", "*.sln", "*.csproj", ".vbproj"); + + static string GetLogFilePathAfterBuild(string baseDirectory, string? buildFileName, IEnumerable buildArgs) + { + var path = buildFileName is not null + ? GetResolvedPath(baseDirectory, buildFileName) + : FindFirstFileWithPattern(baseDirectory, "*.sln", "*.csproj", ".vbproj"); + if (path is null) { - return path; + throw CreateOptionException(); } - path = Directory - .EnumerateFiles(baseDirectory, "*.binlog") + var tag = buildArgs.Any() ? "" : "-t:Rebuild"; + var args = $"build {path} -bl:complog.binlog {tag} {string.Join(' ', buildArgs)}"; + WriteLine($"Building {path}"); + WriteLine($"dotnet {args}"); + var result = ProcessUtil.Run("dotnet", args, baseDirectory); + WriteLine(result.StandardOut); + WriteLine(result.StandardError); + if (!result.Succeeded) + { + throw new Exception("Build failed"); + } + + return Path.Combine(baseDirectory, "complog.binlog"); + } + + static OptionException CreateOptionException() => new("Need a file to analyze", "log"); +} + +static string? FindFirstFileWithPattern(string baseDirectory, params string[] patterns) +{ + foreach (var pattern in patterns) + { + var path = Directory + .EnumerateFiles(baseDirectory, pattern) .OrderBy(x => Path.GetFileName(x), PathUtil.Comparer) .FirstOrDefault(); if (path is not null) { return path; } - - throw CreateOptionException(); } - static OptionException CreateOptionException() => new("Need a path to a log file", "log"); + return null; } string GetBaseOutputPath(string? baseOutputPath) @@ -648,7 +709,12 @@ string GetProjectUniqueName(List compilerCalls, int index) return name; } +static string GetResolvedPath(string baseDirectory, string path) +{ + if (Path.IsPathRooted(path)) + { + return path; + } - - - + return Path.Combine(baseDirectory, path); +}