How to publish this C# Source Generator with dependencies on Nuget? - c#

I've created a C# source generator, and would like to publish it to Nuget.
But I run into warnings/errors such as:
Some target frameworks declared in the dependencies group of the nuspec and the lib/ref folder do not have exact matches in the other location. Consult the list of actions below: - Add lib or ref assemblies for the netstandard2.0 target framework
An instance of analyzer Cosmogenesis.Generator.CosmosGenerator cannot be created from [..]\Cosmogenesis.Generator.dll : Could not load file or assembly 'netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.. (or Version=2.0.0.0 depending on what I'm trying)
There are a few resources explaining how, but none seem to work in my exact scenario, which is:
Cosmogenesis.Core
.NET Standard 2.1
Not depended on by the generator, but the generated source code depends on it
Cosmogenesis.Generator (published on Nuget as just Cosmogenesis)
.NET Standard 2.0
This is the source generator
Ideally, I'd like to be able to just install Cosmogenesis from Nuget and it would automatically bring in Cosmogenesis.Core along with the generator.
Use functionality from NuGet packages <-- explains my scenario (except for differing .net standard requirements). ....And as a side question: Why does it have code to check if an assembly is referenced if it's explaining how to make it be automatically referenced?....
Anyway...
Here is Cosmogenesis on Github
Cosmogenesis.Core is published without any trouble.
Cosmogenesis.Generator is published using this .csproj as well
It works. However, the consumer needs to reference BOTH packages. I'd like them to only need to reference one package.
When I add <IncludeBuildOutput>false</IncludeBuildOutput> (as per tutorial above), on build I get this warning: Some target frameworks declared... [see above]. If I ignore it and publish it anyway, when I consume the package in a project and build it, the generator does not run and I get this warning: An instance of analyzer... [see above].
When I add <PackageReference Include="Cosmogenesis.Core" Version="0.0.1" /> (as per tutorial above), I get an error. The generator is .NET Standard 2.0 while the .Core package is 2.1. The error is: error NU1202: Package Cosmogenesis.Core 0.0.1 is not compatible with netstandard2.0 (.NETStandard,Version=v2.0). Package Cosmogenesis.Core 0.0.1 supports: netstandard2.1 (.NETStandard,Version=v2.1). Understandable.
When I set the generator project to 2.1 to fix the mismatch, no warnings appear while building, however the consumer again gets the An instance of analyzer... error. C# Source Generators - Write code that writes code - David Wengier says they have to be .net standard 2.0.
This is my first nuget package. And my first source generator. Can anyone advise?
EDIT: I'm 75% sure a solution to this roslyn github issue would solve my problem here.

Related

CPack NuGet Packages

Context
I have a Managed C++/CLR library which is built using CMake 3.17, and packaged into a NuGet package using CPack. The resulting nupkg file cannot be imported into a C# project, as the Package Manager issues the following error: "[snip] the package does not contain any assembly references or content files that are compatible with [.NETFramework,Version=v4.5.2]". However, adding a reference to either the project when added to the solution, or the corresponding library file generated by the build, works as intended.
C++/CLR Details
The code itself is very basic and produces a valid library which can be referenced from another project, when manually adding a reference via Visual Studio 2017 -> Add Reference (either the project or the corresponding library can be added this way and it works all the same).
The code consists of the class itself, and AssemblyInfo.cpp provides attributes which describe the metadata and version information only. The dependencies include only System, System::Runtime::InteropServices, and a raft of pre-built native libraries.
I have not added a .nuspec file, nor a nuget.config file, the latter which I believe is generated by the CPack NuGet generator when the package is built.
CMake / CPack Details
CPack NuGet support is relatively new, and I have been unsuccessful in finding a working example, but I have managed to successfully generate a nupkg file. Firstly CMake is instructed to build a Managed C++ library with the included source files, and the following properties set on the corresponding target ManagedLibrary:
set_target_properties (ManagedLibrary PROPERTIES DOTNET_TARGET_FRAMEWORK_VERSION "v4.5.2")
set_target_properties (ManagedLibrary PROPERTIES COMMON_LANGUAGE_RUNTIME "")
The documentation states that this will generate CLR/Mixed code and works as advertised, so I am able to successfully build against the target framework. The next step was to install the library in what I believe is the correct location:
install (TARGET ManagedLibrary DESTINATION . COMPONENT MixedCLR)
And supporting (native C++) libraries are installed similarly:
install (FILES [various..] DESTINATION . COMPONENT MixedCLR)
I also set CPACK_GENERATOR to 'NuGet', and then run the PACKAGE step from the CLI using cmake --build . --target PACKAGE which successfully produces the nupkg file.
Question
How does NuGet know what libraries to add a reference to?
Is a nuspec file required? If so, what must minimally be included in it, and how do I include it in the target CMakeLists.txt?
Is it acceptable to put the managed library, along with supporting native libraries, in the root of the package? If not, where should they go?
Are any other files generally included in a nupkg file?
Finally, if anyone knows anything about packaging and multi-targeting in C++/CLR to support different framework versions / architectures / build configurations, any notes on that would be highly appreciated.
How does NuGet know what libraries to add a reference to?
Primarily NuGet infers the libraries to reference from the package structure. Managed assemblies must be put in a directory which is libs/<TFWM> where TFWM is the Target Framework Moniker (eg: .NET Framework 4.5.2 => net452).
Is a nuspec file required? If so, what must minimally be included in it, and how do I include it in the target CMakeLists.txt?
The nuspec file is automatically generated by CPack at package generation time. The generated file is saved to the output directory, and will preserve the directory structure specified by the install command.
Is it acceptable to put the managed library, along with supporting native libraries, in the root of the package? If not, where should
they go?
As already discussed, the managed libraries go in libs/blah. Native libraries, on the other hand, go in runtimes/<RID>/native where RID is the Runtime ID. In my case I wanted to target Windows 64-bit, so the Runtime ID is win-x64.
Are any other files generally included in a nupkg file?
I bundle the PDB for convenience, but I didn't need to specify any other files or properties.
Finally, if anyone knows anything about packaging and multi-targeting
in C++/CLR to support different framework versions / architectures /
build configurations, any notes on that would be highly appreciated.
If targeting multiple framework versions it's simply a case of creating and installing multiple targets into the respective folders, there's nothing more complex to deal with.
Finally, my finished package structure looks like the following:
libs/
net452/
ManagedLib.dll
ManagedLib.pdb
runtimes/
win-x64/
native/
NativeLib1.dll
NativeLib2.dll
...
I hope this helps someone in the future.

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.

