Call into project in .NET SDK to create deps.json files for tools instead of doing so directly

This commit is contained in:
Daniel Plaisted 2017-04-18 18:48:53 -07:00
parent c8a6b7b97a
commit fa51bb43fc
13 changed files with 337 additions and 195 deletions

View file

@ -8,22 +8,14 @@ namespace Microsoft.DotNet.Cli.Utils
{
public interface IPackagedCommandSpecFactory
{
CommandSpec CreateCommandSpecFromLibrary(
LockFileTargetLibrary toolLibrary,
string commandName,
IEnumerable<string> commandArguments,
IEnumerable<string> allowedExtensions,
string nugetPackagesRoot,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath);
// Code review TODO: Is it OK to make breaking changes to the CLI Utils API surface?
CommandSpec CreateCommandSpecFromLibrary(
LockFileTargetLibrary toolLibrary,
string commandName,
IEnumerable<string> commandArguments,
IEnumerable<string> allowedExtensions,
IEnumerable<string> packageFolders,
LockFile lockFile,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath);

View file

@ -26,5 +26,7 @@ namespace Microsoft.DotNet.Cli.Utils
NuGetFramework DotnetCliToolTargetFramework { get; }
Dictionary<string, string> EnvironmentVariables { get; }
string ToolDepsJsonGeneratorProject { get; }
}
}

View file

