consume bring your own shim(byos) (#9018)

If there are shims packaged by convention in nupkg. Shim Repository will simply copy it to the right location.

The query interface ToolPackageInstance will be in charge of finding the shim folder and filter the right RID. Shim Repository will pick the right file after the folder is located since Shim Repository knows the shim name and it also book keep the files at uninstallation.
During development, due to the wrong adapter level. The mock duplicated too much logic. So, I corrected the abstraction level to lower (only create shim). And replaced the existing mock with a much smaller one without any atomic control and file move, copy logic. At the same time. The chmod, which is a IO action, causes problem during tests. So I added adapter layer to it and put it in Util.
This commit is contained in:
William Li 2018-04-10 15:42:50 -07:00 committed by GitHub
parent 98a1ee6a67
commit b0ee5db411
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 898 additions and 208 deletions

View file

@ -0,0 +1,29 @@
// 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.Runtime.InteropServices;
namespace Microsoft.DotNet.Cli.Utils
{
internal class FilePermissionSetter : IFilePermissionSetter
{
public void SetUserExecutionPermission(string path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
CommandResult result = new CommandFactory()
.Create("chmod", new[] { "u+x", path })
.CaptureStdOut()
.CaptureStdErr()
.Execute();
if (result.ExitCode != 0)
{
throw new FilePermissionSettingException(result.StdErr);
}
}
}
}

View file

@ -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 System.Runtime.Serialization;
namespace Microsoft.DotNet.Cli.Utils
{
[Serializable]
internal class FilePermissionSettingException : Exception
{
public FilePermissionSettingException()
{
}
public FilePermissionSettingException(string message) : base(message)
{
}
public FilePermissionSettingException(string message, Exception innerException) : base(message, innerException)
{
}
protected FilePermissionSettingException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View file

@ -0,0 +1,7 @@
namespace Microsoft.DotNet.Cli.Utils
{
internal interface IFilePermissionSetter
{
void SetUserExecutionPermission(string path);
}
}

View file

@ -51,6 +51,11 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
File.Move(source, destination);
}
public void Copy(string sourceFileName, string destFileName)
{
File.Copy(sourceFileName, destFileName);
}
public void Delete(string path)
{
File.Delete(path);

View file

@ -27,6 +27,8 @@ namespace Microsoft.Extensions.EnvironmentAbstractions
void Move(string source, string destination);
void Copy(string source, string destination);
void Delete(string path);
}
}

View file

@ -640,4 +640,10 @@ setx PATH "%PATH%;{0}"
<data name="FormatVersionIsMissing" xml:space="preserve">
<value>Format version is missing. This tool may not be supported in this SDK version. Please contact the author of the tool.</value>
</data>
<data name="MoreThanOnePackagedShimAvailable" xml:space="preserve">
<value>More than one packaged shim is available: {0}.</value>
</data>
<data name="FailedToReadNuGetLockFile" xml:space="preserve">
<value>Failed to read NuGet LockFile for tool package '{0}': {1}</value>
</data>
</root>

View file

@ -0,0 +1,53 @@
// 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.IO;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.DotNet.Tools.Common;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.ShellShim
{
internal class AppHostShellShimMaker : IAppHostShellShimMaker
{
private const string ApphostNameWithoutExtension = "apphost";
private readonly string _appHostSourceDirectory;
private readonly IFilePermissionSetter _filePermissionSetter;
public AppHostShellShimMaker(string appHostSourceDirectory = null, IFilePermissionSetter filePermissionSetter = null)
{
_appHostSourceDirectory =
appHostSourceDirectory
?? Path.Combine(ApplicationEnvironment.ApplicationBasePath, "AppHostTemplate");
_filePermissionSetter =
filePermissionSetter
?? new FilePermissionSetter();
}
public void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath)
{
string appHostSourcePath;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe");
}
else
{
appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension);
}
var appHostDestinationFilePath = shimPath.Value;
var appBinaryFilePath = PathUtility.GetRelativePath(appHostDestinationFilePath, entryPoint.Value);
EmbedAppNameInHost.EmbedAndReturnModifiedAppHostPath(
appHostSourceFilePath: appHostSourcePath,
appHostDestinationFilePath: appHostDestinationFilePath,
appBinaryFilePath: appBinaryFilePath);
_filePermissionSetter.SetUserExecutionPermission(appHostDestinationFilePath);
}
}
}

View file

@ -0,0 +1,9 @@
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.ShellShim
{
internal interface IAppHostShellShimMaker
{
void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath);
}
}

View file

