NuGet的还原包坚持使用特定的包版本

19

我有一个使用以下packages.config的项目:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Framework.Infrastructure.Core" version="1.4.0.6" />
  <package id="Framework.Infrastructure.Extensions" version="1.4.0.6" />
</packages>

框架*包在我们的本地存储库中的位置。

我已启用软件包还原并将我们的内部存储库添加到源中。但是,当我尝试从packages.config还原软件包(实际上执行nuget install packages.config -sources....),我会收到以下错误消息:

error : Unable to find version '1.4.0.6' of package 'Framework.Infrastructure.Extensions'
error : Unable to find version '1.4.0.6' of package 'Framework.Infrastructure.Core'.

这个仓库不再包含旧版的1.4.0.6软件包(几个月前是相关版本),而是包含了新版本(例如1.5.1.6)。

NuGet为何无法找到新版本的软件包? 我能否在packages.config中指定某些语法以确保下载最新版本?

简而言之,有没有除编写自定义脚本以更新软件包之外的其他方法可用?

谢谢。

4个回答

39
我认为有些人误解了Package Restore的作用。该功能的唯一目的是不要求将软件包提交到版本控制中。许多人抱怨将二进制文件提交到版本库会导致存储库大小增加,而在使用像Git这样的分布式版本控制系统时更糟糕,因为整个存储库都会被本地下载,并包括每个软件包Foo的所有版本。
那么Package Restore到底是什么呢?基本上,它会查找每个项目中的packages.config,并简单地下载列出的软件包的特定版本。这就像删除您的packages文件夹,然后执行 git reset --hard 来重新获取它们(假设该文件夹已被检入)。
为什么这很重要?为什么不升级到最新的软件包版本?如果考虑Package Restore的最常见用例,即执行自动化构建,那应该可以给您一个提示。构建服务器应仅构建由开发人员测试和提交的项目。如果让构建服务器决定何时更新软件包,那么就会得到一个没有经过任何人测试的项目。作为开发人员,您应该决定何时进行升级。
请记住,安装或更新软件包不仅仅是拉取.nupkg文件并添加引用。许多软件包具有副作用,例如更新您的.config文件、添加代码等。当您安装软件包时,所有这些副作用都会发生在本地副本上。现在,您可以提交代码并排除软件包文件。
当另一个开发人员或构建服务器检出代码时,他将拥有与您相同的副作用代码,但不包括软件包文件。Package Restore只需从NuGet存储库中下载这些文件,现在我们拥有了完成此项目所需的一切。
NuGet团队承诺维护所有软件包的所有版本,以便您始终能够获取正确的版本。然而,正如我们几个月前看到的那样,当NuGet服务器崩溃时,它几乎瘫痪了Package Restore,许多人无法构建。我建议您设置自己的NuGet仓库(一个简单的文件共享即可),并在其中保存所有使用的包的副本。这样,您就不会依赖外部服务器来进行构建。并且像NuGet团队一样,您应该保留包的所有版本。这样,如果您需要返回并构建项目的早期版本,您将确保有正确的包版本可用。
希望这能解释说明该功能如何工作以及为什么会这样工作。

8
我建议您阅读NuGet 版本控制文档。它解释了版本号(和范围)如何在packages.config文件中使用,以允许Update-Package命令知道可以升级到哪些可接受的版本。
话虽如此,包恢复功能不会自动更新软件包。
有了这些信息,我认为最好的工作流程是:
  • 安装任何新依赖项的最新稳定版本,除非您真的需要旧版本(或预发布版本)
  • 在 CI 构建中使用包恢复,允许您将 NuGet 包提交到您的 VCS 中
  • 只有当...
    • 您需要最新版本的新 API 调用或错误修复
    • 您拥有一个强大的测试套件以获得信心
    • 您有时间处理可能出现的问题

我不鼓励仅仅因为升级而升级软件包。如果旧版本的依赖关系已经工作良好,最好保留项目现状,因为更新任何软件包的版本存在风险。

