From 37f04ca989d6ac6c19bd7a00a43b9ea69827d642 Mon Sep 17 00:00:00 2001 From: crueter Date: Mon, 24 Nov 2025 00:18:09 -0500 Subject: [PATCH] [cmake, externals] android x86_64 support Updates all of our bundled CI deps to support android x86_64, adds a build flavor thereof, and also adds sirit mingw support. The new FFmpeg package is built in a much better way that actually makes it identically built to the other CI packages, meaning we now have real 8.0.0 support, no need for libvpx/cpu_features/all that other crap. PLUS, we can now statically link it! Hooray! It's also built with MediaCodec support so in the future we can work on that. Rewrote the android build script too, plus added a copyFlavorTypeOutput target that assembles and copies the APK. The code behind it sucks because I'm not great with Gradle but hey, it works. Signed-off-by: crueter --- .ci/android/build.sh | 128 +++++++++++++++++++++++++++++-- .ci/android/package.sh | 22 ------ CMakeLists.txt | 16 ++-- CMakeModules/CPMUtil.cmake | 8 +- cpmfile.json | 2 +- docs/build/Android.md | 62 ++++++++++++--- externals/cpmfile.json | 10 +-- externals/ffmpeg/CMakeLists.txt | 10 --- externals/ffmpeg/cpmfile.json | 6 +- src/android/app/build.gradle.kts | 47 +++++++++--- 10 files changed, 236 insertions(+), 75 deletions(-) delete mode 100755 .ci/android/package.sh diff --git a/.ci/android/build.sh b/.ci/android/build.sh index 836faa38d5..3a2395ebf4 100755 --- a/.ci/android/build.sh +++ b/.ci/android/build.sh @@ -1,21 +1,135 @@ -#!/bin/bash -e +#!/bin/sh -e # SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later -export NDK_CCACHE=$(which ccache) +NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2) +export CMAKE_BUILD_PARALLEL_LEVEL="${NUM_JOBS}" +ARTIFACTS_DIR="$PWD/artifacts" -if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then +: "${CCACHE:=false}" +RETURN=0 + +usage() { + cat < Build flavor (variable: TARGET) + Valid values are: legacy, optimized, standard + Default: standard + -b, --build-type Build type (variable: TYPE) + Valid values are: Release, RelWithDebInfo, Debug + Default: Debug + +Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE) +Set the CCACHE variable to "true" to enable build caching. +The APK and AAB will be output into "artifacts". + +EOF + + exit "$RETURN" +} + +die() { + echo "-- ! $*" >&2 + RETURN=1 usage +} + +target() { + [ -z "$1" ] && die "You must specify a valid target." + + TARGET="$1" +} + +type() { + [ -z "$1" ] && die "You must specify a valid type." + + TYPE="$1" +} + +while true; do + case "$1" in + -c|--chromeos) CHROMEOS=true ;; + -r|--release) DEVEL=false ;; + -t|--target) target "$2"; shift ;; + -b|--build-type) type "$2"; shift ;; + -h|--help) usage ;; + *) break ;; + esac + + shift +done + +: "${CHROMEOS:=false}" +: "${TARGET:=standard}" +: "${TYPE:=Release}" +: "${DEVEL:=true}" + +case "$TARGET" in + legacy) FLAVOR=Legacy ;; + optimized) FLAVOR=GenshinSpoof ;; + standard) FLAVOR=Mainline ;; + *) die "Invalid build flavor $TARGET." +esac + +case "$TYPE" in + RelWithDebInfo|Release|Debug) ;; + *) die "Invalid build type $TYPE." +esac + +LOWER_FLAVOR=$(echo "$FLAVOR" | sed 's/./\L&/') +LOWER_TYPE=$(echo "$TYPE" | sed 's/./\L&/') + +if [ -n "${ANDROID_KEYSTORE_B64}" ]; then export ANDROID_KEYSTORE_FILE="${GITHUB_WORKSPACE}/ks.jks" - base64 --decode <<< "${ANDROID_KEYSTORE_B64}" > "${ANDROID_KEYSTORE_FILE}" + echo "${ANDROID_KEYSTORE_B64}" | base64 --decode > "${ANDROID_KEYSTORE_FILE}" + SHA1SUM=$(keytool -list -v -storepass "${ANDROID_KEYSTORE_PASS}" -keystore "${ANDROID_KEYSTORE_FILE}" | grep SHA1 | cut -d " " -f3) + echo "-- Keystore SHA1 is ${SHA1SUM}" + fi cd src/android chmod +x ./gradlew -./gradlew assembleMainlineRelease -./gradlew bundleMainlineRelease +set -- "$@" -DUSE_CCACHE="${CCACHE}" +[ "$DEVEL" != "true" ] && set -- "$@" -DENABLE_UPDATE_CHECKER=ON +[ "$CHROMEOS" = "true" ] && ABI=x86_64 +: "${ABI:=arm64-v8a}" + +echo "-- packaging APK" +./gradlew copy${FLAVOR}"${TYPE}Output" \ + -Dorg.gradle.caching="${CCACHE}" \ + -Dorg.gradle.parallel="${CCACHE}" \ + -Dorg.gradle.workers.max="${NUM_JOBS}" \ + -Pandroid.injected.build.abi="${ABI}" \ + -PYUZU_ANDROID_ARGS="$*" + +echo "-- building AAB" +./gradlew bundle${FLAVOR}"${TYPE}" \ + -Dorg.gradle.caching="${CCACHE}" \ + -Dorg.gradle.parallel="${CCACHE}" \ + -Dorg.gradle.workers.max="${NUM_JOBS}" \ + -Pandroid.injected.build.abi="${ABI}" \ + -PYUZU_ANDROID_ARGS="$*" -if [ ! -z "${ANDROID_KEYSTORE_B64}" ]; then +echo "-- packaging AAB" +cp app/build/outputs/bundle/"${LOWER_FLAVOR}${TYPE}/app-${LOWER_FLAVOR}-${LOWER_TYPE}.aab" \ + "${ARTIFACTS_DIR}/app-${LOWER_FLAVOR}-${ABI}-${LOWER_TYPE}.aab" || echo "AAB not found" + +if [ -n "${ANDROID_KEYSTORE_B64}" ]; then rm "${ANDROID_KEYSTORE_FILE}" fi + +echo "-- Done! APK and AAB artifacts are in ${ARTIFACTS_DIR}" +ls -l "${ARTIFACTS_DIR}/" \ No newline at end of file diff --git a/.ci/android/package.sh b/.ci/android/package.sh deleted file mode 100755 index 50b7bbc332..0000000000 --- a/.ci/android/package.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -GITDATE="$(git show -s --date=short --format='%ad' | sed 's/-//g')" -GITREV="$(git show -s --format='%h')" -ARTIFACTS_DIR="$PWD/artifacts" -mkdir -p "${ARTIFACTS_DIR}/" - -REV_NAME="eden-android-${GITDATE}-${GITREV}" -BUILD_FLAVOR="mainline" -BUILD_TYPE_LOWER="release" -BUILD_TYPE_UPPER="Release" - -cp src/android/app/build/outputs/apk/"${BUILD_FLAVOR}/${BUILD_TYPE_LOWER}/app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.apk" \ - "${ARTIFACTS_DIR}/${REV_NAME}.apk" || echo "APK not found" - -cp src/android/app/build/outputs/bundle/"${BUILD_FLAVOR}${BUILD_TYPE_UPPER}"/"app-${BUILD_FLAVOR}-${BUILD_TYPE_LOWER}.aab" \ - "${ARTIFACTS_DIR}/${REV_NAME}.aab" || echo "AAB not found" - -ls -la "${ARTIFACTS_DIR}/" diff --git a/CMakeLists.txt b/CMakeLists.txt index 4afbc13683..4a108e47af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,10 +436,10 @@ if (ENABLE_OPENSSL) option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" ${DEFAULT_YUZU_USE_BUNDLED_OPENSSL}) endif() +# TODO(crueter): CPM this if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) - # TODO(crueter): CPM this - set(vvl_version "1.4.321.0") - set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip") + set(vvl_version "1.4.328.0") + set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android-${vvl_version}.zip") if (NOT EXISTS "${vvl_zip_file}") # Download and extract validation layer release to externals directory set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download") @@ -450,8 +450,14 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) endif() # Copy the arm64 binary to src/android/app/main/jniLibs - set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/") - file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so" + if (ARCHITECTURE_arm64) + set(vvl_abi arm64-v8a) + elseif(ARCHITECTURE_x86_64) + set(vvl_abi x86_64) + endif() + + set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/${vvl_abi}/") + file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/${vvl_abi}/libVkLayer_khronos_validation.so" DESTINATION "${vvl_lib_path}") endif() diff --git a/CMakeModules/CPMUtil.cmake b/CMakeModules/CPMUtil.cmake index 78bab05030..53b9e24079 100644 --- a/CMakeModules/CPMUtil.cmake +++ b/CMakeModules/CPMUtil.cmake @@ -603,8 +603,12 @@ function(AddCIPackage) add_ci_package(mingw-arm64) endif() - if (ANDROID AND NOT "android" IN_LIST DISABLED_PLATFORMS) - add_ci_package(android) + if((ANDROID AND ARCHITECTURE_x86_64) AND NOT "android-x86_64" IN_LIST DISABLED_PLATFORMS) + add_ci_package(android-x86_64) + endif() + + if((ANDROID AND ARCHITECTURE_arm64) AND NOT "android-aarch64" IN_LIST DISABLED_PLATFORMS) + add_ci_package(android-aarch64) endif() if(PLATFORM_SUN AND NOT "solaris-amd64" IN_LIST DISABLED_PLATFORMS) diff --git a/cpmfile.json b/cpmfile.json index 6cf7ed1a41..c00f955087 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -4,7 +4,7 @@ "package": "OpenSSL", "name": "openssl", "repo": "crueter-ci/OpenSSL", - "version": "3.6.0-e3608d80df", + "version": "3.6.0-965d6279e8", "min_version": "1.1.1" }, "boost": { diff --git a/docs/build/Android.md b/docs/build/Android.md index 5805ed2797..638d429328 100644 --- a/docs/build/Android.md +++ b/docs/build/Android.md @@ -1,30 +1,37 @@ -# Note: These build instructions are a work-in-progress. +# Android ## Dependencies + * [Android Studio](https://developer.android.com/studio) * [NDK 27+ and CMake 3.22.1](https://developer.android.com/studio/projects/install-ndk#default-version) * [Git](https://git-scm.com/download) -### WINDOWS ONLY - Additional Dependencies - * **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select "Desktop development with C++" support in the installer. Make sure to update to the latest version if already installed.** - * **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** - - A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. +## WINDOWS ONLY - Additional Dependencies + +* **[Visual Studio 2022 Community](https://visualstudio.microsoft.com/downloads/)** - **Make sure to select "Desktop development with C++" support in the installer. Make sure to update to the latest version if already installed.** +* **[Vulkan SDK](https://vulkan.lunarg.com/sdk/home#windows)** - **Make sure to select Latest SDK.** + * A convenience script to install the latest SDK is provided in `.ci\windows\install-vulkan-sdk.ps1`. ## Cloning Eden with Git -``` + +```sh git clone --recursive https://git.eden-emu.dev/eden-emu/eden.git ``` -Eden by default will be cloned into - + +Eden by default will be cloned into: + * `C:\Users\\eden` on Windows * `~/eden` on Linux and macOS ## Building + 1. Start Android Studio, on the startup dialog select `Open`. 2. Navigate to the `eden/src/android` directory and click on `OK`. 3. In `Build > Select Build Variant`, select `release` or `relWithDebInfo` as the "Active build variant". 4. Build the project with `Build > Make Project` or run it on an Android device with `Run > Run 'app'`. ## Building with Terminal + 1. Download the SDK and NDK from Android Studio. 2. Navigate to SDK and NDK paths. 3. Then set ANDROID_SDK_ROOT and ANDROID_NDK_ROOT in terminal via @@ -38,7 +45,44 @@ Eden by default will be cloned into - Remember to have a Java SDK installed if not already, on Debian and similar this is done with `sudo apt install openjdk-17-jdk`. ### Script -A convenience script for building is provided in `.ci/android/build.sh`. The built APK can be put into an `artifacts` directory via `.ci/android/package.sh`. On Windows, these must be done in the Git Bash or MinGW terminal. + +A convenience script for building is provided in `.ci/android/build.sh`. On Windows, this must be run in Git Bash or MSYS2. This script provides the following options: + +```txt +Usage: build.sh [-c|--chromeos] [-t|--target FLAVOR] [-b|--build-type BUILD_TYPE] + [-h|--help] [-r|--release] [extra options] + +Build script for Android. +Associated variables can be set outside the script, +and will apply both to this script and the packaging script. +bool values are "true" or "false" + +Options: + -c, --chromeos Build for ChromeOS (x86_64) (variable: CHROMEOS, bool) + Default: false + -r, --release Enable update checker. If set, sets the DEVEL bool variable to false. + By default, DEVEL is true. + -t, --target Build flavor (variable: TARGET) + Valid values are: legacy, optimized, standard + Default: standard + -b, --build-type Build type (variable: TYPE) + Valid values are: Release, RelWithDebInfo, Debug + Default: Debug + +Extra arguments are passed to CMake (e.g. -DCMAKE_OPTION_NAME=VALUE) +Set the CCACHE variable to "true" to enable build caching. +The APK and AAB will be output into "artifacts". +``` + +Examples: + +* Build legacy release with update checker for ChromeOS: + * `.ci/android/build.sh -c -r -t legacy` +* Build standard release with debug info without update checker for phones: + * `.ci/android/build.sh -b RelWithDebInfo` +* Build optimized release with update checker: + * `.ci/android/build.sh -r -t optimized` ### Additional Resources -https://developer.android.com/studio/intro + + diff --git a/externals/cpmfile.json b/externals/cpmfile.json index ee5ccb451e..7f5af5dfce 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -9,7 +9,7 @@ }, "sirit": { "repo": "eden-emulator/sirit", - "git_version": "1.0.2", + "git_version": "1.0.3", "tag": "v%VERSION%", "artifact": "sirit-source-%VERSION%.tar.zst", "hash_suffix": "sha512sum", @@ -23,11 +23,7 @@ "package": "sirit", "name": "sirit", "repo": "eden-emulator/sirit", - "version": "1.0.2", - "disabled_platforms": [ - "mingw-amd64", - "mingw-arm64" - ] + "version": "1.0.3" }, "httplib": { "repo": "yhirose/cpp-httplib", @@ -167,7 +163,7 @@ "package": "SDL2", "name": "SDL2", "repo": "crueter-ci/SDL2", - "version": "2.32.10-38e0094637", + "version": "2.32.10-a65111bd2d", "min_version": "2.26.4" }, "catch2": { diff --git a/externals/ffmpeg/CMakeLists.txt b/externals/ffmpeg/CMakeLists.txt index 58461d8934..d71613db8c 100644 --- a/externals/ffmpeg/CMakeLists.txt +++ b/externals/ffmpeg/CMakeLists.txt @@ -114,16 +114,6 @@ if (UNIX AND NOT ANDROID) endif() if (YUZU_USE_BUNDLED_FFMPEG) - # MSVC conflicts with ksuser otherwise - # MinGW has the funny quirk of requiring avutil after avcodec - # Android needs some deps to be compiled with PIC (TODO) - # TODO(crueter) fix - if (ANDROID) - set(BUILD_SHARED_LIBS ON) - else() - set(BUILD_SHARED_LIBS OFF) - endif() - AddJsonPackage(ffmpeg-ci) set(FFmpeg_INCLUDE_DIR diff --git a/externals/ffmpeg/cpmfile.json b/externals/ffmpeg/cpmfile.json index 0baeeb4713..0d0a612eda 100644 --- a/externals/ffmpeg/cpmfile.json +++ b/externals/ffmpeg/cpmfile.json @@ -1,8 +1,8 @@ { "ffmpeg": { "repo": "FFmpeg/FFmpeg", - "sha": "c2184b65d2", - "hash": "007b1ccdd4d3ea3324835258d9a255103253bd66edb442b12d9c60dca85149cad52136a3b3120e5094115b6a3d9e80eeacbf9c07e5ffafc9ac459614d5fa3b22", + "sha": "ddf443f1e9", + "hash": "ded1c313843f23805102565bd3ca92602fb9c2951e059ca5e1a486ab3ef7d589acccf3cde05c5ff0cfc5199c3a261dccb4d2a93254e585824850696fb41a292e", "bundled": true }, "ffmpeg-ci": { @@ -10,7 +10,7 @@ "package": "FFmpeg", "name": "ffmpeg", "repo": "crueter-ci/FFmpeg", - "version": "8.0-be99d2c0b2", + "version": "8.0-ddf443f1e9", "min_version": "4.1" } } diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 01a413261e..7abee41efe 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -8,6 +8,7 @@ import android.annotation.SuppressLint import kotlin.collections.setOf import org.jlleitschuh.gradle.ktlint.reporter.ReporterType import com.github.triplet.gradle.androidpublisher.ReleaseStatus +import org.gradle.api.tasks.bundling.AbstractArchiveTask plugins { id("com.android.application") @@ -63,11 +64,6 @@ android { versionName = getGitVersion() versionCode = autoVersion - ndk { - @SuppressLint("ChromeOsAbiSupport") - abiFilters += listOf("arm64-v8a") - } - externalNativeBuild { cmake { val extraCMakeArgs = @@ -90,12 +86,18 @@ android { *extraCMakeArgs.toTypedArray() ) ) - - abiFilters("arm64-v8a") } } } + splits { + abi { + isEnable = true + reset() + include("arm64-v8a", "x86_64") + } + } + val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") signingConfigs { if (keystoreFile != null) { @@ -127,7 +129,7 @@ android { isMinifyEnabled = true isDebuggable = false proguardFiles( - getDefaultProguardFile("proguard-android.txt"), + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } @@ -139,7 +141,7 @@ android { signingConfig = signingConfigs.getByName("default") isDebuggable = true proguardFiles( - getDefaultProguardFile("proguard-android.txt"), + getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) versionNameSuffix = "-relWithDebInfo" @@ -321,3 +323,30 @@ fun getGitVersion(): String { } return versionName.ifEmpty { "0.0" } } + +// this sucks but it works +afterEvaluate { + android.applicationVariants.all { variant -> + val variantName = variant.name.replaceFirstChar { it.uppercase() } + + val packageTask = tasks.named("package${variantName}") + + val copyTask = tasks.register("copy${variantName}Output") { + val outDir = file("../../../artifacts") + into(outDir) + doFirst { outDir.mkdirs() } + + from(packageTask.map { it.outputs.files }) { + include("*.apk") + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + + tasks.named("assemble${variantName}").configure { + finalizedBy(copyTask) + } + + true + } +}