Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
02e76f3
basic work
kartheekp-ms Jun 8, 2022
2533d08
more updates
kartheekp-ms Jun 10, 2022
8c2cdc8
updates
kartheekp-ms Jun 10, 2022
ed23c5f
more updates
kartheekp-ms Jun 10, 2022
9ee902e
more work
kartheekp-ms Jun 11, 2022
bbc507c
update (added appendix)
kartheekp-ms Jun 13, 2022
a23a5f4
update
kartheekp-ms Jun 13, 2022
d5d7b93
fix typos
kartheekp-ms Jun 13, 2022
3c5010f
typos
kartheekp-ms Jun 13, 2022
cb0ce7b
updated command name
kartheekp-ms Jun 15, 2022
771ffbe
Update version option info
kartheekp-ms Jun 15, 2022
e5ef82f
Fixed header
pragnya17 Jun 21, 2022
a001db8
add version parameter to additional improvements section
pragnya17 Jun 21, 2022
809b357
rearrange the intro
pragnya17 Jun 21, 2022
c007b1f
changing the wording here
pragnya17 Jun 21, 2022
9e389a1
Changes based on comments on the PR
pragnya17 Jul 7, 2022
847240b
Remove unresolved questions section, packages.config is out of scope
pragnya17 Jul 8, 2022
06c68f3
diamond dependency
pragnya17 Jul 8, 2022
cddb63f
Fix typos, add more to additional improvements
pragnya17 Jul 8, 2022
20e0b81
update examples and wording in frameworks options sentence
pragnya17 Jul 8, 2022
ccb9f0f
move to additional improvements section
pragnya17 Jul 11, 2022
297b015
Rewording a few sentences/section headings
pragnya17 Jul 11, 2022
b33ca2a
change picture, add clarity to sentence
pragnya17 Jul 11, 2022
31ffcb7
update screenshot
pragnya17 Jul 11, 2022
ba61efc
Clarify sentence, move section around
pragnya17 Jul 12, 2022
f528520
Add sample project.assets.json file
pragnya17 Jul 12, 2022
31560cb
add section to example assets file
pragnya17 Jul 12, 2022
a1bf5d4
add limitation to PMUI view
pragnya17 Jul 19, 2022
768e7d9
Add some things to additional improvements
pragnya17 Jul 29, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file modified meta/resources/TransitiveDependencies/TransitiveVSPMUI.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
225 changes: 225 additions & 0 deletions proposed/2022/dotnet-nuget-why-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# dotnet nuget why command

