如何下载没有依赖项的nuget包? 用法完整的 build.cake 示例nugetinstall.cakenugetmodel.cake

问题描述

我使用下面的代码在 CI 过程中安装 nuget 包,

Cake.Common.Tools.NuGet.NuGetAliases.NuGetInstall(context,packageid,new NuGetInstallSettings { NoCache = true,OutputDirectory = "../Packages",});

问题:

如何下​​载没有依赖的nuget包?

解决方法

底层工具 NuGet.exe 目前不支持只下载包,有一个 GitHub 问题在跟踪这个 https://github.com/NuGet/Home/issues/5919

也就是说,您可以在 Cake 脚本中使用其他 Cake 别名或仅使用简单的 C# 来实现它。

一个定制的例子是 Cake 自己的网站,它直接从 NuGet.org 下载插件,只获取所需的 dll 和 xmldoc 文件,可以在以下位置找到: https://github.com/cake-build/website/blob/9a7bf2fbf8b485488517175376cf11baa3817098/nuget.cake#L33

如果您对定制​​的 NuGetInstall 等效项感兴趣,我可以用它更新此答案。

更新添加了“DownloadLatestPackage”示例

Cake NuGet 包下载示例

这是一个如何在 Cake 脚本中从 V3 NuGet 源下载 NuGet 包的示例。

用法

  #load "nugetinstall.cake"

  await context.DownloadLatestPackage(
        "PackageId","../Packages"
    );

完整的 build.cake 示例

#load "nugetinstall.cake"

Task("DownloadPackages")
    .Does(
        async context => {
            foreach(var packageId in new [] { "Cake.Core","Cake.Common","Cake.Git" })
            {
                await context.DownloadLatestPackage(
                    packageId,"../Packages"
                );
            }
        }
    );

Task("Default")
    .IsDependentOn("DownloadPackages");

RunTarget(Argument("target","Default"));

会输出类似的东西

========================================
DownloadPackages
========================================
Downloading package ../Packages/Cake.Core.1.0.0-rc0002...
Downloading package ../Packages/Cake.Common.1.0.0-rc0002...
Downloading package ../Packages/Cake.Git.0.22.0...

========================================
Default
========================================

Task                          Duration
--------------------------------------------------
DownloadPackages              00:00:03.3939241
--------------------------------------------------
Total:                        00:00:03.3946443

导致包文件夹看起来像下面

Packages
+---Cake.Common.1.0.0-rc0002
| 
+---Cake.Core.1.0.0-rc0002
|
\---Cake.Git.0.22.0

助手蛋糕脚本内容

nugetinstall.cake

#addin nuget:?package=System.Text.Json&version=4.6.0&loaddependencies=true
#load "nugetmodel.cake"
using System.Net.Http;
using System.Text.Json;


public static async Task<T> GetAsync<T>(this HttpClient client,string uri)
{
    using (var stream = await client.GetStreamAsync(uri))
    {
        return await JsonSerializer.DeserializeAsync<T>(
            stream,new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
            );
    }
}