@ -92,6 +92,19 @@ namespace Microsoft.DotNet.Cli.Utils
}
}
public string ToolDepsJsonGeneratorProject
{
get
{
var generatorProject = _project
.AllEvaluatedProperties
.FirstOrDefault(p => p.Name.Equals("ToolDepsJsonGeneratorProject"))
?.EvaluatedValue;
return generatorProject;
}
}
public MSBuildProject(
string msBuildProjectPath,
NuGetFramework framework,

View file

@ -27,28 +27,7 @@ namespace Microsoft.DotNet.Cli.Utils
string commandName,
IEnumerable<string> commandArguments,
IEnumerable<string> allowedExtensions,
string nugetPackagesRoot,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath)
{
return CreateCommandSpecFromLibrary(
toolLibrary,
commandName,
commandArguments,
allowedExtensions,
new List<string> { nugetPackagesRoot },
commandResolutionStrategy,
depsFilePath,
runtimeConfigPath);
}
public CommandSpec CreateCommandSpecFromLibrary(
LockFileTargetLibrary toolLibrary,
string commandName,
IEnumerable<string> commandArguments,
IEnumerable<string> allowedExtensions,
IEnumerable<string> packageFolders,
LockFile lockFile,
CommandResolutionStrategy commandResolutionStrategy,
string depsFilePath,
string runtimeConfigPath)
@ -72,7 +51,7 @@ namespace Microsoft.DotNet.Cli.Utils
return null;
}
var commandPath = GetCommandFilePath(packageFolders, toolLibrary, toolAssembly);
var commandPath = GetCommandFilePath(lockFile, toolLibrary, toolAssembly);
if (!File.Exists(commandPath))
{
@ -89,21 +68,16 @@ namespace Microsoft.DotNet.Cli.Utils
commandArguments,
depsFilePath,
commandResolutionStrategy,
packageFolders,
lockFile.GetNormalizedPackageFolders(),
runtimeConfigPath);
}
private string GetCommandFilePath(
IEnumerable<string> packageFolders,
LockFile lockFile,
LockFileTargetLibrary toolLibrary,
LockFileItem runtimeAssembly)
{
var packageFoldersCount = packageFolders.Count();
var userPackageFolder = packageFoldersCount == 1 ? string.Empty : packageFolders.First();
var fallbackPackageFolders = packageFoldersCount > 1 ? packageFolders.Skip(1) : packageFolders;
var packageDirectory = new FallbackPackagePathResolver(userPackageFolder, fallbackPackageFolders)
.GetPackageDirectory(toolLibrary.Name, toolLibrary.Version);
var packageDirectory = lockFile.GetPackageDirectory(toolLibrary);
if (packageDirectory == null)
{

View file

@ -120,15 +120,13 @@ namespace Microsoft.DotNet.Cli.Utils
var lockFile = project.GetLockFile();
var toolLibrary = GetToolLibraryForContext(lockFile, commandName, framework);
var normalizedNugetPackagesRoot =
PathUtility.EnsureNoTrailingDirectorySeparator(lockFile.PackageFolders.First().Path);
var commandSpec = _packagedCommandSpecFactory.CreateCommandSpecFromLibrary(
toolLibrary,
commandName,
commandArguments,
allowedExtensions,
normalizedNugetPackagesRoot,
lockFile,
s_commandResolutionStrategy,
depsFilePath,
runtimeConfigPath);

View file

@ -170,10 +170,8 @@ namespace Microsoft.DotNet.Cli.Utils
toolLibraryRange,
toolPackageFramework,
toolLockFile,
depsFileRoot);
var packageFolders = toolLockFile.PackageFolders.Select(p =>
PathUtility.EnsureNoTrailingDirectorySeparator(p.Path));
depsFileRoot,
project.ToolDepsJsonGeneratorProject);
Reporter.Verbose.WriteLine(string.Format(
LocalizableStrings.AttemptingToCreateCommandSpec,
@ -184,7 +182,7 @@ namespace Microsoft.DotNet.Cli.Utils
commandName,
args,
_allowedCommandExtensions,
packageFolders,
toolLockFile,
s_commandResolutionStrategy,
depsFilePath,
null);
@ -281,7 +279,8 @@ namespace Microsoft.DotNet.Cli.Utils
SingleProjectInfo toolLibrary,
NuGetFramework framework,
LockFile toolLockFile,
string depsPathRoot)
string depsPathRoot,
string toolDepsJsonGeneratorProject)
{
var depsJsonPath = Path.Combine(
depsPathRoot,
@ -292,7 +291,7 @@ namespace Microsoft.DotNet.Cli.Utils
ProjectToolsCommandResolverName,
depsJsonPath));
EnsureToolJsonDepsFileExists(toolLockFile, framework, depsJsonPath, toolLibrary);
EnsureToolJsonDepsFileExists(toolLockFile, framework, depsJsonPath, toolLibrary, toolDepsJsonGeneratorProject);
return depsJsonPath;
}
@ -301,11 +300,12 @@ namespace Microsoft.DotNet.Cli.Utils
LockFile toolLockFile,
NuGetFramework framework,
string depsPath,
SingleProjectInfo toolLibrary)
SingleProjectInfo toolLibrary,
string toolDepsJsonGeneratorProject)
{
if (!File.Exists(depsPath))
{
GenerateDepsJsonFile(toolLockFile, framework, depsPath, toolLibrary);
GenerateDepsJsonFile(toolLockFile, framework, depsPath, toolLibrary, toolDepsJsonGeneratorProject);
}
}
@ -313,7 +313,8 @@ namespace Microsoft.DotNet.Cli.Utils
LockFile toolLockFile,
NuGetFramework framework,
string depsPath,
SingleProjectInfo toolLibrary)
SingleProjectInfo toolLibrary,
string toolDepsJsonGeneratorProject)
{
Reporter.Verbose.WriteLine(string.Format(
LocalizableStrings.GeneratingDepsJson,
@ -323,11 +324,52 @@ namespace Microsoft.DotNet.Cli.Utils
.Build(toolLibrary, null, toolLockFile, framework, null);
var tempDepsFile = Path.GetTempFileName();
using (var fileStream = File.Open(tempDepsFile, FileMode.Open, FileAccess.Write))
{
var dependencyContextWriter = new DependencyContextWriter();
dependencyContextWriter.Write(dependencyContext, fileStream);
var args = new List<string>();
args.Add(toolDepsJsonGeneratorProject);
args.Add($"/p:ProjectAssetsFile=\"{toolLockFile.Path}\"");
args.Add($"/p:ToolName={toolLibrary.Name}");
args.Add($"/p:ProjectDepsFilePath={tempDepsFile}");
// Look for the .props file in the Microsoft.NETCore.App package, until NuGet
// generates .props and .targets files for tool restores (https://github.com/NuGet/Home/issues/5037)
var platformLibrary = toolLockFile.Targets
.FirstOrDefault(t => framework == t.TargetFramework)
?.GetPlatformLibrary();
if (platformLibrary != null)
{
string buildRelativePath = platformLibrary.Build.FirstOrDefault()?.Path;
var platformLibraryPath = toolLockFile.GetPackageDirectory(platformLibrary);
if (platformLibraryPath != null && buildRelativePath != null)
{
// Get rid of "_._" filename
buildRelativePath = Path.GetDirectoryName(buildRelativePath);
string platformLibraryBuildFolderPath = Path.Combine(platformLibraryPath, buildRelativePath);
var platformLibraryPropsFile = Directory.GetFiles(platformLibraryBuildFolderPath, "*.props").FirstOrDefault();
if (platformLibraryPropsFile != null)
{
args.Add($"/p:AdditionalImport={platformLibraryPropsFile}");
}
}
}
// Delete temporary file created by Path.GetTempFileName(), otherwise the GenerateBuildDependencyFile target
// will think the deps file is up-to-date and skip executing
File.Delete(tempDepsFile);
var result = new MSBuildForwardingAppWithoutLogging(args).Execute();
if (result != 0)
{
// TODO: Can / should we show the MSBuild output if there is a failure?
throw new GracefulException(string.Format(LocalizableStrings.UnableToGenerateDepsJson, toolDepsJsonGeneratorProject));
}
try

View file

@ -0,0 +1,33 @@
using Microsoft.DotNet.Tools.Common;
using NuGet.Packaging;
using NuGet.ProjectModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.DotNet.Cli.Utils
{
static class LockFileExtensions
{
public static string GetPackageDirectory(this LockFile lockFile, LockFileTargetLibrary library)
{
var packageFolders = lockFile.GetNormalizedPackageFolders();
var packageFoldersCount = packageFolders.Count();
var userPackageFolder = packageFoldersCount == 1 ? string.Empty : packageFolders.First();
var fallbackPackageFolders = packageFoldersCount > 1 ? packageFolders.Skip(1) : packageFolders;
var packageDirectory = new FallbackPackagePathResolver(userPackageFolder, fallbackPackageFolders)
.GetPackageDirectory(library.Name, library.Version);
return packageDirectory;
}
public static IEnumerable<string> GetNormalizedPackageFolders(this LockFile lockFile)
{
return lockFile.PackageFolders.Select(p =>
PathUtility.EnsureNoTrailingDirectorySeparator(p.Path));
}
}
}

View file

@ -0,0 +1,108 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Utils
{
/// <summary>
/// A class which encapsulates logic needed to forward arguments from the current process to another process
/// invoked with the dotnet.exe host.
/// </summary>
internal class ForwardingAppImplementation
{
private const string s_hostExe = "dotnet";
private readonly string _forwardApplicationPath;
private readonly IEnumerable<string> _argsToForward;
private readonly string _depsFile;
private readonly string _runtimeConfig;
private readonly string _additionalProbingPath;
private Dictionary<string, string> _environmentVariables;
private readonly string[] _allArgs;
public ForwardingAppImplementation(
string forwardApplicationPath,
IEnumerable<string> argsToForward,
string depsFile = null,
string runtimeConfig = null,
string additionalProbingPath = null,
Dictionary<string, string> environmentVariables = null)
{
_forwardApplicationPath = forwardApplicationPath;
_argsToForward = argsToForward;
_depsFile = depsFile;
_runtimeConfig = runtimeConfig;
_additionalProbingPath = additionalProbingPath;
_environmentVariables = environmentVariables;
var allArgs = new List<string>();
allArgs.Add("exec");
if (_depsFile != null)
{
allArgs.Add("--depsfile");
allArgs.Add(_depsFile);
}
if (_runtimeConfig != null)
{
allArgs.Add("--runtimeconfig");
allArgs.Add(_runtimeConfig);
}
if (_additionalProbingPath != null)
{
allArgs.Add("--additionalprobingpath");
allArgs.Add(_additionalProbingPath);
}
allArgs.Add(_forwardApplicationPath);
allArgs.AddRange(_argsToForward);
_allArgs = allArgs.ToArray();
}
public int Execute()
{
return GetProcessStartInfo().Execute();
}
public ProcessStartInfo GetProcessStartInfo()
{
var processInfo = new ProcessStartInfo
{
FileName = GetHostExeName(),
Arguments = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(_allArgs),
UseShellExecute = false
};
if (_environmentVariables != null)
{
foreach (var entry in _environmentVariables)
{
processInfo.Environment[entry.Key] = entry.Value;
}
}
return processInfo;
}
public ForwardingAppImplementation WithEnvironmentVariable(string name, string value)
{
_environmentVariables = _environmentVariables ?? new Dictionary<string, string>();
_environmentVariables.Add(name, value);
return this;
}
private string GetHostExeName()
{
return $"{s_hostExe}{FileNameSuffixes.CurrentPlatform.Exe}";
}
}
}

View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Microsoft.DotNet.Cli.Utils
{
internal class MSBuildForwardingAppWithoutLogging
{
private const string MSBuildExeName = "MSBuild.dll";
private const string SdksDirectoryName = "Sdks";
private readonly ForwardingAppImplementation _forwardingApp;
private readonly Dictionary<string, string> _msbuildRequiredEnvironmentVariables =
new Dictionary<string, string>
{
{ "MSBuildExtensionsPath", AppContext.BaseDirectory },
{ "CscToolExe", GetRunCscPath() },
{ "MSBuildSDKsPath", GetMSBuildSDKsPath() }
};
private readonly IEnumerable<string> _msbuildRequiredParameters =
new List<string> { "/m", "/v:m" };
public MSBuildForwardingAppWithoutLogging(IEnumerable<string> argsToForward, string msbuildPath = null)
{
_forwardingApp = new ForwardingAppImplementation(
msbuildPath ?? GetMSBuildExePath(),
_msbuildRequiredParameters.Concat(argsToForward.Select(Escape)),
environmentVariables: _msbuildRequiredEnvironmentVariables);
}
public virtual ProcessStartInfo GetProcessStartInfo()
{
return _forwardingApp
.GetProcessStartInfo();
}
public int Execute()
{
return GetProcessStartInfo().Execute();
}
private static string Escape(string arg) =>
// this is a workaround for https://github.com/Microsoft/msbuild/issues/1622
(arg.StartsWith("/p:RestoreSources=", StringComparison.OrdinalIgnoreCase)) ?
arg.Replace(";", "%3B")
.Replace("://", ":%2F%2F") :
arg;
private static string GetMSBuildExePath()
{
return Path.Combine(
AppContext.BaseDirectory,
MSBuildExeName);
}
private static string GetMSBuildSDKsPath()
{
var envMSBuildSDKsPath = Environment.GetEnvironmentVariable("MSBuildSDKsPath");
if (envMSBuildSDKsPath != null)
{
return envMSBuildSDKsPath;
}
return Path.Combine(
AppContext.BaseDirectory,
SdksDirectoryName);
}
private static string GetRunCscPath()
{
var scriptExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".cmd" : ".sh";
return Path.Combine(AppContext.BaseDirectory, "Roslyn", $"RunCsc{scriptExtension}");
}
}
}

View file

@ -4,7 +4,7 @@
using System;
using System.Diagnostics;
namespace Microsoft.DotNet.Cli
namespace Microsoft.DotNet.Cli.Utils
{
internal static class ProcessStartInfoExtensions
{

View file

@ -1,28 +1,14 @@
// 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 Microsoft.DotNet.Cli.Utils;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.DotNet.Cli.Utils;
using System.Text;
namespace Microsoft.DotNet.Cli
{
/// <summary>
/// A class which encapsulates logic needed to forward arguments from the current process to another process
/// invoked with the dotnet.exe host.
/// </summary>
public class ForwardingApp
{
private const string s_hostExe = "dotnet";
private readonly string _forwardApplicationPath;
private readonly IEnumerable<string> _argsToForward;
private readonly string _depsFile;
private readonly string _runtimeConfig;
private readonly string _additionalProbingPath;
private Dictionary<string, string> _environmentVariables;
private readonly string[] _allArgs;
ForwardingAppImplementation _implementation;
public ForwardingApp(
string forwardApplicationPath,
@ -32,77 +18,29 @@ namespace Microsoft.DotNet.Cli
string additionalProbingPath = null,
Dictionary<string, string> environmentVariables = null)
{
_forwardApplicationPath = forwardApplicationPath;
_argsToForward = argsToForward;
_depsFile = depsFile;
_runtimeConfig = runtimeConfig;
_additionalProbingPath = additionalProbingPath;
_environmentVariables = environmentVariables;
var allArgs = new List<string>();
allArgs.Add("exec");
if (_depsFile != null)
{
allArgs.Add("--depsfile");
allArgs.Add(_depsFile);
}
if (_runtimeConfig != null)
{
allArgs.Add("--runtimeconfig");
allArgs.Add(_runtimeConfig);
}
if (_additionalProbingPath != null)
{
allArgs.Add("--additionalprobingpath");
allArgs.Add(_additionalProbingPath);
}
allArgs.Add(_forwardApplicationPath);
allArgs.AddRange(_argsToForward);
_allArgs = allArgs.ToArray();
}
public int Execute()
{
return GetProcessStartInfo().Execute();
_implementation = new ForwardingAppImplementation(
forwardApplicationPath,
argsToForward,
depsFile,
runtimeConfig,
additionalProbingPath,
environmentVariables);
}
public ProcessStartInfo GetProcessStartInfo()
{
var processInfo = new ProcessStartInfo
{
FileName = GetHostExeName(),
Arguments = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(_allArgs),
UseShellExecute = false
};
if (_environmentVariables != null)
{
foreach (var entry in _environmentVariables)
{
processInfo.Environment[entry.Key] = entry.Value;
}
}
return processInfo;
return _implementation.GetProcessStartInfo();
}
public ForwardingApp WithEnvironmentVariable(string name, string value)
{
_environmentVariables = _environmentVariables ?? new Dictionary<string, string>();
_environmentVariables.Add(name, value);
_implementation = _implementation.WithEnvironmentVariable(name, value);
return this;
}
private string GetHostExeName()
public int Execute()
{
return $"{s_hostExe}{FileNameSuffixes.CurrentPlatform.Exe}";
return _implementation.Execute();
}
}
}

View file

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools
{

View file

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.CommandLine;
using System.Diagnostics;
using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Tools.MSBuild
{
@ -17,24 +18,9 @@ namespace Microsoft.DotNet.Tools.MSBuild
{
internal const string TelemetrySessionIdEnvironmentVariableName = "DOTNET_CLI_TELEMETRY_SESSIONID";
private const string MSBuildExeName = "MSBuild.dll";
private MSBuildForwardingAppWithoutLogging _forwardingAppWithoutLogging;
private const string SdksDirectoryName = "Sdks";
private readonly ForwardingApp _forwardingApp;
private readonly Dictionary<string, string> _msbuildRequiredEnvironmentVariables =
new Dictionary<string, string>
{
{ "MSBuildExtensionsPath", AppContext.BaseDirectory },
{ "CscToolExe", GetRunCscPath() },
{ "MSBuildSDKsPath", GetMSBuildSDKsPath() }
};
private readonly IEnumerable<string> _msbuildRequiredParameters =
new List<string> { "/m", "/v:m" };
public MSBuildForwardingApp(IEnumerable<string> argsToForward, string msbuildPath = null)
static IEnumerable<string> ConcatTelemetryLogger(IEnumerable<string> argsToForward)
{
if (Telemetry.CurrentSessionId != null)
{
@ -42,7 +28,7 @@ namespace Microsoft.DotNet.Tools.MSBuild
{
Type loggerType = typeof(MSBuildLogger);
argsToForward = argsToForward
return argsToForward
.Concat(new[]
{
$"/Logger:{loggerType.FullName},{loggerType.GetTypeInfo().Assembly.Location}"
@ -53,57 +39,28 @@ namespace Microsoft.DotNet.Tools.MSBuild
// Exceptions during telemetry shouldn't cause anything else to fail
}
}
return argsToForward;
}
_forwardingApp = new ForwardingApp(
msbuildPath ?? GetMSBuildExePath(),
_msbuildRequiredParameters.Concat(argsToForward.Select(Escape)),
environmentVariables: _msbuildRequiredEnvironmentVariables);
public MSBuildForwardingApp(IEnumerable<string> argsToForward, string msbuildPath = null)
{
_forwardingAppWithoutLogging = new MSBuildForwardingAppWithoutLogging(
ConcatTelemetryLogger(argsToForward),
msbuildPath);
}
public ProcessStartInfo GetProcessStartInfo()
{
return _forwardingApp
.WithEnvironmentVariable(TelemetrySessionIdEnvironmentVariableName, Telemetry.CurrentSessionId)
.GetProcessStartInfo();
var ret = _forwardingAppWithoutLogging.GetProcessStartInfo();
ret.Environment[TelemetrySessionIdEnvironmentVariableName] = Telemetry.CurrentSessionId;
return ret;
}
public int Execute()
{
return GetProcessStartInfo().Execute();
}
private static string Escape(string arg) =>
// this is a workaround for https://github.com/Microsoft/msbuild/issues/1622
(arg.StartsWith("/p:RestoreSources=", StringComparison.OrdinalIgnoreCase)) ?
arg.Replace(";", "%3B")
.Replace("://", ":%2F%2F") :
arg;
private static string GetMSBuildExePath()
{
return Path.Combine(
AppContext.BaseDirectory,
MSBuildExeName);
}
private static string GetMSBuildSDKsPath()
{
var envMSBuildSDKsPath = Environment.GetEnvironmentVariable("MSBuildSDKsPath");
if (envMSBuildSDKsPath != null)
{
return envMSBuildSDKsPath;
}
return Path.Combine(
AppContext.BaseDirectory,
SdksDirectoryName);
}
private static string GetRunCscPath()
{
var scriptExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".cmd" : ".sh";
return Path.Combine(AppContext.BaseDirectory, "Roslyn", $"RunCsc{scriptExtension}");
}
}
}