@ -1,12 +1,13 @@
// 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.Collections.Generic;
using Microsoft.Extensions.EnvironmentAbstractions;
namespace Microsoft.DotNet.ShellShim
{
internal interface IShellShimRepository
{
void CreateShim(FilePath targetExecutablePath, string commandName);
void CreateShim(FilePath targetExecutablePath, string commandName, IReadOnlyList<FilePath> packagedShims = null);
void RemoveShim(string commandName);
}

View file

@ -21,16 +21,24 @@ namespace Microsoft.DotNet.ShellShim
private const string ApphostNameWithoutExtension = "apphost";
private readonly DirectoryPath _shimsDirectory;
private readonly string _appHostSourceDirectory;
private readonly IFileSystem _fileSystem;
private readonly IAppHostShellShimMaker _appHostShellShimMaker;
private readonly IFilePermissionSetter _filePermissionSetter;
public ShellShimRepository(DirectoryPath shimsDirectory, string appHostSourcePath = null)
public ShellShimRepository(
DirectoryPath shimsDirectory,
string appHostSourceDirectory = null,
IFileSystem fileSystem = null,
IAppHostShellShimMaker appHostShellShimMaker = null,
IFilePermissionSetter filePermissionSetter = null)
{
_shimsDirectory = shimsDirectory;
_appHostSourceDirectory = appHostSourcePath ?? Path.Combine(ApplicationEnvironment.ApplicationBasePath,
"AppHostTemplate");
_fileSystem = fileSystem ?? new FileSystemWrapper();
_appHostShellShimMaker = appHostShellShimMaker ?? new AppHostShellShimMaker(appHostSourceDirectory: appHostSourceDirectory);
_filePermissionSetter = filePermissionSetter ?? new FilePermissionSetter();
}
public void CreateShim(FilePath targetExecutablePath, string commandName)
public void CreateShim(FilePath targetExecutablePath, string commandName, IReadOnlyList<FilePath> packagedShims = null)
{
if (string.IsNullOrEmpty(targetExecutablePath.Value))
{
@ -54,19 +62,27 @@ namespace Microsoft.DotNet.ShellShim
{
try
{
if (!Directory.Exists(_shimsDirectory.Value))
if (!_fileSystem.Directory.Exists(_shimsDirectory.Value))
{
Directory.CreateDirectory(_shimsDirectory.Value);
_fileSystem.Directory.CreateDirectory(_shimsDirectory.Value);
}
CreateApphostShim(
commandName,
entryPoint: targetExecutablePath);
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (TryGetPackagedShim(packagedShims, commandName, out FilePath? packagedShim))
{
SetUserExecutionPermission(GetShimPath(commandName));
_fileSystem.File.Copy(packagedShim.Value.Value, GetShimPath(commandName).Value);
_filePermissionSetter.SetUserExecutionPermission(GetShimPath(commandName).Value);
}
else
{
_appHostShellShimMaker.CreateApphostShellShim(
targetExecutablePath,
GetShimPath(commandName));
}
}
catch (FilePermissionSettingException ex)
{
throw new ShellShimException(
string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, ex.Message));
}
catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
{
@ -80,7 +96,7 @@ namespace Microsoft.DotNet.ShellShim
}
},
rollback: () => {
foreach (var file in GetShimFiles(commandName).Where(f => File.Exists(f.Value)))
foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value)))
{
File.Delete(file.Value);
}
@ -94,10 +110,10 @@ namespace Microsoft.DotNet.ShellShim
action: () => {
try
{
foreach (var file in GetShimFiles(commandName).Where(f => File.Exists(f.Value)))
foreach (var file in GetShimFiles(commandName).Where(f => _fileSystem.File.Exists(f.Value)))
{
var tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
File.Move(file.Value, tempPath);
_fileSystem.File.Move(file.Value, tempPath);
files[file.Value] = tempPath;
}
}
@ -115,38 +131,17 @@ namespace Microsoft.DotNet.ShellShim
commit: () => {
foreach (var value in files.Values)
{
File.Delete(value);
_fileSystem.File.Delete(value);
}
},
rollback: () => {
foreach (var kvp in files)
{
File.Move(kvp.Value, kvp.Key);
_fileSystem.File.Move(kvp.Value, kvp.Key);
}
});
}
private void CreateApphostShim(string commandName, FilePath entryPoint)
{
string appHostSourcePath;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe");
}
else
{
appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension);
}
var appHostDestinationFilePath = GetShimPath(commandName).Value;
var appBinaryFilePath = PathUtility.GetRelativePath(appHostDestinationFilePath, entryPoint.Value);
EmbedAppNameInHost.EmbedAndReturnModifiedAppHostPath(
appHostSourceFilePath: appHostSourcePath,
appHostDestinationFilePath: appHostDestinationFilePath,
appBinaryFilePath: appBinaryFilePath);
}
private class StartupOptions
{
public string appRoot { get; set; }
@ -159,7 +154,7 @@ namespace Microsoft.DotNet.ShellShim
private bool ShimExists(string commandName)
{
return GetShimFiles(commandName).Any(p => File.Exists(p.Value));
return GetShimFiles(commandName).Any(p => _fileSystem.File.Exists(p.Value));
}
private IEnumerable<FilePath> GetShimFiles(string commandName)
@ -184,24 +179,37 @@ namespace Microsoft.DotNet.ShellShim
}
}
private static void SetUserExecutionPermission(FilePath path)
private bool TryGetPackagedShim(
IReadOnlyList<FilePath> packagedShims,
string commandName,
out FilePath? packagedShim)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
packagedShim = null;
if (packagedShims != null && packagedShims.Count > 0)
{
return;
FilePath[] candidatepackagedShim =
packagedShims
.Where(s => string.Equals(
Path.GetFileName(s.Value),
Path.GetFileName(GetShimPath(commandName).Value))).ToArray();
if (candidatepackagedShim.Length > 1)
{
throw new ShellShimException(
string.Format(
CommonLocalizableStrings.MoreThanOnePackagedShimAvailable,
string.Join(';', candidatepackagedShim)));
}
if (candidatepackagedShim.Length == 1)
{
packagedShim = candidatepackagedShim.Single();
return true;
}
}
CommandResult result = new CommandFactory()
.Create("chmod", new[] { "u+x", path.Value })
.CaptureStdOut()
.CaptureStdErr()
.Execute();
if (result.ExitCode != 0)
{
throw new ShellShimException(
string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, result.StdErr));
}
return false;
}
}
}

View file

@ -20,6 +20,8 @@ namespace Microsoft.DotNet.ToolPackage
IEnumerable<string> Warnings { get; }
IReadOnlyList<FilePath> PackagedShims { get; }
void Uninstall();
}
}

View file

