SR-3033 Poor diagnostic when importing executable target module in a test
SR-4760 SwiftPM should support easier testing of CLI tools
SR-11866 SwiftPM: Test targets can't depend on executable products
If I have a directory that has a source file and a main.swift in it (e.g. Sources/Foo/main.swift and Sources/Foo/Foo.swift) I can't write run a test for Foo.swift. I get an error that looks like this:
/code/.build/debug/Foo.build/main.swift.o: In function `main':
/code/Sources/Foo/main.swift:(.text+0x0): multiple definition of `main'
/tmp/LinuxMain-3d0090.o:/code/Tests/LinuxMain.swift:(.text+0x0): first defined here
The text was updated successfully, but these errors were encountered:
This isn't something we can currently solve, at least in the way desired for here, where the Tests module is actually capable of importing the code in the executable module.
There are a couple reasons why this is difficult:
1. On Linux, where we need the tests to be a main executable (because of the XCTest implementation), then it isn't going to be possible to link together at all, because of the two main implementations. This is the immediate blocker.
2. Even if we didn't have a main executable, it isn't always possible to link for code which is compiled as the main executable. This is possible with Mach-O images, but I'm not sure if ELF does and I'm not 100% sure that Swift doesn't make assumptions about optimization of modules with main functions.
3. We could try and pull some switch where we built the executable but without its main.swift, but that would require duplicate compilation and break the Swift whole module compilation model (or at least, make it surprising).
The situation here is exactly like it is with C/C++ – if you want to be able to dynamically share/load a body of code it needs to be put into a library, not an executable.
I think what we should actually do for this specific code is not put the swift module for executables in a place it can be imported, but we can't otherwise support this without a radically different testing model which depends on the compilers ability to understand the test code (and compile it onto the side, or something).
We could allow the Package to declare its "entry points", and make executables just a kind of entry point, and let SwiftPM synthesize the code for the main executable. This would allow you to avoid ever writing the `main.swift` if it was as simple as a single function call. The upside of this approach is it also encourages people to write command line tools as testable libraries, which I think is a good thing. The downsides is it doesn't work with any other top-level code, which is very useful for small / script-style command line executables, so we would end up with two ways of doing things.
I'm a bit concerned that we have left this open for three years without even proposing a documentation or error change to work around it. SwiftPM does actually know if a module has a `main.swift` in it, and we could surely arrange to get a somewhat helpful error message emitted that says "It looks like you're trying to test an executable: this isn't supported, consider moving the code you want to test into a library module instead." I've spoken to people who tried out Swift and bounced off because they hit this, which results in an ugly linker error.
While I'm here, I should note that the Package.swift that SwiftPM itself generates from swift package init --type=executable encourages you into this pattern, by having only a single executable target which is a dependency of the test target.