// 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; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using Microsoft.DotNet.ProjectModel.Graph; using NuGet.Frameworks; using NuGet.Packaging; namespace Microsoft.DotNet.ProjectModel.Resolution { public class PackageDependencyProvider { private readonly VersionFolderPathResolver _packagePathResolver; private readonly FrameworkReferenceResolver _frameworkReferenceResolver; public PackageDependencyProvider(string packagesPath, FrameworkReferenceResolver frameworkReferenceResolver) { _packagePathResolver = new VersionFolderPathResolver(packagesPath); _frameworkReferenceResolver = frameworkReferenceResolver; } public PackageDescription GetDescription(NuGetFramework targetFramework, LockFilePackageLibrary package, LockFileTargetLibrary targetLibrary) { // If a NuGet dependency is supposed to provide assemblies but there is no assembly compatible with // current target framework, we should mark this dependency as unresolved var containsAssembly = package.Files .Any(x => x.StartsWith($"ref{Path.DirectorySeparatorChar}") || x.StartsWith($"lib{Path.DirectorySeparatorChar}")); var compatible = targetLibrary.FrameworkAssemblies.Any() || targetLibrary.CompileTimeAssemblies.Any() || targetLibrary.RuntimeAssemblies.Any() || !containsAssembly; var dependencies = new List(targetLibrary.Dependencies.Count + targetLibrary.FrameworkAssemblies.Count); PopulateDependencies(dependencies, targetLibrary, targetFramework); var path = _packagePathResolver.GetInstallPath(package.Name, package.Version); // Remove place holders targetLibrary.CompileTimeAssemblies = targetLibrary.CompileTimeAssemblies.Where(item => !IsPlaceholderFile(item.Path)).ToList(); targetLibrary.RuntimeAssemblies = targetLibrary.RuntimeAssemblies.Where(item => !IsPlaceholderFile(item.Path)).ToList(); // If the package's compile time assemblies is for a portable profile then, read the assembly metadata // and turn System.* references into reference assembly dependencies PopulateLegacyPortableDependencies(targetFramework, dependencies, path, targetLibrary); var packageDescription = new PackageDescription( path, package, targetLibrary, dependencies, compatible); return packageDescription; } private void PopulateLegacyPortableDependencies(NuGetFramework targetFramework, List dependencies, string packagePath, LockFileTargetLibrary targetLibrary) { var seen = new HashSet(); foreach (var assembly in targetLibrary.CompileTimeAssemblies) { // (ref/lib)/{tfm}/{assembly} var pathParts = assembly.Path.Split(Path.DirectorySeparatorChar); if (pathParts.Length != 3) { continue; } var assemblyTargetFramework = NuGetFramework.Parse(pathParts[1]); if (!assemblyTargetFramework.IsPCL) { continue; } var assemblyPath = Path.Combine(packagePath, assembly.Path); foreach (var dependency in GetDependencies(assemblyPath)) { if (seen.Add(dependency)) { string path; Version version; // If there exists a reference assembly on the requested framework with the same name then turn this into a // framework assembly dependency if (_frameworkReferenceResolver.TryGetAssembly(dependency, targetFramework, out path, out version)) { dependencies.Add(new LibraryRange(dependency, LibraryType.ReferenceAssembly, LibraryDependencyType.Build)); } } } } } private static IEnumerable GetDependencies(string path) { using (var peReader = new PEReader(File.OpenRead(path))) { var metadataReader = peReader.GetMetadataReader(); foreach (var assemblyReferenceHandle in metadataReader.AssemblyReferences) { var assemblyReference = metadataReader.GetAssemblyReference(assemblyReferenceHandle); yield return metadataReader.GetString(assemblyReference.Name); } } } private void PopulateDependencies( List dependencies, LockFileTargetLibrary targetLibrary, NuGetFramework targetFramework) { foreach (var dependency in targetLibrary.Dependencies) { dependencies.Add(new LibraryRange( dependency.Id, dependency.VersionRange, LibraryType.Unspecified, LibraryDependencyType.Default)); } if (!targetFramework.IsPackageBased) { // Only add framework assemblies for non-package based frameworks. foreach (var frameworkAssembly in targetLibrary.FrameworkAssemblies) { dependencies.Add(new LibraryRange( frameworkAssembly, LibraryType.ReferenceAssembly, LibraryDependencyType.Default)); } } } public static bool IsPlaceholderFile(string path) { return string.Equals(Path.GetFileName(path), "_._", StringComparison.Ordinal); } public static string ResolvePackagesPath(string rootDirectory, GlobalSettings settings) { // Order // 1. global.json { "packages": "..." } // 2. EnvironmentNames.PackagesStore environment variable // 3. NuGet.config repositoryPath (maybe)? // 4. {DefaultLocalRuntimeHomeDir}\packages if (!string.IsNullOrEmpty(settings?.PackagesPath)) { return Path.Combine(rootDirectory, settings.PackagesPath); } var runtimePackages = Environment.GetEnvironmentVariable(EnvironmentNames.PackagesStore); if (!string.IsNullOrEmpty(runtimePackages)) { return runtimePackages; } var profileDirectory = Environment.GetEnvironmentVariable("USERPROFILE"); if (string.IsNullOrEmpty(profileDirectory)) { profileDirectory = Environment.GetEnvironmentVariable("HOME"); } return Path.Combine(profileDirectory, ".nuget", "packages"); } } }