public static async Task<bool> DownloadLatestPackage(this ICakeContext context,string packageId,DirectoryPath outputDirectory,string nuGetSource = "https://api.nuget.org/v3/index.json")
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }

    if (string.IsNullOrWhiteSpace(packageId))
    {
        throw new ArgumentNullException(nameof(packageId));
    }

    if (string.IsNullOrWhiteSpace(nuGetSource))
    {
        throw new ArgumentNullException(nameof(nuGetSource));
    }

    if (outputDirectory == null)
    {
        throw new ArgumentNullException(nameof(outputDirectory));
    }

    if (!context.DirectoryExists(outputDirectory))
    {
        throw new DirectoryNotFoundException($"{nameof(outputDirectory)} ({outputDirectory}) not found.");
    }

    using(var client = new HttpClient())
    {
        client.DefaultRequestHeaders.UserAgent.ParseAdd($"Cake NuGet Client/{context.Environment.Runtime.CakeVersion.ToString(3)}");
        var nuGetIndex = await client.GetAsync<NuGetIndex>(nuGetSource);
        var cakeBaseUrl = string.Concat(
                                nuGetIndex
                                    ?.Resources
                                    ?.Where(type => type.Type?.Length == 20
                                                    && type.Type == "RegistrationsBaseUrl"
                                                    && type.Id?.Length > 8 == true
                                                    && type.Id.StartsWith("https://"))
                                    .Select(url => url.Id)
                                    .FirstOrDefault()
                                    ?? throw new Exception($"Failed to fetch RegistrationsBaseUrl from {nuGetSource}."),$"{packageId.ToLowerInvariant()}/index.json"
                            );

        var cakeNuGetIndex = await client.GetAsync<NuGetContainer<NuGetContainer<NuGetPackageEntry>>>(cakeBaseUrl);

        var packageEntry = (
            from item in cakeNuGetIndex.Items
            from version in item.Items
            orderby SemVersion.TryParse(
                        version.CatalogEntry.Version,out var semVersion
                    )
                        ? semVersion
                        : SemVersion.Zero
                descending
            select version.CatalogEntry
        ).FirstOrDefault();

        if (string.IsNullOrWhiteSpace(packageEntry?.PackageContent))
        {
            throw new Exception($"Failed to found package uri for {packageId} on source {nuGetSource}");
        }

        var packageDirectory = outputDirectory.Combine($"{packageEntry.PackageId}.{packageEntry.Version}");

        if(context.DirectoryExists(packageDirectory))
        {
            context.Information("Package {0} already downloaded.",packageDirectory);
            return true;
        }

        context.Information("Downloading package {0}...",packageDirectory);
        using (var stream = await client.GetStreamAsync(packageEntry?.PackageContent))
        {
            using (var zipStream = new System.IO.Compression.ZipArchive(stream))
            {
                foreach (var entry in zipStream.Entries)
                {
                    var entryPath = packageDirectory.CombineWithFilePath(entry.FullName);
                    var directory = entryPath.GetDirectory();

                    context.EnsureDirectoryExists(directory);

                    using (System.IO.Stream source = entry.Open(),target = context.FileSystem.GetFile(entryPath).OpenWrite())
                    {
                        source.CopyTo(target);
                    }
                }
            }
        }
        return context.DirectoryExists(packageDirectory);
    }
}

nugetmodel.cake

#addin nuget:?package=System.Text.Json&version=4.6.0&loaddependencies=true
using System;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

public class NuGetIndex
{
    [JsonPropertyName("version")]
    public string Version { get; set; }

    [JsonPropertyName("resources")]
    public NuGetResource[] Resources { get; set; }
}

public class NuGetResource
{
    [JsonPropertyName("@id")]
    public string Id { get; set; }

    [JsonPropertyName("@type")]
    public string Type { get; set; }
}

public class NuGetCommit
{
    [JsonPropertyName("@id")]
    public string Id { get; set; }

    [JsonPropertyName("commitId")]
    public Guid CommitId { get; set; }

    [JsonPropertyName("commitTimeStamp")]
    public DateTimeOffset CommitTimeStamp { get; set; }
}

public class NuGetContainer<T> : NuGetCommit
{
    [JsonPropertyName("count")]
    public int Count { get; set; }

    [JsonPropertyName("items")]
    public T[] Items { get; set; }
}
public class NuGetPackageEntry: NuGetCommit
{
    [JsonPropertyName("catalogEntry")]
    public NuGetCatalogEntry CatalogEntry { get; set; }
}
public class NuGetCatalogEntry: NuGetResource
{
    [JsonPropertyName("version")]
    public string Version { get; set; }

    [JsonPropertyName("packageContent")]
    public string PackageContent { get; set; }

    [JsonPropertyName("id")]
   public string PackageId { get; set; }
}

public struct SemVersion : IComparable,IComparable<SemVersion>,IEquatable<SemVersion>
{
    public static SemVersion Zero { get; } = new SemVersion(0,null,"0.0.0");

    static readonly Regex SemVerRegex =
        new Regex (
            @"(?<Major>0|(?:[1-9]\d*))(?:\.(?<Minor>0|(?:[1-9]\d*))(?:\.(?<Patch>0|(?:[1-9]\d*)))?(?:\-(?<PreRelease>[0-9A-Z\.-]+))?(?:\+(?<Meta>[0-9A-Z\.-]+))?)?",RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase
        );

    public int Major { get; }
    public int Minor { get; }
    public int Patch { get; }
    public string PreRelease { get; }
    public string Meta { get; }
    public bool IsPreRelease { get; }
    public bool HasMeta { get; }
    public string VersionString { get; }

    public SemVersion (int major,int minor,int patch,string preRelease = null,string meta = null) :
        this (major,minor,patch,preRelease,meta,null)
    {
    }

