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

[Proposal] Pack ProjectReferences #11708

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from

Conversation

maxkoshevoi
Copy link

@maxkoshevoi maxkoshevoi commented Mar 30, 2022

Related discussion: #3891

Rendered Proposal

Please 👍 or 👎 this comment to help us with the direction of this feature & leave as much feedback/questions/concerns as you'd like on this issue itself and we will get back to you shortly.

Thank You 🎉

@JonDouglas
Copy link
Contributor

Thank you so much for your contribution, @maxkoshevoi. I have edited the description to help out and will schedule an initial review with the NuGet team for next week.

Please feel free to update the proposal as questions & feedback come in, and I'll do my best to support.

Thanks again!


## Unresolved Questions

None
Copy link

@MeikTranel MeikTranel Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the primary use cases of bundling third party package content within your own package is custom build logic - analyzers, custom msbuild tasks, bundling satellite applications. There will be certain pressure to not only resolve a single level of package but a xcopy ready representation of that dependency's own dependency tree. To give some examples:

  • a custom build task i am working on at the moment uses Microsoft.EntityFrameworkCore.Sqlite to write out gathered information as a database file
    • that package incurs a slew of packages dependent on TFM & platform - Do we stop after bundling the upper node of the dependency tree specified?
  • an analyzer package needs a parser library at runtime - where does that ProprietaryFileFormatParserXLib land without further instructions ? A naive implementation would drop this under lib which would mean the analyzer wouldn't work and the analyzer consumer would reference that parser library to add insult to injury - PackagePath anyone?

A possible basic implementation of the feature would look like a 1:1 copy of PackageDependencyA/lib/[TFM or any]/ to PackageToBePacked/lib/[TFM or any]/ - this would mean that any library package added into a bundled package would end up being referenced by consumers of that bundled package - is that a desired outcome?

I would recommend adding the following questions:

Suggested change
None
- **How do we flow/assign TFM based `lib`/`content` items?** (Is `PackagePath` a desired parameter?)
- **How is conflict resolution going to work with consumers taking in a dependency already present in the bundle?**
- Should license flow/validation be considered right from the start?
- **Should a `PackageReference` or `ProjectReference` tagged with `Pack="true"` be resolved transitively?**

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @AraHaan

Copy link

@AraHaan AraHaan Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I think usually one can check that as an implementation detail using:
  • $(TargetFramework)

However there comes another task it would have to do as well:

  • use the value of $(RuntimeIdentifier) if set in project (otherwise ignore it when it == any).
  1. I think if analyzers/generators are referenced in project and are tagged with Pack="true" that it should package everything under the analyzer's folder in it's package AS IS which means conflicts between internal deps of the analyzers could possibly happen.

  2. As for the license flow, what it could do is add these to the package metadata to support it: licenses/<package name> (bundle them in that package path) where it would bundle the licenses from the included projects/packages.

  3. That just depends entirely on a lot of factors, if the package is meant only for an generator / analyzer (nothing more) than I say that resolving them would not be needed, otherwise yes they would be needed (we do not want to resolve dependencies of analyzers that would never be used on the consuming project and so wastes disc space).

proposed/2022/pack-referenced-projects.md Outdated Show resolved Hide resolved
maxkoshevoi and others added 3 commits April 7, 2022 10:34
Co-authored-by: Meik Tranel <tranelmeik@gmail.com>
Co-authored-by: Meik Tranel <tranelmeik@gmail.com>
@JonDouglas
Copy link
Contributor

Hi all,

Just commenting that we did review this last Friday and I'm trying to find time to write-up some notes. I apologize as this week we had a few large releases as per our blogs.

@Tratcher
Copy link

Do you also get to choose the location in the package? Here's a similar mess I had to figure out recently:
https://github.com/aspnet/AspNetKatana/blob/beb224c88712b08ce45f1d14bb8cf0cd9d4a8503/src/OwinHost/OwinHost.csproj#L31-L45

@maxkoshevoi
Copy link
Author

Do you also get to choose the location in the package? Here's a similar mess I had to figure out recently: aspnet/AspNetKatana@beb224c/src/OwinHost/OwinHost.csproj#L31-L45

Good catch. Updated the proposal to allow to choose the location

