This is an attempt of developing projects with Gradle on a musl based system. Although Gradle doesn't support musl directly we can replace glibc-dependent binaries by static ones.
This guide is not definitive and might stop working on later versions of Gradle.
If you have ever tried running Gradle on a musl based system you might have faced this error:
Starting a Gradle Daemon (subsequent builds will be faster) FAILURE: Build failed with an exception. * What went wrong: Unable to start the daemon process. This problem might be caused by incorrect configuration of the daemon. For example, an unrecognized jvm option is used.For more details on the daemon, please refer to https://docs.gradle.org/9.0.0/userguide/gradle_daemon.html in the Gradle documentation. Process command line: /opt/jdk-21.0.8+9/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -Xms256m -Xmx512m -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -cp /opt/gradle-9.0.0/lib/gradle-daemon-main-9.0.0.jar -javaagent:/opt/gradle-9.0.0/lib/agents/gradle-instrumentation-agent-9.0.0.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 9.0.0 Please read the following process output to find out more: ----------------------- FAILURE: Build failed with an exception. * What went wrong: Could not initialize native services. * Try: > Run with --info or --debug option to get more log output. > Run with --scan to generate a Build Scan (Powered by Develocity). > Get more help at https://help.gradle.org. * Exception is: org.gradle.internal.service.ServiceCreationException: Could not initialize native services. at org.gradle.internal.nativeintegration.services.NativeServices.initialize(NativeServices.java:290) at org.gradle.internal.nativeintegration.services.NativeServices.initializeOnDaemon(NativeServices.java:250) at org.gradle.launcher.daemon.bootstrap.DaemonMain.doAction(DaemonMain.java:114) at org.gradle.launcher.bootstrap.EntryPoint.run(EntryPoint.java:53) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) at java.base/java.lang.reflect.Method.invoke(Method.java:580) at org.gradle.launcher.bootstrap.ProcessBootstrap.runNoExit(ProcessBootstrap.java:72) at org.gradle.launcher.bootstrap.ProcessBootstrap.run(ProcessBootstrap.java:39) at org.gradle.launcher.daemon.bootstrap.GradleDaemon.main(GradleDaemon.java:22) Caused by: net.rubygrapefruit.platform.NativeException: Failed to initialise native integration. at org.gradle.fileevents.FileEvents.init(FileEvents.java:71) at org.gradle.internal.nativeintegration.services.NativeServices\$NativeFeatures\$1.initialize(NativeServices.java:118) at org.gradle.internal.nativeintegration.services.NativeServices.<init>(NativeServices.java:343) at org.gradle.internal.nativeintegration.services.NativeServices.initialize(NativeServices.java:288) ... 8 more Caused by: java.lang.UnsatisfiedLinkError: /home/user/.gradle/native/0.2.8/x86_64-linux-gnu/libgradle-fileevents.so: Error relocating /home/user/.gradle/native/0.2.8/x86_64-linux-gnu/libgradle-fileevents.so: strtoll_l: symbol not found at java.base/jdk.internal.loader.NativeLibraries.load(Native Method) at java.base/jdk.internal.loader.NativeLibraries\$NativeLibraryImpl.open(NativeLibraries.java:331) at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:197) at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(NativeLibraries.java:139) at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2418) at java.base/java.lang.Runtime.load0(Runtime.java:852) at java.base/java.lang.System.load(System.java:2025) at org.gradle.fileevents.FileEvents.init(FileEvents.java:55) ... 11 more * Try: > Run with --stacktrace option to get the stack trace. > Run with --info or --debug option to get more log output. > Run with --scan to generate a Build Scan (Powered by Develocity). > Get more help at https://help.gradle.org.
Which can be isolated to the following cause:
Caused by: java.lang.UnsatisfiedLinkError: /home/user/.gradle/native/0.2.8/x86_64-linux-gnu/libgradle-fileevents.so: Error relocating /home/user/.gradle/native/0.2.8/x86_64-linux-gnu/libgradle-fileevents.so: strtoll_l: symbol not found
Our first attempt will then be to replace this file by a native alternative that we'll build.
I decided to build myself this library from source as it isn't so complex.
But the only build system it supports besides Gradle is Zig, so we must install it first.
Download, extract and add it's directory to the PATH.
Make sure the version you are downloading is the version expected from the build.zig of the project.
$ curl -fLO https://ziglang.org/download/0.13.0/zig-linux-x86_64-0.13.0.tar.xz $ tar xf zig-linux-x86_64-0.13.0.tar.xz $ echo 'PATH=$HOME/zig:$PATH' >> .profile
Download and cache a wrapper for the Gradle version.
#!/bin/sh -e tmp=$(mktemp -d) cd "$tmp" mkdir -p gradle/wrapper curl -fLO https://raw.githubusercontent.com/gradle/gradle/master/gradlew curl -fsSL https://raw.githubusercontent.com/gradle/gradle/master/gradle/wrapper/gradle-wrapper.jar \ -o gradle/wrapper/gradle-wrapper.jar cat > gradle/wrapper/gradle-wrapper.properties <<'EOF' distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.6-bin.zip EOF chmod +x gradlew ./gradlew --version rm -rf "$tmp"
Download the source file for the respective version of this library from the official GitHub repository and extract it.
$ curl -fLO https://github.com/gradle/gradle-fileevents/archive/refs/tags/0.2.8.tar.gz $ tar xf 0.2.8.tar.gz
Run the build script I wrote for this package:
#!/bin/sh -eu
# Builds libgradle-fileevents.so with Zig, without running Gradle.
#
# This reproduces what the project's own Gradle build does (build.gradle.kts +
# build-logic/): generate the version files, let javac generate the JNI headers,
# then run `zig build build` with a target and JAVA_HOME.
#
# Environment:
# JAVA_HOME JDK with javac and include/ (required; JDK 17+)
# GRADLE_LIB directory with Gradle's own jars (native-platform, jsr305, slf4j).
# Default: lib/ of the first Gradle distribution under
# ~/.gradle/wrapper/dists.
# VERSION version string baked into the library. It MUST be identical to
# the version of the gradle-fileevents jar that Gradle ships, or
# FileEvents.init() rejects the library. Gradle 8.13 -> 0.2.7.
# TARGET Zig target (default x86_64-linux-musl)
# OPTIMIZE Zig optimize mode (default ReleaseSmall, as upstream)
# ZIG Zig binary (default: zig). Upstream uses 0.14.1.
VERSION=${VERSION:-'0.2.7'}
TARGET=${TARGET:-'x86_64-linux-musl'}
OPTIMIZE=${OPTIMIZE:-'ReleaseSmall'}
ZIG=${ZIG:-'zig'}
[ -z "$GRADLE_LIB" ] && {
GRADLE_LIB=$(ls -d "$HOME"/.gradle/wrapper/dists/gradle-*/*/gradle-*/lib 2>/dev/null | head -n 1 || true)
}
[ -n "$GRADLE_LIB" ] && [ -d "$GRADLE_LIB" ] || {
echo 'Set GRADLE_LIB to the lib/ directory of a Gradle distribution' >&2
exit 1
}
GEN=build/generated/sources
echo "==> Version $VERSION, target $TARGET, zig $($ZIG version)"
# 1. Version files (same content as build-logic/.../GenerateVersions.kt)
mkdir -p "$GEN/java/version/org/gradle/fileevents/internal" "$GEN/headers/version" "$GEN/headers/java"
cat > "$GEN/java/version/org/gradle/fileevents/internal/FileEventsVersion.java" <<EOF
package org.gradle.fileevents.internal;
public class FileEventsVersion {
public static final String VERSION = "$VERSION";
}
EOF
printf '#define FILE_EVENTS_VERSION "%s"\n' "$VERSION" > "$GEN/headers/version/fileevents_version.h"
# 2. JNI headers: javac -h, like compileJava.options.headerOutputDirectory
CLASSES=$(mktemp -d)
trap 'rm -rf "$CLASSES"' EXIT
rm -f "$GEN"/headers/java/*.
$JAVA_HOME/bin/javac -nowarn \
-cp "$GRADLE_LIB/*" \
-d "$CLASSES" \
-h "$GEN/headers/java" \
$(find src/main/java "$GEN/java" -name '*.java')
# 3. build.zig, adjusted for a Linux JDK. Upstream is built on macOS, so it adds
# $JAVA_HOME/include/darwin; on Linux jni_md.h lives in include/linux.
# -Werror is dropped so a different Zig/clang cannot fail the build on warnings.
sed -e 's#include/darwin#include/linux#; s#java_darwin_include_path#java_linux_include_path#g; /"-Werror",/d' \
build.zig > build.linux.zig
# 4. Build, the same way ZigBuild.kt invokes zig
OUT="$PWD/build/zig/$TARGET"
rm -rf "$OUT"
$ZIG build build --build-file build.linux.zig \
--prefix "$OUT" --cache-dir "$PWD/build/.zig-cache" \
-Dtarget="$TARGET" -Doptimize="$OPTIMIZE"
LIB="$OUT/out/libgradle-fileevents.so"
echo "==> Built $LIB"
ls -l "$LIB"
if command -v readelf >/dev/null 2>&1; then
echo '--- NEEDED:'
readelf -d "$LIB" | grep NEEDED || true
fi
if command -v nm >/dev/null 2>&1; then
echo "--- JNI exports: $(nm -D --defined-only "$LIB" | grep -c ' Java_')"
fi
After it has finished building we can already replace the distributed library by the one we just compiled:
cp build/zig/x86_64-linux-musl/out/libgradle-fileevents.so /home/user/.gradle/native/0.2.8/x86_64-linux-gnu/libgradle-fileevents.so
On some of my experiments running gradle assembleDebug would
make it fail saying aapt2 couldn't be executed:
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:processDebugResources'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
> Failed to transform core-1.13.1.aar (androidx.core:core:1.13.1) to match attributes {artifactType=android-compiled-dependencies-resources, org.gradle.category=library, org.gradle.dependency.bundling=external, org.gradle.libraryelements=aar, org.gradle.status=release, org.gradle.usage=java-runtime}.
> Execution failed for AarResourcesCompilerTransform: /home/user/.gradle/caches/9.3.1/transforms/36b2cade356a97d2665ce9d4675dfa9b/transformed/core-1.13.1.
> AAPT2 aapt2-9.1.0-14792394-linux Daemon #0: Daemon startup failed
This should not happen under normal circumstances, please file an issue if it does.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.
BUILD FAILED in 2m 44s
[========= ] 25%
Running Gradle task 'assembleDebug'... 164.8s
Error: Gradle task assembleDebug failed with exit code 1
In case this happens to you, just replace that aapt2 binary by the SDK's aapt2 binary which should be static:
$ dir=$(find ~/.gradle -name 'aapt2-8.6.1-11315950-linux') $ cp $ANDROID_SDK/build-tools/36.1.0/aapt2 $dir
After all this you are ready to go build the world.