How can I make a Roslyn Analyzer project a transitive dependency? - c#

I have a library that relies on a source generator to work correctly. In the MyLibrary.csproj, I reference the generator like so.
<ItemGroup>
<ProjectReference
Include="..\MyLibrary.Generators\MyLibrary.Generators.csproj"
PrivateAssets="contentfiles;build"
ReferenceOutputAssembly="false"
OutputItemType="analyzer"/>
</ItemGroup>
I need this analyzer reference to be transitive, i.e. projects that reference MyLibrary should get the MyLibrary.Generators analyzer transitively.
A simple reference like so does not seem to reference the analyzer, only MyLibrary
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
I want to stress that I am not looking for MyLibrary.Generators to be consumed as a regular assembly reference, but as a Roslyn Analyzer so my source generator can run as intended during compile time.

I have received an answer to this question on GitHub.
It is sufficient for the library project to have the following reference to the generator project:
<ItemGroup>
<!-- Package the generator in the analyzer directory of the nuget package -->
<None Remove="$(OutputPath)/$(AssemblyName).Generator.dll" />
<None Include="$(OutputPath)/$(AssemblyName).Generator.dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
(I am not sure if the Remove line does anything. It might be unnecessary.)
When the library is consumed as a NuGet package, the generator is automatically included as an analyzer, i.e. the analyzer reference is transitive.
However, when the library is consumed through a project reference (such as in the library's own unit test project), we need the following:
<ItemGroup>
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
<ProjectReference Include="..\MyLibrary.Generator\MyLibrary.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
The examples in the original post all handle project references. From what I have gathered, a transitive project reference to an analyzer seems impossible, unfortunately.

Related

Resolving a circular dependency in C# using MSBuild

TL:DR;
Is there a way to resolve a "partial" circular dependency between C# projects using MSBuild wizardry? 🧙‍♂️
Long version
We have a project A that depends on project B. A.csproj looks like this:
<ItemGroup>
<ProjectReference Include="..\projectB\B.csproj" />
</ItemGroup>
Now project B needs a type from Project A. VS prevents us from adding a reference to project A (circular dependency) but it doesn't complain when we add a link to the specific file, like so in B.csproj:
<ItemGroup>
<Compile Include="..\projectA\ClassA.cs" Link="ClassA.cs" />
</ItemGroup>
The problem appears when we use ClassA on project A. Now VS complains (rightfully) about multiple declarations of ClassA (CS0436 - we have set warnings as errors), one from each project.
I am aware that this shows that our design is flawed and as other answers here in SO indicate the right solution would be to refactor. If this is not possible due to time constraints, is there any MSBuild trick that would allow projectA to compile?
For reference, things that I tried and don’t work
In A.csproj: set ReferenceOutputAssembly=false from project B.
=> Would work if project A doesn’t need anything from B.
In B.csproj: include and then remove 🥴
<ItemGroup>
<Compile Include="..\circdependency\ClassA.cs" Link="ClassA.cs" />
<Compile Remove="ClassA.cs" />
</ItemGroup>
In B.csproj: CopyToOutputDirectory=Never
Other options that work but are not ideal:
Suppressing the warning.
Duplicating ClassA in project B and using a different namespace, making it internal etc.

NuGet Msbuild Pack with non-SDK project

I have an "old style" .NET framework project which includes nuget references and other project references. Now I switched to the PackageReference format (removed the packages.config). I want to create a NuGet package for my project. So I added a reference to the "NuGet.Build.Tasks.Pack" package and used the MSBUILD pack target. In the first place it looked everything as expected, the resulting package contains all my references and the corresponding NuGet references. Now I have the problem, that I use also a project to project reference:
<ProjectReference Include="..\Wrapper\MyWrapper\MyWrapper.csproj">
<Project>{6b9a7dd0-b93f-3a5e-8fdf-99d0bf811652}</Project>
<Name>MyWrapper</Name>
</ProjectReference>
Based on the nuget documentation - for this reference:
Project to project references are considered by default as nuget
package references
But I want that this project reference is packaged into my package instead of a "nuget package reference". I found postings that using
PrivateAssets="all"
for the project reference could fix the problem, but adding this attribute to the project reference node does not change anything. Any help would be great!
I think you have missed something. It is not enough that you set PrivateAssets="all" for the ProjectReference. And actually, nuget will not view the referenced project as a nuget dependency and also nuget will not pack its assembly dll into the nupkg. You need other nodes.
Try these guidances:
Assume all your lib projects are target to net framework 4.7.2.
1) add the PrivateAssets="all" on the xxx.csproj file of the main project.
<ProjectReference Include="..\Wrapper\MyWrapper\MyWrapper.csproj">
<Project>{6b9a7dd0-b93f-3a5e-8fdf-99d0bf811652}</Project>
<Name>MyWrapper</Name>
<PrivateAssets>All</PrivateAssets>
</ProjectReference>
2) also add these node on the xxx.csproj file of the main project to pack the assembly dll into the nupkg:
<PropertyGroup>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
<ItemGroup>
<BuildOutputInPackage Include="#(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
</ItemGroup>
</Target>
3) then use this command to pack the project:
msbuild -t:rebuild,pack -p:PackageOutputPath=xxx\xxx -p:Authors="xxx" -p:Version="x.x.x"
Note: In my side, the main project called Mod and it references a project called Mod1. When I finish the pack process, you can see these in the nupkg.
It packs the refeneced dll as a lib rather than a nuget package.

