NuGet / Home

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

buildTransitive behavior fails #12483

Closed chrisdaiii closed 1 year ago

chrisdaiii commented 1 year ago

NuGet Product Used

Other/NA

Product Version

dotnet 7.0.2 Nuget 6.5.0.136

Worked before?

No response

Impact

It's more difficult to complete my work

Repro Steps & Context

Say I have the following project structure:

ProjectA
│
└── ProjectB
    │
    └── ProjectC
        │
        └── buildTransitive
        │    │
        │    └── ProjectC.props
        └── MyFiles
              │
              └── 1.txt   2.txt   3.txt

ProjectC.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramewrok>net6.0</TargetFramewrok>
  </PropertyGroup>
</Project>

<ItemGroup>
  <None Include="buildTransitive\**">
    <Pack>true</Pack>
    <PackagePath>buildTransitive</PackagePath>
  </None>

  <Content Include="MyFiles\**">
    <Pack>true</Pack>
    <PackageCopyToOutput>true</PackageCopyToOutput>
    <PackagePath>contentFiles\MyFiles</PackagePath>
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content >
</ItemGroup>

ProjectC.props:

<Project>
  <ItemGroup>
    <Content Include="$(MSBuildThisFileDirectory)..\contentFiles\MyFiles\**">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
    </Content>
  </ItemGroup>
</Project>

Both ProjectB and ProjectC are Nuget packages, and ProjectA only references ProjectB.

When my ProjectA is a Framework project (net48), the buildTransitive of the indirectly dependent ProjectC fails, and the content files will not be copied to the output directory of ProjectA, but when my ProjectA project is an SDK-style .net core When projecting, everything is normal.

I want to know why when the .net core project indirectly depends on the Nuget package, the dependency file will be copied to the output directory normally, but the .net Framework project will not. Thanks!

Verbose Logs

No response

erdembayar commented 1 year ago

Thank you for filing this issue. Could you be able to provide us small repro code for both cases? So, I can compare them?

chrisdaiii commented 1 year ago

NugetBuildTransitive

Note: Both ProjectA-net6.0 and ProjectB-net48 refer to the local ProjectB Nuget package, please pack ProjectB and ProjectC into a directory, and modify the NuGet.config file at the same time.

Please help me to understand what is going on, thank you very much!

chrisdaiii commented 1 year ago

After you complete the above operations, compile the entire solution, you can see the ProjectA-net6.0\bin\Denug\net6.0\net6.0\1.txt file, but there is no ProjectA-net48\bin\Debug directory.

erdembayar commented 1 year ago

After you complete the above operations, compile the entire solution, you can see the ProjectA-net6.0\bin\Denug\net6.0\net6.0\1.txt file, but there is no ProjectA-net48\bin\Debug directory.

I think it's by design, I'll check and let you know soon.

chrisdaiii commented 1 year ago

After you complete the above operations, compile the entire solution, you can see the ProjectA-net6.0\bin\Denug\net6.0\net6.0\1.txt file, but there is no ProjectA-net48\bin\Debug directory.

I think it's by design, I'll check and let you know soon.

ok, thanks.

erdembayar commented 1 year ago

Hey, We don't think there's a NuGet bug here. The props in the buildtransitive folder is getting included in the project which is where the NuGet responsibility ends. The implementation of the props file is where the issue is. I'm gonna close this issue, but I'm happy to help you improve the authoring of the props file.

TargetFramework is an opaque string, which is not something that's available in both SDK (or .NET projects) and old style csproj (.NET Framework projects). The .NET Framework project does not have TargetFramework, so it will never evaluate correctly. I.e TargetFramework which is used in ProjectC.prop is not really thing for netframework(net48) unless you define it, you can read more from here.

For net6.0 dotnet case it understands TargetFramework because you defined it in line 3.

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>ProjectA_Core</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

Since net4.8 netframework doesn't have TargetFramework, but it you can use TargetFrameworkMoniker, TargetFrameworkVersion instead.

