Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature : Allow project reference DLLs to be added to the parent nupkg for pack target like IncludeReferencedProjects in nuget.exe #3891

Open
Tracked by #6285
joelverhagen opened this issue Nov 8, 2016 · 366 comments
Assignees
Labels
Functionality:Pack Priority:2 Issues for the current backlog. Style:PackageReference Type:DCR Design Change Request

Comments

@joelverhagen
Copy link
Member

joelverhagen commented Nov 8, 2016

Steps

  1. dotnet new --type lib two .csproj class libraries: projectA and projectB.
  2. Change <TargetFramework> to <TargetFrameworks> if you don't have dotnet pack fails with project references and <TargetFramework> #3865.
  3. Make projectA have a ProjectReference to projectB.
  4. Add <Type>project</Type> to the <ProjectReference>.
  5. dotnet pack projectA
  6. Open the resulting .nupkg's lib folder

Expected

projectB.dll should be in the .nupkg along with projectA.dll

Actual

projectB is still a package reference, not a DLL included in the package.

Environment

Tried latest dev's pack target.

dotnet --info

.NET Command Line Tools (1.0.0-preview3-004056)

Product Information:
 Version:            1.0.0-preview3-004056
 Commit SHA-1 hash:  ccc4968bc3

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.14393
 OS Platform: Windows
 RID:         win10-x64

UPDATE: Please see workaround posted as comment to see how to add ProjectReferences as DLLs in the package dynamically.

@joelverhagen joelverhagen added this to the 4.0 RC2 milestone Nov 8, 2016
@rrelyea
Copy link
Contributor

rrelyea commented Nov 8, 2016

Please check spec on wiki for planned behavior

@joelverhagen
Copy link
Member Author

Spec: https://github.com/NuGet/Home/wiki/Adding-nuget-pack-as-a-msbuild-target

This doesn't work:

<TreatAsPackageReference>false</TreatAsPackageReference>

The output .nuspec still has projectB as a package reference and only one file: projectA.dll under lib.

@emgarten
Copy link
Member

emgarten commented Nov 8, 2016

This is a non-trivial feature that hasn't been implemented for RC yet.

Supporting this will require potentially walking the entire project closure to determine all projects that need to be merged into the package, or reading the assets file and matching the project closure with both the project reference flags and the pack flags found in the project (since it can be set either way).

This is also impacted by build outputs not flowing transitively to parent projects yet.

@joelverhagen
Copy link
Member Author

joelverhagen commented Nov 10, 2016

Plan of action:

  1. Build out some automated tests for pack task to cover basic scenarios and detect regression.
  2. Add the original PackageSpec to the lock/assets file.
  3. Add any missing properties to the assets file pack needs (e.g. path to child project output assemblies).
  4. Update restore (no, not pack) to collapse project references when <TreatAsPackageReference> is true.
  5. Move the PackTask away from looking at child projects. Look at restore's assets file instead.

This has repercussions on:

  • Restore: basically all of the data pack needs should be collected by restore an put in the assets file. Restore should be the only guy doing a walk.
  • Project/Package duality: what if a child project .csproj has a <Reference>. How is this collapsed?
  • What collapsing occurs? Certainly build artifacts and <PackageReference>... what about <Reference>?

@joelverhagen
Copy link
Member Author

@rohit21agrawal, I partially got through consuming the assets file from in the pack task:
https://github.com/joelverhagen/NuGet.Client/tree/jver/3891

This also has some progress on getting the output DLLs of child projects (using @(ReferenceCopyLocalPaths) MSBuild items).

This branch is pretty rough so let me know if I can clarify.

@rohit21agrawal
Copy link
Contributor

As per @rrelyea : #3893 (comment)

"We don't plan to enable this in dotnet pack / msbuild /t:pack in 4.0 timeframe.
We'll listen to customer feedback and consider in the future."

@rohit21agrawal
Copy link
Contributor

moving to future as this is post-rtm work.

@rohit21agrawal rohit21agrawal modified the milestones: 4.0 RTM, 4.0 RC3 Dec 15, 2016
@gulbanana
Copy link

building a nupkg from multiple projects seems like a major feature to be missing :/

@rohit21agrawal
Copy link
Contributor

@gulbanana thanks for the feedback. this is something that is not planned for the 4.0 RTM release, but this is something we will definitely address in a future release.

@bbowman
Copy link

bbowman commented Dec 15, 2016

@kzu 's nugetizer 3000 does this?

@zvirja
Copy link

zvirja commented Jun 20, 2017

Hi guys,

Just out of curiosity - are there any plans to proceed on this one? Currently, I need to use the dirty workaround that is MSBuild internals specifics:

<ItemGroup>
  <_PackageFiles Include="$(OutputPath)\ReferencedProjectDll.dll">
    <BuildAction>None</BuildAction>
    <PackagePath>lib\net45\</PackagePath>
  </_PackageFiles>
</ItemGroup>

@aetos382
Copy link

aetos382 commented Mar 23, 2024

The PackAsAnalyzer is a big step forward. However, it seems to work only with ProjectReference. I wish it would work with PackageReference as well.

Also, this feature is useful for the following use cases other than analyzer, so please reconsider the name.

  • PowerShell Modules
  • MSBuild Tasks
  • Plug-ins for any application

Ideally, if I have the following projects and I build ProjectA.csproj, then ProjectA.nupkg should contain ProjectA.dll, ProjectB.dll, System.Text.Json.dll, and all its dependencies.

ProjectA.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="ProjectB.csproj" PackAsAnalyzer="true" />
  </ItemGroup>
</Project>

ProjectB.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="System.Text.Json" Version="..." />
  </ItemGroup>
</Project>

When I run the dotnet publish command, all the files that I want packaged are collected and copied into the publish directory. However, the dotnet pack command does not do as well. I just want the same files packaged as they are published.

@stevendarby
Copy link

I really assumed that a project having <IsPackable>False</IsPackable> would obviously mean any other packable project referencing it would include it as a file rather than as a package.

I don't want to do PrivateAssets=All because I want the dependencies to flow within test projects etc. I don't want to maintain any extra information in the csproj as I really think the IsPackable=False on the referenced project should be sufficient.

@kzu
Copy link

kzu commented Apr 4, 2024

@aetos382 it really does sound like it should just be a rename of PackAsTool to PackAsPublish so it can be applied to other scenarios. Combined with the PackFolder project property (in NuGetizer at least), that would cover all cases, IMO.

MattParkerDev added a commit to MattParkerDev/ParallelPipelines that referenced this issue Apr 14, 2024
@DomenPigeon
Copy link

DomenPigeon commented Apr 25, 2024

Hello, just have a question if this is possible, because I have already invested a couple of days into this but still cound't find the ideal solution.

So I have a project which I would like to distribute as a nuget package:

  • this project has other nuget dependencies
  • this project has other project dependencies

What I want is to hide all the dependencies for the consumer. So that only classes in the original project which will be packed as nuget are visible and none other.

I have managed to do this for all the nuget dependencies with:
<PackageReference Include="<package>" Version="<version>" PrivateAssets="compile;contentfiles;analyzers;build"/>

And I have also managed to include the referenced projects dll's into the package with:
<ProjectReference Include="<project>" PrivateAssets="all"/> and the help of TargetsForTfmSpecificBuildOutput and

<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"/>

in custom target.

But now when I consume the package I can see all the classes from my project references and I don't want that.
Is there a way to prevent this? In the nuspec file I can see that there is no metadata at all for the additional dlls and I haven't find a way how to add it, if this is the correct approach?

@ViktorHofer
Copy link

There's the SuppressDependenciesWhenPackaging property that you can set if you don't want any dependencies at all for a given TFM: https://github.com/dotnet/runtime/blob/0f508f26cd22fffb7e8ab0ed3f7133ee48f3a6a0/eng/packaging.targets#L19

@DomenPigeon
Copy link

DomenPigeon commented Apr 27, 2024