@ -1,6 +1,7 @@
// 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.Collections.Generic;
using System.Linq;
using NuGet.ProjectModel;
@ -22,36 +23,60 @@ namespace Microsoft.DotNet.ToolPackage
string[] entryPointPathInArray = SplitPathByDirectorySeparator(targetRelativeFilePath);
return entryPointPathInArray.Length >= 1
&& PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder()
&& SubPathMatchesTargetFilePath();
&& PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder(
pathInLockFilePathInArray,
entryPointPathInArray)
&& SubPathMatchesTargetFilePath(pathInLockFilePathInArray, entryPointPathInArray);
}
bool SubPathMatchesTargetFilePath()
/// <summary>
/// Check if LockFileItem is under targetRelativePath directory.
/// The path in LockFileItem is in pattern tools/TFM/RID/my/tool.dll. Tools/TFM/RID is selected by NuGet.
/// And there will be only one TFM/RID combination.
/// When "my/folder/of/tool/tools.dll" part under targetRelativePath "my/folder/of" or "my/folder", return true.
/// </summary>
internal static bool MatchesDirectoryPath(LockFileItem lockFileItem, string targetRelativePath)
{
string[] pathInLockFilePathInArray = SplitPathByDirectorySeparator(lockFileItem.Path);
string[] targetDirectoryPathInArray = SplitPathByDirectorySeparator(targetRelativePath);
return pathInLockFilePathInArray[0] == "tools"
&& SubPathMatchesTargetFilePath(pathInLockFilePathInArray, targetDirectoryPathInArray);
}
private static bool SubPathMatchesTargetFilePath(string[] pathInLockFilePathInArray, string[] targetInArray)
{
string[] pathAfterToolsTfmRid = pathInLockFilePathInArray.Skip(3).ToArray();
return !targetInArray
.Where((directoryOnEveryLevel, i) => directoryOnEveryLevel != pathAfterToolsTfmRid[i])
.Any();
}
private static bool PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder(
string[] pathInLockFilePathInArray,
string[] targetInArray)
{
if (pathInLockFilePathInArray.Length - targetInArray.Length != 3)
{
string[] pathAfterToolsTfmRid = pathInLockFilePathInArray.Skip(3).ToArray();
return !pathAfterToolsTfmRid
.Where((directoryOnEveryLevel, i) => directoryOnEveryLevel != entryPointPathInArray[i])
.Any();
return false;
}
bool PathInLockFileDirectoriesStartWithToolsAndFollowsTwoSubFolder()
if (pathInLockFilePathInArray[0] != "tools")
{
if (pathInLockFilePathInArray.Length - entryPointPathInArray.Length != 3)
{
return false;
}
if (pathInLockFilePathInArray[0] != "tools")
{
return false;
}
return true;
return false;
}
string[] SplitPathByDirectorySeparator(string path)
return true;
}
private static string[] SplitPathByDirectorySeparator(string path)
{
if (string.IsNullOrEmpty(path))
{
return path.Split('\\', '/');
return new string[0];
}
return path.Split('\\', '/');
}
}
}

View file

@ -7,13 +7,17 @@ using Microsoft.DotNet.Tools;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.ProjectModel;
using NuGet.Versioning;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.ToolPackage
{
// This is named "ToolPackageInstance" because "ToolPackage" would conflict with the namespace
internal class ToolPackageInstance : IToolPackage
{
private const string PackagedShimsDirectoryConvention = "shims";
public IEnumerable<string> Warnings => _toolConfiguration.Value.Warnings;
public PackageId Id { get; private set; }
public NuGetVersion Version { get; private set; }
@ -28,12 +32,21 @@ namespace Microsoft.DotNet.ToolPackage
}
}
public IReadOnlyList<FilePath> PackagedShims
{
get
{
return _packagedShims.Value;
}
}
private const string AssetsFileName = "project.assets.json";
private const string ToolSettingsFileName = "DotnetToolSettings.xml";
private IToolPackageStore _store;
private Lazy<IReadOnlyList<CommandSettings>> _commands;
private Lazy<ToolConfiguration> _toolConfiguration;
private Lazy<IReadOnlyList<FilePath>> _packagedShims;
public ToolPackageInstance(
IToolPackageStore store,
@ -43,6 +56,7 @@ namespace Microsoft.DotNet.ToolPackage
{
_store = store ?? throw new ArgumentNullException(nameof(store));
_commands = new Lazy<IReadOnlyList<CommandSettings>>(GetCommands);
_packagedShims = new Lazy<IReadOnlyList<FilePath>>(GetPackagedShims);
Id = id;
Version = version ?? throw new ArgumentNullException(nameof(version));
@ -107,11 +121,11 @@ namespace Microsoft.DotNet.ToolPackage
try
{
var commands = new List<CommandSettings>();
var lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value);
var library = FindLibraryInLockFile(lockFile);
LockFile lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value);
LockFileTargetLibrary library = FindLibraryInLockFile(lockFile);
ToolConfiguration configuration = _toolConfiguration.Value;
var entryPointFromLockFile = FindItemInTargetLibrary(library, configuration.ToolAssemblyEntryPoint);
LockFileItem entryPointFromLockFile = FindItemInTargetLibrary(library, configuration.ToolAssemblyEntryPoint);
if (entryPointFromLockFile == null)
{
throw new ToolConfigurationException(
@ -125,11 +139,7 @@ namespace Microsoft.DotNet.ToolPackage
commands.Add(new CommandSettings(
configuration.CommandName,
"dotnet",
PackageDirectory
.WithSubDirectories(
Id.ToString(),
library.Version.ToNormalizedString())
.WithFile(entryPointFromLockFile.Path)));
LockFileRelativePathToFullFilePath(entryPointFromLockFile.Path, library)));
return commands;
}
@ -143,6 +153,15 @@ namespace Microsoft.DotNet.ToolPackage
}
}
private FilePath LockFileRelativePathToFullFilePath(string lockFileRelativePath, LockFileTargetLibrary library)
{
return PackageDirectory
.WithSubDirectories(
Id.ToString(),
library.Version.ToNormalizedString())
.WithFile(lockFileRelativePath);
}
private ToolConfiguration GetToolConfiguration()
{
try
@ -161,6 +180,55 @@ namespace Microsoft.DotNet.ToolPackage
}
}
private IReadOnlyList<FilePath> GetPackagedShims()
{
LockFileTargetLibrary library;
try
{
LockFile lockFile = new LockFileFormat().Read(PackageDirectory.WithFile(AssetsFileName).Value);
library = FindLibraryInLockFile(lockFile);
}
catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException)
{
throw new ToolPackageException(
string.Format(
CommonLocalizableStrings.FailedToReadNuGetLockFile,
Id,
ex.Message),
ex);
}
IEnumerable<LockFileItem> filesUnderShimsDirectory = library
?.ToolsAssemblies
?.Where(t => LockFileMatcher.MatchesDirectoryPath(t, PackagedShimsDirectoryConvention));
if (filesUnderShimsDirectory == null)
{
return Array.Empty<FilePath>();
}
IEnumerable<string> allAvailableShimRuntimeIdentifiers = filesUnderShimsDirectory
.Select(f => f.Path.Split('\\', '/')?[4]) // ex: "tools/netcoreapp2.1/any/shims/osx-x64/demo" osx-x64 is at [4]
.Where(f => !string.IsNullOrEmpty(f));
if (new FrameworkDependencyFile().TryGetMostFitRuntimeIdentifier(
DotnetFiles.VersionFileObject.BuildRid,
allAvailableShimRuntimeIdentifiers.ToArray(),
out var mostFitRuntimeIdentifier))
{
return library
?.ToolsAssemblies
?.Where(l =>
LockFileMatcher.MatchesDirectoryPath(l, $"{PackagedShimsDirectoryConvention}/{mostFitRuntimeIdentifier}"))
.Select(l => LockFileRelativePathToFullFilePath(l.Path, library)).ToArray()
?? Array.Empty<FilePath>();
}
else
{
return Array.Empty<FilePath>();
}
}
private ToolConfiguration DeserializeToolConfiguration(string ToolSettingsFileName, LockFileTargetLibrary library)
{
var dotnetToolSettings = FindItemInTargetLibrary(library, ToolSettingsFileName);

View file

@ -142,7 +142,7 @@ namespace Microsoft.DotNet.Tools.Tool.Install
foreach (var command in package.Commands)
{
shellShimRepository.CreateShim(command.Executable, command.Name);
shellShimRepository.CreateShim(command.Executable, command.Name, package.PackagedShims);
}
scope.Complete();

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -883,6 +883,16 @@ setx PATH "%PATH%;{0}"
<target state="new">Failed to add '{0}' to the PATH environment variable. Please add this directory to your PATH to use tools installed with 'dotnet tool install'.</target>
<note />
</trans-unit>
<trans-unit id="MoreThanOnePackagedShimAvailable">
<source>More than one packaged shim is available: {0}.</source>
<target state="new">More than one packaged shim is available: {0}.</target>
<note />
</trans-unit>
<trans-unit id="FailedToReadNuGetLockFile">
<source>Failed to read NuGet LockFile for tool package '{0}': {1}</source>
<target state="new">Failed to read NuGet LockFile for tool package '{0}': {1}</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

View file

@ -134,6 +134,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
{
throw new UnauthorizedAccessException();
}
public void Copy(string source, string destination)
{
throw new UnauthorizedAccessException();
}
}
private class NoPermissionDirectoryFake : IDirectory

