为所有解决方案设置共用的NuGet软件包文件夹,当一些项目被包含在多个解决方案中时。

153

我一直在使用NuGet从外部和内部包源检索包,这非常方便。但是我意识到默认情况下包会按解决方案存储,当某些具有NuGet引用的项目包含在多个解决方案中时,这非常令人沮丧。然后引用将更改为其他解决方案包文件夹,这实际上可能对另一个开发人员或构建机器不可用。

我已经看到了一些方法可以指出通用的软件包位置(也许是在项目根级别,我们正在使用TFS源控制),这是通过NuGet的2.1版本实现的,详见发布说明。我正在使用NuGet v2.7

但是我尝试添加nuget.config文件,但没有看到任何效果。软件包仍然存储在解决方案文件夹中。我错过了什么吗? 似乎有不同的xml节点结构要添加到nuget.config文件中,这取决于谁回答了这个问题: Schwarzie在另一个Stackoverflow线程上提出了建议:

<settings>
  <repositoryPath>..\..\[relative or absolute path]</repositoryPath>
</settings>
NuGet 2.1 的发布说明(参见上面的链接)建议使用此格式:
<configuration>
  <config>
    <add key="repositoryPath" value="..\..\[relative or absolute path]" />
  </config>
</configuration>

我不知道这些中的哪一个会最终起作用,或者任何一个还是两个都可以。我已经在解决方案级别上尝试了它们两个。

nuget.config文件必须放置在TFS项目根目录级别上吗?或者必须放在解决方案目录中?看来NuGet按照一定的顺序读取和应用这些文件中的设置,因此在多个级别添加它们是有意义的,其中解决方案级别上的nuget.config文件将覆盖TFS项目根级别上的文件。请问这是否可以澄清?

在引用这些内容之前,我需要删除所有已安装的包吗?如果有人能够提供逐步说明,以从特定于解决方案的nuget使用移动到通用包文件夹为好,那就太好了,这样属于几个解决方案的项目就可以找到其所需的nuget包了。


3
我怀疑你问题的简短答案(在下面 Vermis 的回答中)是,你在相对路径前面缺少了 $。关于 NuGet.Config 文件的问题,答案在这里。它首先查找 .nuget,然后查找所有父目录,然后在您的 AppData 中查找“全局”文件:然后以相反的顺序应用它们(无论意味着什么)。 - Benjol
这似乎很困难。有一个叫做Paket的工具可能是解决这个问题的方法:http://fsprojects.github.io/Paket/ - Tuomas Hietanen
晚来的评论。只是想补充一下,如果您在更改开始时正在运行Visual Studio,则需要重新启动它,因为似乎在VS启动期间会读取nuget.config文件。此外,我没有使用$也没有问题,只是不要以反斜杠开头。 - MBWise
14个回答

155

我有一个类似的情况,涉及到内部和外部包源以及被多个解决方案引用的项目。今天我刚刚在其中一份代码库中做到了这一点,并且似乎在开发人员工作站和构建服务器上都可以工作。下面的过程考虑到了这种情况(尽管将公共包文件夹放在其他位置也不难)。

  • 代码库
    • 项目A
    • 项目B
    • 项目C
    • 解决方案
      • 解决方案1
      • 解决方案2
      • 解决方案3
      • Packages(这是所有解决方案共享的公共文件夹)

使用Visual Studio 2015更新3和NuGet 3.5.0.1484进行更新

现在相比我最初处理此问题时,这个过程要容易些,我认为是时候更新一下了。总的来说,过程是相同的,但步骤更少了。结果是一个可以解决或提供以下功能的工具:

  • 需要提交到源代码控制的所有内容在解决方案中都可见并跟踪
  • 使用Visual Studio的包管理器安装新包或更新包将使用正确的存储库路径
  • 在初始配置之后,不需要修改.csproj文件
  • 不需要修改开发人员工作站(代码可在签出时构建)

还有一些潜在的缺点需要注意(我还没有经历过,可能因人而异)。请参阅Benol的答案和下面的评论。

添加NuGet.Config

