refactor(cli): remove mobile Error enum, use anyhow instead (#5076)

This commit is contained in:
Lucas Fernandes Nogueira 2022-08-28 15:32:50 -03:00 committed by GitHub
parent 0a203a13ae
commit 8cf9af93eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 167 additions and 302 deletions

View File

@ -6,13 +6,12 @@ use cargo_mobile::{
android::{ android::{
adb, adb,
config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig}, config::{Config as AndroidConfig, Metadata as AndroidMetadata, Raw as RawAndroidConfig},
device::{Device, RunError}, device::Device,
env::{Env, Error as AndroidEnvError}, env::Env,
target::{BuildError, Target}, target::Target,
}, },
config::app::App, config::app::App,
device::PromptError, device::PromptError,
env::Error as EnvError,
opts::NoiseLevel, opts::NoiseLevel,
os, os,
util::prompt, util::prompt,
@ -36,34 +35,6 @@ mod dev;
mod open; mod open;
pub(crate) mod project; pub(crate) mod project;
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
EnvInitFailed(EnvError),
#[error(transparent)]
AndroidEnvInitFailed(AndroidEnvError),
#[error(transparent)]
InitDotCargo(super::init::Error),
#[error("invalid tauri configuration: {0}")]
InvalidTauriConfig(String),
#[error("{0}")]
ProjectNotInitialized(String),
#[error(transparent)]
OpenFailed(os::OpenFileError),
#[error("{0}")]
DevFailed(String),
#[error("{0}")]
BuildFailed(String),
#[error(transparent)]
AndroidStudioScriptFailed(BuildError),
#[error(transparent)]
RunFailed(RunError),
#[error("{0}")]
TargetInvalid(String),
#[error(transparent)]
FailedToPromptForDevice(PromptError<adb::device_list::Error>),
}
#[derive(Parser)] #[derive(Parser)]
#[clap( #[clap(
author, author,
@ -138,11 +109,10 @@ pub fn get_config(
fn with_config<T>( fn with_config<T>(
cli_options: Option<CliOptions>, cli_options: Option<CliOptions>,
f: impl FnOnce(&App, &AndroidConfig, &AndroidMetadata, CliOptions) -> Result<T, Error>, f: impl FnOnce(&App, &AndroidConfig, &AndroidMetadata, CliOptions) -> Result<T>,
) -> Result<T, Error> { ) -> Result<T> {
let (app, config, metadata, cli_options) = { let (app, config, metadata, cli_options) = {
let tauri_config = let tauri_config = get_tauri_config(None)?;
get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let cli_options = let cli_options =
@ -153,9 +123,9 @@ fn with_config<T>(
f(&app, &config, &metadata, cli_options) f(&app, &config, &metadata, cli_options)
} }
fn env() -> Result<Env, Error> { fn env() -> Result<Env> {
let env = super::env().map_err(Error::EnvInitFailed)?; let env = super::env()?;
cargo_mobile::android::env::Env::from_env(env).map_err(Error::AndroidEnvInitFailed) cargo_mobile::android::env::Env::from_env(env).map_err(Into::into)
} }
fn delete_codegen_vars() { fn delete_codegen_vars() {

View File

@ -1,4 +1,4 @@
use super::{detect_target_ok, ensure_init, env, init_dot_cargo, with_config, Error, MobileTarget}; use super::{detect_target_ok, ensure_init, env, init_dot_cargo, with_config, MobileTarget};
use crate::Result; use crate::Result;
use clap::Parser; use clap::Parser;
@ -33,11 +33,10 @@ pub fn command(options: Options) -> Result<()> {
}; };
with_config(None, |app, config, metadata, cli_options| { with_config(None, |app, config, metadata, cli_options| {
ensure_init(config.project_dir(), MobileTarget::Android) ensure_init(config.project_dir(), MobileTarget::Android)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
let env = env()?; let env = env()?;
init_dot_cargo(app, Some((&env, config))).map_err(Error::InitDotCargo)?; init_dot_cargo(app, Some((&env, config)))?;
call_for_targets_with_fallback( call_for_targets_with_fallback(
options.targets.unwrap_or_default().iter(), options.targets.unwrap_or_default().iter(),
@ -53,10 +52,9 @@ pub fn command(options: Options) -> Result<()> {
true, true,
profile, profile,
) )
.map_err(Error::AndroidStudioScriptFailed) .map_err(Into::into)
}, },
) )
.map_err(|e| Error::TargetInvalid(e.to_string()))? .map_err(|e| anyhow::anyhow!(e.to_string()))?
}) })
.map_err(Into::into)
} }

View File

