簡體   English   中英

如何在沒有依賴項的情況下下載 nuget package?

[英]How to download nuget package without its dependencies?

我使用下面的代碼在 CI 過程中安裝 nuget 包,

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

問題:

如何在沒有依賴項的情況下下載 nuget package?

The underlying tool, NuGet.exe currently doesn't support downloading just the package, there's a GitHub issue tracking this at https://github.com/NuGet/Home/issues/5919

也就是說,您可以使用其他 Cake 別名或簡單的 C# 在您的 Cake 腳本中實現它。

A tailored example of that is Cake's own website which downloads addins straight from NuGet.org only fetching needed dll's and xmldoc files can be found at: https://github.com/cake-build/website/blob/9a7bf2fbf8b485488517175376cf11baa3817098/nuget.cake #L33

如果您對量身定制的 NuGetInstall 等價物感興趣,我可以用它來更新這個答案。

更新添加了“DownloadLatestPackage”示例

蛋糕 NuGet package 下載示例

這是一個如何在 Cake 腳本中從 V3 NuGet 源下載 NuGet package 的示例。

用法

  #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"));

將 output 之類的

========================================
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,0,0, null, 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, int minor, int patch, 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,
                NumberStyles.Integer,
                CultureInfo.InvariantCulture,
                out var minor) ||
            !int.TryParse (
                match.Groups["Patch"].Value,
                NumberStyles.Integer,
                CultureInfo.InvariantCulture,
                out var patch)) {
            return false;
        }

        semVersion = new SemVersion (
            major,
            minor,
            patch,
            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;
}

要旨

作為參考,完整的工作解決方案可以在以下要點中找到

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM