SwiftPM: Same sources, multiple targets

I'm looking for a way to build same source files into multiple libraries, but with different Swift Settings.

Let's say I have my sources under:

Sources/MyLib

Then, I'd like to define targets like following:

.target(
    name: "MyLibWithFeatureA",
    path: "Sources/MyLib",
    swiftSettings: [.define("ENABLE_FEATURE_A")]
),
.target(
    name: "MyLibWithFeatureB",
    path: "Sources/MyLib",
    swiftSettings: [.define("ENABLE_FEATURE_B")]
),
...

However, the compiler is throwing an error: target 'MyLibWithFeatureA' has sources overlapping sources.....

What is the reason for this restriction and is there a way to workaround it?

I have a library that should conditionally integrate with a number of features. Ideally, users of the library would pass some configuration to the package dependency which features to enable, but I don't think SwiftPM supports anything like that.

Therefore, my idea is to expose the library as multiple libraries, each building from the same core, but with different combinations of features enabled. I know this doesn't scale well, but seems to be the only way at the moment.

Btw, having MyLib target and then MyLibWithFeatureA that depends on MyLib is not possible in my case since code is not strictly additive. It would be possible to do that if swiftSettings were applied to dependencies, but they are not :cry:

1 Like

Generally, compiling the same files twice leads to duplicated symbols.

I don't understand why you can't just put features A and features B in separate targets with separate files and then put code common to both in a third target which is depended on by the former two.

I can't do that because including one of the features affects how common code works. Of course, I could pass down some configuration flags, but it's not really feasible and changes the end product.

Generally, compiling the same files twice leads to duplicated symbols.

Only if the targets end up in the same product, which is not necessarily the case.


I have found that you can work around this limitation with a symlink to the target's source directory, i.e.:

project-dir
└── Sources
   ├── the-target
   ├── the-target-variation -> the-target
   ├── ...

Though I don't know whether this is supported or only works by accident.

2 Likes

Thanks @woolsweater. Symlink seems like it could do the trick, but Xcode doesn't like it.

WARNING: If you use symlinks, you must make them relative, because when someone else checks out your package, you'll have no idea what the absolute path to the package will be.

I am running into the EXACT same issue. I wish that targets could have variations that could share the same source, but simply defines a macro that would be applied when building that variation.

I have a project that uses a very large common library that needs to have different variations based on platform and usage. #if ing all of the code on a per platform basis is easy; but now I need to accommodate a server variation of the same library that would have no interface on Linux or macOS. So I wanted to design a 'target' that uses all the same code, but as you tried to do simply introduces a .define("SERVER") macro and I ran into the issue that it could not share the same source despite them being mutually exclusive, they would never recursively import each other.

If there is already a pitch to solve this issue, I would love to be pointed towards it --otherwise, it would be great to pitch variations on targets.

1 Like

Me too..
I also have some code, for example, auxiliary code for testing the code during testing, protected by the TESTABLE flag .define("TESTABLE"), but I do not want to provide these functions for assembly in the release, for example:(

we have BuildConfiguration in SPM but this not support customization when we change action from RUN to TESTING ;(
we can define for example .define("SOME", .when(buildConfiguration: .debug)) but i want also when run action behavior ;) Because .debug is not only for test )))

I ran into this issue where I wanted to share some test utilities across different test targets. I followed the best practice and defined a separate target "TestUtil" and depended the tests on this new target.

Adjust the package:

let package = Package(
/* ... */
    targets: [
       .target(name: "MyLibrary"),
       .target(
           name: "TestUtil",
           dependencies: ["MyLibrary"]
       ),
        .testTarget(
            name: "MyLibraryTests",
            dependencies: [
                "MyLibrary",
                "TestUtil"
            ]
        ),

Then move the shared test utility sources in Sources/TestUtil/, and use import TestUtil in the tests to get access to the utilities.

This compiles and runs as expected.

However, the benchmarks fail, with some degrading a lot. I have tried to mitigate the performance hit by using @final and @inlinable, which had some result. But for performance reasons, it may be better to keep some duplication instead of modularising all shared code.