@ViktorHofer thanks for you answer, but unfortunatelly this didn't help, inspecting the nuspec file, with the SuppressDependenciesWhenPackaging property all the dependencies were now gone, but the referenced dlls were still found by the consuming project. Is it ok that I have included SuppressDependenciesWhenPackaging into the csproj file of the project which is packaged? In the link you provided the property is in the .targets file.

Here is a set of commands to quickly setup this locally, if you maybe would like to try it out:

dotnet new sln --name solution
dotnet new console --name PackagedApp
dotnet sln add .\PackagedApp
dotnet new classlib --name ReferencedLibrary
dotnet sln add ReferencedLibrary
dotnet new console --name ConsumingApp
dotnet sln add ConsumingApp

Now edit PackagedApp.csproj:

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

    <ItemGroup>
        <ProjectReference Include="..\ReferencedLibrary\ReferencedLibrary.csproj" PrivateAssets="All"/>
    </ItemGroup>

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeProjectReferenceDlls</TargetsForTfmSpecificBuildOutput>
    </PropertyGroup>

    <Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
        <ItemGroup>
            <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"/>
        </ItemGroup>
    </Target>

</Project>
dotnet pack .\PackagedApp
dotnet nuget add source . -n TempSource
dotnet nuget push .\PackagedApp\bin\Release\PackagedApp.1.0.0.nupkg --source TempSource
dotnet add .\ConsumingApp package PackagedApp --version 1.0.0

At this point if you go into the ConsumingApp I would like that the defaul Class1 is not visible, but currently it is.

Cleanup:
dotnet nuget remove source TempSource

MattParkerDev added a commit to MattParkerDev/ParallelPipelines that referenced this issue Apr 29, 2024
MattParkerDev added a commit to MattParkerDev/ParallelPipelines that referenced this issue Apr 29, 2024
@ViktorHofer
Copy link

ViktorHofer commented Apr 29, 2024

Ok so my SuppressDependenciesWhenPackaging suggestion was misleading as you don't have package dependency issues, sorry for that.

The issue is that the "private" class library is put into the "lib" folder in the package and NuGet by default passes all assemblies from the lib folder to the compiler as a reference.

I think you could work around this by only putting your "PackagedApp" assembly into the "ref" folder. If you define a ref folder in addition to the lib folder, the assemblies from the former will be passed to the compiler and the assemblies from the latter will be made available at run time.

You can include the assembly into the ref folder via this item:

      <TfmSpecificPackageFile Include="$(TargetPath)"
                              PackagePath="ref\$(TargetFramework)\" />

This needs to be put in a target that is added to the TargetsForTfmSpecificBuildOutput property.

@DomenPigeon
Copy link

DomenPigeon commented Apr 29, 2024

@ViktorHofer thanks for you answer once again, but I still coudn't make it work I have tried:

<Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
        <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"/>
        <TfmSpecificPackageFile Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" PackagePath="ref\$(TargetFramework)\"/>
    </ItemGroup>
</Target>
<Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
        <TfmSpecificPackageFile Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" PackagePath="ref\$(TargetFramework)\"/>
    </ItemGroup>
</Target>

Thanks to your suggestion I have also found this msdn reference, but even with a nuspec file a coudn't make it to work:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
    <metadata>
        <id>PackagedApp</id>
        <version>1.0.0</version>
        <authors>PackagedApp</authors>
        <description>Package Description</description>
        <repository type="git"/>
        <dependencies>
            <group targetFramework="net8.0"/>
        </dependencies>
        <references>
            <group targetFramework="net8.0">
                <reference file="ReferencedLibrary.dll"/>
            </group>
        </references>
    </metadata>
    <files>
        <file src="bin\Release\net8.0\PackagedApp.dll" target="lib\net8.0\PackagedApp.dll"/>
        <file src="bin\Release\net8.0\ReferencedLibrary.dll" target="ref\net8.0\ReferencedLibrary.dll"/>
        <file src="bin\Release\net8.0\ReferencedLibrary.dll" target="lib\net8.0\ReferencedLibrary.dll"/>
    </files>
</package>

Did you maybe manage to make this work?

Maybe I will just give up, and make the reference projects nuget packages ...

@ViktorHofer
Copy link

