diff --git a/build.cake b/build.cake index d1e775e496f..f8abb674d2f 100644 --- a/build.cake +++ b/build.cake @@ -157,6 +157,10 @@ Task("GetExtensionPackages") .Where(x => !string.IsNullOrEmpty(x.NuGet)) .Select(x => x.NuGet) .ToArray()); + + context.CalcSupportedCakeVersions(extensionDir, + extensionSpecs + .ToDictionary(e => e.NuGet, e => e.TargetCakeVersion)); }); Task("Build") diff --git a/config.wyam b/config.wyam index 3d9ebe1cbad..c19ac43b961 100644 --- a/config.wyam +++ b/config.wyam @@ -27,6 +27,12 @@ Pipelines.InsertBefore(Docs.Code, "Extensions", ? bool.Parse(FileSystem.GetInputFile($"../release/extensions/{@doc.String("NuGet")}.isprerelease").ReadAllText()) : false ), + Meta( + "SupportedCakeVersions", + FileSystem.GetInputFile($"../release/extensions/{@doc.String("NuGet")}.supportedcakeversions").Exists + ? FileSystem.GetInputFile($"../release/extensions/{@doc.String("NuGet")}.supportedcakeversions").ReadAllText() + : null + ), Meta( Keys.WritePath, new FilePath("extensions/" + @doc.String("Name").ToLower().Replace(".", "-") + "/index.html") diff --git a/input/_ExtensionsLayout.cshtml b/input/_ExtensionsLayout.cshtml index d6de5db0c97..9c70573f0be 100644 --- a/input/_ExtensionsLayout.cshtml +++ b/input/_ExtensionsLayout.cshtml @@ -12,6 +12,7 @@ string author = Model.String("Author"); string repository = Model.String("Repository"); string version = Model.String("Version"); + string supportedCakeVersions = Model.String("SupportedCakeVersions"); string categories = (String.Join(" ", Model.List("Categories").Select(x => $"{x}"))); var assemblies = Model.List("Assemblies"); } @@ -76,6 +77,12 @@
  • Latest version: @version
  • + @if (!string.IsNullOrWhiteSpace(supportedCakeVersions)) + { +
  • + Supported Cake versions: @supportedCakeVersions +
  • + }
  • @nuget diff --git a/nuget.cake b/nuget.cake index e35b5ee18b2..bd460de5a3a 100644 --- a/nuget.cake +++ b/nuget.cake @@ -1,9 +1,14 @@ #addin "nuget:https://api.nuget.org/v3/index.json?package=Polly&version=7.1.0" #addin "nuget:https://api.nuget.org/v3/index.json?package=LitJson&version=0.13.0" #addin "nuget:https://api.nuget.org/v3/index.json??package=Cake.FileHelpers&version=3.3.0" -#addin "nuget:https://api.nuget.org/v3/index.json?package=NuGet.Versioning&version=5.7.0" +#addin "nuget:https://api.nuget.org/v3/index.json?package=NuGet.Protocol&version=5.7.0" using System.Net.Http; +using System.Threading; +using NuGet.Common; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; +using NuGetRepository = NuGet.Protocol.Core.Types.Repository; using NuGet.Versioning; using Polly; @@ -104,6 +109,115 @@ public static void DownloadPackage(this ICakeContext context, DirectoryPath exte context.Information("[{0}] done.", packageId); } +// Cake compatilibity ranges derived from `LatestPotentialBreakingChange` +// Version range values use interval notation +// https://docs.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges +static VersionRange[] _compatibilityVersionRanges = new [] +{ + "[1.0.0, )", + "[0.33.0, 1.0.0)", + "[0.28.0, 0.33.0)", + "[0.26.0, 0.28.0)", + "[0.22.0, 0.26.0)", + "[0.16.0, 0.22.0)", + "[0.15.0, 0.16.0)", + "[0.14.0, 0.15.0)", + "[0.13.0, 0.14.0)", + "[0.12.0, 0.13.0)", + "[0.11.0, 0.12.0)", + "[0.10.0, 0.11.0)", + "[0.9.0, 0.10.0)", + "[0.8.0, 0.9.0)", + "[0.7.0, 0.8.0)", + "[0.6.0, 0.7.0)", + "[0.5.0, 0.6.0)", + "[0.4.0, 0.5.0)", + "[0.3.0, 0.4.0)", + "[0.2.0, 0.3.0)", + "[0.1.0, 0.2.0)", + "[0.0.0, 0.1.0)", +} +.Select(r => VersionRange.Parse(r)) +.OrderByDescending(r => r.MinVersion) +.ToArray(); + +public static void CalcSupportedCakeVersions(this ICakeContext context, DirectoryPath extensionDir, IDictionary packageVersionLookup) +{ + var allListedCakeVersions = GetAllListedCakeVersions(); + + foreach (var item in packageVersionLookup) + { + var packageId = item.Key; + var targetCakeVersion = item.Value is null ? null : new NuGetVersion(item.Value); + + var supportedCakeVersions = CalcSupportedCakeVersionsForExtension(targetCakeVersion, allListedCakeVersions); + context.FileWriteText(extensionDir.CombineWithFilePath($"{packageId}.supportedcakeversions"), supportedCakeVersions); + } +} + +static IReadOnlyList GetAllListedCakeVersions() +{ + using (var cacheContext = new SourceCacheContext { NoCache = true }) + { + var repository = NuGetRepository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + + var resource = repository.GetResourceAsync().GetAwaiter().GetResult(); + + var packages = resource.GetMetadataAsync("Cake", includePrerelease: true, + includeUnlisted: true, cacheContext, NullLogger.Instance, CancellationToken.None).GetAwaiter().GetResult(); + + var allVersionsOfCake = packages.OfType() + .OrderByDescending(p => p.Version) + .Select(p => p.Version) + .ToList(); + + return allVersionsOfCake; + } +} + +static string CalcSupportedCakeVersionsForExtension(NuGetVersion extensionVersion, IReadOnlyList allListedCakeVersions) +{ + if (extensionVersion is null) return null; + + // Map AddInDiscoverer's TargetCakeVersion to a listed Cake version + // (edge case if core libraries are released separately from the Cake package) + var minCakeVersion = allListedCakeVersions + .Where(v => v >= extensionVersion && v.IsPrerelease == extensionVersion.IsPrerelease) + .OrderBy(v => v) + .FirstOrDefault(); + + if (minCakeVersion is null) + { + // Extension seems to reference a version of Cake that is not listed + return null; + } + + NuGetVersion maxCakeVersion = null; + if (minCakeVersion.IsPrerelease) + { + // Extensions targeting pre-release versions of Cake are pinned to the specific pre-release version they target + return $"{minCakeVersion}"; + } + else + { + // Find the latest compatibility range that this extension falls into + var compatRange = _compatibilityVersionRanges + .Where(r => r.Satisfies(minCakeVersion)) + .OrderByDescending(r => r.MinVersion) + .First(); + + // Find the maximum stable Cake version that satisfies the compat range + maxCakeVersion = allListedCakeVersions + .Where(v => compatRange.Satisfies(v) && !v.IsPrerelease) + .OrderByDescending(v => v) + .First(); + } + + return (minCakeVersion == maxCakeVersion) + ? $"{maxCakeVersion}" + : $"{minCakeVersion} - {maxCakeVersion}"; +} + public class Package { public PackageItem[] items { get; set; }