In the end I found a way to differentiate between the two Xcode set‐ups, but it is rather roundabout and fragile.
I first stuck this temporarily into Package.swift
:
import Foundation
fatalError("\(ProcessInfo.processInfo.environment)")
And with it there, I ran these two commands, noting the differences between the reported environments:
-
xcodebuild -list
(with no Xcode project in the directory) swift package generate-xcodeproj
Any difference can be used, but the thing that looked the most reliable to me was the fact that Xcode prepends itself to PATH
when it is a superprocess. To turn this difference into a compile time condition, I added the following to the tail end of Package.swift
:
import Foundation
let path = ProcessInfo.processInfo.environment["PATH"] ?? ""
let firstColon = path.range(of: ":")?.lowerBound ?? path.endIndex
let firstEntry = path[..<firstColon]
if firstEntry.hasSuffix("/Contents/Developer/usr/bin") {
let myTarget = package.targets.first(where: { $0.name == "MyTarget" })!
var settings = myTarget.swiftSettings ?? []
settings.append(.define("MANIFEST_LOADED_BY_XCODE"))
myTarget.swiftSettings = settings
}
Then #if MANIFEST_LOADED_BY_XCODE
can be used in the target’s source code, yielding the following table:
swift build |
#if !Xcode |
Package.swift opened directly in Xcode |
#if Xcode && MANIFEST_LOADED_BY_XCODE |
swift package generate-xcodeproj |
#if Xcode && !MANIFEST_LOADED_BY_XCODE |
Note that while this is useful to evade Xcode bugs, deliberately designing a package to compile differently in these situations would generally be a bad idea.
If anyone out there finds a better workaround, let me know.
P.S. Hurray for executable manifests!