Any way to show what led to a dependency?

Is there an SPM command to show how a dependency came to be? Something that shows all the dependency chains that lead to a particular package being included in the build.


the swift-package-catalog SPM plugin can do this. if you add and run the plugin without any arguments, it will emit a list of all the modules, organized by package, in your build, and the list of modules, organized by package, recursively depended upon by each of those targets.

the plugin supports linux, even though the badges on the readme do not indicate it.

swift package show-dependencies

Although it only tells you the packages, not the products and targets.

1 Like

Hmm, I don't think this does what I'm asking for. It seems to show you what a particular package depends on. What I'm trying to find out is "what has a package as a dependency?" Also, is there a way to add SPM plugins globally? What if I can't (or don't want to) modify the local Package.swift?

As I mentioned above, this isn't what I'm looking for. I'm looking for the inverse of this. Given a package, what dependency chains give rise to the package in question?

i don’t know how large your dependency graph is, but maybe swift-package-catalog emits its output as JSON, so maybe you could text-search the name of the package to see where it appears?

if this is not practical, feel free to open a feature request on swift-package-catalog, as this is definitely in-scope for the plugin.

you can create an empty consumer package that depends on both the plugin and the local package. see swift-biome/ecosystem for an example

1 Like

They are the same question, just asked in reverse.

When I am debugging a particular package graph, I use that command and then ⌘F to search the output. From each hit, I visually trace it out backwards to the root package. That has always been simple and fast enough, so it has never occurred to me to bother automating. If you think it would be useful, you could propose a filter option for that command.

On the other hand, if you are asking how to find arbitrary clients of a package out in the wild, that would be a completely different question for which I have no answer.

Right, this is what I'm trying to avoid.

swift package show-dependencies --format dot | dot -Tsvg -o graph.svg

Will give you a nice image to work it out


Prior art: yarn why, cargo tree -i

1 Like

Actually, the command @0xTim shared without dot format is very readable:

-> % swift package show-dependencies
β”œβ”€β”€ swift-nio<>
β”‚   └── swift-atomics<>
β”œβ”€β”€ swift-nio-ssl<>
β”‚   └── swift-nio<>
β”‚       └── swift-atomics<>
β”œβ”€β”€ swift-nio-extras<>
β”‚   └── swift-nio<>
β”‚       └── swift-atomics<>
β”œβ”€β”€ swift-log<>
└── swift-metrics<>

More prior art from the sbt ecosystem; using dependencyTree was always tremendously useful to me back then. It can do a little bit more, but the basic functionality is the same.

Example output:

$ sbt dependencyTree
[info] io.github.gitbucket:gitbucket_2.13:4.36.0 [S]
[info]   +-ch.qos.logback:logback-classic:1.2.3
[info]   | +-ch.qos.logback:logback-core:1.2.3
[info]   | +-org.slf4j:slf4j-api:1.7.25 (evicted by: 2.0.0-alpha1)
[info]   | +-org.slf4j:slf4j-api:2.0.0-alpha1
[info]   |
[info]   +-com.github.takezoe:blocking-slick-32_2.13:0.0.12 [S]
[info]   | +-com.typesafe.slick:slick_2.13:3.3.2 [S]
[info]   |   +-com.typesafe:config:1.3.2 (evicted by: 1.4.1)
[info]   |   +-com.typesafe:config:1.4.1
[info]   |   +-org.reactivestreams:reactive-streams:1.0.2
[info]   |   +-org.scala-lang.modules:scala-collection-compat_2.13:2.0.0 [S]
[info]   |   +-org.slf4j:slf4j-api:1.7.25 (evicted by: 2.0.0-alpha1)
[info]   |   +-org.slf4j:slf4j-api:2.0.0-alpha1

// Yet another great plugin originated by akka team members :wink:

Would be quite nice to replicate this (looks similar to cargo tree) using a SwiftPM plugin -- we could do some query things to answer your specific question "where does this dep come from" etc... Worth some investigation by the community? :slight_smile:

1 Like

swift-package-catalog v0.4.0 now contains a tool called blame, which can be used to attribute dependencies to their direct consumers.

$ swift package blame Atomics NIOCore
direct consumers of Atomics:
0. NIOEmbedded (in 'swift-nio')
1. NIOPosix (in 'swift-nio')

direct consumers of NIOCore:
0. NIO (in 'swift-nio')
1. NIOEmbedded (in 'swift-nio')
2. NIOPosix (in 'swift-nio')
3. NIOSSL (in 'swift-nio-ssl')
4. NIOTLS (in 'swift-nio')

Nice job :slight_smile:

1 Like

That’s close! But my particular need is to see why my project has a particular dependency, so I'd love it to show the chain all the way up to the root.

1 Like

well, dependencies are contagious, so i didn’t think the full recursive penumbra would be that useful. but i can add that if you want!

for now, i suppose you can just feed the descendants back into the blame tool until you get to a leaf node?

For sure, but my intent in asking the question was to find a way to get a direct answer. I basically want to be able to find out why I have some deep dependency that I didn't explicitly add.