@jeffkl jeffkl added the Community PRs (and linked Issues) created by someone not in the NuGet team label May 3, 2022
@shuebner
Copy link

shuebner commented Jul 11, 2022

Regarding the empty "Drawbacks" section:
Consider what happens when the assemblies contained in a nuget package's TFM folder have different TFMs themselves, e. g. one has netstandard2.0 and the other has net6.0 or net48. If the projects from which they were built have the same PackageReference, they can get different assembly reference versions because of their different TFMs. Those can then lead to runtime assembly loading errors.

I detailed a real-life scenario in my blog:
https://svenhuebner-it.com/nuget-packaging-series-2-beware-of-dll-version-conflicts/

2) If `ProjectReference` has `Pack` property and it's set to `True` - include it's artifacts into the NuGet (to the default location or one specified in `PackagePath`).
3) Do the same steps for `PackageReference`s

## Drawbacks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some of the feedback around compatibiltiy and redistribution of packages should be listed in the drawbacks here.


Allow referenced projects' artifacts to be packed inside the NuGet.

## Motivation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great motivation. Thanks for writing it!

- **How do we flow/assign TFM based `lib`/`content` items?** (Is `PackagePath` a desired parameter?)
- **How is conflict resolution going to work with consumers taking in a dependency already present in the bundle?**
- Should license flow/validation be considered right from the start?
- **Should a `PackageReference` or `ProjectReference` tagged with `Pack="true"` be resolved transitively?**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll have to consider the ProjectReference side of this problem with a broader team. I can include them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good question, which is why I think the motivation isn't the correct one here. Understanding the scenarios would guide this answer (the answer would usually be yes.)


This one is less flexible since it cannot switch on per-`ProjectReference` basis. And it isn't applicable to `PackageReferences`.

- Another solution is to introduce a `IncludeReferencedProjects` switch for `nuget` CLI.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be for dotnet CLI instead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a Project is being included into a joined package and never gets packed itself, it's dependencies would need to be something that has to be represented in the joined package.
Example:

Project 1 -> Project 2 -> Package A.

Say, Project 1 includes the reference project, and it is intended to be redistributed as a library, it's extremely likely that Project 1 needs Package A as a dependency.
This is a reverse restore. Instead of given the dependency, give me a resolved graph, it starts with a resolved graph and it tries to generate the correct top level dependencies. Note that we'd need to account for package include/excludes as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be for dotnet CLI instead?

Correct. It'd be for dotnet CLI and the msbuild pack target (which the dotnet pack is just a wrapper for)

@JonDouglas
Copy link
Contributor

I apologize in advance for forgetting about this proposal until now. I wanted to let people know that the earliest we can think about this work will start in September. Thanks again for writing it and everyone's contributions and upvotes thus far. It significantly helps us in our upcoming planning cycles.

Copy link
Member

@nkolev92 nkolev92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the delayed review.

I'd love for us to focus on the scenarios first.

1 package (library), 1 assembly is a policy that tends to provide more predictable outcomes compared to packaging many assemblies into one package.

## Motivation

When SDK-style project is being packed as a NuGet, all projects that it references are considered as NuGet themselves that this NuGet depends on.
This is not always a desired behavior, sometimes user needs artifacts from referenced project(s) to be packed into the NuGet.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the motivation needs to capture the particular scenarios where this is helpful. We'd appreciate a bigger focus on the scenarios.


When SDK-style project is being packed as a NuGet, all projects that it references are considered as NuGet themselves that this NuGet depends on.
This is not always a desired behavior, sometimes user needs artifacts from referenced project(s) to be packed into the NuGet.
Currently there's no easy way to override this behavior except for ditching `csproj`, and going back to `nuspec`. In many cases that involves piping lots of information from the `MSBuild` context into the `nuspec` evaluation via `key=value` parameters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's an unfair characterization to say the only way to pack more than assembly is by going with a nuspec, as None items can glob in an equivalent way the nuspec files elements can.

Furthermore the issue body does link to a comment that contains a workaround:
#3891 (comment), that can be implemented with the documented pack extensibility. https://docs.microsoft.com/en-us/nuget/reference/msbuild-targets#advanced-extension-points-to-create-customized-package

