mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-01-31 00:35:19 +01:00
feat(cli): detect Android env and install SDK and NDK if needed (#14094)
* feat(cli): detect Android env and install SDK and NDK if needed changes the Android setup to be a bit more automated - looking up ANDROID_HOME and NDK_HOME from common system paths and installing the Android SDK and NDK if needed using the command line tools * fix windows * clippy * lint * add prmopts and ci check * also check ANDROID_SDK_ROOT
This commit is contained in:
committed by
GitHub
parent
4188ffdafc
commit
673867aa0e
6
.changes/ensure-android-env.md
Normal file
6
.changes/ensure-android-env.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"tauri-cli": minor:feat
|
||||
"@tauri-apps/cli": minor:feat
|
||||
---
|
||||
|
||||
Try to detect ANDROID_HOME and NDK_HOME environment variables from default system locations and install them if needed using the Android Studio command line tools.
|
||||
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -8627,6 +8627,7 @@ dependencies = [
|
||||
"css-color",
|
||||
"ctrlc",
|
||||
"dialoguer",
|
||||
"dirs 6.0.0",
|
||||
"duct",
|
||||
"dunce",
|
||||
"elf",
|
||||
@@ -8687,7 +8688,9 @@ dependencies = [
|
||||
"url",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
"which",
|
||||
"windows-sys 0.60.2",
|
||||
"zip 4.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -69,6 +69,7 @@ toml = "0.9"
|
||||
jsonschema = "0.33"
|
||||
handlebars = "6"
|
||||
include_dir = "0.7"
|
||||
dirs = "6"
|
||||
minisign = "=0.7.3"
|
||||
base64 = "0.22"
|
||||
ureq = { version = "3", default-features = false, features = ["gzip"] }
|
||||
@@ -110,6 +111,8 @@ memchr = "2"
|
||||
tempfile = "3"
|
||||
uuid = { version = "1", features = ["v5"] }
|
||||
rand = "0.9"
|
||||
zip = { version = "4", default-features = false, features = ["deflate"] }
|
||||
which = "8"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1"
|
||||
|
||||
@@ -131,20 +131,7 @@ struct CrateIoGetResponse {
|
||||
pub fn crate_latest_version(name: &str) -> Option<String> {
|
||||
// Reference: https://github.com/rust-lang/crates.io/blob/98c83c8231cbcd15d6b8f06d80a00ad462f71585/src/controllers/krate/metadata.rs#L88
|
||||
let url = format!("https://crates.io/api/v1/crates/{name}?include");
|
||||
#[cfg(feature = "platform-certs")]
|
||||
let mut response = {
|
||||
let agent = ureq::Agent::config_builder()
|
||||
.tls_config(
|
||||
ureq::tls::TlsConfig::builder()
|
||||
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
.new_agent();
|
||||
agent.get(&url).call().ok()?
|
||||
};
|
||||
#[cfg(not(feature = "platform-certs"))]
|
||||
let mut response = ureq::get(&url).call().ok()?;
|
||||
let mut response = super::http::get(&url).ok()?;
|
||||
let metadata: CrateIoGetResponse =
|
||||
serde_json::from_reader(response.body_mut().as_reader()).unwrap();
|
||||
metadata.krate.default_version
|
||||
|
||||
24
crates/tauri-cli/src/helpers/http.rs
Normal file
24
crates/tauri-cli/src/helpers/http.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use ureq::{http::Response, Body};
|
||||
|
||||
pub fn get(url: &str) -> Result<Response<Body>, ureq::Error> {
|
||||
#[cfg(feature = "platform-certs")]
|
||||
{
|
||||
let agent = ureq::Agent::config_builder()
|
||||
.tls_config(
|
||||
ureq::tls::TlsConfig::builder()
|
||||
.root_certs(ureq::tls::RootCerts::PlatformVerifier)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
.new_agent();
|
||||
agent.get(url).call()
|
||||
}
|
||||
#[cfg(not(feature = "platform-certs"))]
|
||||
{
|
||||
ureq::get(url).call()
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod config;
|
||||
pub mod flock;
|
||||
pub mod framework;
|
||||
pub mod fs;
|
||||
pub mod http;
|
||||
pub mod npm;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod pbxproj;
|
||||
|
||||
@@ -103,7 +103,7 @@ pub fn command(options: Options) -> Result<()> {
|
||||
)?;
|
||||
}
|
||||
|
||||
let env = env()?;
|
||||
let env = env(std::env::var("CI").is_ok())?;
|
||||
|
||||
if cli_options.dev {
|
||||
let dev_url = tauri_config
|
||||
|
||||
@@ -163,7 +163,7 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
MobileTarget::Android,
|
||||
)?;
|
||||
|
||||
let mut env = env()?;
|
||||
let mut env = env(options.ci)?;
|
||||
configure_cargo(&mut env, &config)?;
|
||||
|
||||
crate::build::setup(&interface, &mut build_options, tauri_config.clone(), true)?;
|
||||
|
||||
@@ -158,7 +158,7 @@ fn run_command(options: Options, noise_level: NoiseLevel) -> Result<()> {
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
let env = env()?;
|
||||
let env = env(false)?;
|
||||
let device = if options.open {
|
||||
None
|
||||
} else {
|
||||
|
||||
@@ -20,8 +20,10 @@ use cargo_mobile2::{
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::{
|
||||
env::set_var,
|
||||
fs::{create_dir, create_dir_all, write},
|
||||
process::exit,
|
||||
fs::{create_dir, create_dir_all, read_dir, write},
|
||||
io::Cursor,
|
||||
path::{Path, PathBuf},
|
||||
process::{exit, Command},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -42,6 +44,19 @@ mod build;
|
||||
mod dev;
|
||||
pub(crate) mod project;
|
||||
|
||||
const NDK_VERSION: &str = "29.0.13846066";
|
||||
const SDK_VERSION: u8 = 36;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
const CMDLINE_TOOLS_URL: &str =
|
||||
"https://dl.google.com/android/repository/commandlinetools-mac-13114758_latest.zip";
|
||||
#[cfg(target_os = "linux")]
|
||||
const CMDLINE_TOOLS_URL: &str =
|
||||
"https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip";
|
||||
#[cfg(windows)]
|
||||
const CMDLINE_TOOLS_URL: &str =
|
||||
"https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip";
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
author,
|
||||
@@ -176,11 +191,228 @@ pub fn get_config(
|
||||
(config, metadata)
|
||||
}
|
||||
|
||||
fn env() -> Result<Env> {
|
||||
pub fn env(non_interactive: bool) -> Result<Env> {
|
||||
let env = super::env()?;
|
||||
ensure_env(non_interactive)?;
|
||||
cargo_mobile2::android::env::Env::from_env(env).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn download_cmdline_tools(extract_path: &Path) -> Result<()> {
|
||||
log::info!("Downloading Android command line tools...");
|
||||
|
||||
let mut response = crate::helpers::http::get(CMDLINE_TOOLS_URL)?;
|
||||
let body = response
|
||||
.body_mut()
|
||||
.with_config()
|
||||
.limit(200 * 1024 * 1024 /* 200MB */)
|
||||
.read_to_vec()?;
|
||||
|
||||
let mut zip = zip::ZipArchive::new(Cursor::new(body))?;
|
||||
|
||||
log::info!(
|
||||
"Extracting Android command line tools to {}",
|
||||
extract_path.display()
|
||||
);
|
||||
zip.extract(extract_path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_env(non_interactive: bool) -> Result<()> {
|
||||
ensure_java()?;
|
||||
ensure_sdk(non_interactive)?;
|
||||
ensure_ndk(non_interactive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_java() -> Result<()> {
|
||||
if std::env::var_os("JAVA_HOME").is_none() {
|
||||
#[cfg(windows)]
|
||||
let default_java_home = "C:\\Program Files\\Android\\Android Studio\\jbr";
|
||||
#[cfg(target_os = "macos")]
|
||||
let default_java_home = "/Applications/Android Studio.app/Contents/jbr/Contents/Home";
|
||||
#[cfg(target_os = "linux")]
|
||||
let default_java_home = "/opt/android-studio/jbr";
|
||||
|
||||
if Path::new(default_java_home).exists() {
|
||||
log::info!("Using Android Studio's default Java installation: {default_java_home}");
|
||||
std::env::set_var("JAVA_HOME", default_java_home);
|
||||
} else if which::which("java").is_err() {
|
||||
anyhow::bail!("Java not found in PATH, default Android Studio Java installation not found at {default_java_home} and JAVA_HOME environment variable not set. Please install Java before proceeding");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_sdk(non_interactive: bool) -> Result<()> {
|
||||
let android_home = std::env::var_os("ANDROID_HOME")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from));
|
||||
if !android_home.as_ref().is_some_and(|v| v.exists()) {
|
||||
log::info!(
|
||||
"ANDROID_HOME {}, trying to locate Android SDK...",
|
||||
if let Some(v) = &android_home {
|
||||
format!("not found at {}", v.display())
|
||||
} else {
|
||||
"not set".into()
|
||||
}
|
||||
);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let default_android_home = dirs::home_dir().unwrap().join("Library/Android/sdk");
|
||||
#[cfg(target_os = "linux")]
|
||||
let default_android_home = dirs::home_dir().unwrap().join("Android/Sdk");
|
||||
#[cfg(windows)]
|
||||
let default_android_home = dirs::data_local_dir().unwrap().join("Android/Sdk");
|
||||
|
||||
if default_android_home.exists() {
|
||||
log::info!(
|
||||
"Using installed Android SDK: {}",
|
||||
default_android_home.display()
|
||||
);
|
||||
} else if non_interactive {
|
||||
anyhow::bail!("Android SDK not found. Make sure the SDK and NDK are installed and the ANDROID_HOME and NDK_HOME environment variables are set.");
|
||||
} else {
|
||||
log::error!(
|
||||
"Android SDK not found at {}",
|
||||
default_android_home.display()
|
||||
);
|
||||
|
||||
let extract_path = if create_dir_all(&default_android_home).is_ok() {
|
||||
default_android_home.clone()
|
||||
} else {
|
||||
std::env::current_dir()?
|
||||
};
|
||||
|
||||
let sdk_manager_path = extract_path
|
||||
.join("cmdline-tools/bin/sdkmanager")
|
||||
.with_extension(if cfg!(windows) { "bat" } else { "" });
|
||||
|
||||
let mut granted_permission_to_install = false;
|
||||
|
||||
if !sdk_manager_path.exists() {
|
||||
granted_permission_to_install = crate::helpers::prompts::confirm(
|
||||
"Do you want to install the Android Studio command line tools to setup the Android SDK?",
|
||||
Some(false),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !granted_permission_to_install {
|
||||
anyhow::bail!("Skipping Android Studio command line tools installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android");
|
||||
}
|
||||
|
||||
download_cmdline_tools(&extract_path)?;
|
||||
}
|
||||
|
||||
if !granted_permission_to_install {
|
||||
granted_permission_to_install = crate::helpers::prompts::confirm(
|
||||
"Do you want to install the Android SDK using the command line tools?",
|
||||
Some(false),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !granted_permission_to_install {
|
||||
anyhow::bail!("Skipping Android Studio SDK installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android");
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("Running sdkmanager to install platform-tools, android-{SDK_VERSION} and ndk-{NDK_VERSION} on {}...", default_android_home.display());
|
||||
let status = Command::new(&sdk_manager_path)
|
||||
.arg(format!("--sdk_root={}", default_android_home.display()))
|
||||
.arg("--install")
|
||||
.arg("platform-tools")
|
||||
.arg(format!("platforms;android-{SDK_VERSION}"))
|
||||
.arg(format!("ndk;{NDK_VERSION}"))
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to install Android SDK");
|
||||
}
|
||||
}
|
||||
|
||||
std::env::set_var("ANDROID_HOME", default_android_home);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_ndk(non_interactive: bool) -> Result<()> {
|
||||
// re-evaluate ANDROID_HOME
|
||||
let android_home = std::env::var_os("ANDROID_HOME")
|
||||
.map(PathBuf::from)
|
||||
.or_else(|| std::env::var_os("ANDROID_SDK_ROOT").map(PathBuf::from))
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to locate Android SDK"))?;
|
||||
let mut installed_ndks = read_dir(android_home.join("ndk"))
|
||||
.map(|dir| {
|
||||
dir
|
||||
.into_iter()
|
||||
.flat_map(|e| e.ok().map(|e| e.path()))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
installed_ndks.sort();
|
||||
|
||||
if let Some(ndk) = installed_ndks.last() {
|
||||
log::info!("Using installed NDK: {}", ndk.display());
|
||||
std::env::set_var("NDK_HOME", ndk);
|
||||
} else if non_interactive {
|
||||
anyhow::bail!("Android NDK not found. Make sure the NDK is installed and the NDK_HOME environment variable is set.");
|
||||
} else {
|
||||
let sdk_manager_path = android_home
|
||||
.join("cmdline-tools/bin/sdkmanager")
|
||||
.with_extension(if cfg!(windows) { "bat" } else { "" });
|
||||
|
||||
let mut granted_permission_to_install = false;
|
||||
|
||||
if !sdk_manager_path.exists() {
|
||||
granted_permission_to_install = crate::helpers::prompts::confirm(
|
||||
"Do you want to install the Android Studio command line tools to setup the Android NDK?",
|
||||
Some(false),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !granted_permission_to_install {
|
||||
anyhow::bail!("Skipping Android Studio command line tools installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android");
|
||||
}
|
||||
|
||||
download_cmdline_tools(&android_home)?;
|
||||
}
|
||||
|
||||
if !granted_permission_to_install {
|
||||
granted_permission_to_install = crate::helpers::prompts::confirm(
|
||||
"Do you want to install the Android NDK using the command line tools?",
|
||||
Some(false),
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
if !granted_permission_to_install {
|
||||
anyhow::bail!("Skipping Android Studio NDK installation. Please go through the manual setup process described in the documentation: https://tauri.app/start/prerequisites/#android");
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Running sdkmanager to install ndk-{NDK_VERSION} on {}...",
|
||||
android_home.display()
|
||||
);
|
||||
let status = Command::new(&sdk_manager_path)
|
||||
.arg(format!("--sdk_root={}", android_home.display()))
|
||||
.arg("--install")
|
||||
.arg(format!("ndk;{NDK_VERSION}"))
|
||||
.status()?;
|
||||
|
||||
if !status.success() {
|
||||
anyhow::bail!("Failed to install Android NDK");
|
||||
}
|
||||
|
||||
let ndk_path = android_home.join("ndk").join(NDK_VERSION);
|
||||
log::info!("Installed NDK: {}", ndk_path.display());
|
||||
std::env::set_var("NDK_HOME", ndk_path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_codegen_vars() {
|
||||
for (k, _) in std::env::vars() {
|
||||
if k.starts_with("WRY_") && (k.ends_with("CLASS_EXTENSION") || k.ends_with("CLASS_INIT")) {
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::{
|
||||
ConfigValue, Result,
|
||||
};
|
||||
use cargo_mobile2::{
|
||||
android::env::Env as AndroidEnv,
|
||||
config::app::App,
|
||||
reserved_names::KOTLIN_ONLY_KEYWORDS,
|
||||
util::{
|
||||
@@ -123,33 +122,20 @@ pub fn exec(
|
||||
|
||||
let app = match target {
|
||||
// Generate Android Studio project
|
||||
Target::Android => match AndroidEnv::new() {
|
||||
Ok(_env) => {
|
||||
let (config, metadata) =
|
||||
super::android::get_config(&app, tauri_config_, None, &Default::default());
|
||||
map.insert("android", &config);
|
||||
super::android::project::gen(
|
||||
&config,
|
||||
&metadata,
|
||||
(handlebars, map),
|
||||
wrapper,
|
||||
skip_targets_install,
|
||||
)?;
|
||||
app
|
||||
}
|
||||
Err(err) => {
|
||||
if err.sdk_or_ndk_issue() {
|
||||
Report::action_request(
|
||||
" to initialize Android environment; Android support won't be usable until you fix the issue below and re-run `tauri android init`!",
|
||||
err,
|
||||
)
|
||||
.print(wrapper);
|
||||
app
|
||||
} else {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
},
|
||||
Target::Android => {
|
||||
let _env = super::android::env(non_interactive)?;
|
||||
let (config, metadata) =
|
||||
super::android::get_config(&app, tauri_config_, None, &Default::default());
|
||||
map.insert("android", &config);
|
||||
super::android::project::gen(
|
||||
&config,
|
||||
&metadata,
|
||||
(handlebars, map),
|
||||
wrapper,
|
||||
skip_targets_install,
|
||||
)?;
|
||||
app
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
// Generate Xcode project
|
||||
Target::Ios => {
|
||||
|
||||
Reference in New Issue
Block a user