/
githubmirror
/
tauri
Обзор
Документация
Войти
/
githubmirror
/
tauri
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
dev
crates/tauri-cli/src/mobile/ios/dev.rs
405 строк
13 KB
Lucas Fernandes Nogueira
feat(mobile): add file association support (#14486)
13 апр 2026, 20:41
Не верифицирован
13 апр 2026, 20:41
cc5c976
Код
Авторство
О чём код?
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use super::{ device_prompt, ensure_init, env, get_app, get_config, inject_resources, load_pbxproj, open_and_wait, synchronize_project_config, MobileTarget, ProjectConfig, }; use crate::{ dev::Options as DevOptions, error::{Context, ErrorExt}, helpers::{ app_paths::Dirs, config::{get_config as get_tauri_config, ConfigMetadata}, flock, plist::merge_plist, }, interface::{AppInterface, MobileOptions, Options as InterfaceOptions}, mobile::{ ios::ensure_ios_runtime_installed, use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess, }, ConfigValue, Result, }; use clap::{ArgAction, Parser}; use cargo_mobile2::{ apple::{ config::Config as AppleConfig, device::{Device, DeviceKind, RunError}, target::BuildError, }, env::Env, opts::{NoiseLevel, Profile}, }; use url::Host; use std::{env::set_current_dir, net::Ipv4Addr, path::PathBuf}; const PHYSICAL_IPHONE_DEV_WARNING: &str = "To develop on physical phones you need the `--host` option (not required for Simulators). See the documentation for more information: https://v2.tauri.app/develop/#development-server"; #[derive(Debug, Clone, Parser)] #[clap( about = "Run your app in development mode on iOS", long_about = "Run your app in development mode on iOS with hot-reloading for the Rust code. It makes use of the `build.devUrl` property from your `tauri.conf.json` file. It also runs your `build.beforeDevCommand` which usually starts your frontend devServer. When connected to a physical iOS device, the public network address must be used instead of `localhost` for the devUrl property. Tauri makes that change automatically, but your dev server might need a different configuration to listen on the public address. You can check the `TAURI_DEV_HOST` environment variable to determine whether the public network should be used or not." )] pub struct Options { /// List of cargo features to activate #[clap(short, long, action = ArgAction::Append, num_args(0..), value_delimiter = ',')] pub features: Vec<String>, /// Exit on panic #[clap(short, long)] exit_on_panic: bool, /// JSON strings or paths to JSON, JSON5 or TOML files to merge with the default configuration file /// /// Configurations are merged in the order they are provided, which means a particular value overwrites previous values when a config key-value pair conflicts. /// /// Note that a platform-specific file is looked up and merged with the default file by default /// (tauri.macos.conf.json, tauri.linux.conf.json, tauri.windows.conf.json, tauri.android.conf.json and tauri.ios.conf.json) /// but you can use this for more specific use cases such as different build flavors. #[clap(short, long)] pub config: Vec<ConfigValue>, /// Run the code in release mode #[clap(long = "release")] pub release_mode: bool, /// Skip waiting for the frontend dev server to start before building the tauri application. #[clap(long, env = "TAURI_CLI_NO_DEV_SERVER_WAIT")] pub no_dev_server_wait: bool, /// Disable the file watcher #[clap(long)] pub no_watch: bool, /// Additional paths to watch for changes. #[clap(long)] pub additional_watch_folders: Vec<PathBuf>, /// Open Xcode instead of trying to run on a connected device #[clap(short, long)] pub open: bool, /// Runs on the given device name pub device: Option<String>, /// Force prompting for an IP to use to connect to the dev server on mobile. #[clap(long)] pub force_ip_prompt: bool, /// Use the public network address for the development server. /// If an actual address it provided, it is used instead of prompting to pick one. /// /// This option is particularly useful along the `--open` flag when you intend on running on a physical device. /// /// This replaces the devUrl configuration value to match the public network address host, /// it is your responsibility to set up your development server to listen on this address /// by using 0.0.0.0 as host for instance. /// /// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST` /// environment variable so you can check this on your framework's configuration to expose the development server /// on the public network address. #[clap(long, default_value_t, default_missing_value(""), num_args(0..=1))] pub host: DevHost, /// Disable the built-in dev server for static files. #[clap(long)] pub no_dev_server: bool, /// Specify port for the built-in dev server for static files. Defaults to 1430. #[clap(long, env = "TAURI_CLI_PORT")] pub port: Option<u16>, /// Command line arguments passed to the runner. /// Use `--` to explicitly mark the start of the arguments. /// e.g. `tauri ios dev -- [runnerArgs]`. #[clap(last(true))] pub args: Vec<String>, /// Path to the certificate file used by your dev server. Required for mobile dev when using HTTPS. #[clap(long, env = "TAURI_DEV_ROOT_CERTIFICATE_PATH")] pub root_certificate_path: Option<PathBuf>, } impl From<Options> for DevOptions { fn from(options: Options) -> Self { Self { runner: None, target: None, features: options.features, exit_on_panic: options.exit_on_panic, config: options.config, release_mode: options.release_mode, args: options.args, no_watch: options.no_watch, additional_watch_folders: options.additional_watch_folders, no_dev_server: options.no_dev_server, no_dev_server_wait: options.no_dev_server_wait, port: options.port, host: options.host.0.unwrap_or_default(), } } } pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> { let dirs = crate::helpers::app_paths::resolve_dirs(); let result = run_command(options, noise_level, dirs); if result.is_err() { crate::dev::kill_before_dev_process(); } result } fn run_command(options: Options, noise_level: NoiseLevel, dirs: Dirs) -> Result<()> { // setup env additions before calling env() if let Some(root_certificate_path) = &options.root_certificate_path { std::env::set_var( "TAURI_DEV_ROOT_CERTIFICATE", std::fs::read_to_string(root_certificate_path).fs_context( "failed to read root certificate file", root_certificate_path.clone(), )?, ); } let env = env().context("failed to load iOS environment")?; let device = if options.open { None } else { match device_prompt(&env, options.device.as_deref()) { Ok(d) => Some(d), Err(e) => { log::error!("{e}"); None } } }; if device.is_some() { ensure_ios_runtime_installed()?; } let mut dev_options: DevOptions = options.clone().into(); let target_triple = device .as_ref() .map(|d| d.target().triple.to_string()) .unwrap_or_else(|| "aarch64-apple-ios".into()); dev_options.target = Some(target_triple.clone()); dev_options.args.push("--lib".into()); let tauri_config = get_tauri_config( tauri_utils::platform::Target::Ios, &options.config.iter().map(|c| &c.0).collect::<Vec<_>>(), dirs.tauri, )?; let interface = AppInterface::new(&tauri_config, Some(target_triple), dirs.tauri)?; let app = get_app(MobileTarget::Ios, &tauri_config, &interface, dirs.tauri); let (config, _) = get_config( &app, &tauri_config, &dev_options.features, &CliOptions { dev: true, features: dev_options.features.clone(), args: dev_options.args.clone(), noise_level, vars: Default::default(), config: dev_options.config.clone(), target_device: None, }, dirs.tauri, )?; set_current_dir(dirs.tauri).context("failed to set current directory to Tauri directory")?; ensure_init( &tauri_config, config.app(), config.project_dir(), MobileTarget::Ios, false, )?; inject_resources(&config, &tauri_config)?; let info_plist_path = config .project_dir() .join(config.scheme()) .join("Info.plist"); let mut src_plists = vec![info_plist_path.clone().into()]; if dirs.tauri.join("Info.plist").exists() { src_plists.push(dirs.tauri.join("Info.plist").into()); } if dirs.tauri.join("Info.ios.plist").exists() { src_plists.push(dirs.tauri.join("Info.ios.plist").into()); } { if let Some(info_plist) = &tauri_config.bundle.ios.info_plist { src_plists.push(info_plist.clone().into()); } if let Some(associations) = tauri_config.bundle.file_associations.as_ref() { if let Some(file_associations) = tauri_utils::config::file_associations_plist(associations) { src_plists.push(file_associations.into()); } } } let merged_info_plist = merge_plist(src_plists)?; merged_info_plist .to_file_xml(&info_plist_path) .map_err(std::io::Error::other) .fs_context("failed to save merged Info.plist file", info_plist_path)?; let mut pbxproj = load_pbxproj(&config)?; // synchronize pbxproj synchronize_project_config( &config, &tauri_config, &mut pbxproj, &mut plist::Dictionary::new(), &ProjectConfig { code_sign_identity: None, team_id: None, provisioning_profile_uuid: None, }, !options.release_mode, )?; if pbxproj.has_changes() { pbxproj .save() .fs_context("failed to save pbxproj file", pbxproj.path)?; } run_dev( interface, options, dev_options, tauri_config, device, env, &config, noise_level, &dirs, ) } #[allow(clippy::too_many_arguments)] fn run_dev( mut interface: AppInterface, options: Options, mut dev_options: DevOptions, mut tauri_config: ConfigMetadata, device: Option<Device>, env: Env, config: &AppleConfig, noise_level: NoiseLevel, dirs: &Dirs, ) -> Result<()> { // when --host is provided or running on a physical device or resolving 0.0.0.0 we must use the network IP if options.host.0.is_some() || device .as_ref() .map(|device| !matches!(device.kind(), DeviceKind::Simulator)) .unwrap_or(false) || tauri_config.build.dev_url.as_ref().is_some_and(|url| { matches!( url.host(), Some(Host::Ipv4(i)) if i == Ipv4Addr::UNSPECIFIED ) }) { use_network_address_for_dev_url( &mut tauri_config, &mut dev_options, options.force_ip_prompt, dirs.tauri, )?; } crate::dev::setup(&interface, &mut dev_options, &mut tauri_config, dirs)?; let app_settings = interface.app_settings(); let out_dir = app_settings.out_dir( &InterfaceOptions { debug: !dev_options.release_mode, target: dev_options.target.clone(), ..Default::default() }, dirs.tauri, )?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?; let set_host = options.host.0.is_some(); let open = options.open; interface.mobile_dev( &mut tauri_config, MobileOptions { debug: true, features: options.features, args: options.args, config: dev_options.config.clone(), no_watch: options.no_watch, additional_watch_folders: options.additional_watch_folders, }, |options, tauri_config| { let cli_options = CliOptions { dev: true, features: options.features.clone(), args: options.args.clone(), noise_level, vars: Default::default(), config: dev_options.config.clone(), target_device: None, }; let _handle = write_options(tauri_config, cli_options)?; let open_xcode = || { if !set_host { log::warn!("{PHYSICAL_IPHONE_DEV_WARNING}"); } open_and_wait(config, &env) }; if open { open_xcode() } else if let Some(device) = &device { match run(device, options, config, noise_level, &env) { Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess + Send>), Err(RunError::BuildFailed(BuildError::Sdk(sdk_err))) => { log::warn!("{sdk_err}"); open_xcode() } Err(e) => { crate::dev::kill_before_dev_process(); crate::error::bail!("failed to run iOS app: {}", e) } } } else { open_xcode() } }, dirs, ) } fn run( device: &Device<'_>, options: MobileOptions, config: &AppleConfig, noise_level: NoiseLevel, env: &Env, ) -> std::result::Result<DevChild, RunError> { let profile = if options.debug { Profile::Debug } else { Profile::Release }; device .run( config, env, noise_level, false, // do not quit on app exit profile, ) .map(DevChild::new) }