View file

@ -286,6 +286,11 @@ namespace Microsoft.DotNet.Configurer.UnitTests
{
throw new NotImplementedException();
}
public void Copy(string source, string destination)
{
throw new NotImplementedException();
}
}
private class MockStream : MemoryStream

View file

@ -119,7 +119,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -161,7 +161,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -198,7 +198,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -223,7 +223,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -252,7 +252,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -288,7 +288,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
IShellShimRepository shellShimRepository;
if (testMockBehaviorIsInSync)
{
shellShimRepository = new ShellShimRepositoryMock(new DirectoryPath(pathToShim));
shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
}
else
{
@ -315,6 +315,67 @@ namespace Microsoft.DotNet.ShellShim.Tests
Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty();
}
[Fact]
public void WhenPackagedShimProvidedItCopies()
{
const string tokenToIdentifyCopiedShim = "packagedShim";
var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName();
var pathToShim = GetNewCleanFolderUnderTempRoot();
var packagedShimFolder = GetNewCleanFolderUnderTempRoot();
var dummyShimPath = Path.Combine(packagedShimFolder, shellCommandName);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
dummyShimPath = dummyShimPath + ".exe";
}
File.WriteAllText(dummyShimPath, tokenToIdentifyCopiedShim);
ShellShimRepository shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
shellShimRepository.CreateShim(
new FilePath("dummy.dll"),
shellCommandName,
new[] {new FilePath(dummyShimPath)});
var createdShim = Directory.EnumerateFileSystemEntries(pathToShim).Single();
File.ReadAllText(createdShim).Should().Contain(tokenToIdentifyCopiedShim);
}
[Fact]
public void WhenMutipleSameNamePackagedShimProvidedItThrows()
{
const string tokenToIdentifyCopiedShim = "packagedShim";
var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName();
var pathToShim = GetNewCleanFolderUnderTempRoot();
var packagedShimFolder = GetNewCleanFolderUnderTempRoot();
var dummyShimPath = Path.Combine(packagedShimFolder, shellCommandName);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
dummyShimPath = dummyShimPath + ".exe";
}
File.WriteAllText(dummyShimPath, tokenToIdentifyCopiedShim);
ShellShimRepository shellShimRepository = GetShellShimRepositoryWithMockMaker(pathToShim);
FilePath[] filePaths = new[] { new FilePath(dummyShimPath), new FilePath("path" + dummyShimPath) };
Action a = () => shellShimRepository.CreateShim(
new FilePath("dummy.dll"),
shellCommandName,
new[] { new FilePath(dummyShimPath), new FilePath("path" + dummyShimPath) });
a.ShouldThrow<ShellShimException>()
.And.Message
.Should().Contain(
string.Format(
CommonLocalizableStrings.MoreThanOnePackagedShimAvailable,
string.Join(';', filePaths)));
}
private static void MakeNameConflictingCommand(string pathToPlaceShim, string shellCommandName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -440,5 +501,12 @@ namespace Microsoft.DotNet.ShellShim.Tests
return CleanFolderUnderTempRoot.FullName;
}
private ShellShimRepository GetShellShimRepositoryWithMockMaker(string pathToShim)
{
return new ShellShimRepository(
new DirectoryPath(pathToShim),
appHostShellShimMaker: new AppHostShellShimMakerMock());
}
}
}

