SPM "test-target-only" dependencies

I'm facing a problem with SPM, and my understanding from my research so far is that SPM simply currently does not support what I want to do, so what I'd like to know is if someone could point me to the relevant discussions on this topic or share any insight about the status of this feature.

Here's my setup. I have a package called CoreToolkit, which is my personal toolkit for miscellaneous, generally useful code. I also have another package called XCTestToolkit, which extends the XCTest framework in useful ways and which I import and use for the unit tests of all my other packages, including my unit tests for CoreToolkit. My problem is that, as far as I know, SPM does not give me any way to specify that XCTestToolkit is only used by the test target of CoreToolkit, not by the CoreToolkit library product itself. As a result, I can't use any of my CoreToolkit code in XCTestToolkit because if XCTestToolkit depends on CoreToolkit then SPM thinks there is a cyclic dependency. Unless I'm misunderstanding something I think this is an unnecessary problem to have because the reality is that XCTestToolkit depends on CoreToolkit, CoreToolkitTests depends on both CoreToolkit and XCTestToolkit and CoreToolkit depends on nothing.

Does anyone know if there are any plans to lift this restriction?

SwiftPM can tell, because you had to specify the dependency on the particular target you want. Newer versions of SwiftPM are getting better and better at skipping dependencies not touched by the product whenever a client depends on your package.

I can't use any of my CoreToolkit code in XCTestToolkit because if XCTestToolkit depends on CoreToolkit then SPM thinks there is a cyclic dependency.

The real problem here is that you are trying to wedge an external package in between two targets in the same package. This is a cyclical dependency at the package level.

The standard arrangement for what you are trying to do is to have one package (CoreToolkit) with two products (CoreToolkit and XCTestToolkit).

There is a reason cyclical dependencies at the package level (where the version is attached) are rejected even if their target graphs could theoretically work out in the short term: Breaking changes would be impossible. When it came time to start working on CoreToolkit 2.0.0, you would have to disable the tests, since they depend on XCTestToolkit and in turn CoreToolkit 1.0.0, which cannot co‐exist with the new code you are trying to test.

1 Like

Thank you! I'm unexpectedly satisfied with the solution of putting them in the same package as different products.

I always enjoy when the compiler teaches me things I didn't know about my own concepts - when "annoying" error messages turn out to be golden pieces of architectural advice. I hadn't seen the analogy between my CoreToolkit and XCTestToolkit packages until now... now I see that they're really the same thing - XCTestToolkit is just the manifestation of CoreToolkit in the test universe. Since every Swift package I write should theoretically have unit tests, and "every Swift package" is also pretty much the target demographic of CoreToolkit, it's in fact entirely natural to put them in the same package - I realize now that I wouldn't have it any other way.


1 Like