mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-14 13:21:01 +03:00
refactor(cli): move mobile commands to their own module (#5005)
This commit is contained in:
parent
403859d47e
commit
e56a9dd729
@ -4,7 +4,7 @@
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
aab, adb, apk,
|
||||
adb,
|
||||
config::{Config as AndroidConfig, Metadata as AndroidMetadata},
|
||||
device::{Device, RunError},
|
||||
env::{Env, Error as EnvError},
|
||||
@ -12,26 +12,22 @@ use cargo_mobile::{
|
||||
},
|
||||
config::Config,
|
||||
device::PromptError,
|
||||
opts::{NoiseLevel, Profile},
|
||||
os,
|
||||
target::{call_for_targets_with_fallback, TargetTrait},
|
||||
util::prompt,
|
||||
};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use super::{
|
||||
ensure_init, get_config,
|
||||
init::{command as init_command, Options as InitOptions},
|
||||
write_options, CliOptions, DevChild, Target as MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, DevProcess, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
Result,
|
||||
init::{command as init_command, init_dot_cargo, Options as InitOptions},
|
||||
log_finished, Target as MobileTarget,
|
||||
};
|
||||
use crate::{helpers::config::get as get_tauri_config, Result};
|
||||
|
||||
use std::{fmt::Write, path::PathBuf};
|
||||
|
||||
mod android_studio_script;
|
||||
mod build;
|
||||
mod dev;
|
||||
mod open;
|
||||
pub(crate) mod project;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@ -73,122 +69,24 @@ pub struct Cli {
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct AndroidStudioScriptOptions {
|
||||
/// Targets to build.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
multiple_occurrences(true),
|
||||
multiple_values(true),
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android dev")]
|
||||
pub struct DevOptions {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, multiple_occurrences(true), multiple_values(true))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Android Studio instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<DevOptions> for crate::dev::Options {
|
||||
fn from(options: DevOptions) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
target: None,
|
||||
features: options.features,
|
||||
exit_on_panic: options.exit_on_panic,
|
||||
config: options.config,
|
||||
release_mode: false,
|
||||
args: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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()))
|
||||
)]
|
||||
pub 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),
|
||||
Dev(dev::Options),
|
||||
Build(build::Options),
|
||||
#[clap(hide(true))]
|
||||
AndroidStudioScript(AndroidStudioScriptOptions),
|
||||
AndroidStudioScript(android_studio_script::Options),
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(options, MobileTarget::Android)?,
|
||||
Commands::Open => open()?,
|
||||
Commands::Dev(options) => dev(options)?,
|
||||
Commands::Build(options) => build(options)?,
|
||||
Commands::AndroidStudioScript(options) => android_studio_script(options)?,
|
||||
Commands::Open => open::command()?,
|
||||
Commands::Dev(options) => dev::command(options)?,
|
||||
Commands::Build(options) => build::command(options)?,
|
||||
Commands::AndroidStudioScript(options) => android_studio_script::command(options)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -235,280 +133,6 @@ 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
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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,
|
||||
noise_level,
|
||||
profile,
|
||||
get_targets_or_all(Vec::new())?,
|
||||
options.split_per_abi,
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let aab_outputs = if options.aab {
|
||||
aab::build(
|
||||
config,
|
||||
&env,
|
||||
noise_level,
|
||||
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(|root_conf, config, metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
run_dev(options, root_conf, config, metadata).map_err(|e| Error::DevFailed(e.to_string()))
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_dev(
|
||||
options: DevOptions,
|
||||
root_conf: &Config,
|
||||
config: &AndroidConfig,
|
||||
metadata: &AndroidMetadata,
|
||||
) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
let mut interface = crate::dev::setup(&mut dev_options)?;
|
||||
|
||||
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 app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Android)?;
|
||||
|
||||
if open {
|
||||
open_dev(config)
|
||||
} else {
|
||||
match run(options, root_conf, config, metadata) {
|
||||
Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
|
||||
Err(Error::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_dev(config)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn open_dev(config: &AndroidConfig) -> ! {
|
||||
log::info!("Opening Android Studio");
|
||||
if let Err(e) = os::open_file_with("Android Studio", config.project_dir()) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
|
||||
fn open() -> Result<()> {
|
||||
with_config(|_, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
os::open_file_with("Android Studio", config.project_dir()).map_err(Error::OpenFailed)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run(
|
||||
options: MobileOptions,
|
||||
root_conf: &Config,
|
||||
config: &AndroidConfig,
|
||||
metadata: &AndroidMetadata,
|
||||
) -> Result<DevChild, Error> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
let build_app_bundle = metadata.asset_packs().is_some();
|
||||
|
||||
let env = Env::new().map_err(Error::EnvInitFailed)?;
|
||||
super::init::init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
|
||||
|
||||
device_prompt(&env)
|
||||
.map_err(Error::FailedToPromptForDevice)?
|
||||
.run(
|
||||
config,
|
||||
&env,
|
||||
noise_level,
|
||||
profile,
|
||||
None,
|
||||
build_app_bundle,
|
||||
false,
|
||||
".MainActivity".into(),
|
||||
)
|
||||
.map(|c| DevChild(Some(c)))
|
||||
.map_err(Error::RunFailed)
|
||||
}
|
||||
|
||||
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
|
||||
device_prompt(env).map(|device| device.target()).ok()
|
||||
}
|
||||
|
||||
fn android_studio_script(options: AndroidStudioScriptOptions) -> Result<()> {
|
||||
let profile = if options.release {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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)?;
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.unwrap_or_default().iter(),
|
||||
&detect_target_ok,
|
||||
&env,
|
||||
|target: &Target| {
|
||||
target
|
||||
.build(config, metadata, &env, noise_level, true, profile)
|
||||
.map_err(Error::AndroidStudioScriptFailed)
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::TargetInvalid(e.to_string()))?
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
56
tooling/cli/src/mobile/android/android_studio_script.rs
Normal file
56
tooling/cli/src/mobile/android/android_studio_script.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use super::{detect_target_ok, ensure_init, init_dot_cargo, with_config, Error, MobileTarget};
|
||||
use crate::Result;
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{env::Env, target::Target},
|
||||
opts::{NoiseLevel, Profile},
|
||||
target::{call_for_targets_with_fallback, TargetTrait},
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Options {
|
||||
/// Targets to build.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
multiple_occurrences(true),
|
||||
multiple_values(true),
|
||||
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 profile = if options.release {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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)?;
|
||||
init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.unwrap_or_default().iter(),
|
||||
&detect_target_ok,
|
||||
&env,
|
||||
|target: &Target| {
|
||||
target
|
||||
.build(config, metadata, &env, noise_level, true, profile)
|
||||
.map_err(Error::AndroidStudioScriptFailed)
|
||||
},
|
||||
)
|
||||
.map_err(|e| Error::TargetInvalid(e.to_string()))?
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
174
tooling/cli/src/mobile/android/build.rs
Normal file
174
tooling/cli/src/mobile/android/build.rs
Normal file
@ -0,0 +1,174 @@
|
||||
use super::{ensure_init, init_dot_cargo, log_finished, with_config, Error, MobileTarget};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, Interface, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions},
|
||||
Result,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{aab, apk, config::Config as AndroidConfig, env::Env, target::Target},
|
||||
opts::{NoiseLevel, Profile},
|
||||
target::TargetTrait,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android build")]
|
||||
pub struct Options {
|
||||
/// 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()))
|
||||
)]
|
||||
pub 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<Options> for crate::build::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
debug: options.debug,
|
||||
target: None,
|
||||
features: options.features,
|
||||
bundles: None,
|
||||
config: options.config,
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> 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)?;
|
||||
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: Options, config: &AndroidConfig, env: Env) -> Result<()> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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,
|
||||
noise_level,
|
||||
profile,
|
||||
get_targets_or_all(Vec::new())?,
|
||||
options.split_per_abi,
|
||||
)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let aab_outputs = if options.aab {
|
||||
aab::build(
|
||||
config,
|
||||
&env,
|
||||
noise_level,
|
||||
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 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)
|
||||
}
|
||||
}
|
163
tooling/cli/src/mobile/android/dev.rs
Normal file
163
tooling/cli/src/mobile/android/dev.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use super::{device_prompt, ensure_init, init_dot_cargo, with_config, Error, MobileTarget};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions, DevChild, DevProcess},
|
||||
Result,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
android::{
|
||||
config::{Config as AndroidConfig, Metadata as AndroidMetadata},
|
||||
env::Env,
|
||||
},
|
||||
config::Config,
|
||||
opts::{NoiseLevel, Profile},
|
||||
os,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android dev")]
|
||||
pub struct Options {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, multiple_occurrences(true), multiple_values(true))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Android Studio instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::dev::Options {
|
||||
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: false,
|
||||
args: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
with_config(|root_conf, config, metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
run_dev(options, root_conf, config, metadata).map_err(|e| Error::DevFailed(e.to_string()))
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_dev(
|
||||
options: Options,
|
||||
root_conf: &Config,
|
||||
config: &AndroidConfig,
|
||||
metadata: &AndroidMetadata,
|
||||
) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
let mut interface = crate::dev::setup(&mut dev_options)?;
|
||||
|
||||
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 app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Android)?;
|
||||
|
||||
if open {
|
||||
open_dev(config)
|
||||
} else {
|
||||
match run(options, root_conf, config, metadata) {
|
||||
Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
|
||||
Err(Error::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_dev(config)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn open_dev(config: &AndroidConfig) -> ! {
|
||||
log::info!("Opening Android Studio");
|
||||
if let Err(e) = os::open_file_with("Android Studio", config.project_dir()) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
options: MobileOptions,
|
||||
root_conf: &Config,
|
||||
config: &AndroidConfig,
|
||||
metadata: &AndroidMetadata,
|
||||
) -> Result<DevChild, Error> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
let build_app_bundle = metadata.asset_packs().is_some();
|
||||
|
||||
let env = Env::new().map_err(Error::EnvInitFailed)?;
|
||||
init_dot_cargo(root_conf, Some(&env)).map_err(Error::InitDotCargo)?;
|
||||
|
||||
device_prompt(&env)
|
||||
.map_err(Error::FailedToPromptForDevice)?
|
||||
.run(
|
||||
config,
|
||||
&env,
|
||||
noise_level,
|
||||
profile,
|
||||
None,
|
||||
build_app_bundle,
|
||||
false,
|
||||
".MainActivity".into(),
|
||||
)
|
||||
.map(|c| DevChild(Some(c)))
|
||||
.map_err(Error::RunFailed)
|
||||
}
|
12
tooling/cli/src/mobile/android/open.rs
Normal file
12
tooling/cli/src/mobile/android/open.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use super::{ensure_init, with_config, Error, MobileTarget};
|
||||
use crate::Result;
|
||||
use cargo_mobile::os;
|
||||
|
||||
pub fn command() -> Result<()> {
|
||||
with_config(|_, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Android)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
os::open_file_with("Android Studio", config.project_dir()).map_err(Error::OpenFailed)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
@ -12,28 +12,25 @@ use cargo_mobile::{
|
||||
config::Config,
|
||||
device::PromptError,
|
||||
env::{Env, Error as EnvError},
|
||||
opts::{NoiseLevel, Profile},
|
||||
os,
|
||||
target::{call_for_targets_with_fallback, TargetInvalid, TargetTrait},
|
||||
util,
|
||||
os, util,
|
||||
util::prompt,
|
||||
};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use super::{
|
||||
ensure_init, env_vars, get_config,
|
||||
init::{command as init_command, Options as InitOptions},
|
||||
write_options, CliOptions, DevChild, Target as MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, DevProcess, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
Result,
|
||||
init::{command as init_command, init_dot_cargo, Options as InitOptions},
|
||||
log_finished, Target as MobileTarget,
|
||||
};
|
||||
use crate::{helpers::config::get as get_tauri_config, Result};
|
||||
|
||||
use std::{collections::HashMap, ffi::OsStr, fmt::Write, fs, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
|
||||
mod build;
|
||||
mod dev;
|
||||
mod open;
|
||||
pub(crate) mod project;
|
||||
mod xcode_script;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum Error {
|
||||
@ -84,121 +81,23 @@ pub struct Cli {
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct XcodeScriptOptions {
|
||||
/// Value of `PLATFORM_DISPLAY_NAME` env var
|
||||
#[clap(long)]
|
||||
platform: String,
|
||||
/// Value of `SDKROOT` env var
|
||||
#[clap(long)]
|
||||
sdk_root: PathBuf,
|
||||
/// Value of `CONFIGURATION` env var
|
||||
#[clap(long)]
|
||||
configuration: String,
|
||||
/// Value of `FORCE_COLOR` env var
|
||||
#[clap(long)]
|
||||
force_color: bool,
|
||||
/// Value of `ARCHS` env var
|
||||
#[clap(index = 1, required = true)]
|
||||
arches: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "iOS dev")]
|
||||
pub struct DevOptions {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, multiple_occurrences(true), multiple_values(true))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Run the code in release mode
|
||||
#[clap(long = "release")]
|
||||
pub release_mode: bool,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Xcode instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<DevOptions> for crate::dev::Options {
|
||||
fn from(options: DevOptions) -> 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: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
multiple_occurrences(true),
|
||||
multiple_values(true),
|
||||
default_value = Target::DEFAULT_KEY,
|
||||
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
||||
)]
|
||||
pub targets: 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>,
|
||||
/// Build number to append to the app version.
|
||||
#[clap(long)]
|
||||
pub build_number: Option<u32>,
|
||||
}
|
||||
|
||||
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,
|
||||
Dev(DevOptions),
|
||||
Build(BuildOptions),
|
||||
Dev(dev::Options),
|
||||
Build(build::Options),
|
||||
#[clap(hide(true))]
|
||||
XcodeScript(XcodeScriptOptions),
|
||||
XcodeScript(xcode_script::Options),
|
||||
}
|
||||
|
||||
pub fn command(cli: Cli) -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Init(options) => init_command(options, MobileTarget::Ios)?,
|
||||
Commands::Open => open()?,
|
||||
Commands::Dev(options) => dev(options)?,
|
||||
Commands::Build(options) => build(options)?,
|
||||
Commands::XcodeScript(options) => xcode_script(options)?,
|
||||
Commands::Open => open::command()?,
|
||||
Commands::Dev(options) => dev::command(options)?,
|
||||
Commands::Build(options) => build::command(options)?,
|
||||
Commands::XcodeScript(options) => xcode_script::command(options)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -255,313 +154,3 @@ fn device_prompt<'a>(env: &'_ Env) -> Result<Device<'a>, PromptError<ios_deploy:
|
||||
fn detect_target_ok<'a>(env: &Env) -> Option<&'a Target<'a>> {
|
||||
device_prompt(env).map(|device| device.target()).ok()
|
||||
}
|
||||
|
||||
fn build(options: BuildOptions) -> Result<()> {
|
||||
with_config(|root_conf, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
|
||||
let env = env()?;
|
||||
super::init::init_dot_cargo(root_conf, None).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: &AppleConfig, env: Env) -> Result<()> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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("ios"), "iOS")?;
|
||||
|
||||
let cli_options = CliOptions {
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Ios)?;
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
|
||||
let mut out_files = Vec::new();
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.iter(),
|
||||
&detect_target_ok,
|
||||
&env,
|
||||
|target: &Target| {
|
||||
let mut app_version = config.bundle_version().clone();
|
||||
if let Some(build_number) = options.build_number {
|
||||
app_version.push_extra(build_number);
|
||||
}
|
||||
|
||||
target.build(config, &env, noise_level, profile)?;
|
||||
target.archive(config, &env, noise_level, profile, Some(app_version))?;
|
||||
target.export(config, &env, noise_level)?;
|
||||
|
||||
if let Ok(ipa_path) = config.ipa_path() {
|
||||
let out_dir = config.export_dir().join(target.arch);
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
let path = out_dir.join(ipa_path.file_name().unwrap());
|
||||
fs::rename(&ipa_path, &path)?;
|
||||
out_files.push(path);
|
||||
}
|
||||
|
||||
anyhow::Result::Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(|e: TargetInvalid| Error::TargetInvalid(e.to_string()))?
|
||||
.map_err(|e: anyhow::Error| e)?;
|
||||
|
||||
log_finished(out_files, "IPA");
|
||||
|
||||
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(|root_conf, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
run_dev(options, root_conf, config).map_err(|e| Error::DevFailed(e.to_string()))
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_dev(options: DevOptions, root_conf: &Config, config: &AppleConfig) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
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_guard = tauri_config.lock().unwrap();
|
||||
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
||||
tauri_config_.tauri.bundle.identifier.clone()
|
||||
};
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Ios)?;
|
||||
if open {
|
||||
open_dev(config)
|
||||
} else {
|
||||
match run(options, root_conf, config) {
|
||||
Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
|
||||
Err(Error::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_dev(config)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn open_dev(config: &AppleConfig) -> ! {
|
||||
log::info!("Opening Xcode");
|
||||
if let Err(e) = os::open_file_with("Xcode", config.project_dir()) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
|
||||
fn open() -> Result<()> {
|
||||
with_config(|_, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
os::open_file_with("Xcode", config.project_dir()).map_err(Error::OpenFailed)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run(
|
||||
options: MobileOptions,
|
||||
root_conf: &Config,
|
||||
config: &AppleConfig,
|
||||
) -> Result<DevChild, Error> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
let env = env()?;
|
||||
super::init::init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
|
||||
|
||||
device_prompt(&env)
|
||||
.map_err(Error::FailedToPromptForDevice)?
|
||||
.run(config, &env, noise_level, false, profile)
|
||||
.map(|c| DevChild(Some(c)))
|
||||
.map_err(Error::RunFailed)
|
||||
}
|
||||
|
||||
fn xcode_script(options: XcodeScriptOptions) -> Result<()> {
|
||||
fn macos_from_platform(platform: &str) -> bool {
|
||||
platform == "macOS"
|
||||
}
|
||||
|
||||
fn profile_from_configuration(configuration: &str) -> Profile {
|
||||
if configuration == "release" {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
}
|
||||
}
|
||||
|
||||
let profile = profile_from_configuration(&options.configuration);
|
||||
let macos = macos_from_platform(&options.platform);
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
with_config(|root_conf, config, metadata| {
|
||||
let env = env()?;
|
||||
super::init::init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
|
||||
// The `PATH` env var Xcode gives us is missing any additions
|
||||
// made by the user's profile, so we'll manually add cargo's
|
||||
// `PATH`.
|
||||
let env = env.prepend_to_path(
|
||||
util::home_dir()
|
||||
.map_err(Error::NoHomeDir)?
|
||||
.join(".cargo/bin"),
|
||||
);
|
||||
|
||||
if !options.sdk_root.is_dir() {
|
||||
return Err(Error::SdkRootInvalid {
|
||||
sdk_root: options.sdk_root,
|
||||
});
|
||||
}
|
||||
let include_dir = options.sdk_root.join("usr/include");
|
||||
if !include_dir.is_dir() {
|
||||
return Err(Error::IncludeDirInvalid { include_dir });
|
||||
}
|
||||
|
||||
let mut host_env = HashMap::<&str, &OsStr>::new();
|
||||
|
||||
// Host flags that are used by build scripts
|
||||
let (macos_isysroot, library_path) = {
|
||||
let macos_sdk_root = options
|
||||
.sdk_root
|
||||
.join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk");
|
||||
if !macos_sdk_root.is_dir() {
|
||||
return Err(Error::MacosSdkRootInvalid { macos_sdk_root });
|
||||
}
|
||||
(
|
||||
format!("-isysroot {}", macos_sdk_root.display()),
|
||||
format!("{}/usr/lib", macos_sdk_root.display()),
|
||||
)
|
||||
};
|
||||
host_env.insert("MAC_FLAGS", macos_isysroot.as_ref());
|
||||
host_env.insert("CFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
host_env.insert("CXXFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
|
||||
host_env.insert(
|
||||
"OBJC_INCLUDE_PATH_x86_64_apple_darwin",
|
||||
include_dir.as_os_str(),
|
||||
);
|
||||
|
||||
host_env.insert("RUST_BACKTRACE", "1".as_ref());
|
||||
|
||||
let macos_target = Target::macos();
|
||||
|
||||
let isysroot = format!("-isysroot {}", options.sdk_root.display());
|
||||
|
||||
for arch in options.arches {
|
||||
// Set target-specific flags
|
||||
let triple = match arch.as_str() {
|
||||
"arm64" => "aarch64_apple_ios",
|
||||
"x86_64" => "x86_64_apple_ios",
|
||||
_ => return Err(Error::ArchInvalid { arch }),
|
||||
};
|
||||
let cflags = format!("CFLAGS_{}", triple);
|
||||
let cxxflags = format!("CFLAGS_{}", triple);
|
||||
let objc_include_path = format!("OBJC_INCLUDE_PATH_{}", triple);
|
||||
let mut target_env = host_env.clone();
|
||||
target_env.insert(cflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(cxxflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(objc_include_path.as_ref(), include_dir.as_ref());
|
||||
// Prevents linker errors in build scripts and proc macros:
|
||||
// https://github.com/signalapp/libsignal-client/commit/02899cac643a14b2ced7c058cc15a836a2165b6d
|
||||
target_env.insert("LIBRARY_PATH", library_path.as_ref());
|
||||
|
||||
let target = if macos {
|
||||
&macos_target
|
||||
} else {
|
||||
Target::for_arch(&arch).ok_or_else(|| Error::ArchInvalid {
|
||||
arch: arch.to_owned(),
|
||||
})?
|
||||
};
|
||||
target
|
||||
.compile_lib(
|
||||
config,
|
||||
metadata,
|
||||
noise_level,
|
||||
true,
|
||||
profile,
|
||||
&env,
|
||||
target_env,
|
||||
)
|
||||
.map_err(Error::CompileLibFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
147
tooling/cli/src/mobile/ios/build.rs
Normal file
147
tooling/cli/src/mobile/ios/build.rs
Normal file
@ -0,0 +1,147 @@
|
||||
use super::{
|
||||
detect_target_ok, ensure_init, env, init_dot_cargo, log_finished, with_config, Error,
|
||||
MobileTarget,
|
||||
};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, Interface, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions},
|
||||
Result,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::{config::Config as AppleConfig, target::Target},
|
||||
env::Env,
|
||||
opts::{NoiseLevel, Profile},
|
||||
target::{call_for_targets_with_fallback, TargetInvalid, TargetTrait},
|
||||
};
|
||||
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "Android build")]
|
||||
pub struct Options {
|
||||
/// Builds with the debug flag
|
||||
#[clap(short, long)]
|
||||
pub debug: bool,
|
||||
/// Which targets to build.
|
||||
#[clap(
|
||||
short,
|
||||
long = "target",
|
||||
multiple_occurrences(true),
|
||||
multiple_values(true),
|
||||
default_value = Target::DEFAULT_KEY,
|
||||
value_parser(clap::builder::PossibleValuesParser::new(Target::name_list()))
|
||||
)]
|
||||
pub targets: 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>,
|
||||
/// Build number to append to the app version.
|
||||
#[clap(long)]
|
||||
pub build_number: Option<u32>,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::build::Options {
|
||||
fn from(options: Options) -> Self {
|
||||
Self {
|
||||
runner: None,
|
||||
debug: options.debug,
|
||||
target: None,
|
||||
features: options.features,
|
||||
bundles: None,
|
||||
config: options.config,
|
||||
args: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
with_config(|root_conf, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
|
||||
let env = env()?;
|
||||
init_dot_cargo(root_conf, None).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: Options, config: &AppleConfig, env: Env) -> Result<()> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
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("ios"), "iOS")?;
|
||||
|
||||
let cli_options = CliOptions {
|
||||
features: build_options.features.clone(),
|
||||
args: build_options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Ios)?;
|
||||
|
||||
options
|
||||
.features
|
||||
.get_or_insert(Vec::new())
|
||||
.push("custom-protocol".into());
|
||||
|
||||
let mut out_files = Vec::new();
|
||||
|
||||
call_for_targets_with_fallback(
|
||||
options.targets.iter(),
|
||||
&detect_target_ok,
|
||||
&env,
|
||||
|target: &Target| {
|
||||
let mut app_version = config.bundle_version().clone();
|
||||
if let Some(build_number) = options.build_number {
|
||||
app_version.push_extra(build_number);
|
||||
}
|
||||
|
||||
target.build(config, &env, noise_level, profile)?;
|
||||
target.archive(config, &env, noise_level, profile, Some(app_version))?;
|
||||
target.export(config, &env, noise_level)?;
|
||||
|
||||
if let Ok(ipa_path) = config.ipa_path() {
|
||||
let out_dir = config.export_dir().join(target.arch);
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
let path = out_dir.join(ipa_path.file_name().unwrap());
|
||||
fs::rename(&ipa_path, &path)?;
|
||||
out_files.push(path);
|
||||
}
|
||||
|
||||
anyhow::Result::Ok(())
|
||||
},
|
||||
)
|
||||
.map_err(|e: TargetInvalid| Error::TargetInvalid(e.to_string()))?
|
||||
.map_err(|e: anyhow::Error| e)?;
|
||||
|
||||
log_finished(out_files, "IPA");
|
||||
|
||||
Ok(())
|
||||
}
|
146
tooling/cli/src/mobile/ios/dev.rs
Normal file
146
tooling/cli/src/mobile/ios/dev.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use super::{device_prompt, ensure_init, env, init_dot_cargo, with_config, Error, MobileTarget};
|
||||
use crate::{
|
||||
helpers::{config::get as get_tauri_config, flock},
|
||||
interface::{AppSettings, Interface, MobileOptions, Options as InterfaceOptions},
|
||||
mobile::{write_options, CliOptions, DevChild, DevProcess},
|
||||
Result,
|
||||
};
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::config::Config as AppleConfig,
|
||||
config::Config,
|
||||
opts::{NoiseLevel, Profile},
|
||||
os,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[clap(about = "iOS dev")]
|
||||
pub struct Options {
|
||||
/// List of cargo features to activate
|
||||
#[clap(short, long, multiple_occurrences(true), multiple_values(true))]
|
||||
pub features: Option<Vec<String>>,
|
||||
/// Exit on panic
|
||||
#[clap(short, long)]
|
||||
exit_on_panic: bool,
|
||||
/// JSON string or path to JSON file to merge with tauri.conf.json
|
||||
#[clap(short, long)]
|
||||
pub config: Option<String>,
|
||||
/// Run the code in release mode
|
||||
#[clap(long = "release")]
|
||||
pub release_mode: bool,
|
||||
/// Disable the file watcher
|
||||
#[clap(long)]
|
||||
pub no_watch: bool,
|
||||
/// Open Xcode instead of trying to run on a connected device
|
||||
#[clap(short, long)]
|
||||
pub open: bool,
|
||||
}
|
||||
|
||||
impl From<Options> for crate::dev::Options {
|
||||
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: Vec::new(),
|
||||
no_watch: options.no_watch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
with_config(|root_conf, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
run_dev(options, root_conf, config).map_err(|e| Error::DevFailed(e.to_string()))
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn run_dev(options: Options, root_conf: &Config, config: &AppleConfig) -> Result<()> {
|
||||
let mut dev_options = options.clone().into();
|
||||
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_guard = tauri_config.lock().unwrap();
|
||||
let tauri_config_ = tauri_config_guard.as_ref().unwrap();
|
||||
tauri_config_.tauri.bundle.identifier.clone()
|
||||
};
|
||||
|
||||
let app_settings = interface.app_settings();
|
||||
let bin_path = app_settings.app_binary_path(&InterfaceOptions {
|
||||
debug: !dev_options.release_mode,
|
||||
..Default::default()
|
||||
})?;
|
||||
let out_dir = bin_path.parent().unwrap();
|
||||
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
|
||||
|
||||
let open = options.open;
|
||||
interface.mobile_dev(
|
||||
MobileOptions {
|
||||
debug: true,
|
||||
features: options.features,
|
||||
args: Vec::new(),
|
||||
config: options.config,
|
||||
no_watch: options.no_watch,
|
||||
},
|
||||
|options| {
|
||||
let cli_options = CliOptions {
|
||||
features: options.features.clone(),
|
||||
args: options.args.clone(),
|
||||
vars: Default::default(),
|
||||
};
|
||||
write_options(cli_options, &bundle_identifier, MobileTarget::Ios)?;
|
||||
if open {
|
||||
open_dev(config)
|
||||
} else {
|
||||
match run(options, root_conf, config) {
|
||||
Ok(c) => Ok(Box::new(c) as Box<dyn DevProcess>),
|
||||
Err(Error::FailedToPromptForDevice(e)) => {
|
||||
log::error!("{}", e);
|
||||
open_dev(config)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn open_dev(config: &AppleConfig) -> ! {
|
||||
log::info!("Opening Xcode");
|
||||
if let Err(e) = os::open_file_with("Xcode", config.project_dir()) {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
loop {
|
||||
std::thread::sleep(std::time::Duration::from_secs(24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
|
||||
fn run(
|
||||
options: MobileOptions,
|
||||
root_conf: &Config,
|
||||
config: &AppleConfig,
|
||||
) -> Result<DevChild, Error> {
|
||||
let profile = if options.debug {
|
||||
Profile::Debug
|
||||
} else {
|
||||
Profile::Release
|
||||
};
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
let env = env()?;
|
||||
init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
|
||||
|
||||
device_prompt(&env)
|
||||
.map_err(Error::FailedToPromptForDevice)?
|
||||
.run(config, &env, noise_level, false, profile)
|
||||
.map(|c| DevChild(Some(c)))
|
||||
.map_err(Error::RunFailed)
|
||||
}
|
12
tooling/cli/src/mobile/ios/open.rs
Normal file
12
tooling/cli/src/mobile/ios/open.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use super::{ensure_init, with_config, Error, MobileTarget};
|
||||
use crate::Result;
|
||||
use cargo_mobile::os;
|
||||
|
||||
pub fn command() -> Result<()> {
|
||||
with_config(|_, config, _metadata| {
|
||||
ensure_init(config.project_dir(), MobileTarget::Ios)
|
||||
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
|
||||
os::open_file_with("Xcode", config.project_dir()).map_err(Error::OpenFailed)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
141
tooling/cli/src/mobile/ios/xcode_script.rs
Normal file
141
tooling/cli/src/mobile/ios/xcode_script.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use super::{env, init_dot_cargo, with_config, Error};
|
||||
use crate::Result;
|
||||
use clap::Parser;
|
||||
|
||||
use cargo_mobile::{
|
||||
apple::target::Target,
|
||||
opts::{NoiseLevel, Profile},
|
||||
util,
|
||||
};
|
||||
|
||||
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Options {
|
||||
/// Value of `PLATFORM_DISPLAY_NAME` env var
|
||||
#[clap(long)]
|
||||
platform: String,
|
||||
/// Value of `SDKROOT` env var
|
||||
#[clap(long)]
|
||||
sdk_root: PathBuf,
|
||||
/// Value of `CONFIGURATION` env var
|
||||
#[clap(long)]
|
||||
configuration: String,
|
||||
/// Value of `FORCE_COLOR` env var
|
||||
#[clap(long)]
|
||||
force_color: bool,
|
||||
/// Value of `ARCHS` env var
|
||||
#[clap(index = 1, required = true)]
|
||||
arches: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn command(options: Options) -> Result<()> {
|
||||
fn macos_from_platform(platform: &str) -> bool {
|
||||
platform == "macOS"
|
||||
}
|
||||
|
||||
fn profile_from_configuration(configuration: &str) -> Profile {
|
||||
if configuration == "release" {
|
||||
Profile::Release
|
||||
} else {
|
||||
Profile::Debug
|
||||
}
|
||||
}
|
||||
|
||||
let profile = profile_from_configuration(&options.configuration);
|
||||
let macos = macos_from_platform(&options.platform);
|
||||
let noise_level = NoiseLevel::Polite;
|
||||
|
||||
with_config(|root_conf, config, metadata| {
|
||||
let env = env()?;
|
||||
init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?;
|
||||
// The `PATH` env var Xcode gives us is missing any additions
|
||||
// made by the user's profile, so we'll manually add cargo's
|
||||
// `PATH`.
|
||||
let env = env.prepend_to_path(
|
||||
util::home_dir()
|
||||
.map_err(Error::NoHomeDir)?
|
||||
.join(".cargo/bin"),
|
||||
);
|
||||
|
||||
if !options.sdk_root.is_dir() {
|
||||
return Err(Error::SdkRootInvalid {
|
||||
sdk_root: options.sdk_root,
|
||||
});
|
||||
}
|
||||
let include_dir = options.sdk_root.join("usr/include");
|
||||
if !include_dir.is_dir() {
|
||||
return Err(Error::IncludeDirInvalid { include_dir });
|
||||
}
|
||||
|
||||
let mut host_env = HashMap::<&str, &OsStr>::new();
|
||||
|
||||
// Host flags that are used by build scripts
|
||||
let (macos_isysroot, library_path) = {
|
||||
let macos_sdk_root = options
|
||||
.sdk_root
|
||||
.join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk");
|
||||
if !macos_sdk_root.is_dir() {
|
||||
return Err(Error::MacosSdkRootInvalid { macos_sdk_root });
|
||||
}
|
||||
(
|
||||
format!("-isysroot {}", macos_sdk_root.display()),
|
||||
format!("{}/usr/lib", macos_sdk_root.display()),
|
||||
)
|
||||
};
|
||||
host_env.insert("MAC_FLAGS", macos_isysroot.as_ref());
|
||||
host_env.insert("CFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
host_env.insert("CXXFLAGS_x86_64_apple_darwin", macos_isysroot.as_ref());
|
||||
|
||||
host_env.insert(
|
||||
"OBJC_INCLUDE_PATH_x86_64_apple_darwin",
|
||||
include_dir.as_os_str(),
|
||||
);
|
||||
|
||||
host_env.insert("RUST_BACKTRACE", "1".as_ref());
|
||||
|
||||
let macos_target = Target::macos();
|
||||
|
||||
let isysroot = format!("-isysroot {}", options.sdk_root.display());
|
||||
|
||||
for arch in options.arches {
|
||||
// Set target-specific flags
|
||||
let triple = match arch.as_str() {
|
||||
"arm64" => "aarch64_apple_ios",
|
||||
"x86_64" => "x86_64_apple_ios",
|
||||
_ => return Err(Error::ArchInvalid { arch }),
|
||||
};
|
||||
let cflags = format!("CFLAGS_{}", triple);
|
||||
let cxxflags = format!("CFLAGS_{}", triple);
|
||||
let objc_include_path = format!("OBJC_INCLUDE_PATH_{}", triple);
|
||||
let mut target_env = host_env.clone();
|
||||
target_env.insert(cflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(cxxflags.as_ref(), isysroot.as_ref());
|
||||
target_env.insert(objc_include_path.as_ref(), include_dir.as_ref());
|
||||
// Prevents linker errors in build scripts and proc macros:
|
||||
// https://github.com/signalapp/libsignal-client/commit/02899cac643a14b2ced7c058cc15a836a2165b6d
|
||||
target_env.insert("LIBRARY_PATH", library_path.as_ref());
|
||||
|
||||
let target = if macos {
|
||||
&macos_target
|
||||
} else {
|
||||
Target::for_arch(&arch).ok_or_else(|| Error::ArchInvalid {
|
||||
arch: arch.to_owned(),
|
||||
})?
|
||||
};
|
||||
target
|
||||
.compile_lib(
|
||||
config,
|
||||
metadata,
|
||||
noise_level,
|
||||
true,
|
||||
profile,
|
||||
&env,
|
||||
target_env,
|
||||
)
|
||||
.map_err(Error::CompileLibFailed)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
@ -17,7 +17,7 @@ use cargo_mobile::{
|
||||
config::{app::Raw as RawAppConfig, metadata::Metadata, Config, Raw},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, ffi::OsString, path::PathBuf, process::ExitStatus};
|
||||
use std::{collections::HashMap, ffi::OsString, fmt::Write, path::PathBuf, process::ExitStatus};
|
||||
|
||||
pub mod android;
|
||||
mod init;
|
||||
@ -238,3 +238,14 @@ fn ensure_init(project_dir: PathBuf, target: Target) -> Result<()> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user