I'm working on a (very) large C# project with a significant number of external packages. I'm trying mitigate the problem of unnecessary merge conflicts in *.csproj files caused by package updates.
The new style of PackageReference that has a Version attribute is very prone to merge conflicts if packages are updated on adjacent lines:
<PackageReference Include="Package1" Version="1.0.0" />
<PackageReference Include="Package2" Version="1.0.0" />
<PackageReference Include="Package3" Version="1.0.0" />
Given a csproj file with the above content, if one developer bumps the version of Package1 and Package3 while another bumps Package2, the two conflict. This requires unnecessary manual work although no real conflict has occurred - all three packages have been bumped.
So far, I explored three possible solutions, but haven't found a clear winner:
Setting a diff driver for *.csproj files using .gitattributes. However, none of the built-in diff drivers seem to produce a cleaner diff result (not surprising, given none of them is meant for csproj or even xml files).
Forcing the usage of the old style of PackageReference, in which Version isn't an attribute but an xml element: <Version>1.0.0</Version>. This causes significantly fewer conflicts due to the extra lines. However, I couldn't find a clear way of forcing VS/Rider/Nuget to use the old style, so we'd need to distribute a custom git hook to everyone working on the project to force it.
Distributing the most frequently updated packages to external .targets files, then importing these files using <Import>. This works, but requires custom tooling, especially for adding new references in a consistent manner. This also seems to (partially) break the "Manage NuGet" functionality of both VS (2022) and Rider, though that isn't a big deal.
Can anyone suggest other approaches or ways to improve on the ones above?
For anyone in the same boat, we ended up going with CentralPackageVersions with packages.props being in the "long" format in which Version is an element, not an attribute:
<PackageReference Update="Foo">
<Version>1.0.0</Version>
</PackageReference>
It's also worth pointing out that Microsoft recently introduced Central Package Managemet which is supposed to be supported by Visual Studio and other tooling. However, it requires new versions of just about everything:
The feature is available across all NuGet integrated tooling.
Visual Studio 2022 17.2 and later
.NET SDK 6.0.300 and later
.NET SDK 7.0.0-preview.4 and later
nuget.exe 6.2.0 and later
Related
INTRODUCTION
I am building a class library which could be used by some legacy applications targetting .Net Framework 4.0 and new applications targetting .Net Framework 4.6.1
I am adding some database/Hibernate new code in the class library that requires .Net Framework 4.6.1. This new code is incompatible with .Net Framework 4.0 because the nuGet package FluentNHibernate 3.1.0 requires .Net Framework 4.6.1 and up. And legacy application does not require this functionnality.
WHAT I AM TRYING TO ACHIEVE
I am attempting to conditionnaly build the class library so one code base and the master git branch can be used to build a version compatible for either one or the other application.
So far, I have been able to:
Define a constant indicating the target framework (FWK40)
for use within .cs code to adjust code to target framework
Conditionally define the targetted framework (TargetFrameworkVersion)
Exclude files from build when not under the right TargetFrameworkVersion
Here is what the .CSPROJ looks so far (emphasis for the relevant adjustments):
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug.Net40|AnyCPU'">
<!-- Set target framework here -->
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug.Net40\</OutputPath>
<!-- Define Build-time constant here -->
<DefineConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0'" >FWK40</DefineConstants>
</PropertyGroup>
<ItemGroup>
<-- Conditionally include .cs files here -->
<Compile Include="Database\GeneralSQL.cs" Condition="'$(TargetFrameworkVersion)' != 'v4.0'" />
<Compile Include="Database\NamingStrategy.cs" Condition="'$(TargetFrameworkVersion)' != 'v4.0'" />
<ItemGroup>
<ItemGroup>
<-- In THEORY conditionally include PackageReference here -->
<PackageReference Include="FluentNHibernate" Condition="'$(TargetFramework)' != 'net40'" >
<Version>3.1.0</Version>
</PackageReference>
</ItemGroup>
THE RESULT SO FAR
What is happenning, is I am getting a nuGet error saying:
NU1202: Package FluentNHibernate 3.1.0 is not compatible with net40 (.NETFramework,Version=v4.0). Package FluentNHibernate 3.1.0 supports:
Failed to restore C:\_projets\repos\TestSolution\TestLibrary\TestLibrary.csproj (in 19 ms).
NuGet package restore failed. Please see Error List window for detailed warnings and errors.
Not withstanding this error, the assemblies all are generated properly, the DLL Class Library itself, as well as an EXE Console application using that class library AND so far they run properly.
THE PROBLEM
I haven't been able to conditionnaly include nuGet a PackageReference.
AND The nuGet error still causes MSBuild.exe to fail, which prevents the CI/CD pipeline to work properly..
WHAT I HAVE TRIED
I have tried many many ways to get rid of nuget NU1202 error message.
#1 I have tried initially with other conditions based on $(TargetFrameworkVersion) which works througout the .csproj but to no avail.
#2 According to official documentation, nuGet ReferencePackage only supports conditions based on $(TargetFramework) adding-a-packagereference-condition as shown in the sample .csproj above. YET THIS STILL DOES NOT WORK.
I haven't been able to figure out so far what exactly the Property TargetFramework looks like. I ran MSBUILD.EXE in Verbosity level diagnostics, which dumps all the Properties, but TargetFramework wasn't listed (while others were)
I have tried to "reverse" the condition == 'net461' so that if the expected value is incorrect, it won't be included and the error would disappear => no effect, errors still there
#3 I have tried to define myself the TargetFramework property
<!-- Set target framework here -->
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFramework>net40</TargetFramework>
The outcome would be much worse!
Visual Studio did not like it, the "Configuration Manager" broke down
it would not allow to set a specific Configuration for a project.
changing from Debug.Net40 to any other configuration (or loading the project) would show a nasty error message
Current solution contains incorrect configuration mappings. It may cause projects to not work properly
And the csproj was definitely not loaded properly either and project would be skip the build step.
WHERE I AM AT NOW
I am really stuck! Can't seem to find a way to make this work.
I would hate to have to have a branch master40 and a branch master just to handle this.
I would hate to have to have two csproj different files, unless I can somehow manage to share/include one into the other AND Visual Studio would not complain
The really really right thing would be to make the conditions on the ReferencePackage to actually work as intended.
What you want is multi-targeting.
The documentation you link seems to be only applicable for SDK-style projects.
AFAIK, multi-targeting is not available with the legacy-style project format.
I suggest migrating to the SDK-style project format.
A lot of things are much simpler there, not to mention better documented.
You can use a tool to do this, like hvanbakel/CsprojToVs2017 or dotnet/try-convert. Don't be fooled by its name and wrong usage of terminology.
(since this is also mixed up on SO all the time: yes, you can use the SDK-style format with .NET Framework TFMs; SDK-style has nothing to do with either TFM or Visual Studio version, but only with minimum required MSBuild version).
Once you have done that for your particular project, the documentation for multi-targeting applies and you can use Condition on $(TargetFramework) just like you have already done, in both your PackageReference and the Compile item group and pretty much anywhere you want.
I have a VSIX extension which I have migrated to a new solution (basically to remove older projects targeting older VS versions no longer supported by my company) and to simplify the codebase for ease of maintenance.
Within the IDE, it does not matter if I set the active configuration to Debug|x86 or Release|x86, it will build a VSIX artifact OK. All good so far.
If I use
MSBuuild /t:Build /p:Configuration=Release /p:Platform=x86 -restore -detailedSummary MyExtension.sln
it will build without any errors, but no VSIX is produced.
I have poured over the terminal output and there are no warnings/errors and the DLL output of projects in the solution are produced.
I did read the following:
Project not selected to build for this solution configuration
The option to click deploy from the above link is not available for my VSIX - all the deploy options are disabled.
I have searched S.O. for similar issues regarding a VSIX not being produced, but none seem apt.
How should I debug this? What is different about a command-line MSBuild from the in-IDE build? Hopefully somebody has had a similar experience and can let me know what was causal for them, so that I can give something a try.
Update 1:
It transpired that although I was targeting .NET Framework 4.6, some .csproj references copied over from the migrated project had entries for net472, despite NuGet packages themselves being selected for compatibility with .NET Framework 4.6.
I had to manually edit a few .csproj files. There were some reference issues in associated projects that then needed fixing.
The residual issue now is as follows:
The in-IDE build fails with a single error...
A PackageReference to Microsoft.Build.* without ExcludeAssets="runtime" exists in your project. This will cause MSBuild assemblies to be copied to your output directory, causing your application to load them at runtime. To use the copy of MSBuild registered by MSBuildLocator, set ExcludeAssets="runtime" on the MSBuild PackageReferences. To disable this check, set the property DisableMSBuildAssemblyCopyCheck=true in your project file (not recommended as you must distributed all of MSBuild + associated toolset). Package(s) referenced: Microsoft.Build.Framework
So I grepped my source code folder for <PackageReference Include="Microsoft.Build and only a single project was in the result list. When I checked this project file, the entry in question did have ExcludeAssets="runtime" so I am unsure why the error is reported. I have tried project cleans followed by rebuild, or deleting bin and obj folders before building, to no avail.
I guess my question now is whether <Package Include="Microsoft.Build are relevant, since these are not <ReferencePackage Include elements as mentioned in the error message.
Update 2:
I hang my head in shame. PBKAC regarding Update 1 error. I had sent a copy of the code to a build engineer who committed it to a branch in our VCS. I then cloned this branch to a different location, and copy+pasted my more recent changes over the top. However, the grep tool (AstroGrep) I was using was still pointing at the older location not in the VCS. The older location contained package references with ExcludeAssets="runtime" as required. However, the newer location did not. Once I noticed this, I corrected it by editing the faulty .csproj file and the error from Update 1 went away.
However, I still appear to have the original issue the question is about.
I am awaiting my company's security team to approve the use of MSBuildLog so that I can get more detail and hopefully find the cause.
One other commenter suggest moving to solution PackageReference build rather than using packages.config. There is a question as to why this is needed. I am aware this seems like it could create a significant amount of extra work due to: this for which there are workarounds, but the commenter mentioned a "need" to use NuGet this way, when I think it is optional. I wish to understand more before committing to such a change.
Unfortunately, this is one of those things where it's a case of user beware.
When using NuGet, it is possible for it to appear to have succeeded in updating a NuGet reference, but unless one checks the underlying packages.config meticulously, you may not be getting what you think.
As I am migrating a solution that used packages.config instead of <Project Reference .../> elements in .csproj files, I have been caught out by IDE default behaviour changes.
NuGet seems to update the .csproj using <PacakageReference.../> elements by default. But this does not amend the packages.config entries that may already exist. As such, I ended up with a mish-mash that MSBuild seemed confused about at build time. Rather than throw an error, it just did not build what was expected.
The old packages.config files had entries targeting .NET Framework of net472 in some cases. I was adding NuGet references to earlier versions for net46 since this is what I need to target now, and this resulted in the problem behaviour, since any unchanged net472 entries were no good for producing the build output.
Since the project needs to support VS2015 also, I need to rely on packages.config approach and not <PackageReference.../> approach, which was not updating older references in the expected way.
As such, I had to remove the NuGet <PacakgeReference.../> and re-introduce correct package versions in packages.config. Once these were all correct, the VSIX built OK.
Is there a way to disable updates of specific nuget packages installed in a project?
I have made some local modifications to a couple of javascript library packages and do not want to run the risk of someone updating over the top of my changes in the future.
I've never created my own nuget package, I'm guessing one option might be to fork the existing packages?
You could try constraining the package so it your project will only allow a particular version to be used. Then all updates to any newer version will be prevented.
You can do this by editing your project's package.config file. For example, the line below should only allow version 2.1.0 to be used.
<package id="SomePackage" version="2.1.0" allowedVersions="[2.1.0]" />
I don't have a package.config. (VS 2019, .NET Core 3.1) What I did instead was changing the .csproj of the project which had the package which I needed to stop showing up for updates.
In my case it was EPPlus, and I wrapped the version number within square brackets.
<ItemGroup>
<PackageReference Include="EPPlus" Version="[4.5.3.3]" />
</ItemGroup>
After that, it stopped showing up on the Updates tab in Nuget Package Manager.
And it doesn't give any option to update from anywhere else too. (Installed tab, Nuget for the solution, etc)
You'll need to restart VS to get rid of the yellow triangles next to the packages.
EDIT:
WARNING: Please note that this work only for "Manage nuget packages for [project]" (which is rarelly used), not "Manage nuget packages for Solution" (which is the one you use every other day). See comments.
So this is no solution at all. I will keep it here for some random googlers who will try this, but it is almost useless.
For PackageReference you can block update on single version like this:
<PackageReference Include="IdentityServer4.AspNetIdentity">`
<Version>[3.1.1]</Version>
</PackageReference>
For some reason it have to be in own element and not in attribute, so you are stuck with editing your .csproj by hand.
VS2019 will look funny (some yellow triangles) but just restart it and it will take effect.
It is not the same as allowedVersions= becouse AFAIK you can lock to exactly one version only (for example, [3.1.0, 3.1.1] or (3.0.0, 3.1.1] or whatever else does NOT work!)
(i know i am necromanting this question - accepted answer is about older <Package />, my answer is about newer <PackageReference />)
I am developing a universal windows application. I see that in the reference section i have Microsoft.NETCore.UniversalWindowsPlatform with a blue NuGet icon. When i double click on it, the object explorer does not appear as in normal library references.
As i read the documentation i understand that NuGet does download a copy of the library, however i cannot locate the DLL anywhere, i looked in all the folders.
Where can i find the DLL for the NuGet Reference and what advantage does this new implementation bring? Cause i can't see any gains especially if you are offline and you cant peek in using the object explorer.
It is not a DLL. Documentation is hard to come by and it is likely to change rapidly. But you can get a cue from using a text editor to look at the Microsoft.NETCore.UniversalWindowsPlatform.nuspec file in C:\Users\yourname\ .nuget\packages\Microsoft.NETCore.UniversalWindowsPlatform\5.0.0
You'll see it is actually an XML file that contains a laundry list:
<dependencies>
<dependency id="Microsoft.NETCore.Runtime" version="1.0.0" />
<dependency id="Microsoft.NETCore" version="5.0.0" />
... etc
Those are in turn Nuget packages that are needed to get your project built. The first one in the list is located here.
These packages are retrieved when you first build your project. They are stored in C:\Users\yourname\ .nuget\packages as well. The first one on the list, Microsoft.NETCore.Runtime has yet another redirection in its runtime.json file:
"runtimes": {
"win7-x86": {
"Microsoft.NETCore.Runtime": {
"Microsoft.NETCore.Runtime.CoreCLR-x86": "1.0.0",
"Microsoft.NETCore.Windows.ApiSets-x86": "1.0.0"
}
},
...etc
Note the "win7" moniker, there ought to be a story in there somewhere :) The Microsoft.NETCore.Runtime.CoreCLR-x86 and Microsoft.NETCore.Windows.ApiSets-x86 are yet more nuget packages. Note that there are entries for every possible target, it isn't very "universal" when you get down to the details.
Microsoft.NETCore.Runtime.CoreCLR-x86.nuspec contains yet another laundry list:
<dependencies>
<dependency id="System.Collections" version="[4.0.10]" />
<dependency id="System.Diagnostics.Debug" version="[4.0.10]" />
...etc
We're finally getting to the real DLLs. System.Collections is a Nuget package that the compiler actually sees with the /reference option. Its ref\dotnet subdirectory has the reference assembly, its lib\netcore50 subdirectory has the runtime assembly.
Note how there are a very large number of DLLs involved. The .NET Framework of old is split-up into many sub-assemblies. .NET Native is pretty essential to prevent that from killing cold start time. It compiles all of these assemblies, using only the methods that you actually use, and merges them back into a blob.
Very convoluted, there has to be a machine somewhere that knows how to keep all these balls in the air. Rather scary too, you'd have to wonder if this contraption is still going to work 10 years from now.
UPDATE: and it didn't, lots of changes since I first wrote this post. .NETCore version 5 was renumbered to version 1.0 Version 2.0 just shipped. The team acknowledged that having so many assemblies was only a temporary solution to make it easier to makes changes. I suspect that this only truly starts stabilizing at the proverbial version 3.
I am attempting to publish and consume versioned NuGet packages of class libraries while avoiding headaches for local development. Here is a sample Visual Studio solution layout:
| Libraries
| LibraryA
| LibraryB
| LibraryC
| Applications
| ApplicationD
| ApplicationE
This is a single solution containing both shared class libraries and multiple applications. Currently references to the class libraries by the applications are local in-solution references.
What I would like to do is to publish the libraries (A,B,C) as versioned NuGet packages which are then referenced by the applications as needed (D,E). This allows a change to a shared library to be independent from an update to an application which is deployed. Without this, changing one library could cause the binaries to change in a dozen or more applications, all of which would technically need to be tested. This is undesirable, and versioning with NuGet fixes this.
However, let us say that I want to update the content of LibraryA and ApplicationD at the same time. In order to do this after we have switched to NuGet, I will have to make changes to LibraryA, commit them, wait for the package to be created, tell ApplicationD to update its reference to LibraryA, and then test or develop in ApplicationD. This is far more complicated than simply working with both at the same time using local in-solution references.
What is a better way to get both the robustness of versioned NuGet packages for my shared class libraries while also keeping development simple even if it spans over multiple projects and applications? The only other solutions I have found all involve too much overhead or headache, such as having to constantly change the references for ApplicationD between the NuGet package and the local project.
EDIT: To clarify the premise, this question assumes the following:
The architecture (solution and project organization) cannot be significantly reorganized
Shared libraries are going to change at a non-trivial frequency
Changing a shared library cannot force any application to be updated
Applications can reference different versions of shared libraries
Although it takes some work, it is possible to hand-edit .csproj files in order to set up conditional referencing by adding a Condition attribute to the appropriate references.
EDIT I've moved these conditions into ItemGroups, as it seems this is how my mentioned production code is working, and there has been mention of this being a possible issue in VS 2013.
<ItemGroup Condition="'$(Configuration)' == 'Debug Local'">
<!-- Library A reference as generated by VS for an in-solution reference, children unmodified -->
<ProjectReference>...
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug NuGet'">
<!-- Library A reference as generated by NuGet, child nodes unmodified -->
<Reference Include="LibraryA">...
</ItemGroup>
This would allow you to have, on the Projects D & E, configurations of "Debug NuGet" vs. "Debug Local" which reference the libraries differently. If you then have multiple solution files which have their configurations mapped to the appropriate configurations on the projects within, the end user would never see more than "Debug" and "Release" for most operation, since those are the solution configs, and would only need to open the full solution for editing the A, B, & C projects.
Now, as for getting the A, B, & C projects out of the way, you could set them up under a folder marked as a subrepo (assuming you're using an SCM that supports this, such as Git). Most users would never need to pull the subrepo since they're not accessing the ABC projects, and are instead grabbing from NuGet.
Maintenance wise, I can guarantee that VS will not edit the conditional references, and will respect them during compilation -I have gone through both VS 2010 and 2013 (EDIT: Professional version, though I have delved into doing the same with express) with the same conditional reference projects at work. Keep in mind than in VS, references can be made version-agnostic, making NuGet the only place from which version need be maintained, and that can be done like any other NuGet package. While I'm hopeful, I have NOT tested whether NuGet will fight with the conditional references.
EDIT It may also be prudent to note that conditional references can cause warnings about missing DLLs, but does not actually hinder compilation or run.
EDIT For those still reading this, I'm now (7/2019) hearing that the IDE isn't as friendly to these changes anymore, and either it or the Package Manager may override them. Proceed with caution, and always read your commits!
Update for .NET Core (2.x ++)
.NET Core 2.x actually has this functionality built in!
If you have a project reference to project A in project B, and project A is a .NET Standard or Core project with proper package information (Properties -> Package with Package id set to your NuGet package ID), then you can have a regular project reference in project B's .csproj file:
<ItemGroup>
<ProjectReference Include="..\..\A\ProjectA.csproj" />
</ItemGroup>
When you pack (dotnet pack) project B, because of the Package id in project A, the generated .nuspec file will be set up with a NuGet dependency to that Package ID, together with other NuGet references you might have, instead of just including the built DLL file.
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Project.A" version="1.2.3" exclude="Build,Analyzers" />
<dependency id="Newtonsoft.Json" version="12.0.2" exclude="Build,Analyzers" />
</group>
</dependencies>
I know this is a 2-years old post, but just found it while facing the same situation. Also found this for VS2015, I'm in the process of testing it. I'll come back and adjust my answer accordingly.
https://marketplace.visualstudio.com/items?itemName=RicoSuter.NuGetReferenceSwitcherforVisualStudio2015
I also faced a similar problem. One approach that worked was using local repository (which is basically just a folder in local) and adding post-build script in the libraries. For example: let's say you need to update your implementation for LibraryA, then include following 3 steps in your post-build event for LibraryA:
Check if local repository has that version of package; if yes then delete it
rd /s /q %userprofile%\.nuget\packages\LibraryA\#(VersionNumber) -Recurse -ErrorAction Ignore
Create a nuget package
nuget pack LibraryA.csproj
Push it to local repository
nuget push LibraryA#(VersionNumber) -Source %userprofile%\.nuget\packages
These steps will make sure that the package is always updated for that version after each build (we had to do this since nuget packages are immutable)
Now in ApplicationD, you can point to local repository (%userprofile%.nuget\packages) to get LibraryA; such that after each build of LibraryA, you will receive an updated version of it in ApplicationD
PS: Inorder to get version number of you library you can use this : Determine assembly version during a post-build event
Unfortunately, there really isn't a way to have the best of both worlds. Internally in my company, we've mitigated it somewhat with a fast build/deploy process, which counteracts most of the burdens with always referencing a NuGet package. Basically, all of our applications use a different version of the same library hosted in a local NuGet repository. Since we use our own software to build, deploy, and host the packages, it makes it pretty quick to update the library, then update its NuGet package in another solution. Essentially, the fastest workflow we've found is this:
Make changes to library
Automatically build and deploy version of library incremented by 1 to internal NuGet feed
Update NuGet package in consumer application
The whole process from check-in to updating the consuming project takes around 3 minutes. The NuGet repository also has a symbol/source server which helps tremendously with debugging.
In the properties of ApplicationD, go to the "Reference Paths" tab and add the path of the output folder of LibraryA. Then, if you change and build LibraryA, the next build of ApplicationD will use the modified LibraryA.
When you are finished, don't forget to remove the "Reference Paths" and update the referenced NuGet package version.
My not-so-clean yet fastest solution so far is:
Assuming the following two separate solutions:
VS Solution 1: contains libraries published as nuget packages:
Solution1
|_ my.first.library
|_ my.second.library
VS Solution 2: contains applications, which consume one or more of the above libraries as PackageReferences:
Solution2
|_ my.first.application
| |_ depends on nuget my.first.library (let us say v1.0.1)
|
|_ my.second.application
In case, I'm making changes to my.first.library
I proceed as follows:
Make code changes to my.first.library and rebuild
Navigate to the build output directory of my.first.library (e.g. <Solution1 directory>/my.first.library/bin/debug/netstandard2.0) and copy the .dll and .pdb files
Navigate to the my.first.library's local directory of the currently being used nuget feed (for example at: C:\Users\user.name\.nuget\packages\my.first.library\1.0.1lib\netstandard2.0) and replace the .dll and .pdb files there with the ones generated in step 1 (possibly making backup).
Changes get reflected in my.first.application. Continue working and repeat steps 1-4, when needed
Advantages:
being completely local. No secondary nuget feeds needed.
zero changes to .csproj/.sln files
Caution:
While this solution offers you flexibility, make sure you clear your nuget cache before acting on them, for example by publishing to a nuget server. Thanks #Benrobot