您将希望在\Solutions\文件夹的根目录中创建一个NuGet.Config文件。确保这是一个UTF-8编码的文件,如果不确定如何操作,请使用Visual Studio的“文件”->“新建”->“文件”菜单,然后选择XML文件模板。在NuGet.Config中添加以下内容:

<?xml version="1.0" encoding="utf-8"?>
<configuration>  
  <config>
    <add key="repositoryPath" value="$\..\Packages" />
  </config>
</configuration>

对于 repositoryPath 设置,可以使用 $ 符号指定绝对路径或相对路径(建议使用相对路径)。$ 符号是基于 NuGet.Config 的位置(实际上 $ 符号相对于 NuGet.Config 位置的一级目录下)。因此,如果我有 \Solutions\NuGet.Config 并且我想要 \Solutions\Packages,我需要将 $\..\Packages 指定为值。

接下来,您需要在解决方案中添加一个名为 "NuGet" 的解决方案文件夹(右键单击解决方案,选择-> 新建解决方案文件夹)。 解决方案文件夹是仅存在于 Visual Studio 解决方案中而不会在驱动器上创建实际文件夹的虚拟文件夹(您可以从任何地方引用文件)。右键单击“NuGet”解决方案文件夹,然后选择添加->现有项目,选择\Solutions\NuGet.Config。

我们这样做的原因是为了使其在解决方案中可见,并应有助于确保它被正确提交到源代码控制。您可能需要为与共享项目参与的代码库中的每个解决方案执行此步骤。

通过将 NuGet.Config 文件放置在任何 .sln 文件之上的 \Solutions\ 中,我们利用 NuGet 将从“当前工作目录”向上递归导航文件夹结构,寻找要使用的 NuGet.Config 文件的事实。 “当前工作目录”在这里有几种不同的含义,其中一种是 NuGet.exe 的执行路径,另一种是 .sln 文件的位置。

切换您的 packages 文件夹

首先,我强烈建议您浏览每个解决方案文件夹并删除任何存在的 \Packages\ 文件夹(您需要先关闭 Visual Studio)。这样可以更容易地看到 NuGet 将您新配置的 \Packages\ 文件夹放置在哪里,并确保任何链接到错误 \Packages\ 文件夹的链接将失败,然后可以进行修复。

在 Visual Studio 中打开您的解决方案并启动 Rebuild All。您将收到所有构建错误,但这在此时是预期的。这应该会在构建过程开始时启动 NuGet 包恢复功能。验证您的 \Solutions\Packages\ 文件夹是否已创建在所需位置。如果没有,请重新查看您的配置。

现在,对于您解决方案中的每个项目,您将需要:

  1. 右键单击该项目并选择 Unload Project
  2. 右键单击该项目并选择编辑 your-xxx.csproj
  3. 查找任何引用 \packages\ 的内容,并将其更新为新位置。
    • 大多数这些将是 <HintPath> 引用,但不是所有引用。例如,WebGrease 和 Microsoft.Bcl.Build 将具有需要更新的单独路径设置。
  4. 保存 .csproj,然后右键单击该项目并选择 Reload Project

所有的.csproj文件都更新完毕后,再次进行Rebuild All操作,就不会再出现关于缺少引用的构建错误。此时你已经完成了,并且现在已经配置好NuGet以使用共享的Packages文件夹。

NuGet 2.7.1(2.7.40906.75)及Visual Studio 2012起

首先要记住的是,nuget.config并不能控制nuget包系统中的所有路径设置。这一点特别令人困惑。具体来说,问题在于msbuild和Visual Studio(调用msbuild)没有使用nuget.config中的路径,而是在nuget.targets文件中进行了覆盖。

环境准备

首先,我会遍历你解决方案的文件夹,并删除所有已存在的\packages\文件夹。这将有助于确保所有软件包都能正确地安装到指定文件夹,并帮助发现解决方案中的任何错误路径引用。接下来,确保你已经安装了最新版本的NuGet Visual Studio扩展程序。还要确保你已经在每个解决方案中安装了最新版本的nuget.exe。打开一个命令提示符并进入每个$(SolutionDir)\.nuget\文件夹,执行以下命令:

nuget update -self

为NuGet设置常见的包文件夹路径

打开每个 $(SolutionDir)\.nuget\NuGet.Config 文件,并在 <configuration> 部分中添加以下内容:

<config>
    <add key="repositorypath" value="$\..\..\..\Packages" />
</config>

注意:您可以使用绝对路径或相对路径。请记住,如果您正在使用带有$的相对路径,则它是相对于NuGet.Config位置的下一级(认为这是一个错误)。

为MSBuild和Visual Studio设置常见软件包文件夹路径

打开每个$(SolutionDir)\ .nuget\NuGet.targets文件并修改以下部分(请注意,对于非Windows平台,下面还有另一节):

<PropertyGroup Condition=" '$(OS)' == 'Windows_NT'">
    <!-- Windows specific commands -->
    <NuGetToolsPath>$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))</NuGetToolsPath>
    <PackagesConfig>$([System.IO.Path]::Combine($(ProjectDir), "packages.config"))</PackagesConfig>
    <PackagesDir>$([System.IO.Path]::Combine($(SolutionDir), "packages"))</PackagesDir>
</PropertyGroup>

将PackagesDir更新为

<PackagesDir>$([System.IO.Path]::GetFullPath("$(SolutionDir)\..\Packages"))</PackagesDir>

注意:GetFullPath将相对路径解析为绝对路径。

将所有NuGet包还原到公共文件夹

打开命令提示符并转到每个$(SolutionDir)\ .nuget,并执行以下命令:

nuget restore ..\YourSolution.sln

此时,您应该在公共位置中拥有一个单独的 \packages\ 文件夹,并且不存在于任何解决方案文件夹中。如果没有,请验证您的路径。

修复项目引用

在文本编辑器中打开每个 .csproj 文件并查找对 \packages 的引用,并将其更新为正确的路径。大多数情况下,这些引用将是 <HintPath> 引用,但不是所有引用都是如此。例如,WebGrease 和 Microsoft.Bcl.Build 将具有需要更新的单独路径设置。

构建您的解决方案

在 Visual Studio 中打开您的解决方案并启动构建。如果它抱怨需要恢复缺少的包,请不要假设该包已经丢失并需要进行还原(错误可能会误导)。它可能是您的某个 .csproj 文件中的错误路径。请先检查此问题然后再还原包。

遇到有关缺少包的构建错误?

如果您已经验证了 .csproj 文件中的路径是否正确,则有两种尝试方法。如果这是从源代码控制更新代码的结果,那么您可以尝试签出一个干净的副本,然后构建该副本。这对我们的其中一位开发者起作用了,我认为是 .suo 文件中的某个工件或类似物。另一个选择是在相关解决方案的 .nuget 文件夹中使用命令行手动强制还原包:

nuget restore ..\YourSolution.sln

感谢您对我长篇问题的详细回答。我会尝试这个解决方案并与您联系。 - Mats Isaksson
实际上,刚才我尝试了一下,似乎可以工作(即使没有$符号)。 - Benjol
8
我认为这个答案展示了NuGet的僵化。不得不创建如此复杂、僵化的结构来实现一个简单的概念——“在解决方案之间共享相同的程序集”,说明整个系统设计很差。 - Mick
1
另外一个解决 HintPaths 的方法是,在你更新了 NuGet.config 后,在 Package-Manager 控制台中运行 "Update-Package -reinstall" 命令。这将重新安装所有的包,同时修复它们的 hintpaths。之后,你可能还会有一些残留物(在某些 Silverlight 项目中我还有一些“旧”的目标/构建导入仍然存在)。 - johannes.colmsee
1
@Vermis 如果我为所有解决方案设置一个常见的NuGet包文件夹,那么这对我的Azure DevOps构建会有什么影响? - Pankaj Devrani
显示剩余11条评论

40

不必为所有项目设置共同的包位置,也可以按以下方式更改项目中的HintPath:

<HintPath>$(SolutionDir)\packages\EntityFramework.6.1.0\lib\net40\EntityFramework.dll</HintPath>

在大多数情况下,在共享项目中只会有很少的软件包,因此您可以轻松更改它。