C# project, compiler complaining missing reference to log4net

I am using Visual Studio 2017 to build a big C# project (200+ projects in the solution). When compiling one of the projects, I got many errors as shown below:
error CS0012: The type 'BufferingAppenderSkeleton' is defined in an assembly that is not referenced. You must add a reference to assembly 'log4net, Version=1.2.11.0, Culture=neutral, PublicKeyToken=1b44e1d426115821'.
The project in question, however, does reference to log4net 1.2.11. The only thing suspicious is the net40-full found in the package path of log4net: "C:\XXXX\Src\packages\log4net.1.2.11\lib\net40-full\log4net.dll"
in the package.config of the project, it contains this line:
<package id="log4net" version="1.2.11" targetFramework="net461" />
and in its app.config, it contains this line:
<dependentAssembly>
<assemblyIdentity name="log4net" publicKeyToken="1b44e1d426115821" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-1.2.11.0" newVersion="1.2.11.0"/>
</dependentAssembly>
I wonder if it's the mismatch of the .net version (4.0 vs. 4.61) that causes the compiling error?
C# is not my primary area of expertise, but my understanding is that nuget looks into those config files to download needed packages, in this case, log4net. Then how come did it download the 4.0 version rather than that of 4.6.1?
my understanding is that nuget looks into those config files to download needed packages, in this case, log4net.
NuGet's job is just to download packages and extract whatever's in them, at least in the restore scenario of a package.config project. The targetFramework attribute in the packages.config file is only written, never read by the nuget client. I have no idea what it's purpose or intention was. Anyway, I believe the value of targetFramework is just the .NET Framework your project was using when the package was installed.
Then how come did it download the 4.0 version rather than that of 4.6.1?
Background information, skip to the next paragraph if you really don't care. If you go to the package page on nuget.org, you'll see in the version history that 1.2.11 isn't shown. However, if you look at the URLs for other versions, you can guess the URL for version 1.2.11. Quick off-topic comment, Fabio M was close, but not quite correct in saying the package doesn't exist any more. "nuget.org does not support permanent deletion of packages. Doing so would break every project depending on the availability of the package, especially with build workflows that involve package restore.". On the package version page, there's a message saying "The owner has unlisted this package. This could mean that the package is deprecated or shouldn't be used anymore".
Back to my point, once you're at this URL, change the n to an f in nuget.org to see the package version on fuget.org. Next to frameworks you can see a list of frameworks the package supports. net40 is the highest version that the package supports.
So, the reason why NuGet "downloaded the 4.0 version" is because that's the closest version that the nuget package provides that is compatible with your project. .NET is generally considered to be forward compatible, so net45 binaries work on the net462 runtime, so it's generally ok to use net45 binaries when your project is using a newer version.
Finally, about the error you're getting, as I said in my first paragraph, in packages.config projects, nuget's job is just to download and extract the package. At install time it adds some information to the csproj so the compiler can try to find the dll. So, if you look at your csproj, you should find a reference to log4net.dll, and it will contain a hint path that the compiler uses. If that hint path is wrong, then you'll get the errors you see. This is most common when projects are moved in the directory structure, but don't re-install the package. For example if the repo structure was origignally "project\project.csproj" and it's changed to "src\project\project.csproj", then the hint path of "..\packages\log4net.1.2.11\lib\net45\log4net.dll" is wrong, because an additional "..\" needs to be added so the relative path to the packages folder is correct. There may be other reasons the hint path is wrong, but this is the most common reason.