<Project>
    <>
    <ItemGroup Condition="'$(TargetFrameworkMoniker)' == '.NETFramework,Version=v4.8'">
        <Content Include="$(MSBuildThisFileDirectory)..\contentFiles\any\net48\**">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            <Link>net48\%(Filename)%(Extension)</Link>
        </Content>
    </ItemGroup>

    <ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
        <Content Include="$(MSBuildThisFileDirectory)..\contentFiles\any\net6.0\**">
            <CopyToOutputDirectory>Always</CopyToOutputDirectory>
            <Link>net6.0\%(Filename)%(Extension)</Link>
        </Content>
    </ItemGroup>

    <PropertyGroup>
        <OutputType>Library</OutputType>
    </PropertyGroup>
</Project>

image

But we don't recommend doing this way, because this kind of fragile implementation, it doesn't take into account of the package compatibility, next time if you want to extent above for net7.0 then you need to manually add it even though you don't need to. Probably you need to add for by platform too next time, like net7.0-android etc with above.

Instead, please use asset control (https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#controlling-dependency-assets).

  <ItemGroup>
    <PackageReference Include="ProjectB"  PrivateAssets="none" Version="1.0.0" />
    <PackageReference Include="ProjectY"  IncludeAssets="contentFiles" Version="2.0.0" />
    <PackageReference Include="ProjectZ"  IncludeAssets="build;buildTransitive" Version="3.0.0" />
  </ItemGroup>

Above is more flexible and scalable in long run, see more from here.

chrisdaiii commented 1 year ago

Thank you for helping me figure out what's wrong, thank you very much, wish you a good day every day.

chrisdaiii commented 11 months ago

Hey, We don't think there's a NuGet bug here. The props in the buildtransitive folder is getting included in the project which is where the NuGet responsibility ends. The implementation of the props file is where the issue is. I'm gonna close this issue, but I'm happy to help you improve the authoring of the props file.

TargetFramework is an opaque string, which is not something that's available in both SDK (or .NET projects) and old style csproj (.NET Framework projects). The .NET Framework project does not have TargetFramework, so it will never evaluate correctly. I.e TargetFramework which is used in ProjectC.prop is not really thing for netframework(net48) unless you define it, you can read more from here.

For net6.0 dotnet case it understands TargetFramework because you defined it in line 3.

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>ProjectA_Core</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

Since net4.8 netframework doesn't have TargetFramework, but it you can use TargetFrameworkMoniker, TargetFrameworkVersion instead.

<Project>
  <>
  <ItemGroup Condition="'$(TargetFrameworkMoniker)' == '.NETFramework,Version=v4.8'">
      <Content Include="$(MSBuildThisFileDirectory)..\contentFiles\any\net48\**">
          <CopyToOutputDirectory>Always</CopyToOutputDirectory>
          <Link>net48\%(Filename)%(Extension)</Link>
      </Content>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
      <Content Include="$(MSBuildThisFileDirectory)..\contentFiles\any\net6.0\**">
          <CopyToOutputDirectory>Always</CopyToOutputDirectory>
          <Link>net6.0\%(Filename)%(Extension)</Link>
      </Content>
  </ItemGroup>

  <PropertyGroup>
      <OutputType>Library</OutputType>
  </PropertyGroup>
</Project>

image

But we don't recommend doing this way, because this kind of fragile implementation, it doesn't take into account of the package compatibility, next time if you want to extent above for net7.0 then you need to manually add it even though you don't need to. Probably you need to add for by platform too next time, like net7.0-android etc with above.

Instead, please use asset control (https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#controlling-dependency-assets).

  <ItemGroup>
    <PackageReference Include="ProjectB"  PrivateAssets="none" Version="1.0.0" />
    <PackageReference Include="ProjectY"  IncludeAssets="contentFiles" Version="2.0.0" />
    <PackageReference Include="ProjectZ"  IncludeAssets="build;buildTransitive" Version="3.0.0" />
  </ItemGroup>

Above is more flexible and scalable in long run, see more from here.

Sorry to bother you, I want to ask you a question, if I use asset control as you said (not applicable to .props files), then my ProjectA will not have 1.txt 2.txt 3.txt files, These files are all in ProjectC, ProjectB refers to ProjectC, and ProjectA refers to ProjectB.