/// <summary> /// Restore a build integrated project and update the lock file /// </summary> public static async Task <RestoreResult> RestoreAsync( BuildIntegratedNuGetProject project, ExternalProjectReferenceContext context, IEnumerable <SourceRepository> sources, string effectiveGlobalPackagesFolder, Action <SourceCacheContext> cacheContextModifier, CancellationToken token) { using (var cacheContext = new SourceCacheContext()) { cacheContextModifier(cacheContext); var providers = RestoreCommandProviders.Create(effectiveGlobalPackagesFolder, sources, cacheContext, context.Logger); // Restore var result = await RestoreAsync( project, project.PackageSpec, context, providers, token); // Throw before writing if this has been canceled token.ThrowIfCancellationRequested(); // Write out the lock file and msbuild files await result.CommitAsync(context.Logger, token); return(result); } }
/// <summary> /// Retrieve the full closure of project to project references. /// Warnings and errors encountered will be logged. /// </summary> public virtual Task <IReadOnlyList <ExternalProjectReference> > GetProjectReferenceClosureAsync( ExternalProjectReferenceContext context) { // This cannot be resolved with DTE currently, it is overridden at a higher level return(Task.FromResult <IReadOnlyList <ExternalProjectReference> >( Enumerable.Empty <ExternalProjectReference>().ToList())); }
/// <summary> /// Find the list of parent projects which directly or indirectly reference the child project. /// </summary> public static async Task <IReadOnlyList <BuildIntegratedNuGetProject> > GetParentProjectsInClosure( ISolutionManager solutionManager, BuildIntegratedNuGetProject target, ExternalProjectReferenceContext referenceContext) { var projects = solutionManager.GetNuGetProjects().OfType <BuildIntegratedNuGetProject>().ToList(); return(await GetParentProjectsInClosure(projects, target, referenceContext)); }
/// <summary> /// Determine if a full restore is required. For scenarios with no floating versions /// where all packages are already on disk and no server requests are needed /// we can skip the restore after some validation checks. /// </summary> private async Task <bool> IsRestoreRequired( List <BuildIntegratedProjectSystem> projects, bool forceRestore, ExternalProjectReferenceContext referenceContext) { try { // Swap caches var oldCache = _buildIntegratedCache; _buildIntegratedCache = await BuildIntegratedRestoreUtility.CreateBuildIntegratedProjectStateCache( projects, referenceContext); if (forceRestore) { // The cache has been updated, now skip the check since we are doing a restore anyways. return(true); } if (BuildIntegratedRestoreUtility.CacheHasChanges(oldCache, _buildIntegratedCache)) { // A new project has been added return(true); } var globalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(Settings); var pathResolver = new VersionFolderPathResolver(globalPackagesFolder); var restoreRequired = await BuildIntegratedRestoreUtility.IsRestoreRequired( projects, pathResolver, referenceContext); if (restoreRequired) { // The project.json file does not match the lock file return(true); } } catch (Exception ex) { Debug.Fail("Unable to validate lock files."); ActivityLog.LogError(LogEntrySource, ex.ToString()); WriteLine(VerbosityLevel.Diagnostic, "{0}", ex.ToString()); // If we are unable to validate, run a full restore // This may occur if the lock files are corrupt return(true); } // Validation passed, no restore is needed return(false); }
CreateBuildIntegratedProjectStateCache( IReadOnlyList <BuildIntegratedNuGetProject> projects, ExternalProjectReferenceContext context) { var cache = new Dictionary <string, BuildIntegratedProjectCacheEntry>(); // Find all project closures foreach (var project in projects) { // Get all project.json file paths in the closure var closure = await project.GetProjectReferenceClosureAsync(context); var files = new HashSet <string>(StringComparer.Ordinal); // Store the last modified date of the project.json file // If there are any changes a restore is needed var lastModified = DateTimeOffset.MinValue; if (File.Exists(project.JsonConfigPath)) { lastModified = File.GetLastWriteTimeUtc(project.JsonConfigPath); } foreach (var reference in closure) { if (!string.IsNullOrEmpty(reference.MSBuildProjectPath)) { files.Add(reference.MSBuildProjectPath); } if (reference.PackageSpecPath != null) { files.Add(reference.PackageSpecPath); } } var projectInfo = new BuildIntegratedProjectCacheEntry( project.JsonConfigPath, files, lastModified); var uniqueName = project.GetMetadata <string>(NuGetProjectMetadataKeys.UniqueName); if (!cache.ContainsKey(uniqueName)) { cache.Add(uniqueName, projectInfo); } else { Debug.Fail("project list contains duplicate projects"); } } return(cache); }
public MonoDevelopBuildIntegratedRestorer ( ISourceRepositoryProvider repositoryProvider, ISettings settings) { sourceRepositories = repositoryProvider.GetRepositories ().ToList (); this.settings = settings; packageManagementEvents = PackageManagementServices.PackageManagementEvents; context = CreateRestoreContext (); }
public MonoDevelopBuildIntegratedRestorer( ISourceRepositoryProvider repositoryProvider, ISettings settings) { sourceRepositories = repositoryProvider.GetRepositories().ToList(); this.settings = settings; packageManagementEvents = PackageManagementServices.PackageManagementEvents; context = CreateRestoreContext(); }
/// <summary> /// Restore without writing the lock file /// </summary> internal static async Task <RestoreResult> RestoreAsync( BuildIntegratedNuGetProject project, PackageSpec packageSpec, ExternalProjectReferenceContext context, RestoreCommandProviders providers, CancellationToken token) { // Restoring packages var logger = context.Logger; logger.LogMinimal(string.Format(CultureInfo.CurrentCulture, Strings.BuildIntegratedPackageRestoreStarted, project.ProjectName)); using (var request = new RestoreRequest(packageSpec, providers, logger, disposeProviders: false)) { request.MaxDegreeOfConcurrency = PackageManagementConstants.DefaultMaxDegreeOfParallelism; request.LockFileVersion = await GetLockFileVersion(project, context); // Add the existing lock file if it exists var lockFilePath = ProjectJsonPathUtilities.GetLockFilePath(project.JsonConfigPath); request.LockFilePath = lockFilePath; request.ExistingLockFile = LockFileUtilities.GetLockFile(lockFilePath, logger); // Find the full closure of project.json files and referenced projects var projectReferences = await project.GetProjectReferenceClosureAsync(context); request.ExternalProjects = projectReferences.ToList(); token.ThrowIfCancellationRequested(); var command = new RestoreCommand(request); // Execute the restore var result = await command.ExecuteAsync(token); // Report a final message with the Success result if (result.Success) { logger.LogMinimal(string.Format(CultureInfo.CurrentCulture, Strings.BuildIntegratedPackageRestoreSucceeded, project.ProjectName)); } else { logger.LogMinimal(string.Format(CultureInfo.CurrentCulture, Strings.BuildIntegratedPackageRestoreFailed, project.ProjectName)); } return(result); } }
/// <summary> /// Restore a build integrated project and update the lock file /// </summary> public static async Task <RestoreResult> RestoreAsync( BuildIntegratedNuGetProject project, ExternalProjectReferenceContext context, IEnumerable <SourceRepository> sources, string effectiveGlobalPackagesFolder, CancellationToken token) { return(await RestoreAsync( project, context, sources, effectiveGlobalPackagesFolder, c => { }, token)); }
public MonoDevelopBuildIntegratedRestorer( ISourceRepositoryProvider repositoryProvider, ISettings settings, string solutionDirectory) { sourceRepositories = repositoryProvider.GetRepositories().ToList(); packageManagementEvents = PackageManagementServices.PackageManagementEvents; packagesFolder = BuildIntegratedProjectUtility.GetEffectiveGlobalPackagesFolder( solutionDirectory, settings); context = CreateRestoreContext(); }
/// <summary> /// If the project is non-xproj and has no xproj references it may fallback to v1. /// </summary> public static async Task <int> GetLockFileVersion( NuGetProject project, ExternalProjectReferenceContext referenceContext) { var lockFileVersion = LockFileFormat.Version; var buildProject = project as BuildIntegratedNuGetProject; if (buildProject != null) { var references = await buildProject.GetProjectReferenceClosureAsync(referenceContext); lockFileVersion = LockFileUtilities.GetLockFileVersion(references); } return(lockFileVersion); }
private async Task BuildIntegratedProjectRestoreAsync( BuildIntegratedNuGetProject project, string solutionDirectory, List <SourceRepository> enabledSources, ExternalProjectReferenceContext context, RestoreCommandProvidersCache providerCache, CancellationToken token) { // Go off the UI thread to perform I/O operations await TaskScheduler.Default; var projectName = NuGetProject.GetUniqueNameOrName(project); var nugetPathContext = NuGetPathContext.Create(Settings); using (var cacheContext = new SourceCacheContext()) { providerCache.GetOrCreate( nugetPathContext.UserPackageFolder, nugetPathContext.FallbackPackageFolders, enabledSources, cacheContext, context.Logger); // Pass down the CancellationToken from the dialog var restoreResult = await BuildIntegratedRestoreUtility.RestoreAsync(project, context, enabledSources, nugetPathContext.UserPackageFolder, nugetPathContext.FallbackPackageFolders, token); if (!restoreResult.Success) { // Mark this as having errors _hasErrors = true; // Invalidate cached results for the project. This will cause it to restore the next time. _buildIntegratedCache.Remove(projectName); await BuildIntegratedProjectReportErrorAsync(projectName, restoreResult, token); } } }
/// <summary> /// Find the list of parent projects which directly or indirectly reference the child project. /// </summary> public static async Task <IReadOnlyList <BuildIntegratedNuGetProject> > GetParentProjectsInClosure( IReadOnlyList <BuildIntegratedNuGetProject> projects, BuildIntegratedNuGetProject target, ExternalProjectReferenceContext referenceContext) { if (projects == null) { throw new ArgumentNullException(nameof(projects)); } if (target == null) { throw new ArgumentNullException(nameof(target)); } var parents = new HashSet <BuildIntegratedNuGetProject>(); var targetProjectJson = Path.GetFullPath(target.JsonConfigPath); foreach (var project in projects) { // do not count the target as a parent if (!target.Equals(project)) { var closure = await project.GetProjectReferenceClosureAsync(referenceContext); // find all projects which have a child reference matching the same project.json path as the target if (closure.Any(reference => reference.PackageSpecPath != null && string.Equals(targetProjectJson, Path.GetFullPath(reference.PackageSpecPath), StringComparison.OrdinalIgnoreCase))) { parents.Add(project); } } } // sort parents by name to make this more deterministic during restores return(parents.OrderBy(parent => parent.ProjectName, StringComparer.Ordinal).ToList()); }
/// <summary> /// Validate that all project.lock.json files are validate for the project.json files, /// and that no packages are missing. /// If a full restore is required this will return false. /// </summary> /// <remarks>Floating versions and project.json files with supports require a full restore.</remarks> public static async Task <bool> IsRestoreRequired( IReadOnlyList <BuildIntegratedNuGetProject> projects, IReadOnlyList <string> packageFolderPaths, ExternalProjectReferenceContext referenceContext) { var packagesChecked = new HashSet <PackageIdentity>(); var pathResolvers = packageFolderPaths.Select(path => new VersionFolderPathResolver(path)); // Validate project.lock.json files foreach (var project in projects) { var lockFilePath = ProjectJsonPathUtilities.GetLockFilePath(project.JsonConfigPath); if (!File.Exists(lockFilePath)) { // If the lock file does not exist a restore is needed return(true); } var lockFileFormat = new LockFileFormat(); var lockFile = lockFileFormat.Read(lockFilePath, referenceContext.Logger); var lockFileVersion = await GetLockFileVersion(project, referenceContext); var packageSpec = referenceContext.GetOrCreateSpec(project.ProjectName, project.JsonConfigPath); if (!lockFile.IsValidForPackageSpec(packageSpec, lockFileVersion)) { // The project.json file has been changed and the lock file needs to be updated. return(true); } // Verify all libraries are on disk foreach (var library in lockFile.Libraries) { var identity = new PackageIdentity(library.Name, library.Version); // Each id/version only needs to be checked once if (packagesChecked.Add(identity)) { var found = false; // Check each package folder. These need to match the order used for restore. foreach (var resolver in pathResolvers) { // Verify the SHA for each package var hashPath = resolver.GetHashPath(library.Name, library.Version); if (File.Exists(hashPath)) { found = true; var sha512 = File.ReadAllText(hashPath); if (library.Sha512 != sha512) { // A package has changed return(true); } // Skip checking the rest of the package folders break; } } if (!found) { // A package is missing return(true); } } } } return(false); }
/// <summary> /// Determine if a full restore is required. For scenarios with no floating versions /// where all packages are already on disk and no server requests are needed /// we can skip the restore after some validation checks. /// </summary> private async Task <bool> IsRestoreRequired( List <BuildIntegratedProjectSystem> projects, bool forceRestore, ExternalProjectReferenceContext referenceContext) { try { // Swap caches var oldCache = _buildIntegratedCache; _buildIntegratedCache = await BuildIntegratedRestoreUtility.CreateBuildIntegratedProjectStateCache( projects, referenceContext); if (forceRestore) { // The cache has been updated, now skip the check since we are doing a restore anyways. return(true); } if (BuildIntegratedRestoreUtility.CacheHasChanges(oldCache, _buildIntegratedCache)) { // A new project has been added return(true); } // Read package folder locations var nugetPaths = NuGetPathContext.Create(Settings); var packageFolderPaths = new List <string>(); // Folder paths must be in order of priority packageFolderPaths.Add(nugetPaths.UserPackageFolder); packageFolderPaths.AddRange(nugetPaths.FallbackPackageFolders); // Verify all packages exist and have the expected hashes var restoreRequired = await BuildIntegratedRestoreUtility.IsRestoreRequired( projects, packageFolderPaths, referenceContext); if (restoreRequired) { // The project.json file does not match the lock file return(true); } } catch (Exception ex) { Debug.Fail("Unable to validate lock files."); ActivityLog.LogError(LogEntrySource, ex.ToString()); WriteLine(VerbosityLevel.Diagnostic, "{0}", ex.ToString()); // If we are unable to validate, run a full restore // This may occur if the lock files are corrupt return(true); } // Validation passed, no restore is needed return(false); }
/// <summary> /// Restore projects with project.json and create the lock files. /// </summary> /// <param name="buildEnabledProjects">Projects containing project.json</param> /// <param name="forceRestore">Force the restore to write out the lock files. /// This is used for rebuilds.</param> /// <returns></returns> private async Task RestoreBuildIntegratedProjectsAsync( string solutionDirectory, List <BuildIntegratedProjectSystem> buildEnabledProjects, bool forceRestore, bool isSolutionAvailable) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (buildEnabledProjects.Any() && IsConsentGranted(Settings)) { if (!isSolutionAvailable) { var globalPackagesFolder = SettingsUtility.GetGlobalPackagesFolder(Settings); if (!Path.IsPathRooted(globalPackagesFolder)) { var message = string.Format( CultureInfo.CurrentCulture, NuGet.PackageManagement.VisualStudio.Strings.RelativeGlobalPackagesFolder, globalPackagesFolder); WriteLine(VerbosityLevel.Quiet, message); // Cannot restore packages since globalPackagesFolder is a relative path // and the solution is not available return; } } var enabledSources = SourceRepositoryProvider.GetRepositories().ToList(); // Cache p2ps discovered from DTE var referenceContext = new ExternalProjectReferenceContext(logger: this); // No-op all project closures are up to date and all packages exist on disk. if (await IsRestoreRequired(buildEnabledProjects, forceRestore, referenceContext)) { var waitDialogFactory = ServiceLocator.GetGlobalService <SVsThreadedWaitDialogFactory, IVsThreadedWaitDialogFactory>(); // NOTE: During restore for build integrated projects, // We might show the dialog even if there are no packages to restore // When both currentStep and totalSteps are 0, we get a marquee on the dialog using (var threadedWaitDialogSession = waitDialogFactory.StartWaitDialog( waitCaption: Resources.DialogTitle, initialProgress: new ThreadedWaitDialogProgressData(Resources.RestoringPackages, string.Empty, string.Empty, isCancelable: true, currentStep: 0, totalSteps: 0))) { // Display the restore opt out message if it has not been shown yet DisplayOptOutMessage(); Token = threadedWaitDialogSession.UserCancellationToken; ThreadedWaitDialogProgress = threadedWaitDialogSession.Progress; // Cache resources between requests var providerCache = new RestoreCommandProvidersCache(); var tasks = new List <Task <KeyValuePair <string, Exception> > >(); var maxTasks = 4; // Restore packages and create the lock file for each project foreach (var project in buildEnabledProjects) { // Mark this as having missing packages so that we will not // display a noop message in the summary _hasMissingPackages = true; _displayRestoreSummary = true; if (tasks.Count >= maxTasks) { await ProcessTask(tasks); } // Skip further restores if the user has clicked cancel if (!Token.IsCancellationRequested) { var projectName = NuGetProject.GetUniqueNameOrName(project); // Restore and create a project.lock.json file tasks.Add(RestoreProject(projectName, async() => await BuildIntegratedProjectRestoreAsync( project, solutionDirectory, enabledSources, referenceContext, providerCache, Token))); } } // Wait for the remaining tasks while (tasks.Count > 0) { await ProcessTask(tasks); } if (Token.IsCancellationRequested) { _canceled = true; } } } } }
/// <summary> /// Get only the direct dependencies from a project /// </summary> private DirectReferences GetDirectProjectReferences( EnvDTEProject project, string projectFileFullPath, ExternalProjectReferenceContext context, IVsEnumHierarchyItemsFactory itemsFactory, string rootProjectPath) { var logger = context.Logger; var cache = context.Cache; var result = new DirectReferences(); // Find a project.json in the project // This checks for files on disk to match how BuildIntegratedProjectSystem checks at creation time. // NuGet.exe also uses disk paths and not the project file. var projectName = Path.GetFileNameWithoutExtension(projectFileFullPath); var projectDirectory = Path.GetDirectoryName(projectFileFullPath); // Check for projectName.project.json and project.json string jsonConfigItem = ProjectJsonPathUtilities.GetProjectConfigPath( directoryPath: projectDirectory, projectName: projectName); var hasProjectJson = true; // Verify the file exists, otherwise clear it if (!File.Exists(jsonConfigItem)) { jsonConfigItem = null; hasProjectJson = false; } // Verify ReferenceOutputAssembly var excludedProjects = GetExcludedReferences(project, itemsFactory); var childReferences = new HashSet <string>(StringComparer.Ordinal); var hasMissingReferences = false; // find all references in the project foreach (var childReference in GetProjectReferences(project)) { try { var reference3 = childReference as Reference3; if (reference3 != null && !reference3.Resolved) { // Skip missing references and show a warning hasMissingReferences = true; continue; } // Skip missing references if (childReference.SourceProject != null) { if (EnvDTEProjectUtility.HasUnsupportedProjectCapability(childReference.SourceProject)) { // Skip this shared project continue; } var childName = EnvDTEProjectUtility.GetFullProjectPath(childReference.SourceProject); // Skip projects which have ReferenceOutputAssembly=false if (!string.IsNullOrEmpty(childName) && !excludedProjects.Contains(childName, StringComparer.OrdinalIgnoreCase)) { childReferences.Add(childName); result.ToProcess.Add(new DTEReference(childReference.SourceProject, childName)); } } else if (hasProjectJson) { // SDK references do not have a SourceProject or child references, // but they can contain project.json files, and should be part of the closure // SDKs are not projects, only the project.json name is checked here var possibleSdkPath = childReference.Path; if (!string.IsNullOrEmpty(possibleSdkPath) && !possibleSdkPath.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && Directory.Exists(possibleSdkPath)) { var possibleProjectJson = Path.Combine( possibleSdkPath, ProjectJsonPathUtilities.ProjectConfigFileName); if (File.Exists(possibleProjectJson)) { childReferences.Add(possibleProjectJson); // add the sdk to the results here result.Processed.Add(new ExternalProjectReference( possibleProjectJson, childReference.Name, possibleProjectJson, msbuildProjectPath: null, projectReferences: Enumerable.Empty <string>())); } } } } catch (Exception ex) { // Exceptions are expected in some scenarios for native projects, // ignore them and show a warning hasMissingReferences = true; logger.LogDebug(ex.ToString()); Debug.Fail("Unable to find project closure: " + ex.ToString()); } } if (hasMissingReferences) { // Log a warning message once per project // This warning contains only the names of the root project and the project with the // broken reference. Attempting to display more details on the actual reference // that has the problem may lead to another exception being thrown. var warning = string.Format( CultureInfo.CurrentCulture, Strings.Warning_ErrorDuringProjectClosureWalk, projectName, rootProjectPath); logger.LogWarning(warning); } // For the xproj -> xproj -> csproj scenario find all xproj-> xproj references. if (projectFileFullPath.EndsWith(XProjUtility.XProjExtension, StringComparison.OrdinalIgnoreCase)) { // All xproj paths, these are already checked for project.json var xprojFiles = XProjUtility.GetProjectReferences(projectFileFullPath); if (xprojFiles.Count > 0) { var pathToProject = GetPathToDTEProjectLookup(project); foreach (var xProjPath in xprojFiles) { // Only add projects that we can find in the solution, otherwise they will // end up causing failures. If this is an actual failure the resolver will // fail when resolving the dependency from project.json Project xProjDTE; if (pathToProject.TryGetValue(xProjPath, out xProjDTE)) { var xProjFullPath = EnvDTEProjectUtility.GetFullProjectPath(xProjDTE); if (!string.IsNullOrEmpty(xProjFullPath)) { childReferences.Add(xProjFullPath); // Continue walking this project if it has not been walked already result.ToProcess.Add(new DTEReference(xProjDTE, xProjFullPath)); } } } } } // Only set a package spec project name if a package spec exists var packageSpecProjectName = jsonConfigItem == null ? null : projectName; // Add the parent project to the results result.Processed.Add(new ExternalProjectReference( projectFileFullPath, packageSpecProjectName, jsonConfigItem, projectFileFullPath, childReferences)); return(result); }
/// <summary> /// Returns the closure of all project to project references below this project. /// </summary> /// <remarks>This uses DTE and should be called from the UI thread.</remarks> public override async Task <IReadOnlyList <ExternalProjectReference> > GetProjectReferenceClosureAsync( ExternalProjectReferenceContext context) { var logger = context.Logger; var cache = context.Cache; // DTE calls need to be done from the main thread await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var results = new HashSet <ExternalProjectReference>(); // projects to walk - DFS var toProcess = new Stack <DTEReference>(); // keep track of found projects to avoid duplicates var rootProjectPath = EnvDTEProjectUtility.GetFullProjectPath(EnvDTEProject); // start with the current project toProcess.Push(new DTEReference(EnvDTEProject, rootProjectPath)); var itemsFactory = ServiceLocator.GetInstance <IVsEnumHierarchyItemsFactory>(); var uniqueNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // continue walking all project references until we run out while (toProcess.Count > 0) { var dteReference = toProcess.Pop(); // Find the path of the current project var projectFileFullPath = dteReference.Path; var project = dteReference.Project; if (string.IsNullOrEmpty(projectFileFullPath) || !uniqueNames.Add(projectFileFullPath)) { // This has already been processed or does not exist continue; } IReadOnlyList <ExternalProjectReference> cacheReferences; if (cache.TryGetValue(projectFileFullPath, out cacheReferences)) { // The cached value contains the entire closure, add it to the results and skip // all child references. results.UnionWith(cacheReferences); } else { // Get direct references var projectResult = GetDirectProjectReferences( dteReference.Project, projectFileFullPath, context, itemsFactory, rootProjectPath); // Add results to the closure results.UnionWith(projectResult.Processed); // Continue processing foreach (var item in projectResult.ToProcess) { toProcess.Push(item); } } } // Cache the results for this project and every child project which has not been cached foreach (var project in results) { if (!context.Cache.ContainsKey(project.UniqueName)) { var closure = BuildIntegratedRestoreUtility.GetExternalClosure(project.UniqueName, results); context.Cache.Add(project.UniqueName, closure.ToList()); } } return(cache[rootProjectPath]); }
public override Task <IReadOnlyList <ExternalProjectReference> > GetProjectReferenceClosureAsync(ExternalProjectReferenceContext context) { return(Runtime.RunInMainThread(() => { return GetExternalProjectReferenceClosureAsync(context); })); }
/// <summary> /// Returns the closure of all project to project references below this project. /// The code is a port of src/NuGet.Clients/PackageManagement.VisualStudio/ProjectSystems/BuildIntegratedProjectSystem.cs /// </summary> Task <IReadOnlyList <ExternalProjectReference> > GetExternalProjectReferenceClosureAsync(ExternalProjectReferenceContext context) { var logger = context.Logger; var cache = context.Cache; var results = new HashSet <ExternalProjectReference> (); // projects to walk - DFS var toProcess = new Stack <DotNetProjectReference> (); // keep track of found projects to avoid duplicates string rootProjectPath = dotNetProject.FileName; // start with the current project toProcess.Push(new DotNetProjectReference(dotNetProject.DotNetProject, rootProjectPath)); var uniqueNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // continue walking all project references until we run out while (toProcess.Count > 0) { var reference = toProcess.Pop(); // Find the path of the current project string projectFileFullPath = reference.Path; var project = reference.Project; if (string.IsNullOrEmpty(projectFileFullPath) || !uniqueNames.Add(projectFileFullPath)) { // This has already been processed or does not exist continue; } IReadOnlyList <ExternalProjectReference> cacheReferences; if (cache.TryGetValue(projectFileFullPath, out cacheReferences)) { // The cached value contains the entire closure, add it to the results and skip // all child references. results.UnionWith(cacheReferences); } else { // Get direct references var projectResult = GetDirectProjectReferences( reference.Project, projectFileFullPath, context, rootProjectPath); // Add results to the closure results.UnionWith(projectResult.Processed); // Continue processing foreach (var item in projectResult.ToProcess) { toProcess.Push(item); } } } // Cache the results for this project and every child project which has not been cached foreach (var project in results) { if (!context.Cache.ContainsKey(project.UniqueName)) { var closure = BuildIntegratedRestoreUtility.GetExternalClosure(project.UniqueName, results); context.Cache.Add(project.UniqueName, closure.ToList()); } } var result = cache[rootProjectPath]; return(Task.FromResult(result)); }
/// <summary> /// Get only the direct dependencies from a project /// </summary> private DirectReferences GetDirectProjectReferences( DotNetProject project, string projectFileFullPath, ExternalProjectReferenceContext context, string rootProjectPath) { var logger = context.Logger; var cache = context.Cache; var result = new DirectReferences(); // Find a project.json in the project // This checks for files on disk to match how BuildIntegratedProjectSystem checks at creation time. // NuGet.exe also uses disk paths and not the project file. var projectName = Path.GetFileNameWithoutExtension(projectFileFullPath); var projectDirectory = Path.GetDirectoryName(projectFileFullPath); // Check for projectName.project.json and project.json string jsonConfigItem = ProjectJsonPathUtilities.GetProjectConfigPath( directoryPath: projectDirectory, projectName: projectName); var hasProjectJson = true; // Verify the file exists, otherwise clear it if (!File.Exists(jsonConfigItem)) { jsonConfigItem = null; hasProjectJson = false; } // Verify ReferenceOutputAssembly var excludedProjects = GetExcludedReferences(project); var childReferences = new HashSet <string> (StringComparer.Ordinal); var hasMissingReferences = false; // find all references in the project foreach (var childReference in project.References) { try { if (!childReference.IsValid) { // Skip missing references and show a warning hasMissingReferences = true; continue; } // Skip missing references DotNetProject sourceProject = null; if (childReference.ReferenceType == ReferenceType.Project) { sourceProject = childReference.ResolveProject(project.ParentSolution) as DotNetProject; } if (sourceProject != null) { string childName = sourceProject.FileName; // Skip projects which have ReferenceOutputAssembly=false if (!string.IsNullOrEmpty(childName) && !excludedProjects.Contains(childName, StringComparer.OrdinalIgnoreCase)) { childReferences.Add(childName); result.ToProcess.Add(new DotNetProjectReference(sourceProject, childName)); } } else if (hasProjectJson) { // SDK references do not have a SourceProject or child references, // but they can contain project.json files, and should be part of the closure // SDKs are not projects, only the project.json name is checked here //var possibleSdkPath = childReference.Path; //if (!string.IsNullOrEmpty (possibleSdkPath) // && !possibleSdkPath.EndsWith (".dll", StringComparison.OrdinalIgnoreCase) // && Directory.Exists(possibleSdkPath)) { // var possibleProjectJson = Path.Combine ( // possibleSdkPath, // ProjectJsonPathUtilities.ProjectConfigFileName); // if (File.Exists (possibleProjectJson)) { // childReferences.Add (possibleProjectJson); // // add the sdk to the results here // result.Processed.Add (new ExternalProjectReference ( // possibleProjectJson, // childReference.Name, // possibleProjectJson, // msbuildProjectPath: null, // projectReferences: Enumerable.Empty<string> ())); // } //} } } catch (Exception ex) { // Exceptions are expected in some scenarios for native projects, // ignore them and show a warning hasMissingReferences = true; logger.LogDebug(ex.Message); LoggingService.LogError("Unable to find project closure.", ex); } } if (hasMissingReferences) { // Log a warning message once per project // This warning contains only the names of the root project and the project with the // broken reference. Attempting to display more details on the actual reference // that has the problem may lead to another exception being thrown. var warning = GettextCatalog.GetString("Failed to resolve all project references for '{0}'. The package restore result for '{1}' may be incomplete.", projectName, rootProjectPath); logger.LogWarning(warning); } // Only set a package spec project name if a package spec exists var packageSpecProjectName = jsonConfigItem == null ? null : projectName; // Add the parent project to the results result.Processed.Add(new ExternalProjectReference( projectFileFullPath, packageSpecProjectName, jsonConfigItem, projectFileFullPath, childReferences)); return(result); }
/// <summary> /// Validate that all project.lock.json files are validate for the project.json files, /// and that no packages are missing. /// If a full restore is required this will return false. /// </summary> /// <remarks>Floating versions and project.json files with supports require a full restore.</remarks> public static async Task <bool> IsRestoreRequired( IReadOnlyList <BuildIntegratedNuGetProject> projects, VersionFolderPathResolver pathResolver, ExternalProjectReferenceContext referenceContext) { var hashesChecked = new HashSet <string>(); // Validate project.lock.json files foreach (var project in projects) { var lockFilePath = ProjectJsonPathUtilities.GetLockFilePath(project.JsonConfigPath); if (!File.Exists(lockFilePath)) { // If the lock file does not exist a restore is needed return(true); } var lockFileFormat = new LockFileFormat(); var lockFile = lockFileFormat.Read(lockFilePath, referenceContext.Logger); var lockFileVersion = await GetLockFileVersion(project, referenceContext); var packageSpec = referenceContext.GetOrCreateSpec(project.ProjectName, project.JsonConfigPath); if (!lockFile.IsValidForPackageSpec(packageSpec, lockFileVersion)) { // The project.json file has been changed and the lock file needs to be updated. return(true); } // Verify all libraries are on disk foreach (var library in lockFile.Libraries) { // Verify the SHA for each package var hashPath = pathResolver.GetHashPath(library.Name, library.Version); // Libraries shared between projects can be skipped if (hashesChecked.Add(hashPath)) { if (File.Exists(hashPath)) { var sha512 = File.ReadAllText(hashPath); if (library.Sha512 != sha512) { // A package has changed return(true); } } else { // A package is missing return(true); } } } } return(false); }