NuGet软件包应该遵循语义化版本控制,这有利于提供最无压力的软件包升级体验,但由于它并没有得到执行(相信我,许多软件包发布者并没有遵循SemVer),你不能依赖它。即使一个软件包只是进行了次要版本的更新,你也不能确定(没有足够的测试)新版本是否与你的代码兼容。

总之,自动升级任何软件包通常都是不明智的。最好让开发人员明确选择更新任何给定的软件包,并且只有在足够好的理由时才进行更新。


3
问题在于根据版本规范,version="1.4.0.6"版本(默认情况下在packages.config中)应该被翻译为version >= 1.4.0.6,但事实并非如此。我认为这是nuget restore中的一个bug,因为“update”根本不使用packages.config(似乎它只使用packages目录中的文件夹 - 因此,即使您有packages.config文件,如果没有包存在,“update”也不会做任何事情)。 - Michael Logutov
4
你所描述的版本范围只用于 .nuspec 文件中,而不是 packages.config。它们用于描述包之间的依赖关系。在项目中指定依赖关系时,你需要使用 packages.config 文件,而该文件将始终引用特定版本。是的,你是正确的,除非你最近构建了你的项目并且所有的 NuGet 依赖项都存在于 /packages 目录中,否则 Update-Package 命令将不起作用。这可能有些不方便或不直观,但我认为这不是一个 bug;这只是 NuGet 运行方式。 - Jesse Webb
1
我明白了。好的,我已经在这里描述了https://nuget.codeplex.com/workitem/3264。最大的问题是如果你尝试nuget restore project并且有一些包引用了旧版本,而这个版本在存储库中不再可用(但是有一个新版本),那么没有办法让nuget更新packages.config和.proj文件中该包的版本。 - Michael Logutov
1
@FuriCuri 我认为你混淆了包恢复和更新包的概念。最初,NuGet甚至没有包恢复功能;您必须将/packages目录的内容提交到版本控制中。他们引入了包恢复功能,以允许用户避免这种情况。如果您处于本地存储库中删除软件包的情况下,正如您所描述的问题,您可能需要考虑不使用包恢复。不要与框架对抗。 - Jesse Webb
讨论中有非常微妙的点。我在想为什么范围(range)不能通过packages.config文件工作。 - Manav Sharma

0

如果有人遇到这个问题,我编写了一个PowerShell模块,并将其包装在NuGet包中,用户需要在模板创建时运行它。该脚本会检查解决方案中的每个C#项目,找到其“packages.config”(如果有),然后删除并重新安装其中提到的每个软件包。

显然,在总体方法和小错误方面都有很大的改进空间(例如,在完整名称中带有空格的解决方案上不会运行安装部分中的nuget命令),但这是一个开始。

文件NuGet-RestorePackagesInAllProjects.psm1

$NuGetSources = "https://nuget.org/api/v2/;" # put your internal sources here as needed