ViktorHofer commented Apr 29, 2024

I missed a few thing. Update your PackagedApp.csproj to the following:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeProjectReferenceDlls</TargetsForTfmSpecificBuildOutput>
    <NoWarn>$(NoWarn);NU5131</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\ReferencedLibrary\ReferencedLibrary.csproj" PrivateAssets="all" />
  </ItemGroup>

  <ItemGroup>
    <!-- Add a ref folder to the package which only exposes the library so that the referenced class library doesn't get exposed. -->
    <None Include="$(TargetPath)" PackagePath="ref/$(TargetFramework)" Pack="true" Condition="'$(TargetFramework)' != ''" />
  </ItemGroup>

  <Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"
                            TargetPath="%(ReferenceCopyLocalPaths.DestinationSubDirectory)" />
    </ItemGroup>
  </Target>

</Project>

@DomenPigeon
Copy link

@ViktorHofer Great this now finally works, thank you very much. If you agree and find it valuable a would like to make a pull request to the docs and include what you have provided at the bottem where there already is the exact same example, but the missing part is how to do this with the csproj, currently there are only instructions how to do this with nuspec file.

@ViktorHofer
Copy link

cc @zivkan & @nkolev92 regarding the docs update

@serj1980
Copy link

It work incorrect if dll has translated resources

@dluc
Copy link

dluc commented May 4, 2024

I'm using the approach recommended in the latest comments and it seems to work. However, I'm seeing a bunch of build warnings (NU5131) because the "ref" folders is missing files.
For instance, here's a screenshots from nuget.info:

image

You can see that the "Abstractions" lib is not present in the ref folder. However the files are present in the nupkg.

Is it ok to ignore NU5131 (as suggested above)?

Build output (dotnet build -c Release --verbosity detailed):

Warning

Build succeeded.

"./KernelMemory.sln" (default target) (1:2) ->

"./clients/dotnet/WebClient/WebClient.csproj" (default target) (6:8) ->

(GenerateNuspec target) ->
/usr/local/share/dotnet/sdk/8.0.201/Sdks/NuGet.Build.Tasks.Pack/build/NuGet.Build.Tasks.Pack.targets(221,5): warning NU5131: References were found in the nuspec, but some reference assemblies were not found in both the nuspec and ref folder. Add the following > reference assemblies: [./clients/dotnet/WebClient/WebClient.csproj]

/usr/local/share/dotnet/sdk/8.0.201/Sdks/NuGet.Build.Tasks.Pack/build/NuGet.Build.Tasks.Pack.targets(221,5): warning NU5131: - Add Microsoft.KernelMemory.WebClient.dll to the net8.0 reference group in the nuspec [./clients/dotnet/WebClient/WebClient.csproj]

/usr/local/share/dotnet/sdk/8.0.201/Sdks/NuGet.Build.Tasks.Pack/build/NuGet.Build.Tasks.Pack.targets(221,5): warning NU5131: [./clients/dotnet/WebClient/WebClient.csproj]

@dluc
Copy link

dluc commented May 4, 2024

The suggested approach about using <BuildOutputInPackage Include ... and the dependency on ResolveReferences conflicts with dotnet pack --no-build.
For example, when signing assemblies, the process I'm using is:

  • build assemblies, using dotnet build
  • sign assemblies, by replacing original assemblies with signed ones
  • package, using dotnet pack --no-build

The last step fails with error NETSDK1085: The 'NoBuild' property was set to true but the 'Build' target was invoked.

@prezaei
Copy link

prezaei commented May 6, 2024

@ViktorHofer , I have the same issue as @dluc with no-build

@dahlweid
Copy link

dahlweid commented Jul 5, 2024

@ViktorHofer Thanks for the great example. There was one issue I had when including a nuget package, which contained resources for internationalization. For those, the DLLs are not just included in the lib/<framework> directory, but under lib/<framework>/<lang>. To achieve this, one more small modification was necessary:

 <Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
      <BuildOutputInPackage
        Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"
        TargetPath="%(ReferenceCopyLocalPaths.DestinationSubDirectory)" />
    </ItemGroup>
  </Target>

