Distinguish Package vs Xcode project with #if?

Is there a way to construct an #if statement that satisfies the following:

swift build true
Package.swift opened directly in Xcode. true
swift package generate-xcodeproj false

I am aware of #if Xcode, but that seems to affect both of the bottom two rows.

The use case is a package that vends a subclass of XCTestCase. On iOS and tvOS, it compiles under the conditions marked true above, but fails to compile under the false cell (Unidentified symbol: _OBJC_CLASS_$_XCTestCAse). Clients should be able to use it where possible, but still be able to use the other vended types without a compiler failure in the remaining set‐ups.

You can pass a custom flag during project generation (swift package -Xswiftc -DDISABLE_TESTS generate-xcodeproj) but there's nothing builtin for this. However, your use case sounds like a bug in the generated project. Do you mind filing a JIRA?

I filed it: SR‐11524

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!