123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use cargo_mobile::{
- apple::{
- config::{
- Config as AppleConfig, Metadata as AppleMetadata, Platform as ApplePlatform,
- Raw as RawAppleConfig,
- },
- device::Device,
- ios_deploy, simctl,
- target::Target,
- teams::find_development_teams,
- },
- config::app::App,
- env::Env,
- opts::NoiseLevel,
- os,
- util::prompt,
- };
- use clap::{Parser, Subcommand};
- use sublime_fuzzy::best_match;
- use super::{
- ensure_init, env, get_app,
- init::{command as init_command, init_dot_cargo},
- log_finished, read_options, CliOptions, Target as MobileTarget, MIN_DEVICE_MATCH_SCORE,
- };
- use crate::{
- helpers::config::{get as get_tauri_config, Config as TauriConfig},
- Result,
- };
- use std::{
- process::exit,
- thread::{sleep, spawn},
- time::Duration,
- };
- mod build;
- mod dev;
- mod open;
- pub(crate) mod project;
- mod xcode_script;
- pub const APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME: &str = "TAURI_APPLE_DEVELOPMENT_TEAM";
- #[derive(Parser)]
- #[clap(
- author,
- version,
- about = "iOS commands",
- subcommand_required(true),
- arg_required_else_help(true)
- )]
- pub struct Cli {
- #[clap(subcommand)]
- command: Commands,
- }
- #[derive(Debug, Parser)]
- #[clap(about = "Initializes a Tauri iOS project")]
- pub struct InitOptions {
- /// Skip prompting for values
- #[clap(long)]
- ci: bool,
- /// Reinstall dependencies
- #[clap(short, long)]
- reinstall_deps: bool,
- }
- #[derive(Subcommand)]
- enum Commands {
- Init(InitOptions),
- Open,
- Dev(dev::Options),
- Build(build::Options),
- #[clap(hide(true))]
- XcodeScript(xcode_script::Options),
- }
- pub fn command(cli: Cli, verbosity: u8) -> Result<()> {
- let noise_level = NoiseLevel::from_occurrences(verbosity as u64);
- match cli.command {
- Commands::Init(options) => init_command(MobileTarget::Ios, options.ci, options.reinstall_deps)?,
- Commands::Open => open::command()?,
- Commands::Dev(options) => dev::command(options, noise_level)?,
- Commands::Build(options) => build::command(options, noise_level)?,
- Commands::XcodeScript(options) => xcode_script::command(options)?,
- }
- Ok(())
- }
- pub fn get_config(
- app: Option<App>,
- config: &TauriConfig,
- cli_options: &CliOptions,
- ) -> (App, AppleConfig, AppleMetadata) {
- let app = app.unwrap_or_else(|| get_app(config));
- let ios_options = cli_options.clone();
- let raw = RawAppleConfig {
- development_team: std::env::var(APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME)
- .ok()
- .or_else(|| config.tauri.bundle.ios.development_team.clone())
- .unwrap_or_else(|| {
- let teams = find_development_teams().unwrap_or_default();
- match teams.len() {
- 0 => {
- log::error!("No code signing certificates found. You must add one and set the certificate development team ID on the `tauri > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. To list the available certificates, run `tauri info`.");
- exit(1);
- }
- 1 => teams.first().unwrap().id.clone(),
- _ => {
- log::error!("You must set the code signing certificate development team ID on the `tauri > iOS > developmentTeam` config value or the `{APPLE_DEVELOPMENT_TEAM_ENV_VAR_NAME}` environment variable. Available certificates: {}", teams.iter().map(|t| format!("{} (ID: {})", t.name, t.id)).collect::<Vec<String>>().join(", "));
- exit(1);
- }
- }
- }),
- ios_features: ios_options.features.clone(),
- bundle_version: config.package.version.clone(),
- bundle_version_short: config.package.version.clone(),
- ..Default::default()
- };
- let config = AppleConfig::from_raw(app.clone(), Some(raw)).unwrap();
- let metadata = AppleMetadata {
- supported: true,
- ios: ApplePlatform {
- cargo_args: Some(ios_options.args),
- features: ios_options.features,
- ..Default::default()
- },
- macos: Default::default(),
- };
- (app, config, metadata)
- }
- fn with_config<T>(
- cli_options: Option<CliOptions>,
- f: impl FnOnce(&App, &AppleConfig, &AppleMetadata, CliOptions) -> Result<T>,
- ) -> Result<T> {
- let (app, config, metadata, cli_options) = {
- let tauri_config = get_tauri_config(None)?;
- let tauri_config_guard = tauri_config.lock().unwrap();
- let tauri_config_ = tauri_config_guard.as_ref().unwrap();
- let cli_options = cli_options.unwrap_or_else(read_options);
- let (app, config, metadata) = get_config(None, tauri_config_, &cli_options);
- (app, config, metadata, cli_options)
- };
- f(&app, &config, &metadata, cli_options)
- }
- fn ios_deploy_device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
- let device_list = ios_deploy::device_list(env)
- .map_err(|cause| anyhow::anyhow!("Failed to detect connected iOS devices: {cause}"))?;
- if !device_list.is_empty() {
- let device = if let Some(t) = target {
- let (device, score) = device_list
- .into_iter()
- .rev()
- .map(|d| {
- let score = best_match(t, d.name()).map_or(0, |m| m.score());
- (d, score)
- })
- .max_by_key(|(_, score)| *score)
- // we already checked the list is not empty
- .unwrap();
- if score > MIN_DEVICE_MATCH_SCORE {
- device
- } else {
- anyhow::bail!("Could not find an iOS device matching {t}")
- }
- } else {
- let index = if device_list.len() > 1 {
- prompt::list(
- concat!("Detected ", "iOS", " devices"),
- device_list.iter(),
- "device",
- None,
- "Device",
- )
- .map_err(|cause| anyhow::anyhow!("Failed to prompt for iOS device: {cause}"))?
- } else {
- 0
- };
- device_list.into_iter().nth(index).unwrap()
- };
- println!(
- "Detected connected device: {} with target {:?}",
- device,
- device.target().triple,
- );
- Ok(device)
- } else {
- Err(anyhow::anyhow!("No connected iOS devices detected"))
- }
- }
- fn simulator_prompt(env: &'_ Env, target: Option<&str>) -> Result<simctl::Device> {
- let simulator_list = simctl::device_list(env).map_err(|cause| {
- anyhow::anyhow!("Failed to detect connected iOS Simulator devices: {cause}")
- })?;
- if !simulator_list.is_empty() {
- let device = if let Some(t) = target {
- let (device, score) = simulator_list
- .into_iter()
- .rev()
- .map(|d| {
- let score = best_match(t, d.name()).map_or(0, |m| m.score());
- (d, score)
- })
- .max_by_key(|(_, score)| *score)
- // we already checked the list is not empty
- .unwrap();
- if score > MIN_DEVICE_MATCH_SCORE {
- device
- } else {
- anyhow::bail!("Could not find an iOS Simulator matching {t}")
- }
- } else if simulator_list.len() > 1 {
- let index = prompt::list(
- concat!("Detected ", "iOS", " simulators"),
- simulator_list.iter(),
- "simulator",
- None,
- "Simulator",
- )
- .map_err(|cause| anyhow::anyhow!("Failed to prompt for iOS Simulator device: {cause}"))?;
- simulator_list.into_iter().nth(index).unwrap()
- } else {
- simulator_list.into_iter().next().unwrap()
- };
- log::info!("Starting simulator {}", device.name());
- let handle = device.start(env)?;
- spawn(move || {
- let _ = handle.wait();
- });
- Ok(device)
- } else {
- Err(anyhow::anyhow!("No available iOS Simulator detected"))
- }
- }
- fn device_prompt<'a>(env: &'_ Env, target: Option<&str>) -> Result<Device<'a>> {
- if let Ok(device) = ios_deploy_device_prompt(env, target) {
- Ok(device)
- } else {
- let simulator = simulator_prompt(env, target)?;
- let handle = simulator.start(env)?;
- spawn(move || {
- let _ = handle.wait();
- });
- Ok(simulator.into())
- }
- }
- fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
- device_prompt(env, None).map(|device| device.target()).ok()
- }
- fn open_and_wait(config: &AppleConfig, env: &Env) -> ! {
- log::info!("Opening Xcode");
- if let Err(e) = os::open_file_with("Xcode", config.project_dir(), env) {
- log::error!("{}", e);
- }
- loop {
- sleep(Duration::from_secs(24 * 60 * 60));
- }
- }
|