.NET Core 2 Persistent Project References [duplicate]

While developing a sample web app in .NET Core 1.1 and Visual Studio 2017 RC, I realized the following:
As you can see:
ClassLibrary3 has a reference to ClassLibrary2,
and ClassLibrary2 has a reference to ClassLibrary1
I wrote a simple method in class Class3 of ClassLibrary3 project, and the Intellisense allowed me to use Class1 just writing the name of the class, I mean, without doing an explicit reference to ClassLibrary1 project.
Am I missing some point here? I don't want somebody simply comes and overlooks ClassLibrary2.
Thanks.
Transitive project-to-project references are a new feature of Visual Studio 2017 and Microsoft.NET.Sdk. This is intentional behavior.
See https://github.com/dotnet/sdk/issues/200.
If you're interested in disabling the transitive reference behavior, I finally found a way.
If you want Project A to reference B and B to reference C, but don't want A to reference C, you can add PrivateAssets="All" to B's ProjectReference to C, like so:
In B.csproj
<ItemGroup>
<ProjectReference Include="..\C\C.csproj" PrivateAssets="All" />
</ItemGroup>
This setting makes C's reference private so it only exists within B. Now projects that reference B will no longer also reference C.
Source: https://github.com/dotnet/project-system/issues/2313
You have two options.
In ClassLibrary1.csproj use DisableTransitiveProjectReferences property
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibraryCore2\ClassLibraryCore2.csproj" />
</ItemGroup>
</Project>
In ClassLibrary2.csproj use PrivateAssets="All"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibraryCore3\ClassLibraryCore3.csproj"
PrivateAssets="All" />
</ItemGroup>
</Project>
I explained the difference more in other answer.

Difference between .Net Framework and .Net Core Project Reference? [duplicate]

While developing a sample web app in .NET Core 1.1 and Visual Studio 2017 RC, I realized the following:
As you can see:
ClassLibrary3 has a reference to ClassLibrary2,
and ClassLibrary2 has a reference to ClassLibrary1
I wrote a simple method in class Class3 of ClassLibrary3 project, and the Intellisense allowed me to use Class1 just writing the name of the class, I mean, without doing an explicit reference to ClassLibrary1 project.
Am I missing some point here? I don't want somebody simply comes and overlooks ClassLibrary2.
Thanks.
Transitive project-to-project references are a new feature of Visual Studio 2017 and Microsoft.NET.Sdk. This is intentional behavior.
See https://github.com/dotnet/sdk/issues/200.
If you're interested in disabling the transitive reference behavior, I finally found a way.
If you want Project A to reference B and B to reference C, but don't want A to reference C, you can add PrivateAssets="All" to B's ProjectReference to C, like so:
In B.csproj
<ItemGroup>
<ProjectReference Include="..\C\C.csproj" PrivateAssets="All" />
</ItemGroup>
This setting makes C's reference private so it only exists within B. Now projects that reference B will no longer also reference C.
Source: https://github.com/dotnet/project-system/issues/2313
You have two options.
In ClassLibrary1.csproj use DisableTransitiveProjectReferences property
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<DisableTransitiveProjectReferences>true</DisableTransitiveProjectReferences>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibraryCore2\ClassLibraryCore2.csproj" />
</ItemGroup>
</Project>
In ClassLibrary2.csproj use PrivateAssets="All"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ClassLibraryCore3\ClassLibraryCore3.csproj"
PrivateAssets="All" />
</ItemGroup>
</Project>
I explained the difference more in other answer.

