|
@@ -4,7 +4,7 @@
|
|
|
|
|
|
use cargo_mobile::{
|
|
|
android::{
|
|
|
- adb,
|
|
|
+ aab, adb, apk,
|
|
|
config::{Config as AndroidConfig, Metadata as AndroidMetadata},
|
|
|
device::{Device, RunError},
|
|
|
env::{Env, Error as EnvError},
|
|
@@ -14,7 +14,7 @@ use cargo_mobile::{
|
|
|
device::PromptError,
|
|
|
opts::{NoiseLevel, Profile},
|
|
|
os,
|
|
|
- target::call_for_targets_with_fallback,
|
|
|
+ target::{call_for_targets_with_fallback, TargetTrait},
|
|
|
util::prompt,
|
|
|
};
|
|
|
use clap::{Parser, Subcommand};
|
|
@@ -30,6 +30,8 @@ use crate::{
|
|
|
Result,
|
|
|
};
|
|
|
|
|
|
+use std::{fmt::Write, path::PathBuf};
|
|
|
+
|
|
|
pub(crate) mod project;
|
|
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
@@ -46,8 +48,10 @@ enum Error {
|
|
|
OpenFailed(os::OpenFileError),
|
|
|
#[error("{0}")]
|
|
|
DevFailed(String),
|
|
|
+ #[error("{0}")]
|
|
|
+ BuildFailed(String),
|
|
|
#[error(transparent)]
|
|
|
- BuildFailed(BuildError),
|
|
|
+ AndroidStudioScriptFailed(BuildError),
|
|
|
#[error(transparent)]
|
|
|
RunFailed(RunError),
|
|
|
#[error("{0}")]
|
|
@@ -77,7 +81,8 @@ pub struct AndroidStudioScriptOptions {
|
|
|
long = "target",
|
|
|
multiple_occurrences(true),
|
|
|
multiple_values(true),
|
|
|
- value_parser(clap::builder::PossibleValuesParser::new(["aarch64", "armv7", "i686", "x86_64"]))
|
|
|
+ default_value = Target::DEFAULT_KEY,
|
|
|
+ value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
|
|
)]
|
|
|
targets: Option<Vec<String>>,
|
|
|
/// Builds with the release flag
|
|
@@ -120,12 +125,59 @@ impl From<DevOptions> for crate::dev::Options {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, Clone, Parser)]
|
|
|
+#[clap(about = "Android build")]
|
|
|
+pub struct BuildOptions {
|
|
|
+ /// Builds with the debug flag
|
|
|
+ #[clap(short, long)]
|
|
|
+ pub debug: bool,
|
|
|
+ /// Which targets to build (all by default).
|
|
|
+ #[clap(
|
|
|
+ short,
|
|
|
+ long = "target",
|
|
|
+ multiple_occurrences(true),
|
|
|
+ multiple_values(true),
|
|
|
+ value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
|
|
+ )]
|
|
|
+ targets: Option<Vec<String>>,
|
|
|
+ /// List of cargo features to activate
|
|
|
+ #[clap(short, long, multiple_occurrences(true), multiple_values(true))]
|
|
|
+ pub features: Option<Vec<String>>,
|
|
|
+ /// JSON string or path to JSON file to merge with tauri.conf.json
|
|
|
+ #[clap(short, long)]
|
|
|
+ pub config: Option<String>,
|
|
|
+ /// Whether to split the APKs and AABs per ABIs.
|
|
|
+ #[clap(long)]
|
|
|
+ pub split_per_abi: bool,
|
|
|
+ /// Build APKs.
|
|
|
+ #[clap(long)]
|
|
|
+ pub apk: bool,
|
|
|
+ /// Build AABs.
|
|
|
+ #[clap(long)]
|
|
|
+ pub aab: bool,
|
|
|
+}
|
|
|
+
|
|
|
+impl From<BuildOptions> for crate::build::Options {
|
|
|
+ fn from(options: BuildOptions) -> Self {
|
|
|
+ Self {
|
|
|
+ runner: None,
|
|
|
+ debug: options.debug,
|
|
|
+ target: None,
|
|
|
+ features: options.features,
|
|
|
+ bundles: None,
|
|
|
+ config: options.config,
|
|
|
+ args: Vec::new(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#[derive(Subcommand)]
|
|
|
enum Commands {
|
|
|
Init(InitOptions),
|
|
|
/// Open project in Android Studio
|
|
|
Open,
|
|
|
Dev(DevOptions),
|
|
|
+ Build(BuildOptions),
|
|
|
#[clap(hide(true))]
|
|
|
AndroidStudioScript(AndroidStudioScriptOptions),
|
|
|
}
|
|
@@ -134,8 +186,9 @@ pub fn command(cli: Cli) -> Result<()> {
|
|
|
match cli.command {
|
|
|
Commands::Init(options) => init_command(options, MobileTarget::Android)?,
|
|
|
Commands::Open => open()?,
|
|
|
- Commands::AndroidStudioScript(options) => android_studio_script(options)?,
|
|
|
Commands::Dev(options) => dev(options)?,
|
|
|
+ Commands::Build(options) => build(options)?,
|
|
|
+ Commands::AndroidStudioScript(options) => android_studio_script(options)?,
|
|
|
}
|
|
|
|
|
|
Ok(())
|
|
@@ -182,8 +235,134 @@ fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<adb::device
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>, Error> {
|
|
|
+ if targets.is_empty() {
|
|
|
+ Ok(Target::all().iter().map(|t| t.1).collect())
|
|
|
+ } else {
|
|
|
+ let mut outs = Vec::new();
|
|
|
+
|
|
|
+ let possible_targets = Target::all()
|
|
|
+ .keys()
|
|
|
+ .map(|key| key.to_string())
|
|
|
+ .collect::<Vec<String>>()
|
|
|
+ .join(",");
|
|
|
+
|
|
|
+ for t in targets {
|
|
|
+ let target = Target::for_name(&t).ok_or_else(|| {
|
|
|
+ Error::TargetInvalid(format!(
|
|
|
+ "Target {} is invalid; the possible targets are {}",
|
|
|
+ t, possible_targets
|
|
|
+ ))
|
|
|
+ })?;
|
|
|
+ outs.push(target);
|
|
|
+ }
|
|
|
+ Ok(outs)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn build(options: BuildOptions) -> Result<()> {
|
|
|
+ with_config(|root_conf, config, _metadata| {
|
|
|
+ ensure_init(config.project_dir(), MobileTarget::Android)
|
|
|
+ .map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
|
|
+
|
|
|
+ let env = Env::new().map_err(Error::EnvInitFailed)?;
|
|
|
+ super::init::init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
|
|
|
+
|
|
|
+ run_build(options, config, env).map_err(|e| Error::BuildFailed(e.to_string()))
|
|
|
+ })
|
|
|
+ .map_err(Into::into)
|
|
|
+}
|
|
|
+
|
|
|
+fn run_build(mut options: BuildOptions, config: &AndroidConfig, env: Env) -> Result<()> {
|
|
|
+ let profile = if options.debug {
|
|
|
+ Profile::Debug
|
|
|
+ } else {
|
|
|
+ Profile::Release
|
|
|
+ };
|
|
|
+
|
|
|
+ if !(options.apk || options.aab) {
|
|
|
+ // if the user didn't specify the format to build, we'll do both
|
|
|
+ options.apk = true;
|
|
|
+ options.aab = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ let bundle_identifier = {
|
|
|
+ let tauri_config = get_tauri_config(None)?;
|
|
|
+ let tauri_config_guard = tauri_config.lock().unwrap();
|
|
|
+ let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
|
|
+ tauri_config_.tauri.bundle.identifier.clone()
|
|
|
+ };
|
|
|
+
|
|
|
+ let mut build_options = options.clone().into();
|
|
|
+ let interface = crate::build::setup(&mut build_options)?;
|
|
|
+
|
|
|
+ let app_settings = interface.app_settings();
|
|
|
+ let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
|
|
+ debug: build_options.debug,
|
|
|
+ ..Default::default()
|
|
|
+ })?;
|
|
|
+ let out_dir = bin_path.parent().unwrap();
|
|
|
+ let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
|
|
|
+
|
|
|
+ let cli_options = CliOptions {
|
|
|
+ features: build_options.features.clone(),
|
|
|
+ args: build_options.args.clone(),
|
|
|
+ vars: Default::default(),
|
|
|
+ };
|
|
|
+ write_options(cli_options, &bundle_identifier, MobileTarget::Android)?;
|
|
|
+
|
|
|
+ options
|
|
|
+ .features
|
|
|
+ .get_or_insert(Vec::new())
|
|
|
+ .push("custom-protocol".into());
|
|
|
+
|
|
|
+ let apk_outputs = if options.apk {
|
|
|
+ apk::build(
|
|
|
+ config,
|
|
|
+ &env,
|
|
|
+ NoiseLevel::Polite,
|
|
|
+ profile,
|
|
|
+ get_targets_or_all(Vec::new())?,
|
|
|
+ options.split_per_abi,
|
|
|
+ )?
|
|
|
+ } else {
|
|
|
+ Vec::new()
|
|
|
+ };
|
|
|
+
|
|
|
+ let aab_outputs = if options.aab {
|
|
|
+ aab::build(
|
|
|
+ config,
|
|
|
+ &env,
|
|
|
+ NoiseLevel::Polite,
|
|
|
+ profile,
|
|
|
+ get_targets_or_all(Vec::new())?,
|
|
|
+ options.split_per_abi,
|
|
|
+ )?
|
|
|
+ } else {
|
|
|
+ Vec::new()
|
|
|
+ };
|
|
|
+
|
|
|
+ log_finished(apk_outputs, "APK");
|
|
|
+ log_finished(aab_outputs, "AAB");
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+fn log_finished(outputs: Vec<PathBuf>, kind: &str) {
|
|
|
+ if !outputs.is_empty() {
|
|
|
+ let mut printable_paths = String::new();
|
|
|
+ for path in &outputs {
|
|
|
+ writeln!(printable_paths, " {}", path.display()).unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ log::info!(action = "Finished"; "{} {}{} at:\n{}", outputs.len(), kind, if outputs.len() == 1 { "" } else { "s" }, printable_paths);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
fn dev(options: DevOptions) -> Result<()> {
|
|
|
with_config(|_, config, _metadata| {
|
|
|
+ ensure_init(config.project_dir(), MobileTarget::Android)
|
|
|
+ .map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
|
|
run_dev(options, config).map_err(|e| Error::DevFailed(e.to_string()))
|
|
|
})
|
|
|
.map_err(Into::into)
|
|
@@ -194,8 +373,7 @@ fn run_dev(options: DevOptions, config: &AndroidConfig) -> Result<()> {
|
|
|
let mut interface = crate::dev::setup(&mut dev_options)?;
|
|
|
|
|
|
let bundle_identifier = {
|
|
|
- let tauri_config =
|
|
|
- get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
|
|
|
+ let tauri_config = get_tauri_config(None)?;
|
|
|
let tauri_config_guard = tauri_config.lock().unwrap();
|
|
|
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
|
|
tauri_config_.tauri.bundle.identifier.clone()
|
|
@@ -319,7 +497,7 @@ fn android_studio_script(options: AndroidStudioScriptOptions) -> Result<()> {
|
|
|
|target: &Target| {
|
|
|
target
|
|
|
.build(config, metadata, &env, NoiseLevel::Polite, true, profile)
|
|
|
- .map_err(Error::BuildFailed)
|
|
|
+ .map_err(Error::AndroidStudioScriptFailed)
|
|
|
},
|
|
|
)
|
|
|
.map_err(|e| Error::TargetInvalid(e.to_string()))?
|