mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-02-06 13:17:02 +00:00
* refactor(tauri-cli): introduce replacement functions * refactor(tauri-cli): apply replacement to remove.rs * refactor(tauri-cli): apply replacement to icon.rs * refactor(tauri-cli): apply replacement to bundle.rs * refactor(tauri-cli): apply replacement to build.rs * refactor(tauri-cli): apply replacement to add.rs * refactor(tauri-cli): apply replacement to dev.rs * refactor(tauri-cli): less controlflow * refactor(tauri-cli): split config loading from locking static * refactor(tauri-cli): remove duplicate checks covered by if let Some(tauri_dir) = tauri_dir tauri_dir.is_some() must be true, otherwise the entire block is not run, so the frontend_dir check is irrelevant * fmt * refactor(tauri-cli): apply replacement to inspect.rs * refactor(tauri-cli): dont use statics for config * refactor(tauri-cli): finish threading directory paths through functions * fix: clippy * fixup CI * refactor(tauri-cli): dont need mutex * refactor(tauri-cli): rescope mutex use to minimal necessary * fix CI, reduce mutex use * fixup PR #14607 * fix: clippy * refactor(tauri-cli): remove ConfigHandle * refactor(tauri-cli): remove more unwraps and panicing functions * refactor(tauri-cli): less mutexes * refactor(tauri-cli): undo mistaken change, address review comment * Split android build to 2 functions * Pass in dirs to migration v1 like the v2 beta * Add change file --------- Co-authored-by: Tony <legendmastertony@gmail.com>
324 lines
9.1 KiB
Rust
324 lines
9.1 KiB
Rust
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
use super::{detect_target_ok, ensure_init, env, get_app, get_config, read_options, MobileTarget};
|
|
use crate::{
|
|
error::{Context, ErrorExt},
|
|
helpers::config::{get_config as get_tauri_config, reload_config as reload_tauri_config},
|
|
interface::{AppInterface, Interface},
|
|
mobile::CliOptions,
|
|
Error, Result,
|
|
};
|
|
use clap::{ArgAction, Parser};
|
|
|
|
use cargo_mobile2::{
|
|
android::{adb, target::Target},
|
|
opts::Profile,
|
|
target::{call_for_targets_with_fallback, TargetTrait},
|
|
};
|
|
|
|
use std::path::Path;
|
|
|
|
#[derive(Debug, Parser)]
|
|
pub struct Options {
|
|
/// Targets to build.
|
|
#[clap(
|
|
short,
|
|
long = "target",
|
|
action = ArgAction::Append,
|
|
num_args(0..),
|
|
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<()> {
|
|
let dirs = crate::helpers::app_paths::resolve_dirs();
|
|
|
|
let profile = if options.release {
|
|
Profile::Release
|
|
} else {
|
|
Profile::Debug
|
|
};
|
|
|
|
let mut tauri_config = get_tauri_config(tauri_utils::platform::Target::Android, &[], dirs.tauri)?;
|
|
let cli_options = read_options(&tauri_config);
|
|
|
|
if !cli_options.config.is_empty() {
|
|
// reload config with merges from the android dev|build script
|
|
reload_tauri_config(
|
|
&mut tauri_config,
|
|
&cli_options
|
|
.config
|
|
.iter()
|
|
.map(|conf| &conf.0)
|
|
.collect::<Vec<_>>(),
|
|
dirs.tauri,
|
|
)?
|
|
};
|
|
|
|
let (config, metadata) = {
|
|
let (config, metadata) = get_config(
|
|
&get_app(
|
|
MobileTarget::Android,
|
|
&tauri_config,
|
|
&AppInterface::new(&tauri_config, None, dirs.tauri)?,
|
|
dirs.tauri,
|
|
),
|
|
&tauri_config,
|
|
&[],
|
|
&cli_options,
|
|
);
|
|
(config, metadata)
|
|
};
|
|
|
|
ensure_init(
|
|
&tauri_config,
|
|
config.app(),
|
|
config.project_dir(),
|
|
MobileTarget::Android,
|
|
std::env::var("CI").is_ok(),
|
|
)?;
|
|
|
|
if !cli_options.config.is_empty() {
|
|
crate::helpers::config::merge_config_with(
|
|
&mut tauri_config,
|
|
&cli_options
|
|
.config
|
|
.iter()
|
|
.map(|conf| &conf.0)
|
|
.collect::<Vec<_>>(),
|
|
)?;
|
|
}
|
|
|
|
let env = env(std::env::var("CI").is_ok())?;
|
|
|
|
if cli_options.dev {
|
|
if let Some(url) = &tauri_config.build.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,
|
|
};
|
|
|
|
if localhost {
|
|
if let Some(port) = url.port_or_known_default() {
|
|
adb_forward_port(port, &env, &cli_options)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut validated_lib = false;
|
|
|
|
let installed_targets =
|
|
crate::interface::rust::installation::installed_targets().unwrap_or_default();
|
|
|
|
call_for_targets_with_fallback(
|
|
options.targets.unwrap_or_default().iter(),
|
|
&detect_target_ok,
|
|
&env,
|
|
|target: &Target| {
|
|
if !installed_targets.contains(&target.triple().into()) {
|
|
log::info!("Installing target {}", target.triple());
|
|
target
|
|
.install()
|
|
.map_err(|error| Error::CommandFailed {
|
|
command: "rustup target add".to_string(),
|
|
error,
|
|
})
|
|
.context("failed to install target")?;
|
|
}
|
|
|
|
target
|
|
.build(
|
|
&config,
|
|
&metadata,
|
|
&env,
|
|
cli_options.noise_level,
|
|
true,
|
|
profile,
|
|
)
|
|
.context("failed to build Android app")?;
|
|
|
|
if !validated_lib {
|
|
validated_lib = true;
|
|
|
|
let lib_path = config
|
|
.app()
|
|
.target_dir(target.triple, profile)
|
|
.join(config.so_name());
|
|
|
|
validate_lib(&lib_path).context("failed to validate library")?;
|
|
}
|
|
|
|
Ok(())
|
|
},
|
|
)
|
|
.map_err(|e| Error::GenericError(e.to_string()))?
|
|
}
|
|
|
|
fn validate_lib(path: &Path) -> Result<()> {
|
|
let so_bytes = std::fs::read(path).fs_context("failed to read library", path.to_path_buf())?;
|
|
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") {
|
|
crate::error::bail!(
|
|
"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(())
|
|
}
|
|
|
|
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 {
|
|
crate::error::bail!("Multiple Android devices are connected ({}), please disconnect devices you do not intend to use so Tauri can determine which to use",
|
|
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 {
|
|
let reverse_list_output =
|
|
adb_reverse_list(env, device.serial_no()).map_err(|error| Error::CommandFailed {
|
|
command: "adb reverse --list".to_string(),
|
|
error,
|
|
})?;
|
|
|
|
// 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 {
|
|
run_adb_reverse(env, &target_device_serial_no, &forward, &forward).map_err(|error| {
|
|
Error::CommandFailed {
|
|
command: format!("adb reverse {forward} {forward}"),
|
|
error,
|
|
}
|
|
})?;
|
|
|
|
let reverse_list_output =
|
|
adb_reverse_list(env, &target_device_serial_no).map_err(|error| {
|
|
Error::CommandFailed {
|
|
command: "adb reverse --list".to_string(),
|
|
error,
|
|
}
|
|
})?;
|
|
// 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(())
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
fn adb_reverse_list(
|
|
env: &cargo_mobile2::android::env::Env,
|
|
device_serial_no: &str,
|
|
) -> std::io::Result<std::process::Output> {
|
|
adb::adb(env, ["-s", device_serial_no, "reverse", "--list"])
|
|
.stdin_file(os_pipe::dup_stdin().unwrap())
|
|
.stdout_capture()
|
|
.stderr_capture()
|
|
.run()
|
|
}
|