View file

@ -10,7 +10,6 @@ namespace Microsoft.DotNet.ToolPackage.Tests
{
public class LockFileMatcherTests : TestBase
{
[Theory]
[InlineData("tools/netcoreapp1.1/any/tool.dll", "tool.dll", true)]
[InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "subDirectory/tool.dll", true)]
@ -24,5 +23,20 @@ namespace Microsoft.DotNet.ToolPackage.Tests
LockFileMatcher.MatchesFile(new LockFileItem(pathInLockFileItem), targetRelativeFilePath)
.Should().Be(shouldMatch);
}
[Theory]
[InlineData("tools/netcoreapp1.1/any/tool.dll", "", true)]
[InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "subDirectory", true)]
[InlineData(@"tools\netcoreapp1.1\any\subDirectory\tool.dll", "sub", false)]
[InlineData("tools/netcoreapp1.1/any/subDirectory/tool.dll", "any/subDirectory", false)]
public void MatchesDirectoryPathTests(
string pathInLockFileItem,
string targetRelativeFilePath,
bool shouldMatch)
{
LockFileMatcher.MatchesDirectoryPath(new LockFileItem(pathInLockFileItem), targetRelativeFilePath)
.Should().Be(shouldMatch);
}
}
}

View file

@ -45,6 +45,11 @@
<Content Remove="SampleGlobalTool/**" />
<EmbeddedResource Remove="SampleGlobalTool/**" />
<None Remove="SampleGlobalTool/**" />
<Compile Remove="SampleGlobalToolWithShim/**" />
<Content Remove="SampleGlobalToolWithShim/**" />
<EmbeddedResource Remove="SampleGlobalToolWithShim/**" />
<None Remove="SampleGlobalToolWithShim/**" />
</ItemGroup>
<UsingTask TaskName="DownloadFile" AssemblyFile="$(CLIBuildDll)" />
@ -59,4 +64,17 @@
<MSBuild BuildInParallel="False" Projects="SampleGlobalTool/consoledemo.csproj" Targets="pack" Properties="Configuration=Release;NuspecFile=includepublish.nuspec;NuspecBasePath=$(testAssetSourceRoot);PackageOutputPath=$(OutputPath)/TestAssetLocalNugetFeed">
</MSBuild>
</Target>
<Target Name="CreateNupkgWithShimFromSource" BeforeTargets="Build">
<PropertyGroup>
<testAssetSourceRoot>$(BaseOutputPath)/TestAsset/SampleGlobalToolWithShim</testAssetSourceRoot>
</PropertyGroup>
<Copy SourceFiles="SampleGlobalToolWithShim/DotnetToolSettings.xml" DestinationFolder="$(testAssetSourceRoot)" />
<Copy SourceFiles="SampleGlobalToolWithShim/dummyshim" DestinationFolder="$(testAssetSourceRoot)" />
<Copy SourceFiles="SampleGlobalToolWithShim/dummyshim.exe" DestinationFolder="$(testAssetSourceRoot)" />
<MSBuild BuildInParallel="False" Projects="SampleGlobalToolWithShim/consoledemo.csproj" Targets="Restore;Build;Publish" Properties="Configuration=Release;BaseOutputPath=$(testAssetSourceRoot)/bin/">
</MSBuild>
<MSBuild BuildInParallel="False" Projects="SampleGlobalToolWithShim/consoledemo.csproj" Targets="pack" Properties="Configuration=Release;NuspecFile=includepublish.nuspec;NuspecBasePath=$(testAssetSourceRoot);PackageOutputPath=$(OutputPath)/TestAssetLocalNugetFeed">
</MSBuild>
</Target>
</Project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<DotNetCliTool Version="1" >
<Commands>
<Command Name="demo" EntryPoint="consoledemo.dll" Runner="dotnet" />
</Commands>
</DotNetCliTool>

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
namespace consoledemo
{
class Program
{
static void Main(string[] args)
{
var greeting = "Hello World from Global Tool";
Console.WriteLine(greeting);
}
}
}

View file

@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<AssemblyName>consoledemo</AssemblyName>
</PropertyGroup>
</Project>

View file

@ -0,0 +1 @@
packagedshim

View file

@ -0,0 +1 @@
packagedshim

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>global.tool.console.demo.with.shim</id>
<version>1.0.4</version>
<description>test app</description>
<authors>testauthor</authors>
<packageTypes>
<packageType name="DotnetTool" />
</packageTypes>
</metadata>
<files>
<file src="bin\Release\netcoreapp2.1\publish\*.*" target="tools\netcoreapp2.1\any\" />
<file src="DotnetToolSettings.xml" target="tools\netcoreapp2.1\any\DotnetToolSettings.xml" />
<file src="dummyshim.exe" target="tools\netcoreapp2.1\any\shims\win-x64\demo.exe" />
<file src="dummyshim.exe" target="tools\netcoreapp2.1\any\shims\win-x86\demo.exe" />
<file src="dummyshim" target="tools\netcoreapp2.1\any\shims\osx-x64\demo" />
<file src="dummyshim" target="tools\netcoreapp2.1\any\shims\linux\demo" />
</files>
</package>

View file