我认为当您分支代码并设置常见存储库时,更好的解决方案是更改相对路径,而在这种解决方案中,您不需要这样做。


2
Adam 是正确的。这是对一个非常愚蠢问题最简单的解决方案。 - lapsus
1
这是一个非常出色的解决方案。它简单明了,不需要重建任何代码结构。 - RredCat
2
据我所知,$(SolutionDir) 仅在通过 VS 进行构建时设置。因此,除非您设置 /p:SolutionDir=path,否则不要直接通过 msbuild.exe 进行构建。 - Lars Nielsen
此处可以自动设置 %APPDATA%\Roaming\NuGet\NuGet.Config。 - Korayem
16
在没有更新软件包并覆盖HintPath的新相对路径之前,这个方法非常有效。但在包含许多软件包的大型解决方案中,这变得相当繁琐。如果不得不运行Update-Package -Reinstall命令,那就更麻烦了。 - gravidThoughts

23

我使用的是带有VS 2013的NuGet版本2.8.50926。您无需使用多个nuget.config文件或使用复杂的目录结构。只需修改此处的默认文件:

%APPDATA%\Roaming\NuGet\NuGet.Config

这里是我的文件内容:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <config>
    <add key="repositoryPath" value="C:\Projects\nugetpackages" />
  </config>
  <activePackageSource>
    <add key="nuget.org" value="https://www.nuget.org/api/v2/" />
  </activePackageSource>
</configuration>

所以所有的包都会放在"C:\Projects\nugetpackages"文件夹中,无论解决方案在哪里。

在所有解决方案中,只需删除现有的“packages”文件夹。然后构建您的解决方案,NuGet将自动恢复您指定的新集中目录中缺失的包。


这绝对是所有解决方案中最简单的,它值得更多的喜爱! - Korayem
2
这确实是目前最简单的解决方案,但你的回答中还缺少一件事。你仍然需要处理每个项目的csproj文件中的HintPath。它们不会自动指向新路径。我的理解正确吗? - Emil
事实上,在我的一些旧项目中,有时会提到“旧”的包文件夹。然而对我来说一切都正常工作,所以我猜全局设置优先。 - Matthieu
对于OSX,请参见https://learn.microsoft.com/en-us/nuget/consume-packages/configuring-nuget-behavior。我也喜欢下面的mklink想法。 - sgtz

22

我在使用最新版本的NuGet (2.7)和VS2012时尝试了这个方法:

  • 删除.nuget文件夹(磁盘上和解决方案中)
  • 在所有解决方案的共同父文件夹中放置一个NuGet.Config文件
  • 删除任何现有的packages文件夹
  • 修改所有csproj文件,将HintPaths指向新位置
  • 获利

在我的情况下,我想把所有包放在.packages中,所以我的NuGet.Config如下:

 <?xml version="1.0" encoding="utf-8"?>
 <configuration>
   <config>
     <add key="repositorypath" value=".packages" />
   </config>
 </configuration>
请注意,有一些“奇怪”的事情可能会发生,但我认为它们是可以承受的:
- 如果您从一个解决方案“更新”软件包,它将立即从您的软件包文件夹中删除旧版本(它无法知道您是否有另一个解决方案指向该文件夹)。这并不会对我造成很大困扰,因为其他解决方案在需要时只需还原。 - 如果您尝试从右键单击解决方案添加软件包,如果该软件包已经存在于另一个解决方案中,它将看到它已经存在并显示“绿色勾号”而不是“安装”按钮。我通常从右键单击项目安装,所以这完全不会影响我。
免责声明:我今天刚试过,没有长期的经验来支持它!

