- Device definitions in your devbox.d directory (e.g.,
devbox.d/ios/devices/*.json) devices/devices.lockin your devbox.d directory — generated lock file (tracks evaluated devices).devbox/virtenv/ios/scripts/— runtime scripts (organized by layer)lib/— utility functionsplatform/— Xcode discovery and device configdomain/— device management, simulator, deployment operationsuser/— user-facing CLI scripts (ios.sh, devices.sh, config.sh)init/— initialization hooks
Each device file is JSON with:
name(string, required) — simulator display nameruntime(string, required) — iOS version (e.g., "17.5", "18.0", "15.4")
Example:
{
"name": "iPhone 15 Pro",
"runtime": "17.5"
}Configure the plugin by setting environment variables in devbox.json or plugin.json:
IOS_CONFIG_DIR— Configuration directory (default:devbox.d/ios)IOS_DEVICES_DIR— Device definitions directory (default:devbox.d/ios/devices)IOS_SCRIPTS_DIR— Scripts directory (default:.devbox/virtenv/ios/scripts)IOS_DEVICES— Comma-separated device names to evaluate (empty = all devices)IOS_DEFAULT_DEVICE— Default device name when none specified (default: "max")IOS_DEFAULT_RUNTIME— Default iOS runtime version (empty = latest available)
IOS_DEVELOPER_DIR— Xcode developer directory path (empty = auto-detect)IOS_XCODE_ENV_PATH— Additional PATH entries for Xcode toolsIOS_DOWNLOAD_RUNTIME— Auto-download missing runtimes (1=yes, 0=no; default: 1)
IOS_APP_ARTIFACT— Path or glob pattern for .app bundle (relative to project root; empty = auto-detect)IOS_APP_SCHEME— Xcode scheme override (empty = auto-detect from project filename)IOS_APP_PROJECT— Explicit .xcworkspace or .xcodeproj path (empty = auto-detect)IOS_BUILD_CONFIG— Build configuration: Debug or Release (default: "Debug")IOS_DERIVED_DATA_PATH— DerivedData directory path (default:.devbox/virtenv/ios/DerivedData)
IOS_SKIP_SETUP— Skip iOS environment setup during shell initialization (1=skip, 0=setup; default: 0)- Useful for Android-only contexts in React Native projects to speed up initialization
- When set to 1, skips Xcode path detection, device lock generation, and environment configuration
Start simulator:
ios.sh simulator start [--pure] [device]- If
deviceis specified, uses that device name - Otherwise uses
IOS_DEFAULT_DEVICE - Boots simulator if not already running
--pure: Creates a fresh, isolated test simulator with clean state (for deterministic tests)- Auto-detects pure mode when
DEVBOX_PURE_SHELL=1(set bydevbox run --pure) REUSE_SIM=1: Override pure mode to reuse existing simulator (e.g.,devbox run --pure -e REUSE_SIM=1)- Saves simulator UDID to
$IOS_RUNTIME_DIR/${SUITE_NAME:-default}/simulator-udid.txt
Convenience aliases:
devbox run --pure start:sim [device](equivalent toios.sh simulator startwithout--pure)devbox run --pure stop:sim(equivalent toios.sh simulator stop)
Stop simulator:
ios.sh simulator stop- In pure mode (test simulator exists): shuts down and deletes the test simulator, cleans up state files
- In normal mode: shuts down the simulator via
ios_stop()
Check simulator readiness:
ios.sh simulator ready- Silent readiness probe: exit 0 if simulator is booted, exit 1 if not
- Reads UDID from suite-namespaced state file, falls back to finding any booted simulator
Reset simulators:
ios.sh simulator reset- Stops all running simulators
- Deletes simulators matching device definitions
ios.sh deploy [app_path]- Installs and launches an app on an already-running simulator (no build, no simulator start)
- If
app_pathis provided, installs the specified .app bundle - If no arguments, auto-detects .app using
ios_find_app()(same resolution asrun) - Saves bundle ID to
$IOS_RUNTIME_DIR/${SUITE_NAME:-default}/bundle-id.txt
ios.sh app status- Checks if the deployed app is running on the simulator
- Exit 0 if running, exit 1 if not
ios.sh app stop- Terminates the deployed app via
xcrun simctl terminate
ios.sh run [app_path] [device]- Starts simulator, builds, installs, and launches the app
- If
app_pathis provided, skips build and installs the provided .app bundle directly - If no arguments, builds project (via
build:iosorbuildscripts) and auto-detects the .app bundle - Auto-detection precedence:
IOS_APP_ARTIFACTenv var → xcodebuild settings → recursive search - Bundle ID is auto-extracted from
Info.plist
List devices:
devbox run --pure ios.sh devices listShows all device definitions in your devbox.d directory
Show specific device:
devbox run --pure ios.sh devices show <name>Displays device JSON configuration
Create device:
devbox run --pure ios.sh devices create <name> --runtime <version>name: Device name (used as filename and display name)runtime: iOS version (e.g., "17.5", "18.0")- Example:
ios.sh devices create iphone15 --runtime 17.5
Update device:
devbox run --pure ios.sh devices update <name> [--name <new>] [--runtime <version>]--name: Rename device--runtime: Change iOS version
Delete device:
devbox run --pure ios.sh devices delete <name>Removes device definition file
Generate lock file:
devbox run --pure ios.sh devices eval- Generates
devices.lockfrom device definitions - Respects
IOS_DEVICESfilter (empty = all devices) - Includes checksum for validation
Sync simulators:
devbox run --pure ios.sh devices sync- Reads
devices.lock - Creates/updates simulators to match definitions
- Reports: matched, recreated, created, skipped
Show configuration:
devbox run --pure ios.sh config showDisplays current environment variable configuration
Show SDK info:
devbox run --pure ios.sh infoShows:
- Xcode developer directory
- iOS SDK version
- Available runtimes
- Device configuration
Run diagnostics:
devbox run --pure doctorChecks:
- Xcode installation
- Command-line tools
- xcrun and simctl availability
- Device definitions
- Lock file status
Verify setup:
devbox run --pure verify:setupQuick check that iOS environment is functional (exits 1 on failure)
Control which devices are evaluated using the IOS_DEVICES environment variable in devbox.json:
Evaluate all devices (default):
{
"env": {
"IOS_DEVICES": ""
}
}Evaluate specific devices:
{
"env": {
"IOS_DEVICES": "min,max"
}
}After changing IOS_DEVICES, regenerate the lock file:
devbox run --pure ios.sh devices evalAvailable runtimes are managed by Xcode. To list available runtimes:
xcrun simctl list runtimesRuntimes can be downloaded automatically if IOS_DOWNLOAD_RUNTIME=1:
# Manual download
xcodebuild -downloadPlatform iOS
# Or set in devbox.json for automatic downloads
{
"env": {
"IOS_DOWNLOAD_RUNTIME": "1"
}
}The plugin auto-detects Xcode using this strategy:
- Check
IOS_DEVELOPER_DIRenvironment variable - Find latest Xcode in
/Applications/Xcode*.appby version number - Use
xcode-select -poutput - Fallback to
/Applications/Xcode.app/Contents/Developer
Override discovery by setting IOS_DEVELOPER_DIR:
{
"env": {
"IOS_DEVELOPER_DIR": "/Applications/Xcode-15.4.app/Contents/Developer"
}
}The devices.lock file tracks which devices should be created:
{
"devices": [
{
"name": "iPhone 15 Pro",
"runtime": "17.5"
},
{
"name": "iPad Pro",
"runtime": "17.5"
}
],
"checksum": "abc123..."
}devices: Array of device definitions to createchecksum: SHA-256 hash of all device definition files (for validation)
Scripts are organized in 5 layers (see wiki/project/ARCHITECTURE.md for details):
Layer 1 - lib/: Pure utility functions
lib/lib.sh— path resolution, checksums, validation utilities
Layer 2 - platform/: Platform setup
platform/core.sh— Xcode discovery, environment setup, debug loggingplatform/device_config.sh— Device file management
Layer 3 - domain/: Domain operations
domain/device_manager.sh— Runtime resolution, simulator operationsdomain/simulator.sh— Simulator lifecycle managementdomain/deploy.sh— App building and deploymentdomain/validate.sh— Non-blocking validation
Layer 4 - user/: User-facing CLI
user/ios.sh— Main CLI routeruser/devices.sh— Device management CLIuser/config.sh— Configuration management
Layer 5 - init/: Initialization
init/init-hook.sh— Pre-shell initialization (lock file generation)init/setup.sh— Shell environment setup
These are set automatically by the plugin:
DEVELOPER_DIR— Xcode developer directory (used by xcrun, xcodebuild)CC— C compiler path (/usr/bin/clang)CXX— C++ compiler path (/usr/bin/clang++)PATH— Updated with Xcode tools and plugin scriptsIOS_NODE_BINARY— Node.js binary path (if available, for React Native)
IOS_RUNTIME_DIR— Directory for runtime state files (default:.devbox/virtenv/ios/runtime)SUITE_NAME— Test suite name for state isolation (default: "default")- Each suite gets its own subdirectory under
$IOS_RUNTIME_DIR/$SUITE_NAME/ - State files:
simulator-udid.txt,test-simulator-udid.txt(pure mode only),bundle-id.txt - Set in process-compose environment blocks for parallel test execution
- Each suite gets its own subdirectory under
Set during simulator/app operations:
IOS_SIM_UDID— UUID of running simulatorIOS_SIM_NAME— Name of running simulator
Symptom: "Xcode developer directory not found"
Solution:
# Install Xcode from App Store, then:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
# Or install command-line tools only:
xcode-select --installSymptom: "Runtime iOS X.X not found"
Solution:
# List available runtimes
xcrun simctl list runtimes
# Download runtime
xcodebuild -downloadPlatform iOS
# Or enable auto-download in devbox.json
{
"env": {
"IOS_DOWNLOAD_RUNTIME": "1"
}
}Symptom: "CoreSimulatorService connection became invalid"
Solution:
# Restart CoreSimulatorService
killall -9 com.apple.CoreSimulatorService 2>/dev/null
launchctl kickstart -k gui/$UID/com.apple.CoreSimulatorService
# Open Simulator to initialize
open -a SimulatorSymptom: "Warning: devices.lock may be stale"
Solution:
devbox run --pure ios.sh devices evalSymptom: Xcode build errors
Checklist:
- Check that your
.xcodeprojor.xcworkspaceexists in the project root - Verify
build:iosorbuildscript in devbox.json is correct - Ensure derived data directory is writable
- Clean build:
rm -rf DerivedDataor the path your build script uses
- macOS only — iOS development requires macOS and Xcode
- Xcode — Install from App Store or use Xcode Command Line Tools
- iOS Simulator — Included with Xcode
- Devbox — Required for plugin system
- Use semantic names:
min.json,max.jsonfor version boundaries - Use descriptive names:
iphone15_pro.json,ipad_air.json - Commit device definitions and lock files to version control
- Regenerate lock file after device changes
- Test on minimum and maximum supported iOS versions
- Use
IOS_DEVICES="min,max"for CI to limit evaluated runtimes - Keep runtime versions up to date
- Pin Xcode version in CI using
IOS_DEVELOPER_DIR - Use latest stable Xcode for development
- Keep command-line tools updated
- Use project-relative paths for
IOS_APP_ARTIFACTwhen auto-detect doesn't work - Commit derived data directories to
.gitignore - Auto-detect works best when a single
.xcodeprojor.xcworkspaceexists in project root
cd my-ios-project
# Add iOS plugin to devbox.json
# {
# "include": ["github:segment-integrations/devbox-plugins?dir=plugins/ios"],
# "env": {
# "IOS_DEVICES": "min,max",
# "IOS_DEFAULT_DEVICE": "max"
# }
# }
# Enter devbox shell
devbox shell
# Verify setup
devbox run verify:setup
# List device definitions
ios.sh devices list
# Generate lock file
ios.sh devices eval
# Start simulator (plugin-provided)
devbox run start:sim
# Build and run app (user-defined script in devbox.json)
# devbox run start:app# Create device definition
ios.sh devices create iphone14 --runtime 16.4
# Regenerate lock file
ios.sh devices eval
# Sync simulators
ios.sh devices sync
# Verify
ios.sh devices list# .github/workflows/ios.yml
- name: Setup iOS environment
run: |
devbox install
devbox run verify:setup
- name: Run tests
run: |
devbox run test:ios- Architecture — Script architecture and layer dependencies
- Plugin Conventions — Plugin development patterns
examples/ios/— Example iOS projectexamples/react-native/— React Native with iOS