@ -0,0 +1,130 @@
// 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 System.Linq;
using System.Reflection;
using System.Transactions;
using FluentAssertions;
using Microsoft.DotNet.Tools.Test.Utilities;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Tools;
using Microsoft.DotNet.Tools.Tool.Install;
using Microsoft.DotNet.Tools.Tests.ComponentMocks;
using Microsoft.Extensions.DependencyModel.Tests;
using Microsoft.Extensions.EnvironmentAbstractions;
using NuGet.Versioning;
using Xunit;
namespace Microsoft.DotNet.ToolPackage.Tests
{
public class ToolPackageInstanceTests : TestBase
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void GivenAnInstalledPackageUninstallRemovesThePackage(bool testMockBehaviorIsInSync)
{
var source = GetTestLocalFeedPath();
var (store, installer, reporter, fileSystem) = Setup(
useMock: testMockBehaviorIsInSync,
feeds: GetMockFeedsForSource(source));
var package = installer.InstallPackage(
packageId: TestPackageId,
versionRange: VersionRange.Parse(TestPackageVersion),
targetFramework: _testTargetframework,
additionalFeeds: new[] {source});
package.PackagedShims.Should().ContainSingle(f => f.Value.Contains("demo.exe") || f.Value.Contains("demo"));
package.Uninstall();
}
private static FilePath GetUniqueTempProjectPathEachTest()
{
var tempProjectDirectory =
new DirectoryPath(Path.GetTempPath()).WithSubDirectories(Path.GetRandomFileName());
var tempProjectPath =
tempProjectDirectory.WithFile(Path.GetRandomFileName() + ".csproj");
return tempProjectPath;
}
private static IEnumerable<MockFeed> GetMockFeedsForSource(string source)
{
return new[]
{
new MockFeed
{
Type = MockFeedType.ImplicitAdditionalFeed,
Uri = source,
Packages = new List<MockFeedPackage>
{
new MockFeedPackage
{
PackageId = TestPackageId.ToString(),
Version = TestPackageVersion
}
}
}
};
}
private static (IToolPackageStore, IToolPackageInstaller, BufferedReporter, IFileSystem) Setup(
bool useMock,
IEnumerable<MockFeed> feeds = null,
FilePath? tempProject = null,
DirectoryPath? offlineFeed = null)
{
var root = new DirectoryPath(Path.Combine(TempRoot.Root, Path.GetRandomFileName()));
var reporter = new BufferedReporter();
IFileSystem fileSystem;
IToolPackageStore store;
IToolPackageInstaller installer;
if (useMock)
{
var packagedShimsMap = new Dictionary<PackageId, IReadOnlyList<FilePath>>
{
[TestPackageId] = new FilePath[] {new FilePath("path/demo.exe")}
};
fileSystem = new FileSystemMockBuilder().Build();
store = new ToolPackageStoreMock(root, fileSystem);
installer = new ToolPackageInstallerMock(
fileSystem: fileSystem,
store: store,
projectRestorer: new ProjectRestorerMock(
fileSystem: fileSystem,
reporter: reporter,
feeds: feeds),
packagedShimsMap: packagedShimsMap);
}
else
{
fileSystem = new FileSystemWrapper();
store = new ToolPackageStore(root);
installer = new ToolPackageInstaller(
store: store,
projectRestorer: new ProjectRestorer(reporter),
tempProject: tempProject ?? GetUniqueTempProjectPathEachTest(),
offlineFeed: offlineFeed ?? new DirectoryPath("does not exist"));
}
store.Root.Value.Should().Be(Path.GetFullPath(root.Value));
return (store, installer, reporter, fileSystem);
}
private static string GetTestLocalFeedPath() =>
Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestAssetLocalNugetFeed");
private readonly string _testTargetframework = BundledTargetFramework.GetTargetFrameworkMoniker();
private const string TestPackageVersion = "1.0.4";
private static readonly PackageId TestPackageId = new PackageId("global.tool.console.demo.with.shim");
}
}

View file

@ -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 Microsoft.DotNet.ShellShim;
using Microsoft.Extensions.EnvironmentAbstractions;
using Newtonsoft.Json;
namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
{
internal class AppHostShellShimMakerMock : IAppHostShellShimMaker
{
private static IFileSystem _fileSystem;
public AppHostShellShimMakerMock(IFileSystem fileSystem = null)
{
_fileSystem = fileSystem ?? new FileSystemWrapper();
}
public void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath)
{
var shim = new FakeShim
{
Runner = "dotnet",
ExecutablePath = entryPoint.Value
};
_fileSystem.File.WriteAllText(
shimPath.Value,
JsonConvert.SerializeObject(shim));
}
public class FakeShim
{
public string Runner { get; set; }
public string ExecutablePath { get; set; }
}
}
}

View file

@ -1,104 +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.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ShellShim;
using Microsoft.Extensions.EnvironmentAbstractions;
using Newtonsoft.Json;
namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
{
internal class ShellShimRepositoryMock : IShellShimRepository
{
private static IFileSystem _fileSystem;
private readonly DirectoryPath _pathToPlaceShim;
public ShellShimRepositoryMock(DirectoryPath pathToPlaceShim, IFileSystem fileSystem = null)
{
_pathToPlaceShim = pathToPlaceShim;
_fileSystem = fileSystem ?? new FileSystemWrapper();
}
public void CreateShim(FilePath targetExecutablePath, string commandName)
{
if (ShimExists(commandName))
{
throw new ShellShimException(
string.Format(CommonLocalizableStrings.ShellShimConflict,
commandName));
}
TransactionalAction.Run(
action: () => {
var shim = new FakeShim
{
Runner = "dotnet",
ExecutablePath = targetExecutablePath.Value
};
_fileSystem.File.WriteAllText(
GetShimPath(commandName).Value,
JsonConvert.SerializeObject(shim));
},
rollback: () => {
_fileSystem.File.Delete(GetShimPath(commandName).Value);
});
}
public void RemoveShim(string commandName)
{
var originalShimPath = GetShimPath(commandName);
if (!_fileSystem.File.Exists(originalShimPath.Value))
{
return;
}
string tempShimPath = null;
TransactionalAction.Run(
action: () => {
var tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
_fileSystem.File.Move(originalShimPath.Value, tempFile);
tempShimPath = tempFile;
},
commit: () => {
if (tempShimPath != null)
{
_fileSystem.File.Delete(tempShimPath);
}
},
rollback: () => {
if (tempShimPath != null)
{
_fileSystem.File.Move(tempShimPath, originalShimPath.Value);
}
});
}
private bool ShimExists(string commandName)
{
return _fileSystem.File.Exists(GetShimPath(commandName).Value);
}
private FilePath GetShimPath(string shellCommandName)
{
var shimPath = Path.Combine(_pathToPlaceShim.Value, shellCommandName);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
shimPath += ".exe";
}
return new FilePath(shimPath);
}
public class FakeShim
{
public string Runner { get; set; }
public string ExecutablePath { get; set; }
}
}
}

