From e70f07178aaf6a87f1a4551987ffda53647f524d Mon Sep 17 00:00:00 2001 From: William Li Date: Mon, 2 Apr 2018 12:37:25 -0700 Subject: [PATCH] Apphost shim (#8893) * Publish app host to folder under SDK * Use carried apphost as shim * Remove full framework launcher * Fix test run command issue * Use latest release/2.1 build * Test with 32 bit env * Add missing return * Update to latest prodcon build * Add xlfs --- Microsoft.DotNet.Cli.sln | 3 + build/AppHostTemplate.proj | 49 ++++++ build/DependencyVersions.props | 6 +- .../EmbedAppNameInHost.cs | 145 ++++++++++++++++++ .../EmbedAppNameInHostException.cs | 22 +++ .../LocalizableStrings.resx | 8 +- .../xlf/LocalizableStrings.cs.xlf | 10 ++ .../xlf/LocalizableStrings.de.xlf | 10 ++ .../xlf/LocalizableStrings.es.xlf | 10 ++ .../xlf/LocalizableStrings.fr.xlf | 10 ++ .../xlf/LocalizableStrings.it.xlf | 10 ++ .../xlf/LocalizableStrings.ja.xlf | 10 ++ .../xlf/LocalizableStrings.ko.xlf | 10 ++ .../xlf/LocalizableStrings.pl.xlf | 10 ++ .../xlf/LocalizableStrings.pt-BR.xlf | 10 ++ .../xlf/LocalizableStrings.ru.xlf | 10 ++ .../xlf/LocalizableStrings.tr.xlf | 10 ++ .../xlf/LocalizableStrings.zh-Hans.xlf | 10 ++ .../xlf/LocalizableStrings.zh-Hant.xlf | 10 ++ src/dotnet/ShellShim/ShellShimRepository.cs | 109 +++++++------ src/dotnet/dotnet.csproj | 4 +- src/dotnet/dotnet.win.targets | 25 --- src/redist/redist.csproj | 38 +++++ src/tool_launcher/Program.cs | 116 -------------- src/tool_launcher/app.config | 19 --- src/tool_launcher/tool_launcher.csproj | 30 ---- .../ShellShimRepositoryTests.cs | 109 ++++++++----- .../RepoDirectoriesProvider.cs | 3 + 28 files changed, 532 insertions(+), 284 deletions(-) create mode 100644 build/AppHostTemplate.proj create mode 100644 src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHost.cs create mode 100644 src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHostException.cs delete mode 100644 src/dotnet/dotnet.win.targets delete mode 100644 src/tool_launcher/Program.cs delete mode 100644 src/tool_launcher/app.config delete mode 100644 src/tool_launcher/tool_launcher.csproj diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index 4b341ed9a..0988307b9 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -22,12 +22,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestPackages", "TestPackage EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{89905EC4-BC0F-443B-8ADF-691321F10108}" ProjectSection(SolutionItems) = preProject + build\AppHostTemplate.proj = build\AppHostTemplate.proj build\AzureInfo.props = build\AzureInfo.props build\BackwardsCompatibilityRuntimes.props = build\BackwardsCompatibilityRuntimes.props build\BranchInfo.props = build\BranchInfo.props build\Branding.props = build\Branding.props build\BuildDefaults.props = build\BuildDefaults.props build\BuildInfo.targets = build\BuildInfo.targets + build\BundledDotnetTools.proj = build\BundledDotnetTools.proj + build\BundledDotnetTools.props = build\BundledDotnetTools.props build\BundledRuntimes.props = build\BundledRuntimes.props build\BundledSdks.props = build\BundledSdks.props build\BundledTemplates.proj = build\BundledTemplates.proj diff --git a/build/AppHostTemplate.proj b/build/AppHostTemplate.proj new file mode 100644 index 000000000..598f250b6 --- /dev/null +++ b/build/AppHostTemplate.proj @@ -0,0 +1,49 @@ + + + + + + .exe + AppHost$(NativeExecutableExtension) + + + + + + + + + + + + + + + + + + + + + + --runtime $(Rid) + $(AppHostTemplateRestoreAdditionalParameters) /p:TargetFramework=$(CliTargetFramework) + $(AppHostTemplateRestoreAdditionalParameters) /p:TemplateFillInPackageName=$(TemplateFillInPackageName) + $(AppHostTemplateRestoreAdditionalParameters) /p:TemplateFillInPackageVersion=$(TemplateFillInPackageVersion) + $(AppHostTemplateRestoreAdditionalParameters) /p:RestorePackagesPath=$(AppHostIntermediateDirectory) + + + + + diff --git a/build/DependencyVersions.props b/build/DependencyVersions.props index c2271065a..5bf0a0e2c 100644 --- a/build/DependencyVersions.props +++ b/build/DependencyVersions.props @@ -2,7 +2,7 @@ 2.1.0-preview2-30475 - 2.1.0-preview2-26314-02 + 2.1.0-preview2-26330-03 $(MicrosoftNETCoreAppPackageVersion) 15.7.0-preview-000127 $(MicrosoftBuildPackageVersion) @@ -28,8 +28,8 @@ $(MicrosoftTemplateEngineCliPackageVersion) $(MicrosoftTemplateEngineCliPackageVersion) $(MicrosoftTemplateEngineCliPackageVersion) - 2.1.0-preview2-26314-02 - 2.1.0-preview2-26314-02 + 2.1.0-preview2-26330-03 + 2.1.0-preview2-26330-03 0.1.1-alpha-174 1.2.1-alpha-002133 $(MicrosoftDotNetProjectJsonMigrationPackageVersion) diff --git a/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHost.cs b/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHost.cs new file mode 100644 index 000000000..7a91b6f2f --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHost.cs @@ -0,0 +1,145 @@ +// 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.Text; + +namespace Microsoft.DotNet.Cli.Utils +{ + /// + /// Embeds the App Name into the AppHost.exe + /// + public static class EmbedAppNameInHost + { + private static string _placeHolder = "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2"; //hash value embedded in default apphost executable + private static byte[] _bytesToSearch = Encoding.UTF8.GetBytes(_placeHolder); + public static void EmbedAndReturnModifiedAppHostPath( + string appHostSourceFilePath, + string appHostDestinationFilePath, + string appBinaryName) + { + var hostExtension = Path.GetExtension(appHostSourceFilePath); + var appbaseName = Path.GetFileNameWithoutExtension(appBinaryName); + var bytesToWrite = Encoding.UTF8.GetBytes(appBinaryName); + var destinationDirectory = new FileInfo(appHostDestinationFilePath).Directory.FullName; + + if (File.Exists(appHostDestinationFilePath)) + { + //We have already done the required modification to apphost.exe + return; + } + + if (bytesToWrite.Length > 1024) + { + throw new EmbedAppNameInHostException(string.Format(LocalizableStrings.EmbedAppNameInHostFileNameIsTooLong, appBinaryName)); + } + + var array = File.ReadAllBytes(appHostSourceFilePath); + + SearchAndReplace(array, _bytesToSearch, bytesToWrite, appHostSourceFilePath); + + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + + // Copy AppHostSourcePath to ModifiedAppHostPath so it inherits the same attributes\permissions. + File.Copy(appHostSourceFilePath, appHostDestinationFilePath); + + // Re-write ModifiedAppHostPath with the proper contents. + using (FileStream fs = new FileStream(appHostDestinationFilePath, FileMode.Truncate, FileAccess.ReadWrite, FileShare.Read)) + { + fs.Write(array, 0, array.Length); + } + } + + // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm + private static int[] ComputeKMPFailureFunction(byte[] pattern) + { + int[] table = new int[pattern.Length]; + if (pattern.Length >= 1) + { + table[0] = -1; + } + if (pattern.Length >= 2) + { + table[1] = 0; + } + + int pos = 2; + int cnd = 0; + while (pos < pattern.Length) + { + if (pattern[pos - 1] == pattern[cnd]) + { + table[pos] = cnd + 1; + cnd++; + pos++; + } + else if (cnd > 0) + { + cnd = table[cnd]; + } + else + { + table[pos] = 0; + pos++; + } + } + return table; + } + + // See: https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm + private static int KMPSearch(byte[] pattern, byte[] bytes) + { + int m = 0; + int i = 0; + int[] table = ComputeKMPFailureFunction(pattern); + + while (m + i < bytes.Length) + { + if (pattern[i] == bytes[m + i]) + { + if (i == pattern.Length - 1) + { + return m; + } + i++; + } + else + { + if (table[i] > -1) + { + m = m + i - table[i]; + i = table[i]; + } + else + { + m++; + i = 0; + } + } + } + return -1; + } + + private static void SearchAndReplace(byte[] array, byte[] searchPattern, byte[] patternToReplace, string appHostSourcePath) + { + int offset = KMPSearch(searchPattern, array); + if (offset < 0) + { + throw new EmbedAppNameInHostException(string.Format(LocalizableStrings.EmbedAppNameInHostAppHostHasBeenModified, appHostSourcePath, _placeHolder)); + } + + patternToReplace.CopyTo(array, offset); + + if (patternToReplace.Length < searchPattern.Length) + { + for (int i = patternToReplace.Length; i < searchPattern.Length; i++) + { + array[i + offset] = 0x0; + } + } + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHostException.cs b/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHostException.cs new file mode 100644 index 000000000..a20432d42 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/EmbedAppNameInHostException.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; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class EmbedAppNameInHostException : Exception + { + public EmbedAppNameInHostException() + { + } + + public EmbedAppNameInHostException(string message) : base(message) + { + } + + public EmbedAppNameInHostException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/LocalizableStrings.resx b/src/Microsoft.DotNet.Cli.Utils/LocalizableStrings.resx index 004d7a214..435a26de9 100644 --- a/src/Microsoft.DotNet.Cli.Utils/LocalizableStrings.resx +++ b/src/Microsoft.DotNet.Cli.Utils/LocalizableStrings.resx @@ -265,4 +265,10 @@ MSBuild arguments: {0} - \ No newline at end of file + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + Given file name '{0}' is longer than 1024 bytes + + diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf index 015c486f0..e344ea524 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf index 7af64fb1b..61e4513d5 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf index 96ea5c325..6c204f324 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf index 49dee1f7d..88aad3035 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf index 662b786dc..ca4052e06 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf index b21a43542..b4a5d5c52 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf index 7cb60bcbc..9dfca8978 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf index 6aaade6a0..ef52ffcc3 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf index fc793c1b6..fd20f0ffa 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf index 7b1978f56..35d41a676 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf index a3787c97a..b1afa5763 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf index 83a234d5a..c30d33e41 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf index a6241b6d0..d56372b1f 100644 --- a/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf @@ -249,6 +249,16 @@ MSBuild arguments: {0} + + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + Unable to use '{0}' as application host executable as it does not contain the expected placeholder byte sequence '{1}' that would mark where the application name would be written. + + + + Given file name '{0}' is longer than 1024 bytes + Given file name '{0}' is longer than 1024 bytes + + \ No newline at end of file diff --git a/src/dotnet/ShellShim/ShellShimRepository.cs b/src/dotnet/ShellShim/ShellShimRepository.cs index 92bb55f4b..6fcdf9c11 100644 --- a/src/dotnet/ShellShim/ShellShimRepository.cs +++ b/src/dotnet/ShellShim/ShellShimRepository.cs @@ -6,25 +6,27 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; -using System.Xml.Linq; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.PlatformAbstractions; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; +using Newtonsoft.Json; namespace Microsoft.DotNet.ShellShim { internal class ShellShimRepository : IShellShimRepository { - private const string LauncherExeResourceName = "Microsoft.DotNet.Tools.Launcher.Executable"; - private const string LauncherConfigResourceName = "Microsoft.DotNet.Tools.Launcher.Config"; + private const string ApphostNameWithoutExtension = "apphost"; private readonly DirectoryPath _shimsDirectory; + private readonly string _appHostSourceDirectory; - public ShellShimRepository(DirectoryPath shimsDirectory) + public ShellShimRepository(DirectoryPath shimsDirectory, string appHostSourcePath = null) { _shimsDirectory = shimsDirectory; + _appHostSourceDirectory = appHostSourcePath ?? Path.Combine(ApplicationEnvironment.ApplicationBasePath, + "AppHostTemplate"); } public void CreateShim(FilePath targetExecutablePath, string commandName) @@ -47,7 +49,8 @@ namespace Microsoft.DotNet.ShellShim } TransactionalAction.Run( - action: () => { + action: () => + { try { if (!Directory.Exists(_shimsDirectory.Value)) @@ -55,29 +58,13 @@ namespace Microsoft.DotNet.ShellShim Directory.CreateDirectory(_shimsDirectory.Value); } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + CreateApphostShimAndConfigFile( + commandName, + entryPoint: targetExecutablePath); + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - CreateConfigFile( - outputPath: GetWindowsConfigPath(commandName), - entryPoint: targetExecutablePath, - runner: "dotnet"); - - using (var shim = File.Create(GetWindowsShimPath(commandName).Value)) - using (var resource = typeof(ShellShimRepository).Assembly.GetManifestResourceStream(LauncherExeResourceName)) - { - resource.CopyTo(shim); - } - } - else - { - var script = new StringBuilder(); - script.AppendLine("#!/bin/sh"); - script.AppendLine($"dotnet {targetExecutablePath.ToQuotedString()} \"$@\""); - - var shimPath = GetPosixShimPath(commandName); - File.WriteAllText(shimPath.Value, script.ToString()); - - SetUserExecutionPermission(shimPath); + SetUserExecutionPermission(GetShimPath(commandName)); } } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) @@ -138,18 +125,43 @@ namespace Microsoft.DotNet.ShellShim }); } - internal void CreateConfigFile(FilePath outputPath, FilePath entryPoint, string runner) + private void CreateApphostShimAndConfigFile(string commandName, FilePath entryPoint) { - XDocument config; - using (var resource = typeof(ShellShimRepository).Assembly.GetManifestResourceStream(LauncherConfigResourceName)) + string appHostSourcePath; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - config = XDocument.Load(resource); + appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension + ".exe"); + } + else + { + appHostSourcePath = Path.Combine(_appHostSourceDirectory, ApphostNameWithoutExtension); } - var appSettings = config.Descendants("appSettings").First(); - appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint.Value))); - appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty))); - config.Save(outputPath.Value); + EmbedAppNameInHost.EmbedAndReturnModifiedAppHostPath( + appHostSourceFilePath: appHostSourcePath, + appHostDestinationFilePath: GetShimPath(commandName).Value, + appBinaryName: Path.GetFileName(entryPoint.Value)); + + var config = JsonConvert.SerializeObject( + new RootObject + { + startupOptions = new StartupOptions + { + appRoot = entryPoint.GetDirectoryPath().Value + } + }); + + File.WriteAllText(GetConfigPath(commandName).Value, config); + } + + private class StartupOptions + { + public string appRoot { get; set; } + } + + private class RootObject + { + public StartupOptions startupOptions { get; set; } } private bool ShimExists(string commandName) @@ -164,30 +176,25 @@ namespace Microsoft.DotNet.ShellShim yield break; } + yield return GetShimPath(commandName); + yield return GetConfigPath(commandName); + } + + private FilePath GetShimPath(string commandName) + { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - yield return GetWindowsShimPath(commandName); - yield return GetWindowsConfigPath(commandName); + return new FilePath(_shimsDirectory.WithFile(commandName).Value +".exe"); } else { - yield return GetPosixShimPath(commandName); + return _shimsDirectory.WithFile(commandName); } } - private FilePath GetPosixShimPath(string commandName) + private FilePath GetConfigPath(string commandName) { - return _shimsDirectory.WithFile(commandName); - } - - private FilePath GetWindowsShimPath(string commandName) - { - return new FilePath(_shimsDirectory.WithFile(commandName).Value + ".exe"); - } - - private FilePath GetWindowsConfigPath(string commandName) - { - return new FilePath(GetWindowsShimPath(commandName).Value + ".config"); + return _shimsDirectory.WithFile(commandName + ".startupconfig.json"); } private static void SetUserExecutionPermission(FilePath path) diff --git a/src/dotnet/dotnet.csproj b/src/dotnet/dotnet.csproj index ecbfee407..b7d26a073 100644 --- a/src/dotnet/dotnet.csproj +++ b/src/dotnet/dotnet.csproj @@ -79,5 +79,5 @@ - - \ No newline at end of file + + diff --git a/src/dotnet/dotnet.win.targets b/src/dotnet/dotnet.win.targets deleted file mode 100644 index d46555d09..000000000 --- a/src/dotnet/dotnet.win.targets +++ /dev/null @@ -1,25 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - - - - - - - - - - - - - - - - diff --git a/src/redist/redist.csproj b/src/redist/redist.csproj index 65bd1e2a9..668f64cc0 100644 --- a/src/redist/redist.csproj +++ b/src/redist/redist.csproj @@ -211,6 +211,44 @@ + + + + Microsoft.NETCore.DotNetAppHost + + + + + + + + + + <_NETCoreDotNetAppHostPackageVersion>@(NETCoreDotNetAppHostPackageVersions->Distinct()) + + + + + + AppHostTemplatePath=$(SdkOutputDirectory)/AppHostTemplate; + TemplateFillInPackageName=$(NETCoreDotNetAppHostPackageName); + TemplateFillInPackageVersion=$(_NETCoreDotNetAppHostPackageVersion); + PreviousStageDirectory=$(PreviousStageDirectory); + AppHostIntermediateDirectory=$(IntermediateDirectory)/AppHostIntermediate + + + + + + + + + - /// The app is simple shim into launching arbitrary command line processes. - /// It is configured via app settings .NET config file. (See app.config). - /// - /// - /// Launching new processes using cmd.exe and .cmd files causes issues for long-running process - /// because CTRL+C always hangs on interrupt with the prompt "Terminate Y/N". This can lead to - /// orphaned processes. - /// - class Program - { - private const string TRACE = "DOTNET_LAUNCHER_TRACE"; - private const int ERR_FAILED = -1; - private static bool _trace; - - public static int Main(string[] argsToForward) - { - bool.TryParse(Environment.GetEnvironmentVariable(TRACE), out _trace); - - try - { - var appSettings = ConfigurationManager.AppSettings; - - var entryPoint = appSettings["entryPoint"]; - if (string.IsNullOrEmpty(entryPoint)) - { - LogError("The launcher must specify a non-empty appSetting value for 'entryPoint'."); - return ERR_FAILED; - } - - var exePath = entryPoint; - var runner = appSettings["runner"]; - - var args = new List(); - - if (!string.IsNullOrEmpty(runner)) - { - args.Add(entryPoint); - exePath = runner; - } - - args.AddRange(argsToForward); - - var argString = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - - using (var process = new Process - { - StartInfo = - { - FileName = exePath, - Arguments = argString, - CreateNoWindow = false, - UseShellExecute = false, - } - }) - { - LogTrace("Starting a new process."); - LogTrace("filename = " + process.StartInfo.FileName); - LogTrace("args = " + process.StartInfo.Arguments); - LogTrace("cwd = " + process.StartInfo.WorkingDirectory); - - try - { - process.Start(); - } - catch (Win32Exception ex) - { - LogTrace(ex.ToString()); - LogError($"Failed to start '{process.StartInfo.FileName}'. " + ex.Message); - return ERR_FAILED; - } - - process.WaitForExit(); - - LogTrace("Exited code " + process.ExitCode); - - return process.ExitCode; - } - } - catch (Exception ex) - { - LogError("Unexpected error launching a new process. Run with the environment variable " + TRACE + "='true' for details."); - LogTrace(ex.ToString()); - return ERR_FAILED; - } - } - - private static void LogError(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.BackgroundColor = ConsoleColor.Black; - Console.Error.WriteLine("ERROR: " + message); - Console.ResetColor(); - } - - private static void LogTrace(string message) - { - if (!_trace) - return; - - Console.ForegroundColor = ConsoleColor.DarkGray; - Console.BackgroundColor = ConsoleColor.Black; - Console.WriteLine("[dotnet-launcher] " + message); - Console.ResetColor(); - } - } -} diff --git a/src/tool_launcher/app.config b/src/tool_launcher/app.config deleted file mode 100644 index 14df07456..000000000 --- a/src/tool_launcher/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - diff --git a/src/tool_launcher/tool_launcher.csproj b/src/tool_launcher/tool_launcher.csproj deleted file mode 100644 index 26cd41b46..000000000 --- a/src/tool_launcher/tool_launcher.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net35 - AnyCPU - exe - false - Microsoft.DotNet.Tools.Launcher - dotnet-tool-launcher - - A simple Windows-only shim for launching new processes. - - - - - - - - - - - - - $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client - - - - - - diff --git a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs index cf3599b54..15b9d4f7c 100644 --- a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs +++ b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimRepositoryTests.cs @@ -30,36 +30,12 @@ namespace Microsoft.DotNet.ShellShim.Tests _output = output; } - [WindowsOnlyTheory] - [InlineData("my_native_app.exe", null)] - [InlineData("./my_native_app.js", "nodejs")] - [InlineData(@"C:\tools\my_native_app.dll", "dotnet")] - public void GivenAnRunnerOrEntryPointItCanCreateConfig(string entryPointPath, string runner) - { - var pathToShim = GetNewCleanFolderUnderTempRoot(); - var shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); - var tmpFile = new FilePath(Path.Combine(pathToShim, Path.GetRandomFileName())); - - shellShimRepository.CreateConfigFile(tmpFile, new FilePath(entryPointPath), runner); - - new FileInfo(tmpFile.Value).Should().Exist(); - - var generated = XDocument.Load(tmpFile.Value); - - generated.Descendants("appSettings") - .Descendants("add") - .Should() - .Contain(e => e.Attribute("key").Value == "runner" && e.Attribute("value").Value == (runner ?? string.Empty)) - .And - .Contain(e => e.Attribute("key").Value == "entryPoint" && e.Attribute("value").Value == entryPointPath); - } - [Fact] public void GivenAnExecutablePathItCanGenerateShimFile() { var outputDll = MakeHelloWorldExecutableDll(); var pathToShim = GetNewCleanFolderUnderTempRoot(); - var shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + ShellShimRepository shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); shellShimRepository.CreateShim(outputDll, shellCommandName); @@ -69,12 +45,19 @@ namespace Microsoft.DotNet.ShellShim.Tests stdOut.Should().Contain("Hello World"); } + private static ShellShimRepository ConfigBasicTestDependecyShellShimRepository(string pathToShim) + { + string stage2AppHostTemplateDirectory = GetAppHostTemplateFromStage2(); + + return new ShellShimRepository(new DirectoryPath(pathToShim), stage2AppHostTemplateDirectory); + } + [Fact] public void GivenAnExecutablePathItCanGenerateShimFileInTransaction() { var outputDll = MakeHelloWorldExecutableDll(); var pathToShim = GetNewCleanFolderUnderTempRoot(); - var shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + var shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); using (var transactionScope = new TransactionScope( @@ -95,7 +78,7 @@ namespace Microsoft.DotNet.ShellShim.Tests { var outputDll = MakeHelloWorldExecutableDll(); var extraNonExistDirectory = Path.GetRandomFileName(); - var shellShimRepository = new ShellShimRepository(new DirectoryPath(Path.Combine(TempRoot.Root, extraNonExistDirectory))); + var shellShimRepository = new ShellShimRepository(new DirectoryPath(Path.Combine(TempRoot.Root, extraNonExistDirectory)), GetAppHostTemplateFromStage2()); var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); Action a = () => shellShimRepository.CreateShim(outputDll, shellCommandName); @@ -111,7 +94,7 @@ namespace Microsoft.DotNet.ShellShim.Tests { var outputDll = MakeHelloWorldExecutableDll(); var pathToShim = GetNewCleanFolderUnderTempRoot(); - var shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + var shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); var shellCommandName = nameof(ShellShimRepositoryTests) + Path.GetRandomFileName(); shellShimRepository.CreateShim(outputDll, shellCommandName); @@ -140,7 +123,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Action a = () => @@ -182,7 +165,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Action intendedError = () => throw new ToolPackageException("simulated error"); @@ -219,7 +202,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty(); @@ -244,7 +227,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty(); @@ -273,7 +256,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty(); @@ -309,7 +292,7 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { - shellShimRepository = new ShellShimRepository(new DirectoryPath(pathToShim)); + shellShimRepository = ConfigBasicTestDependecyShellShimRepository(pathToShim); } Directory.EnumerateFileSystemEntries(pathToShim).Should().BeEmpty(); @@ -358,17 +341,28 @@ namespace Microsoft.DotNet.ShellShim.Tests } else { + var file = Path.Combine(cleanFolderUnderTempRoot, shellCommandName); processStartInfo = new ProcessStartInfo { - FileName = "sh", - Arguments = shellCommandName + " " + arguments, + FileName = file, + Arguments = arguments, UseShellExecute = false }; } _output.WriteLine($"Launching '{processStartInfo.FileName} {processStartInfo.Arguments}'"); processStartInfo.WorkingDirectory = cleanFolderUnderTempRoot; - processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath); + + var environmentProvider = new EnvironmentProvider(); + processStartInfo.EnvironmentVariables["PATH"] = environmentProvider.GetEnvironmentVariable("PATH"); + if (Environment.Is64BitProcess) + { + processStartInfo.EnvironmentVariables["DOTNET_ROOT"] = new RepoDirectoriesProvider().DotnetRoot; + } + else + { + processStartInfo.EnvironmentVariables["DOTNET_ROOT(x86)"] = new RepoDirectoriesProvider().DotnetRoot; + } processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr); @@ -377,6 +371,47 @@ namespace Microsoft.DotNet.ShellShim.Tests return stdOut ?? ""; } + private static FileInfo GetStage2DotnetPath() + { + string stage2DotnetPath; + + var environmentProvider = new EnvironmentProvider(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + stage2DotnetPath = environmentProvider.GetCommandPath("dotnet", ".exe"); + } + else + { + stage2DotnetPath = environmentProvider.GetCommandPath("dotnet"); + } + + var stage2Dotnet = new FileInfo(stage2DotnetPath); + return stage2Dotnet; + } + + private static string GetAppHostTemplateFromStage2() + { + var environmentProvider = new EnvironmentProvider(); + string stage2DotnetPath; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + stage2DotnetPath = environmentProvider.GetCommandPath("dotnet", ".exe"); + } + else + { + stage2DotnetPath = environmentProvider.GetCommandPath("dotnet"); + } + + var stage2Dotnet = GetStage2DotnetPath(); + + var stage2AppHostTemplateDirectory = + new DirectoryInfo(new RepoDirectoriesProvider().Stage2Sdk) + .GetDirectory("AppHostTemplate").FullName; + return stage2AppHostTemplateDirectory; + } + private static FilePath MakeHelloWorldExecutableDll() { const string testAppName = "TestAppSimple"; diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs index b68df6b4f..7de277852 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/RepoDirectoriesProvider.cs @@ -15,6 +15,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private static string s_buildRid; private string _artifacts; + private string _dotnetRoot; private string _builtDotnet; private string _nugetPackages; private string _stage2Sdk; @@ -85,6 +86,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities public string Artifacts => _artifacts; public string BuiltDotnet => _builtDotnet; + public string DotnetRoot => _dotnetRoot; public string NugetPackages => _nugetPackages; public string Stage2Sdk => _stage2Sdk; public string Stage2WithBackwardsCompatibleRuntimesDirectory => _stage2WithBackwardsCompatibleRuntimesDirectory; @@ -106,6 +108,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities previousStage.ToString(), BuildRid); _builtDotnet = builtDotnet ?? Path.Combine(_artifacts, "intermediate", "sharedFrameworkPublish"); + _dotnetRoot = Path.Combine(_artifacts, "dotnet"); _nugetPackages = nugetPackages ?? Path.Combine(RepoRoot, ".nuget", "packages"); _stage2Sdk = Directory .EnumerateDirectories(Path.Combine(_artifacts, "dotnet", "sdk"))