NuGet / Home

Repo for NuGet Client issues
Other
1.5k stars 252 forks source link

PackageReference GeneratePathProperty does not work with IsPackable="true" on multi-targeted project #13859

Closed MatthieuMEZIL closed 3 weeks ago

MatthieuMEZIL commented 3 weeks ago

NuGet Product Used

MSBuild.exe

Product Version

.NET 8.0.203, msbuild 17.11.9.46202, nuget 6.10.1

Worked before?

No

Impact

It's more difficult to complete my work

Repro Steps & Context

Output project does not support GeneratePathProperty causing GenerateNuspec target to fail.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net8.0;net472</TargetFrameworks>
    <IsPackable>true</IsPackable>
    <Authors>Name</Authors>
    <Company>Company</Company>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Text.Json" Version="8.0.5" ExcludeAssets="all" GeneratePathProperty="true" />
  </ItemGroup>

  <ItemGroup>
    <Content Include="$(PkgSystem_Text_Json)\lib\netstandard2.0\System.Text.Json.dll" />
  </ItemGroup>

  <Target Name="Foo"
    AfterTargets="Build"
    Condition=" '$(IsCrossTargetingBuild)' == 'true' "
    DependsOnTargets="GenerateNuspec">
  </Target>

</Project>

It fails because PkgSystem_Text_Json is empty for the output project. See NuGetBug.csproj.nuget.g.props:

 <PropertyGroup Condition=" '$(TargetFramework)' == 'net472' AND '$(ExcludeRestorePackageImports)' != 'true' ">
  <PkgSystem_Text_Json Condition=" '$(PkgSystem_Text_Json)' == '' ">D:\NuGet\system.text.json\8.0.5</PkgSystem_Text_Json>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' AND '$(ExcludeRestorePackageImports)' != 'true' ">
  <PkgSystem_Text_Json Condition=" '$(PkgSystem_Text_Json)' == '' ">D:\NuGet\system.text.json\8.0.5</PkgSystem_Text_Json>
</PropertyGroup>

I don't understand why PkgSystem_Text_Json is only defined when TargetFramework is set, and this is causing GenerateNuspec target to fail.

Verbose Logs

System.IO.DirectoryNotFoundException: Could not find a part of the path 'C:\lib\netstandard2.0'.
   at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
   at System.IO.FileSystemEnumerableIterator`1.CommonInit()
   at System.IO.FileSystemEnumerableIterator`1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean checkHost)
   at System.IO.Directory.GetFiles(String path, String searchPattern, SearchOption searchOption)
   at NuGet.Common.PathResolver.PerformWildcardSearch(String basePath, String searchPath, Boolean includeEmptyDirectories, String& normalizedBasePath)
   at NuGet.Packaging.PackageBuilder.ResolveSearchPattern(String basePath, String searchPath, String targetPath, Boolean includeEmptyDirectories)
   at NuGet.Packaging.PackageBuilder.AddFiles(String basePath, String source, String destination, String exclude)
   at NuGet.Packaging.PackageBuilder.PopulateFiles(String basePath, IEnumerable`1 files)
   at NuGet.Commands.MSBuildProjectFactory.CreateBuilder(String basePath, NuGetVersion version, String suffix, Boolean buildIfNeeded, PackageBuilder builder)
   at NuGet.Commands.PackCommandRunner.BuildFromProjectFile(String path)
   at NuGet.Build.Tasks.Pack.PackTask.Execute()
jeffkl commented 3 weeks ago

@MatthieuMEZIL when you multi-target, NuGet will categorize the contents of the package by target framework as well. It does this by running a target once per target framework to get any extra files you want to include for that target framework. A lot of the functionality of .NET build happens per target framework like compilation so it makes sense for NuGet to only make those properties available for the "inner builds" of the individual target frameworks. That said, it is possible to do what you want:

Documentation: https://learn.microsoft.com/nuget/reference/msbuild-targets#targetsfortfmspecificbuildoutput

Example:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>net8.0;net472</TargetFrameworks>
    <IsPackable>true</IsPackable>
    <Authors>Name</Authors>
    <Company>Company</Company>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeSystemTextJson</TargetsForTfmSpecificBuildOutput>
  </PropertyGroup>
   <ItemGroup>
    <PackageReference Include="System.Text.Json" Version="8.0.5" ExcludeAssets="all" PrivateAssets="all" GeneratePathProperty="true" />
  </ItemGroup>
  <Target Name="IncludeSystemTextJson">
    <ItemGroup>
      <BuildOutputInPackage Include="$(PkgSystem_Text_Json)\lib\netstandard2.0\System.Text.Json.dll" TargetPath="SomeDirectory" />
    </ItemGroup>
  </Target>
 </Project>

Result: image

jeffkl commented 3 weeks ago

Talked offline with @MatthieuMEZIL and the solution I provided works.