Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## \[0.23.0]

- Added support to build cargo-mobile2 on Android for use within
termux using the termux feature and updated the android-studio
template to enable use of gradle 9.
## \[0.22.4]

- [`74138d4`](https://github.com/tauri-apps/cargo-mobile2/commit/74138d48435492d49d40831e56544abeb56398bd) ([#509](https://github.com/tauri-apps/cargo-mobile2/pull/509) by [@lucasfernog](https://github.com/tauri-apps/cargo-mobile2/../../lucasfernog)) Fixed Android logcat process detection when running an app with `--application-id-suffix`.
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ brainium = []
rustls = ["ureq/rustls"]
native-tls = ["ureq/native-tls"]
default = ["cli", "native-tls"]
termux = []

[dependencies]
handlebars = "6"
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,21 @@ For fine-grained control of logging, use the `--filter` (or `-f`) option, which
the default device logging level set by `-v` or `-vv`.

If using the `android_logger` crate to handle Rust log messages, `trace` logs from Rust are mapped to `verbose` logs in Android.

## Build and use on Android with termux

Cargo-mobile2 can be build for use within termux on Android to
build Android packages. To compile cargo-mobile2 for termux enable
the termux feature.

Before using cargo-mobile2 you need to:

* install rust packages including the android targets (rust-std-aarch64-linux-android,
rust-std-armv7-linux-androideabi, rust-std-i686-linux-android, rust-std-x86-64-linux-android)
* install java & tools like gradle, aapt2 and android-tools packages
* install termux version of android-sdk and android-ndk

Make sure you set the environment variables `JAVA_HOME`
(eg `$PREFIX/lib/jvm/java-21-openjdk`), `ANDROID_HOME` (location
android sdk: `$PREFIX/opt/android-sdk`) and `NDK_HOME` (location
android ndk: `$PREFIX/opt/android-ndk`).
11 changes: 11 additions & 0 deletions src/android/adb/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{env::ExplicitEnv as _, util::cli::Report, DuctExpressionExt};
use std::{ffi::OsString, str, string::FromUtf8Error};
use thiserror::Error;

#[cfg(not(all(feature = "termux", target_os = "android")))]
pub fn adb<U>(env: &Env, args: U) -> duct::Expression
where
U: IntoIterator,
Expand All @@ -17,6 +18,16 @@ where
duct::cmd(env.platform_tools_path().join("adb"), args).vars(env.explicit_env())
}

#[cfg(all(feature = "termux", target_os = "android"))]
pub fn adb<U>(env: &Env, args: U) -> duct::Expression
where
U: IntoIterator,
U::Item: Into<OsString>,
{
let prefix = crate::os::prefix().expect("Not running on termux");
duct::cmd(prefix.join("bin/adb"), args).vars(env.explicit_env())
}

#[derive(Debug, Error)]
pub enum RunCheckedError {
#[error(transparent)]
Expand Down
6 changes: 6 additions & 0 deletions src/android/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,17 @@ impl Exec for Input {
}
}

#[cfg(not(all(feature = "termux", target_os = "android")))]
fn open_in_android_studio(config: &Config, env: &Env) -> Result<(), Error> {
os::open_file_with("Android Studio", config.project_dir(), &env.base)
.map_err(Error::OpenFailed)
}

#[cfg(all(feature = "termux", target_os = "android"))]
fn open_in_android_studio(_config: &Config, _env: &Env) -> Result<(), Error> {
Err(Error::Unsupported)
}

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())
Expand Down
5 changes: 5 additions & 0 deletions src/android/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ impl ExplicitEnv for Env {
"NDK_HOME".into(),
self.ndk.home().as_os_str().to_os_string(),
);
#[cfg(all(feature = "termux", target_os = "android"))]
envs.insert(
"GRADLE_OPTS".into(),
"-Dorg.gradle.native=false -Djansi.passthrough=true".into(),
);
envs
}
}
20 changes: 20 additions & 0 deletions src/android/ndk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ pub fn host_tag() -> &'static str {
"windows-x86_64"
}

