diff --git a/dotnet-monitor.sln b/dotnet-monitor.sln
index b2baf1b022f..205fd485f16 100644
--- a/dotnet-monitor.sln
+++ b/dotnet-monitor.sln
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monit
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.ExecuteActionApp", "src\Tests\Microsoft.Diagnostics.Monitoring.ExecuteActionApp\Microsoft.Diagnostics.Monitoring.ExecuteActionApp.csproj", "{A5A0CAAB-C200-44D2-BC93-8445C6E748AD}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp", "src\Tests\Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp\Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp.csproj", "{8f8a9a15-24d5-496c-b769-3caed25d1ba8}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureBlobStorage", "src\Extensions\AzureBlobStorage\AzureBlobStorage.csproj", "{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Profiler.UnitTests", "src\Tests\Microsoft.Diagnostics.Monitoring.Profiler.UnitTests\Microsoft.Diagnostics.Monitoring.Profiler.UnitTests.csproj", "{A25AC517-F7C6-43C6-B892-4A447914C42C}"
@@ -108,6 +110,10 @@ Global
{A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5A0CAAB-C200-44D2-BC93-8445C6E748AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8f8a9a15-24d5-496c-b769-3caed25d1ba8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8f8a9a15-24d5-496c-b769-3caed25d1ba8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8f8a9a15-24d5-496c-b769-3caed25d1ba8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8f8a9a15-24d5-496c-b769-3caed25d1ba8}.Release|Any CPU.Build.0 = Release|Any CPU
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -148,6 +154,7 @@ Global
{173F959B-231B-45D1-8328-9460D4C5BC71} = {19FAB78C-3351-4911-8F0C-8C6056401740}
{0DBE362D-82F1-4740-AE6A-40C1A82EDCDB} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{A5A0CAAB-C200-44D2-BC93-8445C6E748AD} = {C7568468-1C79-4944-8136-18812A7F9EA7}
+ {8f8a9a15-24d5-496c-b769-3caed25d1ba8} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{A25AC517-F7C6-43C6-B892-4A447914C42C} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{1CA2284B-A3A0-476A-9A93-A95E665E78BE} = {C7568468-1C79-4944-8136-18812A7F9EA7}
@@ -158,3 +165,4 @@ Global
SolutionGuid = {46465737-C938-44FC-BE1A-4CE139EBB5E0}
EndGlobalSection
EndGlobal
+
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp.csproj
new file mode 100644
index 00000000000..f25e58f42db
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ $(ToolTargetFrameworks)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Program.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Program.cs
new file mode 100644
index 00000000000..61258d2fd5b
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/Program.cs
@@ -0,0 +1,83 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.Tool.UnitTests;
+using Microsoft.Diagnostics.Tools.Monitor.Egress;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.Text.Json;
+
+namespace Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp
+{
+ internal class Program
+ {
+ static int Main(string[] args)
+ {
+ RootCommand rootCommand = new RootCommand();
+
+ Command egressCmd = new Command("Egress");
+
+ egressCmd.SetHandler(() => Egress());
+
+ rootCommand.Add(egressCmd);
+
+ return rootCommand.Invoke(args);
+ }
+
+ private static int Egress()
+ {
+ EgressArtifactResult result = new();
+ try
+ {
+ string jsonConfig = Console.ReadLine();
+
+ ExtensionEgressPayload configPayload = JsonSerializer.Deserialize(jsonConfig);
+ TestEgressProviderOptions options = BuildOptions(configPayload);
+
+ if (options.ShouldSucceed)
+ {
+ result.Succeeded = true;
+ result.ArtifactPath = EgressExtensibilityTests.SampleArtifactPath;
+ }
+ else
+ {
+ result.Succeeded = false;
+ result.FailureMessage = EgressExtensibilityTests.SampleFailureMessage;
+ }
+ }
+ catch (Exception ex)
+ {
+ result.Succeeded = false;
+ result.FailureMessage = ex.Message;
+ }
+
+ string jsonBlob = JsonSerializer.Serialize(result);
+ Console.Write(jsonBlob);
+
+ // return non-zero exit code when failed
+ return result.Succeeded ? 0 : 1;
+ }
+
+ private static TestEgressProviderOptions BuildOptions(ExtensionEgressPayload configPayload)
+ {
+ TestEgressProviderOptions options = new TestEgressProviderOptions()
+ {
+ ShouldSucceed = GetConfig(configPayload.Configuration, nameof(TestEgressProviderOptions.ShouldSucceed)),
+ };
+
+ return options;
+ }
+
+ private static bool GetConfig(IDictionary configDict, string propKey)
+ {
+ if (configDict.ContainsKey(propKey))
+ {
+ return bool.Parse(configDict[propKey]);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/TestEgressProviderOptions.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/TestEgressProviderOptions.cs
new file mode 100644
index 00000000000..1d5c1068d66
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/TestEgressProviderOptions.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp
+{
+ internal sealed partial class TestEgressProviderOptions
+ {
+ public bool ShouldSucceed { get; set; }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/extension.json b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/extension.json
new file mode 100644
index 00000000000..3dd069032b3
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp/extension.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp",
+ "Version": "1.0.0",
+ "Program": "Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp.exe",
+ "Name": "TestingProvider",
+ "SupportedExtensionTypes": [
+ "Egress"
+ ]
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
index 7322f0c0bbf..bbe4e328f20 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Runners/MonitorRunner.cs
@@ -81,6 +81,12 @@ internal class MonitorRunner : IAsyncDisposable
private string UserConfigDirectoryPath =>
Path.Combine(TempPath, "UserConfig");
+ private string DotnetToolsExtensionDirectoryPath =>
+ Path.Combine(TempPath, "DotnetToolsExtension");
+
+ private string ExecutingAssemblyDirectoryPath =>
+ Path.Combine(TempPath, "ExecutingAssembly");
+
private string UserSettingsFilePath =>
Path.Combine(UserConfigDirectoryPath, "settings.json");
@@ -96,6 +102,8 @@ public MonitorRunner(ITestOutputHelper outputHelper)
Directory.CreateDirectory(SharedConfigDirectoryPath);
Directory.CreateDirectory(UserConfigDirectoryPath);
+ Directory.CreateDirectory(DotnetToolsExtensionDirectoryPath);
+ Directory.CreateDirectory(ExecutingAssemblyDirectoryPath);
}
public virtual async ValueTask DisposeAsync()
@@ -154,6 +162,10 @@ public virtual async Task StartAsync(string command, string[] args, Cancellation
_adapter.Environment.Add("DotnetMonitorTestSettings__SharedConfigDirectoryOverride", SharedConfigDirectoryPath);
// Override the user config directory
_adapter.Environment.Add("DotnetMonitorTestSettings__UserConfigDirectoryOverride", UserConfigDirectoryPath);
+ // Override the dotnet tools extension directory
+ _adapter.Environment.Add("DotnetMonitorTestSettings__DotnetToolsExtensionDirectoryOverride", DotnetToolsExtensionDirectoryPath);
+ // Override the "next to me" executing assembly directory
+ _adapter.Environment.Add("DotnetMonitorTestSettings__ExecutingAssemblyDirectoryOverride", ExecutingAssemblyDirectoryPath);
// Enable experimental stacks feature
if (EnableCallStacksFeature)
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EgressExtensibilityTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EgressExtensibilityTests.cs
new file mode 100644
index 00000000000..0273fe8596d
--- /dev/null
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/EgressExtensibilityTests.cs
@@ -0,0 +1,212 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.Monitoring.TestCommon;
+using Microsoft.Diagnostics.Tools.Monitor;
+using Microsoft.Diagnostics.Tools.Monitor.Egress;
+using Microsoft.Diagnostics.Tools.Monitor.Extensibility;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.Monitoring.Tool.UnitTests
+{
+ public sealed class EgressExtensibilityTests
+ {
+ // TODO: Use CommonTestTimeouts.GeneralTimeout
+ private static readonly TimeSpan DefaultTimeout = TimeSpan.FromMinutes(3);
+
+ private ITestOutputHelper _outputHelper;
+
+ private const string ExtensionsFolder = "extensions";
+ public const string SampleArtifactPath = "sample\\path";
+ public const string SampleFailureMessage = "the extension failed";
+ private const string ProviderName = "TestingProvider"; // Must match the name in extension.json
+ private const string AppName = "Microsoft.Diagnostics.Monitoring.EgressExtensibilityApp";
+ private const string DotnetToolsExtensionDir = ".store\\tool-name\\7.0\\tool-name\\7.0\\tools\\net7.0\\any"; // TODO: Don't have this be a fixed version
+ private const string DotnetToolsExeDir = "";
+ private readonly static byte[] ByteArray = Encoding.ASCII.GetBytes(string.Empty);
+
+ public EgressExtensibilityTests(ITestOutputHelper outputHelper)
+ {
+ _outputHelper = outputHelper;
+ }
+
+ [Fact]
+ public void FoundExtension_Failure()
+ {
+ HostBuilderSettings settings = CreateHostBuilderSettings();
+
+ IHost host = TestHostHelper.CreateHost(_outputHelper, rootOptions => { }, host => { }, settings: settings);
+
+ var extensionDiscoverer = host.Services.GetService();
+
+ Assert.Throws(() => extensionDiscoverer.FindExtension("InvalidProviderName"));
+ }
+
+ [Theory]
+ [InlineData(ConfigDirectory.ExecutingAssemblyDirectory, null)]
+ [InlineData(ConfigDirectory.UserConfigDirectory, null)]
+ [InlineData(ConfigDirectory.SharedConfigDirectory, null)]
+ [InlineData(ConfigDirectory.DotnetToolsExtensionDirectory, DotnetToolsExeDir)]
+ public void FoundExtensionFile_Success(ConfigDirectory configDirectory, string exePath)
+ {
+ IEgressExtension extension = FindEgressExtension(configDirectory, exePath);
+
+ Assert.NotNull(extension);
+ }
+
+ [Fact]
+ public async Task ExtensionResponse_Success()
+ {
+ EgressArtifactResult result = await GetExtensionResponse(true);
+
+ Assert.True(result.Succeeded);
+ Assert.Equal(SampleArtifactPath, result.ArtifactPath);
+ }
+
+ [Fact]
+ public async Task ExtensionResponse_Failure()
+ {
+ EgressArtifactResult result = await GetExtensionResponse(false);
+
+ Assert.False(result.Succeeded);
+ Assert.Equal(SampleFailureMessage, result.FailureMessage);
+ }
+
+ private async Task GetExtensionResponse(bool shouldSucceed)
+ {
+ var extension = FindEgressExtension(ConfigDirectory.UserConfigDirectory);
+
+ ExtensionEgressPayload payload = new();
+ payload.Configuration = new Dictionary
+ {
+ { "ShouldSucceed", shouldSucceed.ToString() }
+ };
+
+ CancellationTokenSource tokenSource = new(DefaultTimeout);
+
+ return await extension.EgressArtifact(payload, GetStream, tokenSource.Token);
+ }
+
+ private IEgressExtension FindEgressExtension(ConfigDirectory configDirectory, string exePath = null)
+ {
+ HostBuilderSettings settings = CreateHostBuilderSettings();
+
+ string directoryName = GetExtensionDirectoryName(settings, configDirectory);
+
+ string extensionDirPath = configDirectory != ConfigDirectory.DotnetToolsExtensionDirectory ? Path.Combine(directoryName, ExtensionsFolder, AppName) : Path.Combine(directoryName, DotnetToolsExtensionDir);
+
+ CopyExtensionFiles(extensionDirPath, exePath);
+
+ IHost host = TestHostHelper.CreateHost(_outputHelper, rootOptions => { }, host => { }, settings: settings);
+
+ var extensionDiscoverer = host.Services.GetService();
+
+ return extensionDiscoverer.FindExtension(ProviderName);
+ }
+
+ private static async Task GetStream(Stream stream, CancellationToken cancellationToken)
+ {
+ // The test extension currently does not do anything with this stream.
+ await stream.WriteAsync(ByteArray);
+ }
+
+ private static void CopyExtensionFiles(string extensionDirPath, string exePath = null)
+ {
+ Directory.CreateDirectory(extensionDirPath);
+
+ string testAppDirPath = Path.GetDirectoryName(AssemblyHelper.GetAssemblyArtifactBinPath(Assembly.GetExecutingAssembly(), AppName));
+
+ bool hasSeparateExe = !string.IsNullOrEmpty(exePath);
+
+ foreach (string testAppFilePath in Directory.GetFiles(testAppDirPath, "*.*", SearchOption.AllDirectories))
+ {
+ string extensionFilePath = string.Empty;
+
+ if (hasSeparateExe && IsExecutablePath(testAppFilePath))
+ {
+ Directory.CreateDirectory(exePath);
+ extensionFilePath = testAppFilePath.Replace(testAppDirPath, exePath);
+ }
+ else
+ {
+ extensionFilePath = testAppFilePath.Replace(testAppDirPath, extensionDirPath);
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(extensionFilePath));
+
+ File.Copy(testAppFilePath, extensionFilePath, true);
+ }
+ }
+
+ private static bool IsExecutablePath(string path)
+ {
+ if (Path.GetFileNameWithoutExtension(path) == AppName)
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Path.GetExtension(path) == ".exe")
+ {
+ return true;
+ }
+ else
+ {
+ return Path.GetExtension(path) == string.Empty;
+ }
+ }
+
+ return false;
+ }
+
+ private HostBuilderSettings CreateHostBuilderSettings()
+ {
+ using TemporaryDirectory executingAssemblyDir = new(_outputHelper);
+ using TemporaryDirectory sharedConfigDir = new(_outputHelper);
+ using TemporaryDirectory userConfigDir = new(_outputHelper);
+ using TemporaryDirectory dotnetToolsConfigDir = new(_outputHelper);
+
+ // Set up the initial settings used to create the host builder.
+ return new()
+ {
+ ExecutingAssemblyDirectory = executingAssemblyDir.FullName,
+ SharedConfigDirectory = sharedConfigDir.FullName,
+ UserConfigDirectory = userConfigDir.FullName,
+ DotnetToolsExtensionDirectory = dotnetToolsConfigDir.FullName
+ };
+ }
+
+ private string GetExtensionDirectoryName(HostBuilderSettings settings, ConfigDirectory configDirectory)
+ {
+ switch (configDirectory)
+ {
+ case ConfigDirectory.UserConfigDirectory:
+ return settings.UserConfigDirectory;
+ case ConfigDirectory.SharedConfigDirectory:
+ return settings.SharedConfigDirectory;
+ case ConfigDirectory.DotnetToolsExtensionDirectory:
+ return settings.DotnetToolsExtensionDirectory;
+ case ConfigDirectory.ExecutingAssemblyDirectory:
+ return settings.ExecutingAssemblyDirectory;
+ default:
+ throw new ArgumentException("configDirectory not found.");
+ }
+ }
+
+ public enum ConfigDirectory
+ {
+ ExecutingAssemblyDirectory,
+ SharedConfigDirectory,
+ UserConfigDirectory,
+ DotnetToolsExtensionDirectory
+ }
+ }
+}
diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs
index 1f8ef348e3d..618743be5a9 100644
--- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs
+++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.UnitTests/TestHostHelper.cs
@@ -62,7 +62,8 @@ public static IHost CreateHost(
Action setup,
Action servicesCallback,
Action loggingCallback = null,
- List overrideSource = null)
+ List overrideSource = null,
+ HostBuilderSettings settings = null)
{
return new HostBuilder()
.ConfigureAppConfiguration(builder =>
@@ -100,6 +101,12 @@ public static IHost CreateHost(
services.ConfigureTemplates(context.Configuration);
services.AddSingleton();
services.ConfigureCollectionRules();
+
+ if (settings != null)
+ {
+ services.ConfigureExtensions(settings);
+ }
+
services.ConfigureEgress();
services.ConfigureDiagnosticPort(context.Configuration);
diff --git a/src/Tools/dotnet-monitor/Extensibility/ProgramExtension.cs b/src/Tools/dotnet-monitor/Extensibility/ProgramExtension.cs
index 3c1f7165c66..0d5f3325291 100644
--- a/src/Tools/dotnet-monitor/Extensibility/ProgramExtension.cs
+++ b/src/Tools/dotnet-monitor/Extensibility/ProgramExtension.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -65,7 +66,15 @@ public async Task EgressArtifact(ExtensionEgressPayload co
// This is really weird, yes, but this is one of 2 overloads for [Stream].WriteAsync(...) that supports a CancellationToken, so we use a ReadOnlyMemory instead of a string.
ReadOnlyMemory NewLine = new ReadOnlyMemory("\r\n".ToCharArray());
- string programRelPath = Path.Combine(Path.GetDirectoryName(_exePath), Declaration.Program);
+ string exeName = Declaration.Program;
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Path.GetExtension(exeName) == ".exe")
+ {
+ exeName = Path.GetFileNameWithoutExtension(exeName);
+ }
+
+ string programRelPath = Path.Combine(Path.GetDirectoryName(_exePath), exeName);
+
IFileInfo progInfo = _fileSystem.GetFileInfo(programRelPath);
if (!progInfo.Exists || progInfo.IsDirectory || progInfo.PhysicalPath == null)
{
diff --git a/src/Tools/dotnet-monitor/Extensibility/ToolsExtensionRepository.cs b/src/Tools/dotnet-monitor/Extensibility/ToolsExtensionRepository.cs
index 7ba880284bf..81d85f59dde 100644
--- a/src/Tools/dotnet-monitor/Extensibility/ToolsExtensionRepository.cs
+++ b/src/Tools/dotnet-monitor/Extensibility/ToolsExtensionRepository.cs
@@ -18,17 +18,6 @@ internal class ToolsExtensionRepository : ExtensionRepository
private readonly IFileProvider _fileSystem;
private readonly ILogger _logger;
- private const string DotnetFolderName = "dotnet";
- private const string ToolsFolderName = "tools";
-
- // Location where extensions are stored by default.
- // Windows: "%USERPROFILE%\.dotnet\Tools"
- // Other: "%XDG_CONFIG_HOME%/.dotnet/tools" OR "%HOME%/.dotnet/tools" -> THIS HAS NOT BEEN TESTED YET ON LINUX
- public static readonly string DotnetToolsExtensionDirectoryPath =
- RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "." + DotnetFolderName, ToolsFolderName) :
- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "." + DotnetFolderName, ToolsFolderName);
-
public ToolsExtensionRepository(IFileProvider fileSystem, ILogger logger, string targetFolder)
: base(string.Format(CultureInfo.CurrentCulture, Strings.Message_FolderExtensionRepoName, targetFolder))
{
diff --git a/src/Tools/dotnet-monitor/HostBuilder/HostBuilderSettings.cs b/src/Tools/dotnet-monitor/HostBuilder/HostBuilderSettings.cs
index f56d4deee5e..5decee63939 100644
--- a/src/Tools/dotnet-monitor/HostBuilder/HostBuilderSettings.cs
+++ b/src/Tools/dotnet-monitor/HostBuilder/HostBuilderSettings.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
+using System.Reflection;
using System.Runtime.InteropServices;
namespace Microsoft.Diagnostics.Tools.Monitor
@@ -11,6 +12,8 @@ namespace Microsoft.Diagnostics.Tools.Monitor
internal sealed class HostBuilderSettings
{
private const string ProductFolderName = "dotnet-monitor";
+ private const string DotnetFolderName = "dotnet";
+ private const string ToolsFolderName = "tools";
// Allows tests to override the shared configuration directory so there
// is better control and access of what is visible during test.
@@ -22,6 +25,16 @@ private const string SharedConfigDirectoryOverrideEnvironmentVariable
private const string UserConfigDirectoryOverrideEnvironmentVariable
= "DotnetMonitorTestSettings__UserConfigDirectoryOverride";
+ // Allows tests to override the user configuration directory so there
+ // is better control and access of what is visible during test.
+ private const string DotnetToolsExtensionDirectoryOverrideEnvironmentVariable
+ = "DotnetMonitorTestSettings__DotnetToolsExtensionDirectoryOverride";
+
+ // Allows tests to override the user configuration directory so there
+ // is better control and access of what is visible during test.
+ private const string ExecutingAssemblyDirectoryOverrideEnvironmentVariable
+ = "DotnetMonitorTestSettings__ExecutingAssemblyDirectoryOverride";
+
// Location where shared dotnet-monitor configuration is stored.
// Windows: "%ProgramData%\dotnet-monitor
// Other: /etc/dotnet-monitor
@@ -42,6 +55,22 @@ private const string UserConfigDirectoryOverrideEnvironmentVariable
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "." + ProductFolderName) :
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ProductFolderName));
+ // Location where extensions are stored by default.
+ // Windows: "%USERPROFILE%\.dotnet\Tools"
+ // Other: "%XDG_CONFIG_HOME%/.dotnet/tools" OR "%HOME%/.dotnet/tools" -> THIS HAS NOT BEEN TESTED YET ON LINUX
+ public static readonly string DotnetToolsExtensionDirectoryPath =
+ GetEnvironmentOverrideOrValue(
+ DotnetToolsExtensionDirectoryOverrideEnvironmentVariable,
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "." + DotnetFolderName, ToolsFolderName) :
+ Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "." + DotnetFolderName, ToolsFolderName));
+
+ // Location for dotnet-monitor's executing assembly.
+ public static readonly string ExecutingAssemblyDirectoryPath =
+ GetEnvironmentOverrideOrValue(
+ ExecutingAssemblyDirectoryOverrideEnvironmentVariable,
+ Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
+
public string[] Urls { get; set; }
public string[] MetricsUrls { get; set; }
@@ -58,6 +87,10 @@ private const string UserConfigDirectoryOverrideEnvironmentVariable
public string UserConfigDirectory { get; set; }
+ public string DotnetToolsExtensionDirectory { get; set; }
+
+ public string ExecutingAssemblyDirectory { get; set; }
+
public FileInfo UserProvidedConfigFilePath { get; set; }
///
@@ -81,7 +114,9 @@ public static HostBuilderSettings CreateMonitor(
ContentRootDirectory = AppContext.BaseDirectory,
SharedConfigDirectory = SharedConfigDirectoryPath,
UserConfigDirectory = UserConfigDirectoryPath,
- UserProvidedConfigFilePath = userProvidedConfigFilePath
+ UserProvidedConfigFilePath = userProvidedConfigFilePath,
+ DotnetToolsExtensionDirectory = DotnetToolsExtensionDirectoryPath,
+ ExecutingAssemblyDirectory = ExecutingAssemblyDirectoryPath
};
}
diff --git a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
index c160654d08f..4f5afb25d5e 100644
--- a/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
+++ b/src/Tools/dotnet-monitor/ServiceCollectionExtensions.cs
@@ -31,7 +31,6 @@
using Microsoft.Extensions.Options;
using System;
using System.IO;
-using System.Reflection;
namespace Microsoft.Diagnostics.Tools.Monitor
{
@@ -217,10 +216,10 @@ public static IServiceCollection ConfigureExtensions(this IServiceCollection ser
// Add the services to discover extensions
services.AddSingleton();
- string nextToMeFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
+ string executingAssemblyFolder = settings.ExecutingAssemblyDirectory;
string progDataFolder = settings.SharedConfigDirectory;
string settingsFolder = settings.UserConfigDirectory;
- string dotnetToolsFolder = ToolsExtensionRepository.DotnetToolsExtensionDirectoryPath;
+ string dotnetToolsFolder = settings.DotnetToolsExtensionDirectory;
if (string.IsNullOrWhiteSpace(progDataFolder)
|| string.IsNullOrWhiteSpace(settingsFolder)
@@ -230,7 +229,7 @@ public static IServiceCollection ConfigureExtensions(this IServiceCollection ser
}
// Add the folders we search to get extensions from
- services.AddFolderExtensionRepository(nextToMeFolder);
+ services.AddFolderExtensionRepository(executingAssemblyFolder);
services.AddFolderExtensionRepository(progDataFolder);
services.AddFolderExtensionRepository(settingsFolder);
services.AddToolsExtensionRepository(dotnetToolsFolder);
diff --git a/src/Tools/dotnet-monitor/dotnet-monitor.csproj b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
index 26681e53083..2d601e3f4bd 100644
--- a/src/Tools/dotnet-monitor/dotnet-monitor.csproj
+++ b/src/Tools/dotnet-monitor/dotnet-monitor.csproj
@@ -44,6 +44,7 @@
+