2024-03-01 11:29:01 +00:00
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
2023-02-19 14:21:50 +00:00
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
2023-05-26 16:45:20 +00:00
use super ::{ detect_target_ok , ensure_init , env , get_app , get_config , read_options , MobileTarget } ;
2024-02-03 00:43:33 +00:00
use crate ::{
2025-10-02 09:58:26 +00:00
error ::{ Context , ErrorExt } ,
2025-06-28 20:16:36 +00:00
helpers ::config ::{ get as get_tauri_config , reload as reload_tauri_config } ,
2024-02-03 00:43:33 +00:00
interface ::{ AppInterface , Interface } ,
2024-10-02 10:01:29 +00:00
mobile ::CliOptions ,
2025-10-02 09:58:26 +00:00
Error , Result ,
2024-02-03 00:43:33 +00:00
} ;
2022-10-31 12:49:22 +00:00
use clap ::{ ArgAction , Parser } ;
2022-08-23 00:59:17 +00:00
2023-10-05 19:30:56 +00:00
use cargo_mobile2 ::{
2024-08-03 13:04:26 +00:00
android ::{ adb , target ::Target } ,
2022-08-26 12:24:23 +00:00
opts ::Profile ,
2022-08-23 00:59:17 +00:00
target ::{ call_for_targets_with_fallback , TargetTrait } ,
} ;
2024-08-05 12:46:28 +00:00
use std ::path ::Path ;
2022-08-23 00:59:17 +00:00
#[ derive(Debug, Parser) ]
pub struct Options {
/// Targets to build.
#[ clap(
short ,
long = " target " ,
2022-10-31 12:49:22 +00:00
action = ArgAction ::Append ,
num_args ( 0 .. ) ,
2022-08-23 00:59:17 +00:00
default_value = Target ::DEFAULT_KEY ,
value_parser ( clap ::builder ::PossibleValuesParser ::new ( Target ::name_list ( ) ) )
) ]
targets : Option < Vec < String > > ,
/// Builds with the release flag
#[ clap(short, long) ]
release : bool ,
}
pub fn command ( options : Options ) -> Result < ( ) > {
2024-08-11 12:44:15 +00:00
crate ::helpers ::app_paths ::resolve ( ) ;
2022-08-23 00:59:17 +00:00
let profile = if options . release {
Profile ::Release
} else {
Profile ::Debug
} ;
2025-06-28 20:16:36 +00:00
let ( tauri_config , cli_options ) = {
2025-07-08 11:49:47 +00:00
let tauri_config = get_tauri_config ( tauri_utils ::platform ::Target ::Android , & [ ] ) ? ;
2025-06-28 20:16:36 +00:00
let cli_options = {
let tauri_config_guard = tauri_config . lock ( ) . unwrap ( ) ;
let tauri_config_ = tauri_config_guard . as_ref ( ) . unwrap ( ) ;
read_options ( tauri_config_ )
} ;
2023-05-26 16:45:20 +00:00
2025-06-28 20:16:36 +00:00
let tauri_config = if cli_options . config . is_empty ( ) {
tauri_config
} else {
// reload config with merges from the android dev|build script
reload_tauri_config (
& cli_options
. config
. iter ( )
. map ( | conf | & conf . 0 )
. collect ::< Vec < _ > > ( ) ,
) ?
} ;
( tauri_config , cli_options )
} ;
let ( config , metadata ) = {
2023-05-26 16:45:20 +00:00
let tauri_config_guard = tauri_config . lock ( ) . unwrap ( ) ;
let tauri_config_ = tauri_config_guard . as_ref ( ) . unwrap ( ) ;
2024-02-03 00:43:33 +00:00
let ( config , metadata ) = get_config (
2024-08-21 16:46:25 +00:00
& get_app (
MobileTarget ::Android ,
tauri_config_ ,
& AppInterface ::new ( tauri_config_ , None ) ? ,
) ,
2024-02-03 00:43:33 +00:00
tauri_config_ ,
2024-02-26 18:17:45 +00:00
None ,
2024-02-03 00:43:33 +00:00
& cli_options ,
) ;
2025-06-28 20:16:36 +00:00
( config , metadata )
2023-05-26 16:45:20 +00:00
} ;
2024-08-14 00:11:01 +00:00
2024-08-05 12:45:18 +00:00
ensure_init (
& tauri_config ,
config . app ( ) ,
config . project_dir ( ) ,
MobileTarget ::Android ,
) ? ;
2023-05-26 16:45:20 +00:00
2025-03-14 15:04:58 +00:00
if ! cli_options . config . is_empty ( ) {
crate ::helpers ::config ::merge_with (
& cli_options
. config
. iter ( )
. map ( | conf | & conf . 0 )
. collect ::< Vec < _ > > ( ) ,
) ? ;
2024-08-14 00:11:01 +00:00
}
2025-10-01 12:33:14 +00:00
let env = env ( std ::env ::var ( " CI " ) . is_ok ( ) ) ? ;
2023-05-26 16:45:20 +00:00
2024-08-03 13:04:26 +00:00
if cli_options . dev {
let dev_url = tauri_config
. lock ( )
. unwrap ( )
. as_ref ( )
. unwrap ( )
. build
. dev_url
. clone ( ) ;
2024-08-14 00:11:01 +00:00
2024-10-02 10:01:29 +00:00
if let Some ( url ) = dev_url {
let localhost = match url . host ( ) {
Some ( url ::Host ::Domain ( d ) ) = > d = = " localhost " ,
Some ( url ::Host ::Ipv4 ( i ) ) = > i = = std ::net ::Ipv4Addr ::LOCALHOST ,
_ = > false ,
} ;
2024-08-14 01:07:02 +00:00
2024-10-02 10:01:29 +00:00
if localhost {
if let Some ( port ) = url . port_or_known_default ( ) {
adb_forward_port ( port , & env , & cli_options ) ? ;
}
2024-08-14 01:07:02 +00:00
}
2024-08-03 13:04:26 +00:00
}
}
2024-08-05 12:46:28 +00:00
let mut validated_lib = false ;
2025-08-27 21:05:24 +00:00
let installed_targets =
crate ::interface ::rust ::installation ::installed_targets ( ) . unwrap_or_default ( ) ;
2023-05-26 16:45:20 +00:00
call_for_targets_with_fallback (
options . targets . unwrap_or_default ( ) . iter ( ) ,
& detect_target_ok ,
& env ,
| target : & Target | {
2025-08-27 21:05:24 +00:00
if ! installed_targets . contains ( & target . triple ( ) . into ( ) ) {
log ::info! ( " Installing target {} " , target . triple ( ) ) ;
target
. install ( )
2025-10-02 09:58:26 +00:00
. map_err ( | error | Error ::CommandFailed {
command : " rustup target add " . to_string ( ) ,
error ,
} )
. context ( " failed to install target " ) ? ;
2025-08-27 21:05:24 +00:00
}
2025-10-02 09:58:26 +00:00
target
. build (
& config ,
& metadata ,
& env ,
cli_options . noise_level ,
true ,
profile ,
)
. context ( " failed to build Android app " ) ? ;
2024-08-05 12:46:28 +00:00
if ! validated_lib {
validated_lib = true ;
let lib_path = config
. app ( )
. target_dir ( target . triple , profile )
. join ( config . so_name ( ) ) ;
2025-10-02 09:58:26 +00:00
validate_lib ( & lib_path ) . context ( " failed to validate library " ) ? ;
2024-08-05 12:46:28 +00:00
}
Ok ( ( ) )
2023-05-26 16:45:20 +00:00
} ,
)
2025-10-02 09:58:26 +00:00
. map_err ( | e | Error ::GenericError ( e . to_string ( ) ) ) ?
2022-08-23 00:59:17 +00:00
}
2024-08-05 12:46:28 +00:00
fn validate_lib ( path : & Path ) -> Result < ( ) > {
2025-10-02 09:58:26 +00:00
let so_bytes = std ::fs ::read ( path ) . fs_context ( " failed to read library " , path . to_path_buf ( ) ) ? ;
2024-08-05 12:46:28 +00:00
let elf = elf ::ElfBytes ::< elf ::endian ::AnyEndian > ::minimal_parse ( & so_bytes )
. context ( " failed to parse ELF " ) ? ;
let ( symbol_table , string_table ) = elf
. dynamic_symbol_table ( )
. context ( " failed to read dynsym section " ) ?
. context ( " missing dynsym tables " ) ? ;
let mut symbols = Vec ::new ( ) ;
for s in symbol_table . iter ( ) {
if let Ok ( symbol ) = string_table . get ( s . st_name as usize ) {
symbols . push ( symbol ) ;
}
}
if ! symbols . contains ( & " Java_app_tauri_plugin_PluginManager_handlePluginResponse " ) {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! (
2024-08-05 12:46:28 +00:00
" Library from {} does not include required runtime symbols. This means you are likely missing the tauri::mobile_entry_point macro usage, see the documentation for more information: https://v2.tauri.app/start/migrate/from-tauri-1 " ,
path . display ( )
) ;
}
Ok ( ( ) )
}
2024-08-14 01:07:02 +00:00
2024-10-02 10:01:29 +00:00
fn adb_forward_port (
port : u16 ,
env : & cargo_mobile2 ::android ::env ::Env ,
cli_options : & CliOptions ,
) -> Result < ( ) > {
let forward = format! ( " tcp: {port} " ) ;
log ::info! ( " Forwarding port {port} with adb " ) ;
let mut devices = adb ::device_list ( env ) . unwrap_or_default ( ) ;
// if we could not detect any running device, let's wait a few seconds, it might be booting up
if devices . is_empty ( ) {
log ::warn! (
" ADB device list is empty, waiting a few seconds to see if there's any booting device... "
) ;
let max = 5 ;
let mut count = 0 ;
loop {
std ::thread ::sleep ( std ::time ::Duration ::from_secs ( 1 ) ) ;
devices = adb ::device_list ( env ) . unwrap_or_default ( ) ;
if ! devices . is_empty ( ) {
break ;
}
count + = 1 ;
if count = = max {
break ;
}
}
}
let target_device = if let Some ( target_device ) = & cli_options . target_device {
Some ( ( target_device . id . clone ( ) , target_device . name . clone ( ) ) )
} else if devices . len ( ) = = 1 {
let device = devices . first ( ) . unwrap ( ) ;
Some ( ( device . serial_no ( ) . to_string ( ) , device . name ( ) . to_string ( ) ) )
} else if devices . len ( ) > 1 {
2025-10-02 09:58:26 +00:00
crate ::error ::bail! ( " Multiple Android devices are connected ({}), please disconnect devices you do not intend to use so Tauri can determine which to use " ,
2024-10-02 10:01:29 +00:00
devices . iter ( ) . map ( | d | d . name ( ) ) . collect ::< Vec < _ > > ( ) . join ( " , " ) ) ;
} else {
// when building the app without running to a device, we might have an empty devices list
None
} ;
if let Some ( ( target_device_serial_no , target_device_name ) ) = target_device {
let mut already_forwarded = false ;
// clear port forwarding for all devices
for device in & devices {
2025-10-02 09:58:26 +00:00
let reverse_list_output =
adb_reverse_list ( env , device . serial_no ( ) ) . map_err ( | error | Error ::CommandFailed {
command : " adb reverse --list " . to_string ( ) ,
error ,
} ) ? ;
2024-10-02 10:01:29 +00:00
// check if the device has the port forwarded
if String ::from_utf8_lossy ( & reverse_list_output . stdout ) . contains ( & forward ) {
// device matches our target, we can skip forwarding
if device . serial_no ( ) = = target_device_serial_no {
log ::debug! (
" device {} already has the forward for {} " ,
device . name ( ) ,
forward
) ;
already_forwarded = true ;
}
break ;
}
}
// if there's a known target, we should forward the port to it
if already_forwarded {
log ::info! ( " {forward} already forwarded to {target_device_name} " ) ;
} else {
loop {
2025-10-02 09:58:26 +00:00
run_adb_reverse ( env , & target_device_serial_no , & forward , & forward ) . map_err ( | error | {
Error ::CommandFailed {
command : format ! ( " adb reverse {forward} {forward} " ) ,
error ,
}
2024-10-02 10:01:29 +00:00
} ) ? ;
2025-10-02 09:58:26 +00:00
let reverse_list_output =
adb_reverse_list ( env , & target_device_serial_no ) . map_err ( | error | {
Error ::CommandFailed {
command : " adb reverse --list " . to_string ( ) ,
error ,
}
} ) ? ;
2024-10-02 10:01:29 +00:00
// wait and retry until the port has actually been forwarded
if String ::from_utf8_lossy ( & reverse_list_output . stdout ) . contains ( & forward ) {
break ;
} else {
log ::warn! (
" waiting for the port to be forwarded to {}... " ,
target_device_name
) ;
std ::thread ::sleep ( std ::time ::Duration ::from_secs ( 1 ) ) ;
}
}
}
} else {
log ::warn! ( " no running devices detected with ADB; skipping port forwarding " ) ;
}
Ok ( ( ) )
}
2024-08-14 01:07:02 +00:00
fn run_adb_reverse (
env : & cargo_mobile2 ::android ::env ::Env ,
device_serial_no : & str ,
remote : & str ,
local : & str ,
) -> std ::io ::Result < std ::process ::Output > {
adb ::adb ( env , [ " -s " , device_serial_no , " reverse " , remote , local ] )
. stdin_file ( os_pipe ::dup_stdin ( ) . unwrap ( ) )
. stdout_file ( os_pipe ::dup_stdout ( ) . unwrap ( ) )
. stderr_file ( os_pipe ::dup_stdout ( ) . unwrap ( ) )
. run ( )
}
2024-10-02 10:01:29 +00:00
fn adb_reverse_list (
2024-08-14 01:07:02 +00:00
env : & cargo_mobile2 ::android ::env ::Env ,
device_serial_no : & str ,
2024-10-02 10:01:29 +00:00
) -> std ::io ::Result < std ::process ::Output > {
adb ::adb ( env , [ " -s " , device_serial_no , " reverse " , " --list " ] )
2024-08-14 01:07:02 +00:00
. stdin_file ( os_pipe ::dup_stdin ( ) . unwrap ( ) )
2024-10-02 10:01:29 +00:00
. stdout_capture ( )
. stderr_capture ( )
. run ( )
2024-08-14 01:07:02 +00:00
}