Skip to content

Commit d3af492

Browse files
authored
Define convention to consume and/or package analyzers (#69069)
* Define convention to include analyzers in ref pack Fixes #61321 Until now we required source libraries to define ProjectReferences when an analyzer should be part of the shared framework. That strategy causes analyzer projects to leak into the ProjectReference closure and by that into a solution file. As an example: When another library references the source library that references the analyzer, the analyzer is part of the dependency closure even though it might not be required. This change makes it possible to define the shared framework analyzer projects in the NetCoreAppLibrary.props file for both the .NETCoreApp, and the AspNetCoreApp shared framework. Out-of-band projects which ship analyzers inside their produced package, continue to reference the analyzers via the `AnalyzerProject` item. * Use AnalyzerReference consistently * Don't reference analyzer when its packaged * Fix P2P reference * Fix multi target roslyn component target condition
1 parent 35e4779 commit d3af492

File tree

20 files changed

+137
-94
lines changed

20 files changed

+137
-94
lines changed

docs/coding-guidelines/libraries-packaging.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ In some occasions we may want to include a library in the shared framework, but
1414

1515
Libraries included in the shared framework should ensure all direct and transitive assembly references are also included in the shared framework. This will be validated as part of the build and errors raised if any dependencies are unsatisfied.
1616

17-
Source generators and analyzers can be included in the shared framework by specifying `IsNetCoreAppAnalyzer`. These projects should specify `AnalyzerLanguage` as mentioned [below](#analyzers--source-generators).
17+
Source generators and analyzers can be included in the shared framework by adding their project name into the NetCoreAppLibrary.props file under the `NetCoreAppLibraryGenerator` section. These projects should specify `AnalyzerLanguage` as mentioned [below](#analyzers--source-generators).
1818

1919
Removing a library from the shared framework is a breaking change and should be avoided.
2020

@@ -70,10 +70,10 @@ Build props and targets may be needed in NuGet packages. To define these, author
7070

7171
Some packages may wish to include a companion analyzer or source-generator with their library. Analyzers are much different from normal library contributors: their dependencies shouldn't be treated as nuget package dependencies, their TargetFramework isn't applicable to the project they are consumed in (since they run in the compiler). To facilitate this, we've defined some common infrastructure for packaging Analyzers.
7272

73-
To include an analyzer in a package, simply add an `AnalyzerReference` item to the project that produces the package that should contain the analyzer
73+
To include an analyzer in a package, simply add an `AnalyzerReference` item to the project that produces the package that should contain the analyzer and set the `Pack` metadata to true. If you just want to include the analyzer but not consume it, set the `ReferenceAnalyzer` metadata to false.
7474
```xml
7575
<ItemGroup>
76-
<AnalyzerReference Include="..\gen\System.Banana.Generators.csproj" />
76+
<AnalyzerReference Include="..\gen\System.Banana.Generators.csproj" Pack="true" ReferenceAnalyzer="false" />
7777
</ItemGroup>
7878
```
7979

docs/coding-guidelines/project-guidelines.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ All test outputs should be under
188188

189189
## gen
190190
In the gen directory any source generator related to the assembly should exist. This does not mean the source generator is only used for that assembly only that it is conceptually apart of that assembly. For example, the assembly may provide attributes or low-level types the source generator uses.
191+
To consume a source generator, simply add an `<AnalyzerReference Include="..." />` item to the project, usually next to the `References` and `ProjectReferences` items.
191192

192193
## Facades
193194
Facade are unique in that they don't have any code and instead are generated by finding a contract reference assembly with the matching identity and generating type forwards for all the types to where they live in the implementation assemblies (aka facade seeds). There are also partial facades which contain some type forwards as well as some code definitions. All the various build configurations should be contained in the one csproj file per library.

eng/generators.targets

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,18 @@
4141
That is required as the EnabledGenerators condition checks on the Reference and ProjectReference items and hence can't be a property condition. -->
4242
<ItemGroup Condition="'@(EnabledGenerators)' != '' and
4343
@(EnabledGenerators->AnyHaveMetadataValue('Identity', 'LibraryImportGenerator'))">
44-
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj;
45-
$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj"
46-
OutputItemType="Analyzer"
47-
ReferenceOutputAssembly="false" />
44+
<AnalyzerReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj;
45+
$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" />
4846
</ItemGroup>
4947

50-
<!-- Use a normal property condition as this source generator is opt-in and doesn't read from an item list. -->
51-
<ItemGroup Condition="'$(EnableRegexGenerator)' == 'true'">
52-
<ProjectReference Include="$(LibrariesProjectRoot)System.Text.RegularExpressions\gen\System.Text.RegularExpressions.Generator.csproj"
48+
<!-- AnalyzerReference items are transformed to ProjectReferences with the required analyzer metadata. -->
49+
<ItemGroup>
50+
<ProjectReference Include="@(AnalyzerReference)"
51+
ReferenceOutputAssembly="false"
5352
OutputItemType="Analyzer"
54-
ReferenceOutputAssembly="false" />
53+
Pack="false" />
54+
<ProjectReference Update="@(AnalyzerReference->WithMetadataValue('ReferenceAnalyzer', 'false'))"
55+
OutputItemType="" />
5556
</ItemGroup>
5657

5758
<Target Name="ConfigureGenerators"

eng/packaging.targets

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,12 @@
137137
</ItemGroup>
138138
</Target>
139139

140-
<Target Name="IncludeAnalyzersInPackage" Condition="'@(AnalyzerReference)' != ''">
141-
<!-- Call a target in the analyzer project to get all the files it would normally place in a package.
142-
These will be returned as items with identity pointing to the built file, and PackagePath metadata
143-
set to their location in the package. IsSymbol metadata will be set to distinguish symbols. -->
144-
<MSBuild Projects="@(AnalyzerReference)"
140+
<!-- Call a target in the analyzer project to get all the files it would normally place in a package.
141+
These will be returned as items with identity pointing to the built file, and PackagePath metadata
142+
set to their location in the package. IsSymbol metadata will be set to distinguish symbols. -->
143+
<Target Name="IncludeAnalyzersInPackage"
144+
Condition="'@(AnalyzerReference)' != '' and @(AnalyzerReference->AnyHaveMetadataValue('Pack', 'true'))">
145+
<MSBuild Projects="@(AnalyzerReference->WithMetadataValue('Pack', 'true'))"
145146
Targets="GetAnalyzerPackFiles">
146147
<Output TaskParameter="TargetOutputs" ItemName="_AnalyzerFile" />
147148
</MSBuild>
@@ -165,7 +166,9 @@
165166
<!-- In packages that contain Analyzers, include a .targets file that will select the correct analyzer. -->
166167
<Target Name="IncludeMultiTargetRoslynComponentTargetsInPackage"
167168
AfterTargets="IncludeAnalyzersInPackage"
168-
Condition="'@(AnalyzerReference)' != '' and '$(IncludeMultiTargetRoslynComponentTargets)' == 'true'"
169+
Condition="'@(AnalyzerReference)' != '' and
170+
@(AnalyzerReference->AnyHaveMetadataValue('Pack', 'true')) and
171+
'$(IncludeMultiTargetRoslynComponentTargets)' == 'true'"
169172
DependsOnTargets="GenerateMultiTargetRoslynComponentTargetsFile">
170173
<ItemGroup>
171174
<Content Include="$(MultiTargetRoslynComponentTargetsFileIntermediatePath)" PackagePath="buildTransitive\netstandard2.0\$(PackageId).targets" />

src/libraries/Directory.Build.targets

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
<IsNETCoreAppRef Condition="('$(IsReferenceAssemblyProject)' == 'true' or '$(IsRuntimeAndReferenceAssembly)' == 'true') and
4848
$(NetCoreAppLibrary.Contains('$(AssemblyName);')) and
4949
!$(NetCoreAppLibraryNoReference.Contains('$(AssemblyName);'))">true</IsNETCoreAppRef>
50+
<IsNETCoreAppAnalyzer Condition="'$(IsGeneratorProject)' == 'true' and
51+
$(NetCoreAppLibraryGenerator.Contains('$(MSBuildProjectName);'))">true</IsNETCoreAppAnalyzer>
5052
<!-- By default, disable implicit framework references for NetCoreAppCurrent libraries. -->
5153
<DisableImplicitFrameworkReferences Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and
5254
$([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '$(NETCoreAppCurrentVersion)')) and
@@ -201,25 +203,6 @@
201203
</When>
202204
</Choose>
203205

204-
<PropertyGroup>
205-
<BuildAnalyzerReferences>$(BuildProjectReferences)</BuildAnalyzerReferences>
206-
<BuildAnalyzerReferences Condition="'$(BuildingInsideVisualStudio)' == 'true'">false</BuildAnalyzerReferences>
207-
</PropertyGroup>
208-
209-
<ItemGroup>
210-
<!-- Ensure AnalyzerReference items are restored and built
211-
The target framework of Analyzers has no relationship to that of the refrencing project,
212-
so we don't apply TargetFramework filters nor do we pass in TargetFramework.
213-
When BuildProjectReferences=false we make sure to set BuildReference=false to make
214-
sure not to try to call GetTargetPath in the outerbuild of the analyzer project. -->
215-
<ProjectReference Include="@(AnalyzerReference)"
216-
SkipGetTargetFrameworkProperties="true"
217-
UndefineProperties="TargetFramework"
218-
ReferenceOutputAssembly="false"
219-
PrivateAssets="all"
220-
BuildReference="$(BuildAnalyzerReferences)" />
221-
</ItemGroup>
222-
223206
<Target Name="GetAnalyzerPackFiles"
224207
DependsOnTargets="$(GenerateNuspecDependsOn)"
225208
Returns="@(_AnalyzerPackFile)">
@@ -228,11 +211,13 @@
228211
<_analyzerPath Condition="'$(AnalyzerRoslynVersion)' != ''">$(_analyzerPath)/roslyn$(AnalyzerRoslynVersion)</_analyzerPath>
229212
<_analyzerPath Condition="'$(AnalyzerLanguage)' != ''">$(_analyzerPath)/$(AnalyzerLanguage)</_analyzerPath>
230213
</PropertyGroup>
214+
231215
<ItemGroup>
232216
<_AnalyzerPackFile Include="@(_BuildOutputInPackage)" IsSymbol="false" />
233217
<_AnalyzerPackFile Include="@(_TargetPathsToSymbols)" IsSymbol="true" />
234218
<_AnalyzerPackFile PackagePath="$(_analyzerPath)/%(TargetPath)" />
235219
</ItemGroup>
220+
236221
<Error Condition="'%(_AnalyzerPackFile.TargetFramework)' != 'netstandard2.0'"
237222
Text="Analyzers must only target netstandard2.0 since they run in the compiler which targets netstandard2.0. The following files were found to target '%(_AnalyzerPackFile.TargetFramework)': @(_AnalyzerPackFile)" />
238223
</Target>

src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ Microsoft.Extensions.Logging.Abstractions.NullLogger</PackageDescription>
3838

3939
<ItemGroup>
4040
<AnalyzerReference Include="..\gen\Microsoft.Extensions.Logging.Generators.Roslyn3.11.csproj"
41+
Pack="true"
42+
ReferenceAnalyzer="false"
4143
Condition="'$(DotNetBuildFromSource)' != 'true'" />
42-
<AnalyzerReference Include="..\gen\Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj" />
44+
<AnalyzerReference Include="..\gen\Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj"
45+
Pack="true"
46+
ReferenceAnalyzer="false" />
4347
</ItemGroup>
4448

4549
</Project>

src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
<ItemGroup>
1919
<!-- Requires Private=true to calculate ReferenceCopyLocalPaths items. Also share System.Net.Quic which isn't part of aspnetcore's shared framework but which is needed by them. -->
2020
<ProjectReference Include="@(AspNetCoreAppLibrary->'$(LibrariesProjectRoot)%(Identity)\src\%(Identity).csproj');
21-
$(LibrariesProjectRoot)System.Net.Quic\src\System.Net.Quic.csproj" PrivateAssets="all" Pack="true" Private="true" IncludeReferenceAssemblyInPackage="true" />
22-
<!-- TODO: Find a better way to include source generators without hardcoding them. -->
21+
$(LibrariesProjectRoot)System.Net.Quic\src\System.Net.Quic.csproj"
22+
Pack="true"
23+
PrivateAssets="all"
24+
Private="true"
25+
IncludeReferenceAssemblyInPackage="true" />
2326
<!-- Only include the 4.0 version in the ref pack, since targeting net6.0 requires Roslyn 4.0 -->
24-
<AnalyzerReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Logging.Abstractions\gen\Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj" />
27+
<AnalyzerReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Logging.Abstractions\gen\Microsoft.Extensions.Logging.Generators.Roslyn4.0.csproj"
28+
Pack="true"
29+
ReferenceAnalyzer="false" />
2530
</ItemGroup>
2631
</Project>

src/libraries/NetCoreAppLibrary.props

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@
183183
System.Private.Xml;
184184
System.Private.Xml.Linq;
185185
</NetCoreAppLibraryNoReference>
186+
<!-- List .NETCoreApp shared framework generator project names below. -->
187+
<NetCoreAppLibraryGenerator>
188+
LibraryImportGenerator;
189+
System.Text.Json.SourceGeneration.Roslyn4.0;
190+
System.Text.RegularExpressions.Generator;
191+
</NetCoreAppLibraryGenerator>
186192
<AspNetCoreAppLibrary>
187193
Microsoft.Extensions.Caching.Abstractions;
188194
Microsoft.Extensions.Caching.Memory;
@@ -245,6 +251,7 @@
245251
<NetFxReference Include="$(NetFxReference)" />
246252
<NetCoreAppLibrary Include="$(NetCoreAppLibrary)" />
247253
<NetCoreAppLibraryNoReference Include="$(NetCoreAppLibraryNoReference)" />
254+
<NetCoreAppLibraryGenerator Include="$(NetCoreAppLibraryGenerator)" />
248255
<AspNetCoreAppLibrary Include="$(AspNetCoreAppLibrary)" />
249256
<WindowsDesktopCoreAppLibrary Include="$(WindowsDesktopCoreAppLibrary)" />
250257
</ItemGroup>

src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3+
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
34
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
45
<IsPartialFacadeAssembly>true</IsPartialFacadeAssembly>
5-
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
6-
<EnableRegexGenerator>true</EnableRegexGenerator>
76
</PropertyGroup>
7+
88
<ItemGroup>
99
<ILLinkSubstitutionsXmls Include="$(ILLinkDirectory)ILLink.Substitutions.xml" />
1010
</ItemGroup>
11+
1112
<ItemGroup>
1213
<Compile Include="MS\Internal\Xml\Linq\ComponentModel\XComponentModel.cs" />
1314
<Compile Include="System\ComponentModel\ArrayConverter.cs" />
@@ -238,7 +239,9 @@
238239
<Compile Include="System\ComponentModel\ComponentResourceManager.cs" />
239240
<Compile Include="System\Security\Authentication\ExtendedProtection\ExtendedProtectionPolicyTypeConverter.cs" />
240241
</ItemGroup>
242+
241243
<ItemGroup>
244+
<AnalyzerReference Include="$(LibrariesProjectRoot)System.Text.RegularExpressions\gen\System.Text.RegularExpressions.Generator.csproj" />
242245
<Reference Include="System.Collections" />
243246
<Reference Include="System.Collections.NonGeneric" />
244247
<Reference Include="System.Collections.Specialized" />

src/libraries/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3+
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
34
<NoWarn>$(NoWarn);1634;1691;649</NoWarn>
45
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
5-
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
66
<!-- Too much private reflection. Do not bother with trimming -->
77
<ILLinkTrimAssembly>false</ILLinkTrimAssembly>
8-
<EnableRegexGenerator>true</EnableRegexGenerator>
98
</PropertyGroup>
9+
1010
<PropertyGroup>
1111
<RuntimeSerializationSources>System\Runtime\Serialization</RuntimeSerializationSources>
1212
<JsonSources>System\Runtime\Serialization\Json</JsonSources>
1313
<XmlSources>System\Xml</XmlSources>
1414
<TextSources>System\Text</TextSources>
1515
</PropertyGroup>
16+
1617
<ItemGroup>
1718
<Compile Include="$(CommonPath)System\NotImplemented.cs"
1819
Link="Common\System\NotImplemented.cs" />
@@ -149,7 +150,9 @@
149150
<Compile Include="System\Xml\XmlCanonicalWriter.cs" />
150151
<Compile Include="System\Xml\XmlSigningNodeWriter.cs" />
151152
</ItemGroup>
153+
152154
<ItemGroup>
155+
<AnalyzerReference Include="$(LibrariesProjectRoot)System.Text.RegularExpressions\gen\System.Text.RegularExpressions.Generator.csproj" />
153156
<Reference Include="System.Collections" />
154157
<Reference Include="System.Collections.Concurrent" />
155158
<Reference Include="System.Collections.NonGeneric" />

0 commit comments

Comments
 (0)