diff --git a/TestAssets/TestProjects/TestApp/project.json b/TestAssets/TestProjects/TestApp/project.json index 51b830d57..64ba761af 100644 --- a/TestAssets/TestProjects/TestApp/project.json +++ b/TestAssets/TestProjects/TestApp/project.json @@ -13,10 +13,5 @@ "frameworks": { "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%?"] } } diff --git a/TestAssets/TestProjects/TestAppWithScripts/Program.cs b/TestAssets/TestProjects/TestAppWithScripts/Program.cs new file mode 100644 index 000000000..7bcd4fbd3 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithScripts/Program.cs @@ -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; + } + } +} diff --git a/TestAssets/TestProjects/TestAppWithScripts/TestApp.xproj b/TestAssets/TestProjects/TestAppWithScripts/TestApp.xproj new file mode 100644 index 000000000..4cef17daa --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithScripts/TestApp.xproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 58808bbc-371e-47d6-a3d0-4902145eda4e + TestApp + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + diff --git a/TestAssets/TestProjects/TestAppWithScripts/echoscript.cmd b/TestAssets/TestProjects/TestAppWithScripts/echoscript.cmd new file mode 100644 index 000000000..a996a370f --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithScripts/echoscript.cmd @@ -0,0 +1,2 @@ +@echo off +echo %* \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppWithScripts/echoscript.sh b/TestAssets/TestProjects/TestAppWithScripts/echoscript.sh new file mode 100644 index 000000000..ef9907913 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithScripts/echoscript.sh @@ -0,0 +1 @@ +echo $@ \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppWithScripts/project.json b/TestAssets/TestProjects/TestAppWithScripts/project.json new file mode 100644 index 000000000..8df726e4d --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithScripts/project.json @@ -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%?"] + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs index 30835d159..4a5d0f4fe 100644 --- a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs +++ b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs @@ -86,8 +86,10 @@ namespace Microsoft.DotNet.Cli.Utils { var sb = new StringBuilder(); - var quoted = ShouldSurroundWithQuotes(arg); - if (quoted) sb.Append("\""); + var needsQuotes = ShouldSurroundWithQuotes(arg); + var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg); + + if (needsQuotes) sb.Append("\""); 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 + // when the argument is also quoted. // This ensures the outside quote is interpreted as // an argument delimiter - if (i == arg.Length) + if (i == arg.Length && isQuoted) { 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 else if (arg[i] == '"') { @@ -123,7 +133,7 @@ namespace Microsoft.DotNet.Cli.Utils } } - if (quoted) sb.Append("\""); + if (needsQuotes) sb.Append("\""); return sb.ToString(); } @@ -149,22 +159,14 @@ namespace Microsoft.DotNet.Cli.Utils 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) { - - if (character == '"') - { - - sb.Append('^'); - sb.Append('"'); - sb.Append('^'); - sb.Append(character); - } - else - { - sb.Append("^"); - sb.Append(character); - } + sb.Append("^"); + sb.Append(character); } if (quoted) sb.Append("^\""); @@ -172,35 +174,27 @@ namespace Microsoft.DotNet.Cli.Utils return sb.ToString(); } - /// - /// 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 - /// - /// - /// internal static bool ShouldSurroundWithQuotes(string argument) { // Don't quote already quoted strings - if (argument.StartsWith("\"", StringComparison.Ordinal) && - argument.EndsWith("\"", StringComparison.Ordinal)) + if (IsSurroundedWithQuotes(argument)) { return false; } // Only quote if whitespace exists in the string - if (argument.Contains(" ") || argument.Contains("\t") || argument.Contains("\n")) - { - return true; - } + return ArgumentContainsWhitespace(argument); + } - 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"); } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/Command.cs b/src/Microsoft.DotNet.Cli.Utils/Command.cs index 34a2c62c2..1480c2c7f 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Command.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Command.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; using NuGet.Frameworks; +using Microsoft.DotNet.ProjectModel; namespace Microsoft.DotNet.Cli.Utils { @@ -40,9 +41,9 @@ namespace Microsoft.DotNet.Cli.Utils ResolutionStrategy = commandSpec.ResolutionStrategy; } - public static Command CreateDotNet(string commandName, IEnumerable args, NuGetFramework framework = null, bool useComSpec = false) + public static Command CreateDotNet(string commandName, IEnumerable args, NuGetFramework framework = null) { - return Create("dotnet", new[] { commandName }.Concat(args), framework, useComSpec); + return Create("dotnet", new[] { commandName }.Concat(args), framework); } /// @@ -55,9 +56,23 @@ namespace Microsoft.DotNet.Cli.Utils /// /// /// - public static Command Create(string commandName, IEnumerable args, NuGetFramework framework = null, bool useComSpec = false) + public static Command Create(string commandName, IEnumerable 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 args, Project project, string[] inferredExtensionList) + { + var commandSpec = CommandResolver.TryResolveScriptCommandSpec(commandName, args, project, inferredExtensionList); if (commandSpec == null) { @@ -197,6 +212,8 @@ namespace Microsoft.DotNet.Cli.Utils public string CommandName => _process.StartInfo.FileName; + public string CommandArgs => _process.StartInfo.Arguments; + private string FormatProcessInfo(ProcessStartInfo info) { if (string.IsNullOrWhiteSpace(info.Arguments)) diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs index aecec2211..83589d989 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolutionStrategy.cs @@ -8,6 +8,9 @@ //command loaded from the same directory as the executing assembly BaseDirectory, + //command loaded from the same directory as a project.json file + ProjectLocal, + //command loaded from path Path, diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs index 030322c63..f62486969 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolver.cs @@ -13,52 +13,60 @@ namespace Microsoft.DotNet.Cli.Utils { internal static class CommandResolver { - public static CommandSpec TryResolveCommandSpec(string commandName, IEnumerable args, NuGetFramework framework = null, bool useComSpec = false) + public static CommandSpec TryResolveCommandSpec(string commandName, IEnumerable args, NuGetFramework framework = null) { - return ResolveFromRootedCommand(commandName, args, useComSpec) ?? - ResolveFromProjectDependencies(commandName, args, framework, useComSpec) ?? - ResolveFromProjectTools(commandName, args, useComSpec) ?? - ResolveFromAppBase(commandName, args, useComSpec) ?? - ResolveFromPath(commandName, args, useComSpec); + return ResolveFromRootedCommand(commandName, args) ?? + ResolveFromProjectDependencies(commandName, args, framework) ?? + ResolveFromProjectTools(commandName, args) ?? + ResolveFromAppBase(commandName, args) ?? + ResolveFromPath(commandName, args); + } + + public static CommandSpec TryResolveScriptCommandSpec(string commandName, IEnumerable 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 args, bool useComSpec = false) + private static CommandSpec ResolveFromPath(string commandName, IEnumerable args) { var commandPath = Env.GetCommandPath(commandName); return commandPath == null ? null - : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.Path, useComSpec); + : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.Path); } - private static CommandSpec ResolveFromAppBase(string commandName, IEnumerable args, bool useComSpec = false) + private static CommandSpec ResolveFromAppBase(string commandName, IEnumerable args) { - var commandPath = Env.GetCommandPathFromAppBase(PlatformServices.Default.Application.ApplicationBasePath, commandName); + var commandPath = Env.GetCommandPathFromRootPath(PlatformServices.Default.Application.ApplicationBasePath, commandName); return commandPath == null ? null - : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.BaseDirectory, useComSpec); + : CreateCommandSpecPreferringExe(commandName, args, commandPath, CommandResolutionStrategy.BaseDirectory); + } + + private static CommandSpec ResolveFromProjectPath(string commandName, IEnumerable 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 args, bool useComSpec = false) + private static CommandSpec ResolveFromRootedCommand(string commandName, IEnumerable args) { if (Path.IsPathRooted(commandName)) { - if (useComSpec) - { - return CreateComSpecCommandSpec(commandName, args, CommandResolutionStrategy.Path); - } - else - { - var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - return new CommandSpec(commandName, escapedArgs, CommandResolutionStrategy.Path); - } - + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); + return new CommandSpec(commandName, escapedArgs, CommandResolutionStrategy.Path); } return null; } public static CommandSpec ResolveFromProjectDependencies(string commandName, IEnumerable args, - NuGetFramework framework, bool useComSpec = false) + NuGetFramework framework) { if (framework == null) return null; @@ -72,7 +80,7 @@ namespace Microsoft.DotNet.Cli.Utils 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) @@ -101,7 +109,7 @@ namespace Microsoft.DotNet.Cli.Utils e == FileNameSuffixes.DotNet.DynamicLib)); } - public static CommandSpec ResolveFromProjectTools(string commandName, IEnumerable args, bool useComSpec = false) + public static CommandSpec ResolveFromProjectTools(string commandName, IEnumerable args) { var context = GetProjectContext(FrameworkConstants.CommonFrameworks.DnxCore50); @@ -147,7 +155,7 @@ namespace Microsoft.DotNet.Cli.Utils } private static CommandSpec ConfigureCommandFromPackage(string commandName, IEnumerable args, - PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null, bool useComSpec = false) + PackageDescription commandPackage, ProjectContext projectContext, string depsPath = null) { var files = commandPackage.Library.Files; @@ -157,11 +165,11 @@ namespace Microsoft.DotNet.Cli.Utils 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 args, - IEnumerable files, string packageDir, string depsPath = null, bool useComSpec = false) + IEnumerable files, string packageDir, string depsPath = null) { var fileName = string.Empty; @@ -192,24 +200,18 @@ namespace Microsoft.DotNet.Cli.Utils fileName = Path.Combine(packageDir, commandPath); } - if (useComSpec) - { - return CreateComSpecCommandSpec(fileName, args, CommandResolutionStrategy.NugetPackage); - } - else - { - var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); - return new CommandSpec(fileName, escapedArgs, CommandResolutionStrategy.NugetPackage); - } + var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); + return new CommandSpec(fileName, escapedArgs, CommandResolutionStrategy.NugetPackage); } private static CommandSpec CreateCommandSpecPreferringExe( string commandName, IEnumerable args, string commandPath, - CommandResolutionStrategy resolutionStrategy, - bool useComSpec = false) + CommandResolutionStrategy resolutionStrategy) { + var useComSpec = false; + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows && Path.GetExtension(commandPath).Equals(".cmd", StringComparison.OrdinalIgnoreCase)) { @@ -228,7 +230,7 @@ namespace Microsoft.DotNet.Cli.Utils if (useComSpec) { - return CreateComSpecCommandSpec(commandPath, args, resolutionStrategy); + return CreateCmdCommandSpec(commandPath, args, resolutionStrategy); } else { @@ -237,14 +239,14 @@ namespace Microsoft.DotNet.Cli.Utils } } - private static CommandSpec CreateComSpecCommandSpec( + private static CommandSpec CreateCmdCommandSpec( string command, IEnumerable args, CommandResolutionStrategy resolutionStrategy) { - // To prevent Command Not Found, comspec gets passed in as - // the command already in some cases var comSpec = Environment.GetEnvironmentVariable("ComSpec"); + + // Handle the case where ComSpec is already the command if (command.Equals(comSpec, StringComparison.OrdinalIgnoreCase)) { command = args.FirstOrDefault(); diff --git a/src/Microsoft.DotNet.Cli.Utils/Env.cs b/src/Microsoft.DotNet.Cli.Utils/Env.cs index 66144b02d..6fdac60f1 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Env.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Env.cs @@ -66,14 +66,14 @@ namespace Microsoft.DotNet.Cli.Utils 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()) { 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); return commandPath; diff --git a/src/Microsoft.DotNet.Cli.Utils/ScriptExecutor.cs b/src/Microsoft.DotNet.Cli.Utils/ScriptExecutor.cs index 6a986756e..f28bef08b 100644 --- a/src/Microsoft.DotNet.Cli.Utils/ScriptExecutor.cs +++ b/src/Microsoft.DotNet.Cli.Utils/ScriptExecutor.cs @@ -17,82 +17,47 @@ namespace Microsoft.DotNet.Cli.Utils public static ICommand CreateCommandForScript(Project project, string scriptCommandLine, Func getVariable) { - // Preserve quotation marks around arguments since command is about to be passed to a shell. May need - // the quotes to ensure the shell groups arguments correctly. + var scriptArguments = ParseScriptArguments(project, scriptCommandLine, getVariable); + 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 ParseScriptArguments(Project project, string scriptCommandLine, Func getVariable) + { var scriptArguments = CommandGrammar.Process( scriptCommandLine, 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(); if (scriptArguments.Length == 0) { 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 - // 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(); - } + return new string[] { "", ".cmd" }; } else { - // Special-case a script name that, perhaps with added .sh, matches an existing file. - 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 new string[] { "", ".sh" }; } - - return Command.Create(scriptArguments.FirstOrDefault(), scriptArguments.Skip(1), useComSpec: useComSpec) - .WorkingDirectory(project.ProjectDirectory); } + private static Func WrapVariableDictionary(IDictionary contextVariables) { return key => diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index 40280dd62..d1d284f2e 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -135,7 +135,8 @@ namespace Microsoft.DotNet.Tools.Compiler // Run pre-compile event var contextVariables = new Dictionary() { - { "compile:TargetFramework", context.TargetFramework.DotNetFrameworkName }, + { "compile:TargetFramework", context.TargetFramework.GetShortFolderName() }, + { "compile:FullTargetFramework", context.TargetFramework.DotNetFrameworkName }, { "compile:Configuration", args.ConfigValue }, { "compile:OutputFile", outputName }, { "compile:OutputDir", outputPath.TrimEnd('\\', '/') }, diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index 112712794..ce3acfc60 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -96,7 +96,8 @@ namespace Microsoft.DotNet.Tools.Publish { "publish:ProjectPath", context.ProjectDirectory }, { "publish:Configuration", configuration }, { "publish:OutputPath", outputPath }, - { "publish:Framework", context.TargetFramework.Framework }, + { "publish:TargetFramework", context.TargetFramework.GetShortFolderName() }, + { "publish:FullTargetFramework", context.TargetFramework.DotNetFrameworkName }, { "publish:Runtime", context.RuntimeIdentifier }, }; diff --git a/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs index adde47bf9..4b4bb1614 100644 --- a/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs +++ b/test/ArgumentForwardingTests/ArgumentForwardingTests/ArgumentForwardingTests.cs @@ -94,16 +94,12 @@ namespace Microsoft.DotNet.Tests.ArgumentForwarding [InlineData("\"abc\"\t\td\te")] [InlineData(@"a\\b d""e f""g h")] [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 e")] [InlineData(@"a""b c""d e""f g""h i""j k""l")] [InlineData(@"a b c""def")] - [InlineData(@"""\a\"" \\""\\\ b c")] - [InlineData(@"a\""b \\ cd ""\e f\"" \\""\\\")] public void TestArgumentForwardingCmd(string testUserArgument) { 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); + } + /// /// EscapeAndEvaluateArgumentString returns a representation of string[] args /// when rawEvaluatedArgument is passed as an argument to a process using diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs index f1672a62a..ef78f1018 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs @@ -24,7 +24,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities if (!Path.IsPathRooted(_command)) { _command = Env.GetCommandPath(_command) ?? - Env.GetCommandPathFromAppBase(AppContext.BaseDirectory, _command); + Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, _command); } Console.WriteLine($"Executing - {_command} {args}"); @@ -43,7 +43,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities Console.WriteLine($"Executing (Captured Output) - {_command} {args}"); 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 stdErr = new StreamForwarder(); diff --git a/test/ScriptExecutorTests/ScriptExecutorTests.cs b/test/ScriptExecutorTests/ScriptExecutorTests.cs new file mode 100644 index 000000000..f0ed16ab4 --- /dev/null +++ b/test/ScriptExecutorTests/ScriptExecutorTests.cs @@ -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()); + + 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()); + action.ShouldThrow(); + } + + [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()); + + 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()); + + 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()); + + 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()); + action.ShouldThrow(); + } + + private void CreateTestFile(string filename, string directory) + { + string path = Path.Combine(directory, filename); + File.WriteAllText(path, "echo hello"); + } + } +} diff --git a/test/ScriptExecutorTests/ScriptExecutorTests.xproj b/test/ScriptExecutorTests/ScriptExecutorTests.xproj new file mode 100644 index 000000000..8b66a40d6 --- /dev/null +++ b/test/ScriptExecutorTests/ScriptExecutorTests.xproj @@ -0,0 +1,19 @@ + + + + 14.0.23107 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 833ffee1-7eed-4f51-8dfd-946d48833333 + Microsoft.DotNet.Cli.Utils.ScriptExecutorTests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/test/ScriptExecutorTests/project.json b/test/ScriptExecutorTests/project.json new file mode 100644 index 000000000..22bda6b8e --- /dev/null +++ b/test/ScriptExecutorTests/project.json @@ -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" +} diff --git a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs index a9d465ee5..ac61d0d42 100644 --- a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs +++ b/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs @@ -214,18 +214,15 @@ namespace Microsoft.DotNet.Tools.Publish.Tests } [Fact] - [ActiveIssue(982)] public void PublishScriptsRun() { // create unique directories in the 'temp' folder var root = Temp.CreateDirectory(); - var testAppDir = root.CreateDirectory("TestApp"); - var testLibDir = root.CreateDirectory("TestLibrary"); + var testAppDir = root.CreateDirectory("TestAppWithScripts"); //copy projects to the temp dir - CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestApp"), testAppDir); - CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestLibrary"), testLibDir); + CopyProjectToTempDir(Path.Combine(_testProjectsRoot, "TestAppWithScripts"), testAppDir); // run publish var testProject = GetProjectPath(testAppDir); diff --git a/test/dotnet-publish.Tests/project.json b/test/dotnet-publish.Tests/project.json index db7d665b1..75ab3dd6b 100644 --- a/test/dotnet-publish.Tests/project.json +++ b/test/dotnet-publish.Tests/project.json @@ -23,6 +23,7 @@ "content": [ "../../TestAssets/TestProjects/TestApp/**/*", + "../../TestAssets/TestProjects/TestAppWithScripts/**/*", "../../TestAssets/TestProjects/TestLibrary/**/*", "../../TestAssets/TestProjects/CompileFail/**/*", "../../TestAssets/TestProjects/TestBindingRedirectGeneration/**/*",