/* Given a set of available frameworks ("InboxOnTargetFrameworks"), and a list of desired frameworks, * reduce the set of frameworks to the minimum set of frameworks which is compatible (preferring inbox frameworks. */ public override bool Execute() { if (null == Dependencies) { Log.LogError("Dependencies argument must be specified"); return(false); } if (PackageIndexes == null && PackageIndexes.Length == 0) { Log.LogError("PackageIndexes argument must be specified"); return(false); } var index = PackageIndex.Load(PackageIndexes.Select(pi => pi.GetMetadata("FullPath"))); // Retrieve the list of dependency group TFM's var dependencyGroups = Dependencies .Select(dependencyItem => new TaskItemPackageDependency(dependencyItem)) .GroupBy(dependency => dependency.TargetFramework) .Select(dependencyGrouping => new TaskItemPackageDependencyGroup(dependencyGrouping.Key, dependencyGrouping)) .ToArray(); // Prepare a resolver for evaluating if candidate frameworks are actually supported by the package PackageItem[] packageItems = Files.Select(f => new PackageItem(f)).ToArray(); var packagePaths = packageItems.Select(pi => pi.TargetPath); NuGetAssetResolver resolver = new NuGetAssetResolver(null, packagePaths); // Determine all inbox frameworks which are supported by this package var supportedInboxFrameworks = index.GetAlllInboxFrameworks().Where(fx => IsSupported(fx, resolver)); var newDependencyGroups = new Queue <TaskItemPackageDependencyGroup>(); // For each inbox framework determine its best compatible dependency group and create an explicit group, trimming out any inbox dependencies foreach (var supportedInboxFramework in supportedInboxFrameworks) { var nearestDependencyGroup = dependencyGroups.GetNearest(supportedInboxFramework); // We found a compatible dependency group that is not the same as this framework if (nearestDependencyGroup != null && nearestDependencyGroup.TargetFramework != supportedInboxFramework) { // remove all dependencies which are inbox on supportedInboxFramework var filteredDependencies = nearestDependencyGroup.Packages.Where(d => !index.IsInbox(d.Id, supportedInboxFramework, d.AssemblyVersion)).ToArray(); newDependencyGroups.Enqueue(new TaskItemPackageDependencyGroup(supportedInboxFramework, filteredDependencies)); } } // Remove any redundant groups from the added set (EG: net45 and net46 with the same set of dependencies) int groupsToCheck = newDependencyGroups.Count; for (int i = 0; i < groupsToCheck; i++) { // to determine if this is a redundant group, we dequeue so that it won't be considered in the following check for nearest group. var group = newDependencyGroups.Dequeue(); // of the remaining groups, find the most compatible one var nearestGroup = newDependencyGroups.Concat(dependencyGroups).GetNearest(group.TargetFramework); // either we found no compatible group, // or the closest compatible group has different dependencies, // or the closest compatible group is portable and this is not (Portable profiles have different framework precedence, https://github.com/NuGet/Home/issues/6483), // keep it in the set of additions if (nearestGroup == null || !group.Packages.SetEquals(nearestGroup.Packages) || FrameworkUtilities.IsPortableMoniker(group.TargetFramework) != FrameworkUtilities.IsPortableMoniker(nearestGroup.TargetFramework)) { // not redundant, keep it in the queue newDependencyGroups.Enqueue(group); } } // Build the items representing added dependency groups. List <ITaskItem> trimmedDependencies = new List <ITaskItem>(); foreach (var newDependencyGroup in newDependencyGroups) { if (newDependencyGroup.Packages.Count == 0) { // no dependencies (all inbox), use a placeholder dependency. var item = new TaskItem(PlaceHolderDependency); item.SetMetadata("TargetFramework", newDependencyGroup.TargetFramework.GetShortFolderName()); trimmedDependencies.Add(item); } else { foreach (var dependency in newDependencyGroup.Packages) { var item = new TaskItem(dependency.Item); // emit CopiedFromTargetFramework to aide in debugging. item.SetMetadata("CopiedFromTargetFramework", item.GetMetadata("TargetFramework")); item.SetMetadata("TargetFramework", newDependencyGroup.TargetFramework.GetShortFolderName()); trimmedDependencies.Add(item); } } } TrimmedDependencies = trimmedDependencies.ToArray(); return(!Log.HasLoggedErrors); }
/* Given a set of available frameworks ("InboxOnTargetFrameworks"), and a list of desired frameworks, * reduce the set of frameworks to the minimum set of frameworks which is compatible (preferring inbox frameworks. */ public override bool Execute() { if (null == Dependencies) { Log.LogError("Dependencies argument must be specified"); return(false); } if (null == FrameworkListsPath) { Log.LogError("FrameworkListsPath argument must be specified"); return(false); } // Retrieve the list of generation dependency group TFM's var dependencyGroups = Dependencies.GroupBy(d => d.GetMetadata("TargetFramework")).Select(dg => new { Framework = NuGetFramework.Parse(dg.Key), Dependencies = dg.ToArray() }); List <ITaskItem> addedDependencies = new List <ITaskItem>(); // Exclude any non-portable frameworks that already have specific dependency groups. var frameworksToExclude = dependencyGroups.Select(dg => dg.Framework).Where(fx => !FrameworkUtilities.IsGenerationMoniker(fx)); // Prepare a resolver for evaluating if candidate frameworks are actually supported by the package PackageItem[] packageItems = Files.Select(f => new PackageItem(f)).ToArray(); var packagePaths = packageItems.Select(pi => pi.TargetPath); var targetFrameworksWithPlaceHolders = packageItems.Where(pi => NuGetAssetResolver.IsPlaceholder(pi.TargetPath)).Select(pi => pi.TargetFramework); NuGetAssetResolver resolver = new NuGetAssetResolver(null, packagePaths); foreach (var portableDependencyGroup in dependencyGroups.Where(dg => FrameworkUtilities.IsGenerationMoniker(dg.Framework))) { // Determine inbox frameworks for this generation that don't already have explicit groups HashSet <NuGetFramework> inboxFrameworksList = new HashSet <NuGetFramework>( Frameworks.GetAlllInboxFrameworks(FrameworkListsPath) .Where(fx => !fx.IsPCL) .Where(fx => Generations.DetermineGenerationForFramework(fx, UseNetPlatform) >= portableDependencyGroup.Framework.Version && !frameworksToExclude.Any(exFx => exFx.Framework == fx.Framework && exFx.Version <= fx.Version))); // Check for assets which have a ref, but not a lib asset. If we have any of these, then they are actually not supported frameworks // and we should not include them. inboxFrameworksList.RemoveWhere(inboxFx => !IsSupported(inboxFx, resolver)); // Only add the lowest version for a particular inbox framework. EG if both net45 and net46 are supported by this generation, // only add net45 inboxFrameworksList.RemoveWhere(fx => inboxFrameworksList.Any(otherFx => (otherFx.Framework.Equals(fx.Framework)) && (otherFx.Version < fx.Version))); // Create dependency items for each inbox framework. foreach (var framework in inboxFrameworksList) { bool addedDependencyToFramework = false; foreach (ITaskItem dependency in portableDependencyGroup.Dependencies) { string version = GetVersion(dependency); if (!Frameworks.IsInbox(FrameworkListsPath, framework, dependency.ItemSpec, version)) { addedDependencyToFramework = true; AddDependency(addedDependencies, new TaskItem(dependency), framework, portableDependencyGroup.Framework); } } if (!addedDependencyToFramework) { AddDependency(addedDependencies, new TaskItem("_._"), framework, portableDependencyGroup.Framework); } } } // Collapse frameworks // For any dependency with a targetframework, if there is another target framework which is compatible and older, remove this dependency. // Get all Dependencies which are not in a portable dependency group so that we can collapse the frameworks. If we include // the portable frameworks, then we'll end up collapsing to those. List <NuGetFramework> allDependencyGroups = new List <NuGetFramework>(); allDependencyGroups.AddRange(Dependencies.Select(d => NuGetFramework.Parse(d.GetMetadata("TargetFramework"))).Where(a => !allDependencyGroups.Contains(a) && !FrameworkUtilities.IsGenerationMoniker(a) && !FrameworkUtilities.IsPortableMoniker(a))); allDependencyGroups.AddRange(addedDependencies.Select(d => NuGetFramework.Parse(d.GetMetadata("TargetFramework"))).Where(a => !allDependencyGroups.Contains(a) && !FrameworkUtilities.IsGenerationMoniker(a) && !FrameworkUtilities.IsPortableMoniker(a))); List <NuGetFramework> collapsedDependencyGroups = FrameworkUtilities.ReduceDownwards(allDependencyGroups).ToList <NuGetFramework>(); // Get the list of dependency groups that we collapsed down so that we can add them back if they contained different dependencies than what is present in the collapsed group. /* TODO: Look into NuGet's sorting algorithm, they may have a bug (fixed in this line). They were not including version in the sort. * See ReduceCore in https://github.com/NuGet/NuGet.Client/blob/23ea68b91a439fcfd7f94bcd01bcdee2e8adae92/src/NuGet.Core/NuGet.Frameworks/FrameworkReducer.cs */ IEnumerable <NuGetFramework> removedDependencyGroups = allDependencyGroups.Where(d => !collapsedDependencyGroups.Contains(d))?.OrderBy(f => f.Framework, StringComparer.OrdinalIgnoreCase).ThenBy(f => f.Version); foreach (NuGetFramework removedDependencyGroup in removedDependencyGroups) { // always recalculate collapsedDependencyGroups in case we added an item in a previous iteration. Dependency groups are sorted, so this should be additive and we shouldn't need to restart the collapse / add back cycle var nearest = FrameworkUtilities.GetNearest(removedDependencyGroup, collapsedDependencyGroups.ToArray()); // gather the dependencies for this dependency group and the calculated "nearest" dependency group var nearestDependencies = addedDependencies.Where(d => nearest.Equals(NuGetFramework.Parse(d.GetMetadata("TargetFramework")))).OrderBy(f => f.ToString()); var currentDependencies = addedDependencies.Where(d => removedDependencyGroup.Equals(NuGetFramework.Parse(d.GetMetadata("TargetFramework")))).OrderBy(f => f.ToString()); // The nearest dependency group's dependencies are different than this dependency group's dependencies if (nearestDependencies.Count() != currentDependencies.Count()) { // ignore if dependency is a placeholder if (currentDependencies.Count() > 0) { if (!NuGetAssetResolver.IsPlaceholder(currentDependencies.First().ToString())) { collapsedDependencyGroups.Add(removedDependencyGroup); } } else { collapsedDependencyGroups.Add(removedDependencyGroup); } } // identical dependency count between current and nearest, and the count is > 0 else if (nearestDependencies.Count() > 0) { if (!currentDependencies.SequenceEqual(nearestDependencies, new DependencyITaskItemComparer())) { collapsedDependencyGroups.Add(removedDependencyGroup); } } } List <ITaskItem> collapsedDependencies = new List <ITaskItem>(); foreach (ITaskItem dependency in addedDependencies) { if (collapsedDependencyGroups.Contains(NuGetFramework.Parse(dependency.GetMetadata("TargetFramework")))) { collapsedDependencies.Add(dependency); } } TrimmedDependencies = collapsedDependencies.ToArray(); return(!Log.HasLoggedErrors); }
/* Given a set of available frameworks ("InboxOnTargetFrameworks"), and a list of desired frameworks, * reduce the set of frameworks to the minimum set of frameworks which is compatible (preferring inbox frameworks. */ public override bool Execute() { if (null == Dependencies) { Log.LogError("Dependencies argument must be specified"); return(false); } if (null == FrameworkListsPath) { Log.LogError("FrameworkListsPath argument must be specified"); return(false); } // Retrieve the list of generation dependency group TFM's Dictionary <string, IEnumerable <ITaskItem> > portableDependencyGroups = new Dictionary <string, IEnumerable <ITaskItem> >(); foreach (ITaskItem dependency in Dependencies) { string framework = dependency.GetMetadata("TargetFramework"); if (framework != null && FrameworkUtilities.IsGenerationMoniker(framework) && !portableDependencyGroups.ContainsKey(framework)) { portableDependencyGroups.Add(framework, Dependencies.Where(d => d.GetMetadata("TargetFramework") == framework)); } } List <ITaskItem> addedDependencies = new List <ITaskItem>(); List <string> placeHolderFrameworks = new List <string>(); var frameworksToExclude = TrimFrameworks?.Select(fx => NuGetFramework.Parse(fx))?.ToArray() ?? new NuGetFramework[0]; // Prepare a resolver for evaluating if candidate frameworks are actually supported by the package PackageItem[] packageItems = Files.Select(f => new PackageItem(f)).ToArray(); var packagePaths = packageItems.Select(pi => pi.TargetPath); var targetFrameworksWithPlaceHolders = packageItems.Where(pi => NuGetAssetResolver.IsPlaceholder(pi.TargetPath)).Select(pi => pi.TargetFramework); NuGetAssetResolver resolver = new NuGetAssetResolver(null, packagePaths); foreach (string portableDependency in portableDependencyGroups.Keys) { NuGetFramework portableDependencyFramework = NuGetFramework.Parse(portableDependency); // Determine inbox frameworks for this generations dependencies as a whole HashSet <NuGetFramework> inboxFrameworksList = new HashSet <NuGetFramework>(); foreach (NuGetFramework inboxFramework in Frameworks.GetAlllInboxFrameworks(FrameworkListsPath)) { if (Generations.DetermineGenerationForFramework(inboxFramework, UseNetPlatform) >= portableDependencyFramework.Version && !frameworksToExclude.Contains(inboxFramework)) { inboxFrameworksList.Add(inboxFramework); } } // Only add the lowest version for a particular inbox framework. EG if both net45 and net46 are supported by this generation, // only add net45 inboxFrameworksList.RemoveWhere(fx => inboxFrameworksList.Any(otherFx => (otherFx.Framework.Equals(fx.Framework)) && (otherFx.Version < fx.Version))); // Check for assets which have a ref, but not a lib asset. If we have any of these, then they are actually not supported frameworks // and we should not include them. inboxFrameworksList.RemoveWhere(inboxFx => !IsSupported(inboxFx, resolver)); // Remove the frameworks which have placeholders. inboxFrameworksList.RemoveWhere(fx => targetFrameworksWithPlaceHolders.Any(tfx => tfx != null && fx.DotNetFrameworkName == tfx.DotNetFrameworkName)); // Create dependency items for each inbox framework. foreach (string framework in inboxFrameworksList.Select(fx => fx.GetShortFolderName())) { bool addedDependencyToFramework = false; foreach (ITaskItem dependency in portableDependencyGroups[portableDependency]) { // If we don't have the AssemblyVersion metadata (4 part version string), fall back and use Version (3 part version string) string version = dependency.GetMetadata("AssemblyVersion"); if (string.IsNullOrEmpty(version)) { version = dependency.GetMetadata("Version"); int prereleaseIndex = version.IndexOf('-'); if (prereleaseIndex != -1) { version = version.Substring(0, prereleaseIndex); } } if (!Frameworks.IsInbox(FrameworkListsPath, framework, dependency.ItemSpec, version)) { addedDependencyToFramework = true; TaskItem dependencyItem = new TaskItem(dependency); dependencyItem.SetMetadata("TargetFramework", framework); // "Generation" is not required metadata, we just include it because it can be useful for debugging. dependencyItem.SetMetadata("Generation", portableDependency); addedDependencies.Add(dependencyItem); } } if (!addedDependencyToFramework) { TaskItem dependencyItem = new TaskItem("_._"); dependencyItem.SetMetadata("TargetFramework", framework); // "Generation" is not required metadata, we just include it because it can be useful for debugging. dependencyItem.SetMetadata("Generation", portableDependency); addedDependencies.Add(dependencyItem); placeHolderFrameworks.Add(framework); } } } // Collapse frameworks // For any dependency with a targetframework, if there is another target framework which is compatible and older, remove this dependency. // Get all Dependencies which are not in a portable dependency group so that we can collapse the frameworks. If we include // the portable frameworks, then we'll end up collapsing to those. List <NuGetFramework> allDependencyGroups = new List <NuGetFramework>(); allDependencyGroups.AddRange(Dependencies.Select(d => NuGetFramework.Parse(d.GetMetadata("TargetFramework"))).Where(a => !allDependencyGroups.Contains(a) && !FrameworkUtilities.IsGenerationMoniker(a) && !FrameworkUtilities.IsPortableMoniker(a))); allDependencyGroups.AddRange(addedDependencies.Select(d => NuGetFramework.Parse(d.GetMetadata("TargetFramework"))).Where(a => !allDependencyGroups.Contains(a) && !FrameworkUtilities.IsGenerationMoniker(a) && !FrameworkUtilities.IsPortableMoniker(a))); List <NuGetFramework> collapsedDependencyGroups = FrameworkUtilities.ReduceDownwards(allDependencyGroups).ToList <NuGetFramework>(); // Get the list of dependency groups that we collapsed down so that we can add them back if they contained different dependencies than what is present in the collapsed group. /* TODO: Look into NuGet's sorting algorithm, they may have a bug (fixed in this line). They were not including version in the sort. * See ReduceCore in https://github.com/NuGet/NuGet.Client/blob/23ea68b91a439fcfd7f94bcd01bcdee2e8adae92/src/NuGet.Core/NuGet.Frameworks/FrameworkReducer.cs */ IEnumerable <NuGetFramework> removedDependencyGroups = allDependencyGroups.Where(d => !collapsedDependencyGroups.Contains(d))?.OrderBy(f => f.Framework, StringComparer.OrdinalIgnoreCase).ThenBy(f => f.Version); foreach (NuGetFramework removedDependencyGroup in removedDependencyGroups) { // always recalculate collapsedDependencyGroups in case we added an item in a previous iteration. Dependency groups are sorted, so this should be additive and we shouldn't need to restart the collapse / add back cycle var nearest = FrameworkUtilities.GetNearest(removedDependencyGroup, collapsedDependencyGroups.ToArray()); // gather the dependencies for this dependency group and the calculated "nearest" dependency group var nearestDependencies = addedDependencies.Where(d => nearest.Equals(NuGetFramework.Parse(d.GetMetadata("TargetFramework")))).OrderBy(f => f.ToString()); var currentDependencies = addedDependencies.Where(d => removedDependencyGroup.Equals(NuGetFramework.Parse(d.GetMetadata("TargetFramework")))).OrderBy(f => f.ToString()); // The nearest dependency group's dependencies are different than this dependency group's dependencies if (nearestDependencies.Count() != currentDependencies.Count()) { // ignore if dependency is a placeholder if (currentDependencies.Count() > 0) { if (!NuGetAssetResolver.IsPlaceholder(currentDependencies.First().ToString())) { collapsedDependencyGroups.Add(removedDependencyGroup); } } else { collapsedDependencyGroups.Add(removedDependencyGroup); } } // identical dependency count between current and nearest, and the count is > 0 else if (nearestDependencies.Count() > 0) { if (!currentDependencies.SequenceEqual(nearestDependencies, new DependencyITaskItemComparer())) { collapsedDependencyGroups.Add(removedDependencyGroup); } } } List <ITaskItem> collapsedDependencies = new List <ITaskItem>(); foreach (ITaskItem dependency in addedDependencies) { if (collapsedDependencyGroups.Contains(NuGetFramework.Parse(dependency.GetMetadata("TargetFramework")))) { collapsedDependencies.Add(dependency); } } TrimmedDependencies = collapsedDependencies.ToArray(); return(!Log.HasLoggedErrors); }