From 89fa8b5d7a42314087043b1ae3877e92736bb009 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 13:37:35 +0200 Subject: [PATCH 1/8] Removed non-language-agnostic things from the core project --- src/Buildalyzer/AnalyzerResult.cs | 20 --- .../Compiler/CSharpCompilerCommand.cs | 2 +- src/Buildalyzer/Compiler/Compiler.cs | 118 ------------------ src/Buildalyzer/Compiler/CompilerCommand.cs | 2 +- src/Buildalyzer/Compiler/CompilerLanguage.cs | 17 --- .../Compiler/FSharpCompilerCommand.cs | 2 +- .../Compiler/VisualBasicCompilerCommand.cs | 2 +- .../Extensions/CompilerLanguageExtensions.cs | 14 --- 8 files changed, 4 insertions(+), 173 deletions(-) delete mode 100644 src/Buildalyzer/Compiler/Compiler.cs delete mode 100644 src/Buildalyzer/Compiler/CompilerLanguage.cs delete mode 100644 src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs diff --git a/src/Buildalyzer/AnalyzerResult.cs b/src/Buildalyzer/AnalyzerResult.cs index 7557a966..a5ead913 100644 --- a/src/Buildalyzer/AnalyzerResult.cs +++ b/src/Buildalyzer/AnalyzerResult.cs @@ -107,26 +107,6 @@ internal void ProcessProject(PropertiesAndItems propertiesAndItems) } } - internal void ProcessCscCommandLine(string commandLine, bool coreCompile) - { - // Some projects can have multiple Csc calls (see #92) so if this is the one inside CoreCompile use it, otherwise use the first - if (string.IsNullOrWhiteSpace(commandLine) || (CompilerCommand != null && !coreCompile)) - { - return; - } - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.CSharp); - } - - internal void ProcessVbcCommandLine(string commandLine) - { - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.VisualBasic); - } - - internal void ProcessFscCommandLine(string commandLine) - { - CompilerCommand = Compiler.CommandLine.Parse(new FileInfo(ProjectFilePath).Directory, commandLine, CompilerLanguage.FSharp); - } - private class ProjectItemItemSpecEqualityComparer : IEqualityComparer { public bool Equals(IProjectItem x, IProjectItem y) => x.ItemSpec.Equals(y.ItemSpec, StringComparison.OrdinalIgnoreCase); diff --git a/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs b/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs index c2f8f6d9..d7dceb3e 100644 --- a/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/CSharpCompilerCommand.cs @@ -7,5 +7,5 @@ namespace Buildalyzer; public sealed record CSharpCompilerCommand : RoslynBasedCompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.CSharp; + public override string Language => "C#"; } \ No newline at end of file diff --git a/src/Buildalyzer/Compiler/Compiler.cs b/src/Buildalyzer/Compiler/Compiler.cs deleted file mode 100644 index c146a34e..00000000 --- a/src/Buildalyzer/Compiler/Compiler.cs +++ /dev/null @@ -1,118 +0,0 @@ -#nullable enable - -using System.IO; -using Buildalyzer.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.VisualBasic; - -namespace Buildalyzer; - -public static class Compiler -{ - public static class CommandLine - { - [Pure] - public static string[]? SplitCommandLineIntoArguments(string? commandLine, CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => RoslynCommandLineParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe"), - CompilerLanguage.VisualBasic => RoslynCommandLineParser.SplitCommandLineIntoArguments(commandLine, "vbc.dll", "vbc.exe"), - CompilerLanguage.FSharp => FSharpCommandLineParser.SplitCommandLineIntoArguments(commandLine), - _ => throw new NotSupportedException($"The {language} language is not supported."), - }; - - [Pure] - public static CompilerCommand Parse(DirectoryInfo? baseDir, string commandLine, CompilerLanguage language) - { - var tokens = SplitCommandLineIntoArguments(commandLine, language) ?? throw new FormatException("Commandline could not be parsed."); - var location = new FileInfo(tokens[0]); - var args = tokens[1..]; - - return Parse(baseDir?.ToString(), location.Directory?.ToString(), args, language) with - { - Text = commandLine, - CompilerLocation = location, - Arguments = args.ToImmutableArray(), - }; - - static CompilerCommand Parse(string? baseDir, string? root, string[] args, CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => CSharpParser.Parse(args, baseDir, root), - CompilerLanguage.VisualBasic => VisualBasicParser.Parse(args, baseDir, root), - CompilerLanguage.FSharp => FSharpParser.Parse(args), - _ => throw new NotSupportedException($"The {language} language is not supported."), - }; - } - } - - private static class CSharpParser - { - [Pure] - public static CSharpCompilerCommand Parse(string[] args, string? baseDir, string? root) - { - var arguments = CSharpCommandLineParser.Default.Parse(args, baseDir, root); - var command = new CSharpCompilerCommand() - { - CommandLineArguments = arguments, - }; - return RoslynParser.Enrich(command, arguments); - } - } - - private static class VisualBasicParser - { - [Pure] - public static VisualBasicCompilerCommand Parse(string[] args, string? baseDir, string? root) - { - var arguments = VisualBasicCommandLineParser.Default.Parse(args, baseDir, root); - var command = new VisualBasicCompilerCommand() - { - CommandLineArguments = arguments, - PreprocessorSymbols = arguments.ParseOptions.PreprocessorSymbols.ToImmutableDictionary(), - }; - return RoslynParser.Enrich(command, arguments); - } - } - - private static class FSharpParser - { - [Pure] - public static FSharpCompilerCommand Parse(string[] args) - { - var sourceFiles = args.Where(a => a[0] != '-').Select(IOPath.Parse); - var preprocessorSymbolNames = args.Where(a => a.StartsWith("--define:")).Select(a => a[9..]); - var metadataReferences = args.Where(a => a.StartsWith("-r:")).Select(a => a[3..]); - - return new() - { - MetadataReferences = metadataReferences.ToImmutableArray(), - PreprocessorSymbolNames = preprocessorSymbolNames.ToImmutableArray(), - SourceFiles = sourceFiles.ToImmutableArray(), - }; - } - } - - private static class RoslynParser - { - public static TCommand Enrich(TCommand command, CommandLineArguments arguments) - where TCommand : CompilerCommand - - => command with - { - AnalyzerReferences = arguments.AnalyzerReferences.Select(AsIOPath).ToImmutableArray(), - AnalyzerConfigPaths = arguments.AnalyzerConfigPaths.Select(IOPath.Parse).ToImmutableArray(), - MetadataReferences = arguments.MetadataReferences.Select(m => m.Reference).ToImmutableArray(), - PreprocessorSymbolNames = arguments.ParseOptions.PreprocessorSymbolNames.ToImmutableArray(), - - SourceFiles = arguments.SourceFiles.Select(AsIOPath).ToImmutableArray(), - AdditionalFiles = arguments.AdditionalFiles.Select(AsIOPath).ToImmutableArray(), - EmbeddedFiles = arguments.EmbeddedFiles.Select(AsIOPath).ToImmutableArray(), - }; - } - - [Pure] - internal static IOPath AsIOPath(CommandLineAnalyzerReference file) => IOPath.Parse(file.FilePath); - - [Pure] - internal static IOPath AsIOPath(CommandLineSourceFile file) => IOPath.Parse(file.Path); -} diff --git a/src/Buildalyzer/Compiler/CompilerCommand.cs b/src/Buildalyzer/Compiler/CompilerCommand.cs index 69b6e6aa..b7f30719 100644 --- a/src/Buildalyzer/Compiler/CompilerCommand.cs +++ b/src/Buildalyzer/Compiler/CompilerCommand.cs @@ -10,7 +10,7 @@ namespace Buildalyzer; public abstract record CompilerCommand { /// The compiler lanuague. - public abstract CompilerLanguage Language { get; } + public abstract string Language { get; } /// The original text of the compiler command. public string Text { get; init; } = string.Empty; diff --git a/src/Buildalyzer/Compiler/CompilerLanguage.cs b/src/Buildalyzer/Compiler/CompilerLanguage.cs deleted file mode 100644 index afd76cf7..00000000 --- a/src/Buildalyzer/Compiler/CompilerLanguage.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Buildalyzer; - -/// The compiler language. -public enum CompilerLanguage -{ - /// None. - None = 0, - - /// C#. - CSharp = 1, - - /// VB.NET. - VisualBasic = 2, - - /// F#. - FSharp = 3, -} diff --git a/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs b/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs index 398b177f..323fd053 100644 --- a/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/FSharpCompilerCommand.cs @@ -6,5 +6,5 @@ namespace Buildalyzer; public sealed record FSharpCompilerCommand : CompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.FSharp; + public override string Language => "F#"; } diff --git a/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs b/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs index cfc5e89d..517a96d6 100644 --- a/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs +++ b/src/Buildalyzer/Compiler/VisualBasicCompilerCommand.cs @@ -7,7 +7,7 @@ namespace Buildalyzer; public sealed record VisualBasicCompilerCommand : RoslynBasedCompilerCommand { /// - public override CompilerLanguage Language => CompilerLanguage.VisualBasic; + public override string Language => "VB.NET"; /// public ImmutableDictionary? PreprocessorSymbols { get; init; } diff --git a/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs b/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs deleted file mode 100644 index 1f4e6ed6..00000000 --- a/src/Buildalyzer/Extensions/CompilerLanguageExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Buildalyzer; - -internal static class CompilerLanguageExtensions -{ - /// Represents the as (DEBUG) display string. - [Pure] - public static string Display(this CompilerLanguage language) => language switch - { - CompilerLanguage.CSharp => "C#", - CompilerLanguage.FSharp => "F#", - CompilerLanguage.VisualBasic => "VB.NET", - _ => language.ToString(), - }; -} From f6294af6ece29e28361fd619d375246324c1f664 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 13:51:48 +0200 Subject: [PATCH 2/8] Laid out base interface --- .../Compiler/ICompilerOptionsParser.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/Buildalyzer/Compiler/ICompilerOptionsParser.cs diff --git a/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs new file mode 100644 index 00000000..ec05f5de --- /dev/null +++ b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs @@ -0,0 +1,54 @@ +using System.IO; +using Microsoft.Build.Framework; + +namespace Buildalyzer; + +/// +/// Parses compiler options from a string. +/// +public interface ICompilerOptionsParser +{ + /// + /// The name of the language that this parser supports. + /// + public string Language { get; } + + /// + /// Checks, if the given invocation is one for the language compiler that this parser supports. + /// + /// The event sender. + /// The build event arguments. + /// Contextual information for the parser. + /// True, if this parser supports the event and should be invoked. + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context); + + /// + /// Parses the compiler options from the given command line. + /// + /// The command line to parse. + /// Contextual information for the parser. + /// The parsed . + CompilerCommand? Parse(string commandLine, CompilerOptionsContext context); +} + +/// +/// Contextual information for parsing compiler options. +/// +public readonly struct CompilerOptionsContext +{ + /// + /// True, if this is the first compiler invocation. + /// False, if one is already found. + /// + public bool IsFirst { get; init; } + + /// + /// True, if this is a call inside CoreCompile. + /// + public bool CoreCompile { get; init; } + + /// + /// The base directory of the project. + /// + public DirectoryInfo? BaseDirectory { get; init; } +} From b1a4a181ce0bb201223123e73f1657e2eb3ee8af Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 14:44:11 +0200 Subject: [PATCH 3/8] Wired in parsers --- src/Buildalyzer/AnalyzerManager.cs | 8 ++++-- src/Buildalyzer/AnalyzerManagerOptions.cs | 2 ++ src/Buildalyzer/AnalyzerResult.cs | 2 +- src/Buildalyzer/Logging/EventProcessor.cs | 34 +++++++++++------------ 4 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/Buildalyzer/AnalyzerManager.cs b/src/Buildalyzer/AnalyzerManager.cs index 7d4eb565..b78adf3b 100644 --- a/src/Buildalyzer/AnalyzerManager.cs +++ b/src/Buildalyzer/AnalyzerManager.cs @@ -39,12 +39,14 @@ public class AnalyzerManager : IAnalyzerManager public SolutionFile SolutionFile { get; } + public List CompilerOptionsParsers { get; } + public AnalyzerManager(AnalyzerManagerOptions options = null) : this(null, options) { } - public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions options = null) + public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions? options = null) { options ??= new AnalyzerManagerOptions(); LoggerFactory = options.LoggerFactory; @@ -58,13 +60,15 @@ public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions options = foreach (ProjectInSolution projectInSolution in SolutionFile.ProjectsInOrder) { if (!SupportedProjectTypes.Contains(projectInSolution.ProjectType) - || (options?.ProjectFilter != null && !options.ProjectFilter(projectInSolution))) + || (options.ProjectFilter != null && !options.ProjectFilter(projectInSolution))) { continue; } GetProject(projectInSolution.AbsolutePath, projectInSolution); } } + + CompilerOptionsParsers = options.CompilerOptionsParsers; } public void SetGlobalProperty(string key, string value) diff --git a/src/Buildalyzer/AnalyzerManagerOptions.cs b/src/Buildalyzer/AnalyzerManagerOptions.cs index 12f1a627..7de10e39 100644 --- a/src/Buildalyzer/AnalyzerManagerOptions.cs +++ b/src/Buildalyzer/AnalyzerManagerOptions.cs @@ -30,4 +30,6 @@ public TextWriter LogWriter LoggerFactory.AddProvider(new TextWriterLoggerProvider(value)); } } + + public List CompilerOptionsParsers { get; set; } = []; } diff --git a/src/Buildalyzer/AnalyzerResult.cs b/src/Buildalyzer/AnalyzerResult.cs index a5ead913..1d3d06dc 100644 --- a/src/Buildalyzer/AnalyzerResult.cs +++ b/src/Buildalyzer/AnalyzerResult.cs @@ -11,7 +11,7 @@ public class AnalyzerResult : IAnalyzerResult private readonly Dictionary _items = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Guid _projectGuid; - public CompilerCommand CompilerCommand { get; private set; } + public CompilerCommand CompilerCommand { get; internal set; } internal AnalyzerResult(string projectFilePath, AnalyzerManager manager, ProjectAnalyzer analyzer) { diff --git a/src/Buildalyzer/Logging/EventProcessor.cs b/src/Buildalyzer/Logging/EventProcessor.cs index 9ac7ed26..6b3e7f2d 100644 --- a/src/Buildalyzer/Logging/EventProcessor.cs +++ b/src/Buildalyzer/Logging/EventProcessor.cs @@ -1,4 +1,6 @@ extern alias StructuredLogger; + +using System.IO; using Microsoft.Build.Framework; using Microsoft.Extensions.Logging; @@ -148,26 +150,24 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) AnalyzerResult result = _currentResult.Count == 0 ? null : _currentResult.Peek(); if (result is object) { - // Process the command line arguments for the Fsc task - if (e.SenderName?.Equals("Fsc", StringComparison.OrdinalIgnoreCase) == true - && !string.IsNullOrWhiteSpace(e.Message) - && _targetStack.Any(x => x.TargetName == "CoreCompile") - && result.CompilerCommand is null) - { - result.ProcessFscCommandLine(e.Message); - } - - // Process the command line arguments for the Csc task - if (e is TaskCommandLineEventArgs cmd - && string.Equals(cmd.TaskName, "Csc", StringComparison.OrdinalIgnoreCase)) + CompilerOptionsContext context = new() { - result.ProcessCscCommandLine(cmd.CommandLine, _targetStack.Any(x => x.TargetName == "CoreCompile")); - } + IsFirst = result.CompilerCommand is null, + CoreCompile = _targetStack.Any(x => x.TargetName == "CoreCompile"), + BaseDirectory = new FileInfo(result.ProjectFilePath).Directory, + }; - if (e is TaskCommandLineEventArgs cmdVbc && - string.Equals(cmdVbc.TaskName, "Vbc", StringComparison.OrdinalIgnoreCase)) + foreach (ICompilerOptionsParser parser in _manager.CompilerOptionsParsers) { - result.ProcessVbcCommandLine(cmdVbc.CommandLine); + if (parser.IsSupportedInvocation(sender, e, context)) + { + CompilerCommand? command = parser.Parse(e.Message, context); + if (command is not null) + { + result.CompilerCommand = command; + break; + } + } } } } From 6c24ec74765863348a18a95342390e697f290290 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 15:15:14 +0200 Subject: [PATCH 4/8] Added in parsers --- src/Buildalyzer/AnalyzerManagerOptions.cs | 5 +- src/Buildalyzer/Compiler/CscOptionsParser.cs | 49 +++++++++++++++++++ ...rpCommandLineParser.cs => FSharpParser.cs} | 2 +- src/Buildalyzer/Compiler/FscOptionsParser.cs | 49 +++++++++++++++++++ .../Compiler/ICompilerOptionsParser.cs | 7 ++- .../Compiler/RoslynCommandLineParser.cs | 28 ----------- src/Buildalyzer/Compiler/RoslynParser.cs | 49 +++++++++++++++++++ src/Buildalyzer/Compiler/VbcOptionsParser.cs | 44 +++++++++++++++++ src/Buildalyzer/Logging/EventProcessor.cs | 3 +- .../Compiler/CompilerCommandFixture.cs | 2 +- 10 files changed, 205 insertions(+), 33 deletions(-) create mode 100644 src/Buildalyzer/Compiler/CscOptionsParser.cs rename src/Buildalyzer/Compiler/{FSharpCommandLineParser.cs => FSharpParser.cs} (97%) create mode 100644 src/Buildalyzer/Compiler/FscOptionsParser.cs delete mode 100644 src/Buildalyzer/Compiler/RoslynCommandLineParser.cs create mode 100644 src/Buildalyzer/Compiler/RoslynParser.cs create mode 100644 src/Buildalyzer/Compiler/VbcOptionsParser.cs diff --git a/src/Buildalyzer/AnalyzerManagerOptions.cs b/src/Buildalyzer/AnalyzerManagerOptions.cs index 7de10e39..c1ab407c 100644 --- a/src/Buildalyzer/AnalyzerManagerOptions.cs +++ b/src/Buildalyzer/AnalyzerManagerOptions.cs @@ -31,5 +31,8 @@ public TextWriter LogWriter } } - public List CompilerOptionsParsers { get; set; } = []; + public List CompilerOptionsParsers { get; set; } = [ + CscOptionsParser.Instance, + VbcOptionsParser.Instance, + FscOptionsParser.Instance]; } diff --git a/src/Buildalyzer/Compiler/CscOptionsParser.cs b/src/Buildalyzer/Compiler/CscOptionsParser.cs new file mode 100644 index 00000000..54659c16 --- /dev/null +++ b/src/Buildalyzer/Compiler/CscOptionsParser.cs @@ -0,0 +1,49 @@ +using System.IO; +using Buildalyzer.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Buildalyzer; + +/// +/// A parser for the csc compiler options (Roslyn). +/// +public sealed class CscOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static CscOptionsParser Instance { get; } = new CscOptionsParser(); + + public string Language => "C#"; + + private CscOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs is TaskCommandLineEventArgs cmd + && string.Equals(cmd.TaskName, "Csc", StringComparison.OrdinalIgnoreCase); + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + if (string.IsNullOrWhiteSpace(commandLine) || (!context.IsFirstInvocation && !context.CoreCompile)) + { + return null; + } + + var tokens = Buildalyzer.RoslynParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe") + ?? throw new FormatException("Commandline could not be parsed."); + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var arguments = CSharpCommandLineParser.Default.Parse(args, context.BaseDirectory?.ToString(), location.Directory?.ToString()); + var command = new CSharpCompilerCommand() + { + CommandLineArguments = arguments, + }; + return RoslynParser.Enrich(command, arguments); + } +} diff --git a/src/Buildalyzer/Compiler/FSharpCommandLineParser.cs b/src/Buildalyzer/Compiler/FSharpParser.cs similarity index 97% rename from src/Buildalyzer/Compiler/FSharpCommandLineParser.cs rename to src/Buildalyzer/Compiler/FSharpParser.cs index 54b913bb..a8444a75 100644 --- a/src/Buildalyzer/Compiler/FSharpCommandLineParser.cs +++ b/src/Buildalyzer/Compiler/FSharpParser.cs @@ -2,7 +2,7 @@ namespace Buildalyzer; -internal static class FSharpCommandLineParser +internal static class FSharpParser { [Pure] public static string[]? SplitCommandLineIntoArguments(string? commandLine) diff --git a/src/Buildalyzer/Compiler/FscOptionsParser.cs b/src/Buildalyzer/Compiler/FscOptionsParser.cs new file mode 100644 index 00000000..f27bd42c --- /dev/null +++ b/src/Buildalyzer/Compiler/FscOptionsParser.cs @@ -0,0 +1,49 @@ +using System.IO; +using Buildalyzer.IO; +using Microsoft.Build.Framework; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace Buildalyzer; + +/// +/// A parser for the fsc compiler options (F#). +/// +public sealed class FscOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static FscOptionsParser Instance { get; } = new FscOptionsParser(); + + public string Language => "F#"; + + private FscOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs.SenderName?.Equals("Fsc", StringComparison.OrdinalIgnoreCase) == true + && !string.IsNullOrWhiteSpace(eventArgs.Message) + && context.TargetStack.Any(x => x.TargetName == "CoreCompile") + && context.IsFirstInvocation; + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + var tokens = FSharpParser.SplitCommandLineIntoArguments(commandLine) + ?? throw new FormatException("Commandline could not be parsed."); + + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var sourceFiles = args.Where(a => a[0] != '-').Select(IOPath.Parse); + var preprocessorSymbolNames = args.Where(a => a.StartsWith("--define:")).Select(a => a[9..]); + var metadataReferences = args.Where(a => a.StartsWith("-r:")).Select(a => a[3..]); + + return new FSharpCompilerCommand() + { + MetadataReferences = metadataReferences.ToImmutableArray(), + PreprocessorSymbolNames = preprocessorSymbolNames.ToImmutableArray(), + SourceFiles = sourceFiles.ToImmutableArray(), + }; + } +} diff --git a/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs index ec05f5de..a5d0a358 100644 --- a/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs +++ b/src/Buildalyzer/Compiler/ICompilerOptionsParser.cs @@ -40,7 +40,7 @@ public readonly struct CompilerOptionsContext /// True, if this is the first compiler invocation. /// False, if one is already found. /// - public bool IsFirst { get; init; } + public bool IsFirstInvocation { get; init; } /// /// True, if this is a call inside CoreCompile. @@ -51,4 +51,9 @@ public readonly struct CompilerOptionsContext /// The base directory of the project. /// public DirectoryInfo? BaseDirectory { get; init; } + + /// + /// The target stack. + /// + public IReadOnlyCollection TargetStack { get; init; } } diff --git a/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs b/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs deleted file mode 100644 index dbbed201..00000000 --- a/src/Buildalyzer/Compiler/RoslynCommandLineParser.cs +++ /dev/null @@ -1,28 +0,0 @@ -#nullable enable - -using Microsoft.CodeAnalysis; - -namespace Buildalyzer; - -internal static class RoslynCommandLineParser -{ - [Pure] - public static string[]? SplitCommandLineIntoArguments(string? commandLine, params string[] execs) - => Split(CommandLineParser.SplitCommandLineIntoArguments(commandLine ?? string.Empty, removeHashComments: true).ToArray(), execs); - - [Pure] - private static string[]? Split(string[] args, string[] execs) - { - foreach (var exec in execs) - { - for (var i = 0; i < args.Length - 1; i++) - { - if (args[i].EndsWith(exec, StringComparison.OrdinalIgnoreCase)) - { - return args[i..]; - } - } - } - return null; - } -} diff --git a/src/Buildalyzer/Compiler/RoslynParser.cs b/src/Buildalyzer/Compiler/RoslynParser.cs new file mode 100644 index 00000000..98de04d2 --- /dev/null +++ b/src/Buildalyzer/Compiler/RoslynParser.cs @@ -0,0 +1,49 @@ +#nullable enable + +using Buildalyzer.IO; +using Microsoft.CodeAnalysis; + +namespace Buildalyzer; + +internal static class RoslynParser +{ + public static TCommand Enrich(TCommand command, CommandLineArguments arguments) + where TCommand : CompilerCommand + => command with + { + AnalyzerReferences = arguments.AnalyzerReferences.Select(AsIOPath).ToImmutableArray(), + AnalyzerConfigPaths = arguments.AnalyzerConfigPaths.Select(IOPath.Parse).ToImmutableArray(), + MetadataReferences = arguments.MetadataReferences.Select(m => m.Reference).ToImmutableArray(), + PreprocessorSymbolNames = arguments.ParseOptions.PreprocessorSymbolNames.ToImmutableArray(), + + SourceFiles = arguments.SourceFiles.Select(AsIOPath).ToImmutableArray(), + AdditionalFiles = arguments.AdditionalFiles.Select(AsIOPath).ToImmutableArray(), + EmbeddedFiles = arguments.EmbeddedFiles.Select(AsIOPath).ToImmutableArray(), + }; + + [Pure] + internal static IOPath AsIOPath(CommandLineAnalyzerReference file) => IOPath.Parse(file.FilePath); + + [Pure] + internal static IOPath AsIOPath(CommandLineSourceFile file) => IOPath.Parse(file.Path); + + [Pure] + public static string[]? SplitCommandLineIntoArguments(string? commandLine, params string[] execs) + => Split(CommandLineParser.SplitCommandLineIntoArguments(commandLine ?? string.Empty, removeHashComments: true).ToArray(), execs); + + [Pure] + private static string[]? Split(string[] args, string[] execs) + { + foreach (var exec in execs) + { + for (var i = 0; i < args.Length - 1; i++) + { + if (args[i].EndsWith(exec, StringComparison.OrdinalIgnoreCase)) + { + return args[i..]; + } + } + } + return null; + } +} diff --git a/src/Buildalyzer/Compiler/VbcOptionsParser.cs b/src/Buildalyzer/Compiler/VbcOptionsParser.cs new file mode 100644 index 00000000..28476a43 --- /dev/null +++ b/src/Buildalyzer/Compiler/VbcOptionsParser.cs @@ -0,0 +1,44 @@ +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Tasks; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.VisualBasic; + +namespace Buildalyzer; + +/// +/// A parser for the vbc compiler options (Roslyn). +/// +public sealed class VbcOptionsParser : ICompilerOptionsParser +{ + /// + /// A singleton instance of the parser. + /// + public static VbcOptionsParser Instance { get; } = new VbcOptionsParser(); + + public string Language => "VB.NET"; + + private VbcOptionsParser() + { + } + + public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs, CompilerOptionsContext context) => + eventArgs is TaskCommandLineEventArgs cmd + && string.Equals(cmd.TaskName, "Vbc", StringComparison.OrdinalIgnoreCase); + + public CompilerCommand? Parse(string commandLine, CompilerOptionsContext context) + { + var tokens = RoslynParser.SplitCommandLineIntoArguments(commandLine, "vbc.dll", "vbc.exe") + ?? throw new FormatException("Commandline could not be parsed."); + var location = new FileInfo(tokens[0]); + var args = tokens[1..]; + + var arguments = VisualBasicCommandLineParser.Default.Parse(args, context.BaseDirectory?.ToString(), location.Directory?.ToString()); + var command = new VisualBasicCompilerCommand() + { + CommandLineArguments = arguments, + PreprocessorSymbols = arguments.ParseOptions.PreprocessorSymbols.ToImmutableDictionary(), + }; + return RoslynParser.Enrich(command, arguments); + } +} diff --git a/src/Buildalyzer/Logging/EventProcessor.cs b/src/Buildalyzer/Logging/EventProcessor.cs index 6b3e7f2d..67174d96 100644 --- a/src/Buildalyzer/Logging/EventProcessor.cs +++ b/src/Buildalyzer/Logging/EventProcessor.cs @@ -152,9 +152,10 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) { CompilerOptionsContext context = new() { - IsFirst = result.CompilerCommand is null, + IsFirstInvocation = result.CompilerCommand is null, CoreCompile = _targetStack.Any(x => x.TargetName == "CoreCompile"), BaseDirectory = new FileInfo(result.ProjectFilePath).Directory, + TargetStack = _targetStack, }; foreach (ICompilerOptionsParser parser in _manager.CompilerOptionsParsers) diff --git a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs index 0e882f4a..3153fa46 100644 --- a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs +++ b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs @@ -141,7 +141,7 @@ public void Parse_FSharp() static FSharpParsingOptions GetFSharpParsingOptions(string commandLine) { var checker = FSharpChecker.Instance; - var result = checker.GetParsingOptionsFromCommandLineArgs(ListModule.OfArray(FSharpCommandLineParser.SplitCommandLineIntoArguments(commandLine)), isInteractive: true, isEditing: false); + var result = checker.GetParsingOptionsFromCommandLineArgs(ListModule.OfArray(FSharpParser.SplitCommandLineIntoArguments(commandLine)), isInteractive: true, isEditing: false); return result.Item1; } } From 2620f452a27e45c789e4bb9a021bc23ab35d6019 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 15:18:29 +0200 Subject: [PATCH 5/8] Tests compile too --- .../Compiler/CompilerCommandFixture.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs index 3153fa46..fe254f96 100644 --- a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs +++ b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs @@ -39,12 +39,15 @@ public void Parse_CS() + "Startup.cs " + "/warnaserror+:NU1605"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandline, CompilerLanguage.CSharp); + var command = CscOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext + { + BaseDirectory = new(".") + }); command.Should().BeEquivalentTo(new { Text = commandline, - Language = CompilerLanguage.CSharp, + Language = "C#", PreprocessorSymbolNames = new[] { "TRACE", "DEBUG", "NETCOREAPP", "NETCOREAPP3_1", "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP2_1_OR_GREATER", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP3_1_OR_GREATER" }, SourceFiles = Files(".\\Program.cs", ".\\Startup.cs"), AnalyzerConfigPaths = Files(".\\code\\buildalyzer\\.editorconfig", ".\\code\\buildalyzer\\tests\\.editorconfig"), @@ -67,12 +70,15 @@ public void Parse_VB() + "\"obj\\Debug\\net6.0\\VisualBasicNetConsoleApp.AssemblyInfo.vb\" " + "/warnaserror+:NU1605"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandline, CompilerLanguage.VisualBasic); + var command = VbcOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext + { + BaseDirectory = new("."), + }); command.Should().BeEquivalentTo(new { Text = commandline, - Language = CompilerLanguage.VisualBasic, + Language = "VB.NET", PreprocessorSymbolNames = new[] { "TRACE", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP1_0_OR_GREATER", "NET6_0", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "_MyType", "NETCOREAPP", "NETCOREAPP2_1_OR_GREATER", "NET6_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "CONFIG", "NET", "PLATFORM", "NETCOREAPP3_1_OR_GREATER", "DEBUG", "NET5_0_OR_GREATER", "VBC_VER", "TARGET" }, SourceFiles = Files(".\\Configuration.vb", ".\\Program.vb", ".\\obj\\Debug\\net6.0\\.NETCoreApp,Version=v6.0.AssemblyAttributes.vb", ".\\obj\\Debug\\net6.0\\VisualBasicNetConsoleApp.AssemblyInfo.vb"), AnalyzerReferences = Files("C:\\Program Files\\dotnet\\sdk\\8.0.200\\Sdks\\Microsoft.NET.Sdk\\targets\\..\\analyzers\\Microsoft.CodeAnalysis.VisualBasic.NetAnalyzers.dll", "C:\\Program Files\\dotnet\\sdk\\8.0.200\\Sdks\\Microsoft.NET.Sdk\\targets\\..\\analyzers\\Microsoft.CodeAnalysis.NetAnalyzers.dll"), @@ -121,13 +127,16 @@ public void Parse_FSharp() obj\Debug\netcoreapp3.1\FSharpProject.AssemblyInfo.fs Program.fs"; - var command = Buildalyzer.Compiler.CommandLine.Parse(new("."), commandLine, CompilerLanguage.FSharp); + var command = FscOptionsParser.Instance.Parse(commandLine, new CompilerOptionsContext + { + BaseDirectory = new("."), + }); var options = GetFSharpParsingOptions(commandLine); command.Should().BeEquivalentTo(new { Text = commandLine, - Language = CompilerLanguage.FSharp, + Language = "F#", PreprocessorSymbolNames = new[] { "NETCOREAPP3_1_OR_GREATER", "NETCOREAPP3_0_OR_GREATER", "NETCOREAPP2_2_OR_GREATER", "NETCOREAPP2_1_OR_GREATER", "NETCOREAPP2_0_OR_GREATER", "NETCOREAPP1_1_OR_GREATER", "NETCOREAPP1_0_OR_GREATER", "NETCOREAPP3_1", "NETCOREAPP", "DEBUG", "TRACE" }, SourceFiles = Files("obj\\Debug\\netcoreapp3.1\\.NETCoreApp,Version=v3.1.AssemblyAttributes.fs", "obj\\Debug\\netcoreapp3.1\\FSharpProject.AssemblyInfo.fs", "Program.fs"), MetadataReferences = Array("C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.CSharp.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.VisualBasic.Core.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.VisualBasic.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\Microsoft.Win32.Primitives.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\mscorlib.dll", "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\3.1.0\\ref\\netcoreapp3.1\\netstandard.dll"), From a8619fdfa3158d02e357c8bf577b9cd013829143 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 15:32:08 +0200 Subject: [PATCH 6/8] Update CompilerCommandFixture.cs --- tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs index fe254f96..1a9154ae 100644 --- a/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs +++ b/tests/Buildalyzer.Tests/Compiler/CompilerCommandFixture.cs @@ -41,7 +41,8 @@ public void Parse_CS() var command = CscOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext { - BaseDirectory = new(".") + BaseDirectory = new("."), + IsFirstInvocation = true, }); command.Should().BeEquivalentTo(new @@ -73,6 +74,7 @@ public void Parse_VB() var command = VbcOptionsParser.Instance.Parse(commandline, new CompilerOptionsContext { BaseDirectory = new("."), + IsFirstInvocation = true, }); command.Should().BeEquivalentTo(new @@ -130,6 +132,7 @@ public void Parse_FSharp() var command = FscOptionsParser.Instance.Parse(commandLine, new CompilerOptionsContext { BaseDirectory = new("."), + IsFirstInvocation = true, }); var options = GetFSharpParsingOptions(commandLine); From 3f15cddcbfcb8d680c77d39725f2acbd447a72b4 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 15:37:45 +0200 Subject: [PATCH 7/8] Update CscOptionsParser.cs --- src/Buildalyzer/Compiler/CscOptionsParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Buildalyzer/Compiler/CscOptionsParser.cs b/src/Buildalyzer/Compiler/CscOptionsParser.cs index 54659c16..83615621 100644 --- a/src/Buildalyzer/Compiler/CscOptionsParser.cs +++ b/src/Buildalyzer/Compiler/CscOptionsParser.cs @@ -34,7 +34,7 @@ eventArgs is TaskCommandLineEventArgs cmd return null; } - var tokens = Buildalyzer.RoslynParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe") + var tokens = RoslynParser.SplitCommandLineIntoArguments(commandLine, "csc.dll", "csc.exe") ?? throw new FormatException("Commandline could not be parsed."); var location = new FileInfo(tokens[0]); var args = tokens[1..]; From 3c8837606b664606aaec539bf7ed1afc4dd0b42b Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Fri, 12 Jul 2024 15:40:41 +0200 Subject: [PATCH 8/8] Fix --- src/Buildalyzer/Compiler/CscOptionsParser.cs | 3 +++ src/Buildalyzer/Compiler/FscOptionsParser.cs | 3 +++ src/Buildalyzer/Compiler/VbcOptionsParser.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/Buildalyzer/Compiler/CscOptionsParser.cs b/src/Buildalyzer/Compiler/CscOptionsParser.cs index 83615621..49835bad 100644 --- a/src/Buildalyzer/Compiler/CscOptionsParser.cs +++ b/src/Buildalyzer/Compiler/CscOptionsParser.cs @@ -43,6 +43,9 @@ eventArgs is TaskCommandLineEventArgs cmd var command = new CSharpCompilerCommand() { CommandLineArguments = arguments, + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), }; return RoslynParser.Enrich(command, arguments); } diff --git a/src/Buildalyzer/Compiler/FscOptionsParser.cs b/src/Buildalyzer/Compiler/FscOptionsParser.cs index f27bd42c..268a0437 100644 --- a/src/Buildalyzer/Compiler/FscOptionsParser.cs +++ b/src/Buildalyzer/Compiler/FscOptionsParser.cs @@ -44,6 +44,9 @@ public bool IsSupportedInvocation(object sender, BuildMessageEventArgs eventArgs MetadataReferences = metadataReferences.ToImmutableArray(), PreprocessorSymbolNames = preprocessorSymbolNames.ToImmutableArray(), SourceFiles = sourceFiles.ToImmutableArray(), + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), }; } } diff --git a/src/Buildalyzer/Compiler/VbcOptionsParser.cs b/src/Buildalyzer/Compiler/VbcOptionsParser.cs index 28476a43..e5a4fa7d 100644 --- a/src/Buildalyzer/Compiler/VbcOptionsParser.cs +++ b/src/Buildalyzer/Compiler/VbcOptionsParser.cs @@ -38,6 +38,9 @@ eventArgs is TaskCommandLineEventArgs cmd { CommandLineArguments = arguments, PreprocessorSymbols = arguments.ParseOptions.PreprocessorSymbols.ToImmutableDictionary(), + Text = commandLine, + CompilerLocation = location, + Arguments = args.ToImmutableArray(), }; return RoslynParser.Enrich(command, arguments); }