View file

@ -23,19 +23,22 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
private readonly IFileSystem _fileSystem;
private readonly Action _installCallback;
private readonly Dictionary<PackageId, IEnumerable<string>> _warningsMap;
private readonly Dictionary<PackageId, IReadOnlyList<FilePath>> _packagedShimsMap;
public ToolPackageInstallerMock(
IFileSystem fileSystem,
IToolPackageStore store,
IProjectRestorer projectRestorer,
Action installCallback = null,
Dictionary<PackageId, IEnumerable<string>> warningsMap = null)
Dictionary<PackageId, IEnumerable<string>> warningsMap = null,
Dictionary<PackageId, IReadOnlyList<FilePath>> packagedShimsMap = null)
{
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
_store = store ?? throw new ArgumentNullException(nameof(store));
_projectRestorer = projectRestorer ?? throw new ArgumentNullException(nameof(projectRestorer));
_installCallback = installCallback;
_warningsMap = warningsMap ?? new Dictionary<PackageId, IEnumerable<string>>();
_packagedShimsMap = packagedShimsMap ?? new Dictionary<PackageId, IReadOnlyList<FilePath>>();
}
public IToolPackage InstallPackage(PackageId packageId,
@ -92,7 +95,10 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
IEnumerable<string> warnings = null;
_warningsMap.TryGetValue(packageId, out warnings);
return new ToolPackageMock(_fileSystem, packageId, version, packageDirectory, warnings: warnings);
IReadOnlyList<FilePath> packedShims = null;
_packagedShimsMap.TryGetValue(packageId, out packedShims);
return new ToolPackageMock(_fileSystem, packageId, version, packageDirectory, warnings: warnings, packagedShims: packedShims);
},
rollback: () => {
if (rollbackDirectory != null && _fileSystem.Directory.Exists(rollbackDirectory))

View file

@ -18,6 +18,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
private Lazy<IReadOnlyList<CommandSettings>> _commands;
private Action _uninstallCallback;
private IEnumerable<string> _warnings;
private readonly IReadOnlyList<FilePath> _packagedShims;
public ToolPackageMock(
IFileSystem fileSystem,
@ -25,7 +26,8 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
NuGetVersion version,
DirectoryPath packageDirectory,
Action uninstallCallback = null,
IEnumerable<string> warnings = null)
IEnumerable<string> warnings = null,
IReadOnlyList<FilePath> packagedShims = null)
{
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
Id = id;
@ -34,6 +36,7 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
_commands = new Lazy<IReadOnlyList<CommandSettings>>(GetCommands);
_uninstallCallback = uninstallCallback;
_warnings = warnings ?? new List<string>();
_packagedShims = packagedShims ?? new List<FilePath>();
}
public PackageId Id { get; private set; }
@ -52,6 +55,14 @@ namespace Microsoft.DotNet.Tools.Tests.ComponentMocks
public IEnumerable<string> Warnings => _warnings;
public IReadOnlyList<FilePath> PackagedShims
{
get
{
return _packagedShims;
}
}
public void Uninstall()
{
var rootDirectory = PackageDirectory.GetParentPath();

View file

@ -132,6 +132,20 @@ namespace Microsoft.Extensions.DependencyModel.Tests
_files.Remove(path);
}
public void Copy(string source, string destination)
{
if (!Exists(source))
{
throw new FileNotFoundException("source does not exist.");
}
if (Exists(destination))
{
throw new IOException("destination exists.");
}
_files[destination] = _files[source];
}
}
private class DirectoryMock : IDirectory

View file

