Skip to content

Automated build system #7

Description

@aspauldingcode

I hate xcode.

https://docs.tuist.dev/en/#installation
let's look into this maybe.
or even https://github.com/yonaskolb/XcodeGen.

Got it — I’ll research how developers manually build and run SwiftUI-based watchOS apps entirely from the command line, including how they install simulator runtimes, launch simulators, build apps, and attach debuggers without opening the Xcode GUI. I'll get back to you shortly with a detailed breakdown.

# Command-Line Setup and Simulators

Developers can install and manage watchOS (and iOS) simulator runtimes entirely via the terminal. After installing Xcode (or selecting it with `xcode-select`), run `xcodebuild -runFirstLaunch` to complete initial setup (this installs essential components like the `simctl` tool) ([[How to Manually Download and Install iOS Simulator Files for Xcode on Mac | by Pouya Hallaj | Medium](https://medium.com/@pouyahallaj/how-to-manually-download-and-install-ios-simulator-files-for-xcode-on-mac-b146923fd198#:~:text=4,signature%20and%20installs%20it%20securely)](https://medium.com/@pouyahallaj/how-to-manually-download-and-install-ios-simulator-files-for-xcode-on-mac-b146923fd198#:~:text=4,signature%20and%20installs%20it%20securely)).  Then download simulator platforms as needed. For example, to fetch the latest watchOS simulators, use: 

```bash
xcodebuild -downloadPlatform watchOS

(or xcodebuild -downloadPlatform iOS for iOS, or xcodebuild -downloadAllPlatforms for all) ([

        Deploying Xcode on managed devices
    
](https://www.jamf.com/blog/managed-xcode-deployment/#:~:text=xcodebuild%20,watchOS)) ([xcode - How to install iOS simulators from command line - Stack Overflow](https://stackoverflow.com/questions/34493934/how-to-install-ios-simulators-from-command-line#:~:text=xcodebuild%20)).  This will download the SDKs/runtimes for the specified platforms without opening Xcode.  Alternatively, you can manually download runtime DMG files from Apple’s developer downloads and install them with `xcrun simctl runtime add <path/to/runtime.dmg>` (e.g. `xcrun simctl runtime add "~/Downloads/watchOS 10 Simulator Runtime.dmg"`) ([How to Manually Download and Install iOS Simulator Files for Xcode on Mac | by Pouya Hallaj | Medium](https://medium.com/@pouyahallaj/how-to-manually-download-and-install-ios-simulator-files-for-xcode-on-mac-b146923fd198#:~:text=4,signature%20and%20installs%20it%20securely)).  In summary, the sequence is typically:

These steps ensure the watchOS simulator SDKs are present and up to date without ever launching Xcode’s GUI.

Building the watchOS App via CLI

You can build a SwiftUI-based watchOS app entirely from the terminal using Xcode’s build tool. For example, if your project or workspace is MyApp.xcworkspace with scheme MyWatchApp, you might run:

xcodebuild -workspace MyApp.xcworkspace \
           -scheme MyWatchApp \
           -destination 'platform=watchOS Simulator,name=Apple Watch Series 9 (45mm)' \
           build

This invokes Xcode’s build system from the CLI, targeting a specific watchOS simulator device ([Going Deeper: Xcode’s Command-Line Tools and Advanced Workflows | by Mihai Popa | Medium](https://medium.com/@mihaipopa/going-deeper-xcodes-command-line-tools-and-advanced-workflows-52f5d39e5855#:~:text=xcodebuild%20,build)) ([Build for watchOS simulator | Bitrise Help Center](https://support.bitrise.io/en/articles/9676552-build-for-watchos-simulator#:~:text=WATCH_SIM_ID%3D%24%28xctrace%20list%20%20devices%20,destination%20%22platform%3DwatchOS%20simulator%2Cid%3D%24WATCH_SIM_ID)). Key points:

In short, use xcodebuild as you would for iOS, but with platform=watchOS Simulator and the appropriate device name or UUID. You can list available simulators with xcrun simctl list devices to pick a name or UDID.

Launching Simulators from the Terminal

Once the simulator runtimes are installed, manage simulator devices with simctl. For example:

  • List available devices:
    xcrun simctl list devices
    Look under the “== Devices ==” section for watchOS simulators (e.g. “Apple Watch Series 9 (45mm) – watchOS 10.0” along with a UDID).
  • Boot a simulator:
    xcrun simctl boot <UDID>
    or to boot the first matching bootable watchOS sim by name:
    xcrun simctl boot "Apple Watch Series 9 (45mm)"
  • Shutdown or erase: You can shut down or erase similarly with simctl shutdown <UDID> and simctl erase <UDID>.

No GUI is needed: once booted, the simulator runs headless in the background (use xcrun simctl booted as an alias). You can combine these in scripts. For example, to create and launch a new watch simulator, one might do:

SIM=$(xcrun simctl create "MyWatchSim" "Apple Watch Series 9 (45mm)" watchOS10.0)
xcrun simctl boot "$SIM"

(This pattern is more common for iOS, but watch simulators can be created similarly by specifying a watch device type and runtime.)

Installing and Launching the App on Simulator

After building, deploy the app bundle to the simulator and launch it via simctl. For watchOS apps, you generally only need to install the WatchKit app bundle. For example, if your build produced MyWatchApp.app (the WatchKit App target), install it onto the booted device:

xcrun simctl install booted /path/to/MyWatchApp.app

You do not separately install the watch extension or payload – all watch-related resources are inside the .app bundle. (In Xcode’s terms, the .app bundle contains the WatchKit resources and extension.) This single simctl install is sufficient ([ios - Installing a WatchKit App via simctl - Stack Overflow](https://stackoverflow.com/questions/28597316/installing-a-watchkit-app-via-simctl#:~:text=install%20app%20,it)).

To launch the app on the simulator, use its bundle identifier. For a watch app, the bundle ID is typically your app’s (as set in Xcode’s target settings). For example:

xcrun simctl launch booted com.mycompany.MyWatchApp

If your watch app’s target name or bundle ID was altered (as sometimes happens for watchKit app IDs), ensure you use the exact ID. For instance, one example noted that after installing the main app, you launch the Watch app by its identifier (which may look like com.company.watchkitapp) ([ios - Installing a WatchKit App via simctl - Stack Overflow](https://stackoverflow.com/questions/28597316/installing-a-watchkit-app-via-simctl#:~:text=install%20app%20,it)). You can also launch from the IDE once (via Xcode) and check its console to see the exact ID, or list installed apps via simctl.

In effect:

Debugging with LLDB from Command Line

You can attach the LLDB debugger to a simulator-run app entirely via the terminal. Two common methods are:

  1. Attach by name with lldb: Start LLDB pointing at your app binary, then attach. For example:

    xcrun lldb /path/to/MyWatchApp.app/MyWatchApp
    (lldb) process attach --name MyWatchApp --waitfor

    Then, launch the app in the simulator (e.g. via another terminal or Xcode). LLDB will wait and attach when it starts ([Using LLDB with iOS Simulator from CLI - LLDB - Swift Forums](https://forums.swift.org/t/using-lldb-with-ios-simulator-from-cli/33990#:~:text=xcrun%20lldb%20,launch%20the%20app%20in%20simulator)).

  2. Launch via simctl and attach by PID: First run the app via simctl, capture its PID, then attach. For example:

    PID=$(xcrun simctl launch booted com.mycompany.MyWatchApp)
    xcrun lldb
    (lldb) process attach --pid $PID

    This uses LLDB to attach to the running process. In one report, the user did xcrun simctl launch <bundleID> which printed the PID, and then in LLDB used process attach -p <pid> ([Using LLDB with iOS Simulator from CLI - LLDB - Swift Forums](https://forums.swift.org/t/using-lldb-with-ios-simulator-from-cli/33990#:~:text=Also%2C%20this%20works)).

Either way works. As a summary from a Swift forums discussion: you can run xcrun lldb <path to .app>, then in LLDB do process attach -n <AppName> -w to wait for launch, and then start the app in the simulator ([Using LLDB with iOS Simulator from CLI - LLDB - Swift Forums](https://forums.swift.org/t/using-lldb-with-ios-simulator-from-cli/33990#:~:text=xcrun%20lldb%20,launch%20the%20app%20in%20simulator)). Or simply launch via simctl launch and attach by PID ([Using LLDB with iOS Simulator from CLI - LLDB - Swift Forums](https://forums.swift.org/t/using-lldb-with-ios-simulator-from-cli/33990#:~:text=Also%2C%20this%20works)). This requires no special “connect” step; LLDB will attach directly to the simulator process.

Example Workflows and Best Practices

In practice, CI and advanced builds use these commands. For example, CI scripts often do:

Open-source examples specifically focusing on CLI watch development are rare, but continuous-integration setups for iOS apps with watchOS components are common. For instance, a Bitrise knowledgebase page explicitly shows using xcodebuild with a watchOS destination ([Build for watchOS simulator | Bitrise Help Center](https://support.bitrise.io/en/articles/9676552-build-for-watchos-simulator#:~:text=WATCH_SIM_ID%3D%24%28xctrace%20list%20%20devices%20,destination%20%22platform%3DwatchOS%20simulator%2Cid%3D%24WATCH_SIM_ID)). In summary, the community best practice is to automate simulator downloads and builds with the above commands, treating watchOS apps much like iOS apps but with platform=watchOS simulator destinations and the appropriate bundle IDs for installation and launch.

Sources: Apple’s docs and developer forums (e.g. on using xcodebuild -runFirstLaunch, -downloadPlatform, and simctl), StackOverflow answers on watchKit deployment, and blog/CI references illustrating these CLI commands ([How to Manually Download and Install iOS Simulator Files for Xcode on Mac | by Pouya Hallaj | Medium](https://medium.com/@pouyahallaj/how-to-manually-download-and-install-ios-simulator-files-for-xcode-on-mac-b146923fd198#:~:text=4,signature%20and%20installs%20it%20securely)) ([ios - Installing a WatchKit App via simctl - Stack Overflow](https://stackoverflow.com/questions/28597316/installing-a-watchkit-app-via-simctl#:~:text=install%20app%20,it)) ([Using LLDB with iOS Simulator from CLI - LLDB - Swift Forums](https://forums.swift.org/t/using-lldb-with-ios-simulator-from-cli/33990#:~:text=xcrun%20lldb%20,launch%20the%20app%20in%20simulator)) ([

        Deploying Xcode on managed devices
    
](https://www.jamf.com/blog/managed-xcode-deployment/#:~:text=xcodebuild%20,watchOS)) ([xcode - How to install iOS simulators from command line - Stack Overflow](https://stackoverflow.com/questions/34493934/how-to-install-ios-simulators-from-command-line#:~:text=xcodebuild%20)) ([Build for watchOS simulator | Bitrise Help Center](https://support.bitrise.io/en/articles/9676552-build-for-watchos-simulator#:~:text=WATCH_SIM_ID%3D%24%28xctrace%20list%20%20devices%20,destination%20%22platform%3DwatchOS%20simulator%2Cid%3D%24WATCH_SIM_ID)). These cover the up-to-date (macOS Sonoma and later) methods for fully terminal-based SwiftUI watchOS app workflows.

To develop and run SwiftUI-based watchOS apps using only command-line tools (without Xcode's GUI), follow these steps:


1. Prerequisites

  • Install Xcode Command-Line Tools (includes swift, xcodebuild, simctl, etc.):
    xcode-select --install
  • Install Xcode.app (required for SDKs and simulators):
    • Download from the App Store or Apple Developer.
    • Accept the license:
      sudo xcodebuild -license accept

2. Locate SDKs and Simulators

  • iOS/watchOS SDKs are bundled with Xcode:
    • iOS SDK:
      /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk
    • watchOS SDK:
      /Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk
  • Simulator UDIDs (list available devices):
    xcrun simctl list

3. Create a Project Manually

Use Swift Package Manager (SwiftPM) to avoid Xcode's GUI:

mkdir MyWatchApp && cd MyWatchApp
swift package init --type executable
  • Modify Package.swift for watchOS/iOS support:
    // swift-tools-version:5.9
    import PackageDescription
    
    let package = Package(
        name: "MyWatchApp",
        platforms: [
            .watchOS(.v9), // Specify minimum watchOS version
            .iOS(.v16)     // For companion iOS app (if needed)
        ],
        products: [
            .executable(name: "MyWatchApp", targets: ["MyWatchApp"])
        ],
        targets: [
            .executableTarget(
                name: "MyWatchApp",
                dependencies: [],
                path: "Sources",
                resources: [.process("Resources")]
            )
        ]
    )
  • Add SwiftUI code in Sources/MyWatchApp/main.swift (see SwiftUI Minimal Example).

4. Build for Simulator

Specify the target platform and SDK:

# Build for watchOS Simulator
swift build \
  -Xswiftc "-sdk" -Xswiftc "$(xcrun --sdk watchsimulator --show-sdk-path)" \
  -Xswiftc "-target" -Xswiftc "arm64-apple-watchos9.0-simulator"

5. Launch Simulators

  • Start iOS Simulator:
    xcrun simctl boot "iPhone 15 Pro" # Replace with your device name
    open -a Simulator
  • Start watchOS Simulator:
    xcrun simctl boot "Apple Watch Series 9 (45mm)" # Replace with your device name
    open -a Simulator

6. Install and Run the App

  • Build and install on the watchOS simulator:
    xcrun simctl install "Apple Watch Series 9 (45mm)" .build/debug/MyWatchApp
  • Launch the app:
    xcrun simctl launch --console booted com.example.MyWatchApp

7. Debugging with LLDB

Attach the debugger to the running process:

# Find the process ID
xcrun simctl spawn booted log stream --predicate 'process == "MyWatchApp"'

# Attach LLDB
lldb -p $(pgrep MyWatchApp)

Minimal SwiftUI Example

// Sources/MyWatchApp/main.swift
import SwiftUI

@main
struct MyWatchApp: App {
  var body: some Scene {
    WindowGroup {
      Text("Hello, watchOS!")
    }
  }
}

Tools and Workflow Tips

  • Tuist: Generate Xcode projects declaratively without the GUI (tuist.io).
  • fastlane: Automate simulator testing and deployment (fastlane.tools).
  • For signing, use codesign:
    codesign -s "Your Certificate" path/to/app

Limitations

  • Xcode is still required for SDKs/simulators (but you never need to open the GUI).
  • Complex app setups (e.g., entitlements, asset catalogs) may require manual file editing.

By following these steps, you can fully develop and debug watchOS apps using only the terminal!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions