Home: Allow an upper limit Version for ProjectReference references in nupkg from `dotnet pack` to support semver

Created on 10 Jul 2017  Â·  29Comments  Â·  Source: NuGet/Home

_From @csMACnz on July 10, 2017 10:20_

It would be good to be able to support Semantic Versioning from a csproj ProjectReference, like you can with PackageReference. To do this, upper version limits in nuget packages help a lot. You cannot currently do this with ProjectReferences and dotnet pack.

Steps to reproduce

  • Create two projects, MyReferencedPackage, and MyNewPackage.
  • Add version information for both packages (e.g. version 1.2.3)
  • Reference MyReferencedPackage from MyNewPackage using a ProjectReference attribute.

    • <ProjectReference Include="..\MyReferencedPackage\MyReferencedPackage.csproj" />

  • _(modify the reference above somehow yet to be defined)_
  • run dotnet pack on both projects.

Expected behavior

project version in nupkg has a version range (e.g. MyReferencedPackage (≥1.2.3 && < 2.0.0))

Actual behavior

project version in nupkg has the built packages version (e.g. MyReferencedPackage (≥1.2.3))

I'm not too worried on the implementation detail of what the xml should look like, but as a for instance:

<ProjectReference Include="..\MyReferencedPackage\MyReferencedPackage.csproj" >
    <MaximumVersion Inclusive="false">2</MaximumVersion>
</ProjectReference>

To produce the reference from the example above of MyReferencedPackage (≥1.2.3 && < 2.0.0)
(Or if Inclusive is true, then MyReferencedPackage (≥1.2.3 && ≤ 2.0.0))

_Copied from original issue: dotnet/cli#7113_

Pack Restore Backlog 2 Feature

Most helpful comment

Any update on this?

All 29 comments

@rohit21agrawal Can you please take a peek and add appropriate labels/milestone?

This is something that I would like to see happen as well. Currently, PackageReferences support the full NuGet interval notation, but ProjectReferences don't.

Because of this, the only way to have packages with the intended version range is to fall back to specifying a manually created nuspec, negating most of the benefits of the new project-based packaging.

@emgarten do you think restore can start putting these ranges into the assets file?

@rohit21agrawal If https://github.com/NuGet/Home/issues/4790#issuecomment-337442404 is considered, would it still make sense to be looking to add these to the assets file?

This issue and #4790 are big pain points for me!

aah yes, i forgot about #4790 . let me tackle that first, we'll design this when we are done with that (or while doing that if it makes more sense).

This is relevant for those developing frameworks. If I could specify semver for pack, I could publish an entire framework worth of libraries in a single command.

dotnet msbuild /t:pack /p:Version=[0.0.0-dev]

where 0.0.0-dev is the package version of all framework packages being packed, and [0.0.0-dev] is the package reference version for all project dependencies.

I'm probably oversimplifying this, but this is what I'm after right now.

@rohit21agrawal Now that #4790 is done and this doesn't appear to have been part of it like you suggested it might be, do you have any idea on when this might be prioritized?

@nkolev92 this is the issue I was discussing with you this morning, would love your thoughts on this.

In a similar, but different vein:

On some solutions with several shared components which are developed somewhat independently and reused among various other solutions, in development we use floating versions (e.g. "2.0.100.*") against a local packages directory and internal NuGet server (local packages increment from 5000 up, build server goes from 0-4999) when restoring packages to ensure we use the latest local build first, otherwise the latest from the build server. Build chains use the explicit version from their build chain dependencies when restoring so they aren't particularly impacted by this design; this is primarily employed in the developer experience.

We have found that it would be really nice to be able to use the same floating version logic in from PackageReference Restore transitively for these projects via the Pack-generated nuspec dependencies (even if "take the latest version" was a feature that projects needed to opt-in to). In order to ensure we restore latest we must specify every transitive reference explicitly, which then make them all non-transitive dependencies. The generated nuspecs always resolve to the exact version that was resolved prior to build and the other supported version range functionality always takes the lowest matching version which is the exact opposite of what we want for development purposes against our own packages.

