Generate a .NET Framework shim app in dotnet-install-tools on Windows instead of a batch script (#8384)
Implement a simple launcher tool for running new processes on Windows - This application takes two parameters via the .exe.config configuration file - entryPoint: required - the file path to the new process being launched - runner: optional - the executable or interpretter used to launch the entryPoint - Update dotnet-install-tool to generate an exe instead of a batch script file
This commit is contained in:
parent
5c35438cfe
commit
fa47e95e90
12 changed files with 434 additions and 32 deletions
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27004.2008
|
||||
VisualStudioVersion = 15.0.27130.2020
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}"
|
||||
EndProject
|
||||
|
@ -232,6 +232,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ShellShim.
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ToolPackage.Tests", "test\Microsoft.DotNet.ToolPackage.Tests\Microsoft.DotNet.ToolPackage.Tests.csproj", "{91BFE800-1624-4A58-A1CE-339705A8FFD0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tool_launcher", "src\tool_launcher\tool_launcher.csproj", "{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -1640,6 +1642,30 @@ Global
|
|||
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -1713,6 +1739,7 @@ Global
|
|||
{E7C72EF2-8480-48B4-AAE8-A596F1A6048E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
|
||||
{E84C08C9-70D7-48B0-9507-ADB8B9A2694C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
|
||||
{91BFE800-1624-4A58-A1CE-339705A8FFD0} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
|
||||
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B526D2CE-EE2D-4AD4-93EF-1867D90FF1F5}
|
||||
|
|
|
@ -10,6 +10,14 @@ namespace ConsoleApplication
|
|||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello World!");
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
Console.WriteLine($"{i} = {args[i]}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.DotNet.Cli.Utils
|
||||
{
|
||||
|
@ -22,7 +21,12 @@ namespace Microsoft.DotNet.Cli.Utils
|
|||
/// <returns></returns>
|
||||
public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable<string> args)
|
||||
{
|
||||
return string.Join(" ", EscapeArgArray(args));
|
||||
var escaped = EscapeArgArray(args);
|
||||
#if NET35
|
||||
return string.Join(" ", escaped.ToArray());
|
||||
#else
|
||||
return string.Join(" ", escaped);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -36,7 +40,12 @@ namespace Microsoft.DotNet.Cli.Utils
|
|||
/// <returns></returns>
|
||||
public static string EscapeAndConcatenateArgArrayForCmdProcessStart(IEnumerable<string> args)
|
||||
{
|
||||
return string.Join(" ", EscapeArgArrayForCmd(args));
|
||||
var escaped = EscapeArgArrayForCmd(args);
|
||||
#if NET35
|
||||
return string.Join(" ", escaped.ToArray());
|
||||
#else
|
||||
return string.Join(" ", escaped);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
36
src/dotnet/OSVersionUtil.cs
Normal file
36
src/dotnet/OSVersionUtil.cs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.DotNet.PlatformAbstractions;
|
||||
|
||||
namespace Microsoft.DotNet
|
||||
{
|
||||
class OSVersionUtil
|
||||
{
|
||||
public static bool IsWindows8OrNewer()
|
||||
{
|
||||
if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Version.TryParse(RuntimeEnvironment.OperatingSystemVersion, out var winVersion))
|
||||
{
|
||||
// All current versions of Windows have a valid System.Version value for OperatingSystemVersion.
|
||||
// If parsing fails, let's assume Windows is newer than Win 8.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Windows 7 = "6.1"
|
||||
// Windows 8 = "6.2"
|
||||
// Windows 8.1 = "6.3"
|
||||
if (winVersion.Major > 6)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return winVersion.Minor >= 2;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.Tools;
|
||||
using Microsoft.Extensions.EnvironmentAbstractions;
|
||||
|
@ -13,34 +15,57 @@ namespace Microsoft.DotNet.ShellShim
|
|||
{
|
||||
public class ShellShimMaker
|
||||
{
|
||||
private const string LauncherExeNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net45";
|
||||
private const string LauncherExeNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net35";
|
||||
private const string LauncherConfigNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net45";
|
||||
private const string LauncherConfigNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net35";
|
||||
|
||||
private readonly string _launcherExeResourceName;
|
||||
private readonly string _launcherConfigResourceName;
|
||||
private readonly string _pathToPlaceShim;
|
||||
|
||||
public ShellShimMaker(string pathToPlaceShim)
|
||||
{
|
||||
_pathToPlaceShim =
|
||||
pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim));
|
||||
|
||||
if (OSVersionUtil.IsWindows8OrNewer())
|
||||
{
|
||||
_launcherExeResourceName = LauncherExeNet45ResourceName;
|
||||
_launcherConfigResourceName = LauncherConfigNet45ResourceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_launcherExeResourceName = LauncherExeNet35ResourceName;
|
||||
_launcherConfigResourceName = LauncherConfigNet35ResourceName;
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateShim(string packageExecutablePath, string shellCommandName)
|
||||
{
|
||||
var packageExecutable = new FilePath(packageExecutablePath);
|
||||
FilePath shimPath = GetShimPath(shellCommandName);
|
||||
|
||||
var script = new StringBuilder();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
script.AppendLine("@echo off");
|
||||
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} %*");
|
||||
CreateConfigFile(shimPath.Value + ".config", entryPoint: packageExecutablePath, runner: "dotnet");
|
||||
using (var shim = File.Create(shimPath.Value))
|
||||
using (var exe = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherExeResourceName))
|
||||
{
|
||||
exe.CopyTo(shim);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var packageExecutable = new FilePath(packageExecutablePath);
|
||||
|
||||
var script = new StringBuilder();
|
||||
script.AppendLine("#!/bin/sh");
|
||||
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\"");
|
||||
|
||||
File.WriteAllText(shimPath.Value, script.ToString());
|
||||
|
||||
SetUserExecutionPermissionToShimFile(shimPath);
|
||||
}
|
||||
|
||||
FilePath scriptPath = GetScriptPath(shellCommandName);
|
||||
File.WriteAllText(scriptPath.Value, script.ToString());
|
||||
|
||||
SetUserExecutionPermissionToShimFile(scriptPath);
|
||||
}
|
||||
|
||||
public void EnsureCommandNameUniqueness(string shellCommandName)
|
||||
|
@ -53,17 +78,31 @@ namespace Microsoft.DotNet.ShellShim
|
|||
}
|
||||
}
|
||||
|
||||
public void Remove(string shellCommandName)
|
||||
internal void CreateConfigFile(string outputPath, string entryPoint, string runner)
|
||||
{
|
||||
File.Delete(GetScriptPath(shellCommandName).Value);
|
||||
XDocument config;
|
||||
using (var resource = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherConfigResourceName))
|
||||
{
|
||||
config = XDocument.Load(resource);
|
||||
}
|
||||
|
||||
var appSettings = config.Descendants("appSettings").First();
|
||||
appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint)));
|
||||
appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty)));
|
||||
config.Save(outputPath);
|
||||
}
|
||||
|
||||
private FilePath GetScriptPath(string shellCommandName)
|
||||
public void Remove(string shellCommandName)
|
||||
{
|
||||
File.Delete(GetShimPath(shellCommandName).Value);
|
||||
}
|
||||
|
||||
private FilePath GetShimPath(string shellCommandName)
|
||||
{
|
||||
var scriptPath = Path.Combine(_pathToPlaceShim, shellCommandName);
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
scriptPath += ".cmd";
|
||||
scriptPath += ".exe";
|
||||
}
|
||||
|
||||
return new FilePath(scriptPath);
|
||||
|
@ -71,10 +110,11 @@ namespace Microsoft.DotNet.ShellShim
|
|||
|
||||
private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath)
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return;
|
||||
|
||||
CommandResult result = new CommandFactory()
|
||||
.Create("chmod", new[] {"u+x", scriptPath.Value})
|
||||
.Create("chmod", new[] { "u+x", scriptPath.Value })
|
||||
.CaptureStdOut()
|
||||
.CaptureStdErr()
|
||||
.Execute();
|
||||
|
|
|
@ -76,4 +76,5 @@
|
|||
<Folder Include="commands\dotnet-migrate\xlf" />
|
||||
<Folder Include="dotnet-complete\commands\" />
|
||||
</ItemGroup>
|
||||
<Import Project="dotnet.win.targets" Condition="'$(OS)' == 'Windows_NT'" />
|
||||
</Project>
|
||||
|
|
36
src/dotnet/dotnet.win.targets
Normal file
36
src/dotnet/dotnet.win.targets
Normal file
|
@ -0,0 +1,36 @@
|
|||
<!-- This file should only be used when building dotnet for windows. -->
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Only included to ensure this is built first. -->
|
||||
<ProjectReference Include="..\tool_launcher\tool_launcher.csproj"
|
||||
ReferenceOutputAssembly="false"
|
||||
SkipGetTargetFrameworkProperties="true"
|
||||
SetTargetFramework="TargetFramework=net45"
|
||||
PrivateAssets="All" />
|
||||
<ProjectReference Include="..\tool_launcher\tool_launcher.csproj"
|
||||
ReferenceOutputAssembly="false"
|
||||
SkipGetTargetFrameworkProperties="true"
|
||||
SetTargetFramework="TargetFramework=net35"
|
||||
PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="EmbedDotnetLauncher" BeforeTargets="PrepareForBuild">
|
||||
<MSBuild Projects="..\tool_launcher\tool_launcher.csproj" Targets="GetTargetPath" Properties="TargetFramework=net45;Configuration=$(Configuration)">
|
||||
<Output TaskParameter="TargetOutputs" PropertyName="DotnetLauncherNet45FullPath" />
|
||||
</MSBuild>
|
||||
<MSBuild Projects="..\tool_launcher\tool_launcher.csproj" Targets="GetTargetPath" Properties="TargetFramework=net35;Configuration=$(Configuration)">
|
||||
<Output TaskParameter="TargetOutputs" PropertyName="DotnetLauncherNet35FullPath" />
|
||||
</MSBuild>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="$(DotnetLauncherNet45FullPath)" LogicalName="Microsoft.DotNet.Tools.Launcher.Executable.Net45" />
|
||||
<EmbeddedResource Include="$(DotnetLauncherNet45FullPath).config" LogicalName="Microsoft.DotNet.Tools.Launcher.Config.Net45" />
|
||||
<EmbeddedResource Include="$(DotnetLauncherNet35FullPath)" LogicalName="Microsoft.DotNet.Tools.Launcher.Executable.Net35" />
|
||||
<EmbeddedResource Include="$(DotnetLauncherNet35FullPath).config" LogicalName="Microsoft.DotNet.Tools.Launcher.Config.Net35" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
116
src/tool_launcher/Program.cs
Normal file
116
src/tool_launcher/Program.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
|
||||
namespace Microsoft.DotNet.Tools.Launcher
|
||||
{
|
||||
/// <summary>
|
||||
/// The app is simple shim into launching arbitrary command line processes.
|
||||
/// It is configured via app settings .NET config file. (See app.config).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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<string>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
18
src/tool_launcher/app.net35.config
Normal file
18
src/tool_launcher/app.net35.config
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
Generated by the .NET Core Command Line.
|
||||
-->
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v2.0.50727" />
|
||||
</startup>
|
||||
<appSettings>
|
||||
<!--
|
||||
To use this launcher, this value must be set. It is a file path or name of the new process being launched.
|
||||
<add key="entryPoint" value="%entrypoint%" />
|
||||
|
||||
This value may also be set. It is an path to another executable used to launch the entry point.
|
||||
It is treated as single argument.
|
||||
<add key="runner" value="%runner%" />
|
||||
-->
|
||||
</appSettings>
|
||||
</configuration>
|
18
src/tool_launcher/app.net45.config
Normal file
18
src/tool_launcher/app.net45.config
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!--
|
||||
Generated by the .NET Core Command Line.
|
||||
-->
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||
</startup>
|
||||
<appSettings>
|
||||
<!--
|
||||
To use this launcher, this value must be set. It is a file path or name of the new process being launched.
|
||||
<add key="entryPoint" value="%entrypoint%" />
|
||||
|
||||
This value may also be set. It is an path to another executable used to launch the entry point.
|
||||
It is treated as single argument.
|
||||
<add key="runner" value="%runner%" />
|
||||
-->
|
||||
</appSettings>
|
||||
</configuration>
|
38
src/tool_launcher/tool_launcher.csproj
Normal file
38
src/tool_launcher/tool_launcher.csproj
Normal file
|
@ -0,0 +1,38 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Targets .NET Framework 3.5 because .NET Framework 4 is not bundled in Windows 7. -->
|
||||
<TargetFrameworks>net45;net35</TargetFrameworks>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<OutputType>exe</OutputType>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>Microsoft.DotNet.Tools.Launcher</AssemblyName>
|
||||
<Title>dotnet-tool-launcher</Title>
|
||||
<Description>
|
||||
A simple Windows-only shim for launching new processes.
|
||||
</Description>
|
||||
<AppConfig>app.$(TargetFramework).config</AppConfig>
|
||||
<!-- In other words, a workaround for issues with cmd.exe. -->
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- This project must not have any package references. It must be a standalone .exe file -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Linq" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net35'">
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Workaround https://github.com/Microsoft/msbuild/issues/1333 -->
|
||||
<PropertyGroup>
|
||||
<FrameworkPathOverride Condition="'$(TargetFramework)' == 'net35'">$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client</FrameworkPathOverride>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Microsoft.DotNet.Cli.Utils\ArgumentEscaper.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -6,21 +6,50 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Linq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.DotNet.Cli.Utils;
|
||||
using Microsoft.DotNet.TestFramework;
|
||||
using Microsoft.DotNet.Tools.Test.Utilities;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.DotNet.ShellShim.Tests
|
||||
{
|
||||
public class ShellShimMakerTests : TestBase
|
||||
{
|
||||
private readonly string _pathToPlaceShim;
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public ShellShimMakerTests()
|
||||
public ShellShimMakerTests(ITestOutputHelper output)
|
||||
{
|
||||
_pathToPlaceShim = Path.GetTempPath();
|
||||
_output = output;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("my_native_app.exe", null)]
|
||||
[InlineData("./my_native_app.js", "nodejs")]
|
||||
[InlineData(@"C:\tools\my_native_app.dll", "dotnet")]
|
||||
public void GivenAnRunnerOrEntryPointItCanCreateConfig(string entryPoint, string runner)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
return;
|
||||
|
||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
||||
|
||||
var tmpFile = Path.Combine(TempRoot.Root, Path.GetRandomFileName());
|
||||
|
||||
shellShimMaker.CreateConfigFile(tmpFile, entryPoint, runner);
|
||||
|
||||
new FileInfo(tmpFile).Should().Exist();
|
||||
|
||||
var generated = XDocument.Load(tmpFile);
|
||||
|
||||
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 == entryPoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -28,7 +57,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
|||
{
|
||||
var outputDll = MakeHelloWorldExecutableDll();
|
||||
|
||||
var shellShimMaker = new ShellShimMaker(_pathToPlaceShim);
|
||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||
|
||||
shellShimMaker.CreateShim(
|
||||
|
@ -39,14 +68,37 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
|||
stdOut.Should().Contain("Hello World");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("arg1 arg2", new[] { "arg1", "arg2" })]
|
||||
[InlineData(" \"arg1 with space\" arg2", new[] { "arg1 with space", "arg2" })]
|
||||
[InlineData(" \"arg with ' quote\" ", new[] { "arg with ' quote" })]
|
||||
public void GivenAShimItPassesThroughArguments(string arguments, string[] expectedPassThru)
|
||||
{
|
||||
var outputDll = MakeHelloWorldExecutableDll();
|
||||
|
||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||
|
||||
shellShimMaker.CreateShim(
|
||||
outputDll.FullName,
|
||||
shellCommandName);
|
||||
|
||||
var stdOut = ExecuteInShell(shellCommandName, arguments);
|
||||
|
||||
for (int i = 0; i < expectedPassThru.Length; i++)
|
||||
{
|
||||
stdOut.Should().Contain($"{i} = {expectedPassThru[i]}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GivenAnExecutablePathWithExistingSameNameShimItThrows()
|
||||
{
|
||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||
|
||||
MakeNameConflictingCommand(_pathToPlaceShim, shellCommandName);
|
||||
MakeNameConflictingCommand(TempRoot.Root, shellCommandName);
|
||||
|
||||
var shellShimMaker = new ShellShimMaker(_pathToPlaceShim);
|
||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
||||
|
||||
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
||||
a.ShouldThrow<GracefulException>()
|
||||
|
@ -61,7 +113,7 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
|||
{
|
||||
var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName();
|
||||
|
||||
var shellShimMaker = new ShellShimMaker(_pathToPlaceShim);
|
||||
var shellShimMaker = new ShellShimMaker(TempRoot.Root);
|
||||
|
||||
Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName);
|
||||
a.ShouldNotThrow();
|
||||
|
@ -72,17 +124,18 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
|||
File.WriteAllText(Path.Combine(pathToPlaceShim, shellCommandName), string.Empty);
|
||||
}
|
||||
|
||||
private string ExecuteInShell(string shellCommandName)
|
||||
private string ExecuteInShell(string shellCommandName, string arguments = "")
|
||||
{
|
||||
ProcessStartInfo processStartInfo;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var file = Path.Combine(TempRoot.Root, shellCommandName + ".exe");
|
||||
processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "CMD.exe",
|
||||
Arguments = $"/C {shellCommandName}",
|
||||
UseShellExecute = false
|
||||
FileName = file,
|
||||
UseShellExecute = false,
|
||||
Arguments = arguments,
|
||||
};
|
||||
}
|
||||
else
|
||||
|
@ -90,11 +143,13 @@ namespace Microsoft.DotNet.ShellShim.Tests
|
|||
processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "sh",
|
||||
Arguments = shellCommandName,
|
||||
Arguments = shellCommandName + " " + arguments,
|
||||
UseShellExecute = false
|
||||
};
|
||||
}
|
||||
processStartInfo.WorkingDirectory = _pathToPlaceShim;
|
||||
|
||||
_output.WriteLine($"Launching '{processStartInfo.FileName} {processStartInfo.Arguments}'");
|
||||
processStartInfo.WorkingDirectory = TempRoot.Root;
|
||||
processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath);
|
||||
|
||||
processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr);
|
||||
|
|
Loading…
Reference in a new issue