creator_simctl/
list.rs

1//! Supporting types for the `simctl list` subcommand.
2
3use serde::Deserialize;
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::process::Stdio;
7
8use super::{Device, Result, Simctl};
9
10/// Indicates the state of a device.
11#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
12pub enum DeviceState {
13    /// Indicates that the device is booted.
14    Booted,
15
16    /// Indicates that the device is shutdown.
17    Shutdown,
18
19    /// Indicates that the device is in an unknown state.
20    #[serde(other)]
21    Unknown,
22}
23
24/// Indicates the state of a pair of devices.
25#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq)]
26pub enum DevicePairState {
27    /// Indicates that this pair is unavailable because one of its components is
28    /// unavailable.
29    #[serde(rename = "(unavailable)")]
30    Unavailable,
31
32    /// Indicates that this pair is active but not connected.
33    #[serde(rename = "(active, disconnected)")]
34    ActiveDisconnected,
35
36    /// Indicates that this pair is in a state that is not (yet) recognized by
37    /// this library.
38    #[serde(other)]
39    Unknown,
40}
41
42/// Information about a device type.
43#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
44pub struct DeviceType {
45    /// Contains the minimum runtime version that this device type supports.
46    /// This is relevant for devices that are newer than the oldest runtime that
47    /// has been registered with `simctl`.
48    #[serde(rename = "minRuntimeVersion")]
49    pub min_runtime_version: usize,
50
51    /// Contains the maximum runtime version that this device type supports.
52    /// This is relevant for devices that have been deprecated before the newest
53    /// runtime that has been registered with `simctl`.
54    #[serde(rename = "maxRuntimeVersion")]
55    pub max_runtime_version: usize,
56
57    /// Contains a path to the bundle of this device type. This is usually not
58    /// relevant to end-users.
59    #[serde(rename = "bundlePath")]
60    pub bundle_path: PathBuf,
61
62    /// Contains a human-readable name for this device type.
63    pub name: String,
64
65    /// Contains a unique identifier for this device type.
66    pub identifier: String,
67
68    /// Contains a machine-readable name for the product family of this device
69    /// type.
70    #[serde(rename = "productFamily")]
71    pub product_family: String,
72}
73
74/// Information about a runtime.
75#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
76pub struct Runtime {
77    /// Contains a path to the bundle of this runtime. This is usually not
78    /// relevant to end-users.
79    #[serde(rename = "bundlePath")]
80    pub bundle_path: PathBuf,
81
82    /// Contains the build version of this runtime. This is usually not relevant
83    /// to end-users.
84    #[serde(rename = "buildversion")]
85    pub build_version: String,
86
87    /// Contains the root of this runtime. This is usually not relevant to
88    /// end-users.
89    #[serde(rename = "runtimeRoot")]
90    pub runtime_root: PathBuf,
91
92    /// Contains a unique identifier for this runtime.
93    pub identifier: String,
94
95    /// Contains a human-readable version string for this runtime.
96    pub version: String,
97
98    /// Indicates if this runtime is available. This is false when the runtime
99    /// was first created (automatically) with an older version of Xcode that
100    /// shipped with an older version of the iOS simulator and after upgrading
101    /// to a newer version. In that case, Xcode no longer has the runtime bundle
102    /// for this older runtime, but it will still be registered by `simctl`.
103    /// However, it's not possible to boot a device with an unavailable runtime.
104    #[serde(rename = "isAvailable")]
105    pub is_available: bool,
106
107    /// Contains a human-readable name for this runtime.
108    pub name: String,
109}
110
111/// Information about a device.
112#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
113pub struct DeviceInfo {
114    /// Note: this field is not directly present in JSON. Instead, the JSON
115    /// representation is a hashmap of runtime IDs (keys) and devices (values)
116    /// that we later connect during deserialization.
117    #[serde(skip_deserializing)]
118    pub runtime_identifier: String,
119
120    /// If this device is not available (see [`DeviceInfo::is_available`]), this
121    /// will contain a (slightly) more detailed explanation for its
122    /// unavailability.
123    #[serde(default, rename = "availabilityError")]
124    pub availability_error: Option<String>,
125
126    /// Contains the path where application data is stored.
127    #[serde(rename = "dataPath")]
128    pub data_path: PathBuf,
129
130    /// Contains the path where logs are written to.
131    #[serde(rename = "logPath")]
132    pub log_path: PathBuf,
133
134    /// Contains a unique identifier for this device.
135    pub udid: String,
136
137    /// Indicates if this device is available. Also see
138    /// [`Runtime::is_available`].
139    #[serde(rename = "isAvailable")]
140    pub is_available: bool,
141
142    /// This corresponds to [`DeviceType::identifier`]. This is missing for
143    /// devices whose device type has since been removed from Xcode.
144    #[serde(default, rename = "deviceTypeIdentifier")]
145    pub device_type_identifier: String,
146
147    /// Contains the state of this device.
148    pub state: DeviceState,
149
150    /// Contains the name of this device.
151    pub name: String,
152}
153
154/// Short summary of a device that is used as part of a device pair.
155#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
156pub struct DeviceSummary {
157    /// Contains the name of this device.
158    pub name: String,
159
160    /// Contains a unique identifier for this device.
161    pub udid: String,
162
163    /// Contains the state of this device.
164    pub state: DeviceState,
165}
166
167/// Information about a device pair.
168#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
169pub struct DevicePair {
170    /// Note: this field is not directly present in JSON. Instead, the JSON
171    /// representation is a hashmap of runtime IDs (keys) and devices (values)
172    /// that we later connect during deserialization.
173    #[serde(skip_deserializing)]
174    pub udid: String,
175
176    /// Contains a summary of the watch device.
177    pub watch: DeviceSummary,
178
179    /// Contains a summary of the phone device.
180    pub phone: DeviceSummary,
181
182    /// Contains the state of this device pair.
183    pub state: DevicePairState,
184}
185
186/// Wrapper around the `simctl list` subcommand's output.
187#[derive(Debug)]
188pub struct List {
189    simctl: Simctl,
190    device_types: Vec<DeviceType>,
191    runtimes: Vec<Runtime>,
192    devices: Vec<Device>,
193    pairs: Vec<DevicePair>,
194}
195
196impl List {
197    /// Refreshes the `simctl list` subcommand's output.
198    pub fn refresh(&mut self) -> Result<()> {
199        let mut command = self.simctl.command("list");
200        command.arg("-j");
201        command.stdout(Stdio::piped());
202        let output = command.output()?;
203        let output: ListOutput = serde_json::from_slice(&output.stdout)?;
204        self.device_types = output.device_types;
205        self.runtimes = output.runtimes;
206        self.devices = output
207            .devices
208            .into_iter()
209            .map(|(runtime, devices)| {
210                let simctl = self.simctl.clone();
211
212                devices.into_iter().map(move |device| {
213                    Device::new(
214                        simctl.clone(),
215                        DeviceInfo {
216                            runtime_identifier: runtime.clone(),
217                            ..device
218                        },
219                    )
220                })
221            })
222            .flatten()
223            .collect();
224        self.pairs = output
225            .pairs
226            .into_iter()
227            .map(move |(udid, pair)| DevicePair { udid, ..pair })
228            .collect();
229        Ok(())
230    }
231
232    /// Returns all device types that have been registered with `simctl`.
233    pub fn device_types(&self) -> &[DeviceType] {
234        &self.device_types
235    }
236
237    /// Returns all runtimes that have been registered with `simctl`.
238    pub fn runtimes(&self) -> &[Runtime] {
239        &self.runtimes
240    }
241
242    /// Returns all devices that have been registered with `simctl`.
243    pub fn devices(&self) -> &[Device] {
244        &self.devices
245    }
246
247    /// Returns all device pairs that have been registered with `simctl`.
248    pub fn pairs(&self) -> &[DevicePair] {
249        &self.pairs
250    }
251}
252
253#[derive(Debug, Default, Deserialize)]
254struct ListOutput {
255    #[serde(rename = "devicetypes")]
256    device_types: Vec<DeviceType>,
257    runtimes: Vec<Runtime>,
258    devices: HashMap<String, Vec<DeviceInfo>>,
259    pairs: HashMap<String, DevicePair>,
260}
261
262impl Simctl {
263    /// Returns a list of all device types, runtimes, devices and device pairs
264    /// that have been registered with `simctl`.
265    pub fn list(&self) -> Result<List> {
266        let mut list = List {
267            simctl: self.clone(),
268            device_types: vec![],
269            devices: vec![],
270            pairs: vec![],
271            runtimes: vec![],
272        };
273        list.refresh()?;
274        Ok(list)
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn test_list() -> Result<()> {
284        let simctl = Simctl::new();
285        let _ = simctl.list()?;
286        Ok(())
287    }
288}