"TEST SUCCEEDED. Executed 2 tests — the two that already existed. The new test file compiled nowhere."
Why iOS Needs an Xcode Project, and Why Nobody Edits It
A SwiftPM package cannot express an iOS app: its asset catalog and app icon, an embedded share extension, an embedded watch app, entitlements, an explicit Info.plist, provisioning and a test host. Those live in an .xcodeproj, whose project.pbxproj is a file no human should hand-merge. XcodeGen resolves the tension: you write a readable project.yml, run xcodegen generate, and the Xcode project is produced from it. Every iOS and watch app in this family works this way. The YAML is the source; the project is output.
What Goes in project.yml
- Project-wide settings that must never drift:
DEVELOPMENT_TEAM,CODE_SIGN_STYLE: Automatic,SWIFT_VERSION, andMARKETING_VERSIONplusCURRENT_PROJECT_VERSIONat project level, so an app and its extension always carry the same version pair — App Store Connect refuses an extension whose numbers differ from its host. packages: the shared Swift package, by path.targets: the app (type: application), its extensions (app-extension), the watch app, and test bundles — each with its sources, dependencies,infoproperties,entitlementsproperties and a scheme.
The Two Clicks That Do Not Survive
Anything you change in Xcode's UI changes the generated project, and the next xcodegen generate replaces it. Picking the team in Signing & Capabilities works exactly until then. That is why the team ID is written in project.yml, and why the family's build-number bump edits project.yml, never the pbxproj.
The File the Suite Never Ran
XcodeGen snapshots the file list when it runs. A new .swift file added afterwards is not in the project at all. For an app source that shows up as "type not found". For a test file it is silent: xcodebuild test reports ** TEST SUCCEEDED ** for the tests the project knew about, and the new ones never ran. The family fixed it twice over: the test script runs xcodegen generate before xcodebuild test, and a check asserts every Swift file under the source and test folders appears in the generated project, so a stale project fails loudly instead of reporting a green it never earned. (Family repositories disagree about whether the generated project is committed at all; either way, regenerate before you trust a result.)