Browse Source

[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 <crueter@eden-emu.dev>
pull/3086/head
crueter 4 weeks ago
parent
commit
37f04ca989
  1. 128
      .ci/android/build.sh
  2. 22
      .ci/android/package.sh
  3. 16
      CMakeLists.txt
  4. 8
      CMakeModules/CPMUtil.cmake
  5. 2
      cpmfile.json
  6. 62
      docs/build/Android.md
  7. 10
      externals/cpmfile.json
  8. 10
      externals/ffmpeg/CMakeLists.txt
  9. 6
      externals/ffmpeg/cpmfile.json
  10. 47
      src/android/app/build.gradle.kts

128
.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 <<EOF
Usage: $0 [-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 <FLAVOR> Build flavor (variable: TARGET)
Valid values are: legacy, optimized, standard
Default: standard
-b, --build-type <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}/"

22
.ci/android/package.sh

@ -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}/"

16
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()

8
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)

2
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": {

62
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\<user-name>\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 <FLAVOR> Build flavor (variable: TARGET)
Valid values are: legacy, optimized, standard
Default: standard
-b, --build-type <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
<https://developer.android.com/studio/intro>

10
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": {

10
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

6
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"
}
}

47
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>("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
}
}
Loading…
Cancel
Save