diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index c850fb43d..714749883 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -232,6 +232,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Msbuild.Tests.Utilities", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ToolPackageObtainer.Tests", "test\Microsoft.DotNet.ToolPackageObtainer.Tests\Microsoft.DotNet.ToolPackageObtainer.Tests.csproj", "{F0D50831-9468-4ACB-8FD8-E9883DD553FB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ShellShimMaker.Tests", "test\Microsoft.DotNet.ShellShimMaker.Tests\Microsoft.DotNet.ShellShimMaker.Tests.csproj", "{20376E28-68EB-4BE5-81A5-51500F1235E2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1640,6 +1642,30 @@ Global {F0D50831-9468-4ACB-8FD8-E9883DD553FB}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {F0D50831-9468-4ACB-8FD8-E9883DD553FB}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {F0D50831-9468-4ACB-8FD8-E9883DD553FB}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|x64.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|x86.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Debug|x86.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|x86.ActiveCfg = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.MinSizeRel|x86.Build.0 = Debug|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|Any CPU.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|x64.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|x64.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|x86.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.Release|x86.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {20376E28-68EB-4BE5-81A5-51500F1235E2}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1713,6 +1739,7 @@ Global {44759218-B558-4AF0-8991-515F1100DCF5} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {E7C72EF2-8480-48B4-AAE8-A596F1A6048E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {F0D50831-9468-4ACB-8FD8-E9883DD553FB} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {20376E28-68EB-4BE5-81A5-51500F1235E2} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B526D2CE-EE2D-4AD4-93EF-1867D90FF1F5} diff --git a/scripts/cli-test-env.sh b/scripts/cli-test-env.sh index 2d334b057..a6b3f3fc1 100644 --- a/scripts/cli-test-env.sh +++ b/scripts/cli-test-env.sh @@ -13,9 +13,10 @@ done REPO_ROOT="$( cd -P "$( dirname "$SOURCE" )/../" && pwd )" -if [ "$uname" = "Darwin" ] +uname=$(uname) +if [ "$(uname)" == "Darwin" ] then - RID=osx-x64 + RID=osx.10.13-x64 else RID=linux-x64 fi diff --git a/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPath.cs b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPath.cs new file mode 100644 index 000000000..7c8ddd6bd --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPath.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.Cli.Utils +{ + public interface IEnvironmentPath: IEnvironmentPathInstruction + { + void AddPackageExecutablePathToUserPath(); + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPathInstruction.cs b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPathInstruction.cs new file mode 100644 index 000000000..1ab69e4a1 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentPathInstruction.cs @@ -0,0 +1,7 @@ +namespace Microsoft.DotNet.Cli.Utils +{ + public interface IEnvironmentPathInstruction + { + void PrintAddPathInstructionIfPathDoesNotExist(); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs b/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs index 5ebd03235..b1d6f09ca 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Properties/AssemblyInfo.cs @@ -12,3 +12,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.DotNet.TestFramework, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Tests.Utilities, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.ProjectJsonMigration, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.DotNet.ShellShimMaker.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.DotNet.Configurer/CliFallbackFolderPathCalculator.cs b/src/Microsoft.DotNet.Configurer/CliFallbackFolderPathCalculator.cs deleted file mode 100644 index ddeac908f..000000000 --- a/src/Microsoft.DotNet.Configurer/CliFallbackFolderPathCalculator.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.IO; -using System.Runtime.InteropServices; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.PlatformAbstractions; -using NuGet.Common; - -namespace Microsoft.DotNet.Configurer -{ - public class CliFallbackFolderPathCalculator - { - public string CliFallbackFolderPath => - Environment.GetEnvironmentVariable("DOTNET_CLI_TEST_FALLBACKFOLDER") ?? - Path.Combine(new DirectoryInfo(AppContext.BaseDirectory).Parent.FullName, "NuGetFallbackFolder"); - - public string DotnetUserProfileFolderPath - { - get - { - string profileDir = Environment.GetEnvironmentVariable( - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME"); - - return Path.Combine(profileDir, ".dotnet"); - } - } - - public string NuGetUserSettingsDirectory => - NuGetEnvironment.GetFolderPath(NuGetFolderPath.UserSettingsDirectory); - } -} diff --git a/src/Microsoft.DotNet.Configurer/CliFolderPathCalculator.cs b/src/Microsoft.DotNet.Configurer/CliFolderPathCalculator.cs new file mode 100644 index 000000000..bdc5dbfe6 --- /dev/null +++ b/src/Microsoft.DotNet.Configurer/CliFolderPathCalculator.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.PlatformAbstractions; +using NuGet.Common; + +namespace Microsoft.DotNet.Configurer +{ + public class CliFolderPathCalculator + { + private const string ToolsFolderName = "tools"; + private const string DotnetProfileDirectoryName = ".dotnet"; + + public string CliFallbackFolderPath => Environment.GetEnvironmentVariable("DOTNET_CLI_TEST_FALLBACKFOLDER") ?? + Path.Combine(new DirectoryInfo(AppContext.BaseDirectory).Parent.FullName, "NuGetFallbackFolder"); + + public string ExecutablePackagesPath => Path.Combine(DotnetUserProfileFolderPath, ToolsFolderName) ; + public readonly string ExecutablePackagesPathOnMacEnvPath = $"~/{DotnetProfileDirectoryName}/{ToolsFolderName}"; + + public static string DotnetUserProfileFolderPath + { + get + { + string profileDir = Environment.GetEnvironmentVariable( + RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "USERPROFILE" : "HOME"); + + return Path.Combine(profileDir, DotnetProfileDirectoryName); + } + } + + public string NuGetUserSettingsDirectory => + NuGetEnvironment.GetFolderPath(NuGetFolderPath.UserSettingsDirectory); + } +} diff --git a/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs b/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs index 9a89bcb6f..29e17f7de 100644 --- a/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs +++ b/src/Microsoft.DotNet.Configurer/DotnetFirstTimeUseConfigurer.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.IO; +using System.Runtime.InteropServices; using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.EnvironmentAbstractions; @@ -15,6 +17,7 @@ namespace Microsoft.DotNet.Configurer private INuGetCacheSentinel _nugetCacheSentinel; private IFirstTimeUseNoticeSentinel _firstTimeUseNoticeSentinel; private string _cliFallbackFolderPath; + private readonly IEnvironmentPath _pathAdder; public DotnetFirstTimeUseConfigurer( INuGetCachePrimer nugetCachePrimer, @@ -22,7 +25,8 @@ namespace Microsoft.DotNet.Configurer IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, IEnvironmentProvider environmentProvider, IReporter reporter, - string cliFallbackFolderPath) + string cliFallbackFolderPath, + IEnvironmentPath pathAdder) { _nugetCachePrimer = nugetCachePrimer; _nugetCacheSentinel = nugetCacheSentinel; @@ -30,10 +34,13 @@ namespace Microsoft.DotNet.Configurer _environmentProvider = environmentProvider; _reporter = reporter; _cliFallbackFolderPath = cliFallbackFolderPath; + _pathAdder = pathAdder ?? throw new ArgumentNullException(nameof(pathAdder)); } public void Configure() { + AddPackageExecutablePath(); + if (ShouldPrintFirstTimeUseNotice()) { PrintFirstTimeUseNotice(); @@ -54,6 +61,23 @@ namespace Microsoft.DotNet.Configurer } } + private void AddPackageExecutablePath() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (!_firstTimeUseNoticeSentinel.Exists()) + { + // Invoke when Windows first run + _pathAdder.AddPackageExecutablePathToUserPath(); + } + } + else + { + // Invoke during installer, otherwise, _pathAdder will be no op object that this point + _pathAdder.AddPackageExecutablePathToUserPath(); + } + } + private bool ShouldPrintFirstTimeUseNotice() { var showFirstTimeUseNotice = diff --git a/src/Microsoft.DotNet.Configurer/FirstTimeUseNoticeSentinel.cs b/src/Microsoft.DotNet.Configurer/FirstTimeUseNoticeSentinel.cs index d7335dee5..78ae9caa5 100644 --- a/src/Microsoft.DotNet.Configurer/FirstTimeUseNoticeSentinel.cs +++ b/src/Microsoft.DotNet.Configurer/FirstTimeUseNoticeSentinel.cs @@ -19,9 +19,9 @@ namespace Microsoft.DotNet.Configurer private string SentinelPath => Path.Combine(_dotnetUserProfileFolderPath, SENTINEL); - public FirstTimeUseNoticeSentinel(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) : + public FirstTimeUseNoticeSentinel(CliFolderPathCalculator cliFolderPathCalculator) : this( - cliFallbackFolderPathCalculator.DotnetUserProfileFolderPath, + CliFolderPathCalculator.DotnetUserProfileFolderPath, FileSystemWrapper.Default.File, FileSystemWrapper.Default.Directory) { diff --git a/src/Microsoft.DotNet.Configurer/NuGetCachePrimer.cs b/src/Microsoft.DotNet.Configurer/NuGetCachePrimer.cs index 5233d3359..a0bc3bca5 100644 --- a/src/Microsoft.DotNet.Configurer/NuGetCachePrimer.cs +++ b/src/Microsoft.DotNet.Configurer/NuGetCachePrimer.cs @@ -17,15 +17,15 @@ namespace Microsoft.DotNet.Configurer private readonly INuGetCacheSentinel _nuGetCacheSentinel; - private readonly CliFallbackFolderPathCalculator _cliFallbackFolderPathCalculator; + private readonly CliFolderPathCalculator _cliFolderPathCalculator; public NuGetCachePrimer( INuGetPackagesArchiver nugetPackagesArchiver, INuGetCacheSentinel nuGetCacheSentinel, - CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) + CliFolderPathCalculator cliFolderPathCalculator) : this(nugetPackagesArchiver, nuGetCacheSentinel, - cliFallbackFolderPathCalculator, + cliFolderPathCalculator, FileSystemWrapper.Default.File) { } @@ -33,14 +33,14 @@ namespace Microsoft.DotNet.Configurer internal NuGetCachePrimer( INuGetPackagesArchiver nugetPackagesArchiver, INuGetCacheSentinel nuGetCacheSentinel, - CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator, + CliFolderPathCalculator cliFolderPathCalculator, IFile file) { _nugetPackagesArchiver = nugetPackagesArchiver; _nuGetCacheSentinel = nuGetCacheSentinel; - _cliFallbackFolderPathCalculator = cliFallbackFolderPathCalculator; + _cliFolderPathCalculator = cliFolderPathCalculator; _file = file; } @@ -52,7 +52,7 @@ namespace Microsoft.DotNet.Configurer return; } - var nuGetFallbackFolder = _cliFallbackFolderPathCalculator.CliFallbackFolderPath; + var nuGetFallbackFolder = _cliFolderPathCalculator.CliFallbackFolderPath; _nugetPackagesArchiver.ExtractArchive(nuGetFallbackFolder); diff --git a/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs b/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs index bfc7a29d9..ebaff5819 100644 --- a/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs +++ b/src/Microsoft.DotNet.Configurer/NuGetCacheSentinel.cs @@ -27,8 +27,8 @@ namespace Microsoft.DotNet.Configurer private Stream InProgressSentinel { get; set; } - public NuGetCacheSentinel(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) : - this(cliFallbackFolderPathCalculator.CliFallbackFolderPath, + public NuGetCacheSentinel(CliFolderPathCalculator cliFolderPathCalculator) : + this(cliFolderPathCalculator.CliFallbackFolderPath, FileSystemWrapper.Default.File, FileSystemWrapper.Default.Directory) { diff --git a/src/Microsoft.DotNet.Configurer/UserLevelCacheWriter.cs b/src/Microsoft.DotNet.Configurer/UserLevelCacheWriter.cs index 9e5fd4a78..eeb69e526 100644 --- a/src/Microsoft.DotNet.Configurer/UserLevelCacheWriter.cs +++ b/src/Microsoft.DotNet.Configurer/UserLevelCacheWriter.cs @@ -14,9 +14,9 @@ namespace Microsoft.DotNet.Configurer private readonly IDirectory _directory; private string _dotnetUserProfileFolderPath; - public UserLevelCacheWriter(CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) : + public UserLevelCacheWriter(CliFolderPathCalculator cliFolderPathCalculator) : this( - cliFallbackFolderPathCalculator.DotnetUserProfileFolderPath, + CliFolderPathCalculator.DotnetUserProfileFolderPath, FileSystemWrapper.Default.File, FileSystemWrapper.Default.Directory) { diff --git a/src/Microsoft.DotNet.InternalAbstractions/Properties/Properties.cs b/src/Microsoft.DotNet.InternalAbstractions/Properties/Properties.cs index 1ab5c59fd..3b1a181ad 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/Properties/Properties.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/Properties/Properties.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; [assembly: AssemblyMetadataAttribute("Serviceable", "True")] [assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyModel, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("dotnet, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Tests.Utilities, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.Extensions.DependencyModel.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Configurer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] @@ -13,3 +14,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: InternalsVisibleTo("dotnet-test.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100039ac461fa5c82c7dd2557400c4fd4e9dcdf7ac47e3d572548c04cd4673e004916610f4ea5cbf86f2b1ca1cb824f2a7b3976afecfcf4eb72d9a899aa6786effa10c30399e6580ed848231fec48374e41b3acf8811931343fc2f73acf72dae745adbcb7063cc4b50550618383202875223fc75401351cd89c44bf9b50e7fa3796")] [assembly: InternalsVisibleTo("Microsoft.DotNet.Tools.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.DotNet.ShellShimMaker.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 824577aaa..7e6cafe8c 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -4,15 +4,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.DotNet.Cli.Telemetry; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Configurer; using Microsoft.DotNet.PlatformAbstractions; +using Microsoft.DotNet.ShellShimMaker; using Microsoft.DotNet.Tools.Help; +using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Frameworks; using Command = Microsoft.DotNet.Cli.Utils.Command; +using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment; namespace Microsoft.DotNet.Cli { @@ -80,8 +84,9 @@ namespace Microsoft.DotNet.Cli var success = true; var command = string.Empty; var lastArg = 0; - var cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator(); + var cliFallbackFolderPathCalculator = new CliFolderPathCalculator(); TopLevelCommandParserResult topLevelCommandParserResult = TopLevelCommandParserResult.Empty; + using (INuGetCacheSentinel nugetCacheSentinel = new NuGetCacheSentinel(cliFallbackFolderPathCalculator)) using (IFirstTimeUseNoticeSentinel disposableFirstTimeUseNoticeSentinel = new FirstTimeUseNoticeSentinel(cliFallbackFolderPathCalculator)) @@ -120,22 +125,24 @@ namespace Microsoft.DotNet.Cli { // It's the command, and we're done! command = args[lastArg]; - if (string.IsNullOrEmpty(command)) { command = "help"; } - topLevelCommandParserResult = new TopLevelCommandParserResult(args[lastArg]); + topLevelCommandParserResult = new TopLevelCommandParserResult(command); + var hasSuperUserAccess = false; if (IsDotnetBeingInvokedFromNativeInstaller(topLevelCommandParserResult)) { firstTimeUseNoticeSentinel = new NoOpFirstTimeUseNoticeSentinel(); + hasSuperUserAccess = true; } ConfigureDotNetForFirstTimeUse( nugetCacheSentinel, firstTimeUseNoticeSentinel, - cliFallbackFolderPathCalculator); + cliFallbackFolderPathCalculator, + hasSuperUserAccess); break; } @@ -197,24 +204,29 @@ namespace Microsoft.DotNet.Cli private static void ConfigureDotNetForFirstTimeUse( INuGetCacheSentinel nugetCacheSentinel, IFirstTimeUseNoticeSentinel firstTimeUseNoticeSentinel, - CliFallbackFolderPathCalculator cliFallbackFolderPathCalculator) + CliFolderPathCalculator cliFolderPathCalculator, + bool hasSuperUserAccess) { + var environmentProvider = new EnvironmentProvider(); + using (PerfTrace.Current.CaptureTiming()) { var nugetPackagesArchiver = new NuGetPackagesArchiver(); - var environmentProvider = new EnvironmentProvider(); + var environmentPath = + EnvironmentPathFactory.CreateEnvironmentPath(cliFolderPathCalculator, hasSuperUserAccess, environmentProvider); var commandFactory = new DotNetCommandFactory(alwaysRunOutOfProc: true); var nugetCachePrimer = new NuGetCachePrimer( nugetPackagesArchiver, nugetCacheSentinel, - cliFallbackFolderPathCalculator); + cliFolderPathCalculator); var dotnetConfigurer = new DotnetFirstTimeUseConfigurer( nugetCachePrimer, nugetCacheSentinel, firstTimeUseNoticeSentinel, environmentProvider, Reporter.Output, - cliFallbackFolderPathCalculator.CliFallbackFolderPath); + cliFolderPathCalculator.CliFallbackFolderPath, + environmentPath); dotnetConfigurer.Configure(); } diff --git a/src/dotnet/Properties/AssemblyInfo.cs b/src/dotnet/Properties/AssemblyInfo.cs index ede900dd4..334c80f95 100644 --- a/src/dotnet/Properties/AssemblyInfo.cs +++ b/src/dotnet/Properties/AssemblyInfo.cs @@ -17,4 +17,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("dotnet-sln-remove.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("dotnet-msbuild.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("dotnet-run.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.DotNet.ToolPackageObtainer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file +[assembly: InternalsVisibleTo("Microsoft.DotNet.ToolPackageObtainer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.DotNet.ShellShimMaker.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/dotnet/ShellShimMaker/DoNothingEnvironmentPath.cs b/src/dotnet/ShellShimMaker/DoNothingEnvironmentPath.cs new file mode 100644 index 000000000..79ee09564 --- /dev/null +++ b/src/dotnet/ShellShimMaker/DoNothingEnvironmentPath.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.ShellShimMaker +{ + public class DoNothingEnvironmentPath : IEnvironmentPath + { + public void AddPackageExecutablePathToUserPath() + { + } + + public void PrintAddPathInstructionIfPathDoesNotExist() + { + } + } +} diff --git a/src/dotnet/ShellShimMaker/EnvironmentPathFactory.cs b/src/dotnet/ShellShimMaker/EnvironmentPathFactory.cs new file mode 100644 index 000000000..48ffce82f --- /dev/null +++ b/src/dotnet/ShellShimMaker/EnvironmentPathFactory.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Xsl; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Configurer; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker +{ + internal static class EnvironmentPathFactory + { + public static IEnvironmentPath CreateEnvironmentPath( + CliFolderPathCalculator cliFolderPathCalculator = null, + bool hasSuperUserAccess = false, + IEnvironmentProvider environmentProvider = null) + { + if (cliFolderPathCalculator == null) + { + cliFolderPathCalculator = new CliFolderPathCalculator(); + } + + if (environmentProvider == null) + { + environmentProvider = new EnvironmentProvider(); + } + + IEnvironmentPath environmentPath = new DoNothingEnvironmentPath(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + environmentPath = new WindowsEnvironmentPath( + cliFolderPathCalculator.ExecutablePackagesPath, + Reporter.Output, + environmentProvider); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && hasSuperUserAccess) + { + environmentPath = new LinuxEnvironmentPath( + cliFolderPathCalculator.ExecutablePackagesPath, + Reporter.Output, + environmentProvider, new FileWrapper()); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && hasSuperUserAccess) + { + environmentPath = new OSXEnvironmentPath( + packageExecutablePathWithTilde: cliFolderPathCalculator.ExecutablePackagesPathOnMacEnvPath, + fullPackageExecutablePath: cliFolderPathCalculator.ExecutablePackagesPath, + reporter: Reporter.Output, + environmentProvider: environmentProvider, + fileSystem: new FileWrapper()); + } + + return environmentPath; + } + + public static IEnvironmentPathInstruction CreateEnvironmentPathInstruction( + CliFolderPathCalculator cliFolderPathCalculator = null, + IEnvironmentProvider environmentProvider = null) + { + return CreateEnvironmentPath(cliFolderPathCalculator, true, environmentProvider); + } + } +} diff --git a/src/dotnet/ShellShimMaker/LinuxEnvironmentPath.cs b/src/dotnet/ShellShimMaker/LinuxEnvironmentPath.cs new file mode 100644 index 000000000..0868a6fcf --- /dev/null +++ b/src/dotnet/ShellShimMaker/LinuxEnvironmentPath.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker +{ + internal class LinuxEnvironmentPath : IEnvironmentPath + { + private readonly IFile _fileSystem; + private readonly IEnvironmentProvider _environmentProvider; + private readonly IReporter _reporter; + private const string PathName = "PATH"; + private readonly string _packageExecutablePath; + + private readonly string _profiledDotnetCliToolsPath + = Environment.GetEnvironmentVariable("DOTNET_CLI_TEST_LINUX_PROFILED_PATH") + ?? @"/etc/profile.d/dotnet-cli-tools-bin-path.sh"; + + internal LinuxEnvironmentPath( + string packageExecutablePath, + IReporter reporter, + IEnvironmentProvider environmentProvider, + IFile fileSystem) + { + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _environmentProvider + = environmentProvider ?? throw new ArgumentNullException(nameof(environmentProvider)); + _reporter + = reporter ?? throw new ArgumentNullException(nameof(reporter)); + _packageExecutablePath + = packageExecutablePath ?? throw new ArgumentNullException(nameof(packageExecutablePath)); + } + + public void AddPackageExecutablePathToUserPath() + { + if (PackageExecutablePathExists()) + { + return; + } + + var script = $"export PATH=\"$PATH:{_packageExecutablePath}\""; + _fileSystem.WriteAllText(_profiledDotnetCliToolsPath, script); + } + + private bool PackageExecutablePathExists() + { + return _environmentProvider + .GetEnvironmentVariable(PathName) + .Split(':').Contains(_packageExecutablePath); + } + + public void PrintAddPathInstructionIfPathDoesNotExist() + { + if (!PackageExecutablePathExists()) + { + if (_fileSystem.Exists(_profiledDotnetCliToolsPath)) + { + _reporter.WriteLine("Since you just installed the .NET Core SDK, you will need to logout or restart your session before running the tool you installed."); + } + else + { + // similar to https://code.visualstudio.com/docs/setup/mac + _reporter.WriteLine( + $"Cannot find the tools executable path. Please ensure {_packageExecutablePath} is added to your PATH.{Environment.NewLine}" + + $"If you are using bash. You can do this by running the following command:{Environment.NewLine}{Environment.NewLine}" + + $"cat << EOF >> ~/.bash_profile{Environment.NewLine}" + + $"# Add .NET Core SDK tools{Environment.NewLine}" + + $"export PATH=\"$PATH:{_packageExecutablePath}\"{Environment.NewLine}" + + $"EOF"); + } + } + } + } +} diff --git a/src/dotnet/ShellShimMaker/OsxEnvironmentPath.cs b/src/dotnet/ShellShimMaker/OsxEnvironmentPath.cs new file mode 100644 index 000000000..cc8f8eac8 --- /dev/null +++ b/src/dotnet/ShellShimMaker/OsxEnvironmentPath.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker +{ + internal class OSXEnvironmentPath : IEnvironmentPath + { + private const string PathName = "PATH"; + private readonly string _packageExecutablePathWithTilde; + private readonly string _fullPackageExecutablePath; + private readonly IFile _fileSystem; + private readonly IEnvironmentProvider _environmentProvider; + private readonly IReporter _reporter; + + private static readonly string PathDDotnetCliToolsPath + = Environment.GetEnvironmentVariable("DOTNET_CLI_TEST_OSX_PATHSD_PATH") + ?? @"/etc/paths.d/dotnet-cli-tools"; + + public OSXEnvironmentPath( + string packageExecutablePathWithTilde, + string fullPackageExecutablePath, + IReporter reporter, + IEnvironmentProvider environmentProvider, + IFile fileSystem + ) + { + _fullPackageExecutablePath = fullPackageExecutablePath ?? + throw new ArgumentNullException(nameof(fullPackageExecutablePath)); + _packageExecutablePathWithTilde = packageExecutablePathWithTilde ?? + throw new ArgumentNullException(nameof(packageExecutablePathWithTilde)); + _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); + _environmentProvider + = environmentProvider ?? throw new ArgumentNullException(nameof(environmentProvider)); + _reporter + = reporter ?? throw new ArgumentNullException(nameof(reporter)); + } + + public void AddPackageExecutablePathToUserPath() + { + if (PackageExecutablePathExists()) + { + return; + } + + var script = $"{_packageExecutablePathWithTilde}"; + _fileSystem.WriteAllText(PathDDotnetCliToolsPath, script); + } + + private bool PackageExecutablePathExists() + { + return _environmentProvider.GetEnvironmentVariable(PathName).Split(':') + .Contains(_packageExecutablePathWithTilde) || + _environmentProvider.GetEnvironmentVariable(PathName).Split(':') + .Contains(_fullPackageExecutablePath); + } + + public void PrintAddPathInstructionIfPathDoesNotExist() + { + if (!PackageExecutablePathExists()) + { + if (_fileSystem.Exists(PathDDotnetCliToolsPath)) + { + _reporter.WriteLine( + "Since you just installed the .NET Core SDK, you will need to reopen terminal before running the tool you installed."); + } + else + { + // similar to https://code.visualstudio.com/docs/setup/mac + _reporter.WriteLine( + $"Cannot find the tools executable path. Please ensure {_fullPackageExecutablePath} is added to your PATH.{Environment.NewLine}" + + $"If you are using bash, You can do this by running the following command:{Environment.NewLine}{Environment.NewLine}" + + $"cat << EOF >> ~/.bash_profile{Environment.NewLine}" + + $"# Add .NET Core SDK tools{Environment.NewLine}" + + $"export PATH=\"$PATH:{_fullPackageExecutablePath}\"{Environment.NewLine}" + + $"EOF"); + } + } + } + } +} diff --git a/src/dotnet/ShellShimMaker/ShellShimMaker.cs b/src/dotnet/ShellShimMaker/ShellShimMaker.cs new file mode 100644 index 000000000..6a56f226b --- /dev/null +++ b/src/dotnet/ShellShimMaker/ShellShimMaker.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker +{ + public class ShellShimMaker + { + private readonly string _pathToPlaceShim; + + public ShellShimMaker(string pathToPlaceShim) + { + _pathToPlaceShim = + pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim)); + } + + public void CreateShim(string packageExecutablePath, string shellCommandName) + { + var packageExecutable = new FilePath(packageExecutablePath); + + var script = new StringBuilder(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + script.AppendLine("@echo off"); + script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} %*"); + } + else + { + script.AppendLine("#!/bin/sh"); + script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\""); + } + + FilePath scriptPath = GetScriptPath(shellCommandName); + File.WriteAllText(scriptPath.Value, script.ToString()); + + SetUserExecutionPermissionToShimFile(scriptPath); + } + + public void EnsureCommandNameUniqueness(string shellCommandName) + { + if (File.Exists(Path.Combine(_pathToPlaceShim, shellCommandName))) + { + throw new GracefulException( + $"Failed to install tool {shellCommandName}. A command with the same name already exists."); + } + } + + public void Remove(string shellCommandName) + { + File.Delete(GetScriptPath(shellCommandName).Value); + } + + private FilePath GetScriptPath(string shellCommandName) + { + var scriptPath = Path.Combine(_pathToPlaceShim, shellCommandName); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + scriptPath += ".cmd"; + } + + return new FilePath(scriptPath); + } + + private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + + CommandResult result = new CommandFactory() + .Create("chmod", new[] {"u+x", scriptPath.Value}) + .CaptureStdOut() + .CaptureStdErr() + .Execute(); + + if (result.ExitCode != 0) + { + throw new GracefulException( + "Failed to change permission:" + + $"{Environment.NewLine}Error: " + result.StdErr + + $"{Environment.NewLine}Output: " + result.StdOut); + } + } + } +} diff --git a/src/dotnet/ShellShimMaker/WindowsEnvironmentPath.cs b/src/dotnet/ShellShimMaker/WindowsEnvironmentPath.cs new file mode 100644 index 000000000..bc229d04d --- /dev/null +++ b/src/dotnet/ShellShimMaker/WindowsEnvironmentPath.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker +{ + internal class WindowsEnvironmentPath : IEnvironmentPath + { + private readonly IReporter _reporter; + private readonly IEnvironmentProvider _environmentProvider; + private const string PathName = "PATH"; + private readonly string _packageExecutablePath; + + public WindowsEnvironmentPath( + string packageExecutablePath, IReporter reporter, + IEnvironmentProvider environmentProvider) + { + _packageExecutablePath + = packageExecutablePath ?? throw new ArgumentNullException(nameof(packageExecutablePath)); + _environmentProvider + = environmentProvider ?? throw new ArgumentNullException(nameof(environmentProvider)); + _reporter + = reporter ?? throw new ArgumentNullException(nameof(reporter)); + } + + public void AddPackageExecutablePathToUserPath() + { + if (PackageExecutablePathExists()) + { + return; + } + + var existingUserEnvPath = Environment.GetEnvironmentVariable(PathName, EnvironmentVariableTarget.User); + + Environment.SetEnvironmentVariable( + PathName, + $"{existingUserEnvPath};{_packageExecutablePath}", + EnvironmentVariableTarget.User); + } + + private bool PackageExecutablePathExists() + { + return _environmentProvider.GetEnvironmentVariable(PathName).Split(';').Contains(_packageExecutablePath); + } + + public void PrintAddPathInstructionIfPathDoesNotExist() + { + if (!PackageExecutablePathExists()) + { + if (Environment.GetEnvironmentVariable(PathName, EnvironmentVariableTarget.User).Split(';') + .Contains(_packageExecutablePath)) + { + _reporter.WriteLine( + "Since you just installed the .NET Core SDK, you will need to reopen the Command Prompt window before running the tool you installed."); + } + else + { + _reporter.WriteLine( + $"Cannot find the tools executable path. Please ensure {_packageExecutablePath} is added to your PATH.{Environment.NewLine}" + + $"You can do this by running the following command:{Environment.NewLine}{Environment.NewLine}" + + $"setx PATH \\\"%PATH%;{_packageExecutablePath}\"{Environment.NewLine}"); + } + } + } + } +} diff --git a/src/dotnet/Telemetry/TelemetryCommonProperties.cs b/src/dotnet/Telemetry/TelemetryCommonProperties.cs index a14c5cf84..c19060ab3 100644 --- a/src/dotnet/Telemetry/TelemetryCommonProperties.cs +++ b/src/dotnet/Telemetry/TelemetryCommonProperties.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Cli.Telemetry _hasher = hasher ?? Sha256Hasher.Hash; _getMACAddress = getMACAddress ?? MacAddressGetter.GetMacAddress; _dockerContainerDetector = dockerContainerDetector ?? new DockerContainerDetectorForTelemetry(); - _userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(new CliFallbackFolderPathCalculator()); + _userLevelCacheWriter = userLevelCacheWriter ?? new UserLevelCacheWriter(new CliFolderPathCalculator()); } private readonly IDockerContainerDetector _dockerContainerDetector; diff --git a/src/dotnet/commands/dotnet-msbuild/MSBuildLogger.cs b/src/dotnet/commands/dotnet-msbuild/MSBuildLogger.cs index 1a6433f90..2f60d7d1b 100644 --- a/src/dotnet/commands/dotnet-msbuild/MSBuildLogger.cs +++ b/src/dotnet/commands/dotnet-msbuild/MSBuildLogger.cs @@ -14,7 +14,7 @@ namespace Microsoft.DotNet.Tools.MSBuild public sealed class MSBuildLogger : Logger { private readonly IFirstTimeUseNoticeSentinel _sentinel = - new FirstTimeUseNoticeSentinel(new CliFallbackFolderPathCalculator()); + new FirstTimeUseNoticeSentinel(new CliFolderPathCalculator()); private readonly ITelemetry _telemetry; private const string NewEventName = "msbuild"; private const string TargetFrameworkTelemetryEventName = "targetframeworkeval"; diff --git a/src/dotnet/commands/dotnet-new/NewCommandShim.cs b/src/dotnet/commands/dotnet-new/NewCommandShim.cs index 4b92bdaa8..8c0c2acd4 100644 --- a/src/dotnet/commands/dotnet-new/NewCommandShim.cs +++ b/src/dotnet/commands/dotnet-new/NewCommandShim.cs @@ -32,7 +32,7 @@ namespace Microsoft.DotNet.Tools.New var sessionId = Environment.GetEnvironmentVariable(MSBuildForwardingApp.TelemetrySessionIdEnvironmentVariableName); var telemetry = - new Telemetry(new FirstTimeUseNoticeSentinel(new CliFallbackFolderPathCalculator()), sessionId); + new Telemetry(new FirstTimeUseNoticeSentinel(new CliFolderPathCalculator()), sessionId); var logger = new TelemetryLogger(null); if (telemetry.Enabled) diff --git a/test/Microsoft.DotNet.Cli.Tests.sln b/test/Microsoft.DotNet.Cli.Tests.sln index 9140c1153..5f0d406d3 100644 --- a/test/Microsoft.DotNet.Cli.Tests.sln +++ b/test/Microsoft.DotNet.Cli.Tests.sln @@ -84,6 +84,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.TestFramew EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ToolPackageObtainer.Tests", "Microsoft.DotNet.ToolPackageObtainer.Tests\Microsoft.DotNet.ToolPackageObtainer.Tests.csproj", "{C2A907A3-677B-4C73-9AA4-E53613E13C78}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ShellShimMaker.Tests", "Microsoft.DotNet.ShellShimMaker.Tests\Microsoft.DotNet.ShellShimMaker.Tests.csproj", "{1146EAAC-E434-404A-8198-B4F0CB23BC57}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -550,6 +552,18 @@ Global {C2A907A3-677B-4C73-9AA4-E53613E13C78}.Release|x64.Build.0 = Release|Any CPU {C2A907A3-677B-4C73-9AA4-E53613E13C78}.Release|x86.ActiveCfg = Release|Any CPU {C2A907A3-677B-4C73-9AA4-E53613E13C78}.Release|x86.Build.0 = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|x64.ActiveCfg = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|x64.Build.0 = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|x86.ActiveCfg = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Debug|x86.Build.0 = Debug|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|Any CPU.Build.0 = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|x64.ActiveCfg = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|x64.Build.0 = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|x86.ActiveCfg = Release|Any CPU + {1146EAAC-E434-404A-8198-B4F0CB23BC57}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs index a9a0e904b..f3c549582 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenADotnetFirstTimeUseConfigurer.cs @@ -20,6 +20,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests private Mock _firstTimeUseNoticeSentinelMock; private Mock _environmentProviderMock; private Mock _reporterMock; + private Mock _pathAdder; public GivenADotnetFirstTimeUseConfigurer() { @@ -28,6 +29,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock = new Mock(); _environmentProviderMock = new Mock(); _reporterMock = new Mock(); + _pathAdder = new Mock(); _environmentProviderMock .Setup(e => e.GetEnvironmentVariableAsBool("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", false)) @@ -48,7 +50,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -70,7 +73,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -92,7 +96,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -111,7 +116,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -130,7 +136,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -148,7 +155,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -166,7 +174,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -187,7 +196,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -205,7 +215,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -224,7 +235,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -246,7 +258,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -258,6 +271,26 @@ namespace Microsoft.DotNet.Configurer.UnitTests _reporterMock.Verify(r => r.Write(It.IsAny()), Times.Never); } + [Fact] + public void It_adds_executable_package_path_to_environment_path_when_the_first_notice_sentinel_does_not_exist() + { + _nugetCacheSentinelMock.Setup(n => n.Exists()).Returns(true); + _firstTimeUseNoticeSentinelMock.Setup(n => n.Exists()).Returns(false); + + var dotnetFirstTimeUseConfigurer = new DotnetFirstTimeUseConfigurer( + _nugetCachePrimerMock.Object, + new FakeCreateWillExistNuGetCacheSentinel(false, true), + new FakeCreateWillExistFirstTimeUseNoticeSentinel(false), + _environmentProviderMock.Object, + _reporterMock.Object, + CliFallbackFolderPath, + _pathAdder.Object); + + dotnetFirstTimeUseConfigurer.Configure(); + + _pathAdder.Verify(p => p.AddPackageExecutablePathToUserPath(), Times.AtLeastOnce); + } + [Fact] public void It_prints_the_unauthorized_notice_if_the_cache_sentinel_reports_Unauthorized() { @@ -269,7 +302,8 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); @@ -295,11 +329,69 @@ namespace Microsoft.DotNet.Configurer.UnitTests _firstTimeUseNoticeSentinelMock.Object, _environmentProviderMock.Object, _reporterMock.Object, - CliFallbackFolderPath); + CliFallbackFolderPath, + _pathAdder.Object); dotnetFirstTimeUseConfigurer.Configure(); _nugetCachePrimerMock.Verify(r => r.PrimeCache(), Times.Never); } + + private class FakeCreateWillExistFirstTimeUseNoticeSentinel : IFirstTimeUseNoticeSentinel + { + private bool _exists; + + public FakeCreateWillExistFirstTimeUseNoticeSentinel(bool exists) + { + _exists = exists; + } + + public void Dispose() + { + } + + public bool Exists() + { + return _exists; + } + + public void CreateIfNotExists() + { + _exists = true; + } + } + + private class FakeCreateWillExistNuGetCacheSentinel : INuGetCacheSentinel + { + private bool _inProgressSentinelAlreadyExists; + private bool _exists; + + public FakeCreateWillExistNuGetCacheSentinel(bool inProgressSentinelAlreadyExists, bool exists) + { + _inProgressSentinelAlreadyExists = inProgressSentinelAlreadyExists; + _exists = exists; + } + + public void Dispose() + { + } + + public bool InProgressSentinelAlreadyExists() + { + return _inProgressSentinelAlreadyExists; + } + + public bool Exists() + { + return _exists; + } + + public void CreateIfNotExists() + { + _exists = true; + } + + public bool UnauthorizedAccess { get; set; } + } } } diff --git a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCachePrimer.cs b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCachePrimer.cs index f51fccb84..106afadac 100644 --- a/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCachePrimer.cs +++ b/test/Microsoft.DotNet.Configurer.UnitTests/GivenANuGetCachePrimer.cs @@ -26,7 +26,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests private Mock _nugetPackagesArchiverMock; private Mock _nugetCacheSentinel; - private CliFallbackFolderPathCalculator _cliFallbackFolderPathCalculator; + private CliFolderPathCalculator _cliFolderPathCalculator; public GivenANuGetCachePrimer() { @@ -40,12 +40,12 @@ namespace Microsoft.DotNet.Configurer.UnitTests _nugetCacheSentinel = new Mock(); - _cliFallbackFolderPathCalculator = new CliFallbackFolderPathCalculator(); + _cliFolderPathCalculator = new CliFolderPathCalculator(); var nugetCachePrimer = new NuGetCachePrimer( _nugetPackagesArchiverMock.Object, _nugetCacheSentinel.Object, - _cliFallbackFolderPathCalculator, + _cliFolderPathCalculator, _fileSystemMock.File); nugetCachePrimer.PrimeCache(); @@ -63,7 +63,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests var nugetCachePrimer = new NuGetCachePrimer( nugetPackagesArchiverMock.Object, _nugetCacheSentinel.Object, - _cliFallbackFolderPathCalculator, + _cliFolderPathCalculator, fileSystemMock.File); nugetCachePrimer.PrimeCache(); @@ -75,7 +75,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests public void It_extracts_the_archive_to_the_fallback_folder() { _nugetPackagesArchiverMock.Verify(n => - n.ExtractArchive(_cliFallbackFolderPathCalculator.CliFallbackFolderPath), + n.ExtractArchive(_cliFolderPathCalculator.CliFallbackFolderPath), Times.Exactly(1)); } @@ -95,7 +95,7 @@ namespace Microsoft.DotNet.Configurer.UnitTests var nugetCachePrimer = new NuGetCachePrimer( nugetPackagesArchiveMock.Object, nugetCacheSentinel.Object, - _cliFallbackFolderPathCalculator, + _cliFolderPathCalculator, _fileSystemMock.File); Action action = () => nugetCachePrimer.PrimeCache(); diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeEnvironmentProvider.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeEnvironmentProvider.cs new file mode 100644 index 000000000..4057febef --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeEnvironmentProvider.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + internal class FakeEnvironmentProvider : IEnvironmentProvider + { + private readonly Dictionary _environmentVariables; + + public FakeEnvironmentProvider(Dictionary environmentVariables) + { + _environmentVariables = + environmentVariables ?? throw new ArgumentNullException(nameof(environmentVariables)); + } + + public IEnumerable ExecutableExtensions { get; } + + public string GetCommandPath(string commandName, params string[] extensions) + { + throw new NotImplementedException(); + } + + public string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions) + { + throw new NotImplementedException(); + } + + public string GetCommandPathFromRootPath(string rootPath, string commandName, + IEnumerable extensions) + { + throw new NotImplementedException(); + } + + public bool GetEnvironmentVariableAsBool(string name, bool defaultValue) + { + throw new NotImplementedException(); + } + + public string GetEnvironmentVariable(string name) + { + return _environmentVariables.ContainsKey(name) ? _environmentVariables[name] : ""; + } + } +} diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeFile.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeFile.cs new file mode 100644 index 000000000..a881758b9 --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeFile.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + internal class FakeFile : IFile + { + private Dictionary _files; + + public FakeFile(Dictionary files) + { + _files = files; + } + + public bool Exists(string path) + { + return _files.ContainsKey(path); + } + + public string ReadAllText(string path) + { + throw new NotImplementedException(); + } + + public Stream OpenRead(string path) + { + throw new NotImplementedException(); + } + + public Stream OpenFile(string path, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, + int bufferSize, + FileOptions fileOptions) + { + throw new NotImplementedException(); + } + + + public void CreateEmptyFile(string path) + { + _files.Add(path, String.Empty); + } + + public void WriteAllText(string path, string content) + { + _files[path] = content; + } + + public static FakeFile Empty => new FakeFile(new Dictionary()); + } +} diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeReporter.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeReporter.cs new file mode 100644 index 000000000..3075cf470 --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/FakeReporter.cs @@ -0,0 +1,28 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + internal class FakeReporter : IReporter + { + public string Message { get; private set; } = ""; + + public void WriteLine(string message) + { + Message = message; + } + + public void WriteLine() + { + throw new NotImplementedException(); + } + + public void Write(string message) + { + throw new NotImplementedException(); + } + } +} diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/LinuxEnvironmentPathTests.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/LinuxEnvironmentPathTests.cs new file mode 100644 index 000000000..910721210 --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/LinuxEnvironmentPathTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.DependencyModel.Tests; +using Xunit; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + public class LinuxEnvironmentPathTests + { + [Fact] + public void GivenEnvironmentAndReporterItCanPrintOutInstructionToAddPath() + { + var fakeReporter = new FakeReporter(); + var linuxEnvironmentPath = new LinuxEnvironmentPath( + @"executable\path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", ""} + }), + FakeFile.Empty); + + linuxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + // similar to https://code.visualstudio.com/docs/setup/mac + fakeReporter.Message.Should().Be( + $"Cannot find the tools executable path. Please ensure executable\\path is added to your PATH.{Environment.NewLine}" + + $"If you are using bash. You can do this by running the following command:{Environment.NewLine}{Environment.NewLine}" + + $"cat << EOF >> ~/.bash_profile{Environment.NewLine}" + + $"# Add .NET Core SDK tools{Environment.NewLine}" + + $"export PATH=\"$PATH:executable\\path\"{Environment.NewLine}" + + $"EOF"); + } + + [Fact] + public void GivenEnvironmentAndReporterItPrintsNothingWhenenvironmentExists() + { + var fakeReporter = new FakeReporter(); + var linuxEnvironmentPath = new LinuxEnvironmentPath( + @"executable\path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", @"executable\path"} + }), + FakeFile.Empty); + + linuxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + fakeReporter.Message.Should().BeEmpty(); + } + + [Fact] + public void GivenAddPackageExecutablePathToUserPathJustRunItPrintsInstructionToLogout() + { + var fakeReporter = new FakeReporter(); + var linuxEnvironmentPath = new LinuxEnvironmentPath( + @"executable\path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", @""} + }), + FakeFile.Empty); + linuxEnvironmentPath.AddPackageExecutablePathToUserPath(); + + linuxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + fakeReporter.Message.Should().Be("Since you just installed the .NET Core SDK, you will need to logout or restart your session before running the tool you installed."); + } + } +} diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/Microsoft.DotNet.ShellShimMaker.Tests.csproj b/test/Microsoft.DotNet.ShellShimMaker.Tests/Microsoft.DotNet.ShellShimMaker.Tests.csproj new file mode 100644 index 000000000..3a9912ff0 --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/Microsoft.DotNet.ShellShimMaker.Tests.csproj @@ -0,0 +1,25 @@ + + + $(CliTargetFramework) + $(CLI_SharedFrameworkVersion) + ../../tools/Key.snk + true + true + $(AssetTargetFallback);dotnet5.4;portable-net451+win8 + + + + + + + + + + + + + + + + + diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/OsxEnvironmentPathTests.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/OsxEnvironmentPathTests.cs new file mode 100644 index 000000000..23b5e9462 --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/OsxEnvironmentPathTests.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using FluentAssertions; +using Microsoft.DotNet.Tools.Test.Utilities; +using Microsoft.Extensions.DependencyModel.Tests; +using Xunit; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + public class OsxEnvironmentPathTests + { + [Fact] + public void GivenEnvironmentAndReporterItCanPrintOutInstructionToAddPath() + { + var fakeReporter = new FakeReporter(); + var osxEnvironmentPath = new OSXEnvironmentPath( + @"~/executable/path", + @"/Users/name/executable/path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", ""} + }), + FakeFile.Empty); + + osxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + // similar to https://code.visualstudio.com/docs/setup/mac + fakeReporter.Message.Should().Be( + $"Cannot find the tools executable path. Please ensure /Users/name/executable/path is added to your PATH.{Environment.NewLine}" + + $"If you are using bash, You can do this by running the following command:{Environment.NewLine}{Environment.NewLine}" + + $"cat << EOF >> ~/.bash_profile{Environment.NewLine}" + + $"# Add .NET Core SDK tools{Environment.NewLine}" + + $"export PATH=\"$PATH:/Users/name/executable/path\"{Environment.NewLine}" + + $"EOF"); + } + + [Theory] + [InlineData("/Users/name/executable/path")] + [InlineData("~/executable/path")] + public void GivenEnvironmentAndReporterItPrintsNothingWhenenvironmentExists(string existingPath) + { + var fakeReporter = new FakeReporter(); + var osxEnvironmentPath = new OSXEnvironmentPath( + @"~/executable/path", + @"/Users/name/executable/path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", existingPath} + }), + FakeFile.Empty); + + osxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + fakeReporter.Message.Should().BeEmpty(); + } + + [Fact] + public void GivenAddPackageExecutablePathToUserPathJustRunItPrintsInstructionToLogout() + { + var fakeReporter = new FakeReporter(); + var osxEnvironmentPath = new OSXEnvironmentPath( + @"~/executable/path", + @"/Users/name/executable/path", + fakeReporter, + new FakeEnvironmentProvider( + new Dictionary + { + {"PATH", @""} + }), + FakeFile.Empty); + osxEnvironmentPath.AddPackageExecutablePathToUserPath(); + + osxEnvironmentPath.PrintAddPathInstructionIfPathDoesNotExist(); + + fakeReporter.Message.Should().Be( + "Since you just installed the .NET Core SDK, you will need to reopen terminal before running the tool you installed."); + } + } +} diff --git a/test/Microsoft.DotNet.ShellShimMaker.Tests/ShellShimMakerTests.cs b/test/Microsoft.DotNet.ShellShimMaker.Tests/ShellShimMakerTests.cs new file mode 100644 index 000000000..d9391729e --- /dev/null +++ b/test/Microsoft.DotNet.ShellShimMaker.Tests/ShellShimMakerTests.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using FluentAssertions; +using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.Tools.Test.Utilities; +using Xunit; + +namespace Microsoft.DotNet.ShellShimMaker.Tests +{ + public class ShellShimMakerTests : TestBase + { + private readonly string _pathToPlaceShim; + + public ShellShimMakerTests() + { + _pathToPlaceShim = Path.GetTempPath(); + } + + [Fact] + public void GivenAnExecutablePathItCanGenerateShimFile() + { + var outputDll = MakeHelloWorldExecutableDll(); + + var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); + + shellShimMaker.CreateShim( + outputDll.FullName, + shellCommandName); + var stdOut = ExecuteInShell(shellCommandName); + + stdOut.Should().Contain("Hello World"); + } + + [Fact] + public void GivenAnExecutablePathWithExistingSameNameShimItThrows() + { + var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); + + MakeNameConflictingCommand(_pathToPlaceShim, shellCommandName); + + var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + + Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName); + a.ShouldThrow() + .And.Message + .Should().Contain( + $"Failed to install tool {shellCommandName}. A command with the same name already exists."); + } + + + [Fact] + public void GivenAnExecutablePathWithoutExistingSameNameShimItShouldNotThrow() + { + var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); + + var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + + Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName); + a.ShouldNotThrow(); + } + + private static void MakeNameConflictingCommand(string pathToPlaceShim, string shellCommandName) + { + File.WriteAllText(Path.Combine(pathToPlaceShim, shellCommandName), string.Empty); + } + + private string ExecuteInShell(string shellCommandName) + { + ProcessStartInfo processStartInfo; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + processStartInfo = new ProcessStartInfo + { + FileName = "CMD.exe", + Arguments = $"/C {shellCommandName}", + UseShellExecute = false + }; + } + else + { + processStartInfo = new ProcessStartInfo + { + FileName = "sh", + Arguments = shellCommandName, + UseShellExecute = false + }; + } + processStartInfo.WorkingDirectory = _pathToPlaceShim; + processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath); + + processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr); + + stdErr.Should().BeEmpty(); + + return stdOut ?? ""; + } + + private static FileInfo MakeHelloWorldExecutableDll() + { + const string testAppName = "TestAppSimple"; + const string emptySpaceToTestSpaceInPath = " "; + TestAssetInstance testInstance = TestAssets.Get(testAppName) + .CreateInstance(testAppName + emptySpaceToTestSpaceInPath) + .UseCurrentRuntimeFrameworkVersion() + .WithRestoreFiles() + .WithBuildFiles(); + + var configuration = Environment.GetEnvironmentVariable("CONFIGURATION") ?? "Debug"; + + FileInfo outputDll = testInstance.Root.GetDirectory("bin", configuration) + .GetDirectories().Single() + .GetFile($"{testAppName}.dll"); + + return outputDll; + } + } +} diff --git a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs index 4fd1fb660..18e850ca4 100644 --- a/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs +++ b/test/dotnet.Tests/GivenThatTheUserIsRunningDotNetForTheFirstTime.cs @@ -28,6 +28,8 @@ namespace Microsoft.DotNet.Tests _testDirectory = TestAssets.CreateTestDirectory("Dotnet_first_time_experience_tests").FullName; var testNuGetHome = Path.Combine(_testDirectory, "nuget_home"); var cliTestFallbackFolder = Path.Combine(testNuGetHome, ".dotnet", "NuGetFallbackFolder"); + var profiled = Path.Combine(_testDirectory, "profile.d"); + var pathsd = Path.Combine(_testDirectory, "paths.d"); var command = new DotnetCommand() .WithWorkingDirectory(_testDirectory); @@ -35,6 +37,8 @@ namespace Microsoft.DotNet.Tests command.Environment["USERPROFILE"] = testNuGetHome; command.Environment["APPDATA"] = testNuGetHome; command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = cliTestFallbackFolder; + command.Environment["DOTNET_CLI_TEST_LINUX_PROFILED_PATH"] = profiled; + command.Environment["DOTNET_CLI_TEST_OSX_PATHSD_PATH"] = pathsd; command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; command.Environment["SkipInvalidConfigurations"] = "true"; @@ -92,6 +96,8 @@ namespace Microsoft.DotNet.Tests public void ItDoesNotCreateAFirstUseSentinelFileUnderTheDotDotNetFolderWhenInternalReportInstallSuccessIsInvoked() { var emptyHome = Path.Combine(_testDirectory, "empty_home"); + var profiled = Path.Combine(_testDirectory, "profile.d"); + var pathsd = Path.Combine(_testDirectory, "paths.d"); var command = new DotnetCommand() .WithWorkingDirectory(_testDirectory); @@ -100,6 +106,8 @@ namespace Microsoft.DotNet.Tests command.Environment["APPDATA"] = emptyHome; command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName; command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; + command.Environment["DOTNET_CLI_TEST_LINUX_PROFILED_PATH"] = profiled; + command.Environment["DOTNET_CLI_TEST_OSX_PATHSD_PATH"] = pathsd; // Disable to prevent the creation of the .dotnet folder by optimizationdata. command.Environment["DOTNET_DISABLE_MULTICOREJIT"] = "true"; command.Environment["SkipInvalidConfigurations"] = "true"; @@ -116,6 +124,8 @@ namespace Microsoft.DotNet.Tests { var newHome = Path.Combine(_testDirectory, "new_home"); var newHomeFolder = new DirectoryInfo(Path.Combine(newHome, ".dotnet")); + var profiled = Path.Combine(_testDirectory, "profile.d"); + var pathsd = Path.Combine(_testDirectory, "paths.d"); var command = new DotnetCommand() .WithWorkingDirectory(_testDirectory); @@ -123,6 +133,8 @@ namespace Microsoft.DotNet.Tests command.Environment["USERPROFILE"] = newHome; command.Environment["APPDATA"] = newHome; command.Environment["DOTNET_CLI_TEST_FALLBACKFOLDER"] = _nugetFallbackFolder.FullName; + command.Environment["DOTNET_CLI_TEST_LINUX_PROFILED_PATH"] = profiled; + command.Environment["DOTNET_CLI_TEST_OSX_PATHSD_PATH"] = pathsd; command.Environment["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = ""; command.Environment["SkipInvalidConfigurations"] = "true";