# How device_utils.py Structures xcrun simctl Commands in the iOS Simulator Skill

> Discover how device_utils.py structures xcrun simctl commands as Python lists. Learn to execute them directly with subprocess.run for robust iOS simulator automation.

- Repository: [Conor/ios-simulator-skill](https://github.com/conorluddy/ios-simulator-skill)
- Tags: internals
- Published: 2026-02-27

---

**The `build_simctl_command` function generates `xcrun simctl` commands as a Python list of strings (e.g., `["xcrun", "simctl", "launch", "booted", "com.app.bundle"]`), ready for immediate execution via `subprocess.run` without shell interpolation.**

The `conorluddy/ios-simulator-skill` repository centralizes all simulator automation logic through a single utility module that defines the specific structure and format of `xcrun simctl` commands. By returning structured data rather than raw shell strings, the codebase eliminates parsing errors and ensures consistent device targeting across all iOS simulator operations.

## Command Structure and Format

According to the source code in [`ios-simulator-skill/scripts/common/device_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/ios-simulator-skill/scripts/common/device_utils.py) (lines 58-66), every generated command follows a strict three-part sequence:

1. **Base prefix** – The immutable foundation `["xcrun", "simctl", <operation>]`.
2. **Device identifier** – Either the supplied `udid` or the literal `"booted"` when `udid` is `None`.
3. **Additional arguments** – Any extra parameters stringified and appended in order.

### Base Prefix and Operation

The command list always begins with the Xcode command-line tool invocation followed by the specific `simctl` subcommand:

```python
cmd = ["xcrun", "simctl", operation]

```

Valid operations include standard subcommands such as `launch`, `install`, `boot`, or `shutdown`.

### Device Targeting Logic

The function implements automatic fallback logic to target the currently running simulator when no explicit UUID is provided:

```python
cmd.append(udid if udid else "booted")

```

When the `udid` parameter is `None`, the string `"booted"` is appended, instructing `simctl` to act upon the active device. Otherwise, the specific device identifier is inserted at this position.

### Argument Appending

Remaining positional arguments are safely converted to strings and extended into the list:

```python
cmd.extend(str(arg) for arg in args)

```

This allows file paths, bundle identifiers, or numeric flags to be passed without manual type conversion or concatenation.

## Implementation in device_utils.py

The complete construction logic resides in [`ios-simulator-skill/scripts/common/device_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/ios-simulator-skill/scripts/common/device_utils.py) and builds the command list as follows:

```python
cmd = ["xcrun", "simctl", operation]          # base

cmd.append(udid if udid else "booted")        # device

cmd.extend(str(arg) for arg in args)          # extra args

```

Because the function returns a plain list, callers can pass it directly to `subprocess.run`:

```python
from ios_simulator_skill.scripts.common.device_utils import build_simctl_command
import subprocess

# Launch an app on the currently booted simulator

cmd = build_simctl_command("launch", None, "com.example.myapp")
subprocess.run(cmd, check=True)

# Install an app on a specific device by UDID

cmd = build_simctl_command("install", "123E4567-F89A-0BCD-EF12-3456789ABC0D", "/tmp/MyApp.app")
subprocess.run(cmd, check=True)

# Shut down a specific device

cmd = build_simctl_command("shutdown", "123E4567-F89A-0BCD-EF12-3456789ABC0D")
subprocess.run(cmd, check=True)

```

## Documented Command Examples

The function’s docstring (lines 45-56) provides concrete illustrations of the final list structure:

| Function Call | Resulting Command List |
|---------------|------------------------|
| `build_simctl_command("launch", None, "com.app.bundle")` | `["xcrun", "simctl", "launch", "booted", "com.app.bundle"]` |
| `build_simctl_command("launch", "ABC123", "com.app.bundle")` | `["xcrun", "simctl", "launch", "ABC123", "com.app.bundle"]` |
| `build_simctl_command("install", "ABC123", "/path/to/app.app")` | `["xcrun", "simctl", "install", "ABC123", "/path/to/app.app"]` |

These examples demonstrate the automatic `"booted"` substitution and the preservation of argument order.

## Integration with Subprocess

Scripts such as [`app_launcher.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_launcher.py), [`simctl_boot.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/simctl_boot.py), and [`simctl_shutdown.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/simctl_shutdown.py) consume this builder to ensure consistent command construction. The list format provides inherent safety by bypassing shell interpretation, preventing injection vulnerabilities that would arise from string concatenation.

## Summary

- **List Format**: The function returns a `list[str]` suitable for `subprocess.run(cmd, ...)` without `shell=True`.
- **Three Components**: Commands are assembled as `[base] + [device] + [arguments]`.
- **Automatic Fallback**: A `None` value for `udid` automatically resolves to the `"booted"` target.
- **Type Safety**: Non-string arguments are coerced via `str(arg)` before appending.
- **Source Location**: All logic is contained in [`ios-simulator-skill/scripts/common/device_utils.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/ios-simulator-skill/scripts/common/device_utils.py).

## Frequently Asked Questions

### What is the exact return type of build_simctl_command?

The function returns a **Python list of strings** (e.g., `["xcrun", "simctl", "launch", "booted", "com.app.bundle"]`). This format is explicitly designed for direct consumption by the `subprocess` module, eliminating the need for shell tokenization or escaping.

### How does the function handle an unspecified device identifier?

When the `udid` parameter is `None`, the function automatically appends the literal string `"booted"` as the device target. This convention targets whichever iOS simulator is currently running, simplifying automation scripts that do not track specific UUIDs.

### Can the function process non-string arguments like integers or Path objects?

Yes. The implementation uses `cmd.extend(str(arg) for arg in args)` at line 66, which converts all additional arguments to strings before appending them to the command list. This allows Path objects, integers, or other types to be passed safely without pre-conversion.

### Which scripts in the repository utilize this command builder?

The `build_simctl_command` utility is imported by multiple execution modules, including [`app_launcher.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/app_launcher.py), [`simctl_boot.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/simctl_boot.py), and [`simctl_shutdown.py`](https://github.com/conorluddy/ios-simulator-skill/blob/main/simctl_shutdown.py). This centralized approach ensures that all `xcrun simctl` invocations across the codebase maintain identical structural conventions and device resolution behavior.