Build, pack up and deploy for multiple platform targets - c#

I'm looking forward to setup an environment/configuration that allows me to build and deploy a custom library for multiple platforms / targets, such as build configurations and/or .NET framework versions. For this, I've laid out the following structure:
MyProject.sln
src\
File1.cs
File2.Net30.cs
MyProject.Net40.csproj
MyProject.Net30.csproj
MyProject.Net45.csproj
All project files are included in the solutions and built at once. Each project contains the source files for the framework it targets and/or all files where as different .NET versions are compiled conditionally (using compiler directives, e.g. NET35, NET34_OR_GREATER). Additionally, each project file contains the following msbuild directives:
<OutputPath>bin\$(Configuration)\$(Platform)\$(TargetFrameworkVersion)\$(TargetFrameworkIdentifier)\</OutputPath>
<BaseIntermediateOutputPath>obj\$(Configuration)\$(Platform)\$(TargetFrameworkVersion)\$(TargetFrameworkIdentifier)\</BaseIntermediateOutputPath>
<DocumentationFile>bin\$(Configuration)\$(Platform)\$(TargetFrameworkVersion)\$(TargetFrameworkIdentifier)\$(AssemblyName).xml</DocumentationFile>
This allows me to build them all at once by routing the output into different directories.
Now, that's all for building. However, I'm really stuck with deployment, especially related to NuGet. I've created a .nuspec where I include every dependency manually:
<file src="bin\Release\AnyCPU\v4.0\MyProject.dll" target="lib\net40-client\EIT.Foundation.dll" />
<file src="bin\Release\AnyCPU\v4.0\MyProject.xml" target="lib\net40-client\EIT.Foundation.xml" />
<file src="bin\Release\AnyCPU\v4.5\MyProject.dll" target="lib\net45\MyProject.dll" />
This works fine, but is really tedious. So first question: Is there any way to hook up the files automagically?
And my second problem: Sometimes my libraries have NuGet dependencies themselves. For project dependencies, a packages.config is automatically created in the same folder as the project when downloading NuGet dependencies. The packages.config not only contains the dependency or its version used in the project, but also which framework version of the dependency is required. This is a bit of a problem since every project file (for each framework target) resides in the same folder, so they would need to share the same packages.config file somehow. I'd tried relocating the project files to a different structure like this:
MyProject.sln
target\
net40\MyProject.Net40.csproj
src\
File1.cs
... however then I'm unable to preserve the folder structure in my source folder (if there's any) because the project files only allow me to include files, not folders (they are being automatically included.) Is there any way around this or is NuGet simply not suited for multi-target builds?

Whilst NuGet supports creating a NuGet package for a particular project it is targeted for single projects. The command is NuGet pack YourProject.csproj so I suspect it will not help you. Using a .nuspec file is probably the only way to get this working.
NuGet supports multiple projects in the same directory if you rename the packages.config file. Each packages.config file should be named after the project. So in your example the following should work:
packages.MyProject.Net40.config
packages.MyProject.Net30.config
packages.MyProject.Net45.config

Related

In C#, how to `Paket Add` dependencies to all projects in a folder even when they are in separate solutions?

Say I have a packet.dependencies file in a root folder full of different solutions adding up to hundreds of projects, each with a paket.references file.
Is there an easy way to go about adding the same package/same version in all the projects with a single command (or close) with paket?
In my use case, I'm trying to add an analyzer to every C# project we have. It would be tedious to add the same package and version to hundreds of different C# projects...
It was actually just paket add <package ID>, if ran in the root folder with the paket.dependencies file, it will actually find all the .csproj files and add the dependency.
It was the following line in the docs that threw me off:
By default packages are only added to the solution directory, but not on any of its projects. It's possible to add the package to a specific project:
But it will actually add the dependency to all solutions and projects.

Mix of .netstandard2.0 and .netframework47 DLLs in nuget package - .netstandard not available