@ -46,7 +46,11 @@ namespace Microsoft.DotNet.Tests.Commands
_fileSystem = new FileSystemMockBuilder().Build();
_toolPackageStore = new ToolPackageStoreMock(new DirectoryPath(PathToPlacePackages), _fileSystem);
_createShellShimRepository =
(nonGlobalLocation) => new ShellShimRepositoryMock(new DirectoryPath(PathToPlaceShim), _fileSystem);
(nonGlobalLocation) => new ShellShimRepository(
new DirectoryPath(PathToPlaceShim),
fileSystem: _fileSystem,
appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem),
filePermissionSetter: new NoOpFilePermissionSetter());
_environmentPathInstructionMock =
new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim);
_createToolPackageStoreAndInstaller = (_) => (_toolPackageStore, CreateToolPackageInstaller());
@ -71,7 +75,7 @@ namespace Microsoft.DotNet.Tests.Commands
// It is hard to simulate shell behavior. Only Assert shim can point to executable dll
_fileSystem.File.Exists(ExpectedCommandPath()).Should().BeTrue();
var deserializedFakeShim = JsonConvert.DeserializeObject<ShellShimRepositoryMock.FakeShim>(
var deserializedFakeShim = JsonConvert.DeserializeObject<AppHostShellShimMakerMock.FakeShim>(
_fileSystem.File.ReadAllText(ExpectedCommandPath()));
_fileSystem.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
@ -117,7 +121,7 @@ namespace Microsoft.DotNet.Tests.Commands
_fileSystem.File.Exists(ExpectedCommandPath())
.Should().BeTrue();
var deserializedFakeShim =
JsonConvert.DeserializeObject<ShellShimRepositoryMock.FakeShim>(
JsonConvert.DeserializeObject<AppHostShellShimMakerMock.FakeShim>(
_fileSystem.File.ReadAllText(ExpectedCommandPath()));
_fileSystem.File.Exists(deserializedFakeShim.ExecutablePath).Should().BeTrue();
}
@ -455,6 +459,42 @@ namespace Microsoft.DotNet.Tests.Commands
_reporter.Lines.Should().NotContain(l => l.Contains(EnvironmentPathInstructionMock.MockInstructionText));
}
[Fact]
public void AndPackagedShimIsProvidedWhenRunWithPackageIdItCreateShimUsingPackagedShim()
{
var extension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : string.Empty;
var prepackagedShimPath = "packagedShimDirectory/" + ProjectRestorerMock.FakeCommandName + extension;
var tokenToIdentifyPackagedShim = "packagedShim";
_fileSystem.File.WriteAllText(prepackagedShimPath, tokenToIdentifyPackagedShim);
var result = Parser.Instance.Parse($"dotnet tool install --tool-path /tmp/folder {PackageId}");
var appliedCommand = result["dotnet"]["tool"]["install"];
var parser = Parser.Instance;
var parseResult = parser.ParseFrom("dotnet tool", new[] {"install", "-g", PackageId});
var packagedShimsMap = new Dictionary<PackageId, IReadOnlyList<FilePath>>
{
[new PackageId(PackageId)] = new[] {new FilePath(prepackagedShimPath)}
};
var installCommand = new ToolInstallCommand(appliedCommand,
parseResult,
(_) => (_toolPackageStore, new ToolPackageInstallerMock(
fileSystem: _fileSystem,
store: _toolPackageStore,
packagedShimsMap: packagedShimsMap,
projectRestorer: new ProjectRestorerMock(
fileSystem: _fileSystem,
reporter: _reporter))),
_createShellShimRepository,
new EnvironmentPathInstructionMock(_reporter, PathToPlaceShim),
_reporter);
installCommand.Execute().Should().Be(0);
_fileSystem.File.ReadAllText(ExpectedCommandPath()).Should().Be(tokenToIdentifyPackagedShim);
}
private IToolPackageInstaller CreateToolPackageInstaller(
IEnumerable<MockFeed> feeds = null,
Action installCallback = null)
@ -476,5 +516,12 @@ namespace Microsoft.DotNet.Tests.Commands
"pathToPlace",
ProjectRestorerMock.FakeCommandName + extension);
}
private class NoOpFilePermissionSetter : IFilePermissionSetter
{
public void SetUserExecutionPermission(string path)
{
}
}
}
}

View file

@ -22,6 +22,7 @@ using Xunit;
using Parser = Microsoft.DotNet.Cli.Parser;
using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Uninstall.LocalizableStrings;
using InstallLocalizableStrings = Microsoft.DotNet.Tools.Tool.Install.LocalizableStrings;
using Microsoft.DotNet.ShellShim;
namespace Microsoft.DotNet.Tests.Commands
{
@ -203,7 +204,10 @@ namespace Microsoft.DotNet.Tests.Commands
result["dotnet"]["tool"]["install"],
result,
(_) => (store, packageInstallerMock),
(_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
(_) => new ShellShimRepository(
new DirectoryPath(ShimsDirectory),
fileSystem: _fileSystem,
appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem)),
_environmentPathInstructionMock,
_reporter);
}
@ -219,7 +223,10 @@ namespace Microsoft.DotNet.Tests.Commands
new DirectoryPath(ToolsDirectory),
_fileSystem,
uninstallCallback),
(_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
(_) => new ShellShimRepository(
new DirectoryPath(ShimsDirectory),
fileSystem: _fileSystem,
appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem)),
_reporter);
}
}

View file

@ -17,6 +17,7 @@ using Microsoft.Extensions.EnvironmentAbstractions;
using Xunit;
using Parser = Microsoft.DotNet.Cli.Parser;
using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Update.LocalizableStrings;
using Microsoft.DotNet.ShellShim;
namespace Microsoft.DotNet.Tests.Commands
{
@ -139,7 +140,7 @@ namespace Microsoft.DotNet.Tests.Commands
_mockFeeds
),
installCallback: () => throw new ToolConfigurationException("Simulated error"))),
_ => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
_ => GetMockedShellShimRepository(),
_reporter);
Action a = () => command.Execute();
@ -168,7 +169,7 @@ namespace Microsoft.DotNet.Tests.Commands
_mockFeeds
),
installCallback: () => throw new ToolConfigurationException("Simulated error"))),
_ => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
_ => GetMockedShellShimRepository(),
_reporter);
Action a = () => command.Execute();
@ -216,7 +217,7 @@ namespace Microsoft.DotNet.Tests.Commands
_reporter,
_mockFeeds
))),
(_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
(_) => GetMockedShellShimRepository(),
_environmentPathInstructionMock,
_reporter);
}
@ -236,8 +237,16 @@ namespace Microsoft.DotNet.Tests.Commands
_reporter,
_mockFeeds
))),
(_) => new ShellShimRepositoryMock(new DirectoryPath(ShimsDirectory), _fileSystem),
(_) => GetMockedShellShimRepository(),
_reporter);
}
private ShellShimRepository GetMockedShellShimRepository()
{
return new ShellShimRepository(
new DirectoryPath(ShimsDirectory),
fileSystem: _fileSystem,
appHostShellShimMaker: new AppHostShellShimMakerMock(_fileSystem));
}
}
}