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.
195 lines
7.1 KiB
195 lines
7.1 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 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]; |
|
this.Version = parts[1]; |
|
this.Type = type; |
|
this.Path = path; |
|
this.RuntimeComponents = runtimeComponents ?? new string[0]; |
|
} |
|
} |
|
|
|
static readonly string[] LookupPaths = new string[] { |
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages") |
|
}; |
|
|
|
readonly Dictionary<string, DotNetCorePackageInfo> packages; |
|
ISet<string> packageBasePaths = new HashSet<string>(StringComparer.Ordinal); |
|
readonly string assemblyName; |
|
readonly string basePath; |
|
readonly string targetFrameworkId; |
|
readonly string version; |
|
readonly string dotnetBasePath = FindDotNetExeDirectory(); |
|
|
|
public DotNetCorePathFinder(string parentAssemblyFileName, string targetFrameworkId, string version, ReferenceLoadInfo loadInfo = null) |
|
{ |
|
this.assemblyName = Path.GetFileNameWithoutExtension(parentAssemblyFileName); |
|
this.basePath = Path.GetDirectoryName(parentAssemblyFileName); |
|
this.targetFrameworkId = targetFrameworkId; |
|
this.version = version; |
|
|
|
var depsJsonFileName = Path.Combine(basePath, $"{assemblyName}.deps.json"); |
|
if (!File.Exists(depsJsonFileName)) { |
|
loadInfo?.AddMessage(assemblyName, MessageKind.Warning, $"{assemblyName}.deps.json could not be found!"); |
|
return; |
|
} |
|
|
|
packages = LoadPackageInfos(depsJsonFileName, targetFrameworkId).ToDictionary(i => i.Name); |
|
|
|
foreach (var path in LookupPaths) { |
|
foreach (var pk in packages) { |
|
foreach (var item in pk.Value.RuntimeComponents) { |
|
var itemPath = Path.GetDirectoryName(item); |
|
var fullPath = Path.Combine(path, pk.Value.Name, pk.Value.Version, itemPath).ToLowerInvariant(); |
|
if (Directory.Exists(fullPath)) |
|
packageBasePaths.Add(fullPath); |
|
} |
|
} |
|
} |
|
} |
|
|
|
public string TryResolveDotNetCore(IAssemblyReference name) |
|
{ |
|
foreach (var basePath in 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 FallbackToDotNetSharedDirectory(name, new Version(version)); |
|
} |
|
|
|
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); |
|
} |
|
} |
|
|
|
string FallbackToDotNetSharedDirectory(IAssemblyReference name, Version version) |
|
{ |
|
if (dotnetBasePath == null) return null; |
|
var basePath = Path.Combine(dotnetBasePath, "shared", "Microsoft.NETCore.App"); |
|
var closestVersion = GetClosestVersionFolder(basePath, version); |
|
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"); |
|
} |
|
return null; |
|
} |
|
|
|
static string GetClosestVersionFolder(string basePath, Version version) |
|
{ |
|
string result = null; |
|
foreach (var folder in new DirectoryInfo(basePath).GetDirectories().Select(d => ConvertToVersion(d.Name)).Where(v => v.Item1 != null).OrderByDescending(v => v.Item1)) { |
|
if (folder.Item1 >= version) |
|
result = folder.Item2; |
|
} |
|
return result ?? version.ToString(); |
|
} |
|
|
|
internal static (Version, string) 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); |
|
} |
|
} |
|
|
|
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) { |
|
var sb = new StringBuilder(); |
|
realpath(fileName, sb); |
|
fileName = sb.ToString(); |
|
if (!File.Exists(fileName)) |
|
continue; |
|
} |
|
} |
|
return Path.GetDirectoryName(fileName); |
|
} catch (ArgumentException) { } |
|
} |
|
return null; |
|
} |
|
|
|
[DllImport("libc")] |
|
static extern void realpath(string path, StringBuilder resolvedPath); |
|
} |
|
}
|
|
|