First of all I don't know if what I'm doing is fundamentally the wrong idea, so the answer could be to just not do this, but here goes..
I have a package that I'm creating from a .nuspec file, in its "files" section it contains references to both .net standard and .net framework DLLs. It also has dependencies on both .netstandard20 and .netframework47.
The reason behind this being that the legacy libraries are written in framework, and the new ones are being written in standard.
We have a consuming project (actually, several), written in .netframework 4.7 that needs to address both sets of DLLs. I have changed the project to use PackageReference rather than packages.config, and
Visual Studio shows that the package is being referenced (rather than the individual namespaces) in the References section. When I build, the .netframework DLLs get copied into the bin folder, but not the .netstandard DLL.
I've created a scratch project to isolate the issue, and I'm seeing the same there as well. The project basically just refuses to acknowledge the standard one. If I add a using statement into a class with a type from the .netstandard DLL it just puts a red squiggly under it and tells me it doesn't exist in the namespace.
It's been suggested that I break the .netstandard DLLs out into their own NuGet package, is that the answer or am I missing something?
Here's some examples..
The .nuspec file:
<?xml version="1.0" encoding="utf-8"?>
<package >
<metadata minClientVersion="2.5">
<!-- other tags removed for brevity -->
<dependencies>
<group targetFramework=".NETStandard2.0"></group>
<group targetFramework=".NETFramework4.7">
<dependency id="NLog" version="3.1.0.0" />
</group>
</dependencies>
</metadata>
<files>
<file src="..\Src\MyProj1\bin\Release\netstandard2.0\NetStandardClass.dll" target="lib\netstandard2.0" />
<file src="..\Src\MyProj2\bin\Release\NetFrameworkClass1.dll" target="lib\net47" />
<file src="..\Src\MyProj3\bin\Release\NetFrameworkClass2.dll" target="lib\net47" />
</files>
</package>
The package is referenced as a package, not the individual types within it:
If I try to manually add a using statement referencing the NetStandardClass's namespace it's marked as an error
The package itself contains all 3 DLLs as expected:
Yet when I build the project and inspect the bin folder, the NetStandardClass DLL is not there:
It's been suggested that I break the .netstandard DLLs out into their own NuGet package, is that the answer or am I missing something?
Basically, yes, splitting the package is the answer. Another possibility is to move the netstandard assemblies into the net47 folder, or maybe the net47 assemblies into the netstandard2.0 folder, but either way has potential for problems. Although solution is to recompile all your assemblies so they all use the same target framework, but that also has its own issues.
If you had read the NuGet docs and came to the conclusion that what you're attempting should work, please point me to which docs lead you to believe that, so I can update the docs and try to remove the confusion.
The first thing to understand about why NuGet supports a package having multiple target framework folders is because a package might want to use new framework features where possible, but have support for users of the package using older frameworks. Therefore, the package author needs multiple versions of their assembly, depending on which one is compatible with their users project. So, the point is that NuGet needs to select assets from the package depending on which target framework moniker (TFM) the project uses. The way NuGet actually does asset selection is as follows:
NuGet looks are which TFM folders exist in the package, to get a list of TFMs that the package supports. Generially it looks for lib/<tfm>/ and ref/<tfm> folders, but contentFiles/<tfm> and build/<tfm> files might be relevant as well. Notice it doesn't look at any filenames. Which assemblies exist under lib/net47/*.dll or lib/netstanard2.0/*.dll are not yet considered.
Once it has this list of package TFMs, it looks at the project TFM, and it selects the "best" TFM. For SDK style projects that multi-target, it does this once per project TFM. "Best" TFM means same "family" (net* projects always choose net* assets, if available. only when no net* assemblies exist does netstandard compatible .NET Framework TFMs look for netstandard* assets).
Once a package TFM is selected for the project's TFM, then NuGet selects all the files like lib/<tfm>/*.dll, ref/<tfm>/*.dll, contentFiles/<tfm>/<lang>/*, build/<tfm>/<package_id>.<props|targets>, and so on.
So, you can see that if your package contains lib/net47/assembly1.dll and lib/netstandard2.0/assembly2.dll, NuGet will only ever select one of the two assemblies, never both, depending on if net47 or netstandard2.0 is more compatible with the project.
Although it might seem desirable for you if NuGet did TFM selection per TFM, rather than selecting TFM first and then selecting only the assemblies that exist in that folder, consider when a package adds an additional helper utility to "polyfill" old TFMs with features that the package uses in newer TFMs. This helper utility is not needed for newer TFMs, so it would be undesirable for NuGet to select it.
The NuGet team suggests creating one package per assembly, in which case this problem never would have occured, because NuGet would have done asset selection per package, and assuming each package was authored correctly, everything just selects selected correctly. If you insist on bundling all the assemblies in a single package, from my description above I hope you see you need to put all the assemblies in a single TFM folder, but since different assemblies were compiled against different TFMs, there's potential for problems, particularly if some developers are using older versions of Visual Studio that might not support .NET Standard. I very strongly recommend at the very least creating one package per TFM, or recompiling all the assemblies to use the same TFM.

