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