diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index c194f4bda..2a26480e1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -11,7 +11,7 @@ on: description: Deploy components of the REPL (Compiler, Chr, Repl) deploy-coverage-tool: type: boolean - description: Deploy the coverage tool (Coverage, Coverage.MSBuild) + description: Deploy the coverage tool (Coverage, Coverage.Toolset) deploy-langserver: type: boolean description: Deploy the language server (Compiler, Chr, LanguageServer, Lsp) @@ -66,7 +66,7 @@ jobs: $sdkProjects = "Compiler", "Chr", "Compiler.Toolset", "Sdk", "ProjectTemplates" $replProjects = "Compiler", "Chr", "Repl" - $coverageToolProjects = "Coverage", "Coverage.MSBuild" + $coverageToolProjects = "Coverage", "Coverage.Toolset" $langserverProjects = "Compiler", "Chr", "LanguageServer", "Lsp", "JsonRpc" $debugadapterProjects = "DebugAdapter", "Dap", "JsonRpc" diff --git a/src/Draco.Coverage.Cli/Draco.Coverage.Cli.csproj b/src/Draco.Coverage.Cli/Draco.Coverage.Cli.csproj new file mode 100644 index 000000000..932346cba --- /dev/null +++ b/src/Draco.Coverage.Cli/Draco.Coverage.Cli.csproj @@ -0,0 +1,11 @@ + + + + Exe + + + + + + + diff --git a/src/Draco.Coverage.Cli/Program.cs b/src/Draco.Coverage.Cli/Program.cs new file mode 100644 index 000000000..8dd616ea4 --- /dev/null +++ b/src/Draco.Coverage.Cli/Program.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Linq; + +namespace Draco.Coverage.Cli; + +internal static class Program +{ + private static int Main(string[] args) + { + args = ProcessArgs(args); + + if (args.Length != 2) + { + Log("Usage: Draco.Coverage.Cli "); + Log($"Provided argument(s): {string.Join(", ", args)}"); + return 1; + } + + var inputPaths = args[0]; + var outputPaths = args[1]; + + if (string.IsNullOrEmpty(inputPaths) && string.IsNullOrEmpty(outputPaths)) + { + // Ok, no inputs our outputs + return 0; + } + + var inputPathArray = ProcessPath(inputPaths); + var outputPathArray = ProcessPath(outputPaths); + + if (inputPathArray.Length != outputPathArray.Length) + { + Log("Input and output paths must have the same number of elements"); + return 1; + } + + foreach (var (inputPath, outputPath) in inputPathArray.Zip(outputPathArray)) + { + if (!Path.Exists(inputPath)) + { + Log($"Input path '{inputPath}' does not exist"); + return 1; + } + if (!Path.Exists(outputPath)) + { + Log($"Output path '{outputPath}' does not exist"); + return 1; + } + + InstrumentedAssembly.Weave(inputPath, outputPath); + } + + return 0; + } + + private static void Log(string message) => Console.Error.WriteLine(message); + + // MSBuild passes in the path to all binaries copied, concatenated with a semocilon + // We need to split them and filter for .dll files + private static string[] ProcessPath(string path) => path + .Split(';') + .Where(p => !string.IsNullOrWhiteSpace(p)) + .Where(p => Path.GetExtension(p) == ".dll") + .Select(Path.GetFullPath) + .ToArray(); + + private static string[] ProcessArgs(string[] args) + { + if (args.Length != 1) return args; + if (!args[0].StartsWith('@')) return args; + + // RSP file + var file = args[0][1..]; + return File.ReadAllLines(file); + } +} diff --git a/src/Draco.Coverage.MSBuild/CoverageWeaveTask.cs b/src/Draco.Coverage.MSBuild/CoverageWeaveTask.cs deleted file mode 100644 index 4b90917dc..000000000 --- a/src/Draco.Coverage.MSBuild/CoverageWeaveTask.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace Draco.Coverage.MSBuild; - -public sealed class CoverageWeaveTask : Task -{ - public string InputPath { get; set; } - public string OutputPath { get; set; } - - public override bool Execute() - { - if (string.IsNullOrWhiteSpace(this.InputPath) && string.IsNullOrWhiteSpace(this.OutputPath)) - { - return true; - } - - var inputPaths = ProcessPath(this.InputPath); - var outputPaths = ProcessPath(this.OutputPath); - if (inputPaths.Length != outputPaths.Length) - { - throw new ArgumentException("input and output paths must have the same number of elements"); - } - - for (var i = 0; i < inputPaths.Length; i++) - { - this.Weave(inputPaths[i], outputPaths[i]); - } - - return true; - } - - private void Weave(string inputPath, string outputPath) => InstrumentedAssembly.Weave(inputPath, outputPath); - - // MSBuild passes in the path to all binaries copied, concatenated with a semocilon - // We need to split them and filter for .dll files - private static string[] ProcessPath(string path) => path - .Split(';') - .Where(p => !string.IsNullOrWhiteSpace(p)) - .Where(p => Path.GetExtension(p) == ".dll") - .ToArray(); -} diff --git a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.csproj b/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.csproj deleted file mode 100644 index 228ba297f..000000000 --- a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.csproj +++ /dev/null @@ -1,51 +0,0 @@ - - - - netstandard2.0 - true - disable - true - - $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage;CopyPublicReferencesToPackage - - tasks - NU5100 - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Draco.Coverage.Tasks/CoverageWeaveTask.cs b/src/Draco.Coverage.Tasks/CoverageWeaveTask.cs new file mode 100644 index 000000000..8e99781b0 --- /dev/null +++ b/src/Draco.Coverage.Tasks/CoverageWeaveTask.cs @@ -0,0 +1,87 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Draco.Coverage.Tasks; + +public sealed class CoverageWeaveTask : ToolTask +{ + public string WeaverPath { get; set; } + public string InputPaths { get; set; } + public string OutputPaths { get; set; } + + protected override string ToolName => Path.GetFileName(GetDotNetPath()); + + private int errorCount = 0; + + protected override string GenerateCommandLineCommands() => $"exec \"{this.WeaverPath}\""; + + protected override string GenerateResponseFileCommands() + { + var sb = new StringBuilder(); + sb.AppendLine(this.InputPaths); + sb.AppendLine(this.OutputPaths); + return sb.ToString(); + } + + protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) + { + this.errorCount++; + base.LogEventsFromTextOutput(singleLine, messageImportance); + } + + protected override bool HandleTaskExecutionErrors() + { + if (this.errorCount == 0) + { + var message = "Internal weaver error. Please open an issue with a repro case at https://github.com/Draco-lang/Compiler/issues"; + this.Log.LogCriticalMessage( + subcategory: null, code: "DRC0001", helpKeyword: null, + file: null, + lineNumber: 0, columnNumber: 0, + endLineNumber: 0, endColumnNumber: 0, + message: message.ToString()); + } + + return false; + } + + private const string DotNetHostPathEnvironmentName = "DOTNET_HOST_PATH"; + + // https://github.com/dotnet/roslyn/blob/020db28fa9b744146e6f072dbdc6bf3e62c901c1/src/Compilers/Shared/RuntimeHostInfo.cs#L59 + private static string GetDotNetPath() + { + if (Environment.GetEnvironmentVariable(DotNetHostPathEnvironmentName) is string pathToDotNet) + { + return pathToDotNet; + } + + var (fileName, sep) = Environment.OSVersion.Platform == PlatformID.Win32NT + ? ("dotnet.exe", ';') + : ("dotnet", ':'); + + var path = Environment.GetEnvironmentVariable("PATH") ?? ""; + foreach (var item in path.Split(new[] { sep }, StringSplitOptions.RemoveEmptyEntries)) + { + try + { + var filePath = Path.Combine(item, fileName); + if (File.Exists(filePath)) + { + return filePath; + } + } + catch + { + // If we can't read a directory for any reason just skip it + } + } + + return fileName; + } + + protected override string GenerateFullPathToTool() => Path.GetFullPath(GetDotNetPath()); +} diff --git a/src/Draco.Coverage.Tasks/Draco.Coverage.Tasks.csproj b/src/Draco.Coverage.Tasks/Draco.Coverage.Tasks.csproj new file mode 100644 index 000000000..36af79cbd --- /dev/null +++ b/src/Draco.Coverage.Tasks/Draco.Coverage.Tasks.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + false + disable + + + + + + + diff --git a/src/Draco.Coverage.Toolset/Draco.Coverage.Toolset.csproj b/src/Draco.Coverage.Toolset/Draco.Coverage.Toolset.csproj new file mode 100644 index 000000000..ca937a524 --- /dev/null +++ b/src/Draco.Coverage.Toolset/Draco.Coverage.Toolset.csproj @@ -0,0 +1,40 @@ + + + + true + true + false + $(NoWarn);NU5100 + + + + + + + + + + + <_File Include="$(OutDir)\**\*.*" PackagePath="tools" Exclude="$(OutDir)\tasks\**\*.*;$(OutDir)\lib\**\*.*" /> + <_File Include="$(OutDir)\tasks\**\*.*" PackagePath="tasks" /> + <_File Include="$(OutDir)\lib\**\*.*" PackagePath="lib\$(TargetFramework)" /> + <_File Include="$(OutDir)\lib\**\*.*" PackagePath="tools" /> + + <_File Include="build\**\*.*" PackagePath="build" /> + + + + + + + + + + + + false + false + false + + + diff --git a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.props b/src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.props similarity index 55% rename from src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.props rename to src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.props index bca515e6d..b45ce60f9 100644 --- a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.props +++ b/src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.props @@ -1,5 +1,5 @@ - + diff --git a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.targets b/src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.targets similarity index 58% rename from src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.targets rename to src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.targets index b8c78e655..0e9f6f7a3 100644 --- a/src/Draco.Coverage.MSBuild/Draco.Coverage.MSBuild.targets +++ b/src/Draco.Coverage.Toolset/build/Draco.Coverage.Toolset.targets @@ -10,8 +10,11 @@ + InputPaths="@(ReferencesWithWeave->'$(OutDir)%(Filename)%(Extension)')" + OutputPaths="@(ReferencesWithWeave->'$(OutDir)%(Filename)%(Extension)')" + WeaverPath="$(MSBuildThisFileDirectory)..\tools\Draco.Coverage.Cli.dll" + ToolPath="$(DotNetHostDirectory)" + ToolExe="$(DotNetHostFileName)"/> diff --git a/src/Draco.Coverage/Draco.Coverage.csproj b/src/Draco.Coverage/Draco.Coverage.csproj index 41ea0aaf7..1baf5bf81 100644 --- a/src/Draco.Coverage/Draco.Coverage.csproj +++ b/src/Draco.Coverage/Draco.Coverage.csproj @@ -1,8 +1,4 @@ - - - netstandard2.0 - diff --git a/src/Draco.sln b/src/Draco.sln index 7ece726ac..6596b2bc6 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -57,7 +57,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Examples.Tests", "Dra EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Coverage", "Draco.Coverage\Draco.Coverage.csproj", "{2497B426-9005-4290-A7BB-ED40E7E56218}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Coverage.MSBuild", "Draco.Coverage.MSBuild\Draco.Coverage.MSBuild.csproj", "{C819FDF2-ABC9-4083-979D-6C898B5E7D2F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Coverage.Toolset", "Draco.Coverage.Toolset\Draco.Coverage.Toolset.csproj", "{C819FDF2-ABC9-4083-979D-6C898B5E7D2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Coverage.Cli", "Draco.Coverage.Cli\Draco.Coverage.Cli.csproj", "{DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.Coverage.Tasks", "Draco.Coverage.Tasks\Draco.Coverage.Tasks.csproj", "{332431F9-6E09-429F-8459-2D157BE030A0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -216,6 +220,18 @@ Global {C819FDF2-ABC9-4083-979D-6C898B5E7D2F}.Nuget|Any CPU.Build.0 = Debug|Any CPU {C819FDF2-ABC9-4083-979D-6C898B5E7D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {C819FDF2-ABC9-4083-979D-6C898B5E7D2F}.Release|Any CPU.Build.0 = Release|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC5CD488-4DFC-4B18-831A-4CBD45AE4B5E}.Release|Any CPU.Build.0 = Release|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {332431F9-6E09-429F-8459-2D157BE030A0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE