Merge pull request #4431 from TranceLove/feature/rust-file-operations

This commit is contained in:
Emmanuel M
2025-12-12 08:37:45 -03:00
committed by GitHub
20 changed files with 1165 additions and 65 deletions
+66
View File
@@ -27,6 +27,39 @@ jobs:
ndk-version: r28c
link-to-sdk: true
local-cache: true
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Check formatting using spotless
uses: gradle/actions/setup-gradle@v3
with:
@@ -42,6 +75,39 @@ jobs:
with:
distribution: "temurin"
java-version: 17
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
@@ -46,6 +46,39 @@ jobs:
ndk-version: r28c
link-to-sdk: true
local-cache: true
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
@@ -20,6 +20,39 @@ jobs:
ndk-version: r28c
link-to-sdk: true
local-cache: true
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
+36
View File
@@ -23,6 +23,7 @@ jobs:
distribution: "temurin"
java-version: 17
- name: Set up NDK
id: setup-ndk
uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410
with:
ndk-version: r28c
@@ -32,6 +33,8 @@ jobs:
uses: gradle/actions/setup-gradle@v3
with:
arguments: spotlessCheck --stacktrace
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
build:
name: Build debug and run Jacoco tests
@@ -43,6 +46,39 @@ jobs:
with:
distribution: "temurin"
java-version: 17
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
+33
View File
@@ -45,6 +45,39 @@ jobs:
with:
distribution: "temurin"
java-version: 17
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Cache Rust dependencies
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
file_operations/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Setup Rust for Android
run: |
cd file_operations
chmod +x setup_rust_android.sh
./setup_rust_android.sh
- name: Verify Rust setup
run: |
cd file_operations
echo "🔍 Verifying Rust Android targets..."
rustup target list --installed | grep android || echo "No Android targets found"
echo "🔍 Verifying cargo configuration..."
if [ -f .cargo/config.toml ]; then
echo "✅ .cargo/config.toml exists"
else
echo "❌ .cargo/config.toml missing"
exit 1
fi
- name: Build with Gradle
uses: gradle/actions/setup-gradle@v3
with:
+1
View File
@@ -38,6 +38,7 @@ If we feel your PR is a significant help to us, we'll award you a bounty with an
- Please follow [Android/JAVA code style](https://source.android.com/docs/setup/contribute/code-style) for writing any code. Please see [this](https://github.com/TeamAmaze/AmazeFileManager/issues/986) issue too.
- Also, follow [Android Material Design guidelines](https://m3.material.io/get-started) in case you make changes to any UI element.
- The `file_operations` module contains native code in Rust. Please see [file_operations/src/main/rust/README.md](file_operations/src/main/rust/README.md) for more details on how to work with it.
## PS: please make sure to
+1
View File
@@ -11,6 +11,7 @@ buildscript {
classpath libs.kotlin.gradle.plugin
classpath libs.easylauncher
classpath libs.jacoco.android.plugin
classpath libs.rust.android.gradle
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
@@ -0,0 +1,24 @@
# Cargo configuration for Android cross-compilation
# This file configures the linkers for different Android architectures
# Generated from config.toml.template by setup_rust_android.sh
[target.aarch64-linux-android]
linker = "{{NDK_PATH}}/aarch64-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
[target.armv7-linux-androideabi]
linker = "{{NDK_PATH}}/armv7a-linux-androideabi{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
[target.i686-linux-android]
linker = "{{NDK_PATH}}/i686-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
[target.x86_64-linux-android]
linker = "{{NDK_PATH}}/x86_64-linux-android{{API_LEVEL}}-clang{{EXE_SUFFIX}}"
# Note: This file is auto-generated. Do not edit manually.
# To regenerate, run: ./setup_rust_android.sh
#
# Configuration details:
# - API level: {{API_LEVEL}} (matching minSdkVersion from build.gradle)
# - Platform: {{PLATFORM}}
# - NDK version: {{NDK_VERSION}}
# - Android SDK: {{ANDROID_SDK_PATH}}
+10 -4
View File
@@ -1,5 +1,11 @@
/build
# Rust build artifacts
target/
**/*.rs.bk
Cargo.lock
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Auto-generated configuration (generated from template)
.cargo/config.toml
# Android build artifacts
.cxx/
build/
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "rootoperations"
version = "0.1.0"
edition = "2024"
[lib]
name = "rootoperations"
crate-type = ["cdylib"]
path = "src/main/rust/lib.rs"
[dependencies]
jni = "0.21"
libc = "0.2"
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11"
log = "0.4"
+27 -12
View File
@@ -1,10 +1,12 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'com.google.devtools.ksp'
apply plugin: 'org.mozilla.rust-android-gradle.rust-android'
android {
namespace "com.amaze.filemanager.fileoperations"
compileSdk libs.versions.compileSdk.get().toInteger()
ndkVersion libs.versions.ndk.get()
defaultConfig {
minSdkVersion libs.versions.minSdk.get().toInteger()
@@ -14,12 +16,6 @@ android {
consumerProguardFiles "consumer-rules.pro"
multiDexEnabled true
externalNativeBuild {
cmake {
cFlags '-std=c11'
}
}
}
buildTypes {
@@ -46,21 +42,40 @@ android {
}
}
externalNativeBuild {
cmake {
path file('src/main/c/CMakeLists.txt')
}
}
kotlinOptions {
jvmTarget = '17'
}
}
// Rust configuration
cargo {
module = "./"
libname = "rootoperations"
targets = ["arm", "arm64", "x86", "x86_64"]
profile = "release"
// Optional: Use cargo and rustc from the path
rustcCommand = "rustc"
cargoCommand = "cargo"
pythonCommand = "python3"
}
kotlin {
jvmToolchain(17)
}
// Ensure Rust libraries are built before Android tasks
afterEvaluate {
android.libraryVariants.all { variant ->
variant.outputs.all { output ->
def buildTask = tasks.findByName("cargoBuild")
if (buildTask) {
tasks.getByName("merge${variant.name.capitalize()}JniLibFolders").dependsOn buildTask
}
}
}
}
dependencies {
modules {
module("org.bouncycastle:bcprov-jdk15on") {
+277
View File
@@ -0,0 +1,277 @@
# C++ to Rust Migration Guide
This document outlines the migration from C++ to Rust in the `file_operations` module using Mozilla's Rust Android Gradle plugin.
## Overview
The migration replaces the C++ implementation in `src/main/c/rootoperations.c` with a Rust implementation while maintaining the same JNI interface for seamless integration with the existing Kotlin code.
## What Was Migrated
### Original C++ Code
- **File**: `src/main/c/rootoperations.c`
- **Function**: `Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory`
- **Purpose**: Checks if a given path is a directory using the `stat()` system call
- **Build System**: CMake
### New Rust Code
- **File**: `src/lib.rs`
- **Function**: Same JNI function signature
- **Purpose**: Identical functionality using Rust's `std::fs::metadata()`
- **Build System**: Cargo with Mozilla's Rust Android Gradle plugin
## Prerequisites
1. **Rust Installation**: Install Rust from https://rustup.rs/
2. **Android NDK**: Install via Android Studio → Tools → SDK Manager → SDK Tools → NDK
3. **Environment Variables**: Set `ANDROID_HOME` or `ANDROID_NDK_ROOT`
4. **Rust-compatible IDE**: Before Android Studio gets first-class Rust support, consider using [Visual Studio Code](https://code.visualstudio.com) with Rust extensions for coding.
## Setup Instructions
### 1. Automated Setup (Recommended)
The project includes an automated setup script that detects your system configuration and generates the correct configuration files:
```bash
cd file_operations
./setup_rust_android.sh
```
This script will:
- **Auto-detect your operating system** (macOS, Linux, Windows)
- **Find your Android SDK** path (from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, or common locations)
- **Detect the latest NDK version** installed
- **Extract API level** from your `build.gradle`
- **Install necessary Rust targets** for Android
- **Generate `.cargo/config.toml`** with correct linker paths
### 2. Manual Setup (If Needed)
If the automated setup doesn't work, you can manually configure:
1. **Install Android Rust targets:**
```bash
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
```
2. **Create `.cargo/config.toml`** based on your system:
- Copy `.cargo/config.toml.template` to `.cargo/config.toml`
- Replace placeholders with your actual paths:
- `{{NDK_PATH}}`: Path to your NDK toolchain binaries
- `{{API_LEVEL}}`: Your minimum API level (e.g., 21)
- `{{EXE_SUFFIX}}`: `.exe` for Windows, empty for Unix systems
### 3. System Requirements
**Android SDK/NDK:**
- Android SDK installed (via Android Studio or command-line tools)
- NDK installed (via SDK Manager)
- Set `ANDROID_HOME` environment variable
**Supported Platforms:**
- macOS (`darwin-x86_64`)
- Linux (`linux-x86_64`)
- Windows (`windows-x86_64`)
### 4. Build Configuration
The `build.gradle` file now includes:
- Mozilla's Rust Android Gradle plugin
- Cargo configuration for cross-compilation
- Automatic building of Rust code during Android build
## File Structure
```
file_operations/
├── src/
│ └── main/
│ └── rust/
│ ├── lib.rs # Main library file (module organizer)
│ └── rootoperations.rs # Root operations functions
├── .cargo/
│ ├── config.toml # Auto-generated Cargo configuration for Android
│ └── config.toml.template # Template for generating config.toml
├── Cargo.toml # Rust project configuration
├── setup_rust_android.sh # Automated setup script
├── build.gradle # Updated with Rust plugin
└── RUST_MIGRATION.md # This file
```
## Key Changes
### 1. Build System
- **Before**: CMake with `externalNativeBuild`
- **After**: Cargo with Mozilla's Rust Android Gradle plugin
### 2. Dependencies
- **Added**: `jni`, `libc`, `android_logger`, `log` crates
- **Removed**: CMake build configuration
### 3. Function Implementation
- **Before**: C function using `stat()` system call
- **After**: Rust function using `std::fs::metadata()`
### 4. Error Handling
- **Before**: errno-based error handling
- **After**: Rust's `Result` type with proper error handling
### 5. Modular Structure
- **Before**: Single C file with all functions
- **After**: Organized module structure:
- `lib.rs`: Main library file that organizes and re-exports modules
- `rootoperations.rs`: Root-specific operations (migrated from C)
## Testing
The Rust implementation includes unit tests:
```bash
cd file_operations
cargo test
```
## Building
The Rust code is automatically built when you build the Android project:
```bash
./gradlew :file_operations:build
```
## Advantages of the Migration
1. **Memory Safety**: Rust prevents memory leaks and buffer overflows
2. **Better Error Handling**: Rust's type system catches errors at compile time
3. **Cross-Platform**: Easier to port to other platforms
4. **Modern Tooling**: Cargo package manager and ecosystem
5. **Performance**: Comparable performance to C++ with better safety
## Troubleshooting
### Configuration Issues
First, try re-running the automated setup script:
```bash
cd file_operations
./setup_rust_android.sh
```
This will re-detect your system configuration and regenerate `.cargo/config.toml`.
### NDK Path Issues
If the automated setup fails to find your NDK, ensure your Android SDK is properly configured:
```bash
export ANDROID_HOME=/path/to/android/sdk
# The script will automatically find the NDK in $ANDROID_HOME/ndk/[version]
```
### Linker Not Found
If you see linker errors, verify your NDK installation:
1. Check that NDK is installed via Android Studio → SDK Manager → SDK Tools → NDK
2. The setup script will automatically use the latest NDK version found
3. If needed, manually verify the generated `.cargo/config.toml` paths exist
### Build Errors
Clean and rebuild:
```bash
cd file_operations
cargo clean
cd ..
./gradlew clean
./gradlew :file_operations:build
```
### Cross-Platform Issues
The setup script supports:
- **macOS**: Automatically detects `darwin-x86_64` platform
- **Linux**: Automatically detects `linux-x86_64` platform
- **Windows**: Automatically detects `windows-x86_64` platform and adds `.exe` suffix
If you encounter platform-specific issues, check the generated linker paths in `.cargo/config.toml`.
## Extending the Codebase
The modular structure makes it easy to add new functionality:
### Adding a New Module
1. **Create a new `.rs` file** in `src/main/rust/` (e.g., `network_ops.rs`)
2. **Add the module declaration** in `lib.rs`:
```rust
pub mod network_ops;
```
3. **Re-export functions** in `lib.rs` for easy access:
```rust
pub use network_ops::{
download_file,
upload_file,
};
```
### Adding JNI Functions
To add new JNI functions that can be called from Kotlin:
1. **Add the function** in the appropriate module:
```rust
#[no_mangle]
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_newFunction(
env: JNIEnv,
_class: JClass,
param: JString,
) -> jboolean {
// Implementation
}
```
2. **Add the corresponding method** in `NativeOperations.kt`:
```kotlin
@JvmStatic
external fun newFunction(param: String?): Boolean
```
### Example: Adding Compression Operations
```rust
// In src/main/rust/compression_ops.rs
pub mod compression_ops {
use jni::objects::{JClass, JString};
use jni::JNIEnv;
use jni::sys::jboolean;
#[no_mangle]
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_compressFile(
env: JNIEnv,
_class: JClass,
source: JString,
destination: JString,
) -> jboolean {
// Implementation
}
}
```
## Future Enhancements
1. **Add More Functions**: Migrate additional C++ functions to Rust
2. **Async Operations**: Use Rust's async/await for non-blocking operations
3. **Better Logging**: Enhanced logging with structured logs
4. **Performance Monitoring**: Add metrics collection
5. **Cross-Platform Support**: Extend to other platforms (iOS, Windows, etc.)
6. **Error Types**: Create custom error types for better error handling
7. **Benchmarking**: Add performance benchmarks for critical operations
## Resources
- [Mozilla Rust Android Gradle Plugin](https://github.com/mozilla/rust-android-gradle)
- [JNI for Rust](https://docs.rs/jni/latest/jni/)
- [Android NDK Documentation](https://developer.android.com/ndk)
- [Rust Cross-Compilation](https://rust-lang.github.io/rustup/cross-compilation.html)
## Support
For questions or issues with the migration, please refer to:
- [Mozilla Rust Android Gradle Issues](https://github.com/mozilla/rust-android-gradle/issues)
- [Rust Android Development](https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html)
+182
View File
@@ -0,0 +1,182 @@
#!/bin/bash
# Setup script for Rust Android development
# This script:
# 1. Installs necessary Rust targets for Android
# 2. Auto-detects system configuration
# 3. Generates .cargo/config.toml with correct linker paths
set -e
echo "🦀 Setting up Rust for Android development..."
# Function to detect operating system
detect_os() {
case "$(uname -s)" in
Darwin*) echo "darwin-x86_64" ;;
Linux*) echo "linux-x86_64" ;;
CYGWIN*|MINGW*|MSYS*) echo "windows-x86_64" ;;
*) echo "unknown" ;;
esac
}
# Function to find Android SDK path
find_android_sdk() {
if [ -n "$ANDROID_HOME" ]; then
echo "$ANDROID_HOME"
elif [ -n "$ANDROID_SDK_ROOT" ]; then
echo "$ANDROID_SDK_ROOT"
elif [ -d "$HOME/Android/Sdk" ]; then
echo "$HOME/Android/Sdk"
elif [ -d "$HOME/Library/Android/sdk" ]; then
echo "$HOME/Library/Android/sdk"
else
echo ""
fi
}
# Function to find the latest NDK version
find_ndk_version() {
local sdk_path="$1"
local ndk_dir="$sdk_path/ndk"
if [ -d "$ndk_dir" ]; then
# Find the latest version (highest version number)
ls -1 "$ndk_dir" | sort -V | tail -n 1
else
echo ""
fi
}
# Function to extract API level from build.gradle and version catalog
extract_api_level() {
local build_gradle="build.gradle"
local version_catalog="../gradle/libs.versions.toml"
# First, check if build.gradle uses version catalog
if [ -f "$build_gradle" ] && grep -q "libs.versions.minSdk" "$build_gradle"; then
# Extract from version catalog
if [ -f "$version_catalog" ]; then
grep "^minSdk" "$version_catalog" | cut -d '"' -f 2
else
echo "21" # Default fallback
fi
elif [ -f "$build_gradle" ]; then
# Look for direct minSdkVersion in build.gradle
grep -E "minSdkVersion|minSdk" "$build_gradle" | head -n 1 | sed 's/.*[[:space:]]\([0-9]\+\).*/\1/'
else
echo "21" # Default fallback
fi
}
# Detect system configuration
OS_PLATFORM=$(detect_os)
echo "📱 Detected OS: $OS_PLATFORM"
if [ "$OS_PLATFORM" = "unknown" ]; then
echo "❌ Unsupported operating system. Please configure manually."
exit 1
fi
# Find Android SDK
ANDROID_SDK_PATH=$(find_android_sdk)
if [ -z "$ANDROID_SDK_PATH" ]; then
echo "❌ Android SDK not found. Please set ANDROID_HOME or ANDROID_SDK_ROOT environment variable."
echo " Or install Android SDK to ~/Android/Sdk (Linux) or ~/Library/Android/sdk (macOS)"
exit 1
fi
echo "📁 Found Android SDK: $ANDROID_SDK_PATH"
# Find NDK version
NDK_VERSION=$(find_ndk_version "$ANDROID_SDK_PATH")
if [ -z "$NDK_VERSION" ]; then
echo "❌ NDK not found in $ANDROID_SDK_PATH/ndk"
echo " Please install NDK through Android Studio SDK Manager"
exit 1
fi
echo "🔨 Found NDK version: $NDK_VERSION"
# Extract API level
API_LEVEL=$(extract_api_level)
echo "📋 Using API level: $API_LEVEL"
# Set executable suffix for Windows
EXE_SUFFIX=""
if [ "$OS_PLATFORM" = "windows-x86_64" ]; then
EXE_SUFFIX=".exe"
fi
# Construct NDK path
NDK_PATH="$ANDROID_SDK_PATH/ndk/$NDK_VERSION/toolchains/llvm/prebuilt/$OS_PLATFORM/bin"
# Verify NDK path exists
if [ ! -d "$NDK_PATH" ]; then
echo "❌ NDK toolchain not found at: $NDK_PATH"
echo " Please check your NDK installation"
exit 1
fi
echo "🛠️ NDK toolchain path: $NDK_PATH"
# Install Rust targets for Android
echo "📦 Installing Rust targets for Android..."
rustup target add aarch64-linux-android
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
# Create .cargo directory if it doesn't exist
mkdir -p .cargo
# Generate config.toml from template
echo "⚙️ Generating .cargo/config.toml..."
if [ ! -f ".cargo/config.toml.template" ]; then
echo "❌ Template file .cargo/config.toml.template not found"
exit 1
fi
# Use sed to replace placeholders in template
sed -e "s|{{NDK_PATH}}|$NDK_PATH|g" \
-e "s|{{API_LEVEL}}|$API_LEVEL|g" \
-e "s|{{EXE_SUFFIX}}|$EXE_SUFFIX|g" \
-e "s|{{PLATFORM}}|$OS_PLATFORM|g" \
-e "s|{{NDK_VERSION}}|$NDK_VERSION|g" \
-e "s|{{ANDROID_SDK_PATH}}|$ANDROID_SDK_PATH|g" \
.cargo/config.toml.template > .cargo/config.toml
echo "✅ Successfully generated .cargo/config.toml"
# Verify generated linkers exist
echo "🔍 Verifying linkers..."
LINKERS=(
"aarch64-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
"armv7a-linux-androideabi${API_LEVEL}-clang${EXE_SUFFIX}"
"i686-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
"x86_64-linux-android${API_LEVEL}-clang${EXE_SUFFIX}"
)
for linker in "${LINKERS[@]}"; do
if [ -f "$NDK_PATH/$linker" ]; then
echo " ✅ $linker"
else
echo " ❌ $linker (not found)"
fi
done
echo ""
echo "🎉 Rust Android setup complete!"
echo ""
echo "📋 Configuration summary:"
echo " - OS Platform: $OS_PLATFORM"
echo " - Android SDK: $ANDROID_SDK_PATH"
echo " - NDK Version: $NDK_VERSION"
echo " - API Level: $API_LEVEL"
echo " - Config file: .cargo/config.toml"
echo ""
echo "🚀 You can now build for Android using:"
echo " cargo build --target aarch64-linux-android"
echo " cargo build --target armv7-linux-androideabi"
echo " cargo build --target i686-linux-android"
echo " cargo build --target x86_64-linux-android"
+2 -5
View File
@@ -1,5 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.amaze.filemanager.fileoperations">
/
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.amaze.filemanager.fileoperations" />
-13
View File
@@ -1,13 +0,0 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
cmake_minimum_required(VERSION 3.10.2)
project("rootoperations")
add_library(rootoperations SHARED rootoperations.c)
find_library(log-lib log)
target_link_libraries(rootoperations ${log-lib})
target_link_options(${CMAKE_PROJECT_NAME} PRIVATE "-Wl,-z,max-page-size=16384")
@@ -1,29 +0,0 @@
#include <jni.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
JNIEXPORT jboolean JNICALL Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory(
JNIEnv * env,
jobject thiz,
jstring path
) {
struct stat path_stat;
const char * cPath = (*env)->GetStringUTFChars(env, path, NULL);
int returnCode = stat(cPath, &path_stat);
(*env)->ReleaseStringUTFChars(env, path, cPath);
if(returnCode == -1) {
switch (errno) {
case ELOOP:
return true;
default:
return false;
}
}
//This follows links
return S_ISDIR(path_stat.st_mode);
}
+216
View File
@@ -0,0 +1,216 @@
# Rust File Operations Module
This directory contains the Rust implementation of file operations for the Amaze File Manager, which
was migrated from C/C++ to Rust for more modern, maintainable more safe code, with Rust's inherent
type-safety and memory-safety features.
Please refer to [RUST_MIGRATION.md](../doc/RUST_MIGRATION.md) for detailed information
about the migration process, key changes, and advantages of using Rust.
## Module Structure
The code is organized into separate modules for better maintainability and extensibility:
### `lib.rs` - Main Library File
- **Purpose**: Organizes and re-exports functionality from various modules
- **Content**: Module declarations and re-exports for easy access
- **Note**: This is the entry point for the Rust library
### `rootoperations.rs` - Root Operations
- **Purpose**: Contains root-specific file operations (migrated from C++)
- **Functions**:
- `Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory` - JNI function for directory checking
- `check_is_directory` - Helper function to check if a path is a directory
- `check_path_exists` - Helper function to check if a path exists
- `check_is_file` - Helper function to check if a path is a file
- `get_file_size` - Helper function to get file size
## Usage
### From Kotlin/Java
The JNI functions are automatically exported and can be called from Kotlin:
```kotlin
// In NativeOperations.kt
@JvmStatic
external fun isDirectory(path: String?): Boolean
// Usage
val isDir = NativeOperations.isDirectory("/some/path")
```
### From Rust (Internal)
Functions can be called directly within the Rust codebase:
```rust
use crate::rootoperations::check_is_directory;
let is_dir = check_is_directory("/some/path")?;
```
### Through Re-exports
Functions can be accessed through the main library:
```rust
use crate::check_is_directory;
let is_dir = check_is_directory("/some/path")?;
```
## Adding New Functionality
### 1. Create a New Module
Create a new `.rs` file (e.g., `network_ops.rs`):
```rust
// network_ops.rs
pub fn download_file(url: &str, destination: &str) -> Result<(), Box<dyn std::error::Error>> {
// Implementation
Ok(())
}
```
### 2. Add Module Declaration
In `lib.rs`, add the module declaration:
```rust
pub mod network_ops;
```
### 3. Re-export Functions (Optional)
In `lib.rs`, add re-exports for easy access:
```rust
pub use network_ops::{
download_file,
upload_file,
};
```
### 4. Add JNI Functions (If Needed)
If you need to call the function from Kotlin:
```rust
// In network_ops.rs
#[no_mangle]
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_downloadFile(
env: JNIEnv,
_class: JClass,
url: JString,
destination: JString,
) -> jboolean {
// Convert JString to Rust String
let url_str: String = env.get_string(url).unwrap().into();
let dest_str: String = env.get_string(destination).unwrap().into();
// Call the actual function
match download_file(&url_str, &dest_str) {
Ok(_) => 1, // true
Err(_) => 0, // false
}
}
```
Then add the corresponding method in `NativeOperations.kt`:
```kotlin
@JvmStatic
external fun downloadFile(url: String?, destination: String?): Boolean
```
## Testing
Each module includes its own tests. Run tests with:
```bash
cd file_operations
cargo test
```
To test a specific module:
```bash
cargo test rootoperations::tests
```
## Best Practices
1. **Separation of Concerns**: Keep related functions in the same module
2. **Error Handling**: Use `Result<T, E>` for functions that can fail
3. **Documentation**: Add doc comments to all public functions
4. **Testing**: Include unit tests for all functions
5. **JNI Safety**: Always handle JNI string conversion errors
6. **Logging**: Use the `log` crate for debugging information
## Example: Complete Function Implementation
```rust
// In your_module.rs
use jni::objects::{JClass, JString};
use jni::JNIEnv;
use jni::sys::jboolean;
/// Check if a file is executable
pub fn is_executable(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
let path = std::path::Path::new(path);
let metadata = path.metadata()?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
Ok(metadata.permissions().mode() & 0o111 != 0)
}
#[cfg(not(unix))]
{
// On non-Unix systems, check if it's a file with executable extension
Ok(path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| matches!(ext.to_lowercase().as_str(), "exe" | "bat" | "cmd"))
.unwrap_or(false))
}
}
/// JNI function to check if a file is executable
#[no_mangle]
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isExecutable(
env: JNIEnv,
_class: JClass,
path: JString,
) -> jboolean {
let path_str = match env.get_string(path) {
Ok(java_str) => java_str,
Err(_) => {
log::error!("Failed to get string from JString");
return 0;
}
};
let path_string: String = path_str.into();
match is_executable(&path_string) {
Ok(is_exec) => if is_exec { 1 } else { 0 },
Err(e) => {
log::error!("Error checking if file is executable: {}", e);
0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_executable() {
// Test with a known executable (on Unix systems)
#[cfg(unix)]
assert!(is_executable("/bin/ls").unwrap_or(false));
// Test with a non-executable file
assert!(!is_executable("/etc/passwd").unwrap_or(true));
}
}
```
This structure makes the codebase highly maintainable and easy to extend with new functionality while keeping everything organized and well-tested.
+29
View File
@@ -0,0 +1,29 @@
// Main library file for the Rust file operations module
// This file organizes and re-exports functionality from various modules
// Module declarations
pub mod rootoperations;
// Re-export commonly used functions for easier access
pub use rootoperations::{
check_is_directory,
check_path_exists,
check_is_file,
get_file_size,
};
// The JNI functions are automatically exported due to #[no_mangle] in their respective modules
// No need to re-export them here as they're called directly by the JVM
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_integration() {
// Test that we can access rootoperations functions through re-exports
assert!(check_path_exists("/").unwrap_or(false));
assert!(check_is_directory("/").unwrap_or(false));
assert!(!check_is_file("/").unwrap_or(true));
}
}
@@ -0,0 +1,174 @@
use jni::objects::{JClass, JString};
use jni::JNIEnv;
use jni::sys::jboolean;
use std::path::Path;
use std::sync::Once;
#[cfg(target_os = "android")]
use android_logger::Config;
#[cfg(target_os = "android")]
use log;
// Per Copilot review suggested, use Once::new to make Android logging init only once
static INIT: Once = Once::new();
/// Initialize Android logging (optional, but useful for debugging)
#[cfg(target_os = "android")]
fn init_logging() {
INIT.call_once(|| {
android_logger::init_once(
Config::default()
.with_min_level(log::Level::Debug)
.with_tag("RustRootOperations")
);
});
}
/// Log error message (Android only)
#[cfg(target_os = "android")]
fn log_error(msg: &str) {
log::error!("{}", msg);
}
/// Log error message (non-Android platforms)
#[cfg(not(target_os = "android"))]
fn log_error(msg: &str) {
eprintln!("ERROR: {}", msg);
}
/// JNI function to check if a path is a directory
/// This replaces the C function: Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_amaze_filemanager_fileoperations_filesystem_root_NativeOperations_isDirectory(
mut env: JNIEnv,
_class: JClass,
path: JString,
) -> jboolean {
#[cfg(target_os = "android")]
init_logging();
// Convert the JString to a Rust string
let path_str = match env.get_string(&path) {
Ok(java_str) => java_str,
Err(_) => {
log_error("Failed to get string from JString");
return 0; // false
}
};
let path_string: String = path_str.into();
// Check if the path is a directory using Rust's standard library
match check_is_directory(&path_string) {
Ok(is_dir) => {
if is_dir { 1 } else { 0 } // true : false
}
Err(e) => {
log_error(&format!("Error checking if path is directory: {}", e));
0 // false on error
}
}
}
/// Helper function to check if a path is a directory
/// This replicates the logic from the original C code
pub fn check_is_directory(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
let path = Path::new(path);
match path.metadata() {
Ok(metadata) => {
Ok(metadata.is_dir())
}
Err(e) => {
use std::io::ErrorKind;
match e.kind() {
ErrorKind::NotFound => Ok(false),
ErrorKind::PermissionDenied => Ok(false),
_ => {
// For other errors, we'll return false similar to the C code
// The original C code had special handling for ELOOP (too many symbolic links)
// which would return true, but Rust's std::fs handles this differently
Ok(false)
}
}
}
}
}
/// Helper function to check if a path exists
/// This can be used for future extensions
pub fn check_path_exists(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
let path = Path::new(path);
Ok(path.exists())
}
/// Helper function to check if a path is a file
/// This can be used for future extensions
pub fn check_is_file(path: &str) -> Result<bool, Box<dyn std::error::Error>> {
let path = Path::new(path);
match path.metadata() {
Ok(metadata) => {
Ok(metadata.is_file())
}
Err(e) => {
use std::io::ErrorKind;
match e.kind() {
ErrorKind::NotFound => Ok(false),
ErrorKind::PermissionDenied => Ok(false),
_ => Ok(false)
}
}
}
}
/// Helper function to get file size
/// This can be used for future extensions
pub fn get_file_size(path: &str) -> Result<u64, Box<dyn std::error::Error>> {
let path = Path::new(path);
match path.metadata() {
Ok(metadata) => {
Ok(metadata.len())
}
Err(e) => {
Err(Box::new(e))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_check_is_directory() {
// Test with a known directory
assert!(check_is_directory("/tmp").unwrap_or(false));
// Test with a non-existent path
assert!(!check_is_directory("/this/path/does/not/exist").unwrap_or(true));
// Test with root directory
assert!(check_is_directory("/").unwrap_or(false));
}
#[test]
fn test_check_path_exists() {
// Test with root directory (should exist)
assert!(check_path_exists("/").unwrap_or(false));
// Test with non-existent path
assert!(!check_path_exists("/this/path/does/not/exist").unwrap_or(true));
}
#[test]
fn test_check_is_file() {
// Test with a directory (should return false)
assert!(!check_is_file("/").unwrap_or(true));
// Test with non-existent path
assert!(!check_is_file("/this/path/does/not/exist").unwrap_or(true));
}
}
+4 -2
View File
@@ -3,7 +3,7 @@ compileSdk = "34"
minSdk = "21"
targetSdk = "35"
kotlin = "1.9.25"
ndk = "28.2.13676358"
ndk = "28.2.13676358" #r28c
jacocoAndroid = "0.2.1"
gradle = "8.5.2"
@@ -35,7 +35,7 @@ androidXTestRunner = "1.6.2"
androidXTestExt = "1.2.1"
androidXArchCoreTest = "2.2.0"
androidXMultidex = "2.0.1"
autoService = "1.0-rc4"
autoService = "1.1.1"
kotlinxCoroutineTest = "1.7.3"
kotlinStdlibJdk8 = "1.9.20"
uiAutomator = "2.3.0"
@@ -73,11 +73,13 @@ mpAndroidChart = "v3.0.2"
concurrentTrees = "2.6.1"
aboutLibraries = "6.1.1"
amazeTrashBin = "1.0.10"
rustAndroidGradle = "0.9.6"
[libraries]
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacocoAndroid" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
rust-android-gradle = { module = "org.mozilla.rust-android-gradle.rust-android:org.mozilla.rust-android-gradle.rust-android.gradle.plugin", version.ref = "rustAndroidGradle" }
androidX-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidXAppCompat" }
androidX-constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidXConstraintLayout" }