Issue finding and using lib folder in Nuget package C# project

I am working on creating a sample Nuget package to test out the process of creating an internal Nuget package for use in another project of mine. My end goal is to create a simple Nuget package, which can be installed onto another simple C# project, and tested out.
I have been following the Microsoft tutorial to create & publish a package using VS:
https://learn.microsoft.com/en-us/nuget/quickstart/create-and-publish-a-package-using-visual-studio-net-framework
I successfully created & published my package on nuget.org, called MyNugetPackage, and attempted to install it onto my other C# project called TestingMyNugetPackage. I received an error in the NuGet package console stating:
Package does not support any target framework
This error makes sense, because I had read about supporting multiple .NET versions and specifying the version under the lib folder, and I definitely did not do that when creating my package:
https://learn.microsoft.com/en-us/nuget/create-packages/supporting-multiple-target-frameworks
This idea of lib folder makes sense to me and I think I understand how to add my target .NET version to it. However, I cannot find this folder anywhere! It's not anywhere in the C# project directory. I assume I may need to create it on my own, but I'm not sure where to put it.
Many tutorials and SO questions I have read about this topic talk about how to use the lib folder, but no one ever says where it is. I'm a complete beginner to this and I know I am missing something obvious here, but I'm not sure what it is.
Edit: I did try to change my .nupkg file to a .zip file and extracting the contents in attempt to view the lib folder. This did work in extracting the contents, but I did not see any lib folder after expanding entire project tree and searching for lib.
Here is a quick layout of my C# solution tree:
Solution titled MyNugetPackage with a MyNugetPackage.sln file, a MyNugetPackage.csproj file, and a simple class Logger.cs that just has a public void Print(string text) { Console.WriteLine(text); } method:
MyNugetPackage
MyNugetPackage.csproj.1.0.0.nupkg
MyNugetPackage.nuspec
MyNugetPackage.sln
MyNugetPackage (folder)
bin (folder)
Debug (folder) -> .dll, .pdb
Release (folder) -> .dll, .pdb
obj (folder)
Debug (folder)
Release (folder)
Properties (folder)
AssemblyInfo.cs
Logger.cs
MyNugetPackage.csproj
Could someone direct me where I need to place my lib folder, so that I can add my supported .NET 4.7 framework reference, and successfully install my package?
A NuGet package (.nupkg) is just a zip file. If you are trying to view the contents of this file, open it like a zip file (using 7zip or something). Alternatively change the extension to zip. In the package you will find the "lib" folder as well as the .nuspec, and package folder (among other contents). But this is the resulting package that is built when you Pack your project, changes here would have no affect on your code.
If you're just trying to target one or more frameworks. In VS, edit your project file (.csproj). This file is an XML with a PropertyGroup that contains either a "TargetFramework" OR a "TargetFrameworks" element. To target a single framework add a TargetFramework element, to target multiple use the TragetFrameworks instead.
To target a single .Net framework:
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
</PropertyGroup>
Alternatively, you can target multiple frameworks.
<PropertyGroup>
<TargetFrameworks>net472; netcoreapp3.0; netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
This would target .Net 4.7.2, .Net Core 3.0, and .Net Core 2.1

