From 1c1ca559b69810904538c2e46c2326d2ebcaa014 Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Wed, 8 Jan 2025 09:33:44 +0100 Subject: [PATCH 1/4] Add gradle rust plugin --- .github/workflows/android-app.yml | 85 +++++++------- .github/workflows/daemon.yml | 1 - .github/workflows/testframework.yml | 1 - android/BuildInstructions.md | 10 +- android/app/build.gradle.kts | 110 +++++++++++++++---- android/build.gradle.kts | 3 + build-apk.sh => android/build.sh | 61 +--------- android/buildSrc/src/main/kotlin/Versions.kt | 1 + android/docker/Dockerfile | 2 +- android/docs/BuildInstructions.macos.md | 24 ++-- android/docs/DebugInstructions.md | 16 ++- android/gradle/libs.versions.toml | 6 + android/scripts/update-lockfile.sh | 4 +- building/android-container-image.txt | 2 +- building/containerized-build.sh | 2 +- 15 files changed, 176 insertions(+), 152 deletions(-) rename build-apk.sh => android/build.sh (59%) diff --git a/.github/workflows/android-app.yml b/.github/workflows/android-app.yml index 5e784b5d6904..a035439ead3e 100644 --- a/.github/workflows/android-app.yml +++ b/.github/workflows/android-app.yml @@ -98,7 +98,7 @@ jobs: retention-days: 7 generate-relay-list: - name: Generate relay list + name: Generate relay list # Used by wait for jobs. needs: prepare runs-on: ubuntu-latest container: @@ -129,11 +129,8 @@ jobs: - name: Generate if: steps.cache-relay-list.outputs.cache-hit != 'true' - env: - RUSTFLAGS: --deny warnings - run: | - mkdir -p android/app/build/extraAssets - cargo run --bin relay_list > android/app/build/extraAssets/relays.json + shell: bash + run: ./android/gradlew -p android generateRelayList - name: Upload uses: actions/upload-artifact@v4 @@ -144,7 +141,7 @@ jobs: retention-days: 7 build-native: - name: Build native + name: Build native # Used by wait for jobs. needs: prepare runs-on: ubuntu-latest container: @@ -152,18 +149,14 @@ jobs: strategy: matrix: include: - - arch: "x86_64" - abi: "x86_64" - target: "x86_64-linux-android" - - arch: "i686" - abi: "x86" - target: "i686-linux-android" - - arch: "aarch64" - abi: "arm64-v8a" - target: "aarch64-linux-android" - - arch: "armv7" - abi: "armeabi-v7a" - target: "armv7-linux-androideabi" + - abi: "x86_64" + task-variant: "X86_64" + - abi: "x86" + task-variant: "X86" + - abi: "arm64-v8a" + task-variant: "Arm64" + - abi: "armeabi-v7a" + task-variant: "Arm" steps: # Fix for HOME path overridden by GH runners when building in containers, see: # https://github.com/actions/runner/issues/863 @@ -197,27 +190,28 @@ jobs: env: cache_hash: ${{ steps.native-lib-cache-hash.outputs.native_lib_hash }} with: - path: ./android/app/build/extraJni + path: ./android/app/build/rustJniLibs/android key: android-native-libs-${{ runner.os }}-${{ matrix.abi }}-${{ env.cache_hash }} - name: Build native libraries if: steps.cache-native-libs.outputs.cache-hit != 'true' - env: - RUSTFLAGS: --deny warnings - BUILD_TYPE: debug - run: | - ARCHITECTURES="${{ matrix.abi }}" - UNSTRIPPED_LIB_PATH="$CARGO_TARGET_DIR/${{ matrix.target }}/$BUILD_TYPE/libmullvad_jni.so" - STRIPPED_LIB_PATH="./android/app/build/extraJni/${{ matrix.abi }}/libmullvad_jni.so" - NDK_TOOLCHAIN_STRIP_TOOL="$NDK_TOOLCHAIN_DIR/llvm-strip" - cargo build --target ${{ matrix.target }} --verbose --package mullvad-jni --features api-override - $NDK_TOOLCHAIN_STRIP_TOOL --strip-debug --strip-unneeded -o "$STRIPPED_LIB_PATH" "$UNSTRIPPED_LIB_PATH" + uses: burrunan/gradle-cache-action@v1 + with: + job-id: jdk17 + arguments: cargoBuild${{ matrix.task-variant }} + gradle-version: wrapper + build-root-directory: android + execution-only-caches: false + # Disable if logs are hard to follow. + concurrent: true + read-only: ${{ github.ref != 'refs/heads/main' }} + - name: Upload native libs uses: actions/upload-artifact@v4 with: - name: native-libs-${{ matrix.arch }} - path: android/app/build/extraJni + name: native-libs-${{ matrix.abi }} + path: android/app/build/rustJniLibs/android if-no-files-found: error retention-days: 7 @@ -290,7 +284,9 @@ jobs: uses: burrunan/gradle-cache-action@v1 with: job-id: jdk17 - arguments: compileOssProdDebugKotlin + arguments: | + compileOssProdDebugKotlin + -x cargoBuild gradle-version: wrapper build-root-directory: android execution-only-caches: false @@ -299,24 +295,26 @@ jobs: read-only: ${{ github.ref != 'refs/heads/main' }} - name: Wait for other jobs (native, relay list) - uses: kachick/wait-other-jobs@v2.0.3 + uses: kachick/wait-other-jobs@v3.6.0 with: + wait-seconds-before-first-polling: '0' wait-list: | [ { "workflowFile": "android-app.yml", - "jobName": "build-native" + "jobMatchMode": "prefix", + "jobName": "Build native" }, { "workflowFile": "android-app.yml", - "jobName": "generate-relay-list" + "jobName": "Generate relay list" } ] - uses: actions/download-artifact@v4 with: pattern: native-libs-* - path: android/app/build/extraJni + path: android/app/build/rustJniLibs/android merge-multiple: true - uses: actions/download-artifact@v4 @@ -328,7 +326,9 @@ jobs: uses: burrunan/gradle-cache-action@v1 with: job-id: jdk17 - arguments: assembleOssProdDebug + arguments: | + assembleOssProdDebug + -x cargoBuild gradle-version: wrapper build-root-directory: android execution-only-caches: true @@ -341,7 +341,9 @@ jobs: if: github.event.inputs.run_firebase_tests == 'true' with: job-id: jdk17 - arguments: assemblePlayStagemoleDebug + arguments: | + assemblePlayStagemoleDebug + -x cargoBuild gradle-version: wrapper build-root-directory: android execution-only-caches: true @@ -396,7 +398,10 @@ jobs: uses: burrunan/gradle-cache-action@v1 with: job-id: jdk17 - arguments: ${{ matrix.assemble-command }} + arguments: | + ${{ matrix.assemble-command }} + -x cargoBuild + -x generateRelayList gradle-version: wrapper build-root-directory: android execution-only-caches: false diff --git a/.github/workflows/daemon.yml b/.github/workflows/daemon.yml index 2b6241d76f3f..2727d6697b54 100644 --- a/.github/workflows/daemon.yml +++ b/.github/workflows/daemon.yml @@ -10,7 +10,6 @@ on: - '!.github/CODEOWNERS' - '!android/**' - '!audits/**' - - '!build-apk.sh' - '!build.sh' - '!ci/**' - 'ci/check-rust.sh' diff --git a/.github/workflows/testframework.yml b/.github/workflows/testframework.yml index f8d729d27156..288aee4273f3 100644 --- a/.github/workflows/testframework.yml +++ b/.github/workflows/testframework.yml @@ -10,7 +10,6 @@ on: - '.github/workflows/daemon.yml' - '!android/**' - '!audits/**' - - '!build-apk.sh' - '!build.sh' - '!ci/**' - '!clippy.toml' diff --git a/android/BuildInstructions.md b/android/BuildInstructions.md index c80214f01395..e89d1b310244 100644 --- a/android/BuildInstructions.md +++ b/android/BuildInstructions.md @@ -118,10 +118,10 @@ Linux distro: ```bash cd "$ANDROID_HOME" # Or some other directory to place the Android NDK - wget https://dl.google.com/android/repository/android-ndk-r27b-linux.zip - unzip android-ndk-r27b-linux.zip + wget https://dl.google.com/android/repository/android-ndk-r27c-linux.zip + unzip android-ndk-r27c-linux.zip - cd android-ndk-r27b + cd android-ndk-r27c export ANDROID_NDK_HOME="$PWD" ``` @@ -162,7 +162,7 @@ Run the following command to download wireguard-go-rs submodule: `git submodule ### Debug build Run the following command to build a debug build: ```bash -../build-apk.sh --dev-build +../android/build.sh --dev-build ``` ### Release build @@ -170,7 +170,7 @@ Run the following command to build a debug build: 2. Move, copy or symlink the directory from step 1 to [./credentials/](./credentials/) (`/android/credentials/`). 3. Run the following command to build: ```bash - ../build-apk.sh --app-bundle + ../android/build.sh --app-bundle ``` ## Configure signing key diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5fed7575d420..dd1444302cb4 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,7 +1,9 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.android.build.gradle.internal.tasks.factory.dependsOn import com.github.triplet.gradle.androidpublisher.ReleaseStatus +import java.io.ByteArrayOutputStream import java.io.FileInputStream +import java.io.FileOutputStream import java.util.Properties import org.gradle.internal.extensions.stdlib.capitalized @@ -13,6 +15,7 @@ plugins { alias(libs.plugins.kotlin.ksp) alias(libs.plugins.compose) alias(libs.plugins.protobuf.core) + alias(libs.plugins.rust.android.gradle) id(Dependencies.junit5AndroidPluginId) version Versions.junit5Plugin } @@ -21,7 +24,6 @@ val repoRootPath = rootProject.projectDir.absoluteFile.parentFile.absolutePath val extraAssetsDirectory = layout.buildDirectory.dir("extraAssets").get() val relayListPath = extraAssetsDirectory.file("relays.json").asFile val defaultChangelogAssetsDirectory = "$repoRootPath/android/src/main/play/release-notes/" -val extraJniDirectory = layout.buildDirectory.dir("extraJni").get() val credentialsPath = "${rootProject.projectDir}/credentials" val keystorePropertiesFile = file("$credentialsPath/keystore.properties") @@ -35,6 +37,7 @@ android { namespace = "net.mullvad.mullvadvpn" compileSdk = Versions.compileSdkVersion buildToolsVersion = Versions.buildToolsVersion + ndkVersion = Versions.ndkVersion defaultConfig { val localProperties = gradleLocalProperties(rootProject.projectDir, providers) @@ -126,7 +129,6 @@ android { .getOrDefault("OVERRIDE_CHANGELOG_DIR", defaultChangelogAssetsDirectory) assets.srcDirs(extraAssetsDirectory, changelogDir) - jniLibs.srcDirs(extraJniDirectory) } } @@ -239,12 +241,14 @@ android { createDistBundle.dependsOn("bundle$capitalizedVariantName") - // Ensure all relevant assemble tasks depend on our ensure tasks. - tasks["assemble$capitalizedVariantName"].apply { - dependsOn(tasks["ensureRelayListExist"]) - dependsOn(tasks["ensureJniDirectoryExist"]) - dependsOn(tasks["ensureValidVersionCode"]) - } + // Ensure we have relay list ready before merging assets. + tasks["merge${capitalizedVariantName}Assets"].dependsOn(tasks["generateRelayList"]) + + // Ensure that we have all the JNI libs before merging them. + tasks["merge${capitalizedVariantName}JniLibFolders"].dependsOn("cargoBuild") + + // Ensure all relevant assemble tasks depend on our ensure task. + tasks["assemble$capitalizedVariantName"].dependsOn(tasks["ensureValidVersionCode"]) } } @@ -255,6 +259,80 @@ junitPlatform { } } +cargo { + val isReleaseBuild = isReleaseBuild() + val enableApiOverride = !isReleaseBuild || isAlphaOrDevBuild() + module = repoRootPath + libname = "mullvad-jni" + // All available targets: + // https://github.com/mozilla/rust-android-gradle/tree/master?tab=readme-ov-file#targets + targets = + gradleLocalProperties(rootProject.projectDir, providers) + .getProperty("CARGO_TARGETS") + ?.split(",") ?: listOf("arm", "arm64", "x86", "x86_64") + profile = + if (isReleaseBuild) { + "release" + } else { + "debug" + } + prebuiltToolchains = true + targetDirectory = "$repoRootPath/target" + features { + if (enableApiOverride) { + defaultAnd(arrayOf("api-override")) + } + } + targetIncludes = arrayOf("libmullvad_jni.so") + extraCargoBuildArguments = buildList { + add("--package=mullvad-jni") + if (isReleaseBuild) { + add("--locked") + } + } +} + +tasks.register("generateRelayList") { + workingDir = File(repoRootPath) + standardOutput = ByteArrayOutputStream() + + onlyIf { isReleaseBuild() || !relayListPath.exists() } + + commandLine("cargo", "run", "--bin", "relay_list") + + doLast { + val output = standardOutput as ByteArrayOutputStream + // Create file if needed + relayListPath.parentFile.mkdirs() + relayListPath.createNewFile() + FileOutputStream(relayListPath).use { it.write(output.toByteArray()) } + } +} + +tasks.register("cargoClean") { + workingDir = File(repoRootPath) + commandLine("cargo", "clean") +} + +if ( + gradleLocalProperties(rootProject.projectDir, providers) + .getProperty("CLEAN_CARGO_BUILD") + ?.toBoolean() != false +) { + tasks["clean"].dependsOn("cargoClean") +} + +// This is a hack and will not work correctly under all scenarios. +// See DROID-1696 for how we can improve this. +fun isReleaseBuild() = + gradle.startParameter.getTaskNames().any { it.contains("release", ignoreCase = true) } + +fun isAlphaOrDevBuild(): Boolean { + val localProperties = gradleLocalProperties(rootProject.projectDir, providers) + val versionName = generateVersionName(localProperties) + return versionName.contains("dev") || versionName.contains("alpha") +} + androidComponents { beforeVariants { variantBuilder -> variantBuilder.enable = @@ -276,22 +354,6 @@ configure { skipConfigurations = listOf("lintClassPath") } -tasks.register("ensureRelayListExist") { - doLast { - if (!relayListPath.exists()) { - throw GradleException("Missing relay list: $relayListPath") - } - } -} - -tasks.register("ensureJniDirectoryExist") { - doLast { - if (!extraJniDirectory.asFile.exists()) { - throw GradleException("Missing JNI directory: $extraJniDirectory") - } - } -} - // This is a safety net to avoid generating too big version codes, since that could potentially be // hard and inconvenient to recover from. tasks.register("ensureValidVersionCode") { diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 24bcb0e4d0a7..b43f4fec860d 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -14,6 +14,7 @@ plugins { alias(libs.plugins.kotlin.ksp) apply false alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.protobuf.core) apply false + alias(libs.plugins.rust.android.gradle) apply false alias(libs.plugins.detekt) apply true alias(libs.plugins.dependency.versions) apply true @@ -68,6 +69,8 @@ buildscript { classpath("$prebuilt:linux-x86_64@tar.gz") classpath("$prebuilt:macos-aarch64@tar.gz") classpath("$prebuilt:macos-x86_64@tar.gz") + + classpath("org.mozilla.rust-android-gradle:plugin:${libs.versions.rust.android.gradle}") } } diff --git a/build-apk.sh b/android/build.sh similarity index 59% rename from build-apk.sh rename to android/build.sh index da09e0647962..43dc034ff81c 100755 --- a/build-apk.sh +++ b/android/build.sh @@ -18,15 +18,11 @@ BUILD_BUNDLE="no" BUNDLE_TASKS=(createPlayProdReleaseDistBundle) RUN_PLAY_PUBLISH_TASKS="no" PLAY_PUBLISH_TASKS=() -CARGO_ARGS=( "--release" ) -CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-"target"} -SKIP_STRIPPING=${SKIP_STRIPPING:-"no"} while [ -n "${1:-""}" ]; do if [[ "${1:-""}" == "--dev-build" ]]; then BUILD_TYPE="debug" GRADLE_BUILD_TYPE="debug" - CARGO_ARGS=() GRADLE_TASKS=(createOssProdDebugDistApk) BUNDLE_TASKS=(createOssProdDebugDistBundle) elif [[ "${1:-""}" == "--fdroid" ]]; then @@ -37,15 +33,13 @@ while [ -n "${1:-""}" ]; do BUILD_BUNDLE="yes" elif [[ "${1:-""}" == "--enable-play-publishing" ]]; then RUN_PLAY_PUBLISH_TASKS="yes" - elif [[ "${1:-""}" == "--skip-stripping" ]]; then - SKIP_STRIPPING="yes" fi shift 1 done if [[ "$GRADLE_BUILD_TYPE" == "release" ]]; then - if [ ! -f "$SCRIPT_DIR/android/credentials/keystore.properties" ]; then + if [ ! -f "$SCRIPT_DIR/credentials/keystore.properties" ]; then echo "ERROR: No keystore.properties file found" >&2 echo " Please configure the signing keys as described in the README" >&2 exit 1 @@ -54,28 +48,16 @@ fi if [[ "$BUILD_TYPE" == "release" ]]; then if [[ "$PRODUCT_VERSION" == *"-dev-"* ]]; then - CARGO_ARGS+=( "--features" "api-override" ) GRADLE_TASKS+=(createPlayDevmoleReleaseDistApk createPlayStagemoleReleaseDistApk) BUNDLE_TASKS+=(createPlayDevmoleReleaseDistBundle createPlayStagemoleReleaseDistBundle) elif [[ "$PRODUCT_VERSION" == *"-alpha"* ]]; then echo "Removing old Rust build artifacts" - CARGO_ARGS+=( "--locked" ) - cargo clean - CARGO_ARGS+=( "--features" "api-override" ) GRADLE_TASKS+=(createPlayStagemoleReleaseDistApk) BUNDLE_TASKS+=(createPlayStagemoleReleaseDistBundle) PLAY_PUBLISH_TASKS=(publishPlayStagemoleReleaseBundle) - else - echo "Removing old Rust build artifacts" - CARGO_ARGS+=( "--locked" ) - cargo clean fi -else - CARGO_ARGS+=( "--features" "api-override" ) fi -pushd "$SCRIPT_DIR/android" - # Fallback to the system-wide gradle command if the gradlew script is removed. # It is removed by the F-Droid build process before the build starts. if [ -f "gradlew" ]; then @@ -89,48 +71,7 @@ else fi $GRADLE_CMD --console plain clean -mkdir -p "app/build/extraAssets" -mkdir -p "app/build/extraJni" -popd - -for ARCHITECTURE in ${ARCHITECTURES:-aarch64 armv7 x86_64 i686}; do - case "$ARCHITECTURE" in - "x86_64") - TARGET="x86_64-linux-android" - ABI="x86_64" - ;; - "i686") - TARGET="i686-linux-android" - ABI="x86" - ;; - "aarch64") - TARGET="aarch64-linux-android" - ABI="arm64-v8a" - ;; - "armv7") - TARGET="armv7-linux-androideabi" - ABI="armeabi-v7a" - ;; - esac - - echo "Building mullvad-daemon for $TARGET" - cargo build "${CARGO_ARGS[@]}" --target "$TARGET" --package mullvad-jni - - STRIP_TOOL="${NDK_TOOLCHAIN_DIR}/llvm-strip" - TARGET_LIB_PATH="$SCRIPT_DIR/android/app/build/extraJni/$ABI/libmullvad_jni.so" - UNSTRIPPED_LIB_PATH="$CARGO_TARGET_DIR/$TARGET/$BUILD_TYPE/libmullvad_jni.so" - - if [[ "$SKIP_STRIPPING" == "yes" ]]; then - cp "$UNSTRIPPED_LIB_PATH" "$TARGET_LIB_PATH" - else - $STRIP_TOOL --strip-debug --strip-unneeded -o "$TARGET_LIB_PATH" "$UNSTRIPPED_LIB_PATH" - fi -done - -echo "Updating relays.json..." -cargo run --bin relay_list "${CARGO_ARGS[@]}" > android/app/build/extraAssets/relays.json -cd "$SCRIPT_DIR/android" $GRADLE_CMD --console plain "${GRADLE_TASKS[@]}" if [[ "$BUILD_BUNDLE" == "yes" ]]; then diff --git a/android/buildSrc/src/main/kotlin/Versions.kt b/android/buildSrc/src/main/kotlin/Versions.kt index 223331cfc55f..d08012324b32 100644 --- a/android/buildSrc/src/main/kotlin/Versions.kt +++ b/android/buildSrc/src/main/kotlin/Versions.kt @@ -4,6 +4,7 @@ object Versions { const val buildToolsVersion = "35.0.0" const val minSdkVersion = 26 const val targetSdkVersion = 35 + const val ndkVersion = "27.2.12479018" const val junitJupiter = "5.11.4" const val junit5Android = "1.6.0" diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 17a68510ed32..32175853a641 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -8,7 +8,7 @@ # -v $GRADLE_CACHE_VOLUME_NAME:/root/.gradle:Z \ # -v $ANDROID_CREDENTIALS_DIR:/build/android/credentials:Z \ # -v /path/to/repository_root:/build:Z \ -# mullvadvpn-app-build-android ./build-apk.sh --dev-build +# mullvadvpn-app-build-android ./android/build.sh --dev-build # # See the base image Dockerfile in the repository root (../../Dockerfile) # for more information. diff --git a/android/docs/BuildInstructions.macos.md b/android/docs/BuildInstructions.macos.md index 78bf345d1d03..0368c1e917a7 100644 --- a/android/docs/BuildInstructions.macos.md +++ b/android/docs/BuildInstructions.macos.md @@ -17,7 +17,7 @@ brew install --cask android-studio Install the following packages: ```bash -brew install protobuf gcc go openjdk@17 rustup-init +brew install protobuf gcc go openjdk@17 rustup-init python3 ``` > __*NOTE:*__ Ensure that you setup `openjdk@17` to be the active JDK, follow instructions in @@ -38,7 +38,7 @@ Open Android Studio -> Tools -> SDK Manager, and install `Android SDK Command-li Install the necessary Android SDK tools ```bash -~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager "platforms;android-35" "build-tools;35.0.0" "platform-tools" "ndk;27.1.12297006" +~/Library/Android/sdk/cmdline-tools/latest/bin/sdkmanager "platforms;android-35" "build-tools;35.0.0" "platform-tools" "ndk;27.2.12479018" ``` Install Android targets @@ -50,7 +50,7 @@ Export the following environmental variables, and possibly store them for exampl `~/.zprofile` or `~/.zshrc` file: ```bash export ANDROID_HOME="$HOME/Library/Android/sdk" -export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.1.12297006" +export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.2.12479018" export NDK_TOOLCHAIN_DIR="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin" export AR_aarch64_linux_android="$NDK_TOOLCHAIN_DIR/llvm-ar" export AR_armv7_linux_androideabi="$NDK_TOOLCHAIN_DIR/llvm-ar" @@ -74,10 +74,23 @@ git submodule update --init --recursive --depth=1 wireguard-go-rs ``` ## 4. Debug build + +### Android Studio + +Create the file `android/local.properties` if it does not exist and add the following line: + +```bash +rust.pythonCommand=/opt/homebrew/bin/python3 +``` + +You should now be able to run the app directly from Android Studio. + +### `android/build.sh` + Run the build script in the root of the project to assemble all the native libraries and the app: ```bash -./build-apk.sh --dev-build +./android/build.sh --dev-build ``` Once the build is complete you should receive a message looking similar to this: @@ -92,9 +105,6 @@ Once the build is complete you should receive a message looking similar to this: ********************************** ``` -Your native binaries have now been built, any subsequent builds that does not have changes to the -native code can be done in Android Studio or using gradle. - # Build options and configuration For configuring signing or options to your build continue with the general [build instructions](../BuildInstructions.md). diff --git a/android/docs/DebugInstructions.md b/android/docs/DebugInstructions.md index e4c9100e0e6e..9faa5237b2eb 100644 --- a/android/docs/DebugInstructions.md +++ b/android/docs/DebugInstructions.md @@ -1,15 +1,13 @@ ## Debugging the native libraries in Android Studio with LLDB -1. Make sure the native libraries have been built with debug symbols. If using the `build-apk.sh` - script, run `SKIP_STRIPPING=yes ../build-apk.sh --dev-build`. -2. In Android Studio, go to `Run -> Edit configurations...` -3. Make sure the `app` configuration is selected. -4. In the `Debugger` tab, select `Dual (Java + Native)` -5. Start debugging the app as usual from Android Studio. The app should now stop on a SIGURG signal. -6. Select the `LLDB` tab in the debugger. Now you can set breakpoints etc, e.g. +1. In Android Studio, go to `Run -> Edit configurations...` +2. Make sure the `app` configuration is selected. +3. In the `Debugger` tab, select `Dual (Java + Native)` +4. Start debugging the app as usual from Android Studio. The app should now stop on a SIGURG signal. +5. Select the `LLDB` tab in the debugger. Now you can set breakpoints etc, e.g. `breakpoint set -n open_tun` -7. Before continuing run `pro hand -p true -s false SIGURG` -8. Click `Resume Program` and the app will resume until the breakpoint is hit. +6. Before continuing run `pro hand -p true -s false SIGURG` +7. Click `Resume Program` and the app will resume until the breakpoint is hit. NOTE: When running LLDB, Android Studio can sometimes get into a state where it will try to connect to the debugger when running the app normally, which blocks the app from starting. diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 3428c8a8b968..bab6a4fd49fb 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -54,6 +54,9 @@ kotlinx-serialization = "2.1.0" protobuf-gradle-plugin = "0.9.4" protobuf = "4.29.2" +# Rust Android Gradle +rust-android-gradle = "0.9.5" + # Misc commonsvalidator = "1.9.0" dependency-check = "10.0.4" @@ -186,6 +189,9 @@ protobuf-protoc = { id = "com.google.protobuf:protoc", version.ref = "protobuf" grpc-protoc-gen-grpc-java = { id = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" } grpc-protoc-gen-grpc-kotlin = { id = "io.grpc:protoc-gen-grpc-kotlin", version.ref = "grpc-kotlin-jar" } +# Rust Android Gradle +rust-android-gradle = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rust-android-gradle" } + # Misc dependency-check = { id = "org.owasp.dependencycheck", version.ref = "dependency-check" } dependency-versions = { id = "com.github.ben-manes.versions", version.ref = "dependency-versions" } diff --git a/android/scripts/update-lockfile.sh b/android/scripts/update-lockfile.sh index e400caed72ae..0c20ae31c391 100755 --- a/android/scripts/update-lockfile.sh +++ b/android/scripts/update-lockfile.sh @@ -20,8 +20,8 @@ GRADLE_TASKS=( "lint" ) EXCLUDED_GRADLE_TASKS=( - "-xensureRelayListExist" - "-xensureJniDirectoryExist" + "-xgenerateRelayList" + "-xcargoBuild" ) export GRADLE_OPTS diff --git a/building/android-container-image.txt b/building/android-container-image.txt index 0f1f220ce4aa..973dbf7a5cf2 100644 --- a/building/android-container-image.txt +++ b/building/android-container-image.txt @@ -1 +1 @@ -ghcr.io/mullvad/mullvadvpn-app-build-android:7b6bc0f44 +ghcr.io/mullvad/mullvadvpn-app-build-android:7b6bc0f44 \ No newline at end of file diff --git a/building/containerized-build.sh b/building/containerized-build.sh index 86b0148a7237..16e09ea8f293 100755 --- a/building/containerized-build.sh +++ b/building/containerized-build.sh @@ -18,7 +18,7 @@ case $platform in shift 1 ;; android) - build_command=("./build-apk.sh") + build_command=("./android/build.sh") shift 1 ;; *) From 40f4decf6ba578e57a4cd04b4c50b34088d05d79 Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Wed, 8 Jan 2025 09:37:56 +0100 Subject: [PATCH 2/4] Update android build container --- building/android-container-image.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/building/android-container-image.txt b/building/android-container-image.txt index 973dbf7a5cf2..2f448285efcd 100644 --- a/building/android-container-image.txt +++ b/building/android-container-image.txt @@ -1 +1 @@ -ghcr.io/mullvad/mullvadvpn-app-build-android:7b6bc0f44 \ No newline at end of file +ghcr.io/mullvad/mullvadvpn-app-build-android:3ac5745b0 From 1e7cb914a343893404612324895ba414ca9f4eca Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Wed, 8 Jan 2025 09:34:31 +0100 Subject: [PATCH 3/4] Improve libwg rerun-if-changed detection --- wireguard-go-rs/build.rs | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/wireguard-go-rs/build.rs b/wireguard-go-rs/build.rs index 5c7feebb6bf9..c011cf9c3089 100644 --- a/wireguard-go-rs/build.rs +++ b/wireguard-go-rs/build.rs @@ -168,13 +168,16 @@ fn build_android_dynamic_lib(daita: bool) -> anyhow::Result<()> { let target_triple = env::var("TARGET").context("Missing 'TARGET'")?; let target = AndroidTarget::from_str(&target_triple)?; - // TODO: Since `libwg.so` is always copied to `android_output_path`, this rerun-directive will - // always trigger cargo to rebuild this crate. Some mechanism to detected changes to `android_output_path` - // is needed, because some external program may clean it at any time (e.g. gradle). + // This will either trigger a rebuild if any changes have been made to the libwg code + // or if the libwg.so file has been changed. The latter is required since the + // libwg.so file could be deleted. It however means that this build will need + // to run two times before it is properly cached. + // FIXME: Figure out a way to do this better. This is tracked in DROID-1697. println!( "cargo::rerun-if-changed={}", - android_output_path(target)?.display() + android_output_path(target)?.join("libwg.so").display() ); + println!("cargo::rerun-if-changed={}", libwg_path()?.display()); // Before calling `canonicalize`, the directory we're referring to actually has to exist. std::fs::create_dir_all("../build")?; @@ -229,12 +232,15 @@ fn android_move_binary(binary: &Path, output: &Path) -> anyhow::Result<()> { ))?; std::fs::create_dir_all(parent_of_output)?; - let mut move_command = Command::new("mv"); - move_command + let mut copy_command = Command::new("cp"); + // -p command is required to preserve ownership and timestamp of the file to prevent a + // rebuild of this module every time. + copy_command + .arg("-p") .arg(binary.to_str().unwrap()) .arg(output.to_str().unwrap()); - exec(&mut move_command)?; + exec(&mut copy_command)?; Ok(()) } @@ -273,12 +279,20 @@ fn android_arch_name(target: AndroidTarget) -> String { // Returns the path where the Android project expects Rust binaries to be fn android_output_path(target: AndroidTarget) -> anyhow::Result { - let relative_output_path = Path::new("../android/app/build/extraJni").join(android_abi(target)); + let relative_output_path = + Path::new("../android/app/build/rustJniLibs/android").join(android_abi(target)); std::fs::create_dir_all(relative_output_path.clone())?; let output_path = relative_output_path.canonicalize()?; Ok(output_path) } +// Return the path of the libwg folder so that we can trigger rebuilds when any code is +fn libwg_path() -> anyhow::Result { + let relative_output_path = Path::new("libwg"); + let output_path = relative_output_path.canonicalize()?; + Ok(output_path) +} + /// Execute a command, assert that it succeeds, and return stdout as a string. fn exec(mut command: impl BorrowMut) -> anyhow::Result { let command = command.borrow_mut(); From a07dc5c5699392051f9e34d4883ba0d2808ea496 Mon Sep 17 00:00:00 2001 From: Jonatan Rhodin Date: Wed, 8 Jan 2025 09:34:58 +0100 Subject: [PATCH 4/4] Update lockfile --- android/gradle/verification-metadata.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/android/gradle/verification-metadata.xml b/android/gradle/verification-metadata.xml index 3d24211f0c9d..c06d78cd1664 100644 --- a/android/gradle/verification-metadata.xml +++ b/android/gradle/verification-metadata.xml @@ -6499,6 +6499,14 @@ + + + + + + + +