@evelyn-bi
Copy link

@prezaei @dluc @ViktorHofer I was able to work around the "NETSDK1085" error when using dotnet pack --no-build with the BuildOutputInPackage trick by including /p:GeneratePackageOnBuild=true in my dotnet pack call:

dotnet pack project.csproj --no-build /p:GeneratePackageOnBuild=true

I assume this doesn't cause any side effects as I'm just packing after the build is already done separately.

Reading the pack log it says,

Target "_CheckForBuildWithNoBuild" in file "C:\Program Files\dotnet\sdk\8.0.303\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.Sdk.targets"

Finding that target in the mentioned .targets file has this,

  <!-- TODO: this target should not check GeneratePackageOnBuild.
             remove when https://github.com/NuGet/Home/issues/7801 is fixed.
    -->
  <Target Name="_CheckForBuildWithNoBuild"
          Condition="'$(NoBuild)' == 'true' and '$(GeneratePackageOnBuild)' != 'true'">
    <NETSdkError ResourceName="NoBuildRequested" />
  </Target>

Although that github 7801 issue mentioned in the todo was closed 5 years ago this todo still exists--maybe for other reasons, or it was just forgotten? Anyways, because I pass in /p:GeneratePackageOnBuild=true directly to dotnet pack it's transient only for the lifetime of packing, and build is never called, and my nuget package now has my referenced project DLL so I assume it's fine.

@nathanmascitelli
Copy link

nathanmascitelli commented Sep 27, 2024

I've tried the solution that @ViktorHofer laid out for @DomenPigeon. Using @DomenPigeon's example, the DLL for ReferencedLibrary is included in the nupkg for PackagedApp, however when I try and add the PackagedApp package to ConsumingApp project I get an error complaining that a package for ReferencedLibrary cannot be found. Has anyone encountered this before?

The only difference in my code vs the example is PackagedApp is another classlib.

@MartyIX
Copy link

MartyIX commented Oct 14, 2024

Regarding #3891 (comment), can one say that these included DLLs are supposed to be only runtime dependencies (i.e. can one add exclude="compile" somehow in the final nuspec)?

owencampbell pushed a commit to SkillsFundingAgency/das-payments-v2-provideradjustments that referenced this issue Oct 23, 2024
@ViktorHofer
Copy link

ViktorHofer commented Nov 5, 2024

It work incorrect if dll has translated resources

@serj1980 you are right, thanks for pointing that out. I just updated my answer above which now works with satellite assemblies as well:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeProjectReferenceDlls</TargetsForTfmSpecificBuildOutput>
    <NoWarn>$(NoWarn);NU5131</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\ReferencedLibrary\ReferencedLibrary.csproj" PrivateAssets="all" />
  </ItemGroup>

  <ItemGroup>
    <!-- Add a ref folder to the package which only exposes the library so that the referenced class library doesn't get exposed. -->
    <None Include="$(TargetPath)" PackagePath="ref/$(TargetFramework)" Pack="true" Condition="'$(TargetFramework)' != ''" />
  </ItemGroup>

  <Target Name="IncludeProjectReferenceDlls" DependsOnTargets="BuildOnlySettings;ResolveReferences">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))"
                            TargetPath="%(ReferenceCopyLocalPaths.DestinationSubDirectory)" />
    </ItemGroup>
  </Target>

</Project>

Is it ok to ignore NU5131 (as suggested above)?

@dluc yes that's fine to suppress. There isn't a way to put a reference assembly into a package and make the nuspec list it as a reference item. There's a tracking issue for that: #8684

LiamWright pushed a commit to SkillsFundingAgency/das-payments-v2-providerpayments that referenced this issue Nov 21, 2024
@EronWright
Copy link

For those looking for a working example, see the Microsoft.ML package. It contains a suite of assemblies such as "Microsoft.ML.KMeansClustering.dll". Look at these files:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Functionality:Pack Priority:2 Issues for the current backlog. Style:PackageReference Type:DCR Design Change Request
Projects
None yet
Development

No branches or pull requests