How do you multi-target a .NET Core class library with csproj?

When .NET Core still used the project.json format, you could build a class library targeting multiple frameworks (e.g. net451, netcoreapp1.0).
Now that the official project format is csproj using MSBuild, how do you specify multiple frameworks to target? I am trying to look for this from the project settings in VS2017, but I am able to only target a single framework from the .NET Core frameworks (it doesn't even list the other full .NET Framework versions which I do have installed):
You need to manually edit the project file and add s to the default TargetFramework and basically change it to TargetFrameworks. Then you mention the Moniker with a ; separator.
Also you can put the Nuget package references in a conditional ItemGroup manually or using VS Nuget Package Manager.
Here is what your .csproj should look like:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.6;net452</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">
<PackageReference Include="Microsoft.Azure.DocumentDB">
<Version>1.12.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
<PackageReference Include="Microsoft.Azure.DocumentDB.Core">
<Version>1.1.0</Version>
</PackageReference>
</ItemGroup>
</Project>
Another workaround I do these days because of missing documentation is that I create a project in VS2015 and form the project.json using the available documentation and intellisense, then open the solution in VS2017 and use the built-in upgrade. I will then look at the csproj file to figure out how to make that configuration happen.
Multi-targeting more esoteric targets without a Moniker:
Microsoft:
PCLs are not recommended+
Although PCLs are supported, package authors should support
netstandard instead. The .NET Platform Standard is an evolution of
PCLs and represents binary portability across platforms using a single
moniker that isn't tied to a static like like portable-a+b+c monikers.
If you want to target a Portable Profile it doesn't have a predefined moniker so Portable Profiles also can't infer TargetFrameworkIdentifier, TargetFrameworkVersion, and TargetFrameworkProfile. Also a compiler constant isn't defined automatically. Finally you have to add all assembly references none are provided by default.
This Example below is taken from a project that used the dynamic keyword so it additionally needed the Microsoft.CSharp assembly, thus you can see how it's references for different targets.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard1.5;net40;portable40-net45+sl5+win8+wp8</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
<TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile158</TargetFrameworkProfile>
<DefineConstants>$(DefineConstants);PORTABLE158</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard1.5'">
<PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
<PackageReference Include="System.ComponentModel" Version="4.3.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net40'">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows" />
</ItemGroup>
</Project>
You can manually edit .csproj file for this and set TargetFrameworks (not TargetFramework) property.
<TargetFrameworks>net451;netstandard1.4</TargetFrameworks>
For example see EFCore.csproj:
https://github.com/aspnet/EntityFrameworkCore/blob/951e4826a38ad5499b9b3ec6645e47c825fa842a/src/EFCore/EFCore.csproj
I actually selected Class Library (.NET Core).
That is not the project template you want if your library needs to work on multiple platform targets. With this project template, your library can only ever be used in a project that targets .NETCore. The PCL library approach was retired, you now have to pick a .NETStandard.
You do so by starting the project with the "Class Library (.NET Standard)" project template. You now have the option of picking the .NETStandard version. The current compatibility grid is here.
Hopefully they'll keep that linked article updated. This is in flux, .NETStandard 2.0 was nailed down but does not ship yet. Targeted for Q2 of 2017, end of spring probably, it currently shows as 97% done. I overheard the designers saying that using 1.5 or 1.6 is not recommended, not compatible enough with 2.0
I did a simple guide to multi-targeting net framework and netcore which starts with the minimum 15 second fix but then walks you through each of the complications.
The very simplest approach is:
First, get a netcore or netstandard target working.
Then
Edit the .csproj project file and work through these steps for the other targets.
Change the <TargetFramework> tag to <TargetFrameworks> and add your next target to the list, delimited by ;
Learn about conditional sections in your csproj file. Create one for each target. Use them to declare dependencies for each target.
Add <Reference />s for System.* dlls for any netframework targets just by reading what the build error messages say is missing.
Deal with NuGet <PackageReference />s dependencies in the cases where they are not the same for each target. (The easiest trick here is to temporarily revert to single targetting so that the GUI will just handle the Nuget references correctly for you).
If you must: learn a creative variety of techniques, workarounds and timesavers to deal with code that doesn’t compile on all targets.
Know when to cut your losses when the cost of adding more targets is too high.

Categories

Resources