#[cfg(all(feature = "termux", target_os = "android", target_arch = "aarch64"))]
pub fn host_tag() -> &'static str {
"linux-aarch64"
}

#[cfg(all(feature = "termux", target_os = "android", target_arch = "x86_64"))]
pub fn host_tag() -> &'static str {
"linux-x86_64"
}

#[cfg(all(feature = "termux", target_os = "android", target_arch = "x86"))]
pub fn host_tag() -> &'static str {
"linux-x86"
}

#[cfg(all(feature = "termux", target_os = "android", target_arch = "arm"))]
pub fn host_tag() -> &'static str {
"linux-arm"
}

#[derive(Clone, Copy, Debug)]
pub enum Compiler {
Clang,
Expand Down
6 changes: 6 additions & 0 deletions src/android/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ pub fn gen(
map.insert("has-asset-packs", !asset_packs.is_empty());
map.insert("asset-packs", asset_packs);
map.insert("windows", cfg!(windows));
map.insert(
"termux",
cfg!(all(feature = "termux", target_os = "android")),
);
#[cfg(all(feature = "termux", target_os = "android"))]
map.insert("termux_prefix", crate::os::prefix().unwrap_or_default());
},
filter.fun(),
)
Expand Down
7 changes: 6 additions & 1 deletion src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
cli::{Report, Reportable, TextWrapper},
},
};

