Refactor ScriptExecutor, add test cases for scriptexecutor.

Add TargetFramework and FullTargetFramework to compile and publish script
variables.

Add ProjectLocal Command Resolution Strategy.

Fixup ArgumentEscaper to not always quote things.

Fixes #1216
Fixes #1016
Fixes #982
This commit is contained in:
Bryan Thornbury 2016-02-09 15:30:04 -08:00
parent dbc9032202
commit c1e28ae921
21 changed files with 421 additions and 164 deletions

View file

@ -13,10 +13,5 @@
"frameworks": { "frameworks": {
"dnxcore50": { } "dnxcore50": { }
},
"scripts": {
"prepublish" : ["echo prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"],
"postpublish" : ["echo postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"]
} }
} }

View file

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
namespace TestApp
{
public class Program
{
public static int Main(string[] args)
{
Console.WriteLine("Hello World");
return 0;
}
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>58808bbc-371e-47d6-a3d0-4902145eda4e</ProjectGuid>
<RootNamespace>TestApp</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,2 @@
@echo off
echo %*

View file

@ -0,0 +1 @@
echo $@

View file

@ -0,0 +1,22 @@
{
"version": "1.0.0-*",
"compilationOptions": {
"emitEntryPoint": true
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23811"
},
"frameworks": {
"dnxcore50": { }
},
"scripts": {
"prepublish" : ["echoscript prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?"],
"postpublish" : ["echoscript postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?"],
"precompile" : ["echoscript precompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?"],
"postcompile" : ["echoscript postcompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?"]
}
}

View file

@ -86,8 +86,10 @@ namespace Microsoft.DotNet.Cli.Utils
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
var quoted = ShouldSurroundWithQuotes(arg); var needsQuotes = ShouldSurroundWithQuotes(arg);
if (quoted) sb.Append("\""); var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg);
if (needsQuotes) sb.Append("\"");
for (int i = 0; i < arg.Length; ++i) for (int i = 0; i < arg.Length; ++i)
{ {
@ -101,13 +103,21 @@ namespace Microsoft.DotNet.Cli.Utils
} }
// Escape any backslashes at the end of the arg // Escape any backslashes at the end of the arg
// when the argument is also quoted.
// This ensures the outside quote is interpreted as // This ensures the outside quote is interpreted as
// an argument delimiter // an argument delimiter
if (i == arg.Length) if (i == arg.Length && isQuoted)
{ {
sb.Append('\\', 2 * backslashCount); sb.Append('\\', 2 * backslashCount);
} }
// At then end of the arg, which isn't quoted,
// just add the backslashes, no need to escape
else if (i == arg.Length)
{
sb.Append('\\', backslashCount);
}
// Escape any preceding backslashes and the quote // Escape any preceding backslashes and the quote
else if (arg[i] == '"') else if (arg[i] == '"')
{ {
@ -123,7 +133,7 @@ namespace Microsoft.DotNet.Cli.Utils
} }
} }
if (quoted) sb.Append("\""); if (needsQuotes) sb.Append("\"");
return sb.ToString(); return sb.ToString();
} }
@ -149,22 +159,14 @@ namespace Microsoft.DotNet.Cli.Utils
if (quoted) sb.Append("^\""); if (quoted) sb.Append("^\"");
// Prepend every character with ^
// This is harmless when passing through cmd
// and ensures cmd metacharacters are not interpreted
// as such
foreach (var character in argument) foreach (var character in argument)
{ {
sb.Append("^");
if (character == '"') sb.Append(character);
{
sb.Append('^');
sb.Append('"');
sb.Append('^');
sb.Append(character);
}
else
{
sb.Append("^");
sb.Append(character);
}
} }
if (quoted) sb.Append("^\""); if (quoted) sb.Append("^\"");
@ -172,35 +174,27 @@ namespace Microsoft.DotNet.Cli.Utils
return sb.ToString(); return sb.ToString();
} }
/// <summary>
/// Prepare as single argument to
/// roundtrip properly through cmd.
///
/// This prefixes every character with the '^' character to force cmd to
/// interpret the argument string literally. An alternative option would
/// be to do this only for cmd metacharacters.
///
/// See here for more info:
/// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
internal static bool ShouldSurroundWithQuotes(string argument) internal static bool ShouldSurroundWithQuotes(string argument)
{ {
// Don't quote already quoted strings // Don't quote already quoted strings
if (argument.StartsWith("\"", StringComparison.Ordinal) && if (IsSurroundedWithQuotes(argument))
argument.EndsWith("\"", StringComparison.Ordinal))
{ {
return false; return false;
} }
// Only quote if whitespace exists in the string // Only quote if whitespace exists in the string
if (argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n")) return ArgumentContainsWhitespace(argument);
{ }
return true;
}
return true; internal static bool IsSurroundedWithQuotes(string argument)
{
return argument.StartsWith("\"", StringComparison.Ordinal) &&
argument.EndsWith("\"", StringComparison.Ordinal);
}
internal static bool ArgumentContainsWhitespace(string argument)
{
return argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n");
} }
} }
} }

