How to use a custom build configuration in a Swift Package?

How can I do something like this:

    .target(
        // some other settings ...    
        swiftSettings: [.define("ENABLE_TESTABILITY=YES", .when(configuration: .testing))]
    )

We use a custom configuration called "Testing" (instead of the default "Debug" or "Release") when conducting unit tests.

However, Swift packages only seem to know about .debug and .release configurations.

As a result this package's own tests cannot @testable import it when it's built and tested as part of a Scheme that defines "Testing" as the build configuration to use when building tests.

4 Likes

Packages don't support custom build configurations.

Some replies to this earlier post seemed to indicate that Xcode 11.3 had added support for custom build configs to Swift packages: Swift package manager and custom build configurations

If that's wrong and indeed there is still no way to add another config besides Debug or Release, do we have a timeline on when this would be added? This could be deal-breaker for us... the only workaround I've seen, requires the bundle name to be different based on the configuration.

Maybe we can add a check for a compile-time environment variable or something.

Like maybe it would work in our .xcconfig files to have:

GCC_PREPROCESSOR_DEFINITIONS = TESTING=1

Then in Package.swift:

#if TESTING
// define target including "ENABLE_TESTABILITY=YES"
#else
// don't
#end

Question is, when Package.swift is compiled, will config-specific GCC flags have been set yet?

Prior to 11.3, there were issues when using packages with Xcode targets that were using custom configurations. Those have been fixed, but nothing has changed in regards to being able to conditionalize based on custom configurations.

Packages do not inherit build settings from projects in any way.

Has anyone made a proposal to implement support for alternate configs?

Seems like it would suffice to simply have a third option called .custom("MyConfigName") in addition to .debug and .release.

This seems more like a workaround than a solution to me, because now clients of that package need to use "MyConfigName" to get that behaviour. What if I am using two packages with different custom configurations that I need to use? I think this really only works if the packages and the client are tightly coupled, e.g. when you are using packages to break up a larger app.

What would be better is something like "Package Features" idea that Daniel mentioned in My SwiftPM wishlist (aka proposal proposals), a way to opt in to building the package with additional build settings that is independent of the build configuration and can be selected on a per-package basis.

1 Like

The same argument applies to the use of "Debug" and "Release" configurations though.

These are merely arbitrary labels subject to change by the user. There's no guarantee someone will have a "Debug" or a "Release" configuration nor is there any objective reason why they should be required to have one.

At least half the projects I've worked on in my career changed these to other things, such as "Dev," "Staging," and "Distro" etc. It's also common for a specific team within the org with their own CI to use special configs that only they and their CI use, to insulate the production CI pipeline from whatever they're doing.

In our case we're simply using Packages to create modules to add new modules to our app (in the past we always used Frameworks but we are hoping to use Swift packages now since it's so much lighter-weight than creating a whole framework, and nobody likes dealing with merge conflicts in Xcode project files when multiple devs are moving files around).

The inability to specify the name of our existing build configurations is really crippling for using Swift packages within our app. I don't understand why simply letting the user specify whatever configuration name they want would be a big deal. We use a monorepo so there's no sense in which "package management" even comes into play for us, at least not for our app's own modules.

If you're making a Swift Package for distribution, and the concern is that people won't want to add configurations with the names specified by the package, then I agree with you, we shouldn't use "the name of which configuration is active" as a criteria in the first place.

What would be better is something like "Package Features" idea that Daniel mentioned in My SwiftPM wishlist (aka proposal proposals),

100% agreed here.

Like maybe a package could define its own build-time flags like "MyPackageName_Testing", and the BuildConfigurationCondition would be contingent on this flag being set.

I think there is a small misconception, because the model for packages is slightly different from the one for Xcode targets you are describing here. Packages always have release and debug configurations, there is no way to rename or remove them. When building in Xcode, one of those two gets picked, based on heuristics (e.g. if the scheme uses "Development", debug gets used).

Are you primarily asking for a new feature, or asking how to best update your existing set‐up?

If the second, what exactly are you trying to accomplish?

The package’s own tests can @testable import its modules without any extra set‐up. Debug builds have testing enabled. If it isn’t working for you, what is the error message you’re getting?

Outside Xcode, the manifest inherits the terminal’s environment, so you can do stuff like this if necessary, as long as you stick to the command line (or use it to generate your Xcode project).

It may help ease the transition of your workflow, but consider it a last resort. Always prefer using directly supported methods instead wherever possible.

the model for packages is slightly different from the one for Xcode targets you are describing here. Packages always have release and debug configurations, there is no way to rename or remove them

If that's the case then a Swift Package should not derive it's build configuration from the active Xcode configuration in any way.

Instead, each Xcode configuration should independently define whether to use a "Debug Package-build-configuration" or a "Release Package-build-configuration".

