Skip to content

Commit 538934c

Browse files
authored
Only add placeholder pkg file if folder is empty (#67647)
* Only add placeholder pkg file if folder is empty Originally reported in #63413, placeholder files are added unconditionally by the .NETStandard compat error packaging infrastructure, even if the buildTransitive/$(SupportedTFM) folder isn't empty. That hinders our libraries to package their own set of buildTransitive props and targets files for the supported set of tfms. This change makes sure that placeholder files are added only if no None or Content items are declared that point to the same package folder. Adding documentation that explains the .NETStandard compatibility packaging infrasturcture and how to correctly package hand-authored msbuild files next to the generated targets files. * Fix NETStandardCompatError empty cases * Update packaging.targets
1 parent cfd1241 commit 538934c

File tree

6 files changed

+95
-65
lines changed

6 files changed

+95
-65
lines changed

docs/coding-guidelines/libraries-packaging.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Removing a library from the shared framework is a breaking change and should be
2222

2323
Transport packages are non-shipping packages that dotnet/runtime produces in order to share binaries with other repositories.
2424

25-
### Microsoft.Internal.Runtime.**TARGET**.Transport
25+
### Microsoft.Internal.Runtime.**[TargetRepositoryName]**.Transport
2626

2727
Such transport packages represent the set of libraries which are produced in dotnet/runtime and ship in target repo's shared framework (i.e. Microsoft.AspNetCore.App and Microsoft.WindowsDesktop.App). We produce a transport package so that we can easily share reference, implementation and analyzer assemblies that might not be present in NuGet packages that also ship.
2828

@@ -92,3 +92,36 @@ In order to mitigate design-time/build-time performance issues with source gener
9292
<DisableSourceGeneratorPropertyName>CustomPropertyName</DisableSourceGeneratorPropertyName>
9393
</PropertyGroup>
9494
```
95+
96+
### NETStandard Compatibility Error infrastructure
97+
For libraries that support .NETStandard, the _.NETStandard Compatibility packaging infrastructure_ makes sure that out-of-support target frameworks like _netcoreapp3.1_ or _net461_ are unsupported by the produced package. That enables library authors to support .NETStandard but explicitly not support unsupported .NETStandard compatible target frameworks.
98+
99+
The infrastructure generates a targets file that throws a user readable Error when msbuild invokes a project with an unsupported target framework. In addition to the targets file, placeholder files `_._` are placed into the minimum supported .NETStandard compatible target framework's package folder (as time of writing `net6.0` and `net462`), so that the generated targets files don't apply for that and any newer/compatible target framework. Example:
100+
101+
```
102+
buildTransitive\net461\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is generated and throws an Error
103+
buildTransitive\net462\_._
104+
buildTransitive\netcoreapp2.0\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is generated and throws an Error
105+
buildTransitive\net6.0\_._
106+
```
107+
108+
Whenever a library wants to author their own set of props and targets files (i.e. for source generators) and the above mentioned infrastructure kicks in (because the library targets .NETStandard), such files **must be included not only for the .NETStandard target framework but also for the specific minimum supported target frameworks**. The _.NETStandard Compatibility packaging infrastructure_ then omits the otherwise necessary placeholder files. Example:
109+
110+
```
111+
buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is hand authored and doesn't throw an error
112+
buildTransitive\net461\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is generated and throws an Error
113+
buildTransitive\net462\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is hand authored and doesn't throw an error
114+
buildTransitive\netcoreapp2.0\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is generated and throws an Error
115+
buildTransitive\net6.0\Microsoft.Extensions.Configuration.UserSecrets.targets <- This file is hand authored and doesn't throw an error
116+
```
117+
118+
The above layout is achieved via the following item declaration in the project file. In that case, the hand authored msbuild props and/or targets files are located in a buildTransitive folder in the project tree. Note that the trailing directory separators are required.
119+
120+
```xml
121+
<ItemGroup>
122+
<Content Include="buildTransitive\$(MSBuildProjectName).*"
123+
PackagePath="buildTransitive\netstandard2.0\;
124+
buildTransitive\$(NetFrameworkMinimum)\;
125+
buildTransitive\$(NetCoreAppMinimum)\" />
126+
</ItemGroup>
127+
```

eng/packaging.targets

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
<!-- Don't restore prebuilt packages during sourcebuild. -->
66
<DisablePackageBaselineValidation Condition="'$(DotNetBuildFromSource)' == 'true'">true</DisablePackageBaselineValidation>
77
<PackageValidationBaselineVersion Condition="'$(PackageValidationBaselineVersion)' == ''">$(NetCoreAppLatestStablePackageBaselineVersion)</PackageValidationBaselineVersion>
8-
<!-- PackDependsOn is the right hook in a targets file if the NuGet.Build.Tasks.Pack nuget package is used, otherwise
9-
BeforePack must be used. Setting both to ensure that we are always running before other targets. -->
10-
<PackDependsOn>AddNETStandardCompatErrorFileForPackaging;IncludeAnalyzersInPackage;$(PackDependsOn)</PackDependsOn>
11-
<BeforePack>AddNETStandardCompatErrorFileForPackaging;IncludeAnalyzersInPackage;$(BeforePack)</BeforePack>
8+
<BeforePack>$(BeforePack);IncludeAnalyzersInPackage;AddNETStandardCompatErrorFileForPackaging</BeforePack>
129
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);AddRuntimeSpecificFilesToPackage;IncludeProjectReferencesWithPackAttributeInPackage</TargetsForTfmSpecificContentInPackage>
1310
<IncludeBuildOutput Condition="'$(TargetPlatformIdentifier)' != ''">false</IncludeBuildOutput>
1411
<!-- Don't include target platform specific dependencies, since we use the target platform to represent RIDs instead -->
@@ -71,20 +68,38 @@
7168
<!-- Placeholders don't need a dependency group. -->
7269
<NoWarn>$(NoWarn);NU5128</NoWarn>
7370
</PropertyGroup>
74-
<ItemGroup Condition="'$(AddNETFrameworkPlaceholderFileToPackage)' == 'true'">
75-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/$(NetFrameworkMinimum)" Pack="true" />
76-
</ItemGroup>
77-
<ItemGroup Condition="'$(AddXamarinPlaceholderFilesToPackage)' == 'true'">
78-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/MonoAndroid10" Pack="true" />
79-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/MonoTouch10" Pack="true" />
80-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/xamarinios10" Pack="true" />
81-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/xamarinmac20" Pack="true" />
82-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/xamarintvos10" Pack="true" />
83-
<None Include="$(PlaceholderFile)" PackagePath="$(BuildOutputTargetFolder)/xamarinwatchos10" Pack="true" />
71+
<ItemGroup>
72+
<None Include="$(PlaceholderFile)"
73+
Pack="true"
74+
PackagePath="$(BuildOutputTargetFolder)\$(NetFrameworkMinimum)\"
75+
Condition="'$(AddNETFrameworkPlaceholderFileToPackage)' == 'true'" />
76+
<None Include="$(PlaceholderFile)"
77+
Pack="true"
78+
PackagePath="$(BuildOutputTargetFolder)\MonoAndroid10\;
79+
$(BuildOutputTargetFolder)\MonoTouch10\;
80+
$(BuildOutputTargetFolder)\xamarinios10\;
81+
$(BuildOutputTargetFolder)\xamarinmac20\;
82+
$(BuildOutputTargetFolder)\xamarintvos10\;
83+
$(BuildOutputTargetFolder)\xamarinwatchos10\"
84+
Condition="'$(AddXamarinPlaceholderFilesToPackage)' == 'true'" />
8485
</ItemGroup>
8586
</When>
8687
</Choose>
8788

89+
<!-- Include a netstandard compat error if the project targets both .NETStandard and
90+
.NETCoreApp. This prohibits users to consume packages on an older .NETCoreApp version
91+
than the minimum supported one. -->
92+
<ItemGroup>
93+
<NETStandardCompatError Include="netcoreapp2.0"
94+
Supported="$(NetCoreAppMinimum)"
95+
Condition="$(TargetFrameworks.Contains('netstandard2.')) and
96+
($(TargetFrameworks.Contains('$(NetCoreAppMinimum)')) or $(TargetFrameworks.Contains('$(NetCoreAppCurrent)')))" />
97+
<NETStandardCompatError Include="net461"
98+
Supported="$(NetFrameworkMinimum)"
99+
Condition="$(TargetFrameworks.Contains('netstandard2.0')) and
100+
($(TargetFrameworks.Contains('$(NetFrameworkMinimum)')) or $(TargetFrameworks.Contains('net47')) or $(TargetFrameworks.Contains('net48')))" />
101+
</ItemGroup>
102+
88103
<!-- TODO: Remove this target when no library relies on the intellisense documentation file anymore.-->
89104
<Target Name="ChangeDocumentationFileForPackaging"
90105
AfterTargets="DocumentationProjectOutputGroup"
@@ -114,7 +129,7 @@
114129
@(BuiltProjectOutputGroupOutput);
115130
@(DocumentationProjectOutputGroupOutput)" />
116131
<TfmSpecificPackageFile Include="@(TfmRuntimeSpecificPackageFile)"
117-
PackagePath="runtimes/$(_packageTargetRuntime)/$(BuildOutputTargetFolder)/$(_targetFrameworkWithoutSuffix)" />
132+
PackagePath="runtimes\$(_packageTargetRuntime)\$(BuildOutputTargetFolder)\$(_targetFrameworkWithoutSuffix)\" />
118133
<TfmSpecificDebugSymbolsFile Include="$(RuntimeSymbolPath)"
119134
TargetPath="/runtimes/$(_packageTargetRuntime)/$(BuildOutputTargetFolder)/$(_targetFrameworkWithoutSuffix)/%(Filename)%(Extension)"
120135
TargetFramework="$(_targetFrameworkWithoutSuffix)"
@@ -153,9 +168,8 @@
153168
Condition="'@(AnalyzerReference)' != '' and '$(IncludeMultiTargetRoslynComponentTargets)' == 'true'"
154169
DependsOnTargets="GenerateMultiTargetRoslynComponentTargetsFile">
155170
<ItemGroup>
156-
<Content Include="$(MultiTargetRoslynComponentTargetsFileIntermediatePath)"
157-
PackagePath="build\$(PackageId).targets"
158-
Pack="True" />
171+
<Content Include="$(MultiTargetRoslynComponentTargetsFileIntermediatePath)" PackagePath="buildTransitive\netstandard2.0\$(PackageId).targets" />
172+
<Content Include="$(MultiTargetRoslynComponentTargetsFileIntermediatePath)" PackagePath="buildTransitive\%(NETStandardCompatError.Supported)\$(PackageId).targets" Condition="'@(NETStandardCompatError)' != ''" />
159173
</ItemGroup>
160174
</Target>
161175

@@ -175,30 +189,13 @@
175189
Overwrite="true" />
176190
</Target>
177191

178-
<!-- Include a netstandard compat error if the project targets both .NETStandard and
179-
.NETCoreApp. This prohibits users to consume packages on an older .NETCoreApp version
180-
than the minimum supported one. -->
181-
<ItemGroup>
182-
<NETStandardCompatError Include="netcoreapp2.0"
183-
Supported="$(NetCoreAppMinimum)"
184-
Condition="$(TargetFrameworks.Contains('netstandard2.')) and
185-
($(TargetFrameworks.Contains('$(NetCoreAppMinimum)')) or $(TargetFrameworks.Contains('$(NetCoreAppCurrent)'))) and
186-
'$(DisableNETStandardCompatErrorForNETCoreApp)' != 'true'" />
187-
<NETStandardCompatError Include="net461"
188-
Supported="$(NetFrameworkMinimum)"
189-
Condition="$(TargetFrameworks.Contains('netstandard2.0')) and
190-
($(TargetFrameworks.Contains('$(NetFrameworkMinimum)')) or $(TargetFrameworks.Contains('net47')) or $(TargetFrameworks.Contains('net48'))) and
191-
'$(DisableNETStandardCompatErrorForNETFramework)' != 'true'" />
192-
193-
</ItemGroup>
194-
195192
<!-- Add targets file that marks a .NETStandard applicable tfm as unsupported. -->
196193
<Target Name="AddNETStandardCompatErrorFileForPackaging"
197-
Condition="'@(NETStandardCompatError)' != ''"
194+
Condition="'@(NETStandardCompatError)' != '' and '$(DisableNETStandardCompatErrors)' != 'true'"
198195
Inputs="%(NETStandardCompatError.Identity)"
199196
Outputs="unused">
200197
<PropertyGroup>
201-
<_NETStandardCompatErrorFilePath>$(BaseIntermediateOutputPath)netstandardcompaterrors\%(NETStandardCompatError.Identity)\$(PackageId).targets</_NETStandardCompatErrorFilePath>
198+
<_NETStandardCompatErrorFilePath>$(BaseIntermediateOutputPath)netstandardcompaterror_%(NETStandardCompatError.Identity).targets</_NETStandardCompatErrorFilePath>
202199
<_NETStandardCompatErrorFileTarget>NETStandardCompatError_$(PackageId.Replace('.', '_'))_$([System.String]::new('%(NETStandardCompatError.Supported)').Replace('.', '_'))</_NETStandardCompatErrorFileTarget>
203200
<_NETStandardCompatErrorFileContent>
204201
<![CDATA[<Project InitialTargets="$(_NETStandardCompatErrorFileTarget)">
@@ -208,6 +205,7 @@
208205
</Target>
209206
</Project>]]>
210207
</_NETStandardCompatErrorFileContent>
208+
<_NETStandardCompatErrorPlaceholderFilePackagePath>buildTransitive$([System.IO.Path]::DirectorySeparatorChar)%(NETStandardCompatError.Supported)</_NETStandardCompatErrorPlaceholderFilePackagePath>
211209
</PropertyGroup>
212210

213211
<WriteLinesToFile File="$(_NETStandardCompatErrorFilePath)"
@@ -216,24 +214,30 @@
216214
WriteOnlyWhenDifferent="true" />
217215

218216
<ItemGroup>
217+
<_PackageBuildFile Include="@(None->Metadata('PackagePath'));
218+
@(Content->Metadata('PackagePath'))" />
219+
<_PackageBuildFile PackagePathWithoutFilename="$([System.IO.Path]::GetDirectoryName('%(Identity)'))" />
220+
219221
<None Include="$(_NETStandardCompatErrorFilePath)"
220-
PackagePath="buildTransitive\%(NETStandardCompatError.Identity)"
222+
PackagePath="buildTransitive\%(NETStandardCompatError.Identity)\$(PackageId).targets"
221223
Pack="true" />
224+
<!-- Add the placeholder file to the supported target framework buildTransitive folder, if it's empty. -->
222225
<None Include="$(PlaceholderFile)"
223-
PackagePath="buildTransitive\%(NETStandardCompatError.Supported)"
224-
Pack="true" />
225-
<FileWrites Include="$(_NETStandardCompatErrorFilePath)" />
226+
PackagePath="$(_NETStandardCompatErrorPlaceholderFilePackagePath)\$(PackageId).targets"
227+
Pack="true"
228+
Condition="'@(_PackageBuildFile)' == '' or
229+
!@(_PackageBuildFile->AnyHaveMetadataValue('PackagePathWithoutFilename', '$(_NETStandardCompatErrorPlaceholderFilePackagePath)'))" />
226230
</ItemGroup>
227231
</Target>
228232

229233
<Target Name="IncludeProjectReferencesWithPackAttributeInPackage"
230-
Condition="'@(ProjectReference->WithMetadataValue('Pack', 'true'))' != ''"
234+
Condition="'@(ProjectReference)' != '' and @(ProjectReference->AnyHaveMetadataValue('Pack', 'true'))"
231235
DependsOnTargets="BuildOnlySettings;ResolveReferences">
232236
<ItemGroup>
233237
<!-- Add ReferenceCopyLocalPaths for ProjectReferences which are flagged as Pack="true" into the package. -->
234238
<_projectReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('Pack', 'true'))" />
235239
<TfmSpecificPackageFile Include="@(_projectReferenceCopyLocalPaths)"
236-
PackagePath="$([MSBuild]::ValueOrDefault('%(ReferenceCopyLocalPaths.PackagePath)', '$(BuildOutputTargetFolder)/$(TargetFramework)'))" />
240+
PackagePath="$([MSBuild]::ValueOrDefault('%(ReferenceCopyLocalPaths.PackagePath)', '$(BuildOutputTargetFolder)\$(TargetFramework)\'))" />
237241
<TfmSpecificDebugSymbolsFile Include="@(TfmSpecificPackageFile->WithMetadataValue('Extension', '.pdb'))"
238242
TargetPath="/%(TfmSpecificPackageFile.PackagePath)/%(Filename)%(Extension)"
239243
TargetFramework="$(TargetFramework)"
@@ -249,7 +253,7 @@
249253
<_referenceAssemblyPaths Include="@(_projectReferenceCopyLocalPaths->WithMetadataValue('Extension', '.dll')->WithMetadataValue('IncludeReferenceAssemblyInPackage', 'true')->Metadata('ReferenceAssembly'))" />
250254
<_referenceAssemblyPaths Include="@(_projectReferenceCopyLocalPaths->WithMetadataValue('Extension', '.xml')->WithMetadataValue('IncludeReferenceAssemblyInPackage', 'true'))" />
251255
<TfmSpecificPackageFile Include="@(_referenceAssemblyPaths)"
252-
PackagePath="ref/$(TargetFramework)" />
256+
PackagePath="ref\$(TargetFramework)\" />
253257
</ItemGroup>
254258
</Target>
255259

src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/Microsoft.Extensions.Configuration.UserSecrets.csproj

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@
88
<DisableImplicitAssemblyReferences>false</DisableImplicitAssemblyReferences>
99
<IsPackable>true</IsPackable>
1010
<PackageDescription>User secrets configuration provider implementation for Microsoft.Extensions.Configuration.</PackageDescription>
11-
12-
<!--
13-
See https://github.com/dotnet/runtime/issues/63413.
14-
Since this library contains its own buildTransitive logic, disable the netstandard compat logic since they conflict.
15-
-->
16-
<DisableNETStandardCompatErrorForNETCoreApp>true</DisableNETStandardCompatErrorForNETCoreApp>
17-
<DisableNETStandardCompatErrorForNETFramework>true</DisableNETStandardCompatErrorForNETFramework>
1811
</PropertyGroup>
1912

2013
<ItemGroup>
@@ -25,8 +18,10 @@
2518
</ItemGroup>
2619

2720
<ItemGroup>
28-
<Content Include="buildTransitive\netstandard2.0\$(MSBuildProjectName).props" PackagePath="%(Identity)" />
29-
<Content Include="buildTransitive\netstandard2.0\$(MSBuildProjectName).targets" PackagePath="%(Identity)" />
21+
<Content Include="buildTransitive\$(MSBuildProjectName).*"
22+
PackagePath="buildTransitive\netstandard2.0\;
23+
buildTransitive\$(NetFrameworkMinimum)\;
24+
buildTransitive\$(NetCoreAppMinimum)\" />
3025
</ItemGroup>
3126

3227
</Project>

src/libraries/System.Resources.Extensions/src/System.Resources.Extensions.csproj

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<DefineConstants>$(DefineConstants);RESOURCES_EXTENSIONS</DefineConstants>
66
<Nullable>enable</Nullable>
77
<IsPackable>true</IsPackable>
8-
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);GeneratePackageTargetsFile</TargetsForTfmSpecificContentInPackage>
9-
<DisableNETStandardCompatErrorForNETFramework>true</DisableNETStandardCompatErrorForNETFramework>
8+
<SuggestedBindingRedirectsPackageFile>$(BaseIntermediateOutputPath)SuggestedBindingRedirects.targets</SuggestedBindingRedirectsPackageFile>
9+
<BeforePack>$(BeforePack);GeneratePackageTargetsFile</BeforePack>
1010
<PackageDescription>Provides classes which read and write resources in a format that supports non-primitive objects.
1111

1212
Commonly Used Types:
@@ -59,32 +59,30 @@ System.Resources.Extensions.PreserializedResourceWriter</PackageDescription>
5959
</ItemGroup>
6060

6161
<PropertyGroup>
62-
<_packageTargetsFile>$(IntermediateOutputPath)$(AssemblyName).targets</_packageTargetsFile>
6362
</PropertyGroup>
6463

6564
<Target Name="GeneratePackageTargetsFile"
66-
Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'"
6765
Inputs="$(MSBuildAllProjects)"
68-
Outputs="$(_packageTargetsFile)">
66+
Outputs="$(SuggestedBindingRedirectsPackageFile)">
6967
<PropertyGroup>
70-
<_packageTargetsFileContent><![CDATA[<Project>
68+
<SuggestedBindingRedirectsPackageFileContent><![CDATA[<Project>
7169
<!-- ResolveAssemblyReferences will never see the assembly reference embedded in the resources type,
7270
force a binding redirect ourselves so that we'll always unify to the System.Resources.Extensions
7371
version provided by this package -->
7472
<ItemGroup>
7573
<SuggestedBindingRedirects Include="$(AssemblyName), Culture=neutral, PublicKeyToken=$(PublicKeyToken)" MaxVersion="$(AssemblyVersion)" />
7674
</ItemGroup>
7775
</Project>
78-
]]></_packageTargetsFileContent>
76+
]]></SuggestedBindingRedirectsPackageFileContent>
7977
</PropertyGroup>
8078

81-
<WriteLinesToFile File="$(_packageTargetsFile)"
82-
Lines="$(_packageTargetsFileContent)"
79+
<WriteLinesToFile File="$(SuggestedBindingRedirectsPackageFile)"
80+
Lines="$(SuggestedBindingRedirectsPackageFileContent)"
8381
Overwrite="true" />
8482

8583
<ItemGroup>
86-
<TfmSpecificPackageFile Include="$(_packageTargetsFile)"
87-
PackagePath="buildTransitive/$(TargetFramework)" />
84+
<Content Include="$(SuggestedBindingRedirectsPackageFile)"
85+
PackagePath="buildTransitive\$(NetFrameworkMinimum)\$(PackageId).targets" />
8886
</ItemGroup>
8987
</Target>
9088
</Project>

0 commit comments

Comments
 (0)