This would be very helpful. I was wanting this feature the other day and was going to ask if this had been discussed yet.
The use case I had was when working on a project that is both a Swift Package and Xcode Framework that depend on C code in the same git repository. I have it setup such that the C code is one target of the Package and the Swift target depends on the C target.
But this resulted in me needing to use import to get my C module when compiling with swift build and the import being invalid when compiling with Xcode and the need for different #import statements in the C code. I could have made the Xcode project compile in such a way that it uses import as well, but I've had undefined symbol issues when sharing the framework binary with other people when taking that approach, so I avoid it.
My solution was to make swift build just work by doing the extra flags in Xcode by passing in -DXCODE_FRAMEWORK=1 to both C and Swift. Then using #if to determine if I import the C module and how to #import my headers.
In addition to per target basis it would be nice if you could specify flags for different compilation modes such as debug and release. I have seen quite a few people suggest adding -DDebug as a debug swift build flag for Xcode projects so you can use #if Debug to make decisions.
It’s not exactly what you're asking for, but it is possible to specify a .json destination file via the --destination file.json argument to swift build. In that, you can configure flags for the linker, swiftc, and clang.
I can't find the documentation right now, but the source file within swiftpm is here, and all of the members of the Destination struct can be set through the json file.
It's also possible to set Xcode-specific configuration flags through the --xcconfig-overrides argument to swift build, using an xcconfig file.
An example json destination file looks something like this:
destination.json is for quite different purpose
As you see toolchain-bin-dir, target, sdk all is required all configure "custom target"
For example we use global destination.json for build for android
Dont see xcconfig overrides
error: unknown option --xcconfig-overrides
This is definitely something we need. Some of the devs have discussed this in the past and started working on a proposal for a real build settings model, which I think is the right way to support this sort of thing. We put that on hold for a while due to other priorities, but I hope we'll be able to get back to it and kick off a discussion here with an initial proposal before too long.
I've been thinking about this a bit as part of some experiments.
My current thinking is that for it to generalize properly, you need two things:
a high level key/value way of specifying settings
a series of mappings which convert/transform them so that they can be supplied in whatever form any given tool requires
For example you might specify something like the minimum platform setting as
.setting("minimum-target", "macosx10.12"),
or even
.minimumTarget(.macosx10.12),
but you want to be able to then transform it depending on what you're doing.
When invoking the Swift compiler you want it to be transformed to "-Xswiftc", "-target", "-Xswiftc", "x86_64-apple-macosx10.12".
But you might also want to generate an xcconfig (as part of swift package generate-xcodeproj), in which case you'd want it to be transformed into MACOSX_DEPLOYMENT_TARGET = "10.12".
You might also want to pass it to a bundler tool to inject the same value into the Info.plist file, in which case you'd probably need another transformation again.
For any non-trivial project you probably need to be able to sub-divide settings into groups that are applied in certain contexts: "here are the common settings", "here are the ones that only apply in debug", "here are the ones that only apply on the Mac", etc.
This could be done with straight inheritance (xcconfig-style), but in Xcode it tends to lead to a combinatorial explosion where you have to define a top-level xcconfig file for every combination you care about, and then #include the relevant bits into it. You end up with files like MyProjReleaseWithAssertionStatic.xcconfig, which includes MyProjReleaseWithAssertion.xcconfig and MyProjCommon.xcconfig, which in turn import other things, etc, etc. This can get messy if you're also importing settings from sub-projects or libraries, and they also have the same explosion.
I started thinking it might be more flexible to allow some kind of conditional-filtering which lets you mix-in / import settings under certain conditions - without having to explicitly specify every combination.
Workaround for Xcode project with local packages. In your local package you can get the Xcode flags (ie: OTHER_SWIFT_FLAGS) using xcodebuild -showBuildSettings.
Warning this seems to work but might not in the future.
// swift-tools-version: 5.6
import PackageDescription
import Foundation
// Path to .xcodeproj relative to the folder Package.swift is in
let settings = otherSwiftFlags("../MyXcodeProject.xcodeproj")
let package = Package(
name: "MyPackage",
platforms: [
.iOS(.v15),
],
products: [
.library(name: "MyLibary", targets: ["MyLibary"])
],
dependencies: [ ],
targets: [
.target(name: "MyLibary",
swiftSettings: settings
),
.testTarget(
name: "MyLibaryTests",
dependencies: ["MyLibary"],
swiftSettings: settings),
]
)
func otherSwiftFlags(_ path: String) -> [SwiftSetting] {
var settings = [SwiftSetting]()
let task = Process()
let pipe = Pipe()
let command = "xcodebuild -project \"\(#file.replacingOccurrences(of: "Package.swift", with: ""))\"\(path) -showBuildSettings | grep OTHER_SWIFT_FLAGS"
task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.standardInput = nil
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8),
let regex = try? NSRegularExpression(pattern: "-D [a-zA-Z0-9_]+", options: []) else { return [] }
let matches = regex.matches(in: output, options: [], range: NSRange(location: 0, length: output.count))
matches.forEach {
let r = $0.range(at: 0)
guard r.location != NSNotFound,
let flag = (output as NSString).substring(with: r).components(separatedBy: " ").last
else { return }
settings.append(.define(flag))
}
return settings
}
Has there been any further development on this topic ? I need to be able to specify a library type in a Package.swift depending on an external condition (namely if the package is built through xcodebuild or Xcode), so :
#if TEST_FLAG
let libType:Product.Library.LibraryType? = nil
#else
let libType:Product.Library.LibraryType? = Product.Library.LibraryType.dynamic
#endif
(...)
let package = Package(
name: "TestPackage",
products: [
.library(
name: "TestPackage",
type: libType,
targets: ["TestPackage"]),
]
but I tried passing -DTEST_VALUE through OTHER_SWIFT_FLAGS both through xcodebuild or in Xcode settings, and neither seem to work.
Hi glaurent, I was trying the same approach. But I couldn't find a good place to set the environment variable.
May I ask when and where did you put the export SPM_GENERATE_FRAMEWORK=""?