function NuGet-RestorePackagesInAllProjects {
    # get the solution directory
    $solutionDir = (get-childitem $dte.Solution.FullName).DirectoryName

    # for each C# project in the solution, process packages.config file, if there is one
    $dte.Solution.Projects | Where-Object { $_.Type -eq "C#" } | ForEach-Object {
        $currentProject = $_
        $currentProjectName = $currentProject.ProjectName
        $currentProjectDir = (get-childitem $_.FullName).DirectoryName

        Write-Host ******* Starting processing $currentProjectName

        # get the packages.config file for the current project
        $packagesFile = $currentProject.ProjectItems | Where-Object { $_.Name -eq "packages.config" }

        # if there's no packages.config, print a message and continue to the next project
        if ($packagesFile -eq $null -or $packagesFile.count -gt 1) { 
            write-host ------- Project $currentProjectName doesn''t have packages.config
            return 
        }

        # read the contents of packages.config file and extract the list of packages in it
        $fileName = $currentProjectDir + "\packages.config"
        [xml]$content = Get-Content $fileName
        $packageList = $content.packages.package | % { $_.id }

        # for each package in the packages.config, uninstall the package (or simply remove the line from the file, if the uninstall fails)
        $packageList | ForEach-Object {
            $currentPackage = $_

            write-host Uninstalling $currentPackage from $currentProjectName

            try {
                Uninstall-Package $currentPackage -ProjectName $currentProjectName -RemoveDependencies -Force
            }
            catch {
                write-host '!!!!!!! $_.Exception.Message is' $_.Exception.Message
                $node = $content.SelectSingleNode("//package[@id='$currentPackage']")
                [Void]$node.ParentNode.RemoveChild($node)
                $content.Save($fileName)
            }
        }

        # download each package into the $(SolutionDir)packages folder, and install it into the current project from there
        $packageList | ForEach-Object {
            $currentPackage = $_
            $localPackagesDir = $solutionDir + "\packages"
            $cmd = $solutionDir + "\.nuget\nuget.exe install " + $currentPackage + " -Source """ + $NuGetSources +  """ -o " + $localPackagesDir

            write-host Installing $currentPackage to $currentProjectName
            invoke-expression -command $cmd
            Install-Package $currentPackage -ProjectName $currentProjectName -Source $localPackagesDir
        }

        Write-Host ******* Finished processing $currentProjectName
    }
}

Export-ModuleMember NuGet-RestorePackagesInAllProjects

文件 init.ps1

param($installPath, $toolsPath, $package)

Import-Module (Join-Path $toolsPath NuGet-RestorePackagesInAllProjects.psm1)

Enable-PackageRestore

NuGet-RestorePackagesInAllProjects

该软件包的.nuspec文件

<?xml version="1.0" encoding="utf-16"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
    <metadata>
        <id>NuGet-RestorePackagesInAllProjects</id>
        <version>0.5.0</version>
        <title>Custom NuGet Package Restore</title>
        <authors>Me (c) 2012</authors>
        <owners />
        <requireLicenseAcceptance>false</requireLicenseAcceptance>
        <description>Restore all packages in a given solution from packages.config in each project. For each packages.config, uninstalls all packages and then re-install them again from the sources specified in the script.</description>
        <dependencies>
            <dependency id="NuGetPowerTools" />
        </dependencies>
    </metadata>
    <files>
        <file src="init.ps1" target="tools\init.ps1" />
        <file src="NuGet-RestorePackagesInAllProjects.psm1" target="tools\NuGet-RestorePackagesInAllProjects.psm1" />
    </files>
</package>

0

如果您只是从NuGet中删除并重新安装包,版本属性将指向最新版本。

在从NuGet重新安装之前,您可能需要手动编辑packages.config以删除旧引用(因为我最近遇到了这样的情况,即NuGet认为我已经安装了旧包,因此不允许我安装新包)。


是的,它可以,但OP提到是否有任何软件包语法配置来获取最新版本。一旦完成了这个步骤,使用存储库的其他人将能够使用软件包还原来获取项目。 - dougajmcdonald
1
所涉及的项目是我为我们内部项目制作的自定义解决方案模板的一部分。想法是开发人员可以从模板创建新的解决方案,并获得整个结构(包括对基础设施包的引用)并准备就绪。我没有问题编写一个自定义脚本,以便在需要时检查解决方案中的所有项目并重新安装包,但告诉人们开始在模板中胡乱更改以使其工作是错误的... - Ilya Ayzenshtok
无论如何,我发现NuGet不尝试从存储库获取最新包非常奇怪。 - Ilya Ayzenshtok
个人而言,我本来不希望NuGet获取最新的软件包,因为这可能会在您的软件中引入错误,其中一个开发人员可能会遇到,而另一个则不会。上一次我使用软件包还原是为了确保我使用的是特定版本的软件包,以确保我们不会在团队中创建差异或强制人们每次打开项目时都获取新的软件包。 - dougajmcdonald
2
包还原的目的是恢复软件包,而不是更新它们。NuGet 兼容的工作流程应该是保留旧的软件包,以便 NuGet 可以恢复它们,然后再运行更新操作。 - Jim Counts
2
如果您有包含NuGet软件包的解决方案模板,我建议您添加一个README文件来解释包含哪些软件包,并提醒开发人员可以运行“Update-Package”命令以获取最新版本。 - Kiliman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接