Skip to content

Commit a50f309

Browse files
authored
Add Analyzer packaging support and packaging documentation (#52554)
* Add Analyzer packaging support and packaging documentation To package Microsoft.Extensions.Logging.Abstractions we needed support for packing an Analyzer. This adds that support. I wanted to document this addition, so I created the start of a doc that's meant to describe the packaging options for libraries in dotnet/runtime. * Address code review feedback. * More feedback * Address more feedback * Remove src.proj build of generators * Update to use Microsoft.DotNet.PackageTesting * Fix typos * Fix package testing on net46*
1 parent 5045334 commit a50f309

12 files changed

+173
-30
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Packaging
2+
3+
Libraries can be packaged in one or more of the following ways: as part of the .NETCore shared framework, part of a transport package, or as a NuGet package.
4+
5+
## .NETCore Shared framework
6+
7+
To add a library to the .NETCore shared framework, that library's `AssemblyName` should be added to [NetCoreAppLibrary.props](../../src/libraries/NetCoreAppLibrary.props)
8+
9+
The library should have both a `ref` and `src` project. Its reference assembly will be included in the ref-pack for the Microsoft.NETCore.App shared framework, and its implementation assembly will be included in the runtime pack.
10+
11+
Including a library in the shared framework only includes the best applicable TargetFramework build of that library: `$(NetCoreAppCurrent)` if it exists, but possibly `netstandard2.1` or another if that is best. If a library has builds for other frameworks those will only be shipped if the library also produces a [Nuget package](#nuget-package). If a library ships both in the shared framework and a nuget package, it may decide to exclude its latest `$(NetCoreAppCurrent)` build from the package. This can be done by setting `ExcludeCurrentNetCoreAppFromPackage` to true. Libraries should take care when doing this to ensure that whatever asset in the package that would apply to `$(NetCoreAppCurrent)` is functionally equivalent to that which it replaces from the shared framework, to avoid breaking applications which reference a newer package than the shared framework. If possible, it's preferable to avoid this by choosing to target frameworks which can both ship in the package and shared framework.
12+
13+
In some occasions we may want to include a library in the shared framework, but not expose it publicly. To do so, include the library in the `NetCoreAppLibraryNoReference` property in [NetCoreAppLibrary.props](../../src/libraries/NetCoreAppLibrary.props). The library should also be named in a way to discourage use at runtime, for example using the `System.Private` prefix. We should avoid hiding arbitrary public libraries as it complicates deployment and servicing, though some platform specific libraries are in this state due to historical reasons.
14+
15+
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.
16+
17+
Removing a library from the shared framework is a breaking change and should be avoided.
18+
19+
## Transport package
20+
21+
Transport packages are non-shipping packages that dotnet/runtime produces in order to share binaries with other repositories.
22+
23+
### Microsoft.AspNetCore.Internal.Transport
24+
25+
This package represents the set of libraries which are produced in dotnet/runtime and ship in the ASP.NETCore shared framework. We produce a transport package so that we can easily share reference assemblies and implementation configurations that might not be present in NuGet packages that also ship.
26+
27+
To add a library to the ASP.NETCore shared framework, that library should set the `IsAspNetCoreApp` property for its `ref` and `src` project. This is typically done in the library's `Directory.Build.props`, for example https://github.com/dotnet/runtime/blob/98ac23212e6017c615e7e855e676fc43c8e44cb8/src/libraries/Microsoft.Extensions.Logging.Abstractions/Directory.Build.props#L4.
28+
29+
Libraries included in this transport package should ensure all direct and transitive assembly references are also included in either the ASP.NETCore shared framework or the .NETCore shared framework. This is not validated in dotnet/runtime at the moment: https://github.com/dotnet/runtime/issues/52562
30+
31+
Removing a library from this transport package is a breaking change and should be avoided.
32+
33+
## NuGet package
34+
35+
Libraries to be packaged must be referenced by the [traversal packaging project](../../src/libraries/libraries-packages.proj) and set `IsPackable` to true. By default, all `Libraries/*/src` projects are considered for packaging.
36+
37+
Package versions and shipping state should be controlled using the properties defined by the [Arcade SDK](https://github.com/dotnet/arcade/blob/master/Documentation/ArcadeSdk.md#project-properties-defined-by-the-sdk). Typically libraries should not need to explicitly set any of these properties.
38+
39+
Most metadata for packages is controlled centrally in the repository and individual projects may not need to make any changes to these. One property is required to be set in each project: `PackageDescription`. This should be set to a descriptive summary of the purpose of the package, and a list of common entry-point types for the package: to aide in search engine optimization. Example:
40+
```xml
41+
<PackageDescription>Logging abstractions for Microsoft.Extensions.Logging.
42+
43+
Commonly Used Types:
44+
Microsoft.Extensions.Logging.ILogger
45+
Microsoft.Extensions.Logging.ILoggerFactory
46+
Microsoft.Extensions.Logging.ILogger&lt;TCategoryName&gt;
47+
Microsoft.Extensions.Logging.LogLevel
48+
Microsoft.Extensions.Logging.Logger&lt;T&gt;
49+
Microsoft.Extensions.Logging.LoggerMessage
50+
Microsoft.Extensions.Logging.Abstractions.NullLogger</PackageDescription>
51+
```
52+
53+
Package content can be defined using any of the publicly defined Pack inputs: https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets
54+
55+
### TargetFrameworks
56+
57+
By default all TargetFrameworks listed in your project will be included in the package. You may exclude specific TargetFrameworks by setting `ExcludeFromPackage` on that framework.
58+
```xml
59+
<PropertyGroup>
60+
<ExcludeFromPackage Condition="'$(TargetFramework)' == 'net5.0'">true</ExcludeFromPackage>
61+
</PropertyGroup>
62+
```
63+
64+
A common pattern is to build for the latest .NET version, for example to include a library in the shared framework or a transport package, but then excluded this from the NuGet package. This can be done to avoid growing the NuGet package in size. To do this set
65+
```xml
66+
<PropertyGroup>
67+
<ExcludeCurrentNetCoreAppFromPackage>true</ExcludeCurrentNetCoreAppFromPackage>
68+
</PropertyGroup>
69+
```
70+
71+
When excluding TargetFrameworks from a package special care should be taken to ensure that the builds included are equivalent to those excluded. Avoid ifdef'ing the implementation only in an excluded TargetFramework. Doing so will result in testing something different than what we ship, or shipping a nuget package that degrades the shared framework.
72+
73+
### Build props / targets and other content
74+
75+
Build props and targets may be needed in NuGet packages. To define these, author a build folder in your src project and place the necessary props/targets in this subfolder. You can then add items to include these in the package by defining `Content` items and setting `PackagePath` as follows:
76+
```xml
77+
<ItemGroup>
78+
<Content Include="build\netstandard2.0\$(MSBuildProjectName).props" PackagePath="%(Identity)" />
79+
<Content Include="build\netstandard2.0\$(MSBuildProjectName).targets" PackagePath="%(Identity)" />
80+
</ItemGroup>
81+
```
82+
83+
### Analyzers / source generators
84+
85+
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.
86+
87+
To include an analyzer in a package, simply add an `AnalyzerReference` item to the project that produces the package that should contain the analyzer
88+
```xml
89+
<ItemGroup>
90+
<AnalyzerReference Include="..\gen\System.Banana.Generators.csproj" />
91+
</ItemGroup>
92+
```
93+
94+
In the analyzer project make sure to do the following. Ensure it only targets `netstandard2.0` since this is a requirement of the compiler. Enable localization by setting `UsingToolXliff`. Set the `AnalyzerLanguage` property to either `cs` or `vb` if the analyzer is specific to that language. By default the analyzer will be packaged as language-agnostic. Avoid any dependencies in Analyzer projects that aren't already provided by the compiler.
95+
```xml
96+
<PropertyGroup>
97+
<TargetFrameworks>netstandard2.0</TargetFrameworks>
98+
<UsingToolXliff>true</UsingToolXliff>
99+
<AnalyzerLanguage>cs</AnalyzerLanguage>
100+
</PropertyGroup>
101+
```

docs/coding-guidelines/package-projects.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Package projects
2+
** IMPORTANT ** Package projects are in the process of being deprecated, please do not create new package projects. Instead use normal projects (csproj) following the guidelines here: [libraries-packaging](libraries-packaging.md)
3+
24
Package projects bring together all the assemblies that make up a library on different platforms into a set of NuGet packages.
35

46
## Package hierarchy

eng/Tools.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
<ItemGroup>
99
<PackageReference Include="Microsoft.DotNet.Build.Tasks.Packaging" Version="$(MicrosoftDotNetBuildTasksPackagingVersion)" />
10-
<PackageReference Include="Microsoft.DotNet.PackageValidation" Version="$(MicrosoftDotNetPackageValidationVersion)" />
10+
<PackageReference Include="Microsoft.DotNet.PackageTesting" Version="$(MicrosoftDotNetPackageTestingVersion)" />
1111
<!-- enable source link in pkgproj -->
1212
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkVersion)" PrivateAssets="all" IsImplicitlyDefined="true" />
1313
<PackageReference Include="Microsoft.SourceLink.AzureRepos.Git" Version="$(MicrosoftSourceLinkVersion)" PrivateAssets="all" IsImplicitlyDefined="true" />

eng/Version.Details.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@
186186
<Uri>https://github.com/dotnet/xharness</Uri>
187187
<Sha>9596c3fbbb718819bc4bb9a9e3f6271b50fc718a</Sha>
188188
</Dependency>
189-
<Dependency Name="Microsoft.DotNet.PackageValidation" Version="6.0.0-beta.21260.1">
189+
<Dependency Name="Microsoft.DotNet.PackageTesting" Version="6.0.0-beta.21263.1">
190190
<Uri>https://github.com/dotnet/arcade</Uri>
191-
<Sha>44324e2d3563921f60b1522fccf3fef45dcfe636</Sha>
191+
<Sha>6b9758661f4483a70654bcaf6f8d7c6a79ee5660</Sha>
192192
</Dependency>
193193
<Dependency Name="optimization.windows_nt-x64.MIBC.Runtime" Version="1.0.0-prerelease.21255.6">
194194
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>

eng/Versions.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
<MicrosoftDotNetBuildTasksInstallersVersion>6.0.0-beta.21263.1</MicrosoftDotNetBuildTasksInstallersVersion>
6464
<MicrosoftDotNetRemoteExecutorVersion>6.0.0-beta.21263.1</MicrosoftDotNetRemoteExecutorVersion>
6565
<MicrosoftDotNetVersionToolsTasksVersion>6.0.0-beta.21263.1</MicrosoftDotNetVersionToolsTasksVersion>
66-
<MicrosoftDotNetPackageValidationVersion>6.0.0-beta.21260.1</MicrosoftDotNetPackageValidationVersion>
66+
<MicrosoftDotNetPackageTestingVersion>6.0.0-beta.21263.1</MicrosoftDotNetPackageTestingVersion>
6767
<!-- NuGet dependencies -->
6868
<NuGetBuildTasksPackVersion>5.9.0-preview.2</NuGetBuildTasksPackVersion>
6969
<!-- Installer dependencies -->

src/libraries/Directory.Build.targets

+46
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,50 @@
301301
</PropertyGroup>
302302
</Target>
303303

304+
<PropertyGroup>
305+
<BeforePack>IncludeAnalyzersInPackage;$(BeforePack)</BeforePack>
306+
</PropertyGroup>
307+
308+
<ItemGroup>
309+
<!-- Ensure AnalyzerReference items are restored and built
310+
The target framework of Analyzers has no relationship to that of the refrencing project,
311+
so we don't apply TargetFramework filters nor do we pass in TargetFramework.
312+
When BuildProjectReferences=false we make sure to set BuildReference=false to make
313+
sure not to try to call GetTargetPath in the outerbuild of the analyzer project. -->
314+
<ProjectReference Include="@(AnalyzerReference)" SkipGetTargetFrameworkProperties="true" UndefineProperties="TargetFramework" ReferenceOutputAssembly="false" PrivateAssets="all" BuildReference="$(BuildProjectReferences)" />
315+
</ItemGroup>
316+
317+
<Target Name="IncludeAnalyzersInPackage" Condition="'@(AnalyzerReference)' != ''">
318+
<!-- Call a target in the analyzer project to get all the files it would normally place in a package.
319+
These will be returned as items with identity pointing to the built file, and PackagePath metadata
320+
set to their location in the package. IsSymbol metadata will be set to distinguish symbols. -->
321+
<MSBuild Projects="@(AnalyzerReference)"
322+
Targets="GetAnalyzerPackFiles">
323+
<Output TaskParameter="TargetOutputs" ItemName="_AnalyzerFile" />
324+
</MSBuild>
325+
326+
<ItemGroup>
327+
<Content Include="@(_AnalyzerFile)" Pack="True" Condition="!%(_AnalyzerFile.IsSymbol)" />
328+
<!-- Symbols don't honor PackagePath. By default they are placed in lib/%(TargetFramework).
329+
Pack does honor TargetPath and does Path.Combine("lib/%(TargetFramework)", "%(TargetPath)"),
330+
so a rooted path value for TargetPath will override lib.
331+
https://github.com/NuGet/Home/issues/10860 -->
332+
<_TargetPathsToSymbols Include="@(_AnalyzerFile)" TargetPath="/%(_AnalyzerFile.PackagePath)" Condition="%(_AnalyzerFile.IsSymbol)" />
333+
</ItemGroup>
334+
</Target>
335+
336+
<Target Name="GetAnalyzerPackFiles" DependsOnTargets="$(GenerateNuspecDependsOn)" Returns="@(_AnalyzerPackFile)">
337+
<PropertyGroup>
338+
<_analyzerPath>analyzers/dotnet</_analyzerPath>
339+
<_analyzerPath Condition="'$(AnalyzerLanguage)' != ''">$(_analyzerPath)/$(AnalyzerLanguage)</_analyzerPath>
340+
</PropertyGroup>
341+
<ItemGroup>
342+
<_AnalyzerPackFile Include="@(_BuildOutputInPackage)" IsSymbol="false" />
343+
<_AnalyzerPackFile Include="@(_TargetPathsToSymbols)" IsSymbol="true" />
344+
<_AnalyzerPackFile PackagePath="$(_analyzerPath)/%(TargetPath)" />
345+
</ItemGroup>
346+
<Error Condition="'%(_AnalyzerPackFile.TargetFramework)' != 'netstandard2.0'"
347+
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)" />
348+
</Target>
349+
304350
</Project>

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

+2-10
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,10 @@
77
<EnableDefaultEmbeddedResourceItems>false</EnableDefaultEmbeddedResourceItems>
88
<UsingToolXliff>true</UsingToolXliff>
99
<CLSCompliant>false</CLSCompliant>
10+
<IsPackable>false</IsPackable>
11+
<AnalyzerLanguage>cs</AnalyzerLanguage>
1012
</PropertyGroup>
1113

12-
<ItemGroup>
13-
<PackageDestination Include="analyzers\dotnet\cs" />
14-
</ItemGroup>
15-
16-
<Target Name="IncludeSatteliteResourceInPackage" BeforeTargets="GetFilesToPackage" DependsOnTargets="SatelliteDllsProjectOutputGroup">
17-
<ItemGroup>
18-
<AdditionalFileToPackage Include="%(SatelliteDllsProjectOutputGroupOutput.FullPath)" SubFolder="/%(SatelliteDllsProjectOutputGroupOutput.Culture)" />
19-
</ItemGroup>
20-
</Target>
21-
2214
<ItemGroup>
2315
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(MicrosoftCodeAnalysisCSharpWorkspacesVersion)" PrivateAssets="all" />
2416
<PackageReference Include="Microsoft.DotNet.Build.Tasks.Packaging" Version="$(MicrosoftDotNetBuildTasksPackagingVersion)" PrivateAssets="all" />

src/libraries/Microsoft.Extensions.Logging.Abstractions/pkg/Microsoft.Extensions.Logging.Abstractions.pkgproj

-10
This file was deleted.

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,16 @@
88
<!-- Use targeting pack references instead of granular ones in the project file. -->
99
<DisableImplicitAssemblyReferences>false</DisableImplicitAssemblyReferences>
1010
<Nullable>enable</Nullable>
11-
<IsPackable>false</IsPackable>
11+
<PackageDescription>Logging abstractions for Microsoft.Extensions.Logging.
12+
13+
Commonly Used Types:
14+
Microsoft.Extensions.Logging.ILogger
15+
Microsoft.Extensions.Logging.ILoggerFactory
16+
Microsoft.Extensions.Logging.ILogger&lt;TCategoryName&gt;
17+
Microsoft.Extensions.Logging.LogLevel
18+
Microsoft.Extensions.Logging.Logger&lt;T&gt;
19+
Microsoft.Extensions.Logging.LoggerMessage
20+
Microsoft.Extensions.Logging.Abstractions.NullLogger</PackageDescription>
1221
</PropertyGroup>
1322

1423
<ItemGroup>
@@ -27,4 +36,8 @@
2736
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
2837
</ItemGroup>
2938

39+
<ItemGroup>
40+
<AnalyzerReference Include="..\gen\Microsoft.Extensions.Logging.Generators.csproj" />
41+
</ItemGroup>
42+
3043
</Project>

src/libraries/System.Text.Json/src/System.Text.Json.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@
309309
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" Condition="'$(TargetFramework)' == 'net461'" />
310310
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Bcl.AsyncInterfaces\src\Microsoft.Bcl.AsyncInterfaces.csproj" />
311311
</ItemGroup>
312+
<ItemGroup>
313+
<AnalyzerReference Include="..\gen\System.Text.Json.SourceGeneration.csproj" />
314+
</ItemGroup>
312315

313316
<!-- Application tfms (.NETCoreApp, .NETFramework) need to use the same or higher version of .NETStandard's dependencies. -->
314317
<Choose>

src/libraries/pkg/test/testPackages.proj

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
<Copy SourceFiles="@(TestSupportFiles)" DestinationFolder="%(TestSupportFiles.DestinationFolder)" />
8383
</Target>
8484

85-
<UsingTask TaskName="GetCompatiblePackageTargetFrameworks" AssemblyFile="$(DotNetPackageValidationAssembly)"/>
85+
<UsingTask TaskName="GetCompatiblePackageTargetFrameworks" AssemblyFile="$(DotNetPackageTestingAssembly)"/>
8686

8787
<Target Name="GetSupportedPackages">
8888
<GetCompatiblePackageTargetFrameworks PackagePaths="@(TestPackagesPath)">

src/libraries/src.proj

-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@
77
<ItemGroup>
88
<_allSrc Include="$(MSBuildThisFileDirectory)*\src\*.csproj"
99
Exclude="@(ProjectExclusions)" />
10-
<NonNetCoreAppProject Include="$(MSBuildThisFileDirectory)*\gen\*.csproj"
11-
Exclude="@(ProjectExclusions)" />
1210
<NonNetCoreAppProject Include="@(_allSrc)"
1311
Exclude="@(NetCoreAppLibrary->'%(Identity)\src\%(Identity).csproj')" />
14-
<NonNetCoreAppProject Include="$(MSBuildThisFileDirectory)*\gen\*.csproj"
15-
Exclude="@(ProjectExclusions)" />
1612
<NetCoreAppProject Include="$(CoreLibProject);
1713
@(_allSrc);
1814
$(MSBuildThisFileDirectory)Microsoft.VisualBasic.Core\src\Microsoft.VisualBasic.Core.vbproj;

0 commit comments

Comments
 (0)