/// <summary> /// Order dependencies by children first. /// </summary> public static IReadOnlyList <PackageSpec> SortPackagesByDependencyOrder( IEnumerable <PackageSpec> packages) { return(TopologicalSortUtility.SortPackagesByDependencyOrder( items: packages, comparer: StringComparer.Ordinal, getId: GetPackageSpecId, getDependencies: GetPackageSpecDependencyIds)); }
public void TopologicalSortUtility_GivenASingleDepChainVerifyReverseOrder() { var packages = new[] { new PackageDependencyInfo(new PackageIdentity("b", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("a") }), new PackageDependencyInfo(new PackageIdentity("c", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("b") }), new PackageDependencyInfo(new PackageIdentity("a", NuGetVersion.Parse("1.0.0")), Enumerable.Empty <PackageDependency>()), }; var sorted = TopologicalSortUtility.SortPackagesByDependencyOrder(packages); sorted.Select(e => e.Id).Should().ContainInOrder(new[] { "a", "b", "c" }); }
public void TopologicalSortUtility_GivenUnrelatedPackagesWithMissingDepsVerifySortOrder() { var packages = new[] { new PackageDependencyInfo(new PackageIdentity("b", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("x") }), new PackageDependencyInfo(new PackageIdentity("a", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("y") }), new PackageDependencyInfo(new PackageIdentity("C", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("z") }), }; var sorted = TopologicalSortUtility.SortPackagesByDependencyOrder(packages); sorted.Select(e => e.Id).Should().ContainInOrder(new[] { "C", "b", "a" }); }
public void TopologicalSortUtility_SortOrderOfDiamondDependencyWithMissingVerifyNoChange() { var packages = new[] { new PackageDependencyInfo(new PackageIdentity("a", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("b"), new PackageDependency("c"), new PackageDependency("x") }), new PackageDependencyInfo(new PackageIdentity("b", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("d"), new PackageDependency("x") }), new PackageDependencyInfo(new PackageIdentity("c", NuGetVersion.Parse("1.0.0")), new[] { new PackageDependency("d") }), new PackageDependencyInfo(new PackageIdentity("d", NuGetVersion.Parse("1.0.0")), Enumerable.Empty <PackageDependency>()), }; var sorted = TopologicalSortUtility.SortPackagesByDependencyOrder(packages); sorted.Select(e => e.Id).Should().ContainInOrder(new[] { "d", "c", "b", "a" }); }
public void TopologicalSortUtility_GivenInterlinkedDepsVerifyRevSortOrder() { var packages = new[] { CreateInfo("f", "e", "d", "c", "b", "a"), CreateInfo("e", "d", "c", "b", "a"), CreateInfo("d", "c", "b", "a"), CreateInfo("c", "b", "a"), CreateInfo("b", "a"), CreateInfo("a") }; var sorted = TopologicalSortUtility.SortPackagesByDependencyOrder(packages); sorted.Select(e => e.Id).Should().ContainInOrder(new[] { "a", "b", "c", "d", "e", "f" }); }
/// <summary> /// Get ordered dependencies from the lock file /// </summary> /// <param name="lockFile"></param> /// <returns></returns> public static IReadOnlyList <LibraryIdentity> GetOrderedLockFileDependencies(LockFile lockFile) { var results = new List <LibraryIdentity>(); var dependencies = new HashSet <PackageDependencyInfo>(PackageIdentity.Comparer); var typeMappings = new Dictionary <PackageDependencyInfo, LibraryIdentity>(PackageIdentity.Comparer); foreach (var target in lockFile.Targets) { foreach (var targetLibrary in target.Libraries) { var identity = new PackageIdentity(targetLibrary.Name, targetLibrary.Version); var dependency = new PackageDependencyInfo(identity, targetLibrary.Dependencies); dependencies.Add(dependency); if (!typeMappings.ContainsKey(dependency)) { var libraryIdentity = new LibraryIdentity( targetLibrary.Name, targetLibrary.Version, LibraryType.Parse(targetLibrary.Type)); typeMappings.Add(dependency, libraryIdentity); } } } // Sort dependencies var sortedDependencies = TopologicalSortUtility.SortPackagesByDependencyOrder(dependencies); foreach (var dependency in sortedDependencies) { // Convert back // PackageDependencyInfo -> LibraryIdentity results.Add(typeMappings[dependency]); } return(results); }
public static List <MSBuildOutputFile> GetMSBuildOutputFiles(PackageSpec project, LockFile assetsFile, IEnumerable <RestoreTargetGraph> targetGraphs, IReadOnlyList <NuGetv3LocalRepository> repositories, RestoreRequest request, string assetsFilePath, bool restoreSuccess, ILogger log) { // Generate file names var targetsPath = GetMSBuildFilePath(project, request, "targets"); var propsPath = GetMSBuildFilePath(project, request, "props"); // Targets files contain a macro for the repository root. If only the user package folder was used // allow a replacement. If fallback folders were used the macro cannot be applied. // Do not use macros for fallback folders. Use only the first repository which is the user folder. var repositoryRoot = repositories.First().RepositoryRoot; // Invalid msbuild projects should write out an msbuild error target if (!targetGraphs.Any()) { return(GenerateMultiTargetFailureFiles( targetsPath, propsPath, request.ProjectStyle)); } // Add additional conditionals for multi targeting var multiTargetingFromMetadata = (request.Project.RestoreMetadata?.CrossTargeting == true); var isMultiTargeting = multiTargetingFromMetadata || request.Project.TargetFrameworks.Count > 1; // ItemGroups for each file. var props = new List <MSBuildRestoreItemGroup>(); var targets = new List <MSBuildRestoreItemGroup>(); // MultiTargeting imports are shared between TFMs, to avoid // duplicate import warnings only add each once. var multiTargetingImportsAdded = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // Skip runtime graphs, msbuild targets may not come from RID specific packages. var ridlessTargets = assetsFile.Targets .Where(e => string.IsNullOrEmpty(e.RuntimeIdentifier)); var packagesWithTools = new HashSet <string>(assetsFile.Libraries.Where(i => i.HasTools).Select(i => i.Name), StringComparer.OrdinalIgnoreCase); foreach (var ridlessTarget in ridlessTargets) { // There could be multiple string matches from the MSBuild project. var frameworkConditions = GetMatchingFrameworkStrings(project, ridlessTarget.TargetFramework) .Select(match => string.Format(CultureInfo.InvariantCulture, TargetFrameworkCondition, match)) .ToArray(); // Find matching target in the original target graphs. var targetGraph = targetGraphs.FirstOrDefault(e => string.IsNullOrEmpty(e.RuntimeIdentifier) && ridlessTarget.TargetFramework == e.Framework); // Sort by dependency order, child package assets should appear higher in the // msbuild targets and props files so that parents can depend on them. var sortedGraph = TopologicalSortUtility.SortPackagesByDependencyOrder(ConvertToPackageDependencyInfo(targetGraph.Flattened)); // Filter out to packages only, exclude projects. var packageType = new HashSet <string>( targetGraph.Flattened.Where(e => e.Key.Type == LibraryType.Package) .Select(e => e.Key.Name), StringComparer.OrdinalIgnoreCase); // Package -> PackageInfo // PackageInfo is kept lazy to avoid hitting the disk for packages // with no relevant assets. var sortedPackages = sortedGraph.Where(e => packageType.Contains(e.Id)) .Select(sortedPkg => new KeyValuePair <LockFileTargetLibrary, Lazy <LocalPackageSourceInfo> >( key: ridlessTarget.Libraries.FirstOrDefault(assetsPkg => sortedPkg.Version == assetsPkg.Version && sortedPkg.Id.Equals(assetsPkg.Name, StringComparison.OrdinalIgnoreCase)), value: new Lazy <LocalPackageSourceInfo>(() => NuGetv3LocalRepositoryUtility.GetPackage( repositories, sortedPkg.Id, sortedPkg.Version)))) .Where(e => e.Key != null) .ToArray(); // build/ {packageId}.targets var buildTargetsGroup = new MSBuildRestoreItemGroup(); buildTargetsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildTargetsGroup.Position = 2; buildTargetsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.Build.WithExtension(TargetsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); targets.AddRange(GenerateGroupsWithConditions(buildTargetsGroup, isMultiTargeting, frameworkConditions)); // props/ {packageId}.props var buildPropsGroup = new MSBuildRestoreItemGroup(); buildPropsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildPropsGroup.Position = 2; buildPropsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.Build.WithExtension(PropsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); props.AddRange(GenerateGroupsWithConditions(buildPropsGroup, isMultiTargeting, frameworkConditions)); // Create an empty PropertyGroup for package properties var packagePathsPropertyGroup = MSBuildRestoreItemGroup.Create("PropertyGroup", Enumerable.Empty <XElement>(), 1000, isMultiTargeting ? frameworkConditions : Enumerable.Empty <string>()); var projectGraph = targetGraph.Graphs.FirstOrDefault(); // Packages with GeneratePathProperty=true var packageIdsToCreatePropertiesFor = new HashSet <string>(projectGraph.Item.Data.Dependencies.Where(i => i.GeneratePathProperty).Select(i => i.Name), StringComparer.OrdinalIgnoreCase); var localPackages = sortedPackages.Select(e => e.Value); // Find the packages with matching IDs in the list of sorted packages, filtering out ones that there was no match for or that don't exist var packagePathProperties = localPackages .Where(pkg => pkg?.Value?.Package != null && (packagesWithTools.Contains(pkg.Value.Package.Id) || packageIdsToCreatePropertiesFor.Contains(pkg.Value.Package.Id)) && pkg.Exists()) .Select(pkg => pkg.Value.Package) // Get the property .Select(GeneratePackagePathProperty); packagePathsPropertyGroup.Items.AddRange(packagePathProperties); // Don't bother adding the PropertyGroup if there were no properties added if (packagePathsPropertyGroup.Items.Any()) { props.Add(packagePathsPropertyGroup); } if (isMultiTargeting) { // buildMultiTargeting/ {packageId}.targets var buildCrossTargetsGroup = new MSBuildRestoreItemGroup(); buildCrossTargetsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildCrossTargetsGroup.Position = 0; buildCrossTargetsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.BuildMultiTargeting.WithExtension(TargetsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Where(path => multiTargetingImportsAdded.Add(path)) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); targets.AddRange(GenerateGroupsWithConditions(buildCrossTargetsGroup, isMultiTargeting, CrossTargetingCondition)); // buildMultiTargeting/ {packageId}.props var buildCrossPropsGroup = new MSBuildRestoreItemGroup(); buildCrossPropsGroup.RootName = MSBuildRestoreItemGroup.ImportGroup; buildCrossPropsGroup.Position = 0; buildCrossPropsGroup.Items.AddRange(sortedPackages.SelectMany(pkg => pkg.Key.BuildMultiTargeting.WithExtension(PropsExtension) .Where(e => pkg.Value.Exists()) .Select(e => pkg.Value.GetAbsolutePath(e))) .Where(path => multiTargetingImportsAdded.Add(path)) .Select(path => GetPathWithMacros(path, repositoryRoot)) .Select(GenerateImport)); props.AddRange(GenerateGroupsWithConditions(buildCrossPropsGroup, isMultiTargeting, CrossTargetingCondition)); } // Write out contentFiles only for XPlat PackageReference projects. if (request.ProjectStyle != ProjectStyle.ProjectJson && request.Project.RestoreMetadata?.SkipContentFileWrite != true) { // Create a group for every package, with the nearest from each of allLanguages props.AddRange(sortedPackages.Select(pkg => pkg.Key.ContentFiles .Where(e => pkg.Value.Exists()) .OrderBy(e => e.Path, StringComparer.Ordinal) .Select(e => Tuple.Create( item1: pkg.Key, item2: e, item3: GetPathWithMacros(pkg.Value.GetAbsolutePath(e), repositoryRoot)))) .SelectMany(e => GetLanguageGroups(e)) .SelectMany(group => GenerateGroupsWithConditions(group, isMultiTargeting, frameworkConditions))); } } // Add exclude all condition to all groups foreach (var group in props.Concat(targets)) { group.Conditions.Add(ExcludeAllCondition); } // Create XML, these may be null if the file should be deleted/not written out. var propsXML = GenerateMSBuildFile(props, request.ProjectStyle); var targetsXML = GenerateMSBuildFile(targets, request.ProjectStyle); // Return all files to write out or delete. var files = new List <MSBuildOutputFile> { new MSBuildOutputFile(propsPath, propsXML), new MSBuildOutputFile(targetsPath, targetsXML) }; var packageFolders = repositories.Select(e => e.RepositoryRoot); AddNuGetPropertiesToFirstImport(files, packageFolders, repositoryRoot, request.ProjectStyle, assetsFilePath, restoreSuccess); return(files); }