Because I'm not the one who's confusing these two concepts. They're confused in the design itself.

The problem is, we have a scheme called "AllTests" which builds and runs all the various test targets of our modules.

If I just build the Package by itself, then its tests are able to use @testable import just fine.

However if I build the package as part of this other scheme, then it no longer works.

To get around the currently insufficient implementation of "conditionality based on build configuration", the workaround I came up with is to define a second, identical .target in Package.swift, which is named MyPackage_Testing. I then setup Sources/MyPackage_Testing as a symlink to Sources/MyPackage.

The tests will @import MyPackage_Testing. Then in the .target definition for MyPackage_Testing I defined swiftSettings: [ .define("ENABLE_TESTABILITY") ].

Kind of hacky but it works.

1 Like

That's the problem. Can we please fix this by allowing Swift Packages to have whatever configurations the user wants them to have?

Either that, or Xcode needs a way to allow the user to map any Xcode configuration to whichever Swift Package configuration is desired, since the current behavior is totally broken.

I'm going to file an Xcode bug with Apple about this since, it's not clear whether the fix should be a change to Xcode or a change to SPM. If I should also file an SPM ticket let me know!

UPDATE: Filed the bug with Apple developer bugs & feedback under FB8914293.

BTW this solution turned out not to be viable since basically, now all source files appear twice in Xcode which is just not cool

This is still a major issue that's currently interfering with my organization's ability to actually use Swift Packages in Xcode.

There needs to be a way to support other configurations than "Release" and "Debug" in SPM. Do we need to make a proposal to Swift Evolution about this or is this being actively addressed already?

Note: I still don't understand why, if a scheme is configured to build a given package only for "Testing", then Xcode would still not build for it testing, just because the configuration is called "Testing". This makes literally no sense.

1 Like

This is not a topic we are actively pursuing at the moment. You could start an evolution pitch thread if you have a concrete design for a SwiftPM feature in mind and/or file radars to make your desire for different behavior in Xcode known.

This is not a topic we are actively pursuing at the moment. You could start an evolution pitch thread if you have a concrete design for a SwiftPM feature in mind and/or file radars to make your desire for different behavior in Xcode known.

Yeah I did file bug reports with Apple about this awhile back, to no avail so far.

And I just made a pitch to let SwiftPM support more configurations. I don't have a concrete design yet but I'm sure we can come up with one if the pitch doesn't get rejected outright. Just want to make sure people are open to the idea first before investing the time to come up with a concrete proposal.

There was an evolution pitch that was accepted https://github.com/apple/swift-evolution/blob/main/proposals/0273-swiftpm-conditional-target-dependencies.md

This would allow for targets and settings have conditional configuration for the build and the platform. But in the implementation I only see that there is a BuildSettingsCondition. What happened to the TargetDependencyCondition that should also have a BuildSettingsCondition like the proposal described?

The reason for this feature is security. For a banking application it is very important that production env variables are not in debug build. They also have to come from a different source. So now I created 2 target libraries EnvironmentProduction and EnvironmentSandbox. I want both to be set as a dependency only when in release or in debug.

As a workaround I can now do

let dependencies: [Target.Dependency] = ["Environment", "EnvironmentProduction", "EnvironmentSandbox"]

 targets.append(
        .target(name: "Run",
                dependencies: dependencies,
                swiftSettings: [
                    .define("PRODUCTION", .when(configuration: .release)),
                    .define("SANDBOX", .when(configuration: .debug))
                ]
        )
    )

And in code then main.swift

#if PRODUCTION
import EnvironmentProduction
#else
import EnvironmentSandbox
#endif

...

But this would be more secure if I could do, as then the EnvironmentProduction does not have to be stated as a dependency, only for release builds.

#if canImport(EnvironmentProduction)
import EnvironmentProduction
#else
import EnvironmentSandbox
1 Like

This hasn't been implemented yet, see status of that same proposal:

Status: Partially implemented (Swift 5.3 supports platform conditionals, but not configuration conditionals)

BuildSettingsCondition is useless for our needs because you cannot specify simulator as different from device builds and we don't have a way to map our Xcode configurations to build configurations.

Because you can't make a BuildSettingsCondition depend on which Xcode configuration is being used.

For example in one project we have four Xcode configurations, "Prod", "Staging", "Testing", and "Dev". (There literally is no "Debug" or "Release" config.)

Swift Packages aren't directly aware of these configurations and there's no way to make build configurations specific to a given Xcode configuration. SPM uses some kind of automatic heuristic to "decide" whether to use its "Debug" or "Release" configuration based on the name of the Xcode configuration, which is worse than useless because it's not even predictable. For example, for example it maps "Testing" configuration to "Release" which breaks @Testability in all of our Swift packages.

So annoying!

Terms of Service

Privacy Policy

Cookie Policy