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.
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.
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.