mirror of https://github.com/icsharpcode/ILSpy.git
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); |
|
} |
|
}
|
|
|