private Possible <Unit> ValidateUniquenes(out Dictionary <string, INugetPackage> idToPackageMap, out Dictionary <string, INugetPackage> idPlusVersionToPackageMap) { // TODO: the overall uniquness/aliasing thing should be improved. // Today the following case will lead to weird results: // package1: id: n1, alias: n2 // package2: id: n2, alias: n1 // This will work, but it is not clear what the expected semantic should be. // When the user requests 'n1' which package should be used? idToPackageMap = idPlusVersionToPackageMap = null; // Packages should have unique (id or alias) // And (id + version) -- regardless of the alias. var duplicateNames = new HashSet <string>(); var duplicateIdPlusVersion = new HashSet <string>(); var localIdToPackageMap = new Dictionary <string, INugetPackage>(m_packages.Count); var localIdPlusVersionToPackageMap = new Dictionary <string, INugetPackage>(m_packages.Count); foreach (var package in m_packages) { // Track id+version first string idPlusVersion = package.Id + "." + package.Version; if (!localIdPlusVersionToPackageMap.TryAdd(idPlusVersion, package)) { duplicateIdPlusVersion.Add(package.Id); } // Then track id or alias if (!localIdToPackageMap.TryAdd(package.Id, package)) { // A package with the same id is already in the map. if (string.IsNullOrEmpty(package.Alias) || localIdToPackageMap.ContainsKey(package.Alias)) { duplicateNames.Add(package.Alias ?? package.Id); } else { // This allows us to have the same package id coexist in our workspace as long as it defines a unique alias. localIdToPackageMap.Add(package.Alias, package); } } } if (duplicateNames.Count != 0) { return(NugetFailure.DuplicatedPackagesWithTheSameIdOrAlias(duplicateNames.ToList())); } if (duplicateIdPlusVersion.Count != 0) { return(NugetFailure.DuplicatedPackagesWithTheSameIdAndVersion(duplicateIdPlusVersion.ToList())); } idToPackageMap = localIdToPackageMap; idPlusVersionToPackageMap = localIdPlusVersionToPackageMap; return(Unit.Void); }
/// <summary> /// Sorts analyzedPackages into sortedPackages such that given a package A in sortedPackages, its dependencies are always in the /// tail of A. /// </summary> /// <returns> /// Returns true if the packages were successfully sorted, and in that case failure is null. An unsuccessfull sort is always due /// to a cycle in the package references. In that case, failure contains one package involved in the cycle. /// TODO: Consider enhancing the failure case with information about the whole cycle, for better diagnostics /// </returns> private static bool TryToposortPackages( IDictionary <string, NugetAnalyzedPackage> analyzedPackages, out List <NugetAnalyzedPackage> sortedPackages, out NugetFailure failure) { sortedPackages = new List <NugetAnalyzedPackage>(analyzedPackages.Count); // All packages start as unmarked, and none as temporary marked. We loop until all packages are marked var unmarkedPackages = new HashSet <NugetAnalyzedPackage>(analyzedPackages.Values); var temporaryMarkedPackages = new HashSet <NugetAnalyzedPackage>(); while (unmarkedPackages.Any()) { var aPackage = unmarkedPackages.First(); if (!TryVisit(analyzedPackages, unmarkedPackages, temporaryMarkedPackages, aPackage, sortedPackages)) { sortedPackages = null; failure = new NugetFailure(aPackage.PackageOnDisk.Package, NugetFailure.FailureType.CyclicPackageDependency); return(false); } } failure = null; return(true); }
private Possible <Unit> TryValidatePackagesConfiguration() { var invalidPackages = m_packages.Where(pkg => !ValidatePackageConfiguration(pkg)).Select(pkg => pkg.Alias ?? pkg.Id).ToList(); return(invalidPackages.Count == 0 ? (Possible <Unit>)Unit.Void : NugetFailure.InvalidConfiguration(invalidPackages)); }
/// <summary> /// Given a dictionary of packageID -> NugetAnalyzedPackage that is closed under dependencies (if a nuget package A depends on B, if /// A is in the dictionary then B is in the dictionary), updates the set of supported target frameworks for each package considering its package /// dependencies. /// </summary> /// <remarks> /// The way the patch works is as follows: /// - For a managed package, the set of supported frameworks is explicit given the package directory structure, so it is left as is /// - For a non-managed package, the patched set of supported frameworks is the union of the supported frameworks of all its dependencies, considering /// that all its dependencies were already patched /// </remarks> public static bool TryPatchSupportedTargetFrameworksForPackageExtent(NugetFrameworkMonikers nugetFrameworkMonikers, IDictionary <string, NugetAnalyzedPackage> packageExtent, out NugetFailure failure) { // We topo sort the list of packages, where dependents are always after their dependencies if (!TryToposortPackages(packageExtent, out List <NugetAnalyzedPackage> sortedAnalyzedPackages, out failure)) { return(false); } // We traverse the list from head to tail, so when we retrieve a dependency, it is already patched foreach (var analyzedPackage in sortedAnalyzedPackages) { var targetFrameworkWithFallBacks = analyzedPackage.TargetFrameworkWithFallbacks; // If the package is managed, the qualifier space is already the right one if (analyzedPackage.IsManagedPackage) { /* * TODO:Nuget: Another workaround to make Nuget packages that are already exposing .NET Standard targets compatible to .NET Framework 452 qualifiers, * unfortunately we have many of those in use with BuildXL already e.g. ProtocolReader. This makes coercing between .NETStandard1.1 packages and * .NET Framework 4.5.1 work, until we have proper support built in. If we encounter more packages with different .NETStandard this would * at least be the only place we introduce more special casing. */ if (targetFrameworkWithFallBacks.Count == 1) { if (targetFrameworkWithFallBacks.Keys.FirstOrDefault().Equals(nugetFrameworkMonikers.NetStandard10) || targetFrameworkWithFallBacks.Keys.FirstOrDefault().Equals(nugetFrameworkMonikers.NetStandard11)) { targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net472); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net462); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net461); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net46); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net452); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net451); targetFrameworkWithFallBacks.Add(nugetFrameworkMonikers.Net45); } } continue; } // Otherwise, we compute the union of the qualifier spaces of its dependencies foreach (var dependency in analyzedPackage.Dependencies) { Contract.Assert(packageExtent.Keys.Contains(dependency.GetPackageIdentity())); var analyzedDependency = packageExtent[dependency.GetPackageIdentity()]; CombineTargetFrameworksForUnmanagedPackage(targetFrameworkWithFallBacks, analyzedDependency.TargetFrameworkWithFallbacks); } } return(true); }