Setting up a common nuget packages folder for all solutions when some projects are included in multiple solutions
I have a similar situation with external and internal package sources with projects referenced in more than one solution. I just got this working with one of our code bases today and it seems to be working with the developer workstations and our build server. The below process has this scenario in mind (although it shouldn't be hard to adapt to have the common packages folder else where).
- Codebase
- Project A
- Project B
- Project C
- Solutions
- Solution 1
- Solution 2
- Solution 3
- Packages (this is the common one shared by all solutions)
Updated answer as of NuGet 3.5.0.1484 with Visual Studio 2015 Update 3
This process is a bit easier now than when I originally tackled this and thought it was time to update this. In general, the process is the same just with less steps. The result is a process that solves or provides the following:
- Everything that needs to be commited to source code control is visible and tracked in the solution
- Installing new packages or updating packages using the Package Manager in Visual Studio will use the correct repository path
- After the initial configuration, no hacking of .csproj files
- No modifications of developer workstation (Code is build ready on check out)
There are some potential downsides to be aware of (I haven't experience them yet, YMMV). See Benol's answer and comments below.
Add NuGet.Config
You will want to create a NuGet.Config file in the root of the \Solutions\ folder. Make sure this is a UTF-8 encoded file that you create, if you are not sure how to do this, use Visual Studio's File->New->File menu and then pick the XML File template. Add to NuGet.Config the following:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="repositoryPath" value="$\..\Packages" />
</config>
</configuration>
For the repositoryPath setting, you can specify an absolute path or relative path (recommended) using the $ token. The $ token is based on where the NuGet.Config is located (The $ token is actually relative to one level below the location of the NuGet.Config). So, if I have \Solutions\NuGet.Config and I want \Solutions\Packages I would need to specify $\..\Packages as the value.
Next, you will want to add a Solution Folder to you solution called something like "NuGet" (Right-Click on your solution, Add->New Solution Folder). Solution Folders are virtual folders that only exist in the Visual Studio solution and will not create an actual folder on the drive (and you can reference files from anywhere). Right-Click on your "NuGet" solution folder and then Add->Existing Item and select \Solutions\NuGet.Config.
The reason we are doing this is so that it is visible in the solution and should help with making sure it is properly committed to your source code control. You may want to do this step for each solution in your codebase that is participating with your shared projects.
By placing the NuGet.Config file in \Solutions\ above any .sln files, we are taking advantage of the fact that NuGet will recursively navigate the folder structure upwards from the "current working directory" looking for a NuGet.Config file to use. The "current working directory" means a couple of different things here, one is the execution path of NuGet.exe and the other is the location of the .sln file.
Switching over your packages folder
First, I highly recommend you go through each of your solution folders and delete any \Packages\ folders that exist (you'll need to close Visual Studio first). This makes it easier to see where NuGet is placing your newly configured \Packages\ folder and ensures that any links to wrong \Packages\ folder will fail and can then be fixed.
Open your solution in Visual Studio and kick off a Rebuild All. Ignore all of the build errors you will receive, this is expected at this point. This should kick off the NuGet package restore feature at the start of the build process however. Verify that your \Solutions\Packages\ folder has been created in the spot you want. If it hasn't, review your configuration.
Now, for each project in your solution you will want to:
- Right-click on the project and select Unload Project
- Right-click on the project and select Edit your-xxx.csproj
- Find any references to \packages\ and update them to the new location.
- Most of these will be <HintPath> references, but not all of them. For example, WebGrease and Microsoft.Bcl.Build will have separate path settings that will need to be updated.
- Save the .csproj and then Right-click on the project and select Reload Project
Once all of your .csproj files have been updated, kick off another Rebuild All and you should have no more build errors about missing references. At this point you are done, and now have NuGet configured to use a shared Packages folder.
As of NuGet 2.7.1 (2.7.40906.75) with VStudio 2012
First off the thing to keep in mind is that nuget.config does not control all of the path settings in the nuget package system. This was particularly confusing to figure out. Specifically, the issue is that msbuild and Visual Studio (calling msbuild) do not use the path in nuget.config but rather are overriding it in the nuget.targets file.
Environment Preparation
First, I would go through your solution's folder and remove all \packages\ folders that exist. This will help ensure that all packages are visibly installing into the correct folder and to help discover any bad path references throughout your solutions. Next, I would make sure you have the latest nuget Visual Studio extension installed. I would also make sure you have the latest nuget.exe installed into each solution. Open a command prompt and go into each $(SolutionDir)\ .nuget\ folder and execute the following command:
nuget update -self
Setting common package folder path for NuGet
Open each $(SolutionDir)\ .nuget\NuGet.Config and add the following inside the <configuration> section:
<config>
<add key="repositorypath" value="$\..\..\..\Packages" />
</config>
Note: You can use an absolute path or a relative path. Keep in mind, if you are using a relative path with $ that it is relative to one level below the location of the NuGet.Config (believe this is a bug).
Setting common package folder path for MSBuild and Visual Studio
Open each $(SolutionDir)\ .nuget\NuGet.targets and modify the following section (note that for non-Windows there is another section below it):
<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>
Update PackagesDir to be
<PackagesDir>$([System.IO.Path]::GetFullPath("$(SolutionDir)\..\Packages"))</PackagesDir>
Note: The GetFullPath will resolve our relative path into an absolute path.
Restoring all of the nuget packages into common folder
Open a command prompt and goto each $(SolutionDir)\ .nuget and execute the following command:
nuget restore ..\YourSolution.sln
At this point, you should have a single \packages\ folder in your common location and none within any of your solution folders. If not, then verify your paths.
Fixing project references
Open every .csproj file in a text editor and find any references to \packages and update them to the correct path. Most of these will be <HintPath> references, but not all of them. For example, WebGrease and Microsoft.Bcl.Build will have separate path settings that will need to be updated.
Build your solution
Open your solution in Visual Studio and kick off a build. If it complains about missing packages that need to be restored, don't assume that the package is missing and needs to be restored (error can be misleading). It could be a bad path in one of your .csproj files. Check that first before restoring the package.
Have a build error about missing packages?
If you have already verified that the paths in your .csproj files are correct, then you have two options to try. If this is the result of updating your code from source code control then you can try checking out a clean copy and then building that. This worked for one of our developers and I think there was an artifact in the .suo file or something similar. The other option is to manually force a package restore using the command line in the .nuget folder of the solution in question:
nuget restore ..\YourSolution.sln
Instead of setting common package location for all projects, it is also possible to change HintPath in project as follow:
<HintPath>$(SolutionDir)\packages\EntityFramework.6.1.0\lib\net40\EntityFramework.dll</HintPath>
In most cases there in shared project will be only few packages, so you can easily change it.
I think it is better solution, when you branching code, when setting common repo, you must change relative path, in this solution you don't need to do this.
My experience trying this with the latest version of NuGet (2.7) and VS2012:
- Delete the .nuget folder (on disk and in solution)
- Put a NuGet.Config file in a common parent folder of all solutions
- Delete any existing packages folders
- Go through all csproj files and change HintPaths to point to the new location
- Profit
In my case, I wanted to put all packages in .packages
, so my NuGet.Config looked like below.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<config>
<add key="repositorypath" value=".packages" />
</config>
</configuration>
Note that there are a few 'strange' things that can happen, but I think they're bearable:
- If you 'Update' a package from one solution, it will promptly delete the older version from your packages folder (it can't know whether you have another solution which is pointing there). This doesn't bother me greatly, as the other solution will just restore when required.
- If you try to add package from right-click-on-solution, if the package is already present in another solution, it will see that it's there and show you the 'green tick' instead of the 'install' button. I usually install from right-click-on-project, so this doesn't bother me at all.
Disclaimer: I just tried this today, I don't have any long term experience to back it up!