use std::{
fs, io,
path::{Path, PathBuf},
Expand Down Expand Up @@ -93,7 +94,7 @@ impl Reportable for Error {
pub fn exec(
wrapper: &TextWrapper,
non_interactive: bool,
skip_dev_tools: bool,
#[allow(unused)] skip_dev_tools: bool, // not used in termux
skip_targets_install: bool,
#[cfg_attr(not(target_os = "macos"), allow(unused))] reinstall_deps: bool,
open_in_editor: bool,
Expand Down Expand Up @@ -133,6 +134,10 @@ pub fn exec(
fs::create_dir_all(&asset_dir)
.map_err(|cause| Error::AssetDirCreationFailed { asset_dir, cause })?;
}

#[cfg(all(feature = "termux", target_os = "android"))]
let skip_dev_tools = false;

if !skip_dev_tools && util::command_present("code").map_err(Error::CodeCommandPresentFailed)? {
code_command()
.before_spawn(move |cmd| {
Expand Down
13 changes: 12 additions & 1 deletion src/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,18 @@ mod windows;
#[cfg(windows)]
pub use self::windows::*;

#[cfg(not(any(target_os = "macos", target_os = "linux", windows)))]
#[cfg(all(feature = "termux", target_os = "android"))]
mod termux;

#[cfg(all(feature = "termux", target_os = "android"))]
pub use self::termux::*;

#[cfg(not(any(
target_os = "macos",
target_os = "linux",
windows,
all(feature = "termux", target_os = "android")
)))]
compile_error!("Host platform not yet supported by cargo-mobile2! We'd love if you made a PR to add support for this platform ❤️");

// TODO: we should probably expose common functionality throughout `os` in a
Expand Down
153 changes: 153 additions & 0 deletions src/os/termux/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use thiserror::Error;

pub use crate::{
env::{Env, ExplicitEnv},
util::ln,
};

#[derive(Debug, Error)]
pub enum Error {
#[error("termux is not installed")]
NoTermux,
#[error("termux::API is not installed")]
NoTermuxApi,
#[error("IO error")]
IoError(#[from] std::io::Error),
#[error("Env error")]
VarError(#[from] std::env::VarError),
}

#[derive(Debug, Error)]
pub enum DetectEditorError {
#[error("No default editor is set: xdg-mime queries for \"text/rust\" and \"text/plain\" both failed")]
NoDefaultEditorSet,
}

#[derive(Debug, Error)]
pub enum OpenFileError {
#[error("Failed to run {command}: {error}")]
CommandFailed {
command: String,
error: std::io::Error,
},
#[error("Command parsing failed")]
CommandParsingFailed,
#[error("termux::API is not installed")]
NoTermuxApi,
}

#[derive(Debug)]
pub struct Application {
exec_command: OsString,
// icon: Option<OsString>,
// xdg_entry_path: PathBuf,
}

impl Application {
pub fn detect_editor() -> Result<Self, DetectEditorError> {
let editor = std::env::var("EDITOR").map_err(|_| DetectEditorError::NoDefaultEditorSet)?;
Ok(Self {
exec_command: editor.into(),
// icon: None,
// xdg_entry_path: PathBuf::new(),
})
}

pub fn open_file(&self, path: impl AsRef<Path>) -> Result<(), OpenFileError> {
let path = path.as_ref();
duct::cmd(&self.exec_command, path)
.run()
.map(|_| ())
.map_err(|error| OpenFileError::CommandFailed {
command: self.exec_command.to_string_lossy().to_string(),
error,
})
}
}

pub fn open_file_with(
_application: impl AsRef<OsStr>,
path: impl AsRef<OsStr>,
_env: &Env,
) -> Result<(), OpenFileError> {
let _ = termux_api_version().ok_or(OpenFileError::NoTermuxApi)?;
duct::cmd("termux-open", vec![path.as_ref()])
.run()
.map(|_| ())
.map_err(|error| OpenFileError::CommandFailed {
command: "termux-open".to_string(),
error,
})
}

/// Open file with the $EDITOR (or nano)
pub fn open_with_editor(path: impl AsRef<Path>) -> Result<std::process::Output, Error> {
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
Ok(duct::cmd(editor, path.as_ref()).run()?)
}

/// Open file with termux-open (requires termux::API)
pub fn open_with_termux(path: impl AsRef<Path>) -> Result<std::process::Output, Error> {
let _ = termux_api_version().ok_or(Error::NoTermuxApi);
Ok(duct::cmd("termux-open", path.as_ref()).run()?)
}

/// Installs an apk by running termux-open, returns an error
/// when termux::API is not installed or termux-open fails.
pub fn run_apk(apk_path: impl AsRef<Path>) -> Result<std::process::Output, Error> {
open_with_termux(apk_path)
}

/// Returns the `PathBuf` where termux is installed (eg ``/data/data/com.termux/files/usr``)
pub fn prefix() -> Result<PathBuf, Error> {
let prefix = std::env::var("PREFIX")?;
Ok(PathBuf::from(prefix))
}

/// Returns `Some(api version)` or None when termux::API version is
/// not available.
pub fn termux_api_version() -> Option<String> {
std::env::var("TERMUX_API_VERSION").ok()
}

/// Returns `Some(api version)` or None when termux::API version is
/// not available.
pub fn termux_version() -> Option<String> {
std::env::var("TERMUX_VERSION").ok()
}

pub fn command_path(name: &str) -> std::io::Result<std::process::Output> {
duct::cmd("sh", ["-c", format!("command -v {name}").as_str()]).run()
}

pub fn code_command() -> duct::Expression {
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nano".to_string());
duct::cmd!(editor)
}

pub fn replace_path_separator(path: OsString) -> OsString {
path
}

pub mod consts {
pub const CLANG: &str = "clang";
pub const CLANGXX: &str = "clang++";
pub const AR: &str = "ar";
pub const LD: &str = "ld";
pub const READELF: &str = "readelf";
pub const NDK_STACK: &str = "ndk-stack";
}

pub mod info {
use super::Error;
use crate::os::Info;

pub fn check() -> Result<Info, Error> {
Ok(Info {
name: "termux".to_string(),
version: super::termux_version().ok_or(Error::NoTermux)?,
})
}
}
Loading