View file

@ -8,6 +8,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using NuGet.Frameworks; using NuGet.Frameworks;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Utils namespace Microsoft.DotNet.Cli.Utils
{ {
@ -40,9 +41,9 @@ namespace Microsoft.DotNet.Cli.Utils
ResolutionStrategy = commandSpec.ResolutionStrategy; ResolutionStrategy = commandSpec.ResolutionStrategy;
} }
public static Command CreateDotNet(string commandName, IEnumerable<string> args, NuGetFramework framework = null, bool useComSpec = false) public static Command CreateDotNet(string commandName, IEnumerable<string> args, NuGetFramework framework = null)
{ {
return Create("dotnet", new[] { commandName }.Concat(args), framework, useComSpec); return Create("dotnet", new[] { commandName }.Concat(args), framework);
} }
/// <summary> /// <summary>
@ -55,9 +56,23 @@ namespace Microsoft.DotNet.Cli.Utils
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="framework"></param> /// <param name="framework"></param>
/// <returns></returns> /// <returns></returns>
public static Command Create(string commandName, IEnumerable<string> args, NuGetFramework framework = null, bool useComSpec = false) public static Command Create(string commandName, IEnumerable<string> args, NuGetFramework framework = null)
{ {
var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, args, framework, useComSpec); var commandSpec = CommandResolver.TryResolveCommandSpec(commandName, args, framework);
if (commandSpec == null)
{
throw new CommandUnknownException(commandName);
}
var command = new Command(commandSpec);
return command;
}
public static Command CreateForScript(string commandName, IEnumerable<string> args, Project project, string[] inferredExtensionList)
{
var commandSpec = CommandResolver.TryResolveScriptCommandSpec(commandName, args, project, inferredExtensionList);
if (commandSpec == null) if (commandSpec == null)
{ {
@ -197,6 +212,8 @@ namespace Microsoft.DotNet.Cli.Utils
public string CommandName => _process.StartInfo.FileName; public string CommandName => _process.StartInfo.FileName;
public string CommandArgs => _process.StartInfo.Arguments;
private string FormatProcessInfo(ProcessStartInfo info) private string FormatProcessInfo(ProcessStartInfo info)
{ {
if (string.IsNullOrWhiteSpace(info.Arguments)) if (string.IsNullOrWhiteSpace(info.Arguments))

View file

@ -8,6 +8,9 @@
//command loaded from the same directory as the executing assembly //command loaded from the same directory as the executing assembly
BaseDirectory, BaseDirectory,
//command loaded from the same directory as a project.json file
ProjectLocal,
//command loaded from path //command loaded from path
Path, Path,

View file

@ -13,52 +13,60 @@ namespace Microsoft.DotNet.Cli.Utils
{ {
internal static class CommandResolver internal static class CommandResolver
{ {
public static CommandSpec TryResolveCommandSpec(string commandName, IEnumerable<string> args, NuGetFramework framework = null, bool useComSpec = false) public static CommandSpec TryResolveCommandSpec(string commandName, IEnumerable<string> args, NuGetFramework framework = null)
{ {
return ResolveFromRootedCommand(commandName, args, useComSpec) ?? return ResolveFromRootedCommand(commandName, args) ??
ResolveFromProjectDependencies(commandName, args, framework, useComSpec) ?? ResolveFromProjectDependencies(commandName, args, framework) ??
ResolveFromProjectTools(commandName, args, useComSpec) ?? ResolveFromProjectTools(commandName, args) ??
ResolveFromAppBase(commandName, args, useComSpec) ?? ResolveFromAppBase(commandName, args) ??
ResolveFromPath(commandName, args, useComSpec); ResolveFromPath(commandName, args);
}
public static CommandSpec TryResolveScriptCommandSpec(string commandName, IEnumerable<string> args, Project project, string[] inferredExtensionList)
{
return ResolveFromRootedCommand(commandName, args) ??
ResolveFromProjectPath(commandName, args, project, inferredExtensionList) ??
ResolveFromAppBase(commandName, args) ??
ResolveFromPath(commandName, args);
} }
private static CommandSpec ResolveFromPath(string commandName, IEnumerable<string> args, bool useComSpec = false) private static CommandSpec ResolveFromPath(string commandName, IEnumerable<string> args)
{ {
var commandPath = Env.GetCommandPath(commandName); var commandPath = Env.GetCommandPath(commandName);
return commandPath == null return commandPath == null
? null ? null
: CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.Path, useComSpec); : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.Path);
} }
private static CommandSpec ResolveFromAppBase(string commandName, IEnumerable<string> args, bool useComSpec = false) private static CommandSpec ResolveFromAppBase(string commandName, IEnumerable<string> args)
{ {
var commandPath = Env.GetCommandPathFromAppBase(PlatformServices.Default.Application.ApplicationBasePath, commandName); var commandPath = Env.GetCommandPathFromRootPath(PlatformServices.Default.Application.ApplicationBasePath, commandName);
return commandPath == null return commandPath == null
? null ? null
: CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.BaseDirectory, useComSpec); : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.BaseDirectory);
}
private static CommandSpec ResolveFromProjectPath(string commandName, IEnumerable<string> args, Project project, string[] inferredExtensionList)
{
var commandPath = Env.GetCommandPathFromRootPath(project.ProjectDirectory, commandName, inferredExtensionList);
return commandPath == null
? null
: CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.ProjectLocal);
} }
private static CommandSpec ResolveFromRootedCommand(string commandName, IEnumerable<string> args, bool useComSpec = false) private static CommandSpec ResolveFromRootedCommand(string commandName, IEnumerable<string> args)
{ {
if (Path.IsPathRooted(commandName)) if (Path.IsPathRooted(commandName))
{ {
if (useComSpec) var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args);
{ return new CommandSpec(commandName, escapedArgs, CommandResolutionStrategy.Path);
return CreateComSpecCommandSpec(commandName, args, CommandResolutionStrategy.Path);
}
else
{
var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args);
return new CommandSpec(commandName, escapedArgs, CommandResolutionStrategy.Path);
}
} }
return null; return null;
} }
public static CommandSpec ResolveFromProjectDependencies(string commandName, IEnumerable<string> args, public static CommandSpec ResolveFromProjectDependencies(string commandName, IEnumerable<string> args,
NuGetFramework framework, bool useComSpec = false) NuGetFramework framework)
{ {
if (framework == null) return null; if (framework == null) return null;
@ -72,7 +80,7 @@ namespace Microsoft.DotNet.Cli.Utils
var depsPath = projectContext.GetOutputPaths(Constants.DefaultConfiguration).RuntimeFiles.Deps; var depsPath = projectContext.GetOutputPaths(Constants.DefaultConfiguration).RuntimeFiles.Deps;
return ConfigureCommandFromPackage(commandName, args, commandPackage, projectContext, depsPath, useComSpec); return ConfigureCommandFromPackage(commandName, args, commandPackage, projectContext, depsPath);
} }
private static ProjectContext GetProjectContext(NuGetFramework framework) private static ProjectContext GetProjectContext(NuGetFramework framework)
@ -101,7 +109,7 @@ namespace Microsoft.DotNet.Cli.Utils
e == FileNameSuffixes.DotNet.DynamicLib)); e == FileNameSuffixes.DotNet.DynamicLib));
} }
public static CommandSpec ResolveFromProjectTools(string commandName, IEnumerable<string> args, bool useComSpec = false) public static CommandSpec ResolveFromProjectTools(string commandName, IEnumerable<string> args)
{ {
var context = GetProjectContext(FrameworkConstants.CommonFrameworks.DnxCore50); var context = GetProjectContext(FrameworkConstants.CommonFrameworks.DnxCore50);
@ -147,7 +155,7 @@ namespace Microsoft.DotNet.Cli.Utils
} }
private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable<string> args, private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable<string> args,
PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null, bool useComSpec = false) PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null)
{ {
var files = commandPackage.Library.Files; var files = commandPackage.Library.Files;
@ -157,11 +165,11 @@ namespace Microsoft.DotNet.Cli.Utils
var packageDir = Path.Combine(packageRoot, packagePath); var packageDir = Path.Combine(packageRoot, packagePath);
return ConfigureCommandFromPackage(commandName, args, files, packageDir, depsPath, useComSpec); return ConfigureCommandFromPackage(commandName, args, files, packageDir, depsPath);
} }
private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable<string> args, private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable<string> args,
IEnumerable<string> files, string packageDir, string depsPath = null, bool useComSpec = false) IEnumerable<string> files, string packageDir, string depsPath = null)
{ {
var fileName = string.Empty; var fileName = string.Empty;
@ -192,24 +200,18 @@ namespace Microsoft.DotNet.Cli.Utils
fileName = Path.Combine(packageDir, commandPath); fileName = Path.Combine(packageDir, commandPath);
} }
if (useComSpec) var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args);
{ return new CommandSpec(fileName, escapedArgs, CommandResolutionStrategy.NugetPackage);
return CreateComSpecCommandSpec(fileName, args, CommandResolutionStrategy.NugetPackage);
}
else
{
var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args);
return new CommandSpec(fileName, escapedArgs, CommandResolutionStrategy.NugetPackage);
}
} }
private static CommandSpec CreateCommandSpecPreferringExe( private static CommandSpec CreateCommandSpecPreferringExe(
string commandName, string commandName,
IEnumerable<string> args, IEnumerable<string> args,
string commandPath, string commandPath,
CommandResolutionStrategy resolutionStrategy, CommandResolutionStrategy resolutionStrategy)
bool useComSpec = false)
{ {
var useComSpec = false;
if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows && if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows &&
Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase)) Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase))
{ {
@ -228,7 +230,7 @@ namespace Microsoft.DotNet.Cli.Utils
if (useComSpec) if (useComSpec)
{ {
return CreateComSpecCommandSpec(commandPath, args, resolutionStrategy); return CreateCmdCommandSpec(commandPath, args, resolutionStrategy);
} }
else else
{ {
@ -237,14 +239,14 @@ namespace Microsoft.DotNet.Cli.Utils
} }
} }
private static CommandSpec CreateComSpecCommandSpec( private static CommandSpec CreateCmdCommandSpec(
string command, string command,
IEnumerable<string> args, IEnumerable<string> args,
CommandResolutionStrategy resolutionStrategy) CommandResolutionStrategy resolutionStrategy)
{ {
// To prevent Command Not Found, comspec gets passed in as
// the command already in some cases
var comSpec = Environment.GetEnvironmentVariable("ComSpec"); var comSpec = Environment.GetEnvironmentVariable("ComSpec");
// Handle the case where ComSpec is already the command
if (command.Equals(comSpec, StringComparison.OrdinalIgnoreCase)) if (command.Equals(comSpec, StringComparison.OrdinalIgnoreCase))
{ {
command = args.FirstOrDefault(); command = args.FirstOrDefault();

View file

@ -66,14 +66,14 @@ namespace Microsoft.DotNet.Cli.Utils
return commandPath; return commandPath;
} }
public static string GetCommandPathFromAppBase(string appBase, string commandName, params string[] extensions) public static string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions)
{ {
if (!extensions.Any()) if (!extensions.Any())
{ {
extensions = Env.ExecutableExtensions.ToArray(); extensions = Env.ExecutableExtensions.ToArray();
} }
var commandPath = extensions.Select(e => Path.Combine(appBase, commandName + e)) var commandPath = extensions.Select(e => Path.Combine(rootPath, commandName + e))
.FirstOrDefault(File.Exists); .FirstOrDefault(File.Exists);
return commandPath; return commandPath;

View file

@ -17,82 +17,47 @@ namespace Microsoft.DotNet.Cli.Utils
public static ICommand CreateCommandForScript(Project project, string scriptCommandLine, Func<string, string> getVariable) public static ICommand CreateCommandForScript(Project project, string scriptCommandLine, Func<string, string> getVariable)
{ {
// Preserve quotation marks around arguments since command is about to be passed to a shell. May need var scriptArguments = ParseScriptArguments(project, scriptCommandLine, getVariable);
// the quotes to ensure the shell groups arguments correctly. if (scriptArguments == null)
{
throw new Exception($"ScriptExecutor: failed to parse script \"{scriptCommandLine}\"");
}
var inferredExtensions = DetermineInferredScriptExtensions();
return Command
.CreateForScript(scriptArguments.First(), scriptArguments.Skip(1), project, inferredExtensions)
.WorkingDirectory(project.ProjectDirectory);
}
private static IEnumerable<string> ParseScriptArguments(Project project, string scriptCommandLine, Func<string, string> getVariable)
{
var scriptArguments = CommandGrammar.Process( var scriptArguments = CommandGrammar.Process(
scriptCommandLine, scriptCommandLine,
GetScriptVariable(project, getVariable), GetScriptVariable(project, getVariable),
preserveSurroundingQuotes: true); preserveSurroundingQuotes: false);
// Ensure the array won't be empty and the elements won't be null or empty strings.
scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray(); scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();
if (scriptArguments.Length == 0) if (scriptArguments.Length == 0)
{ {
return null; return null;
} }
var useComSpec = false; return scriptArguments;
}
if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) private static string[] DetermineInferredScriptExtensions()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
// Only forward slashes are used in script blocks. Replace with backslashes to correctly return new string[] { "", ".cmd" };
// locate the script. The directory separator is platform-specific.
scriptArguments[0] = scriptArguments[0].Replace(
Path.AltDirectorySeparatorChar,
Path.DirectorySeparatorChar);
// Command-lines on Windows are executed via "cmd /S /C" in order to support batch files, &&,
// built-in commands like echo, et cetera. /S allows quoting the command as well as the arguments.
// ComSpec is Windows-specific, and contains the full path to cmd.exe
var comSpec = Environment.GetEnvironmentVariable("ComSpec");
if (!string.IsNullOrEmpty(comSpec))
{
useComSpec = true;
scriptArguments = new string[] { comSpec }
.Concat(scriptArguments)
.ToArray();
}
} }
else else
{ {
// Special-case a script name that, perhaps with added .sh, matches an existing file. return new string[] { "", ".sh" };
var surroundWithQuotes = false;
var scriptCandidate = scriptArguments[0];
if (scriptCandidate.StartsWith("\"", StringComparison.Ordinal) &&
scriptCandidate.EndsWith("\"", StringComparison.Ordinal))
{
// Strip surrounding quotes; they were required in project.json to keep the script name
// together but confuse File.Exists() e.g. "My Script", lacking ./ prefix and .sh suffix.
surroundWithQuotes = true;
scriptCandidate = scriptCandidate.Substring(1, scriptCandidate.Length - 2);
}
if (!scriptCandidate.EndsWith(".sh", StringComparison.Ordinal))
{
scriptCandidate = scriptCandidate + ".sh";
}
if (File.Exists(Path.Combine(project.ProjectDirectory, scriptCandidate)))
{
// scriptCandidate may be a path relative to the project root. If so, likely will not be found
// in the $PATH; add ./ to let bash know where to look.
var prefix = Path.IsPathRooted(scriptCandidate) ? string.Empty : "./";
var quote = surroundWithQuotes ? "\"" : string.Empty;
scriptArguments[0] = $"{ quote }{ prefix }{ scriptCandidate }{ quote }";
}
// Always use /usr/bin/env bash -c in order to support redirection and so on; similar to Windows case.
// Unlike Windows, must escape quotation marks within the newly-quoted string.
scriptArguments = new[] { "/usr/bin/env", "bash", "-c", "\"" }
.Concat(scriptArguments.Select(argument => argument.Replace("\"", "\\\"")))
.Concat(new[] { "\"" })
.ToArray();
} }
return Command.Create(scriptArguments.FirstOrDefault(), scriptArguments.Skip(1), useComSpec: useComSpec)
.WorkingDirectory(project.ProjectDirectory);
} }
private static Func<string, string> WrapVariableDictionary(IDictionary<string, string> contextVariables) private static Func<string, string> WrapVariableDictionary(IDictionary<string, string> contextVariables)
{ {
return key => return key =>

View file

@ -135,7 +135,8 @@ namespace Microsoft.DotNet.Tools.Compiler
// Run pre-compile event // Run pre-compile event
var contextVariables = new Dictionary<string, string>() var contextVariables = new Dictionary<string, string>()
{ {
{ "compile:TargetFramework", context.TargetFramework.DotNetFrameworkName }, { "compile:TargetFramework", context.TargetFramework.GetShortFolderName() },
{ "compile:FullTargetFramework", context.TargetFramework.DotNetFrameworkName },
{ "compile:Configuration", args.ConfigValue }, { "compile:Configuration", args.ConfigValue },
{ "compile:OutputFile", outputName }, { "compile:OutputFile", outputName },
{ "compile:OutputDir", outputPath.TrimEnd('\\', '/') }, { "compile:OutputDir", outputPath.TrimEnd('\\', '/') },

View file

@ -96,7 +96,8 @@ namespace Microsoft.DotNet.Tools.Publish
{ "publish:ProjectPath", context.ProjectDirectory }, { "publish:ProjectPath", context.ProjectDirectory },
{ "publish:Configuration", configuration }, { "publish:Configuration", configuration },
{ "publish:OutputPath", outputPath }, { "publish:OutputPath", outputPath },
{ "publish:Framework", context.TargetFramework.Framework }, { "publish:TargetFramework", context.TargetFramework.GetShortFolderName() },
{ "publish:FullTargetFramework", context.TargetFramework.DotNetFrameworkName },
{ "publish:Runtime", context.RuntimeIdentifier }, { "publish:Runtime", context.RuntimeIdentifier },
}; };

View file

@ -94,16 +94,12 @@ namespace Microsoft.DotNet.Tests.ArgumentForwarding
[InlineData("\"abc\"\t\td\te")] [InlineData("\"abc\"\t\td\te")]
[InlineData(@"a\\b d""e f""g h")] [InlineData(@"a\\b d""e f""g h")]
[InlineData(@"\ \\ \\\")] [InlineData(@"\ \\ \\\")]
[InlineData(@"a\""b c d")]
[InlineData(@"a\\""b c d")] [InlineData(@"a\\""b c d")]
[InlineData(@"a\\\""b c d")]
[InlineData(@"a\\\\""b c d")] [InlineData(@"a\\\\""b c d")]
[InlineData(@"a\\\\""b c d")] [InlineData(@"a\\\\""b c d")]
[InlineData(@"a\\\\""b c"" d e")] [InlineData(@"a\\\\""b c"" d e")]
[InlineData(@"a""b c""d e""f g""h i""j k""l")] [InlineData(@"a""b c""d e""f g""h i""j k""l")]
[InlineData(@"a b c""def")] [InlineData(@"a b c""def")]
[InlineData(@"""\a\"" \\""\\\ b c")]
[InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")]
public void TestArgumentForwardingCmd(string testUserArgument) public void TestArgumentForwardingCmd(string testUserArgument)
{ {
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -153,6 +149,29 @@ namespace Microsoft.DotNet.Tests.ArgumentForwarding
} }
} }
[Theory]
[InlineData(@"a\""b c d")]
[InlineData(@"a\\\""b c d")]
[InlineData(@"""\a\"" \\""\\\ b c")]
[InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")]
public void TestArgumentForwardingCmdFailsWithUnbalancedQuote(string testArgString)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
// Get Baseline Argument Evaluation via Reflector
// This does not need to be different for cmd because
// it only establishes what the string[] args should be
var rawEvaluatedArgument = RawEvaluateArgumentString(testArgString);
// Escape and Re-Evaluate the rawEvaluatedArgument
var escapedEvaluatedRawArgument = EscapeAndEvaluateArgumentStringCmd(rawEvaluatedArgument);
rawEvaluatedArgument.Length.Should().NotBe(escapedEvaluatedRawArgument.Length);
}
/// <summary> /// <summary>
/// EscapeAndEvaluateArgumentString returns a representation of string[] args /// EscapeAndEvaluateArgumentString returns a representation of string[] args
/// when rawEvaluatedArgument is passed as an argument to a process using /// when rawEvaluatedArgument is passed as an argument to a process using

View file

@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
if (!Path.IsPathRooted(_command)) if (!Path.IsPathRooted(_command))
{ {
_command = Env.GetCommandPath(_command) ?? _command = Env.GetCommandPath(_command) ??
Env.GetCommandPathFromAppBase(AppContext.BaseDirectory, _command); Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, _command);
} }
Console.WriteLine($"Executing - {_command} {args}"); Console.WriteLine($"Executing - {_command} {args}");
@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities
Console.WriteLine($"Executing (Captured Output) - {_command} {args}"); Console.WriteLine($"Executing (Captured Output) - {_command} {args}");
var commandPath = Env.GetCommandPath(_command, ".exe", ".cmd", "") ?? var commandPath = Env.GetCommandPath(_command, ".exe", ".cmd", "") ??
Env.GetCommandPathFromAppBase(AppContext.BaseDirectory, _command, ".exe", ".cmd", ""); Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, _command, ".exe", ".cmd", "");
var stdOut = new StreamForwarder(); var stdOut = new StreamForwarder();
var stdErr = new StreamForwarder(); var stdErr = new StreamForwarder();

View file

@ -0,0 +1,155 @@
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.Tools.Test.Utilities;
using System.Runtime.InteropServices;
using Xunit;
using FluentAssertions;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils.ScriptExecutorTests
{
public class ScriptExecutorTests : TestBase
{
private static readonly string s_testProjectRoot = Path.Combine(AppContext.BaseDirectory, "TestAssets/TestProjects");
private TempDirectory _root;
public ScriptExecutorTests()
{
_root = Temp.CreateDirectory();
}
[Fact]
public void Test_Project_Local_Script_is_Resolved()
{
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
CreateTestFile("some.script", binTestProjectPath);
var scriptCommandLine = "some.script";
var command = ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
command.Should().NotBeNull();
command.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal);
}
[Fact]
public void Test_Nonexistent_Project_Local_Script_is_not_Resolved()
{
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
var scriptCommandLine = "nonexistent.script";
Action action = () => ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
action.ShouldThrow<CommandUnknownException>();
}
[Fact]
public void Test_Extension_Inference_in_Resolution_for_Project_Local_Scripts()
{
var extensionList = new string[] {".cmd", ".sh"};
var expectedExtension = default(string);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
expectedExtension = ".cmd";
}
else
{
expectedExtension = ".sh";
}
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
foreach (var extension in extensionList)
{
CreateTestFile("uniquescriptname" + extension, binTestProjectPath);
}
// Don't include extension
var scriptCommandLine = "uniquescriptname";
var command = ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
command.Should().NotBeNull();
command.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal);
command.CommandArgs.Should().Contain(scriptCommandLine + expectedExtension);
}
[Fact]
public void Test_Script_Exe_Files_Dont_Use_Cmd_or_Sh()
{
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
CreateTestFile("some.exe", binTestProjectPath);
var scriptCommandLine = "some.exe";
var command = ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
command.Should().NotBeNull();
command.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal);
Path.GetFileName(command.CommandName).Should().NotBe("cmd.exe");
Path.GetFileName(command.CommandName).Should().NotBe("sh");
}
[Fact]
public void Test_Script_Cmd_Files_Use_CmdExe()
{
if (! RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;
}
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
CreateTestFile("some.cmd", binTestProjectPath);
var scriptCommandLine = "some.cmd";
var command = ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
command.Should().NotBeNull();
command.ResolutionStrategy.Should().Be(CommandResolutionStrategy.ProjectLocal);
Path.GetFileName(command.CommandName).Should().Be("cmd.exe");
}
[Fact]
public void Test_Script_Builtins_Fail()
{
var sourceTestProjectPath = Path.Combine(s_testProjectRoot, "TestApp");
var binTestProjectPath = _root.CopyDirectory(sourceTestProjectPath).Path;
var project = ProjectContext.Create(binTestProjectPath, NuGetFramework.Parse("dnxcore50")).ProjectFile;
var scriptCommandLine = "echo";
Action action = () => ScriptExecutor.CreateCommandForScript(project, scriptCommandLine, new Dictionary<string, string>());
action.ShouldThrow<CommandUnknownException>();
}
private void CreateTestFile(string filename, string directory)
{
string path = Path.Combine(directory, filename);
File.WriteAllText(path, "echo hello");
}
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0.23107" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0.23107</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>833ffee1-7eed-4f51-8dfd-946d48833333</ProjectGuid>
<RootNamespace>Microsoft.DotNet.Cli.Utils.ScriptExecutorTests</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View file

@ -0,0 +1,26 @@
{
"version": "1.0.0-*",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23805",
"Microsoft.DotNet.ProjectModel": { "target": "project" },
"Microsoft.DotNet.Cli.Utils": { "target": "project" },
"Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" },
"xunit": "2.1.0",
"dotnet-test-xunit": "1.0.0-dev-48273-16"
},
"frameworks": {
"dnxcore50": {
"imports": "portable-net45+win8"
}
},
"content": [
"../../TestAssets/TestProjects/TestApp/**/*"
],
"testRunner": "xunit"
}

View file

@ -214,18 +214,15 @@ namespace Microsoft.DotNet.Tools.Publish.Tests
} }
[Fact] [Fact]
[ActiveIssue(982)]
public void PublishScriptsRun() public void PublishScriptsRun()
{ {
// create unique directories in the 'temp' folder // create unique directories in the 'temp' folder
var root = Temp.CreateDirectory(); var root = Temp.CreateDirectory();
var testAppDir = root.CreateDirectory("TestApp"); var testAppDir = root.CreateDirectory("TestAppWithScripts");
var testLibDir = root.CreateDirectory("TestLibrary");
//copy projects to the temp dir //copy projects to the temp dir
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestApp"), testAppDir); CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestAppWithScripts"), testAppDir);
CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir);
// run publish // run publish
var testProject = GetProjectPath(testAppDir); var testProject = GetProjectPath(testAppDir);

View file

@ -23,6 +23,7 @@
"content": [ "content": [
"../../TestAssets/TestProjects/TestApp/**/*", "../../TestAssets/TestProjects/TestApp/**/*",
"../../TestAssets/TestProjects/TestAppWithScripts/**/*",
"../../TestAssets/TestProjects/TestLibrary/**/*", "../../TestAssets/TestProjects/TestLibrary/**/*",
"../../TestAssets/TestProjects/CompileFail/**/*", "../../TestAssets/TestProjects/CompileFail/**/*",
"../../TestAssets/TestProjects/TestBindingRedirectGeneration/**/*", "../../TestAssets/TestProjects/TestBindingRedirectGeneration/**/*",