As an aside, it seems with older project formats Visual Studio 2017 still complains about using MSBuild variables for PackageReference versions, which is how we currently control a lot of this in a shared manner. This is just an added inconvenience as we have to rely on other tools to call nuget/msbuild restore via "command line" even while operating within the IDE. This issue appears to go away when all projects in a solution use the new format and we are able to handle most of this issue as long as we always build every solution that generates nupkgs in dependency order, even if it hasn't changed (otherwise some projects/packages report version conflicts).

We'd find this issue very useful too. We version all the projects in a solution independently and we'd like to be able to specify both a lower version and especially an upper version for project references else it doesn't work well with semantic versioning

If not that, then a way of picking up local projects in the solution via a ProjectReference would be good

Is there currently any way to specify exact version = instead of default >= for the project reference?

@jainaashish @nkolev92 @dsplaisted @nguerrera @livarcocc @Pilchie - we should likely discuss this request in a Monday sync.

For us the ideal config would be just a flag to use SemVer from the current version and then the referencing project wouldn't have to change if the package major version gets bumped. So something like:

<ProjectReference Include="..\MyReferencedPackage\MyReferencedPackage.csproj" MaxVersion="SemVer" />

would cause it to pick the current version from the referenced project and pick the next major version as the exclusive upper limit

We have a similar problem for our versioning: we are building multiple packages using different builds for the same repo.

Projects are referencing each other using ProjectReference, and our build system set the third part of the version number based on azure devops BuildID. We a trying to respect SemVer, in the sense that we update the minor & major version number when breaking or non trivial changes are detected.

By default, the pack command generate dependencies using the three parts of the version (>=1.0.0). The problem in our case is that the "1.0.0" version do not exists by itself, only 1.0.x where x is an arbitrary build number. When building projects, nuget complains about non existing version (warning) : "NU1603: xxx 1.9.52870 depends on yyyy (>= 1.8.0) but yyyy 1.8.0 was not found. An approximate best match of yyyy 1.8.52292 was resolved."

Example:

  • ProjectA (1.0.0) references ProjectB (1.1.0) using ProjectReference
  • We have one build for each project. We clone the entire solution and keep ProjectReference as-is.
  • When building a project, a task updates the .csproj for the project being built to set the third part of the version with the unique buildID. Ex: when building ProjectB we set "1.1.0" => "1.1.5123" ; when building ProjectA, "1.0.0" becomes "1.0.5124" but ProjectB version is not modified.
  • We build the package of the project using msbuild /t:pack.

When editing a nuspec file by hand, I am able to define a package dependency as >=1.0.*. I tried overriding the GetPackageVersionDependsOn target to force the PackageVersion to "1.0", but a padding zero is added in the final nuspec (I think related to the parsing used). We can't define a floating PackageVersion, because it can't be parsed as a valid version by PackTask and raise an error.

I think the solution proposed in #7213 could be used in our case.

Or instead of customizing the ProjectReference element for each project with a ProjectReference, maybe we could allow the dependency project to define how the dependency version range should be generated ? By allowing the PackageVersion to be a range instead of only a version number, or creating a new Property used to define the version range ?

Allowing the PackageVersion to be a range does not seems to be a good idea, as it's also used when building the project itself.

Adding a new property PackageDependencyVersion (defaulting to PackageVersion when not set), and updating the parsing to first try parsing the value as a range, then as a version could allow this usage.

Also, defining a new Property in the dependency project can allow this property to use other properties from the project itself (Version, PackageVersion, etc.) to generate its value: <PackageDependencyRange>[$(VersionPrefix).*,)</PackageDependencyRange>.

Perhaps both solution could be set: first using a new ProjectReference metadata, then using the referenced project properties?

Ultimately, what we want is the possibility to define the dependency version range as "1.8.*" instead of "1.8.0" in the project (ProjectB in my case).

Edit: I just created a small POC with one solution.

Any update on this?

@tibel helped out with a change that would enable some extra customization at pack time even if it does not address the core problem here.

@nkolev92 Can you provide an example of how to take advantage of the customization enabled by the PR?

@tibel added a test there:

https://github.com/NuGet/NuGet.Client/blob/66a01b3c4df37bcf567b072443629b558241a7bd/test/NuGet.Core.FuncTests/Dotnet.Integration.Test/PackCommandTests.cs#L665-L676

Specifically an example target he wrote is:

<Target Name="_ExactProjectReferencesVersion" AfterTargets="_GetProjectReferenceVersions">
    <ItemGroup>
      <_ProjectReferencesWithExactVersions Include="@(_ProjectReferencesWithVersions)">
        <ProjectVersion>[%(_ProjectReferencesWithVersions.ProjectVersion)]</ProjectVersion>
      </_ProjectReferencesWithExactVersions>
    </ItemGroup>
    <ItemGroup>
      <_ProjectReferencesWithVersions Remove="@(_ProjectReferencesWithVersions)" />
      <_ProjectReferencesWithVersions Include="@(_ProjectReferencesWithExactVersions)" />
    </ItemGroup>
  </Target>

@nkolev92 I saw that, but it's not clear to me how that helps for the scenario in this issue. How would I use that to specify a version range on a project reference for example?

Pack runs the _GetProjectVersion target on every direct project reference.

https://github.com/NuGet/NuGet.Client/blob/66a01b3c4df37bcf567b072443629b558241a7bd/src/NuGet.Core/NuGet.Build.Tasks.Pack/NuGet.Build.Tasks.Pack.targets#L305-L315 and puts that in an item _ProjectReferencesWithVersions that later gets consumed.

That item would have a ProjectVersion metadata which would be something like 1.0.0.

The above target switches them out (runs after _GetProjectReferenceVersions).

It's not an easy workaround, but something that can be used until this is fixed.
As always keep in mind workarounds like this might be broken when the feature talked about in this issue is implemented.

This would be helpful to for us as well. Currently we are still manually maintaining nuspecs because of this limitation,

@nkolev92 the target you posted leads to a build error for me in VS:

Severity    Code    Description Project File    Line    Suppression State
Error       '[1.5.0]' is not a valid version string.
Parameter name: value   MyProject   C:\Program Files\dotnet\sdk\3.1.100\Sdks\NuGet.Build.Tasks.Pack\buildCrossTargeting\NuGet.Build.Tasks.Pack.targets  198 

@madelson

You are using the 3.1.100 SDK, the change made by @tibel is in the 3.1.200 SDK which is still in previews.

@nkolev92 same issue for me using 3.1.201 SDK.

C:\Program Files\dotnet\sdk\3.1.201\Sdks\NuGet.Build.Tasks.Pack\buildCrossTargeting\NuGet.Build.Tasks.Pack.targets(198,5):
 error : '[7.5.1]' is not a valid version string. (Parameter 'value')

We badly need this. Currenty we are patching our packages after they have been packed by NuGet.

For anyone looking to use the above custom target workaround with a version range instead of bounding to a single version, here's a variant I ended up using:

  <Target Name="_ExactProjectReferencesVersion" AfterTargets="_GetProjectReferenceVersions">
    <ItemGroup>
      <_ProjectReferencesWithExactVersions Include="@(_ProjectReferencesWithVersions)">
        <ProjectVersion>[%(_ProjectReferencesWithVersions.ProjectVersion), $([MSBuild]::Add($([System.Text.RegularExpressions.Regex]::Match('%(_ProjectReferencesWithVersions.ProjectVersion)', '^\d+').Value), '1')))</ProjectVersion>
      </_ProjectReferencesWithExactVersions>
    </ItemGroup>
    <ItemGroup>
      <_ProjectReferencesWithVersions Remove="@(_ProjectReferencesWithVersions)" />
      <_ProjectReferencesWithVersions Include="@(_ProjectReferencesWithExactVersions)" />
    </ItemGroup>
  </Target>

This results in ranges like [2.5.1, 3)

@madelson I am intrested in using "version range" work around that you posted.

But I have not been able to undersatnd how to use yours or the one mentioned above.

Is this a post step after dot net pack?
Or is this somthing that is passed in with dotnet pack?

Could you place in sample command line of how you would use that?

Thank you.

@msdickinson this is an MSBuild target which you'd either want to add to or import from each package project's csproj file. This runs as part of the package generation process if you're using GeneratePackageOnBuild. See https://github.com/madelson/DistributedLock/blob/master/DistributedLock.Azure/DistributedLock.Azure.csproj#L53 for the file where I'm importing this.

Thank you. I was able to get the target nkolev92 posted and yours to work.

This was a huge help.

Was this page helpful?
0 / 5 - 0 ratings