@ -1,6 +1,6 @@
use super::{ use super::{
delete_codegen_vars, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config, delete_codegen_vars, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config,
Error, MobileTarget, MobileTarget,
}; };
use crate::{ use crate::{
helpers::{config::get as get_tauri_config, flock}, helpers::{config::get as get_tauri_config, flock},
@ -75,15 +75,13 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
set_var("WRY_RUSTWEBVIEWCLIENT_CLASS_EXTENSION", ""); set_var("WRY_RUSTWEBVIEWCLIENT_CLASS_EXTENSION", "");
set_var("WRY_RUSTWEBVIEW_CLASS_INIT", ""); set_var("WRY_RUSTWEBVIEW_CLASS_INIT", "");
ensure_init(config.project_dir(), MobileTarget::Android) ensure_init(config.project_dir(), MobileTarget::Android)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
let env = env()?; let env = env()?;
init_dot_cargo(app, Some((&env, config))).map_err(Error::InitDotCargo)?; init_dot_cargo(app, Some((&env, config)))?;
let open = options.open; let open = options.open;
run_build(options, config, &env, noise_level) run_build(options, config, &env, noise_level)?;
.map_err(|e| Error::BuildFailed(format!("{:#}", e)))?;
if open { if open {
open_and_wait(config, &env); open_and_wait(config, &env);
@ -176,7 +174,7 @@ fn run_build(
Ok(()) Ok(())
} }
fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>, Error> { fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>> {
if targets.is_empty() { if targets.is_empty() {
Ok(Target::all().iter().map(|t| t.1).collect()) Ok(Target::all().iter().map(|t| t.1).collect())
} else { } else {
@ -190,10 +188,11 @@ fn get_targets_or_all<'a>(targets: Vec<String>) -> Result<Vec<&'a Target<'a>>, E
for t in targets { for t in targets {
let target = Target::for_name(&t).ok_or_else(|| { let target = Target::for_name(&t).ok_or_else(|| {
Error::TargetInvalid(format!( anyhow::anyhow!(
"Target {} is invalid; the possible targets are {}", "Target {} is invalid; the possible targets are {}",
t, possible_targets t,
)) possible_targets
)
})?; })?;
outs.push(target); outs.push(target);
} }

View File

@ -1,6 +1,6 @@
use super::{ use super::{
delete_codegen_vars, device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config, delete_codegen_vars, device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config,
Error, MobileTarget, MobileTarget, PromptError,
}; };
use crate::{ use crate::{
helpers::{config::get as get_tauri_config, flock}, helpers::{config::get as get_tauri_config, flock},
@ -12,7 +12,9 @@ use clap::Parser;
use cargo_mobile::{ use cargo_mobile::{
android::{ android::{
adb,
config::{Config as AndroidConfig, Metadata as AndroidMetadata}, config::{Config as AndroidConfig, Metadata as AndroidMetadata},
device::RunError as DeviceRunError,
env::Env, env::Env,
}, },
config::app::App, config::app::App,
@ -75,10 +77,8 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
WEBVIEW_CLIENT_CLASS_EXTENSION, WEBVIEW_CLIENT_CLASS_EXTENSION,
); );
set_var("WRY_RUSTWEBVIEW_CLASS_INIT", WEBVIEW_CLASS_INIT); set_var("WRY_RUSTWEBVIEW_CLASS_INIT", WEBVIEW_CLASS_INIT);
ensure_init(config.project_dir(), MobileTarget::Android) ensure_init(config.project_dir(), MobileTarget::Android)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?; run_dev(options, app, config, metadata, noise_level).map_err(Into::into)
run_dev(options, app, config, metadata, noise_level)
.map_err(|e| Error::DevFailed(format!("{:#}", e)))
}, },
) )
.map_err(Into::into) .map_err(Into::into)
@ -110,7 +110,7 @@ fn run_dev(
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?; let _lock = flock::open_rw(&out_dir.join("lock").with_extension("android"), "Android")?;
let env = env()?; let env = env()?;
init_dot_cargo(app, Some((&env, config))).map_err(Error::InitDotCargo)?; init_dot_cargo(app, Some((&env, config)))?;
let open = options.open; let open = options.open;
let exit_on_panic = options.exit_on_panic; let exit_on_panic = options.exit_on_panic;
@ -142,7 +142,7 @@ fn run_dev(
}); });
Ok(Box::new(c) as Box<dyn DevProcess>) Ok(Box::new(c) as Box<dyn DevProcess>)
} }
Err(Error::FailedToPromptForDevice(e)) => { Err(RunError::FailedToPromptForDevice(e)) => {
log::error!("{}", e); log::error!("{}", e);
open_and_wait(config, &env) open_and_wait(config, &env)
} }
@ -153,13 +153,21 @@ fn run_dev(
) )
} }
#[derive(Debug, thiserror::Error)]
enum RunError {
#[error(transparent)]
FailedToPromptForDevice(PromptError<adb::device_list::Error>),
#[error(transparent)]
RunFailed(DeviceRunError),
}
fn run( fn run(
options: MobileOptions, options: MobileOptions,
config: &AndroidConfig, config: &AndroidConfig,
env: &Env, env: &Env,
metadata: &AndroidMetadata, metadata: &AndroidMetadata,
noise_level: NoiseLevel, noise_level: NoiseLevel,
) -> Result<DevChild, Error> { ) -> Result<DevChild, RunError> {
let profile = if options.debug { let profile = if options.debug {
Profile::Debug Profile::Debug
} else { } else {
@ -169,7 +177,7 @@ fn run(
let build_app_bundle = metadata.asset_packs().is_some(); let build_app_bundle = metadata.asset_packs().is_some();
device_prompt(env) device_prompt(env)
.map_err(Error::FailedToPromptForDevice)? .map_err(RunError::FailedToPromptForDevice)?
.run( .run(
config, config,
env, env,
@ -181,5 +189,5 @@ fn run(
".MainActivity".into(), ".MainActivity".into(),
) )
.map(DevChild::new) .map(DevChild::new)
.map_err(Error::RunFailed) .map_err(RunError::RunFailed)
} }

View File

@ -1,4 +1,4 @@
use super::{ensure_init, env, with_config, Error, MobileTarget}; use super::{ensure_init, env, with_config, MobileTarget};
use crate::Result; use crate::Result;
use cargo_mobile::os; use cargo_mobile::os;
@ -6,12 +6,9 @@ pub fn command() -> Result<()> {
with_config( with_config(
Some(Default::default()), Some(Default::default()),
|_root_conf, config, _metadata, _cli_options| { |_root_conf, config, _metadata, _cli_options| {
ensure_init(config.project_dir(), MobileTarget::Android) ensure_init(config.project_dir(), MobileTarget::Android)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
let env = env()?; let env = env()?;
os::open_file_with("Android Studio", config.project_dir(), &env.base) os::open_file_with("Android Studio", config.project_dir(), &env.base).map_err(Into::into)
.map_err(Error::OpenFailed)
}, },
) )
.map_err(Into::into)
} }

View File

@ -2,13 +2,14 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use crate::helpers::template; use crate::{helpers::template, Result};
use anyhow::Context;
use cargo_mobile::{ use cargo_mobile::{
android::{ android::{
config::{Config, Metadata}, config::{Config, Metadata},
target::Target, target::Target,
}, },
bossy, os, os,
target::TargetTrait as _, target::TargetTrait as _,
util::{ util::{
self, self,
@ -19,45 +20,18 @@ use cargo_mobile::{
use handlebars::Handlebars; use handlebars::Handlebars;
use include_dir::{include_dir, Dir}; use include_dir::{include_dir, Dir};
use std::{ use std::{ffi::OsStr, fs, path::Path};
ffi::OsStr,
fs,
path::{Path, PathBuf},
};
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/android"); const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/android");
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("failed to run rustup: {0}")]
RustupFailed(bossy::Error),
#[error("failed to process template: {0}")]
TemplateProcessingFailed(String),
#[error("failed to create directory at {path}: {cause}")]
DirectoryCreationFailed {
path: PathBuf,
cause: std::io::Error,
},
#[error("failed to symlink asset directory")]
AssetDirSymlinkFailed,
#[error("failed to copy {src} to {dest}: {cause}")]
FileCopyFailed {
src: PathBuf,
dest: PathBuf,
cause: std::io::Error,
},
#[error("asset source {0} is invalid")]
AssetSourceInvalid(PathBuf),
}
pub fn gen( pub fn gen(
config: &Config, config: &Config,
metadata: &Metadata, metadata: &Metadata,
(handlebars, mut map): (Handlebars, template::JsonMap), (handlebars, mut map): (Handlebars, template::JsonMap),
wrapper: &TextWrapper, wrapper: &TextWrapper,
) -> Result<(), Error> { ) -> Result<()> {
println!("Installing Android toolchains..."); println!("Installing Android toolchains...");
Target::install_all().map_err(Error::RustupFailed)?; Target::install_all().with_context(|| "failed to run rustup")?;
println!("Generating Android Studio project..."); println!("Generating Android Studio project...");
let dest = config.project_dir(); let dest = config.project_dir();
let asset_packs = metadata.asset_packs().unwrap_or_default(); let asset_packs = metadata.asset_packs().unwrap_or_default();
@ -143,7 +117,7 @@ pub fn gen(
options.create_new(true).write(true).open(path) options.create_new(true).write(true).open(path)
}, },
) )
.map_err(|e| Error::TemplateProcessingFailed(e.to_string()))?; .with_context(|| "failed to process template")?;
if !asset_packs.is_empty() { if !asset_packs.is_empty() {
Report::action_request( Report::action_request(
@ -157,23 +131,27 @@ pub fn gen(
let source_src = config.app().root_dir().join(&source); let source_src = config.app().root_dir().join(&source);
let source_file = source_src let source_file = source_src
.file_name() .file_name()
.ok_or_else(|| Error::AssetSourceInvalid(source_src.clone()))?; .ok_or_else(|| anyhow::anyhow!("asset source {} is invalid", source_src.display()))?;
fs::copy(&source_src, source_dest.join(source_file)).map_err(|cause| { fs::copy(&source_src, source_dest.join(source_file)).map_err(|cause| {
Error::FileCopyFailed { anyhow::anyhow!(
src: source_src, "failed to copy {} to {}: {}",
dest: source_dest.clone(), source_src.display(),
cause, source_dest.display(),
} cause
)
})?; })?;
} }
let dest = prefix_path(dest, "app/src/main/"); let dest = prefix_path(dest, "app/src/main/");
fs::create_dir_all(&dest).map_err(|cause| Error::DirectoryCreationFailed { fs::create_dir_all(&dest).map_err(|cause| {
path: dest.clone(), anyhow::anyhow!(
cause, "failed to create directory at {}: {}",
dest.display(),
cause
)
})?; })?;
os::ln::force_symlink_relative(config.app().asset_dir(), dest, ln::TargetStyle::Directory) os::ln::force_symlink_relative(config.app().asset_dir(), dest, ln::TargetStyle::Directory)
.map_err(|_| Error::AssetDirSymlinkFailed)?; .map_err(|_| anyhow::anyhow!("failed to symlink asset directory"))?;
Ok(()) Ok(())
} }

View File

@ -7,10 +7,8 @@ use crate::helpers::{config::get as get_tauri_config, template::JsonMap};
use crate::Result; use crate::Result;
use cargo_mobile::{ use cargo_mobile::{
android::{ android::{
self, config::Config as AndroidConfig, env::Env as AndroidEnv, ndk, config::Config as AndroidConfig, env::Env as AndroidEnv, target::Target as AndroidTarget,
target::Target as AndroidTarget,
}, },
bossy,
config::app::App, config::app::App,
dot_cargo, dot_cargo,
os::code_command, os::code_command,
@ -23,7 +21,7 @@ use cargo_mobile::{
use clap::Parser; use clap::Parser;
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError}; use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};
use std::{env::current_dir, fs, io, path::PathBuf}; use std::{env::current_dir, fs, path::PathBuf};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[clap(about = "Initializes a Tauri Android project")] #[clap(about = "Initializes a Tauri Android project")]
@ -41,39 +39,8 @@ pub fn command(mut options: Options, target: Target) -> Result<()> {
Ok(()) Ok(())
} }
#[derive(Debug, thiserror::Error)] pub fn init_dot_cargo(app: &App, android: Option<(&AndroidEnv, &AndroidConfig)>) -> Result<()> {
pub enum Error { let mut dot_cargo = dot_cargo::DotCargo::load(app)?;
#[error("invalid tauri configuration: {0}")]
InvalidTauriConfig(String),
#[error("failed to create asset dir {asset_dir}: {cause}")]
AssetDirCreation {
asset_dir: PathBuf,
cause: io::Error,
},
#[error("failed to install LLDB VS Code extension: {0}")]
LldbExtensionInstall(bossy::Error),
#[error(transparent)]
DotCargoLoad(dot_cargo::LoadError),
#[error(transparent)]
DotCargoGenFailed(ndk::MissingToolError),
#[error(transparent)]
HostTargetTripleDetection(util::HostTargetTripleError),
#[cfg(target_os = "macos")]
#[error(transparent)]
IosInit(super::ios::project::Error),
#[error(transparent)]
AndroidEnv(android::env::Error),
#[error(transparent)]
AndroidInit(super::android::project::Error),
#[error(transparent)]
DotCargoWrite(dot_cargo::WriteError),
}
pub fn init_dot_cargo(
app: &App,
android: Option<(&AndroidEnv, &AndroidConfig)>,
) -> Result<(), Error> {
let mut dot_cargo = dot_cargo::DotCargo::load(app).map_err(Error::DotCargoLoad)?;
// Mysteriously, builds that don't specify `--target` seem to fight over // Mysteriously, builds that don't specify `--target` seem to fight over
// the build cache with builds that use `--target`! This means that // the build cache with builds that use `--target`! This means that
// alternating between i.e. `cargo run` and `cargo apple run` would // alternating between i.e. `cargo run` and `cargo apple run` would
@ -84,21 +51,18 @@ pub fn init_dot_cargo(
// //
// This behavior could be explained here: // This behavior could be explained here:
// https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags // https://doc.rust-lang.org/cargo/reference/config.html#buildrustflags
dot_cargo dot_cargo.set_default_target(util::host_target_triple()?);
.set_default_target(util::host_target_triple().map_err(Error::HostTargetTripleDetection)?);
if let Some((env, config)) = android { if let Some((env, config)) = android {
for target in AndroidTarget::all().values() { for target in AndroidTarget::all().values() {
dot_cargo.insert_target( dot_cargo.insert_target(
target.triple.to_owned(), target.triple.to_owned(),
target target.generate_cargo_config(config, env)?,
.generate_cargo_config(config, env)
.map_err(Error::DotCargoGenFailed)?,
); );
} }
} }
dot_cargo.write(app).map_err(Error::DotCargoWrite) dot_cargo.write(app).map_err(Into::into)
} }
pub fn exec( pub fn exec(
@ -107,9 +71,8 @@ pub fn exec(
non_interactive: bool, non_interactive: bool,
skip_dev_tools: bool, skip_dev_tools: bool,
#[allow(unused_variables)] reinstall_deps: bool, #[allow(unused_variables)] reinstall_deps: bool,
) -> Result<App, Error> { ) -> Result<App> {
let tauri_config = let tauri_config = get_tauri_config(None)?;
get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap();
@ -117,7 +80,12 @@ pub fn exec(
let asset_dir = app.asset_dir(); let asset_dir = app.asset_dir();
if !asset_dir.is_dir() { if !asset_dir.is_dir() {
fs::create_dir_all(&asset_dir).map_err(|cause| Error::AssetDirCreation { asset_dir, cause })?; fs::create_dir_all(&asset_dir).map_err(|cause| {
anyhow::anyhow!(
"failed to create asset dir {path}: {cause}",
path = asset_dir.display()
)
})?;
} }
if !skip_dev_tools && util::command_present("code").unwrap_or_default() { if !skip_dev_tools && util::command_present("code").unwrap_or_default() {
let mut command = code_command(); let mut command = code_command();
@ -125,9 +93,7 @@ pub fn exec(
if non_interactive { if non_interactive {
command.add_arg("--force"); command.add_arg("--force");
} }
command command.run_and_wait()?;
.run_and_wait()
.map_err(Error::LldbExtensionInstall)?;
} }
let (handlebars, mut map) = handlebars(&app); let (handlebars, mut map) = handlebars(&app);
@ -173,8 +139,7 @@ pub fn exec(
let (app, config, metadata) = let (app, config, metadata) =
super::android::get_config(Some(app), tauri_config_, &Default::default()); super::android::get_config(Some(app), tauri_config_, &Default::default());
map.insert("android", &config); map.insert("android", &config);
super::android::project::gen(&config, &metadata, (handlebars, map), wrapper) super::android::project::gen(&config, &metadata, (handlebars, map), wrapper)?;
.map_err(Error::AndroidInit)?;
init_dot_cargo(&app, Some((&env, &config)))?; init_dot_cargo(&app, Some((&env, &config)))?;
app app
} }
@ -188,7 +153,7 @@ pub fn exec(
init_dot_cargo(&app, None)?; init_dot_cargo(&app, None)?;
app app
} else { } else {
return Err(Error::AndroidEnv(err)); return Err(err.into());
} }
} }
}, },
@ -206,8 +171,7 @@ pub fn exec(
non_interactive, non_interactive,
skip_dev_tools, skip_dev_tools,
reinstall_deps, reinstall_deps,
) )?;
.map_err(Error::IosInit)?;
init_dot_cargo(&app, None)?; init_dot_cargo(&app, None)?;
app app
} }

View File

@ -8,15 +8,15 @@ use cargo_mobile::{
Config as AppleConfig, Metadata as AppleMetadata, Platform as ApplePlatform, Config as AppleConfig, Metadata as AppleMetadata, Platform as ApplePlatform,
Raw as RawAppleConfig, Raw as RawAppleConfig,
}, },
device::{Device, RunError}, device::Device,
ios_deploy, ios_deploy,
target::{CompileLibError, Target}, target::Target,
}, },
config::app::App, config::app::App,
device::PromptError, device::PromptError,
env::{Env, Error as EnvError}, env::Env,
opts::NoiseLevel, opts::NoiseLevel,
os, util, os,
util::prompt, util::prompt,
}; };
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@ -31,50 +31,12 @@ use crate::{
Result, Result,
}; };
use std::path::PathBuf;
mod build; mod build;
mod dev; mod dev;
mod open; mod open;
pub(crate) mod project; pub(crate) mod project;
mod xcode_script; mod xcode_script;
#[derive(Debug, thiserror::Error)]
enum Error {
#[error(transparent)]
EnvInitFailed(#[from] EnvError),
#[error(transparent)]
InitDotCargo(super::init::Error),
#[error("invalid tauri configuration: {0}")]
InvalidTauriConfig(String),
#[error("{0}")]
ProjectNotInitialized(String),
#[error(transparent)]
OpenFailed(os::OpenFileError),
#[error("{0}")]
DevFailed(String),
#[error("{0}")]
BuildFailed(String),
#[error(transparent)]
NoHomeDir(util::NoHomeDir),
#[error("SDK root provided by Xcode was invalid. {sdk_root} doesn't exist or isn't a directory")]
SdkRootInvalid { sdk_root: PathBuf },
#[error("Include dir was invalid. {include_dir} doesn't exist or isn't a directory")]
IncludeDirInvalid { include_dir: PathBuf },
#[error("macOS SDK root was invalid. {macos_sdk_root} doesn't exist or isn't a directory")]
MacosSdkRootInvalid { macos_sdk_root: PathBuf },
#[error("Arch specified by Xcode was invalid. {arch} isn't a known arch")]
ArchInvalid { arch: String },
#[error(transparent)]
CompileLibFailed(CompileLibError),
#[error(transparent)]
FailedToPromptForDevice(PromptError<ios_deploy::DeviceListError>),
#[error(transparent)]
RunFailed(RunError),
#[error("{0}")]
TargetInvalid(String),
}
#[derive(Parser)] #[derive(Parser)]
#[clap( #[clap(
author, author,
@ -146,11 +108,10 @@ pub fn get_config(
fn with_config<T>( fn with_config<T>(
cli_options: Option<CliOptions>, cli_options: Option<CliOptions>,
f: impl FnOnce(&App, &AppleConfig, &AppleMetadata, CliOptions) -> Result<T, Error>, f: impl FnOnce(&App, &AppleConfig, &AppleMetadata, CliOptions) -> Result<T>,
) -> Result<T, Error> { ) -> Result<T> {
let (app, config, metadata, cli_options) = { let (app, config, metadata, cli_options) = {
let tauri_config = let tauri_config = get_tauri_config(None)?;
get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap();
let cli_options = cli_options.unwrap_or_else(|| read_options(tauri_config_, MobileTarget::Ios)); let cli_options = cli_options.unwrap_or_else(|| read_options(tauri_config_, MobileTarget::Ios));

View File

@ -1,6 +1,6 @@
use super::{ use super::{
detect_target_ok, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config, detect_target_ok, ensure_init, env, init_dot_cargo, log_finished, open_and_wait, with_config,
Error, MobileTarget, MobileTarget,
}; };
use crate::{ use crate::{
helpers::{config::get as get_tauri_config, flock}, helpers::{config::get as get_tauri_config, flock},
@ -67,15 +67,13 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
with_config( with_config(
Some(Default::default()), Some(Default::default()),
|app, config, _metadata, _cli_options| { |app, config, _metadata, _cli_options| {
ensure_init(config.project_dir(), MobileTarget::Ios) ensure_init(config.project_dir(), MobileTarget::Ios)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
let env = env()?; let env = env()?;
init_dot_cargo(app, None).map_err(Error::InitDotCargo)?; init_dot_cargo(app, None)?;
let open = options.open; let open = options.open;
run_build(options, config, &env, noise_level) run_build(options, config, &env, noise_level)?;
.map_err(|e| Error::BuildFailed(format!("{:#}", e)))?;
if open { if open {
open_and_wait(config, &env); open_and_wait(config, &env);
@ -157,7 +155,7 @@ fn run_build(
anyhow::Result::Ok(()) anyhow::Result::Ok(())
}, },
) )
.map_err(|e: TargetInvalid| Error::TargetInvalid(e.to_string()))? .map_err(|e: TargetInvalid| anyhow::anyhow!(e.to_string()))?
.map_err(|e: anyhow::Error| e)?; .map_err(|e: anyhow::Error| e)?;
log_finished(out_files, "IPA"); log_finished(out_files, "IPA");

View File

@ -1,5 +1,5 @@
use super::{ use super::{
device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config, Error, MobileTarget, device_prompt, ensure_init, env, init_dot_cargo, open_and_wait, with_config, MobileTarget,
}; };
use crate::{ use crate::{
helpers::{config::get as get_tauri_config, flock}, helpers::{config::get as get_tauri_config, flock},
@ -10,8 +10,9 @@ use crate::{
use clap::Parser; use clap::Parser;
use cargo_mobile::{ use cargo_mobile::{
apple::config::Config as AppleConfig, apple::{config::Config as AppleConfig, device::RunError as DeviceRunError, ios_deploy},
config::app::App, config::app::App,
device::PromptError,
env::Env, env::Env,
opts::{NoiseLevel, Profile}, opts::{NoiseLevel, Profile},
}; };
@ -58,12 +59,10 @@ pub fn command(options: Options, noise_level: NoiseLevel) -> Result<()> {
with_config( with_config(
Some(Default::default()), Some(Default::default()),
|app, config, _metadata, _cli_options| { |app, config, _metadata, _cli_options| {
ensure_init(config.project_dir(), MobileTarget::Ios) ensure_init(config.project_dir(), MobileTarget::Ios)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?; run_dev(options, app, config, noise_level).map_err(Into::into)
run_dev(options, app, config, noise_level).map_err(|e| Error::DevFailed(format!("{:#}", e)))
}, },
) )
.map_err(Into::into)
} }
fn run_dev( fn run_dev(
@ -76,8 +75,7 @@ fn run_dev(
let mut interface = crate::dev::setup(&mut dev_options)?; let mut interface = crate::dev::setup(&mut dev_options)?;
let bundle_identifier = { let bundle_identifier = {
let tauri_config = let tauri_config = get_tauri_config(None)?;
get_tauri_config(None).map_err(|e| Error::InvalidTauriConfig(e.to_string()))?;
let tauri_config_guard = tauri_config.lock().unwrap(); let tauri_config_guard = tauri_config.lock().unwrap();
let tauri_config_ = tauri_config_guard.as_ref().unwrap(); let tauri_config_ = tauri_config_guard.as_ref().unwrap();
tauri_config_.tauri.bundle.identifier.clone() tauri_config_.tauri.bundle.identifier.clone()
@ -92,7 +90,7 @@ fn run_dev(
let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?; let _lock = flock::open_rw(&out_dir.join("lock").with_extension("ios"), "iOS")?;
let env = env()?; let env = env()?;
init_dot_cargo(app, None).map_err(Error::InitDotCargo)?; init_dot_cargo(app, None)?;
let open = options.open; let open = options.open;
let exit_on_panic = options.exit_on_panic; let exit_on_panic = options.exit_on_panic;
@ -124,7 +122,7 @@ fn run_dev(
}); });
Ok(Box::new(c) as Box<dyn DevProcess>) Ok(Box::new(c) as Box<dyn DevProcess>)
} }
Err(Error::FailedToPromptForDevice(e)) => { Err(RunError::FailedToPromptForDevice(e)) => {
log::error!("{}", e); log::error!("{}", e);
open_and_wait(config, &env) open_and_wait(config, &env)
} }
@ -135,12 +133,19 @@ fn run_dev(
) )
} }
#[derive(Debug, thiserror::Error)]
enum RunError {
#[error(transparent)]
FailedToPromptForDevice(PromptError<ios_deploy::DeviceListError>),
#[error(transparent)]
RunFailed(DeviceRunError),
}
fn run( fn run(
options: MobileOptions, options: MobileOptions,
config: &AppleConfig, config: &AppleConfig,
env: &Env, env: &Env,
noise_level: NoiseLevel, noise_level: NoiseLevel,
) -> Result<DevChild, Error> { ) -> Result<DevChild, RunError> {
let profile = if options.debug { let profile = if options.debug {
Profile::Debug Profile::Debug
} else { } else {
@ -150,8 +155,8 @@ fn run(
let non_interactive = true; // ios-deploy --noninteractive (quit when app crashes or exits) let non_interactive = true; // ios-deploy --noninteractive (quit when app crashes or exits)
device_prompt(env) device_prompt(env)
.map_err(Error::FailedToPromptForDevice)? .map_err(RunError::FailedToPromptForDevice)?
.run(config, env, noise_level, non_interactive, profile) .run(config, env, noise_level, non_interactive, profile)
.map(DevChild::new) .map(DevChild::new)
.map_err(Error::RunFailed) .map_err(RunError::RunFailed)
} }

View File

@ -1,4 +1,4 @@
use super::{ensure_init, env, with_config, Error, MobileTarget}; use super::{ensure_init, env, with_config, MobileTarget};
use crate::Result; use crate::Result;
use cargo_mobile::os; use cargo_mobile::os;
@ -6,11 +6,9 @@ pub fn command() -> Result<()> {
with_config( with_config(
Some(Default::default()), Some(Default::default()),
|_root_conf, config, _metadata, _cli_options| { |_root_conf, config, _metadata, _cli_options| {
ensure_init(config.project_dir(), MobileTarget::Ios) ensure_init(config.project_dir(), MobileTarget::Ios)?;
.map_err(|e| Error::ProjectNotInitialized(e.to_string()))?;
let env = env()?; let env = env()?;
os::open_file_with("Xcode", config.project_dir(), &env).map_err(Error::OpenFailed) os::open_file_with("Xcode", config.project_dir(), &env).map_err(Into::into)
}, },
) )
.map_err(Into::into)
} }

View File

@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use crate::helpers::template; use crate::{helpers::template, Result};
use anyhow::Context;
use cargo_mobile::{ use cargo_mobile::{
apple::{ apple::{
config::{Config, Metadata}, config::{Config, Metadata},
@ -23,29 +24,6 @@ use std::{
const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/ios"); const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/mobile/ios");
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Rustup(bossy::Error),
#[error(transparent)]
RustVersionCheck(util::RustVersionError),
#[error("failed to install Apple dependencies: {0}")]
DepsInstall(deps::Error),
#[error("failed to process template: {0}")]
TemplateProcessing(String),
#[error("failed to symlink asset directory")]
AssetDirSymlink,
#[error("failed to create directory at {path}: {cause}")]
DirectoryCreation {
path: PathBuf,
cause: std::io::Error,
},
#[error("failed to run `xcodegen`: {0}")]
Xcodegen(bossy::Error),
#[error("failed to run `pod install`: {0}")]
PodInstall(bossy::Error),
}
// unprefixed app_root seems pretty dangerous!! // unprefixed app_root seems pretty dangerous!!
// TODO: figure out what cargo-mobile meant by that // TODO: figure out what cargo-mobile meant by that
pub fn gen( pub fn gen(
@ -56,13 +34,13 @@ pub fn gen(
non_interactive: bool, non_interactive: bool,
skip_dev_tools: bool, skip_dev_tools: bool,
reinstall_deps: bool, reinstall_deps: bool,
) -> Result<(), Error> { ) -> Result<()> {
println!("Installing iOS toolchains..."); println!("Installing iOS toolchains...");
Target::install_all().map_err(Error::Rustup)?; Target::install_all()?;
rust_version_check(wrapper).map_err(Error::RustVersionCheck)?; rust_version_check(wrapper)?;
deps::install_all(wrapper, non_interactive, skip_dev_tools, reinstall_deps) deps::install_all(wrapper, non_interactive, skip_dev_tools, reinstall_deps)
.map_err(Error::DepsInstall)?; .with_context(|| "failed to install Apple dependencies")?;
let dest = config.project_dir(); let dest = config.project_dir();
let rel_prefix = util::relativize_path(config.app().root_dir(), &dest); let rel_prefix = util::relativize_path(config.app().root_dir(), &dest);
@ -161,16 +139,18 @@ pub fn gen(
File::create(path) File::create(path)
}, },
) )
.map_err(|e| Error::TemplateProcessing(e.to_string()))?; .with_context(|| "failed to process template")?;
ln::force_symlink_relative(config.app().asset_dir(), &dest, ln::TargetStyle::Directory) ln::force_symlink_relative(config.app().asset_dir(), &dest, ln::TargetStyle::Directory)
.map_err(|_| Error::AssetDirSymlink)?; .map_err(|_| anyhow::anyhow!("failed to symlink asset directory"))?;
// Create all asset catalog directories if they don't already exist // Create all asset catalog directories if they don't already exist
for dir in asset_catalogs { for dir in asset_catalogs {
std::fs::create_dir_all(dir).map_err(|cause| Error::DirectoryCreation { std::fs::create_dir_all(dir).map_err(|cause| {
path: dest.clone(), anyhow::anyhow!(
cause, "failed to create directory at {path}: {cause}",
path = dir.display()
)
})?; })?;
} }
@ -181,13 +161,13 @@ pub fn gen(
.with_args(&["generate", "--spec"]) .with_args(&["generate", "--spec"])
.with_arg(dest.join("project.yml")) .with_arg(dest.join("project.yml"))
.run_and_wait() .run_and_wait()
.map_err(Error::Xcodegen)?; .with_context(|| "failed to run `xcodegen`")?;
if !ios_pods.is_empty() || !macos_pods.is_empty() { if !ios_pods.is_empty() || !macos_pods.is_empty() {
bossy::Command::impure_parse("pod install") bossy::Command::impure_parse("pod install")
.with_arg(format!("--project-directory={}", dest.display())) .with_arg(format!("--project-directory={}", dest.display()))
.run_and_wait() .run_and_wait()
.map_err(Error::PodInstall)?; .with_context(|| "failed to run `pod install`")?;
} }
Ok(()) Ok(())
} }

View File

@ -1,4 +1,4 @@
use super::{env, init_dot_cargo, with_config, Error}; use super::{env, init_dot_cargo, with_config};
use crate::Result; use crate::Result;
use clap::Parser; use clap::Parser;
@ -43,24 +43,24 @@ pub fn command(options: Options) -> Result<()> {
with_config(None, |root_conf, config, metadata, cli_options| { with_config(None, |root_conf, config, metadata, cli_options| {
let env = env()?; let env = env()?;
init_dot_cargo(root_conf, None).map_err(Error::InitDotCargo)?; init_dot_cargo(root_conf, None)?;
// The `PATH` env var Xcode gives us is missing any additions // The `PATH` env var Xcode gives us is missing any additions
// made by the user's profile, so we'll manually add cargo's // made by the user's profile, so we'll manually add cargo's
// `PATH`. // `PATH`.
let env = env.prepend_to_path( let env = env.prepend_to_path(util::home_dir()?.join(".cargo/bin"));
util::home_dir()
.map_err(Error::NoHomeDir)?
.join(".cargo/bin"),
);
if !options.sdk_root.is_dir() { if !options.sdk_root.is_dir() {
return Err(Error::SdkRootInvalid { return Err(anyhow::anyhow!(
sdk_root: options.sdk_root, "SDK root provided by Xcode was invalid. {} doesn't exist or isn't a directory",
}); options.sdk_root.display(),
));
} }
let include_dir = options.sdk_root.join("usr/include"); let include_dir = options.sdk_root.join("usr/include");
if !include_dir.is_dir() { if !include_dir.is_dir() {
return Err(Error::IncludeDirInvalid { include_dir }); return Err(anyhow::anyhow!(
"Include dir was invalid. {} doesn't exist or isn't a directory",
include_dir.display()
));
} }
let mut host_env = HashMap::<&str, &OsStr>::new(); let mut host_env = HashMap::<&str, &OsStr>::new();
@ -71,7 +71,10 @@ pub fn command(options: Options) -> Result<()> {
.sdk_root .sdk_root
.join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk"); .join("../../../../MacOSX.platform/Developer/SDKs/MacOSX.sdk");
if !macos_sdk_root.is_dir() { if !macos_sdk_root.is_dir() {
return Err(Error::MacosSdkRootInvalid { macos_sdk_root }); return Err(anyhow::anyhow!(
"macOS SDK root was invalid. {0} doesn't exist or isn't a directory",
macos_sdk_root.display()
));
} }
( (
format!("-isysroot {}", macos_sdk_root.display()), format!("-isysroot {}", macos_sdk_root.display()),
@ -98,7 +101,12 @@ pub fn command(options: Options) -> Result<()> {
let triple = match arch.as_str() { let triple = match arch.as_str() {
"arm64" => "aarch64_apple_ios", "arm64" => "aarch64_apple_ios",
"x86_64" => "x86_64_apple_ios", "x86_64" => "x86_64_apple_ios",
_ => return Err(Error::ArchInvalid { arch }), _ => {
return Err(anyhow::anyhow!(
"Arch specified by Xcode was invalid. {} isn't a known arch",
arch
))
}
}; };
let cflags = format!("CFLAGS_{}", triple); let cflags = format!("CFLAGS_{}", triple);
let cxxflags = format!("CFLAGS_{}", triple); let cxxflags = format!("CFLAGS_{}", triple);
@ -114,12 +122,14 @@ pub fn command(options: Options) -> Result<()> {
let target = if macos { let target = if macos {
&macos_target &macos_target
} else { } else {
Target::for_arch(&arch).ok_or_else(|| Error::ArchInvalid { Target::for_arch(&arch).ok_or_else(|| {
arch: arch.to_owned(), anyhow::anyhow!(
"Arch specified by Xcode was invalid. {} isn't a known arch",
arch
)
})? })?
}; };
target target.compile_lib(
.compile_lib(
config, config,
metadata, metadata,
cli_options.noise_level, cli_options.noise_level,
@ -127,8 +137,7 @@ pub fn command(options: Options) -> Result<()> {
profile, profile,
&env, &env,
target_env, target_env,
) )?;
.map_err(Error::CompileLibFailed)?;
} }
Ok(()) Ok(())
}) })