26
Vote

Remove solution directory path from hintpath and solutiondir tags

description

If I import an existent project to my solution and install a Nuget package with package restore, the project contains a solutiondir tag similar to:
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\path\to\remote\solution\</SolutionDir>
and an hintpath like:
<HintPath>..\path\to\remote\solution\packages\package_name**\myLib.dll</HintPath>

in my opinion this is very bad. Why Nuget can't put "..\" as $(solutiondir) parameter and "$(solutiondir)\packages\" as hintpath? with these modifications and package restore we are able to create the package folder where needed. or not?

comments

drewmiller wrote May 7, 2012 at 9:57 PM

@ManuelSpezzani, The SolutionDir variable is only available in Visual Studio. Many people also build the solution outside of VS via MSBuild, so we can't safely use it a hint path. We add SolutionDir to projects when we enabled Package Restore, but not all projects (in fact, very few yet) use package restore, so we can't depend on that.

We could consider adding SolutionDir when you install a package as we do in package restore. We'd have to consider the consequences.

(For history of this discussion, see http://nuget.codeplex.com/workitem/1923.)

drewmiller wrote May 7, 2012 at 10:00 PM

Sorry, I should have been more clear, that the code that adds the hint path isn't related to package restore, hence the seeming disconnect. In addition to considering generally adding SolutionDir, I guess we could at least make adding a NuGet reference smart enough to see if it is available, or at least if package restore is enabled.

jagui wrote Aug 9, 2012 at 11:03 AM

In my case I have two solutions, one contains some common libraries, the other one contains more projects as well as the common libraries. If the hint path was set to $(SolutionDir)\packages\package the build will work regardless of the solution I'm building. The difference is that in one case the packages are downloaded to the common libraries's solution packages folder and on the other are copied to the application's solution

DanHarman wrote Mar 27, 2013 at 2:07 AM

This is actually a bit of a horror when using tfs online build preview. I had moved a few projects into sub directories and not realised how dependent the package restore feature was on the hintpath. I hadn't updated the hint path when nesting and was getting unable to resolve package reference problems, but only on the build server, it was all fine on my machine.

It would be nice if we could make the project files less fragile when using package restore. Its never good when you have to start hacking at csproj just to relocate a project.

dotnetjunky wrote Jun 25, 2013 at 7:18 PM

dotnetjunky wrote Sep 14, 2013 at 12:27 AM

The MS-Build based package restore model has been replaced with the new restore implementation model in 2.7. We recommend everyone to move to the new model. As a result, we will not fix this bug.

** Closed by dotnetjunky 09/13/2013 4:27PM

philwray wrote Oct 3, 2013 at 11:54 AM

This has been closed - but I don't see how the new package restore model fixes this.
When I reference the same project from two different solutions in two different directories the package restore still fails as the hintpath in the csproj still contains a relative path to the project.

Can you clarify whether there is now a workaround in the new restore model please?

deepakverma wrote Oct 3, 2013 at 7:57 PM

re-opening as per comment from @philwray

RanjiniM wrote Oct 28, 2013 at 9:26 PM

When verifying the fix for this bug, please verify https://nuget.codeplex.com/workitem/2999 also. Closing https://nuget.codeplex.com/workitem/2999 as a dupe of this one.

feiling wrote Dec 6, 2013 at 6:55 PM

We'll revisit this in 2.9.

JeffHandley wrote Dec 18, 2013 at 2:28 AM

Instead of using $(SolutionDir), we'll want to use $(NuGetRepositoryPath).

Here's my proposal:
  1. When NuGet is adding a reference, it will check to see if the $(NuGetRepositoryPath) property property exists.
  2. If the property does exist, the hint path will be rooted on $(NuGetRepositoryPath) instead of a static value
  3. If the property does not exist, the hint path will still work like it does today
This would allow advanced users to create the $(NuGetRepositoryPath) property in their projects to make it dynamic, while mitigating the risk of the feature. Additionally, nuget.exe config repositoryPath -aspath can be used from the command-line to get the effective repository path for a project. This output can be used as a command-line argument when building the project file through msbuild, which would then respect the dynamic hint paths, even allowing them to be changed at build time when the project is built under multiple solutions.

Andy99Andy99 wrote May 16 at 3:38 PM

There appears to be duplicate issues 3897 and 4061.

AVee00 wrote Jun 13 at 4:32 PM

I like JeffHandley proposal, but I'd change 3 to:
If the property does not exist, create it and set it to the path currently used.

This should behave the same, but has the benefit of always having a $(NuGetRepositoryPath) in the project. That would allow builds outside Visual Studio to just assume the property exists, which ensures all tooling (build servers mainly) will just work against a project which was created in Visual Studio without any manual editing of the project file. It would also allow a build server to use a single $(NuGetRepositoryPath) for lots of projects out of the box.

philn5d wrote Jul 2 at 7:44 PM

Building off JeffHandley's suggestion -


I put this in a project file and it works for me with solution build and msbuild from command line:

<Choose>
<When  Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">
    <PropertyGroup>
        <NuGetRepositoryPath>..\</NuGetRepositoryPath>
    </PropertyGroup> 
</When>

<When Condition="$(SolutionDir) != '' And $(SolutionDir) != '*Undefined*'">
    <PropertyGroup>
        <NuGetRepositoryPath >$(SolutionDir)</NuGetRepositoryPath>
    </PropertyGroup> 
</When>
</Choose>
<Reference Include="AutoMapper.Net4">
  <HintPath>$(NuGetRepositoryPath)packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll</HintPath>
</Reference>

JeffHandley wrote Jul 2 at 10:36 PM

$(NuGetRepositoryPath) should include the "packages" part too. If you set the RepositoryPath config setting, it overrides the "packages" folder name.

Micah071381 wrote Aug 14 at 9:31 PM

Perhaps this is too confrontational of a change, but I would propose that if $(SolutionDir) is not set then the path is set to $(ProjectDir) rather than ..\. The reason for this is because a build with no $(SolutionDir) set likely means that a project is being built directly rather than as part of a solution build. I do not believe it is safe to assume that a project built in isolation is sitting one-level down from a solution, or even that the parent directory exists and has write-access.

Take for example a project in source control that has the csproj and source files in the root directory. The build server will check out that repository and give the build process enough permission to write to the checkout directory. In the current scenario though, NuGet will try to find/create the packages directory a level up, which it does not have access to. This is especially likely in a shared-build server scenario where the builder process doesn't have full access to the file system. Another contrived example would be a csproj file in a root directory, or building off of a file share that points directly at the project directory. In all of these cases, NuGet can't create the packages folder in the parent directory.

Not taking into account backwards compatibility, my suggestion would be to have NuGet default to either $(SolutionDir)\packages or $(ProjectDir)\packages (if SolutionDir doesn't resolve).

Such a solution will be far more robust going forward than the current ..\ solution and I believe that as NuGet continues to grow, robustness in the face of complex scenarios is more and more important.

As far as backwards compatibility, perhaps use $(SolutionDir)\packages if it exists, then fallback to ..\packages if it exists && the process has write access, then fallback to $(ProjectDir). This would cause a project built in isolation to use ..\packages if something previously built and put packages in ..\packages but it will use the more reasonable $(ProjectDir) for clean/new isolated builds.

To further exemplify the problem of the current ..\ solution, if you create a project at C:\foo\bar\project\project.csproj and have a solution that loads that project sitting in C:\baz\solution.sln, adding a NuGet package to the project while the solution is open will (I believe) create a hint path of ..\..\baz\packages. This hint path will critically fail if someone checks the project out into C:\project\project.csproj because C:\project\..\..\baz\packages is an invalid path (tries to navigate up past C:).

While all of these examples may seem contrived, their purpose is to show that the current solution of using project relative path to solution-shared packages folder does not work for complex projects. MSBuild was designed in such a way that the solutions and projects are not tightly coupled, NuGet should follow a system that also decouples the two of them from each other.

Micah071381 wrote Aug 15 at 5:11 PM

An additional scenario where the ..\packages solution doesn't work. If you have two solution files that both refer to the same project. $(SolutionDir) will work in this scenario but depending on the solution that resulted in the NuGet package getting added, building the project in isolation may fail. This is particularly true if someone has a monolithic solution with all projects in a large architecture in it while someone else has solutions per project. If the monolithic solution is the one open when the package is added, then the packages hint path may be very far away from the project.