4
这是推荐的做法。旧的msbuild集成方式在接受的答案中执行NuGet还原很麻烦,我可以确认将NuGet.config放置在sln文件旁边对我们使用自动包恢复很有效。但放置在共同的父目录中我无法确认是否可行。 - 9swampy
1
如何将NuGet.Config放置在所有解决方案的公共父文件夹中(在TFS中),以便它们可以引用? 我知道的唯一方法是在每个解决方案下的.nuget文件夹中拥有nuget.config文件;如果“repositorypath value=.packages”,那么它会在.nuget下创建一个子文件夹,例如:C:\TFS\Comp\Ent\SolABC.nuget.packages - 这并不能以干净的方式解决嵌套项目/解决方案问题,也无法在自动化TFS构建上工作。 接受的答案需要一些手工编码工作,加上“repositorypath value=$......\Packages”,如果有不同相对路径的嵌套项目,则无法正常工作。 - hB0
2
适用于Visual Studio 2015和Nuget 3.4.3。 - Huseyin Yagli
它不仅会迅速删除旧的包,还会覆盖并破坏您手动编码到csproj文件中的HintPath。@hB0是正确的。这仅适用于相对简单的解决方案。一旦涉及到具有分支、共享项目等复杂TFS工作区结构,它就无法高效扩展。 - gravidThoughts

8

3

从Visual Studio 2013更新4和Nugget Package Manager版本>2.8.5开始...

在仓库的根目录下创建nuget.config文件。

文件内容:

<configuration>
  <config>
    <add key="repositoryPath" value="packages" />
  </config>
  <packageSources>
    <add key="nuget.org" value="https://www.nuget.org/api/v2/" />
  </packageSources>
</configuration>

这将导致所有包都放在您的nuget.config文件所在的包文件夹中。

现在,您可以使用命令“update-package -reinstall”来进入每个.sln nuget控制台。

如果您有多个位于同一级别的存储库,并希望在它们之间共享相同的包文件夹,请尝试使用向上移动一个文件夹的方法。

 <add key="repositoryPath" value="..\packages" />

但是这样做会导致NuGet包引用的csproj指向一个在你的存储库路径之外的上级文件夹。


3
我已经准备了一个NuGet包,可以将项目中的所有NuGet引用透明地转换为$(SolutionDir)-相对格式。它使用构建时XSLT转换完成,因此您无需手动修改项目文件。您可以自由地更新软件包,不会导致任何问题。 https://www.nuget.org/packages/NugetRelativeRefs 或者,如果您使用Visual Studio 2015 Update 3,您只需按照这里描述的步骤将软件包引用迁移到project.json格式即可:https://oren.codes/2016/02/08/project-json-all-the-things

不幸的是,看起来微软正在逐渐放弃project.json。此外,我很喜欢你的NuGet包。一段时间以前,我使用了NuGetReferenceHintPathRewrite,它基本上做了同样的事情,但方式不同。 - Andrey Borisko

2

只需将/packages硬链接到所需的共享位置。然后,您的项目就不会破坏其他没有特殊包位置的用户。

以管理员身份打开命令提示符并使用以下命令:

mklink /prefix link_path Target_file/folder_path

2

更新我的nuget 2.8.3使用经验。它相对简单。我所做的只是从右键单击解决方案中启用包还原编辑NuGet.Config并添加以下行:

  <config>
    <add key="repositorypath" value="..\Core\Packages" />
  </config>

然后重新构建解决方案,它会将所有包下载到我想要的文件夹并自动更新引用。对于我的其他项目,我也做了同样的操作,只下载增量包并引用现有包。因此,为所有项目设置了一个共享的包存储库。
以下是启用包恢复的逐步过程。 http://blogs.4ward.it/enable-nuget-package-restore-in-visual-studio-and-tfs-2012-rc-to-building-windows-8-metro-apps/

2
对于任何重要的解决方案,上述答案都不够。简单地说,一个具有不同相对路径、分支、共享项目等复杂TFS工作区结构使得单一中央存储库不可能存在。
使用($SolutionDir)是朝着正确方向迈出的一步,但在一个有数百个经常更新的包的代码库中手动编写带有($SolutionDir)的csproj文件将变得非常繁琐(每次更新时,HintPath都会被新的相对路径覆盖)。如果你必须运行Update-Package -Reinstall会发生什么?
有一个很好的解决方案叫做NugetReferenceHintPathRewrite。它可以在构建前自动将($SolutionDir)注入到HintPaths中(而不实际更改csproj文件)。我想它很容易被纳入自动化构建系统中。

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