Nuget package (EF Core Sqlite) referenced by PCL referenced by Xamarin Android not copied to bin

Curent status
I have got the following setup (simplified example):
MySolution
┠ MyPclLibrary (PCL targeting .NET Standard 1.3)
┃ ┗ Referencing Microsoft.EntityFrameworkCore.Sqlite (NuGet package)
┕ MyAndroidApp (Xamarin Android)
┗ Referencing MyPclLibrary
In other words MyAndroidApp references MyPclLibrary which in turn references Microsoft.EntityFrameworkCore.Sqlite installed as a NuGet package. I work with Visual Studio 2015 Update 3 on a Window 7 SP1 machine.
The problem
Now the problem is that when I try to build the solution I get the following error message:
Exception while loading assemblies: System.IO.FileNotFoundException:
Could not load assembly 'Microsoft.EntityFrameworkCore.Sqlite,
Version=1.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.
Perhaps it doesn't exist in the Mono for Android profile?
I have already checked the MyAndroidApp\bin\Debug directory and I can see that the Sqlite DLL has NOT been copied to the output directory.
Of course, MyAndroidApp does NOT make direct calls to Microsoft.EntityFrameworkCore.Sqlite. Instead, MyPclLibrary is responsible for communication with Sqlite.
My best guess it that this problem is somehow related to the new dependency management based on project.json (MyPclLibrary uses project.json while MyAndroidApp uses packages.config).
What have I already tried?
Direct reference (works but is not acceptable): An easy fix would be to add reference to Microsoft.EntityFrameworkCore.Sqlite directly from MyAndroidApp - so make the library a first class reference. However this completely defies the solution architecture and therefore is not acceptable.
Dummy class: I thought the problem might be that there is no obvious usage of the Sqlite (as it is basically a set of extension methods). Therefore I created a dummy class with a single method which enforces a call to the Sqlite so the Sqlite may not be optimized out - to no avail.
No Linker in Adnroid Options: I have set the Project Properties (of MyAndroidApp) -> Android Options -> Linker -> Linking to None as Android Linker may ignore some assemblies that are used solely dynamically - again, to no avail.
Question
How can I use a secondary reference within a Xamarin Android project? I might have missed something really stupid so I am open to any suggestions.
There are two different ways of dependency management, just as your guess it. MyPclLibrary project use MyPclLibrary. deps.json to merge compilationOptions from the input project.json, while MyAndroidApp project use the packages.config to copy the reference dll to bin folder directly. So we could not copy the Sqlite DLL from the MyPclLibrary. deps.json to the MyAndroidApp\bin\Debug directory.
The workaround for this issue is that add reference to Microsoft.EntityFrameworkCore.Sqlite directly from MyAndroidApp (Just as you have already tried). The default behavior for Building Android Apps with Entity Framework is add the Sqlite NuGet packages to the Android project.

OCR Reader WS Upgrade from .Net Framework 2.0 to 4.0

I have been tasked with converting an old .Net 2.0 application to 4.0 and am running into a build error related to a missing dependency.
The build error is as follows:
Error 1 Could not load file or assembly 'Atalasoft.dotImage.Ocr.GlyphReader.DLL' or one of its dependencies. The specified module could not be found.
I was thinking that I needed to find a newer version of this DLL but I wanted to see if anyone knew an easier method for resolving this first.
Any help would be great!
A .NET 4.0 or later assembly can reference and use an assembly built on the .NET 2.0 framework. The problem is that the compiler cannot find the assembly, not that the assembly is the wrong version. Try the following steps, this should clear up your problem:
Highlight the reference in the project and go the the properties tab (F4 shortcut). Check the path that is used to reference the assembly and verify that it is actually located there.
Check if the project requires a specific version of the assembly. Go to the same properties page like mentioned in step 1 and see if specific version is set to true, change it to false if this is not a requirement for you.
If you are using NuGet ensure that the latest assemblies have been downloaded if you are not persisting your references to the source repository. You check this by going to the nuget project references and in the top right corner there will be a message asking you if you want to restore all package references if they cannot be found.
If you are not using nuget for this and it still does not work then remove the reference and manually re-add it to the project.

Categories