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 @@ +