[PATCH 0/4] MR10354: wineandroid: update Android build to work with modern toolchains
Update the Android project and build logic to work with recent Gradle and Android SDK versions, adjust types for consistency, separate APK generation from the default build, and add the required staging steps to prepare assets and libraries before building the APK. These changes allow Wine to be built with NDK r29 and modern Android build tools. The resulting build runs on Android 7 and later (only devices using the legacy gralloc interface). The build process still remains somewhat non-standard, so a build script and screenshot are provided for reference. <details> <summary>Screenshot</summary> {width=792 height=600} </details> [wineandroid-builder.tar.gz](/uploads/0b47ae812cfcd2d64f71aa9dee7042f6/wineandroid-builder.tar.gz) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354
From: Twaik Yont <9674930+twaik@users.noreply.github.com> The wineandroid Gradle project no longer builds with recent Android build tools. Replace jcenter() with google() and mavenCentral(), update the Android Gradle plugin, and add the required namespace and android:exported attributes. The archivesBaseName property is no longer supported and is replaced with the current mechanism for setting the APK output name. Update task wiring to use configureEach so icon generation and asset checksums continue to run correctly with the modern task graph. This allows wineandroid to build with recent Gradle and Android SDK versions. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/wineandroid.drv/AndroidManifest.xml | 1 + dlls/wineandroid.drv/build.gradle.in | 77 ++++++++++++++++-------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/dlls/wineandroid.drv/AndroidManifest.xml b/dlls/wineandroid.drv/AndroidManifest.xml index 574b98e3820..7f0a5e6ce64 100644 --- a/dlls/wineandroid.drv/AndroidManifest.xml +++ b/dlls/wineandroid.drv/AndroidManifest.xml @@ -9,6 +9,7 @@ android:icon="@drawable/wine" android:label="Wine" > <activity + android:exported="true" android:label="Wine" android:name=".WineActivity" android:launchMode="singleInstance" diff --git a/dlls/wineandroid.drv/build.gradle.in b/dlls/wineandroid.drv/build.gradle.in index 662e8b7ea22..547233d8fde 100644 --- a/dlls/wineandroid.drv/build.gradle.in +++ b/dlls/wineandroid.drv/build.gradle.in @@ -24,14 +24,21 @@ buildscript { repositories { - jcenter() + google() + mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:2.2.1" + classpath "com.android.tools.build:gradle:9.1.0" } } +repositories +{ + google() + mavenCentral() +} + def get_srcdir() { if (srcdir.equals(".")) { return "."; } @@ -52,32 +59,39 @@ def add_icon_task( dir, scale ) } } -def checksum_task() +def checksum_task = tasks.create( "checksumAssets", Exec ) { - return tasks.create( "checksumAssets", Exec ) { - commandLine "sh", "-c", - "(test -d assets && " + - "rm -f assets/files.sum assets/sums.sum && " + - "sha256sum \$(find assets -type f -print) | sed 's/ assets\\// /' >files.sum && " + - "sha256sum files.sum >sums.sum && " + - "mv files.sum sums.sum assets) || rm -rf assets"; - } + commandLine "sh", "-c", + "(test -d assets && " + + "rm -f assets/files.sum assets/sums.sum && " + + "sha256sum \$(find assets -type f -print) | sed 's/ assets\\// /' >files.sum && " + + "sha256sum files.sum >sums.sum && " + + "mv files.sum sums.sum assets) || rm -rf assets"; } -tasks.whenTaskAdded +def icon_tasks = [ + add_icon_task( "ldpi", 0.75 ), + add_icon_task( "mdpi", 1 ), + add_icon_task( "hdpi", 1.5 ), + add_icon_task( "xhdpi", 2 ), + add_icon_task( "xxhdpi", 3 ), + add_icon_task( "xxxhdpi", 4 ) +] + +tasks.configureEach { - if (name.equals( "generateDebugResources" )) + if (name.equals( "preBuild" ) || + name.equals( "processDebugResources" ) || + name.equals( "processDebugNavigationResources" ) || + name.equals( "mergeDebugResources" )) { - dependsOn add_icon_task( "ldpi", 0.75 ) - dependsOn add_icon_task( "mdpi", 1 ) - dependsOn add_icon_task( "hdpi", 1.5 ) - dependsOn add_icon_task( "xhdpi", 2 ) - dependsOn add_icon_task( "xxhdpi", 3 ) - dependsOn add_icon_task( "xxxhdpi", 4 ) + dependsOn icon_tasks } - if (name.equals( "generateDebugAssets" )) + if (name.equals( "preBuild" ) || + name.equals( "generateDebugAssets" ) || + name.equals( "mergeDebugAssets" )) { - dependsOn checksum_task() + dependsOn checksum_task } } @@ -88,8 +102,8 @@ tasks.withType(JavaCompile) android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + namespace "org.winehq.wine" + compileSdkVersion 36 defaultConfig { @@ -97,7 +111,6 @@ android minSdkVersion 17 versionCode 1 versionName "@PACKAGE_VERSION@" - setProperty( "archivesBaseName", "wine" ) } sourceSets @@ -110,3 +123,19 @@ android main.manifest.srcFile get_srcdir() + "/AndroidManifest.xml" } } + +configurations.configureEach +{ + exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib" +} + +androidComponents +{ + onVariants(selector().all()) + { variant -> + variant.outputs.forEach + { output -> + output.outputFileName.set("wine-${variant.name}.apk") + } + } +} -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10354
From: Twaik Yont <9674930+twaik@users.noreply.github.com> Replace the bool type with BOOL for the is_desktop field to match the rest of device.c and avoid mixing C99 bool with Win32 types. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/wineandroid.drv/device.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/wineandroid.drv/device.c b/dlls/wineandroid.drv/device.c index b976968f092..8ae0172a57f 100644 --- a/dlls/wineandroid.drv/device.c +++ b/dlls/wineandroid.drv/device.c @@ -132,7 +132,7 @@ struct ioctl_android_create_window struct ioctl_header hdr; int parent; float scale; - bool is_desktop; + BOOL is_desktop; }; struct ioctl_android_destroy_window -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10354
From: Twaik Yont <9674930+twaik@users.noreply.github.com> Remove `wine-debug.apk` from `EXTRA_TARGETS` so it is no longer built as part of the default `all` target. Building the APK requires first installing the Wine prefix into the Android project `assets` directory and copying native libraries into the `libs` directory, with additional preparation steps to adjust the project layout before packaging. This staging is required because `aapt` cannot package the needed filesystem layout through symlinks, so the Android project tree has to be populated explicitly before the final APK build step. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- dlls/wineandroid.drv/Makefile.in | 2 -- 1 file changed, 2 deletions(-) diff --git a/dlls/wineandroid.drv/Makefile.in b/dlls/wineandroid.drv/Makefile.in index 9a8be0ef90a..45152b16123 100644 --- a/dlls/wineandroid.drv/Makefile.in +++ b/dlls/wineandroid.drv/Makefile.in @@ -12,5 +12,3 @@ SOURCES = \ opengl.c \ window.c \ wine.svg - -EXTRA_TARGETS = wine-debug.apk -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10354
From: Twaik Yont <9674930+twaik@users.noreply.github.com> Build the APK from an explicitly staged Android project tree. Add the Android ABI name separately from exec_prefix and use it to prepare the assets and lib directories before invoking Gradle. The APK build now installs the Wine prefix into assets, copies the freetype shared library into the matching lib directory, removes unneeded files, and replaces launcher symlinks in bin with shell wrappers suitable for APK packaging. These are the additional preparation steps required by wineandroid: the APK cannot be built directly from the normal install layout and needs a staged prefix with Android-specific path fixups before the final Gradle build. Signed-off-by: Twaik Yont <9674930+twaik@users.noreply.github.com> --- configure | 47 ++++++++++++++++++++++++++++++++++++++--------- configure.ac | 47 ++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/configure b/configure index 08ae1b52e89..7e660111844 100755 --- a/configure +++ b/configure @@ -13060,14 +13060,15 @@ printf "%s\n" "#define SONAME_LIBGLESV2 \"$ac_cv_lib_soname_GLESv2\"" >>confdefs esac fi + case $HOST_ARCH in + i386) android_abi='x86' ;; + x86_64) android_abi='x86_64' ;; + arm) android_abi='armeabi-v7a' ;; + aarch64) android_abi='arm64-v8a' ;; + esac if test "x$exec_prefix" = xNONE then - case $HOST_ARCH in - i386) exec_prefix='${prefix}/x86' ;; - x86_64) exec_prefix='${prefix}/x86_64' ;; - arm) exec_prefix='${prefix}/armeabi-v7a' ;; - aarch64) exec_prefix='${prefix}/arm64-v8a' ;; - esac + exec_prefix='${prefix}/'${android_abi} fi ;; @@ -24679,14 +24680,42 @@ server/wineserver" fi +freetype_copy_cmd=true + +freetype_copy_cmd=true +rm -f conftest.c conftest +echo "int main(void) { return 0; }" > conftest.c +freetype_so=`$CC conftest.c $CPPFLAGS $CFLAGS $LDFLAGS $FREETYPE_LIBS -Wl,-t -o conftest 2>&1 | grep -m1 'libfreetype\.so'` +rm -f conftest.c conftest +if test -n "$freetype_so" && test -f "$freetype_so"; then + freetype_copy_cmd="cp -f '$freetype_so' dlls/wineandroid.drv/lib/${android_abi}/" +fi + as_fn_append wine_rules " dlls/ntdll/unix/version.c: dummy @version=\`(GIT_DIR=${wine_srcdir}.git git describe HEAD 2>/dev/null || echo \"wine-\$(PACKAGE_VERSION)\") | sed -n -e '\$\$s/\(.*\)/const char wine_build[] = \"\\1\";/p'\` && (echo \$\$version | cmp -s - \$@) || echo \$\$version >\$@ || (rm -f \$@ && exit 1) programs/winetest/build.rc: dummy @build=\"STRINGTABLE { 1 \\\"\`GIT_DIR=${wine_srcdir}.git git rev-parse HEAD 2>/dev/null\`\\\" }\" && (echo \$\$build | cmp -s - \$@) || echo \$\$build >\$@ || (rm -f \$@ && exit 1) -dlls/wineandroid.drv/wine-debug.apk: dlls/wineandroid.drv/build.gradle ${wine_srcdir}dlls/wineandroid.drv/AndroidManifest.xml ${wine_srcdir}dlls/wineandroid.drv/WineActivity.java ${wine_srcdir}dlls/wineandroid.drv/wine.svg - cd dlls/wineandroid.drv && gradle -q -Psrcdir=$srcdir assembleDebug - mv dlls/wineandroid.drv/build/outputs/apk/wine-debug.apk \$@" +dlls/wineandroid.drv/wine-debug.apk: all dlls/wineandroid.drv/build.gradle ${wine_srcdir}dlls/wineandroid.drv/AndroidManifest.xml ${wine_srcdir}dlls/wineandroid.drv/WineActivity.java ${wine_srcdir}dlls/wineandroid.drv/wine.svg + @rm -rf dlls/wineandroid.drv/assets + @mkdir -p dlls/wineandroid.drv/assets dlls/wineandroid.drv/lib/${android_abi} + @\$(MAKE) install prefix= DESTDIR=dlls/wineandroid.drv/assets STRIP=true + @${freetype_copy_cmd} + @rm -rf dlls/wineandroid.drv/assets/include + @for f in dlls/wineandroid.drv/assets/${android_abi}/bin/*; \ +do \ + if test -L "\$\$f"; then \ + rm -f "\$\$f"; \ + printf '%s\n' '#!/system/bin/sh' \ + 'name=\$\${0##*/}' \ + 'name=\$\${name%.exe}' \ + 'name=\$\${name%%-*}' \ + 'exec ../lib/wine/i386-unix/wine "\$\$name.exe" "\$\$@"' > "\$\$f"; \ + chmod 755 "\$\$f"; \ + fi; \ +done + @cd dlls/wineandroid.drv && gradle -q -Psrcdir=$srcdir assembleDebug + @mv dlls/wineandroid.drv/build/outputs/apk/debug/wine-debug.apk \$@" EXTERNAL_SUBDIRS="libs/capstone libs/c++ libs/c++abi libs/faudio libs/fluidsynth libs/gsm libs/icucommon libs/icui18n libs/jpeg libs/jxr libs/lcms2 libs/ldap libs/mpg123 libs/musl libs/png libs/tiff libs/tomcrypt libs/unwind libs/vkd3d libs/xml2 libs/xslt libs/zlib libs/compiler-rt" diff --git a/configure.ac b/configure.ac index 89243b45be0..51c3c6ac98f 100644 --- a/configure.ac +++ b/configure.ac @@ -1056,14 +1056,15 @@ case $host_os in WINE_CHECK_SONAME(GLESv2,glFlush) + case $HOST_ARCH in + i386) android_abi='x86' ;; + x86_64) android_abi='x86_64' ;; + arm) android_abi='armeabi-v7a' ;; + aarch64) android_abi='arm64-v8a' ;; + esac if test "x$exec_prefix" = xNONE then - case $HOST_ARCH in - i386) exec_prefix='${prefix}/x86' ;; - x86_64) exec_prefix='${prefix}/x86_64' ;; - arm) exec_prefix='${prefix}/armeabi-v7a' ;; - aarch64) exec_prefix='${prefix}/arm64-v8a' ;; - esac + exec_prefix='${prefix}/'${android_abi} fi ;; @@ -3831,14 +3832,42 @@ fi dnl Rules for generated source files +freetype_copy_cmd=true + +freetype_copy_cmd=true +rm -f conftest.c conftest +echo "int main(void) { return 0; }" > conftest.c +freetype_so=`$CC conftest.c $CPPFLAGS $CFLAGS $LDFLAGS $FREETYPE_LIBS -Wl,-t -o conftest 2>&1 | grep -m1 'libfreetype\.so'` +rm -f conftest.c conftest +if test -n "$freetype_so" && test -f "$freetype_so"; then + freetype_copy_cmd="cp -f '$freetype_so' dlls/wineandroid.drv/lib/${android_abi}/" +fi + WINE_APPEND_RULE( [dlls/ntdll/unix/version.c: dummy @version=\`(GIT_DIR=${wine_srcdir}.git git describe HEAD 2>/dev/null || echo \"wine-\$(PACKAGE_VERSION)\") | sed -n -e '\$\$s/\(.*\)/const char wine_build[[]] = \"\\1\";/p'\` && (echo \$\$version | cmp -s - \$[@]) || echo \$\$version >\$[@] || (rm -f \$[@] && exit 1) programs/winetest/build.rc: dummy @build=\"STRINGTABLE { 1 \\\"\`GIT_DIR=${wine_srcdir}.git git rev-parse HEAD 2>/dev/null\`\\\" }\" && (echo \$\$build | cmp -s - \$[@]) || echo \$\$build >\$[@] || (rm -f \$[@] && exit 1) -dlls/wineandroid.drv/wine-debug.apk: dlls/wineandroid.drv/build.gradle ${wine_srcdir}dlls/wineandroid.drv/AndroidManifest.xml ${wine_srcdir}dlls/wineandroid.drv/WineActivity.java ${wine_srcdir}dlls/wineandroid.drv/wine.svg - cd dlls/wineandroid.drv && gradle -q -Psrcdir=$srcdir assembleDebug - mv dlls/wineandroid.drv/build/outputs/apk/wine-debug.apk \$[@]]) +dlls/wineandroid.drv/wine-debug.apk: all dlls/wineandroid.drv/build.gradle ${wine_srcdir}dlls/wineandroid.drv/AndroidManifest.xml ${wine_srcdir}dlls/wineandroid.drv/WineActivity.java ${wine_srcdir}dlls/wineandroid.drv/wine.svg + @rm -rf dlls/wineandroid.drv/assets + @mkdir -p dlls/wineandroid.drv/assets dlls/wineandroid.drv/lib/${android_abi} + @\$(MAKE) install prefix= DESTDIR=dlls/wineandroid.drv/assets STRIP=true + @${freetype_copy_cmd} + @rm -rf dlls/wineandroid.drv/assets/include + @for f in dlls/wineandroid.drv/assets/${android_abi}/bin/*; \ +do \ + if test -L "\$\$f"; then \ + rm -f "\$\$f"; \ + printf '%s\n' '#!/system/bin/sh' \ + 'name=\$\${0##*/}' \ + 'name=\$\${name%.exe}' \ + 'name=\$\${name%%-*}' \ + 'exec ../lib/wine/i386-unix/wine "\$\$name.exe" "\$\$@"' > "\$\$f"; \ + chmod 755 "\$\$f"; \ + fi; \ +done + @cd dlls/wineandroid.drv && gradle -q -Psrcdir=$srcdir assembleDebug + @mv dlls/wineandroid.drv/build/outputs/apk/debug/wine-debug.apk \$[@]]) dnl Misc rules -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10354
The goal is not to build the full package from the Wine makefile, it needs to be done from an external script (cf. https://gitlab.winehq.org/winehq/tools/-/blob/master/packaging/android/build...). The makefile rules are only meant to test that the build can succeed. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354#note_132550
https://gitlab.winehq.org/winehq/tools/-/blob/master/packaging/android/build...
It’s good to know that such a script exists. I wasn’t aware of it before, as there are no references to it in the Wine source tree, and manual searches mostly lead to third-party scripts. I also tried to locate it with the help of ChatGPT, but it didn’t come up there either. In any case, I’m glad there is an official one. Would it make sense to add a reference to this script in `wineandroid.drv`'s `Makefile.in`? That said, I still have the impression that preparing the prefix and pulling required dependencies (NOT building them) are steps that could be handled by the main Wine build system, since they are required for producing a usable Android package. For the purpose of the makefile test rules, missing dependencies could simply result in warnings rather than hard failures. Resolving library paths from detected CFLAGS/LDFLAGS can also be reliably reproduced using the linker trace option (`-Wl,-t`), so it would likely be more appropriate to handle this in the Wine build system rather than in external scripts. Another issue is that tools like `wineboot` and `winedbg` used to be scripts, but are now symlinks. Since `aapt` does not preserve or handle symlinks, they cannot be packaged directly into the APK, which requires replacing them with wrapper scripts during staging.
The makefile rules are only meant to test that the build can succeed.
I understand that. However, in practice the Android project will not build with the system Gradle anyway. The Android Gradle plugin is updated frequently by Google and typically requires a newer Gradle version than what is packaged in most distributions, so relying on the system Gradle is not sufficient to validate the build. Given that, it still makes sense to keep APK generation separate from the default `all` target, since a proper build requires additional setup and a suitable Gradle environment. The other changes should be fine. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354#note_132596
That said, I still have the impression that preparing the prefix and pulling required dependencies (NOT building them) are steps that could be handled by the main Wine build system, since they are required for producing a usable Android package. For the purpose of the makefile test rules, missing dependencies could simply result in warnings rather than hard failures. Resolving library paths from detected CFLAGS/LDFLAGS can also be reliably reproduced using the linker trace option (-Wl,-t), so it would likely be more appropriate to handle this in the Wine build system rather than in external scripts.
Any complexity that's related to packaging should be handled in external scripts, like we do for Debian/RPM/macOS.
Another issue is that tools like wineboot and winedbg used to be scripts, but are now symlinks. Since aapt does not preserve or handle symlinks, they cannot be packaged directly into the APK, which requires replacing them with wrapper scripts during staging.
There shouldn't be any reason to ship these symlinks on Android, nobody is going to run them from the command line.
Given that, it still makes sense to keep APK generation separate from the default all target, since a proper build requires additional setup and a suitable Gradle environment.
I'm happy to remove things from the makefile, but that means the gradle script etc. should be moved to the packaging repo as well. We don't want to include dead code. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354#note_132607
Any complexity that's related to packaging should be handled in external scripts, like we do for Debian/RPM/macOS.
That makes sense. In that case, it would be even more consistent to keep APK generation separate from the default `all` target, so the build can be completed first, then optionally staged (prefix prepared, files adjusted), and only then packaged by the external script. At the same time, to avoid confusion or silent misbuilds, the APK target could perform explicit checks and fail fast if the staging is incomplete, for example verifying that key components like `libfreetype.so` in libs, and `wineserver` and `ntdll.so` in their expected locations are present. If not, it could print a clear error message pointing to the official packaging script as the expected workflow.
There shouldn't be any reason to ship these symlinks on Android, nobody is going to run them from the command line.
You're right. I initially ran into crashes and suspected symlinks were the issue, but it turned out to be a `wine-preloader` segfaulting problem that was resolved by updating to a newer NDK. I will drop that part later.
I'm happy to remove things from the makefile, but that means the gradle script etc. should be moved to the packaging repo as well. We don't want to include dead code.
I’m not suggesting to remove this entirely, only to move APK generation into a separate target instead of keeping it as part of `all`. To reduce the risk of misuse, I can also update the Gradle script to explicitly fail with clear errors if the Gradle/AGP versions do not meet the expected requirements. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354#note_132658
That makes sense. In that case, it would be even more consistent to keep APK generation separate from the default all target, so the build can be completed first, then optionally staged (prefix prepared, files adjusted), and only then packaged by the external script.
The only reason for building the APK in the makefile is to make sure that the code builds, in particular the Java side. That's why it has to be part of the main target. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10354#note_132690
participants (3)
-
Alexandre Julliard (@julliard) -
Twaik Yont -
Twaik Yont (@twaik)