- Author: [Kartheek Penagamuri](https://github.com/kartheekp-ms), [Pragnya Pandrate](https://github.com/pragnya17)
- GitHub Issue [11782](https://github.com/NuGet/Home/issues/11782)

## Summary

Currently, there is not a great solution for developers to understand the nature of top-level packages and their transitive dependencies. The solution explorer in Visual Studio does a decent job at allowing a user to dive into each NuGet package & see all transitive dependencies from the top-level dependency.

![](../../meta/resources/TransitiveDependencies/SolutionView.png)

However, in comparison to Visual Studio, the dotnet CLI does not provide insight into “why” a transitive dependency is listed, although it does list everything that has been resolved as seen below.

![](../../meta/resources/TransitiveDependencies/DotNetCLI.png)

## Motivation

A developer should be able to understand where every package in a solution/project originated from. This can help them understand complex dependency chains they may have within their projects. Having this understanding can help a developer promote a transitive dependency quickly & easily to a top-level dependency in case there is a security concern or conflict.

## Explanation

### Functional Explanation

The NuGet restore operation generates a `project.assets.json` file under the `obj` folder for `PackageReference` style project. This file maintains a project's dependency graph. Therefore, this file can be used in order to produce a dependency graph when the `dotnet nuget why` command is called.

Sample `project.assets.json` file:

```
{
"version": 3,
"targets": {
"net6.0": {
"Microsoft.CSharp/4.3.0": {
"type": "package",
"dependencies": {
"System.Collections": "4.3.0",
"System.Diagnostics.Debug": "4.3.0",
"System.Dynamic.Runtime": "4.3.0",
"System.Globalization": "4.3.0",
"System.Linq": "4.3.0",
"System.Linq.Expressions": "4.3.0",
"System.ObjectModel": "4.3.0",
"System.Reflection": "4.3.0",
"System.Reflection.Extensions": "4.3.0",
"System.Reflection.Primitives": "4.3.0",
"System.Reflection.TypeExtensions": "4.3.0",
"System.Resources.ResourceManager": "4.3.0",
"System.Runtime": "4.3.0",
"System.Runtime.Extensions": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Threading": "4.3.0"
},
"compile": {
...
}
},
"runtime": {
...
}
},
"Microsoft.ML/1.7.1": {
"type": "package",
"dependencies": {
"Microsoft.ML.CpuMath": "1.7.1",
"Microsoft.ML.DataView": "1.7.1",
"Newtonsoft.Json": "10.0.3",
"System.CodeDom": "4.4.0",
"System.Collections.Immutable": "1.5.0",
"System.Memory": "4.5.3",
"System.Reflection.Emit.Lightweight": "4.3.0",
"System.Threading.Channels": "4.7.1"
},
"compile": {
...
},
"runtime": {
...
},
"build": {
...
},
"runtimeTargets": {
...
},
},
"libraries": {
...
},
"projectFileDependencyGroups": {
"net6.0": [
"Microsoft.ML >= 1.7.1",
"Microsoft.ML.SampleUtils >= 0.19.1"
]
},
"packageFolders": {
...
},
"project": {
"version": "1.0.0",
"restore": {
...
},
"frameworks": {
...
}
}
}

```

### Technical Explanation

The `dotnet nuget why` command will print out the dependency graph of a given package only if it is part of the final graph.

```
dotnet nuget why [<PROJECT>|<SOLUTION>] <PACKAGE_NAME>
[-f|--framework <FRAMEWORK>]

dotnet nuget why -h|--help
```
#### Arguments

- PROJECT | SOLUTION

The project or solution file to operate on. If more than one solution is found, an error is thrown. If more than one project is found, the dependency graph for each project is printed.

- PACKAGE_NAME

The exact package name/id for which the dependency graph has to be identified. Package name wildcards are not supported.

#### Options

- -f|--framework <FRAMEWORK>

Print out the dependency graph of a given package for a specific target framework.

- -?|-h|--help

Prints out a description of how to use the command.

#### Examples

- List dependency graph of a package given `package id`.

```
dotnet nuget why packageA

Project 'projectNameA' has the following dependency graph for 'packageA'
[net6.0]:
Microsoft.ML (1.0.0) -> Microsoft.ML.Util (1.0.0) -> packageA (1.0.0)
[net472]:
Microsoft.ML (1.0.0) -> Microsoft.ML.Util (1.0.0) -> packageA (1.0.0)

Project 'projectNameB' has the following dependency graph for 'packageA'
[net6.0]:
Microsoft.ML (1.1.0) -> Microsoft.ML.Util (1.1.0) -> packageA (1.1.0)
[net472]:
Microsoft.ML (1.1.0) -> Microsoft.ML.Util (1.1.0) -> packageA (1.1.0)
```

- List dependency graph of a package given `package id` when there is a diamond dependency (a package is brought in by more than one path).

```
dotnet nuget why packageA

Project 'projectNameA' has the following dependency graph for 'packageA'
[net6.0]:
Microsoft.ML (1.0.0) -> Microsoft.ML.Util (1.0.0) -> packageA (1.0.0)
Microsoft.ML (1.0.0) -> Microsoft.ML.SampleUtils (1.0.0) -> packageA (1.0.0)
```

- List dependency graph of a package given `package id` and `target framework`.

```
dotnet nuget why packageA -f net6.0

Project 'projectNameA' has the following dependency graph for 'packageA'
[net6.0]:
Microsoft.ML (1.0.0) -> Microsoft.ML.Util (1.0.0) -> packageA (1.0.0)

Project 'projectNameB' has the following dependency graph for 'packageA'
[net6.0]:
Microsoft.ML (1.1.0) -> Microsoft.ML.Util (1.1.0) -> packageA (1.1.0)
```

## Rationale and Alternatives

We could have considered a minor tweak to the existing experience of “dotnet list package --include-transitive” to provide the user with a sense of a package's dependencies. This could have been a new column next to “Resolved” called “Transitively Referenced” that had a list of the top-level packages that requested the dependency. The corresponding tracking issue can be found here: https://github.com/NuGet/Home/issues/11625. Unfortunately, this solution doesn't provide an easy way to identify the dependency graph `(starting from project -> top-level package -> transitive-dependency (1) -> ..-> transitive dependency (n))` for a particular package.

![](../../meta/resources/TransitiveDependencies/DotNetCLI.png)

## Prior Art

Within Visual Studio, there is a new panel in the Package Manager UI called “Transitive Packages” in which all transitive packages are displayed to the user. There is also an additional header titled “Top-level Packages”.

When a user highlights a transitive package, they will see a pop-up that displays what top-level package(s) are bringing it in.

![](../../meta/resources/TransitiveDependencies/TransitiveVSPMUI.png)

However, the Package Manager UI view still does not provide the user with enough detail about how transitive packages originate; for example, transitive dependencies that are brought in by a project are hidden. Therefore we propose the `dotnet nuget why` command.

## Additional improvements
If not specified, the command searches the current directory for one.

Add a [--version <VERSION>] option to the command if the user wants to print dependency graphs for a specific version of the package. NuGet currently `flattens`, so it only allows one version of a package per framework to be resolved. See NuGet package versioning for more information.

Allow the customer to look up transitive dependencies of more than one package. For example: `dotnet nuget why [<PROJECT>|<SOLUTION>] packages package1, package2` or `dotnet nuget why [<PROJECT>|<SOLUTION>] package 'nuget.*'`.

Allow the customer to transitive dependencies of more than one framework. For example: `--framework net6.0 --framework netstandard2.0`.

Create a better visualization of the dependency graph that is printed by the `dotnet nuget why` command by displaying a tree rather than just printing out a list of dependencies.

Packages acquired through `PackageDownload` are recorded in the `project.assets.json` file under `project -> frameworks -> {frameworkName} -> downloadDependencies`. However, since they are recorded under `downloadDependcies` rather than `dependencies` they are not included in the current output of the `dotnet nuget why` command. In the future packages acquired through `PackageDownload` can be included in the output of the `dotnet nuget why` command.

The NuGet restore operation downloads other packages during dependency resolution which are not part of the final dependency graph that the `dotnet nuget why` command prints out. The absence of a package id and version in the `project.assets.json` file indicates that a package was downloaded during dependency resolution but is not part of the final dependency graph. In the future these other packages can also be included in the dependency graph that the `dotnet nuget why` command prints out.

Use non-zero exit codes for errors such as when the `project.assets.json` file is not present or the package is not found in the `project.assets.json` file.

## Appendix

- https://github.com/NuGet/Home/blob/dev/proposed/2020/Transitive-Dependencies.md
- [npm-why](https://github.com/amio/npm-why#npm-why-)
- [cargo-tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html)
- [mvn dependency:tree](https://maven.apache.org/plugins/maven-dependency-plugin/usage.html#dependency:tree)