Create multi-target nuget package from several csproj contained in different folders from nuspec file

I am trying to create a nuget package for a library which exists in 2 versions, each of them targeting two different .NET versions.
Here is my folder structure:
As you can see my nuspec file is one directory above my csproj (which are in the /net452 and /netcoreapp2.0 folders).
I am using the following command line to build my nuget package:
nuget pack .\my.nuspec -properties Configuration=Release -Build
The issue is that the build can not be achieved because the csproj files are not in the same folder as the nuspec file.
Please note that the packaging works fine when both projects have been priorly manually built.
I'm very new to this process and I'm not sure what I should do in that scenario, is there a simple way to reference the 2 csproj from the nuspec or - assuming I want to keep this folder structure - would I need to make a script that builds the projects first and then invoke nuget pack?
EDIT: To clarify my issue, I do have two different csproj files, contained respectively in /net452 and /netcoreapp2.0, and they compile the source code in their respective directory to produce two different dll. These two dll are then referenced in my nuspec file in order to offer my package in net452 or netcoreapp2.0 with the following syntax:
<files>
<file src="lib\**" target="lib" />
</files>
Note: after I manually compiled my two projects, the directory looks like this (note the lib folder that contains a net452 and netcoreapp2.0 folder with the appropriate version of my dll).
Not sure if understood correctly - you have one library and want to build it in two versions (net452 and netcore).
Did you try adding following items to *.csproj?
<PropertyGroup>
<TargetFrameworks>net45;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
Then you can have on project with two outputs from build

Best way to maintain .NET project dependencies and updated binaries in large multi solution application

