Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calculate & display the supported Cake versions for extensions #1288

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.cake
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 6 additions & 0 deletions config.wyam
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
7 changes: 7 additions & 0 deletions input/_ExtensionsLayout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>("Categories").Select(x => $"<span class=\"badge badge-secondary\">{x}</span>")));
var assemblies = Model.List<string>("Assemblies");
}
Expand Down Expand Up @@ -76,6 +77,12 @@
<li>
Latest version: @version
</li>
@if (!string.IsNullOrWhiteSpace(supportedCakeVersions))
{
<li>
Supported Cake versions: @supportedCakeVersions
</li>
}
<li>
<img src="/assets/img/nuget.png">
<a href="https://www.nuget.org/packages/@nuget" target="_blank">@nuget</a>
Expand Down
116 changes: 115 additions & 1 deletion nuget.cake
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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<string, string> 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<NuGetVersion> GetAllListedCakeVersions()
{
using (var cacheContext = new SourceCacheContext { NoCache = true })
{
var repository = NuGetRepository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json");

var resource = repository.GetResourceAsync<PackageMetadataResource>().GetAwaiter().GetResult();

var packages = resource.GetMetadataAsync("Cake", includePrerelease: true,
includeUnlisted: true, cacheContext, NullLogger.Instance, CancellationToken.None).GetAwaiter().GetResult();

var allVersionsOfCake = packages.OfType<PackageSearchMetadataRegistration>()
.OrderByDescending(p => p.Version)
.Select(p => p.Version)
.ToList();

return allVersionsOfCake;
}
}

static string CalcSupportedCakeVersionsForExtension(NuGetVersion extensionVersion, IReadOnlyList<NuGetVersion> 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; }
Expand Down