You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
336 lines
10 KiB
336 lines
10 KiB
// Copyright (c) 2018 Siegfried Pammer
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
using LightJson.Serialization;
|
|
|
|
namespace ICSharpCode.Decompiler.Metadata
|
|
{
|
|
public class DotNetCorePathFinder
|
|
{
|
|
class DotNetCorePackageInfo
|
|
{
|
|
public readonly string Name;
|
|
public readonly string Version;
|
|
public readonly string Type;
|
|
public readonly string Path;
|
|
public readonly string[] RuntimeComponents;
|
|
|
|
public DotNetCorePackageInfo(string fullName, string type, string path, string[] runtimeComponents)
|
|
{
|
|
var parts = fullName.Split('/');
|
|
this.Name = parts[0];
|
|
if (parts.Length > 1)
|
|
{
|
|
this.Version = parts[1];
|
|
}
|
|
else
|
|
{
|
|
this.Version = "<UNKNOWN>";
|
|
}
|
|
|
|
this.Type = type;
|
|
this.Path = path;
|
|
this.RuntimeComponents = runtimeComponents ?? Empty<string>.Array;
|
|
}
|
|
}
|
|
|
|
static readonly string[] LookupPaths = new string[] {
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages")
|
|
};
|
|
|
|
static readonly string[] RuntimePacks = new[] {
|
|
"Microsoft.NETCore.App",
|
|
"Microsoft.WindowsDesktop.App",
|
|
"Microsoft.AspNetCore.App",
|
|
"Microsoft.AspNetCore.All"
|
|
};
|
|
|
|
readonly DotNetCorePackageInfo[] packages;
|
|
readonly List<string> searchPaths = new List<string>();
|
|
readonly List<string> packageBasePaths = new List<string>();
|
|
readonly Version targetFrameworkVersion;
|
|
readonly string dotnetBasePath = FindDotNetExeDirectory();
|
|
readonly string preferredRuntimePack;
|
|
|
|
public DotNetCorePathFinder(TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion,
|
|
string preferredRuntimePack)
|
|
{
|
|
this.targetFrameworkVersion = targetFrameworkVersion;
|
|
this.preferredRuntimePack = preferredRuntimePack;
|
|
|
|
if (targetFramework == TargetFrameworkIdentifier.NETStandard)
|
|
{
|
|
// .NET Standard 2.1 is implemented by .NET Core 3.0 or higher
|
|
if (targetFrameworkVersion.Major == 2 && targetFrameworkVersion.Minor == 1)
|
|
{
|
|
this.targetFrameworkVersion = new Version(3, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkIdString, string preferredRuntimePack,
|
|
TargetFrameworkIdentifier targetFramework, Version targetFrameworkVersion, ReferenceLoadInfo loadInfo = null)
|
|
: this(targetFramework, targetFrameworkVersion, preferredRuntimePack)
|
|
{
|
|
string assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName);
|
|
string basePath = Path.GetDirectoryName(parentAssemblyFileName);
|
|
|
|
searchPaths.Add(basePath);
|
|
|
|
var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json");
|
|
if (File.Exists(depsJsonFileName))
|
|
{
|
|
packages = LoadPackageInfos(depsJsonFileName, targetFrameworkIdString).ToArray();
|
|
|
|
foreach (var path in LookupPaths)
|
|
{
|
|
foreach (var p in packages)
|
|
{
|
|
foreach (var item in p.RuntimeComponents)
|
|
{
|
|
var itemPath = Path.GetDirectoryName(item);
|
|
var fullPath = Path.Combine(path, p.Name, p.Version, itemPath).ToLowerInvariant();
|
|
if (Directory.Exists(fullPath))
|
|
packageBasePaths.Add(fullPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
loadInfo?.AddMessage(assemblyName, MessageKind.Warning, $"{assemblyName}.deps.json could not be found!");
|
|
}
|
|
}
|
|
|
|
public void AddSearchDirectory(string path)
|
|
{
|
|
this.searchPaths.Add(path);
|
|
}
|
|
|
|
public void RemoveSearchDirectory(string path)
|
|
{
|
|
this.searchPaths.Remove(path);
|
|
}
|
|
|
|
public string TryResolveDotNetCore(IAssemblyReference name)
|
|
{
|
|
foreach (var basePath in searchPaths.Concat(packageBasePaths))
|
|
{
|
|
if (File.Exists(Path.Combine(basePath, name.Name + ".dll")))
|
|
{
|
|
return Path.Combine(basePath, name.Name + ".dll");
|
|
}
|
|
else if (File.Exists(Path.Combine(basePath, name.Name + ".exe")))
|
|
{
|
|
return Path.Combine(basePath, name.Name + ".exe");
|
|
}
|
|
}
|
|
|
|
return TryResolveDotNetCoreShared(name, out _);
|
|
}
|
|
|
|
internal string GetReferenceAssemblyPath(string targetFramework)
|
|
{
|
|
var (tfi, version) = UniversalAssemblyResolver.ParseTargetFramework(targetFramework);
|
|
string identifier, identifierExt;
|
|
switch (tfi)
|
|
{
|
|
case TargetFrameworkIdentifier.NETCoreApp:
|
|
identifier = "Microsoft.NETCore.App";
|
|
identifierExt = "netcoreapp" + version.Major + "." + version.Minor;
|
|
break;
|
|
case TargetFrameworkIdentifier.NETStandard:
|
|
identifier = "NETStandard.Library";
|
|
identifierExt = "netstandard" + version.Major + "." + version.Minor;
|
|
break;
|
|
case TargetFrameworkIdentifier.NET:
|
|
identifier = "Microsoft.NETCore.App";
|
|
identifierExt = "net" + version.Major + "." + version.Minor;
|
|
break;
|
|
default:
|
|
throw new NotSupportedException();
|
|
}
|
|
string basePath = Path.Combine(dotnetBasePath, "packs", identifier + ".Ref");
|
|
string versionFolder = GetClosestVersionFolder(basePath, version);
|
|
return Path.Combine(basePath, versionFolder, "ref", identifierExt);
|
|
}
|
|
|
|
static IEnumerable<DotNetCorePackageInfo> LoadPackageInfos(string depsJsonFileName, string targetFramework)
|
|
{
|
|
var dependencies = JsonReader.Parse(File.ReadAllText(depsJsonFileName));
|
|
var runtimeInfos = dependencies["targets"][targetFramework].AsJsonObject;
|
|
var libraries = dependencies["libraries"].AsJsonObject;
|
|
if (runtimeInfos == null || libraries == null)
|
|
yield break;
|
|
foreach (var library in libraries)
|
|
{
|
|
var type = library.Value["type"].AsString;
|
|
var path = library.Value["path"].AsString;
|
|
var runtimeInfo = runtimeInfos[library.Key].AsJsonObject?["runtime"].AsJsonObject;
|
|
string[] components = new string[runtimeInfo?.Count ?? 0];
|
|
if (runtimeInfo != null)
|
|
{
|
|
int i = 0;
|
|
foreach (var component in runtimeInfo)
|
|
{
|
|
components[i] = component.Key;
|
|
i++;
|
|
}
|
|
}
|
|
yield return new DotNetCorePackageInfo(library.Key, type, path, components);
|
|
}
|
|
}
|
|
|
|
public string TryResolveDotNetCoreShared(IAssemblyReference name, out string runtimePack)
|
|
{
|
|
if (dotnetBasePath == null)
|
|
{
|
|
runtimePack = null;
|
|
return null;
|
|
}
|
|
|
|
IEnumerable<string> runtimePacks = RuntimePacks;
|
|
|
|
if (preferredRuntimePack != null)
|
|
{
|
|
runtimePacks = new[] { preferredRuntimePack }.Concat(runtimePacks);
|
|
}
|
|
|
|
foreach (string pack in runtimePacks)
|
|
{
|
|
runtimePack = pack;
|
|
string basePath = Path.Combine(dotnetBasePath, "shared", pack);
|
|
if (!Directory.Exists(basePath))
|
|
continue;
|
|
var closestVersion = GetClosestVersionFolder(basePath, targetFrameworkVersion);
|
|
if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".dll")))
|
|
{
|
|
return Path.Combine(basePath, closestVersion, name.Name + ".dll");
|
|
}
|
|
else if (File.Exists(Path.Combine(basePath, closestVersion, name.Name + ".exe")))
|
|
{
|
|
return Path.Combine(basePath, closestVersion, name.Name + ".exe");
|
|
}
|
|
}
|
|
runtimePack = null;
|
|
return null;
|
|
}
|
|
|
|
static string GetClosestVersionFolder(string basePath, Version version)
|
|
{
|
|
var foundVersions = new DirectoryInfo(basePath).GetDirectories()
|
|
.Select(d => ConvertToVersion(d.Name))
|
|
.Where(v => v.version != null);
|
|
foreach (var folder in foundVersions.OrderBy(v => v.version))
|
|
{
|
|
if (folder.version >= version)
|
|
return folder.directoryName;
|
|
}
|
|
return version.ToString();
|
|
}
|
|
|
|
internal static (Version version, string directoryName) ConvertToVersion(string name)
|
|
{
|
|
string RemoveTrailingVersionInfo()
|
|
{
|
|
string shortName = name;
|
|
int dashIndex = shortName.IndexOf('-');
|
|
if (dashIndex > 0)
|
|
{
|
|
shortName = shortName.Remove(dashIndex);
|
|
}
|
|
return shortName;
|
|
}
|
|
|
|
try
|
|
{
|
|
return (new Version(RemoveTrailingVersionInfo()), name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Trace.TraceWarning(ex.ToString());
|
|
return (null, null);
|
|
}
|
|
}
|
|
|
|
public static string FindDotNetExeDirectory()
|
|
{
|
|
string dotnetExeName = (Environment.OSVersion.Platform == PlatformID.Unix) ? "dotnet" : "dotnet.exe";
|
|
foreach (var item in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
|
|
{
|
|
try
|
|
{
|
|
string fileName = Path.Combine(item, dotnetExeName);
|
|
if (!File.Exists(fileName))
|
|
continue;
|
|
if (Environment.OSVersion.Platform == PlatformID.Unix)
|
|
{
|
|
if ((new FileInfo(fileName).Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
|
|
{
|
|
fileName = GetRealPath(fileName, Encoding.Default);
|
|
if (!File.Exists(fileName))
|
|
continue;
|
|
}
|
|
}
|
|
return Path.GetDirectoryName(fileName);
|
|
}
|
|
catch (ArgumentException) { }
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static unsafe string GetRealPath(string path, Encoding encoding)
|
|
{
|
|
var bytes = encoding.GetBytes(path);
|
|
fixed (byte* input = bytes)
|
|
{
|
|
|
|
byte* output = GetRealPath(input, null);
|
|
if (output == null)
|
|
{
|
|
return null;
|
|
}
|
|
int len = 0;
|
|
for (byte* c = output; *c != 0; c++)
|
|
{
|
|
len++;
|
|
}
|
|
byte[] result = new byte[len];
|
|
Marshal.Copy((IntPtr)output, result, 0, result.Length);
|
|
Free(output);
|
|
return encoding.GetString(result);
|
|
}
|
|
}
|
|
|
|
[DllImport("libc", EntryPoint = "realpath")]
|
|
static extern unsafe byte* GetRealPath(byte* path, byte* resolvedPath);
|
|
|
|
[DllImport("libc", EntryPoint = "free")]
|
|
static extern unsafe void Free(void* ptr);
|
|
}
|
|
}
|