    private SemVersion (int major,string preRelease,string meta,string versionString)
    {
        Major = major;
        Minor = minor;
        Patch = patch;
        IsPreRelease = !string.IsNullOrEmpty (preRelease);
        HasMeta = !string.IsNullOrEmpty (meta);
        PreRelease = IsPreRelease ? preRelease : null;
        Meta = HasMeta ? meta : null;

        if (!string.IsNullOrEmpty (versionString)) {
            VersionString = versionString;
        } else {
            var sb = new StringBuilder ();
            sb.AppendFormat (CultureInfo.InvariantCulture,"{0}.{1}.{2}",Major,Minor,Patch);

            if (IsPreRelease) {
                sb.AppendFormat (CultureInfo.InvariantCulture,"-{0}",PreRelease);
            }

            if (HasMeta) {
                sb.AppendFormat (CultureInfo.InvariantCulture,"+{0}",Meta);
            }

            VersionString = sb.ToString ();
        }
    }

    public static bool TryParse (string version,out SemVersion semVersion)
    {
        semVersion = Zero;

        if (string.IsNullOrEmpty(version)) {
            return false;
        }

        var match = SemVerRegex.Match (version);
        if (!match.Success) {
            return false;
        }

        if (!int.TryParse (
                match.Groups["Major"].Value,NumberStyles.Integer,CultureInfo.InvariantCulture,out var major) ||
            !int.TryParse (
                match.Groups["Minor"].Value,out var minor) ||
            !int.TryParse (
                match.Groups["Patch"].Value,out var patch)) {
            return false;
        }

        semVersion = new SemVersion (
            major,match.Groups["PreRelease"]?.Value,match.Groups["Meta"]?.Value,version);

        return true;
    }



    public bool Equals (SemVersion other)
    {
        return Major == other.Major
                && Minor == other.Minor
                && Patch == other.Patch
                && string.Equals(PreRelease,other.PreRelease,StringComparison.OrdinalIgnoreCase)
                && string.Equals(Meta,other.Meta,StringComparison.OrdinalIgnoreCase);
    }

    public int CompareTo (SemVersion other)
    {
        if (Equals(other))
        {
            return 0;
        }

        if (Major > other.Major) {
            return 1;
        }

        if (Major < other.Major) {
            return -1;
        }

        if (Minor > other.Minor) {
            return 1;
        }

        if (Minor < other.Minor) {
            return -1;
        }

        if (Patch > other.Patch) {
            return 1;
        }

        if (Patch < other.Patch) {
            return -1;
        }

        switch(StringComparer.InvariantCultureIgnoreCase.Compare(PreRelease,other.PreRelease)) {
            case 1:
                return 1;

            case -1:
                return -1;

            default:
                return StringComparer.InvariantCultureIgnoreCase.Compare (Meta,other.Meta);
        }
    }

    public int CompareTo (object obj)
    {
        return (obj is SemVersion semVersion)
            ? CompareTo (semVersion)
            : -1;
    }

    public override bool Equals (object obj)
    {
        return (obj is SemVersion semVersion)
                && Equals (semVersion);
    }

    public override int GetHashCode ()
    {
        unchecked {
            var hashCode = Major;
            hashCode = (hashCode * 397) ^ Minor;
            hashCode = (hashCode * 397) ^ Patch;
            hashCode = (hashCode * 397) ^ (PreRelease != null ? StringComparer.OrdinalIgnoreCase.GetHashCode (PreRelease) : 0);
            hashCode = (hashCode * 397) ^ (Meta != null ? StringComparer.OrdinalIgnoreCase.GetHashCode (Meta) : 0);
            return hashCode;
        }
    }

    public override string ToString ()
        => VersionString;

    // Define the is greater than operator.
    public static bool operator > (SemVersion operand1,SemVersion operand2)
        => operand1.CompareTo (operand2) == 1;

    // Define the is less than operator.
    public static bool operator < (SemVersion operand1,SemVersion operand2)
        => operand1.CompareTo (operand2) == -1;

    // Define the is greater than or equal to operator.
    public static bool operator >= (SemVersion operand1,SemVersion operand2)
        => operand1.CompareTo (operand2) >= 0;

    // Define the is less than or equal to operator.
    public static bool operator <= (SemVersion operand1,SemVersion operand2)
        => operand1.CompareTo (operand2) <= 0;
}

要点

作为参考,完整的工作解决方案可以在下面的要点中找到 https://gist.github.com/devlead/ca566f58457f558dd33484a73f1352ed#file-build-cake