Do test targets in a Package.swift need to include transitive dependencies?

I have the following package description:

let package = Package(
  products: [ ... ],
  dependencies: [
    .package(url: ...), // X
  ],
  targets: [
    .target(name: "A"),
    .target(
      name: "B",
      dependencies: ["A", .product(name: "X", package: "X")]
    ),
    .testTarget(
      name: "BTests",
      dependencies: ["B"] // does this need to include "A" and "X"?
    ),
  ]
)

Do I need to specify A and X explicitly as dependencies for BTests?

It's no problem obviously to do that, but it feels like noise and I'd like to keep my package description as free of clutter as it can be.

Within BTests, I also import A and import X and use some minimal functionality from each of them (the B target itself is a kind of unofficial cross-import overlay, gluing A and X together). Can I rely on SwiftPM to recognise the dependency and make those modules available to import within BTests? It does seem to work, but I'm not sure if it's guaranteed to work.

Thanks!

Best practice would be to declare everything you use directly (i.e. import), even if it logically must be there for another of the dependencies to have been built successfully. Transitive dependencies whose APIs you do not touch or which were forwarded to you by re‐exporting should not need to be declared.

At the moment all build products are placed in the same directory, so the compiler finds what it needs anyway and it doesn’t seem to matter. But some work has been done recently toward providing diagnostics about out‐of‐graph imports that can fail or not depending on what has or hasn’t been built yet. This work will likely turn even provably present transitive dependencies into warning for the sake of consistency.

Note that when the middle node is in a different package that isn’t pinned exactly, then it can end up resolved to a different version that no longer includes the dependency it used to bring with it. In that case leaving the transitive dependency undeclared risks compilation failure even now.

1 Like

Thanks!

Yes this is exactly what I was concerned about. It seems unfortunate that the test target needs to restate all of the dependencies from the targets it is testing.

What do you mean by "middle node" here? Do you mean X? Because I'm only using a declared product of package X, not any of its transitive dependencies (I would expect that to be fragile).

No. I was referring to if your example were refactored into the realm you call “fragile”. Namely, if...

  • A, B and C are in different packages,
  • B depends on C, and
  • A imports both B and C, but only declares a dependency on C,

...then it appears to work at first.

But as soon as B releases a patch update that no longer depends on C, then A is suddenly and unexpectedly broken, because C is AWOL.

(If A checks in its pins, its authors might not notice, but those pins won’t apply to any higher level package that depends on A. So to those clients A will just seem broken and it will have been A’s fault.)


Your particular manifest as posted is “safe”, but as I have shown there are other cases which are not “safe”. I predict based on the work I have seen that your case will probably be disallowed along with them for the sake of consistency and having a simpler rule for users to learn. But I am neither the one doing that work nor the one responsible for the decision. You are welcome to chime in on the relevant pull requests (e.g. #3562) and evolution proposals as they come along.