Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/scripts/crossbundle_release.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/bash
# This file is intended to be run from Github Actions publish.yml.

[[ -z "$RELEASE_FLOW_TARGET" ]] && {
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/publish_crates.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/bash
# This file is intended to be run from Github Actions publish.yml.

# Legends say that the order of the crates should be kept.
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Build APK
run: |
cd examples/macroquad-3d
crossbundle build android --release --quad
crossbundle build android --apk --quad --release

apple-build-macos:
name: Build Apple example on macOS latest
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Build Apple app
run: |
cd ~/example/
crossbundle build android --release --quad
crossbundle build android --apk --release --quad

clean:
name: Check code format
Expand All @@ -82,6 +82,11 @@ jobs:
run: cargo +nightly fmt --all -- --check
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings -A clippy::unnecessary-unwrap -A clippy::too-many-arguments
# Blocked by https://github.com/rust-lang/rust/issues/99261
# - name: Run clippy on crossbundle
# run: |
# cd crossbundle/cli
# cargo clippy --all-targets --all-features -- -D warnings -A clippy::unnecessary-unwrap -A clippy::too-many-arguments
- name: Check for deadlinks
run: |
cargo install cargo-deadlinks
Expand Down
124 changes: 123 additions & 1 deletion crossbundle/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ name = "android.permission.INTERNET"

# Specifies that an app wants a particular permission, but only if the app is installed on a device running
# Android 6.0 (API level 23) or higher. If the device is running API level 22 or lower, the app does not have the specified permission.
#
#
# See https://developer.android.com/guide/topics/manifest/uses-permission-sdk-23-element
[[package.metadata.android.permissions_sdk_23]]
name = "android.permission.WRITE_EXTERNAL_STORAGE"
Expand Down Expand Up @@ -162,6 +162,128 @@ SUBCOMMANDS:
device/emulator
```

Result of `crossbundle build android -h`:

```text
Starts the process of building/packaging/signing of the rust crate for Android

USAGE:
crossbundle build android [OPTIONS]

OPTIONS:
--aab
Generating native aab without Java. By default crossbow generating gradle project

--all-features
Activate all available features of selected package

--apk
Generating native apk without Java. By default crossbow generating gradle project

--example <EXAMPLE>
Build the specified example

--export-path <EXPORT_PATH>
Path to export Gradle project. By default exports to `target/android/` folder

--features <FEATURES>
Space or comma separated list of features to activate. These features only apply to the
current directory's package. Features of direct dependencies may be enabled with
`<dep-name>/<feature-name>` syntax. This flag may be specified multiple times, which
enables all specified features

-h, --help
Print help information

--lib <LIB>
Compile rust code as a dynamic library [default: crossbow-android]

--no-default-features
Do not activate the `default` feature of the current directory's package

--quad
Specifies to build macroquad-based game with Sokol application wrapper

--release
Build optimized artifact with the `release` profile

--sign-key-alias <SIGN_KEY_ALIAS>
Signing key alias

--sign-key-pass <SIGN_KEY_PASS>
Signing key password

--sign-key-path <SIGN_KEY_PATH>
Path to the signing key

--target <TARGET>
Build for the given android architecture. Supported targets are:
`armv7-linux-androideabi`, `aarch64-linux-android`, `i686-linux-android`,
`x86_64-linux-android` [default: aarch64-linux-android]

--target-dir <TARGET_DIR>
Directory for generated artifact and intermediate files
```

Result of `crossbundle build apple -h`:

```text
Starts the process of building/packaging/signing of the rust crate for iOS

USAGE:
crossbundle build apple [OPTIONS]

OPTIONS:
--all-features
Activate all available features of selected package

--bin <BIN>
Specify custom cargo binary

--example <EXAMPLE>
Build the specified example

--features <FEATURES>
Space or comma separated list of features to activate. These features only apply to the
current directory's package. Features of direct dependencies may be enabled with
`<dep-name>/<feature-name>` syntax. This flag may be specified multiple times, which
enables all specified features

-h, --help
Print help information

--identity <IDENTITY>
The id of the identity used for signing. It won't start the signing process until you
provide this flag

--no-default-features
Do not activate the `default` feature of the current directory's package

--profile-name <PROFILE_NAME>
Provisioning profile name to find in this directory:
`~/Library/MobileDevice/Provisioning\ Profiles/`

--profile-path <PROFILE_PATH>
Absolute path to provisioning profile

--quad
Specifies to build macroquad-based game with Sokol application wrapper

--release
Build optimized artifact with the `release` profile

--target <TARGET>
Build for the given apple architecture. Supported targets are: `aarch64-apple-ios`,
`aarch64-apple-ios-sim`, `armv7-apple-ios`, `armv7s-apple-ios`, `i386-apple-ios`,
`x86_64-apple-ios` [default: aarch64-apple-ios-sim]

--target-dir <TARGET_DIR>
Directory for generated artifact and intermediate files

--team-identifier <TEAM_IDENTIFIER>
The team identifier of your signing identity
```

## ❌ Troubleshooting

### Shared library "<lib_name>" not found
Expand Down
39 changes: 20 additions & 19 deletions crossbundle/cli/src/commands/build/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@ pub struct AndroidBuildCommand {
/// `i686-linux-android`, `x86_64-linux-android`
#[clap(long, default_value = "aarch64-linux-android")]
pub target: Vec<AndroidTarget>,
/// Generating aab. By default crossbow generating apk
/// Generating native aab without Java. By default crossbow generating gradle project
#[clap(long)]
pub aab: bool,
/// Generating native apk without Java. By default crossbow generating gradle project
#[clap(long)]
pub apk: bool,
/// Compile rust code as a dynamic library [default: crossbow-android]
#[clap(long, default_missing_value = "crossbow_android")]
pub lib: Option<String>,
/// Compile rust code as a dynamic library, generate Gradle project and build
/// generate apk/aab. If value provided - should be in Path format and it will
/// be used as a path to export Gradle project [default: :target]
#[clap(long, default_missing_value = ":target")]
pub gradle: Option<String>,
/// Path to export Gradle project. By default exports to `target/android/` folder
#[clap(long)]
pub export_path: Option<PathBuf>,
/// Path to the signing key
#[clap(long, requires_all = &["sign-key-pass", "sign-key-alias"])]
pub sign_key_path: Option<PathBuf>,
Expand All @@ -55,12 +56,12 @@ impl AndroidBuildCommand {
let context = BuildContext::new(config, self.shared.target_dir.clone())?;
if self.aab {
self.execute_aab(config, &context)?;
} else if self.apk {
self.execute_apk(config, &context)?;
} else if let Some(lib_name) = &self.lib {
self.build_rust_lib(config, &context, lib_name, None)?;
} else if let Some(export_path) = &self.gradle {
self.build_gradle(config, &context, export_path)?;
} else {
self.execute_apk(config, &context)?;
self.build_gradle(config, &context, &self.export_path)?;
}
Ok(())
}
Expand All @@ -70,18 +71,18 @@ impl AndroidBuildCommand {
&self,
config: &Config,
context: &BuildContext,
export_path: &str,
) -> crate::error::Result<PathBuf> {
export_path: &Option<PathBuf>,
) -> crate::error::Result<(AndroidManifest, AndroidSdk, PathBuf)> {
let profile = self.shared.profile();
let example = self.shared.example.as_ref();
let (_, target_dir, package_name) = Self::needed_project_dirs(example, context)?;

config.status_message("Starting gradle build process", &package_name)?;
let android_build_dir = if export_path == ":target" {
target_dir.join("android").join(&package_name)
} else {
let android_build_dir = if let Some(export_path) = export_path {
std::fs::create_dir_all(export_path)?;
dunce::canonicalize(export_path)?
} else {
target_dir.join("android").join(&package_name)
};

config.status("Generating gradle project")?;
Expand All @@ -94,7 +95,7 @@ impl AndroidBuildCommand {

// Get AndroidManifest.xml from file or generate from Cargo.toml
let (sdk, _, _) = Self::android_toolchain(context)?;
let (_android_manifest, _manifest_path) = Self::android_manifest(
let (android_manifest, _manifest_path) = Self::android_manifest(
config,
context,
&sdk,
Expand All @@ -111,7 +112,7 @@ impl AndroidBuildCommand {
"Gradle project generated",
gradle_project_path.to_str().unwrap(),
)?;
Ok(gradle_project_path)
Ok((android_manifest, sdk, gradle_project_path))
}

/// Compile rust code as a dynamic library
Expand Down Expand Up @@ -176,7 +177,7 @@ impl AndroidBuildCommand {
let (sdk, ndk, target_sdk_version) = Self::android_toolchain(context)?;

let android_build_dir = target_dir.join("android").join(&package_name);
let native_build_dir = android_build_dir.join("native");
let native_build_dir = android_build_dir.join("native").join("apk");
let outputs_build_dir = android_build_dir.join("outputs");
if !outputs_build_dir.exists() {
std::fs::create_dir_all(&outputs_build_dir)?;
Expand Down Expand Up @@ -281,7 +282,7 @@ impl AndroidBuildCommand {
let (sdk, ndk, target_sdk_version) = Self::android_toolchain(context)?;

let android_build_dir = target_dir.join("android").join(&package_name);
let native_build_dir = android_build_dir.join("native");
let native_build_dir = android_build_dir.join("native").join("aab");
let outputs_build_dir = android_build_dir.join("outputs");
if !outputs_build_dir.exists() {
std::fs::create_dir_all(&outputs_build_dir)?;
Expand Down Expand Up @@ -319,7 +320,7 @@ impl AndroidBuildCommand {
std::fs::create_dir_all(&compiled_res_path)?;
}
let aapt2_compile = sdk.aapt2()?.compile_incremental(
dunce::simplified(&res),
dunce::simplified(res),
dunce::simplified(&compiled_res_path),
);
let compiled_res = aapt2_compile.run()?;
Expand Down
27 changes: 24 additions & 3 deletions crossbundle/cli/src/commands/install/sdkmanager.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::Path;

use clap::Parser;
use crossbundle_tools::{
error::CommandExt, tools::AndroidSdk, utils::Config, EXECUTABLE_SUFFIX_BAT,
Expand Down Expand Up @@ -157,10 +159,29 @@ impl SdkManagerInstallCommand {
/// Run sdkmanager command with specified flags and options
pub fn run(&self, _config: &Config) -> crate::error::Result<()> {
let sdk_root = AndroidSdk::sdk_install_path()?;
let sdkmanager_path = sdk_root.join("cmdline-tools").join("latest").join("bin");
let sdkmanager_bat = sdkmanager_path.join(format!("sdkmanager{}", EXECUTABLE_SUFFIX_BAT));
// Android studio install cmdline tools into SDK_ROOT/cmdline-tools/<version>/bin.
// Crossbundle install command ignores <version> directory so we need convert cmd-line-tools path to Option<T> to avoid confusion
let cmdline_tools_path = sdk_root.join("cmdline-tools").join("latest").join("bin");
if cmdline_tools_path.exists() {
let sdkmanager_path =
cmdline_tools_path.join(format!("sdkmanager{}", EXECUTABLE_SUFFIX_BAT));
self.sdkmanager_command(&sdkmanager_path, &sdk_root)?;
} else {
let sdkmanager_path = sdk_root
.join("cmdline-tools")
.join("bin")
.join(format!("sdkmanager{}", EXECUTABLE_SUFFIX_BAT));
self.sdkmanager_command(&sdkmanager_path, &sdk_root)?;
};
Ok(())
}

let mut sdkmanager = std::process::Command::new(sdkmanager_bat);
pub fn sdkmanager_command(
&self,
sdkmanager_path: &Path,
sdk_root: &Path,
) -> crate::error::Result<()> {
let mut sdkmanager = std::process::Command::new(sdkmanager_path);
if let Some(sdk_root) = &self.sdk_root {
sdkmanager.arg(sdk_root);
} else {
Expand Down
30 changes: 18 additions & 12 deletions crossbundle/cli/src/commands/run/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,7 @@ impl AndroidRunCommand {
/// Deployes and runs application in AAB or APK format on your device or emulator
pub fn run(&self, config: &Config) -> Result<()> {
let context = BuildContext::new(config, self.build_command.shared.target_dir.clone())?;
if let Some(export_path) = &self.build_command.gradle {
let gradle_project_path =
self.build_command
.build_gradle(config, &context, export_path)?;
let mut gradle = gradle_init()?;
gradle
.arg("installDebug")
.arg("-p")
.arg(dunce::simplified(&gradle_project_path));
gradle.output_err(true)?;
} else if self.build_command.aab {
if self.build_command.aab {
let (android_manifest, sdk, aab_path, package_name, key) =
self.build_command.execute_aab(config, &context)?;
config.status("Generating apks")?;
Expand All @@ -48,7 +38,7 @@ impl AndroidRunCommand {
config.status("Run finished successfully")?;
} else if self.build_command.lib.is_some() {
config.status("Can not run dynamic library")?;
} else {
} else if self.build_command.apk {
let (android_manifest, sdk, apk_path) =
self.build_command.execute_apk(config, &context)?;
config.status("Starting run process")?;
Expand All @@ -57,6 +47,22 @@ impl AndroidRunCommand {
config.status("Starting APK file")?;
android::start_apk(&sdk, &android_manifest.package)?;
config.status("Run finished successfully")?;
} else {
let (android_manifest, sdk, gradle_project_path) = self.build_command.build_gradle(
config,
&context,
&self.build_command.export_path,
)?;
config.status("Installing APK file on device")?;
let mut gradle = gradle_init()?;
gradle
.arg("installDebug")
.arg("-p")
.arg(dunce::simplified(&gradle_project_path));
gradle.output_err(true)?;
config.status("Starting APK file")?;
android::start_apk(&sdk, &android_manifest.package)?;
config.status("Run finished successfully")?;
}
Ok(())
}
Expand Down
Loading