I know this is a vague question, but I spent hours of searching and couldn't find a solution.
I have a class library project - A
I have another class library project - B
B has a reference to A
I have another console application project - C
C has a references to both A and B.
Now, what happens is:
A's code is being updated. A is being rebuilt.
Now B needs to get that new A binary, and then B needs to be rebuilt.
Now C needs to get that new A binary AND that new B binary.
Now imagine that I maintain several projects, each in its own solution, and that chain of dependencies is longer.
What is the best way to maintain all those binaries and keep them up to date?
How can I make sure that C always has the most up-to-date versions of A and B?
One solution I found is to create a nuget server in which there will be a nuget package for A and for B,
and when A or B's binaries are updated, new nuget packages will be rebuilt, and because C will use these nuget packages, it will always have the most up-to-date binary versions of A and B.
We work with SVN but I don't see how it can help managing dependencies between projects.
Any suggestion/direction will be very helpful
Thanks
The dependency chain you are describing is handled differently in different Visual Studio versions what depends on type of the project you are using. In old C# project version (pre VS 2017) when project reference is used (it is displayed in meta folder of the project as a references) and Visual Studio is unable to track and copy dependencies of project reference. This is why it was necessary to have your project C to reference both project A and project B.
Whereas in new format of csproj in Visual Studio 2017 when dependencies are used instead of references MSBuild is capable of traversing whole dependency tree and will handle properly situation where your project C references only project B which references project A. All required by application assemblies will be built and copied into output directory of project C.
In both cases there is no need to use NuGet server.
Now imagine that I maintain several projects, each in its own solution, and that chain of dependencies is longer.
What is the best way to maintain all those binaries and keep them up to date? How can I make sure that C always has the most up-to-date versions of A and B?
Once you have broken your dependency chain handled easily by MSBuild by separating your projects into separate solutions your build system lands in unsupported directly by Visual Studio territory. For pure managed .NET applications I would avoid that at any cost. For examples of building large, complex managed projects have a look at Roslyn compiler.
To solve that problem without using NuGet server at all - and it is my recommendation - you can create tree of dependencies spanning through solution boundaries by referencing directly from projects of one solution their dependencies (projects) from another solution. This will make your build much easier to manage (and it is particularly important for CI and DevOps solutions) and expand.
If you cannot modify current project/solution structure just create a new one as an overlay over existing project/solution structure and configure it properly. You will end up with one solution/project system which is perhaps easier to work with during code editing and testing and a second one which spans all maintained projects which is better suited for building whole application.
If the above option is not working just modify your all projects to output all final artifacts to one common output directory (move bin of every project to common top level bin directory, you may want to move obj intermediate directories as well).
If the choice of the NuGet server is a must the easiest way to do it it is to use common output directory and use NuGet.config in all projects consuming dependent projects by pointing them to new package source.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- NOTE: Leave this file here and keep it in sync with list in dir.props. -->
<!-- The command-line doesn't need it, but the IDE does. -->
<packageSources>
<clear/>
<add key="private NuGet" value="file:///e:/src/myproject/.nuget" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<config>
<add key="repositoryPath" value="..\packages" />
</config>
<packageRestore>
<!-- Automated package restore in VS does not work at this time with
this project and it causes build failures in VS. Disable it. -->
<add key="automatic" value="false" />
</packageRestore>
</configuration>
In the above NuGet.config file:///e:/src/myproject/.nuget points to main directory of your NuGet server (simple file system directory) where all .nupkg(s) are stored - it will require setting common output directory for nupkg(es) from all projects. This will not work on network shares yet as this nuget.client feature is under development right now. See nuget.clinet pull request which is still open at the time of writing Adding support for Network Share Hosted Package Source and compatability with generic build tools.
If you want to use full blown NuGet server first I would seriously consider using it as an external service i.e. myget.org. They have free plan if your packages can be public or paid plans if you want to keep your packages private.
Finally if you want to use your very own NuGet server go to NuGet project site and choose one you would prefer. NuGetGallery - equivalent to current nuget web site or smaller one NuGet.Server and follow installation instrcutions.
I have my own local NuGet repository so I think that my method should work on some shared network disk where you will have newest nuget packages.
First you need to install nuget standalone. You will download it from official website and you will have nuget.exe. I suggest adding it to the folder which will be added to system PATH environment variable. By doind this it will be accessible from your computer everywhere just by typing 'nuget'.
Now you need to get to the folder where is your .csproj file from your library.
Initialize your .nuspec file by typing nuget nuspec
You should have file [project_name].nuspec. Open it and add this line as second line of file after <xml> tag. It's weird that nuspec do not add this automatically.
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
authors tag and description tag on my computer could not be fetched from .csproj file so i needed to input values there manually.
I defined post-build event to my .csproj file as this:
nuget.exe pack $(ProjectPath) -IncludeReferencedProjects -OutputDirectory "D:\programowanie\nugetPackages"
powershell.exe D:\programowanie\nugetPackages\install.ps1
First command packs your project files into .nupkg files and send them directly to nugetPackages folder on disk D (there you could use some shared network drive).
-IncludeReferencedProjects description from specification:
Indicates that the built package should include referenced projects either as dependencies or as part of the package. If a referenced project has a corresponding .nuspec file that has the same name as the project, then that referenced project is added as a dependency. Otherwise, the referenced project is added as part of the package.
Install.ps1 contents:
nuget init D:\programowanie\nugetPackages D:\programowanie\nugetServer\
It creates folder structure in NugetServer directory for my packages and copy .nupkg files to those folders.
Now you need to add new package source inside your viual studio. You just enter path to the D:\programowanie\nugetServer\ in my case and name. You do it here:
I created solution for my .csproj library. I do not know if I done it by accident or is it really needed.
It was my first approach to having my own nuget package folder to maintain dependencies. I think that my solution is still not very good and lack a lot of automation. If you would like to have more info on the topic i suggest to stick with oficcial guidelines.
I do not have so complicated dependencies as in your case. But maybe creating solution which includes all of those libraries and definining good build order will enable to automatically build and deploy them to nuget server with my method.

Categories