|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.DotNet.Cli.Utils;
using Microsoft.DotNet.Cli.Utils.Extensions;
using Microsoft.Extensions.DependencyModel;
namespace Microsoft.DotNet.Cli.CommandFactory.CommandResolution;
public class DepsJsonCommandResolver(Muxer muxer, string nugetPackageRoot) : ICommandResolver
{
private static readonly string[] s_extensionPreferenceOrder =
[
"",
".exe",
".dll"
];
private readonly string _nugetPackageRoot = nugetPackageRoot;
private readonly Muxer _muxer = muxer;
public DepsJsonCommandResolver(string nugetPackageRoot)
: this(new Muxer(), nugetPackageRoot) { }
public CommandSpec Resolve(CommandResolverArguments commandResolverArguments)
{
if (commandResolverArguments.CommandName == null
|| commandResolverArguments.DepsJsonFile == null)
{
return null;
}
return ResolveFromDepsJsonFile(
commandResolverArguments.CommandName,
commandResolverArguments.CommandArguments.OrEmptyIfNull(),
commandResolverArguments.DepsJsonFile);
}
private CommandSpec ResolveFromDepsJsonFile(
string commandName,
IEnumerable<string> commandArgs,
string depsJsonFile)
{
var dependencyContext = LoadDependencyContextFromFile(depsJsonFile);
var commandPath = GetCommandPathFromDependencyContext(commandName, dependencyContext);
if (commandPath == null)
{
return null;
}
return CreateCommandSpecUsingMuxerIfPortable(
commandPath,
commandArgs,
depsJsonFile,
_nugetPackageRoot,
IsPortableApp(commandPath));
}
public DependencyContext LoadDependencyContextFromFile(string depsJsonFile)
{
DependencyContext dependencyContext = null;
DependencyContextJsonReader contextReader = new();
using (var contextStream = File.OpenRead(depsJsonFile))
{
dependencyContext = contextReader.Read(contextStream);
}
return dependencyContext;
}
public string GetCommandPathFromDependencyContext(string commandName, DependencyContext dependencyContext)
{
var commandCandidates = new List<CommandCandidate>();
var assemblyCommandCandidates = GetCommandCandidates(
commandName,
dependencyContext,
CommandCandidateType.RuntimeCommandCandidate);
var nativeCommandCandidates = GetCommandCandidates(
commandName,
dependencyContext,
CommandCandidateType.NativeCommandCandidate);
commandCandidates.AddRange(assemblyCommandCandidates);
commandCandidates.AddRange(nativeCommandCandidates);
var command = ChooseCommandCandidate(commandCandidates);
return command?.GetAbsoluteCommandPath(_nugetPackageRoot);
}
private IEnumerable<CommandCandidate> GetCommandCandidates(
string commandName,
DependencyContext dependencyContext,
CommandCandidateType commandCandidateType)
{
var commandCandidates = new List<CommandCandidate>();
foreach (var runtimeLibrary in dependencyContext.RuntimeLibraries)
{
IEnumerable<RuntimeAssetGroup> runtimeAssetGroups = null;
if (commandCandidateType == CommandCandidateType.NativeCommandCandidate)
{
runtimeAssetGroups = runtimeLibrary.NativeLibraryGroups;
}
else if (commandCandidateType == CommandCandidateType.RuntimeCommandCandidate)
{
runtimeAssetGroups = runtimeLibrary.RuntimeAssemblyGroups;
}
commandCandidates.AddRange(GetCommandCandidatesFromRuntimeAssetGroups(
commandName,
runtimeAssetGroups,
runtimeLibrary.Name,
runtimeLibrary.Version));
}
return commandCandidates;
}
private static IEnumerable<CommandCandidate> GetCommandCandidatesFromRuntimeAssetGroups(
string commandName,
IEnumerable<RuntimeAssetGroup> runtimeAssetGroups,
string PackageName,
string PackageVersion)
{
var candidateAssetGroups = runtimeAssetGroups
.Where(r => r.Runtime == string.Empty)
.Where(a =>
a.AssetPaths.Any(p =>
Path.GetFileNameWithoutExtension(p).Equals(commandName, StringComparison.OrdinalIgnoreCase)));
var commandCandidates = new List<CommandCandidate>();
foreach (var candidateAssetGroup in candidateAssetGroups)
{
var candidateAssetPaths = candidateAssetGroup.AssetPaths.Where(
p => Path.GetFileNameWithoutExtension(p)
.Equals(commandName, StringComparison.OrdinalIgnoreCase));
foreach (var candidateAssetPath in candidateAssetPaths)
{
commandCandidates.Add(new CommandCandidate
{
PackageName = PackageName,
PackageVersion = PackageVersion,
RelativeCommandPath = candidateAssetPath
});
}
}
return commandCandidates;
}
private static CommandCandidate ChooseCommandCandidate(IEnumerable<CommandCandidate> commandCandidates)
{
foreach (var extension in s_extensionPreferenceOrder)
{
var candidate = commandCandidates
.FirstOrDefault(p => Path.GetExtension(p.RelativeCommandPath)
.Equals(extension, StringComparison.OrdinalIgnoreCase));
if (candidate != null)
{
return candidate;
}
}
return null;
}
private CommandSpec CreateCommandSpecUsingMuxerIfPortable(
string commandPath,
IEnumerable<string> commandArgs,
string depsJsonFile,
string nugetPackagesRoot,
bool isPortable)
{
var depsFileArguments = GetDepsFileArguments(depsJsonFile);
var additionalProbingPathArguments = GetAdditionalProbingPathArguments();
List<string> muxerArgs = ["exec"];
muxerArgs.AddRange(depsFileArguments);
muxerArgs.AddRange(additionalProbingPathArguments);
muxerArgs.Add(commandPath);
muxerArgs.AddRange(commandArgs);
var escapedArgString = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(muxerArgs);
return new CommandSpec(_muxer.MuxerPath, escapedArgString);
}
private static bool IsPortableApp(string commandPath)
{
var commandDir = Path.GetDirectoryName(commandPath);
var runtimeConfigPath = Directory.EnumerateFiles(commandDir)
.FirstOrDefault(x => x.EndsWith("runtimeconfig.json"));
if (runtimeConfigPath == null)
{
return false;
}
var runtimeConfig = new RuntimeConfig(runtimeConfigPath);
return runtimeConfig.IsPortable;
}
private static IEnumerable<string> GetDepsFileArguments(string depsJsonFile)
{
return ["--depsfile", depsJsonFile];
}
private IEnumerable<string> GetAdditionalProbingPathArguments()
{
return ["--additionalProbingPath", _nugetPackageRoot];
}
private class CommandCandidate
{
public string PackageName { get; set; }
public string PackageVersion { get; set; }
public string RelativeCommandPath { get; set; }
public string GetAbsoluteCommandPath(string nugetPackageRoot)
{
return Path.Combine(
nugetPackageRoot.Replace('/', Path.DirectorySeparatorChar),
PackageName.Replace('/', Path.DirectorySeparatorChar),
PackageVersion.Replace('/', Path.DirectorySeparatorChar),
RelativeCommandPath.Replace('/', Path.DirectorySeparatorChar));
}
}
private enum CommandCandidateType
{
NativeCommandCandidate,
RuntimeCommandCandidate
}
}
|