Skip to content
This repository has been archived by the owner on Apr 20, 2023. It is now read-only.

Commit

Permalink
consume bring your own shim(byos) (#9018)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
William Li authored Apr 10, 2018
1 parent 98a1ee6 commit b0ee5db
Show file tree
Hide file tree
Showing 47 changed files with 898 additions and 208 deletions.
29 changes: 29 additions & 0 deletions src/Microsoft.DotNet.Cli.Utils/CommandPermission.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
28 changes: 28 additions & 0 deletions src/Microsoft.DotNet.Cli.Utils/FilePermissionSettingException.cs
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
}
7 changes: 7 additions & 0 deletions src/Microsoft.DotNet.Cli.Utils/IFilePermissionSetter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.DotNet.Cli.Utils
{
internal interface IFilePermissionSetter
{
void SetUserExecutionPermission(string path);
}
}
5 changes: 5 additions & 0 deletions src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public void Move(string source, string destination)
File.Move(source, destination);
}

public void Copy(string sourceFileName, string destFileName)
{
File.Copy(sourceFileName, destFileName);
}

public void Delete(string path)
{
File.Delete(path);
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.DotNet.InternalAbstractions/IFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Stream OpenFile(

void Move(string source, string destination);

void Copy(string source, string destination);

void Delete(string path);
}
}
6 changes: 6 additions & 0 deletions src/dotnet/CommonLocalizableStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
53 changes: 53 additions & 0 deletions src/dotnet/ShellShim/AppHostShimMaker.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
9 changes: 9 additions & 0 deletions src/dotnet/ShellShim/IApphostShellShimMaker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.Extensions.EnvironmentAbstractions;

namespace Microsoft.DotNet.ShellShim
{
internal interface IAppHostShellShimMaker
{
void CreateApphostShellShim(FilePath entryPoint, FilePath shimPath);
}
}
3 changes: 2 additions & 1 deletion src/dotnet/ShellShim/IShellShimRepository.cs
Original file line number Diff line number Diff line change
@@ -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);
}
Expand Down
116 changes: 62 additions & 54 deletions src/dotnet/ShellShim/ShellShimRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,24 @@ internal class ShellShimRepository : IShellShimRepository
private const string ApphostNameWithoutExtension = "apphost";

private readonly DirectoryPath _shimsDirectory;
private readonly string _appHostSourceDirectory;

public ShellShimRepository(DirectoryPath shimsDirectory, string appHostSourcePath = null)
private readonly IFileSystem _fileSystem;
private readonly IAppHostShellShimMaker _appHostShellShimMaker;
private readonly IFilePermissionSetter _filePermissionSetter;

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))
{
Expand All @@ -54,20 +62,28 @@ public void CreateShim(FilePath targetExecutablePath, string commandName)
{
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))
{
_fileSystem.File.Copy(packagedShim.Value.Value, GetShimPath(commandName).Value);
_filePermissionSetter.SetUserExecutionPermission(GetShimPath(commandName).Value);
}
else
{
SetUserExecutionPermission(GetShimPath(commandName));
_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)
{
throw new ShellShimException(
Expand All @@ -80,7 +96,7 @@ public void CreateShim(FilePath targetExecutablePath, string commandName)
}
},
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);
}
Expand All @@ -94,10 +110,10 @@ public void RemoveShim(string commandName)
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;
}
}
Expand All @@ -115,38 +131,17 @@ public void RemoveShim(string commandName)
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; }
Expand All @@ -159,7 +154,7 @@ private class RootObject

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)
Expand All @@ -184,24 +179,37 @@ private FilePath GetShimPath(string commandName)
}
}

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();

CommandResult result = new CommandFactory()
.Create("chmod", new[] { "u+x", path.Value })
.CaptureStdOut()
.CaptureStdErr()
.Execute();
if (candidatepackagedShim.Length > 1)
{
throw new ShellShimException(
string.Format(
CommonLocalizableStrings.MoreThanOnePackagedShimAvailable,
string.Join(';', candidatepackagedShim)));
}

if (result.ExitCode != 0)
{
throw new ShellShimException(
string.Format(CommonLocalizableStrings.FailedSettingShimPermissions, result.StdErr));
if (candidatepackagedShim.Length == 1)
{
packagedShim = candidatepackagedShim.Single();
return true;
}
}

return false;
}
}
}
2 changes: 2 additions & 0 deletions src/dotnet/ToolPackage/IToolPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ internal interface IToolPackage

IEnumerable<string> Warnings { get; }

IReadOnlyList<FilePath> PackagedShims { get; }

void Uninstall();
}
}
Loading

0 comments on commit b0ee5db

Please sign in to comment.