When SDK-style project is being packed as a NuGet, all projects that it references are considered as NuGet themselves that this NuGet depends on.
This is not always a desired behavior, sometimes user needs artifacts from referenced project(s) to be packed into the NuGet.
Currently there's no easy way to override this behavior except for ditching `csproj`, and going back to `nuspec`. In many cases that involves piping lots of information from the `MSBuild` context into the `nuspec` evaluation via `key=value` parameters.
Chaining together dozens of arguments to achieve parity with metadata usually automagically introduced by the SDK becomes tedious, which eventually weakens the otherwise rich set of information attached to a modern NuGet package.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone brought up:
One of the primary use cases of bundling third party package content within your own package is custom build logic - analyzers, custom msbuild tasks, bundling satellite applications

I think this what the motivation should cover.

If `Pack` is `False` or not present - consider the project (or package) as a NuGet's dependency, if it's `True` - pack its artifacts in.
If `PackagePath` is not present - pack artifacts to the default location within the package. If it's present - pack them to the specified path.

For `PackageReference`s it's useful in case referenced package is from private feed, but NuGet that's being packed will be public.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This needs to go into the motivation.

Copy link
Member

@nkolev92 nkolev92 Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally an aside, packing assemblies from packages published on private feeds only can lead to a lot of issues.

If a package is in a private feed, it must have been done so for a reason. Redistributing assemblies for packages you don't own might lead into some licensing violations.

There's no guarantee that this package currently on a private feed (maybe because it's in previews) does not make it to NuGet.org. Redistributing the same assembly in multiple packages is something that's known to cause a lot of problems for the consumers.
The build and runtime work best when only 1 reference of the same assembly is used in an application. NuGet ensures that you cannot have multiple versions of the package in the project, but that guarantee cannot be transferred to the assemblies themselves. If an assembly is in multiple packages, it's very possible that someone ends up referencing both. The more dependency issues can be resolved at restore time the better.

The above concerns can be generalized to Never include the same assembly in multiple library packages.
Note the focus on library packages. In some scenarios, packages contain tools, and those often need the entry assembly + some of it's dependencies.


## Explanation

### Functional explanation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think multiple asks are being conflated into 1 here.

  • Packaging multiple projects into 1 package.
  • Packaging assemblies from packages into a different package.

These are obviously related, but the scenarios vary and the potential for misuses of a feature that does things automatically varies.

It's worth nothing that IncludeReferenceProjects handled projects only, so I don't think it'd be unfair to suggest that these features are not all combined into one.


## Unresolved Questions

- **How do we flow/assign TFM based `lib`/`content` items?** (Is `PackagePath` a desired parameter?)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I fully understand this example. Can you clarify better what the ask is?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referring to the conversation earlier - here: #11708 (comment)

Copy link
Member

@nkolev92 nkolev92 Nov 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that link. In that case, I think the compat side would probably need to not be configurable.

In other words, given a project reference today, the particular assembly chosen by NuGet/SDK is the best compatible framework one, so I think the switch would only really need to be copy for project, or don't.

The conflict resolution between package items and references is more complex as that happens in a part that NuGet doesn't understand. So we'd need NuGet and the SDK to work through that in some way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand there is potentially a better framework for some of the dependent assemblies, but accounting for that would make things too complicated imo. NuGet today chooses a full lib folder, so we can't really pick and choose from different folders.

## Unresolved Questions

- **How do we flow/assign TFM based `lib`/`content` items?** (Is `PackagePath` a desired parameter?)
- **How is conflict resolution going to work with consumers taking in a dependency already present in the bundle?**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It unfortunately doesn't. The controls for assembly includes are on a package level. Furthermore, it's not something that NuGet tries to address today. The build itself does go through conflict resolution itself.

- **How do we flow/assign TFM based `lib`/`content` items?** (Is `PackagePath` a desired parameter?)
- **How is conflict resolution going to work with consumers taking in a dependency already present in the bundle?**
- Should license flow/validation be considered right from the start?
- **Should a `PackageReference` or `ProjectReference` tagged with `Pack="true"` be resolved transitively?**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good question, which is why I think the motivation isn't the correct one here. Understanding the scenarios would guide this answer (the answer would usually be yes.)

