dotnet-installer/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs

583 lines
22 KiB
C#
Raw Normal View History

// 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.Text;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Resolution;
using Microsoft.Extensions.Internal;
using Microsoft.Extensions.PlatformAbstractions;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ProjectModel
{
public class ProjectContextBuilder
{
private Project Project { get; set; }
private LockFile LockFile { get; set; }
private GlobalSettings GlobalSettings { get; set; }
private NuGetFramework TargetFramework { get; set; }
private IEnumerable<string> RuntimeIdentifiers { get; set; } = Enumerable.Empty<string>();
private string RootDirectory { get; set; }
private string ProjectDirectory { get; set; }
private string PackagesDirectory { get; set; }
private string ReferenceAssembliesPath { get; set; }
private bool IsDesignTime { get; set; }
private Func<string, Project> ProjectResolver { get; set; }
private Func<string, LockFile> LockFileResolver { get; set; }
private ProjectReaderSettings Settings { get; set; } = ProjectReaderSettings.ReadFromEnvironment();
2015-12-08 23:22:08 -08:00
public ProjectContextBuilder()
{
ProjectResolver = ResolveProject;
LockFileResolver = ResolveLockFile;
}
public ProjectContextBuilder WithLockFile(LockFile lockFile)
{
LockFile = lockFile;
return this;
}
public ProjectContextBuilder WithProject(Project project)
{
Project = project;
return this;
}
public ProjectContextBuilder WithProjectDirectory(string projectDirectory)
{
ProjectDirectory = projectDirectory;
return this;
}
public ProjectContextBuilder WithTargetFramework(NuGetFramework targetFramework)
{
TargetFramework = targetFramework;
return this;
}
public ProjectContextBuilder WithTargetFramework(string targetFramework)
{
TargetFramework = NuGetFramework.Parse(targetFramework);
return this;
}
public ProjectContextBuilder WithRuntimeIdentifiers(IEnumerable<string> runtimeIdentifiers)
{
RuntimeIdentifiers = runtimeIdentifiers;
return this;
}
public ProjectContextBuilder WithReferenceAssembliesPath(string referenceAssembliesPath)
{
ReferenceAssembliesPath = referenceAssembliesPath;
return this;
}
public ProjectContextBuilder WithPackagesDirectory(string packagesDirectory)
{
PackagesDirectory = packagesDirectory;
return this;
}
public ProjectContextBuilder WithRootDirectory(string rootDirectory)
{
RootDirectory = rootDirectory;
return this;
}
public ProjectContextBuilder WithProjectResolver(Func<string, Project> projectResolver)
{
ProjectResolver = projectResolver;
return this;
}
public ProjectContextBuilder WithLockFileResolver(Func<string, LockFile> lockFileResolver)
{
LockFileResolver = lockFileResolver;
return this;
}
2015-12-09 00:47:57 -08:00
public ProjectContextBuilder WithReaderSettings(ProjectReaderSettings settings)
2015-12-08 23:22:08 -08:00
{
Settings = settings;
return this;
}
public ProjectContextBuilder AsDesignTime()
{
IsDesignTime = true;
return this;
}
2015-12-08 23:22:08 -08:00
public IEnumerable<ProjectContext> BuildAllTargets()
{
ProjectDirectory = Project?.ProjectDirectory ?? ProjectDirectory;
EnsureProjectLoaded();
LockFile = LockFile ?? LockFileResolver(ProjectDirectory);
if (LockFile != null)
{
foreach (var target in LockFile.Targets)
{
yield return new ProjectContextBuilder()
.WithProject(Project)
.WithLockFile(LockFile)
.WithTargetFramework(target.TargetFramework)
.WithRuntimeIdentifiers(new[] { target.RuntimeIdentifier })
.Build();
}
}
}
public ProjectContext Build()
{
var diagnostics = new List<DiagnosticMessage>();
ProjectDirectory = Project?.ProjectDirectory ?? ProjectDirectory;
if (GlobalSettings == null && ProjectDirectory != null)
{
RootDirectory = ProjectRootResolver.ResolveRootDirectory(ProjectDirectory);
GlobalSettings globalSettings;
if (GlobalSettings.TryGetGlobalSettings(RootDirectory, out globalSettings))
{
GlobalSettings = globalSettings;
}
}
RootDirectory = GlobalSettings?.DirectoryPath ?? RootDirectory;
PackagesDirectory = PackagesDirectory ?? PackageDependencyProvider.ResolvePackagesPath(RootDirectory, GlobalSettings);
ReferenceAssembliesPath = ReferenceAssembliesPath ?? FrameworkReferenceResolver.GetDefaultReferenceAssembliesPath();
var frameworkReferenceResolver = new FrameworkReferenceResolver(ReferenceAssembliesPath);
LockFileLookup lockFileLookup = null;
EnsureProjectLoaded();
ReadLockFile(diagnostics);
var validLockFile = true;
string lockFileValidationMessage = null;
if (LockFile != null)
{
if (Project != null)
{
validLockFile = LockFile.IsValidForProject(Project, out lockFileValidationMessage);
}
lockFileLookup = new LockFileLookup(LockFile);
}
var libraries = new Dictionary<LibraryKey, LibraryDescription>();
var projectResolver = new ProjectDependencyProvider(ProjectResolver);
ProjectDescription mainProject = null;
if (Project != null)
{
mainProject = projectResolver.GetDescription(TargetFramework, Project, targetLibrary: null);
// Add the main project
libraries.Add(new LibraryKey(mainProject.Identity.Name), mainProject);
}
LibraryRange? platformDependency = null;
if (mainProject != null)
{
platformDependency = mainProject.Dependencies
.Where(d => d.Type.Equals(LibraryDependencyType.Platform))
.Cast<LibraryRange?>()
.FirstOrDefault();
}
bool isPortable = platformDependency != null || TargetFramework.IsDesktop();
LockFileTarget target = null;
2016-04-08 16:27:09 -07:00
LibraryDescription platformLibrary = null;
if (lockFileLookup != null)
{
target = SelectTarget(LockFile, isPortable);
if (target != null)
{
2016-03-23 15:26:36 -07:00
var nugetPackageResolver = new PackageDependencyProvider(PackagesDirectory, frameworkReferenceResolver);
var msbuildProjectResolver = new MSBuildDependencyProvider(Project, ProjectResolver);
2016-03-23 15:26:36 -07:00
ScanLibraries(target, lockFileLookup, libraries, msbuildProjectResolver, nugetPackageResolver, projectResolver);
2016-04-08 16:27:09 -07:00
if (platformDependency != null)
{
platformLibrary = libraries[new LibraryKey(platformDependency.Value.Name)];
}
}
}
string runtime;
if (TargetFramework.IsDesktop())
{
var legacyRuntime = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier();
if (RuntimeIdentifiers.Contains(legacyRuntime))
{
runtime = legacyRuntime;
}
else
{
runtime = RuntimeIdentifiers.FirstOrDefault();
}
}
else
{
runtime = target?.RuntimeIdentifier;
}
var referenceAssemblyDependencyResolver = new ReferenceAssemblyDependencyResolver(frameworkReferenceResolver);
bool requiresFrameworkAssemblies;
// Resolve the dependencies
ResolveDependencies(libraries, referenceAssemblyDependencyResolver, out requiresFrameworkAssemblies);
// REVIEW: Should this be in NuGet (possibly stored in the lock file?)
if (LockFile == null)
{
diagnostics.Add(new DiagnosticMessage(
ErrorCodes.NU1009,
$"The expected lock file doesn't exist. Please run \"dotnet restore\" to generate a new lock file.",
Path.Combine(Project.ProjectDirectory, LockFile.FileName),
DiagnosticMessageSeverity.Error));
}
if (!validLockFile)
{
diagnostics.Add(new DiagnosticMessage(
ErrorCodes.NU1006,
$"{lockFileValidationMessage}. Please run \"dotnet restore\" to generate a new lock file.",
Path.Combine(Project.ProjectDirectory, LockFile.FileName),
DiagnosticMessageSeverity.Warning));
}
if (requiresFrameworkAssemblies)
{
var frameworkInfo = Project.GetTargetFramework(TargetFramework);
if (string.IsNullOrEmpty(ReferenceAssembliesPath))
{
// If there was an attempt to use reference assemblies but they were not installed
// report an error
diagnostics.Add(new DiagnosticMessage(
ErrorCodes.DOTNET1012,
$"The reference assemblies directory was not specified. You can set the location using the DOTNET_REFERENCE_ASSEMBLIES_PATH environment variable.",
filePath: Project.ProjectFilePath,
severity: DiagnosticMessageSeverity.Error,
startLine: frameworkInfo.Line,
startColumn: frameworkInfo.Column
));
}
else if (!frameworkReferenceResolver.IsInstalled(TargetFramework))
{
// If there was an attempt to use reference assemblies but they were not installed
// report an error
diagnostics.Add(new DiagnosticMessage(
ErrorCodes.DOTNET1011,
$"Framework not installed: {TargetFramework.DotNetFrameworkName} in {ReferenceAssembliesPath}",
filePath: Project.ProjectFilePath,
severity: DiagnosticMessageSeverity.Error,
startLine: frameworkInfo.Line,
startColumn: frameworkInfo.Column
));
}
}
// Create a library manager
var libraryManager = new LibraryManager(libraries.Values.ToList(), diagnostics, Project?.ProjectFilePath);
return new ProjectContext(
GlobalSettings,
mainProject,
platformLibrary,
TargetFramework,
isPortable,
runtime,
PackagesDirectory,
libraryManager,
LockFile);
}
private void ReadLockFile(ICollection<DiagnosticMessage> diagnostics)
{
try
{
LockFile = LockFile ?? LockFileResolver(ProjectDirectory);
}
catch (FileFormatException e)
{
var lockFilePath = "";
if (LockFile != null)
{
lockFilePath = LockFile.LockFilePath;
}
else if (Project != null)
{
lockFilePath = Path.Combine(Project.ProjectDirectory, LockFile.FileName);
}
diagnostics.Add(new DiagnosticMessage(
ErrorCodes.DOTNET1014,
ComposeMessageFromInnerExceptions(e),
lockFilePath,
DiagnosticMessageSeverity.Error));
}
}
private static string ComposeMessageFromInnerExceptions(Exception exception)
{
var sb = new StringBuilder();
2016-03-30 18:22:35 -07:00
var messages = new HashSet<string>();
while (exception != null)
{
2016-03-30 18:22:35 -07:00
messages.Add(exception.Message);
exception = exception.InnerException;
}
2016-03-30 18:22:35 -07:00
foreach (var message in messages)
{
sb.AppendLine(message);
}
return sb.ToString();
}
private void ResolveDependencies(Dictionary<LibraryKey, LibraryDescription> libraries,
ReferenceAssemblyDependencyResolver referenceAssemblyDependencyResolver,
out bool requiresFrameworkAssemblies)
{
// Remark: the LibraryType in the key of the given dictionary are all "Unspecified" at the beginning.
requiresFrameworkAssemblies = false;
foreach (var pair in libraries.ToList())
{
var library = pair.Value;
// The System.* packages provide placeholders on any non netstandard platform
// To make them work seamlessly on those platforms, we fill the gap with a reference
// assembly (if available)
var package = library as PackageDescription;
if (package != null && package.Resolved && !package.CompileTimeAssemblies.Any())
{
var replacement = referenceAssemblyDependencyResolver.GetDescription(new LibraryRange(library.Identity.Name, LibraryType.ReferenceAssembly), TargetFramework);
if (replacement?.Resolved == true)
{
requiresFrameworkAssemblies = true;
// Remove the original package reference
libraries.Remove(pair.Key);
// Insert a reference assembly key if there isn't one
var key = new LibraryKey(replacement.Identity.Name, LibraryType.ReferenceAssembly);
if (!libraries.ContainsKey(key))
{
libraries[key] = replacement;
}
}
}
}
foreach (var pair in libraries.ToList())
{
var library = pair.Value;
library.Framework = library.Framework ?? TargetFramework;
foreach (var dependency in library.Dependencies)
{
var keyType = dependency.Target == LibraryType.ReferenceAssembly ?
LibraryType.ReferenceAssembly :
LibraryType.Unspecified;
var key = new LibraryKey(dependency.Name, keyType);
LibraryDescription dependencyDescription;
if (!libraries.TryGetValue(key, out dependencyDescription))
{
if (keyType == LibraryType.ReferenceAssembly)
{
// a dependency is specified to be reference assembly but fail to match
// then add a unresolved dependency
dependencyDescription = referenceAssemblyDependencyResolver.GetDescription(dependency, TargetFramework) ??
UnresolvedDependencyProvider.GetDescription(dependency, TargetFramework);
libraries[key] = dependencyDescription;
}
else if (!libraries.TryGetValue(new LibraryKey(dependency.Name, LibraryType.ReferenceAssembly), out dependencyDescription))
{
// a dependency which type is unspecified fails to match, then try to find a
// reference assembly type dependency
dependencyDescription = UnresolvedDependencyProvider.GetDescription(dependency, TargetFramework);
libraries[key] = dependencyDescription;
}
}
dependencyDescription.RequestedRanges.Add(dependency);
dependencyDescription.Parents.Add(library);
}
}
}
private void ScanLibraries(LockFileTarget target,
LockFileLookup lockFileLookup,
Dictionary<LibraryKey, LibraryDescription> libraries,
MSBuildDependencyProvider msbuildResolver,
PackageDependencyProvider packageResolver,
ProjectDependencyProvider projectResolver)
{
foreach (var library in target.Libraries)
{
LibraryDescription description = null;
var type = LibraryType.Unspecified;
if (string.Equals(library.Type, "project"))
{
var projectLibrary = lockFileLookup.GetProject(library.Name);
if (projectLibrary != null)
{
2016-03-23 15:26:36 -07:00
if (MSBuildDependencyProvider.IsMSBuildProjectLibrary(projectLibrary))
{
description = msbuildResolver.GetDescription(TargetFramework, projectLibrary, library, IsDesignTime);
2016-03-23 15:26:36 -07:00
type = LibraryType.MSBuildProject;
}
else
{
var path = Path.GetFullPath(Path.Combine(ProjectDirectory, projectLibrary.Path));
description = projectResolver.GetDescription(library.Name, path, library, ProjectResolver);
type = LibraryType.Project;
}
}
}
else
{
var packageEntry = lockFileLookup.GetPackage(library.Name, library.Version);
if (packageEntry != null)
{
description = packageResolver.GetDescription(TargetFramework, packageEntry, library);
}
type = LibraryType.Package;
}
description = description ?? UnresolvedDependencyProvider.GetDescription(new LibraryRange(library.Name, type), target.TargetFramework);
libraries.Add(new LibraryKey(library.Name), description);
}
}
private void EnsureProjectLoaded()
{
if (Project == null && ProjectDirectory != null)
{
Project = ProjectResolver(ProjectDirectory);
}
}
private LockFileTarget SelectTarget(LockFile lockFile, bool isPortable)
{
if (!isPortable)
{
foreach (var runtimeIdentifier in RuntimeIdentifiers)
{
foreach (var scanTarget in lockFile.Targets)
{
if (Equals(scanTarget.TargetFramework, TargetFramework) && string.Equals(scanTarget.RuntimeIdentifier, runtimeIdentifier, StringComparison.Ordinal))
{
return scanTarget;
}
}
}
}
foreach (var scanTarget in lockFile.Targets)
{
if (Equals(scanTarget.TargetFramework, TargetFramework) && string.IsNullOrEmpty(scanTarget.RuntimeIdentifier))
{
return scanTarget;
}
}
return null;
}
private Project ResolveProject(string projectDirectory)
{
// TODO: Handle diagnostics
Project project;
if (ProjectReader.TryGetProject(projectDirectory, out project, diagnostics: null, settings: Settings))
{
return project;
}
else
{
return null;
}
}
private static LockFile ResolveLockFile(string projectDir)
{
var projectLockJsonPath = Path.Combine(projectDir, LockFile.FileName);
return File.Exists(projectLockJsonPath) ?
LockFileReader.Read(Path.Combine(projectDir, LockFile.FileName), designTime: false) :
null;
}
private struct LibraryKey
{
public LibraryKey(string name) : this(name, LibraryType.Unspecified)
{
}
public LibraryKey(string name, LibraryType libraryType)
{
Name = name;
LibraryType = libraryType;
}
public string Name { get; }
public LibraryType LibraryType { get; }
public override bool Equals(object obj)
{
var otherKey = (LibraryKey)obj;
return string.Equals(otherKey.Name, Name, StringComparison.Ordinal) &&
otherKey.LibraryType.Equals(LibraryType);
}
public override int GetHashCode()
{
var combiner = new HashCodeCombiner();
combiner.Add(Name);
combiner.Add(LibraryType);
return combiner.CombinedHash;
}
public override string ToString()
{
return Name + " " + LibraryType;
}
}
}
}