diff --git a/.changes/macos-14-fix.md b/.changes/macos-14-fix.md new file mode 100644 index 00000000..71369b8c --- /dev/null +++ b/.changes/macos-14-fix.md @@ -0,0 +1,5 @@ +--- +"cargo-mobile2": minor +--- + +Use `devicectl` on macOS 14+ to connect to iOS 17+ devices. diff --git a/Cargo.lock b/Cargo.lock index a2347c11..d36cbe0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,7 @@ dependencies = [ "objc_id", "once-cell-regex", "openssl", + "os_info", "os_pipe", "path_abs", "rstest", @@ -987,6 +988,17 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_info" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +dependencies = [ + "log", + "serde", + "winapi", +] + [[package]] name = "os_pipe" version = "1.1.4" diff --git a/Cargo.toml b/Cargo.toml index 5aeb0d3e..e9ff58cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ core-foundation = "0.9" openssl = "0.10" objc = "0.2" objc_id = "0.1" +os_info = "3" [target."cfg(not(target_os = \"macos\"))".dependencies] ureq = { version = "2.8", default-features = false, features = [ "gzip" ] } diff --git a/src/apple/cli.rs b/src/apple/cli.rs index 7fcfc2a5..4d2aeb80 100644 --- a/src/apple/cli.rs +++ b/src/apple/cli.rs @@ -1,8 +1,8 @@ use crate::{ apple::{ config::{Config, Metadata}, - device::{Device, RunError}, - ios_deploy, rust_version_check, + device::{self, Device, RunError}, + rust_version_check, target::{ArchiveError, BuildError, CheckError, CompileLibError, ExportError, Target}, NAME, }, @@ -152,7 +152,7 @@ pub enum Command { pub enum Error { EnvInitFailed(EnvError), RustVersionCheckFailed(util::RustVersionError), - DevicePromptFailed(PromptError), + DevicePromptFailed(PromptError), TargetInvalid(TargetInvalid), ConfigFailed(LoadOrGenError), MetadataFailed(metadata::Error), @@ -164,7 +164,7 @@ pub enum Error { ArchiveFailed(ArchiveError), ExportFailed(ExportError), RunFailed(RunError), - ListFailed(ios_deploy::DeviceListError), + ListFailed(String), NoHomeDir(util::NoHomeDir), CargoEnvFailed(std::io::Error), SdkRootInvalid { sdk_root: PathBuf }, @@ -197,7 +197,7 @@ impl Reportable for Error { Self::ArchiveFailed(err) => err.report(), Self::ExportFailed(err) => err.report(), Self::RunFailed(err) => err.report(), - Self::ListFailed(err) => err.report(), + Self::ListFailed(err) => Report::error("Failed to list devices", err), Self::NoHomeDir(err) => Report::error("Failed to load cargo env profile", err), Self::CargoEnvFailed(err) => Report::error("Failed to load cargo env profile", err), Self::SdkRootInvalid { sdk_root } => Report::error( @@ -232,7 +232,7 @@ impl Exec for Input { } fn exec(self, wrapper: &TextWrapper) -> Result<(), Self::Report> { - define_device_prompt!(ios_deploy::device_list, ios_deploy::DeviceListError, iOS); + define_device_prompt!(crate::apple::device::list_devices, String, iOS); fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> { device_prompt(env).map(|device| device.target()).ok() } @@ -356,17 +356,19 @@ impl Exec for Input { .map_err(Error::DevicePromptFailed)? .run(config, &env, noise_level, non_interactive, profile) .and_then(|h| { - h.wait().map(|_| ()).map_err(|e| { - RunError::DeployFailed(ios_deploy::RunAndDebugError::DeployFailed(e)) - }) + h.wait() + .map(|_| ()) + .map_err(|e| RunError::DeployFailed(e.to_string())) }) .map_err(Error::RunFailed) }), - Command::List => ios_deploy::device_list(&env) - .map_err(Error::ListFailed) - .map(|device_list| { - prompt::list_display_only(device_list.iter(), device_list.len()); - }), + Command::List => { + device::list_devices(&env) + .map_err(Error::ListFailed) + .map(|device_list| { + prompt::list_display_only(device_list.iter(), device_list.len()); + }) + } Command::Pod { mut arguments } => with_config(non_interactive, wrapper, |config, _| { arguments.push(format!( "--project-directory={}", diff --git a/src/apple/deps/mod.rs b/src/apple/deps/mod.rs index 43f4c801..67c47fcc 100644 --- a/src/apple/deps/mod.rs +++ b/src/apple/deps/mod.rs @@ -2,7 +2,10 @@ mod update; pub(crate) mod xcode_plugin; use self::update::{Outdated, OutdatedError}; -use super::system_profile::{self, DeveloperTools}; +use super::{ + device_ctl_available, + system_profile::{self, DeveloperTools}, +}; use crate::util::{ self, cli::{Report, TextWrapper}, @@ -14,7 +17,6 @@ use thiserror::Error; static PACKAGES: &[PackageSpec] = &[ PackageSpec::brew("xcodegen"), - PackageSpec::brew("ios-deploy"), PackageSpec::brew("libimobiledevice").with_bin_name("idevicesyslog"), PackageSpec::brew_or_gem("cocoapods").with_bin_name("pod"), ]; @@ -188,6 +190,9 @@ pub fn install_all( for package in PACKAGES { package.install(reinstall_deps, &mut gem_cache)?; } + if !device_ctl_available() { + PackageSpec::brew("ios-deploy").install(reinstall_deps, &mut gem_cache)?; + } gem_cache.initialize()?; let outdated = Outdated::load(&mut gem_cache)?; outdated.print_notice(); diff --git a/src/apple/device.rs b/src/apple/device.rs deleted file mode 100644 index ad6d5e57..00000000 --- a/src/apple/device.rs +++ /dev/null @@ -1,144 +0,0 @@ -use super::{ - config::Config, - ios_deploy, simctl, - target::{ArchiveError, BuildError, ExportError, Target}, -}; -use crate::{ - env::{Env, ExplicitEnv as _}, - opts, - util::cli::{Report, Reportable}, - DuctExpressionExt, -}; -use std::{ - fmt::{self, Display}, - path::PathBuf, -}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum RunError { - #[error(transparent)] - BuildFailed(BuildError), - #[error(transparent)] - ArchiveFailed(ArchiveError), - #[error(transparent)] - ExportFailed(ExportError), - #[error("IPA appears to be missing. Not found at either {old} or {new}")] - IpaMissing { old: PathBuf, new: PathBuf }, - #[error("Failed to unzip archive: {0}")] - UnzipFailed(std::io::Error), - #[error(transparent)] - DeployFailed(ios_deploy::RunAndDebugError), - #[error(transparent)] - SimulatorDeployFailed(simctl::RunError), -} - -impl Reportable for RunError { - fn report(&self) -> Report { - match self { - Self::BuildFailed(err) => err.report(), - Self::ArchiveFailed(err) => err.report(), - Self::ExportFailed(err) => err.report(), - Self::IpaMissing { old, new } => Report::error( - "IPA appears to be missing", - format!("Not found at either {:?} or {:?}", old, new), - ), - Self::UnzipFailed(err) => Report::error("Failed to unzip archive", err), - Self::DeployFailed(err) => err.report(), - Self::SimulatorDeployFailed(err) => err.report(), - } - } -} - -#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Device<'a> { - id: String, - name: String, - model: String, - target: &'a Target<'a>, - simulator: bool, -} - -impl<'a> Display for Device<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} ({})", self.name, self.model) - } -} - -impl<'a> Device<'a> { - pub(super) fn new(id: String, name: String, model: String, target: &'a Target<'a>) -> Self { - Self { - id, - name, - model, - target, - simulator: false, - } - } - - pub fn simulator(mut self) -> Self { - self.simulator = true; - self - } - - pub fn target(&self) -> &'a Target<'a> { - self.target - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn model(&self) -> &str { - &self.model - } - - pub fn run( - &self, - config: &Config, - env: &Env, - noise_level: opts::NoiseLevel, - non_interactive: bool, - profile: opts::Profile, - ) -> Result { - // TODO: These steps are run unconditionally, which is slooooooow - println!("Building app..."); - self.target - .build(config, env, noise_level, profile) - .map_err(RunError::BuildFailed)?; - println!("Archiving app..."); - self.target - .archive(config, env, noise_level, profile, None) - .map_err(RunError::ArchiveFailed)?; - - if self.simulator { - simctl::run(config, env, non_interactive, &self.id) - .map_err(RunError::SimulatorDeployFailed) - } else { - println!("Exporting app..."); - self.target - .export(config, env, noise_level) - .map_err(RunError::ExportFailed)?; - println!("Extracting IPA..."); - - let ipa_path = config - .ipa_path() - .map_err(|(old, new)| RunError::IpaMissing { old, new })?; - let export_dir = config.export_dir(); - let cmd = duct::cmd::<&str, [String; 0]>("unzip", []) - .vars(env.explicit_env()) - .before_spawn(move |cmd| { - if noise_level.pedantic() { - cmd.arg("-q"); - } - cmd.arg("-o").arg(&ipa_path).arg("-d").arg(&export_dir); - Ok(()) - }); - - cmd.run().map_err(RunError::UnzipFailed)?; - - ios_deploy::run_and_debug(config, env, non_interactive, &self.id) - .map_err(RunError::DeployFailed) - } - } -} diff --git a/src/apple/device/devicectl/device_list.rs b/src/apple/device/devicectl/device_list.rs new file mode 100644 index 00000000..1c42ee92 --- /dev/null +++ b/src/apple/device/devicectl/device_list.rs @@ -0,0 +1,126 @@ +use crate::{ + apple::{ + device::{Device, DeviceKind}, + target::Target, + }, + env::{Env, ExplicitEnv as _}, + util::cli::{Report, Reportable}, + DuctExpressionExt, +}; +use serde::Deserialize; +use std::{collections::BTreeSet, env::temp_dir, fs::read_to_string}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum DeviceListError { + #[error("Failed to request device list from `devicectl`: {0}")] + DetectionFailed(#[from] std::io::Error), + #[error("`simctl list` returned an invalid JSON: {0}")] + InvalidDeviceList(#[from] serde_json::Error), +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceProperties { + name: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CpuType { + name: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct HardwareProperties { + udid: String, + platform: String, + product_type: String, + cpu_type: CpuType, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectionProperties { + pairing_state: String, + tunnel_state: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DeviceListDevice { + connection_properties: ConnectionProperties, + device_properties: DeviceProperties, + hardware_properties: HardwareProperties, +} + +#[derive(Deserialize)] +struct DeviceListResult { + devices: Vec, +} + +#[derive(Deserialize)] +struct DeviceListOutput { + result: DeviceListResult, +} + +impl Reportable for DeviceListError { + fn report(&self) -> Report { + Report::error("Failed to detect connected iOS simulators", self) + } +} + +fn parse_device_list<'a>(json: String) -> Result>, DeviceListError> { + let devices = serde_json::from_str::(&json)? + .result + .devices + .into_iter() + .filter(|device| { + device.connection_properties.tunnel_state != "unavailable" + && (device.hardware_properties.platform.contains("iOS") + || device.hardware_properties.platform.contains("xrOS")) + }) + .map(|device| { + Device::new( + device.hardware_properties.udid, + device.device_properties.name, + device.hardware_properties.product_type, + if device + .hardware_properties + .cpu_type + .name + .starts_with("arm64") + { + Target::for_arch("arm64") + } else { + Target::for_arch("x86_64") + } + .expect("invalid target arch"), + DeviceKind::DeviceCtlDevice, + ) + .paired(device.connection_properties.pairing_state == "paired") + }) + .collect(); + + Ok(devices) +} + +pub fn device_list<'a>(env: &Env) -> Result>, DeviceListError> { + let json_output_path = temp_dir().join("devicelist.json"); + let json_output_path_ = json_output_path.clone(); + std::fs::write(&json_output_path, "")?; + + duct::cmd("xcrun", ["devicectl", "list", "devices", "--json-output"]) + .before_spawn(move |cmd| { + cmd.arg(&json_output_path); + Ok(()) + }) + .stdout_capture() + .vars(env.explicit_env()) + .run() + .map_err(DeviceListError::DetectionFailed)?; + + let contents = read_to_string(&json_output_path_)?; + parse_device_list(contents) +} diff --git a/src/apple/device/devicectl/mod.rs b/src/apple/device/devicectl/mod.rs new file mode 100644 index 00000000..4f474fcd --- /dev/null +++ b/src/apple/device/devicectl/mod.rs @@ -0,0 +1,5 @@ +mod device_list; +mod run; + +pub use device_list::{device_list, DeviceListError}; +pub use run::{run, RunError}; diff --git a/src/apple/device/devicectl/run.rs b/src/apple/device/devicectl/run.rs new file mode 100644 index 00000000..6e5f42e6 --- /dev/null +++ b/src/apple/device/devicectl/run.rs @@ -0,0 +1,134 @@ +use std::{env::temp_dir, fs::read_to_string}; + +use crate::{ + apple::config::Config, + env::{Env, ExplicitEnv as _}, + util::cli::{Report, Reportable}, + DuctExpressionExt, +}; +use serde::Deserialize; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum RunError { + #[error("Failed to deploy app to simulator: {0}")] + DeployFailed(std::io::Error), + #[error("`devicectl` returned an invalid JSON: {0}")] + InvalidDevicectlJson(#[from] serde_json::Error), + #[error("`devicectl` did not return the installed application metadata")] + MissingInstalledApplication, +} + +impl Reportable for RunError { + fn report(&self) -> Report { + match self { + Self::DeployFailed(err) => Report::error("Failed to deploy app to simulator", err), + Self::InvalidDevicectlJson(err) => { + Report::error("Failed to read `devicectl` output", err) + } + Self::MissingInstalledApplication => Report::error( + "Failed to deploy application", + "`devicectl` did not return the installed application metadata", + ), + } + } +} + +#[derive(Deserialize)] +struct InstalledApplication { + #[serde(rename = "bundleID")] + bundle_id: String, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct InstallResult { + installed_applications: Vec, +} + +#[derive(Deserialize)] +struct InstallOutput { + result: InstallResult, +} + +pub fn run( + config: &Config, + env: &Env, + non_interactive: bool, + id: &str, + paired: bool, +) -> Result { + if !paired { + println!("Pairing with device..."); + + duct::cmd("xcrun", ["devicectl", "manage", "pair", "--device", id]) + .vars(env.explicit_env()) + .stdout_capture() + .stderr_capture() + .run() + .map_err(RunError::DeployFailed)?; + } + + println!("Deploying app to device..."); + + let app_dir = config + .export_dir() + .join(format!("{}_iOS.xcarchive", config.app().name())) + .join("Products/Applications") + .join(format!("{}.app", config.app().stylized_name())); + let json_output_path = temp_dir().join("deviceinstall.json"); + let json_output_path_ = json_output_path.clone(); + std::fs::write(&json_output_path, "").map_err(RunError::DeployFailed)?; + let cmd = duct::cmd( + "xcrun", + ["devicectl", "device", "install", "app", "--device", id], + ) + .vars(env.explicit_env()) + .before_spawn(move |cmd| { + cmd.arg(&app_dir) + .arg("--json-output") + .arg(&json_output_path_); + Ok(()) + }); + + cmd.run().map_err(RunError::DeployFailed)?; + + let install_output_json = read_to_string(&json_output_path).map_err(RunError::DeployFailed)?; + let install_output = serde_json::from_str::(&install_output_json)?; + let installed_application = install_output + .result + .installed_applications + .into_iter() + .next() + .ok_or(RunError::MissingInstalledApplication)?; + let app_id = installed_application.bundle_id; + + let launcher_cmd = duct::cmd( + "xcrun", + [ + "devicectl", + "device", + "process", + "launch", + "--device", + id, + &app_id, + ], + ) + .vars(env.explicit_env()); + + if non_interactive { + launcher_cmd.start().map_err(RunError::DeployFailed) + } else { + launcher_cmd + .start() + .map_err(RunError::DeployFailed)? + .wait() + .map_err(RunError::DeployFailed)?; + + duct::cmd("idevicesyslog", ["--process", config.app().stylized_name()]) + .vars(env.explicit_env()) + .start() + .map_err(RunError::DeployFailed) + } +} diff --git a/src/apple/ios_deploy/device_list.rs b/src/apple/device/ios_deploy/device_list.rs similarity index 82% rename from src/apple/ios_deploy/device_list.rs rename to src/apple/device/ios_deploy/device_list.rs index 4d4f667f..53e38f72 100644 --- a/src/apple/ios_deploy/device_list.rs +++ b/src/apple/device/ios_deploy/device_list.rs @@ -1,6 +1,9 @@ use super::{DeviceInfo, Event}; use crate::{ - apple::{device::Device, target::Target}, + apple::{ + device::{Device, DeviceKind}, + target::Target, + }, env::{Env, ExplicitEnv as _}, util::cli::{Report, Reportable}, DuctExpressionExt, @@ -37,7 +40,15 @@ fn parse_device_list<'a>( model_name, }| { Target::for_arch(&model_arch) - .map(|target| Device::new(device_identifier, device_name, model_name, target)) + .map(|target| { + Device::new( + device_identifier, + device_name, + model_name, + target, + DeviceKind::IosDeployDevice, + ) + }) .ok_or_else(|| DeviceListError::ArchInvalid(model_arch)) }, ) diff --git a/src/apple/ios_deploy/mod.rs b/src/apple/device/ios_deploy/mod.rs similarity index 100% rename from src/apple/ios_deploy/mod.rs rename to src/apple/device/ios_deploy/mod.rs diff --git a/src/apple/ios_deploy/run.rs b/src/apple/device/ios_deploy/run.rs similarity index 100% rename from src/apple/ios_deploy/run.rs rename to src/apple/device/ios_deploy/run.rs diff --git a/src/apple/device/mod.rs b/src/apple/device/mod.rs new file mode 100644 index 00000000..75302ab1 --- /dev/null +++ b/src/apple/device/mod.rs @@ -0,0 +1,202 @@ +use super::{ + config::Config, + deps::{GemCache, PackageSpec}, + target::{ArchiveError, BuildError, ExportError, Target}, +}; +use crate::{ + env::{Env, ExplicitEnv as _}, + opts, + util::cli::{Report, Reportable}, + DuctExpressionExt, +}; +use std::{ + collections::BTreeSet, + fmt::{self, Display}, + path::PathBuf, +}; +use thiserror::Error; + +mod devicectl; +mod ios_deploy; +mod simctl; + +pub use simctl::Device as Simulator; + +#[derive(Debug, Error)] +pub enum RunError { + #[error(transparent)] + BuildFailed(BuildError), + #[error(transparent)] + ArchiveFailed(ArchiveError), + #[error(transparent)] + ExportFailed(ExportError), + #[error("IPA appears to be missing. Not found at either {old} or {new}")] + IpaMissing { old: PathBuf, new: PathBuf }, + #[error("Failed to unzip archive: {0}")] + UnzipFailed(std::io::Error), + #[error("{0}")] + DeployFailed(String), +} + +impl Reportable for RunError { + fn report(&self) -> Report { + match self { + Self::BuildFailed(err) => err.report(), + Self::ArchiveFailed(err) => err.report(), + Self::ExportFailed(err) => err.report(), + Self::IpaMissing { old, new } => Report::error( + "IPA appears to be missing", + format!("Not found at either {:?} or {:?}", old, new), + ), + Self::UnzipFailed(err) => Report::error("Failed to unzip archive", err), + Self::DeployFailed(err) => Report::error("Failed to deploy app", err), + } + } +} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub enum DeviceKind { + Simulator, + IosDeployDevice, + DeviceCtlDevice, +} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Device<'a> { + id: String, + name: String, + model: String, + target: &'a Target<'a>, + kind: DeviceKind, + paired: bool, +} + +impl<'a> Display for Device<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ({})", self.name, self.model) + } +} + +impl<'a> Device<'a> { + pub(super) fn new( + id: String, + name: String, + model: String, + target: &'a Target<'a>, + kind: DeviceKind, + ) -> Self { + Self { + id, + name, + model, + target, + kind, + paired: true, + } + } + + pub fn paired(mut self, paired: bool) -> Self { + self.paired = paired; + self + } + + pub fn target(&self) -> &'a Target<'a> { + self.target + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn model(&self) -> &str { + &self.model + } + + pub fn run( + &self, + config: &Config, + env: &Env, + noise_level: opts::NoiseLevel, + non_interactive: bool, + profile: opts::Profile, + ) -> Result { + // TODO: These steps are run unconditionally, which is slooooooow + println!("Building app..."); + self.target + .build(config, env, noise_level, profile) + .map_err(RunError::BuildFailed)?; + println!("Archiving app..."); + self.target + .archive(config, env, noise_level, profile, None) + .map_err(RunError::ArchiveFailed)?; + + match self.kind { + DeviceKind::Simulator => simctl::run(config, env, non_interactive, &self.id) + .map_err(|e| RunError::DeployFailed(e.to_string())), + DeviceKind::IosDeployDevice | DeviceKind::DeviceCtlDevice => { + println!("Exporting app..."); + self.target + .export(config, env, noise_level) + .map_err(RunError::ExportFailed)?; + println!("Extracting IPA..."); + + let ipa_path = config + .ipa_path() + .map_err(|(old, new)| RunError::IpaMissing { old, new })?; + let export_dir = config.export_dir(); + let cmd = duct::cmd::<&str, [String; 0]>("unzip", []) + .vars(env.explicit_env()) + .before_spawn(move |cmd| { + if noise_level.pedantic() { + cmd.arg("-q"); + } + cmd.arg("-o").arg(&ipa_path).arg("-d").arg(&export_dir); + Ok(()) + }); + + cmd.run().map_err(RunError::UnzipFailed)?; + + if self.kind == DeviceKind::IosDeployDevice { + ios_deploy::run_and_debug(config, env, non_interactive, &self.id) + .map_err(|e| RunError::DeployFailed(e.to_string())) + } else { + devicectl::run(config, env, non_interactive, &self.id, self.paired) + .map_err(|e| RunError::DeployFailed(e.to_string())) + } + } + } + } +} + +pub fn list_devices<'a>(env: &Env) -> Result>, String> { + let mut devices = BTreeSet::default(); + let mut error = None; + + // devicectl + match devicectl::device_list(env) { + Ok(d) => { + devices.extend(d); + } + Err(e) => { + error = Some(e); + } + } + + // if we could not find a device with devicectl, let's use ios-deploy + if devices.is_empty() { + PackageSpec::brew("ios-deploy") + .install(false, &mut GemCache::new()) + .map_err(|e| e.to_string())?; + return ios_deploy::device_list(env).map_err(|e| e.to_string()); + } + + if let Some(err) = error { + Err(err.to_string()) + } else { + Ok(devices) + } +} + +pub fn list_simulators<'a>(env: &Env) -> Result, String> { + simctl::device_list(env).map_err(|e| e.to_string()) +} diff --git a/src/apple/simctl/device_list.rs b/src/apple/device/simctl/device_list.rs similarity index 100% rename from src/apple/simctl/device_list.rs rename to src/apple/device/simctl/device_list.rs diff --git a/src/apple/simctl/mod.rs b/src/apple/device/simctl/mod.rs similarity index 94% rename from src/apple/simctl/mod.rs rename to src/apple/device/simctl/mod.rs index 8acffdfe..92cbe642 100644 --- a/src/apple/simctl/mod.rs +++ b/src/apple/device/simctl/mod.rs @@ -1,4 +1,5 @@ -use super::target::Target; +use super::super::target::Target; +use super::DeviceKind; use crate::apple::device::Device as AppleDevice; use crate::env::{Env, ExplicitEnv}; use crate::DuctExpressionExt; @@ -36,8 +37,8 @@ impl<'a> From for AppleDevice<'a> { "x86_64" }) .unwrap(), + DeviceKind::Simulator, ) - .simulator() } } diff --git a/src/apple/simctl/run.rs b/src/apple/device/simctl/run.rs similarity index 100% rename from src/apple/simctl/run.rs rename to src/apple/device/simctl/run.rs diff --git a/src/apple/mod.rs b/src/apple/mod.rs index 4aa6638d..be854d6a 100644 --- a/src/apple/mod.rs +++ b/src/apple/mod.rs @@ -3,9 +3,7 @@ pub mod cli; pub mod config; pub mod deps; pub mod device; -pub mod ios_deploy; pub mod project; -pub mod simctl; pub(crate) mod system_profile; pub mod target; pub mod teams; @@ -26,3 +24,10 @@ pub fn rust_version_check(wrapper: &TextWrapper) -> Result<(), util::RustVersion ).print(wrapper); }) } + +pub fn device_ctl_available() -> bool { + matches!( + os_info::get().version(), + os_info::Version::Semantic(major, _, _) + if *major >= 14) +} diff --git a/src/device.rs b/src/device.rs index 143d0853..3ddaa0dd 100644 --- a/src/device.rs +++ b/src/device.rs @@ -6,7 +6,7 @@ use std::{ }; #[derive(Debug, thiserror::Error)] -pub enum PromptErrorCause { +pub enum PromptErrorCause { #[error(transparent)] DetectionFailed(T), #[error(transparent)] @@ -16,12 +16,12 @@ pub enum PromptErrorCause { } #[derive(Debug)] -pub struct PromptError { +pub struct PromptError { name: &'static str, cause: PromptErrorCause, } -impl Display for PromptError { +impl Display for PromptError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.cause { PromptErrorCause::DetectionFailed(err) => write!(f, "{}", err), @@ -37,12 +37,14 @@ impl Display for PromptError { } } -impl Error for PromptError {} +impl Error for PromptError {} -impl Reportable for PromptError { +impl Reportable for PromptError { fn report(&self) -> Report { match &self.cause { - PromptErrorCause::DetectionFailed(err) => err.report(), + PromptErrorCause::DetectionFailed(err) => { + Report::error("failed to detect devices", err) + } PromptErrorCause::PromptFailed(err) => { Report::error(format!("Failed to prompt for {} device", self.name), err) } @@ -54,7 +56,7 @@ impl Reportable for PromptError { } } -impl PromptError { +impl PromptError { pub fn new(name: &'static str, cause: PromptErrorCause) -> Self { Self { name, cause } } diff --git a/src/doctor/section/device_list.rs b/src/doctor/section/device_list.rs index 3436736f..cc228667 100644 --- a/src/doctor/section/device_list.rs +++ b/src/doctor/section/device_list.rs @@ -9,8 +9,7 @@ pub fn check(env: &Env) -> Section { #[cfg(target_os = "macos")] let section = { - use crate::apple::ios_deploy; - match ios_deploy::device_list(env) { + match crate::apple::device::list_devices(env) { Ok(list) => section.with_victories(list), Err(err) => section.with_failure(format!("Failed to get iOS device list: {}", err)), } diff --git a/src/util/path.rs b/src/util/path.rs index a52a19b2..e7020577 100644 --- a/src/util/path.rs +++ b/src/util/path.rs @@ -67,10 +67,6 @@ pub fn tools_dir() -> Result { install_dir().map(|install_dir| install_dir.join("tools")) } -pub fn temp_dir() -> PathBuf { - std::env::temp_dir().join("com.brainiumstudios.cargo-mobile2") -} - #[derive(Debug)] pub struct PathNotPrefixed { path: PathBuf,