I have a solution containing several projects. Let's say PackageA and PackageB, where PackageB depends on PackageA with a ProjectReference.
Each project is set to also output a NuGet package on build. This process itself works perfectly but I am unable to specify a package version-range for individual builds.
E.g. I'd like to restrict PackageB to only refer to PackageA version 1.0.* (patch steps).
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup
<TargetFrameworks>netstandard2.0;netcoreapp2.0;net46</TargetFrameworks>
<RootNamespace>PackageB</RootNamespace>
<Company>MyCompany</Company>
<Authors>John Doe</Authors>
<Description>This package depends on a specific version of PackageA.</Description>
<Version>1.1.0</Version>
<Copyright>Copyright © 2018 John Doe</Copyright>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PackageA\PackageA.csproj" />
</ItemGroup>
</Project>
MSBuild seems to ignore any Version="1.0.*" or AllowVersion="1.0.*" arguments within the ProjectReference tag.
Is there a possibility to specify a version range without breaking the ProjectReference or using PackageReference?
Existing NuGet targets don't support this directly. A couple of issues on GitHub (1, 2) requesting this functionality have been open for years. However, with a bit of MSBuild item trickery, I was able to 'extend' ProjectReference with a custom attribute PackageVersion, used as follows:
<!-- MyProject.csproj -->
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
...
<ItemGroup>
<!-- This uses MyOtherProject1 with [1.1.0, 2.0.0) -->
<ProjectReference Include="..\MyOtherProject1\MyOtherProject1.csproj"
PackageVersion="[1.1,2)" />
<!-- This uses MyOtherProject2 with [x] where x is the project version -->
<ProjectReference Include="..\MyOtherProject2\MyOtherProject2.csproj"
PackageVersion="[~]" />
<!-- This uses MyOtherProject3 with [1,x] where x is the project version -->
<ProjectReference Include="..\MyOtherProject3\MyOtherProject3.csproj"
PackageVersion="[1,~]" />
<!-- This uses MyOtherProject4 with [x,2) where x is the project version -->
<ProjectReference Include="..\MyOtherProject4\MyOtherProject4.csproj"
PackageVersion="[~,2)" />
<!-- This uses MyOtherProject5 with (,x] where x is the project version -->
<ProjectReference Include="..\MyOtherProject5\MyOtherProject5.csproj"
PackageVersion="(,~]" />
<!-- This uses MyOtherProject6 with [x,) where x is the project version -->
<!-- (note that in this case PackageVersion attribute is superfluous
as this is the default behavior of GenerateNuspec) -->
<ProjectReference Include="..\MyOtherProject6\MyOtherProject6.csproj"
PackageVersion="[~,)" />
</ItemGroup>
<Target Name="UseExplicitPackageVersions" BeforeTargets="GenerateNuspec">
<ItemGroup>
<_ProjectReferencesWithVersions Condition="'%(FullPath)' != ''">
<PackageVersion>@(ProjectReference->'%(PackageVersion)')</PackageVersion>
</_ProjectReferencesWithVersions>
<_ProjectReferencesWithVersions Condition="'%(Identity)' != '' And '%(PackageVersion)' != ''">
<ProjectVersion>$([System.String]::new('%(PackageVersion)').Replace('~',%(ProjectVersion)))</ProjectVersion>
</_ProjectReferencesWithVersions>
</ItemGroup>
</Target>
...
</Project>
Given package versions specified in other projects like this
<!-- ..\MyOtherProject1\MyOtherProject1.csproj -->
<!-- ..\MyOtherProject2\MyOtherProject2.csproj -->
<!-- ..\MyOtherProject2\MyOtherProject3.csproj -->
<!-- ..\MyOtherProject2\MyOtherProject4.csproj -->
<!-- ..\MyOtherProject2\MyOtherProject5.csproj -->
<!-- ..\MyOtherProject2\MyOtherProject6.csproj -->
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Version>1.1.3</Version>
</PropertyGroup>
...
</Project>
the generated MyProject.nuspec file will contain the following dependencies:
<?xml version="1.0" encoding="utf-8" ?>
<package>
<metadata>
<dependencies>
<group targetFramework="...">
<dependency id="MyOtherProject1" version="[1.1.0, 2.0.0)" />
<dependency id="MyOtherProject2" version="[1.1.3]" />
<dependency id="MyOtherProject3" version="[1.0.0, 1.1.3]" />
<dependency id="MyOtherProject4" version="[1.1.3, 2.0.0)" />
<dependency id="MyOtherProject5" version="(,1.1.3]" />
<dependency id="MyOtherProject6" version="1.1.3" />
</group>
</dependencies>
</metadata>
</package>
I use ~ as the placeholder for the referenced project's version because it is not a valid character in a SemVer string. This useful target can be put into Directory.Build.targets to cover all projects in your solution. It is also possible to make this approach work with project references conditional on TargetFramework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With