- **How is conflict resolution going to work with consumers taking in a dependency already present in the bundle?**
- Should license flow/validation be considered right from the start?
- **Should a `PackageReference` or `ProjectReference` tagged with `Pack="true"` be resolved transitively?**
- What happens to NuGets that are referenced in `PackageReference` tagged with `Pack="true"`? Do they become dependencies of the resulting NuGet or being packed into it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This a reverse restore cases similar to the above one.


## Future Possibilities

This proposal would allow users more flexibility in choosing what is being packed as part of a NuGet, and how it's dependencies look like.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: More of a motivation thing.

When SDK-style project is being packed as a NuGet, all projects that it references are considered as NuGet themselves that this NuGet depends on.
This is not always a desired behavior, sometimes user needs artifacts from referenced project(s) to be packed into the NuGet.
Currently there's no easy way to override this behavior except for ditching `csproj`, and going back to `nuspec`. In many cases that involves piping lots of information from the `MSBuild` context into the `nuspec` evaluation via `key=value` parameters.
Chaining together dozens of arguments to achieve parity with metadata usually automagically introduced by the SDK becomes tedious, which eventually weakens the otherwise rich set of information attached to a modern NuGet package.
Copy link

@baronfel baronfel Jul 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One specific motivating example for this feature is MSBuild Tasks. Happy to see this brought up!

@ghost
Copy link

ghost commented Sep 26, 2023

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 330 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@maxkoshevoi
Copy link
Author

Ping

@ghost ghost removed the Status:No recent activity No recent activity. label Sep 26, 2023
@ghost ghost added the Status:No recent activity No recent activity. label Oct 26, 2023
@ghost
Copy link

ghost commented Oct 26, 2023

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 330 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@igitur
Copy link

igitur commented Oct 26, 2023

Ping

@ghost ghost removed the Status:No recent activity No recent activity. label Oct 26, 2023
@AraHaan
Copy link

AraHaan commented Nov 18, 2023

ping, anyone going to review this again? This would be useful in the future.

@donnie-msft
Copy link
Contributor

Hi, we have removed our "proposed" folder, so please move this proposal to the "accepted" folder.
See the update here, and let me know if you have questions/concerns: https://github.com/NuGet/Home/blob/b18b5cc1507df04ea9785f8ba613b1ceb2ad93ea/meta/README.md#what-happens-to-a-proposal
Thanks!

@ghost ghost added the Status:No recent activity No recent activity. label Dec 28, 2023
@ghost
Copy link

ghost commented Dec 28, 2023

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 330 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@AraHaan
Copy link

AraHaan commented Dec 28, 2023

We still waiting.

@ghost ghost removed the Status:No recent activity No recent activity. label Dec 28, 2023
@AraHaan
Copy link

AraHaan commented Dec 28, 2023

Also shouldn't this be moved to 2024 folder?

@ghost ghost added the Status:No recent activity No recent activity. label Jan 27, 2024
@ghost
Copy link

ghost commented Jan 27, 2024

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 330 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

@stevendarby
Copy link

stevendarby commented Apr 3, 2024

What is the proposal for nested references?

For example, when we were manually creating nuspec files, the referenced projects that we weren't creating packages for (IsPackable=false) were included in the nuspec as files instead of dependencies, but the packages that those referenced projects themselves referenced were added as dependencies - because they actually were packages.

If we go down the route of Pack=false on a project reference, then I'd hope this wouldn't prevent it recursively checking the references within that project to determine what else should be added as a dependency and what as a file. But this may then require setting Pack=false against a lot more project references across a solution and becomes a bit messy and confusing.

That's why I have to say I prefer it if we just used the IsPackable setting on the project. It just feels more intuitive and clearer and also means we don't need to set Pack=false everywhere that the project is referenced. It'd be a property of the project rather than of references to it.

@dotnet-policy-service dotnet-policy-service bot removed the Status:No recent activity No recent activity. label Apr 3, 2024
@dotnet-policy-service dotnet-policy-service bot added the Status:No recent activity No recent activity. label May 4, 2024
Copy link
Contributor

This PR has been automatically marked as stale because it has no activity for 30 days. It will be closed if no further activity occurs within another 330 days of this comment. If it is closed, you may reopen it anytime when you're ready again, as long as you don't delete the branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community PRs (and linked Issues) created by someone not in the NuGet team Status:No recent activity No recent activity.
Projects
None yet
Development

Successfully merging this pull request may close these issues.