Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions ci/Jenkinsfile.android
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.9.32'
library 'status-jenkins-lib@fix-android-signing'

/* Options section can't access functions in objects. */
def isPRBuild = utils.isPRBuild()
Expand All @@ -11,7 +11,7 @@ pipeline {
/* Image with Ubuntu 22.04, QT 6.9.2, Android SDK/NDK, Go, and Nim */
docker {
label 'linuxcontainer'
image 'harbor.status.im/status-im/status-desktop-build:1.0.6-qt6.9.2-android'
image 'harbor.status.im/status-im/status-desktop-build:1.0.7-qt6.9.2-android'
alwaysPull true
args '--entrypoint="" ' +
'--volume=/nix:/nix ' +
Expand Down Expand Up @@ -70,8 +70,11 @@ pipeline {
QMAKE = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a/bin/qmake"
/* override package type if set, otherwise auto-select based on branch */
PACKAGE_TYPE = "${params.PACKAGE_TYPE != 'auto' ? params.PACKAGE_TYPE : (isReleaseBranch ? 'aab' : 'apk')}"
/* BUILD_VARIANT controls package name and signing: pr = app.status.mobile.pr, release = app.status.mobile */
BUILD_VARIANT = "${utils.isReleaseBuild() ? 'release' : 'pr'}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have it in 2 places? here and app.groovy.

STATUS_ANDROID_APP_NAME = "${utils.isReleaseBuild() ? 'Status' : 'StatusPR'}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this logic in multiple places:

  • here
  • build.gradle
  • buildApp.sh
  • Status.pro

Can we reduce it?

STATUS_ARTIFACT = "pkg/${utils.pkgFilename(ext: env.PACKAGE_TYPE, arch: 'arm64', version: env.VERSION, type: env.APP_TYPE)}"
STATUS_BINARY = "${WORKSPACE}/mobile/bin/android/qt6/Status.${env.PACKAGE_TYPE}"
STATUS_BINARY = "${WORKSPACE}/mobile/bin/android/qt6/${env.STATUS_ANDROID_APP_NAME}.${env.PACKAGE_TYPE}"
NIM_SDS_SOURCE_DIR = "${env.WORKSPACE_TMP}/nim-sds"
}

Expand Down
17 changes: 14 additions & 3 deletions mobile/android/qt6/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.status.mobile"
android:installLocation="auto"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:versionName="-- %%INSERT_VERSION_NAME%% --">
android:installLocation="auto">
<!-- non-dangerous permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.NFC"/>
Expand Down Expand Up @@ -60,6 +58,19 @@
<meta-data
android:name="android.app.arguments"
android:value="" />

<!-- Required for Qt6 to properly detect and load native libraries -->
<meta-data
android:name="android.app.system_libs_prefix"
android:value="/system/lib64/" />

<meta-data
android:name="android.app.use_local_qt_libs"
android:value="1" />

<meta-data
android:name="android.app.bundle_local_qt_libs"
android:value="1" />
</activity>

<provider
Expand Down
36 changes: 36 additions & 0 deletions mobile/android/qt6/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,42 @@ android {
versionCode = (System.currentTimeMillis() / 60000).toInteger()
}

signingConfigs {
pr {
storeFile file(System.getenv("PR_KEYSTORE_PATH") ?: "/dev/null")
storePassword System.getenv("PR_KEYSTORE_PASSWORD") ?: ""
keyAlias System.getenv("PR_KEY_ALIAS") ?: ""
keyPassword System.getenv("PR_KEY_PASSWORD") ?: ""
}
production {
storeFile file(System.getenv("RELEASE_KEYSTORE_PATH") ?: "/dev/null")
storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD") ?: ""
keyAlias System.getenv("RELEASE_KEY_ALIAS") ?: ""
keyPassword System.getenv("RELEASE_KEY_PASSWORD") ?: ""
}
}

flavorDimensions "variant"

productFlavors {
pr {
dimension "variant"
applicationId "app.status.mobile.pr"
resValue "string", "app_name", "StatusPR"
if (System.getenv("PR_KEYSTORE_PATH") && file(System.getenv("PR_KEYSTORE_PATH")).exists()) {
signingConfig signingConfigs.pr
}
}
production {
dimension "variant"
applicationId "app.status.mobile"
resValue "string", "app_name", "Status"
if (System.getenv("RELEASE_KEYSTORE_PATH") && file(System.getenv("RELEASE_KEYSTORE_PATH")).exists()) {
signingConfig signingConfigs.production
}
}
}

buildTypes {
release {
debuggable = false
Expand Down
11 changes: 11 additions & 0 deletions mobile/android/qt6/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
android.useAndroidX=true
qtGradlePluginType=com.android.application
androidPackageName=app.status.mobile
androidBuildToolsVersion=35.0.0
androidCompileSdkVersion=android-35
androidNdkVersion=27.2.12479018
qtAndroidDir=/opt/qt/6.9.2/android_arm64_v8a/src/android/java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI I guess? Will it harm on dev machinse if path is different?

qtMinSdkVersion=28
qtTargetSdkVersion=35
qtTargetAbiList=arm64-v8a
org.gradle.jvmargs=-Xmx8704M -XX:+UseParallelGC
6 changes: 6 additions & 0 deletions mobile/android/qt6/res/xml/qtprovider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="." />
<cache-path name="cache" path="." />
<files-path name="files" path="." />
</paths>
9 changes: 9 additions & 0 deletions mobile/android/qt6/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}

rootProject.name = 'android-build'
9 changes: 8 additions & 1 deletion mobile/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,13 @@ RUN apt update -yq && apt install -yq software-properties-common \
--slave /usr/bin/g++ g++ /usr/bin/g++-9 \
&& apt-get -qq clean

ENV PATH="/root/go/bin:${PATH}"
ARG GRADLE_VERSION=8.11.1
RUN wget -q https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip \
&& unzip -q /tmp/gradle.zip -d /opt \
&& ln -s /opt/gradle-${GRADLE_VERSION} /opt/gradle \
&& rm /tmp/gradle.zip

ENV GRADLE_HOME=/opt/gradle
ENV PATH="/opt/gradle/bin:/root/go/bin:${PATH}"

LABEL description="Build image for the Status Android APK."
137 changes: 81 additions & 56 deletions mobile/scripts/buildApp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ BUILD_TYPE=${BUILD_TYPE:-"apk"}
# BUILD_VARIANT controls bundle ID: "pr" = app.status.mobile.pr, "release" = app.status.mobile
export BUILD_VARIANT=${BUILD_VARIANT:-"release"}

# BUILD_VARIANT controls package name and signing:
# - "pr" = app.status.mobile.pr with PR keystore
# - "release" or unset = app.status.mobile with release keystore
BUILD_VARIANT=${BUILD_VARIANT:-"release"}
export BUILD_VARIANT
Comment on lines +20 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One line is better.


QMAKE_BIN="${QMAKE:-qmake}"
QMAKE_CONFIG="CONFIG+=device CONFIG+=release"

PRO_FILE="$CWD/../wrapperApp/Status.pro"

echo "Building wrapperApp for ${OS}, ${ANDROID_ABI}"
echo "Using project file: $PRO_FILE"
echo "Build variant: ${BUILD_VARIANT}"

mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
Expand All @@ -47,84 +54,102 @@ if [[ "${OS}" == "android" ]]; then
echo "Building for Android 35"
ANDROID_PLATFORM=android-35

"$QMAKE_BIN" "$PRO_FILE" "$QMAKE_CONFIG" -spec android-clang ANDROID_ABIS="$ANDROID_ABI" APP_VARIANT="${APP_VARIANT}" VERSION="$DESKTOP_VERSION" -after
# Map BUILD_VARIANT to gradle flavor
if [[ "$BUILD_VARIANT" == "pr" ]]; then
GRADLE_FLAVOR="pr"
OUTPUT_NAME="StatusPR"
else
GRADLE_FLAVOR="production"
OUTPUT_NAME="Status"
fi

echo "Using Gradle flavor: $GRADLE_FLAVOR"
echo "Target name: $OUTPUT_NAME"

"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" "$QMAKE_CONFIG" -spec android-clang ANDROID_ABIS="$ANDROID_ABI" APP_VARIANT="${APP_VARIANT}" VERSION="$DESKTOP_VERSION" -after

# Build the app
make -j"$(nproc)" apk_install_target

if [[ "$BUILD_TYPE" == "aab" ]]; then
if [[ -z "$KEYSTORE_PATH" || -z "$KEYSTORE_PASSWORD" || -z "$KEY_ALIAS" || -z "$KEY_PASSWORD" ]]; then
echo "Error: AAB builds require signing credentials"
echo "Required: KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD"
exit 1
fi
# aux-mode copies dependencies AND generates libs.xml
echo "Preparing Android build..."
androiddeployqt \
--input "$BUILD_DIR/android-${OUTPUT_NAME}-deployment-settings.json" \
--output "$BUILD_DIR/android-build" \
--android-platform "$ANDROID_PLATFORM" \
--verbose \
--aux-mode

# Build with gradle directly to use product flavors
CUSTOM_ANDROID="$CWD/../android/qt${QT_MAJOR}"
ANDROID_BUILD="$BUILD_DIR/android-build"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this should be defined and used earlier.


# Override with custom Android files (manifest, gradle for flavors, src)
# Keep Qt-generated res/ (including libs.xml) but add our custom resources
cp "$CUSTOM_ANDROID/AndroidManifest.xml" "$ANDROID_BUILD/"
cp "$CUSTOM_ANDROID/build.gradle" "$ANDROID_BUILD/"
cp "$CUSTOM_ANDROID/settings.gradle" "$ANDROID_BUILD/"
cp "$CUSTOM_ANDROID/gradle.properties" "$ANDROID_BUILD/"

# Copy custom res files (icons, styles, etc.) but preserve Qt-generated libs.xml
if [[ -d "$CUSTOM_ANDROID/res" ]]; then
# Copy everything except values/libs.xml (Qt generates that)
find "$CUSTOM_ANDROID/res" -type f ! -name "libs.xml" -exec sh -c '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be way simpler to just use rsync with --exclude.

Or you could enable extended glob with shopt -s extglob too, but that might be a bit obscure.

src="$1"
rel="${src#'"$CUSTOM_ANDROID/res/"'}"
dest="'"$ANDROID_BUILD/res/"'$rel"
mkdir -p "$(dirname "$dest")"
cp "$src" "$dest"
' _ {} \;
fi

if [[ ! -f "$KEYSTORE_PATH" ]]; then
echo "Error: Keystore file not found at $KEYSTORE_PATH"
exit 1
fi
if [[ -d "$CUSTOM_ANDROID/src" ]]; then
mkdir -p "$ANDROID_BUILD/src"
cp -r "$CUSTOM_ANDROID/src/"* "$ANDROID_BUILD/src/"
fi

cd "$BUILD_DIR/android-build"

echo "Building AAB..."
androiddeployqt \
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
--output "$BUILD_DIR/android-build" \
--aab \
--release \
--android-platform "$ANDROID_PLATFORM"
if [[ "$BUILD_TYPE" == "aab" ]]; then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Branches look very simmilar with very small diff.
I think it should be one code, parametrised:

  • file extention: aap, apk
  • gradle task: bundle${GRADLE_FLAVOR^}Release, assemble${GRADLE_FLAVOR^}Release

echo "Building AAB with flavor: $GRADLE_FLAVOR..."
gradle "bundle${GRADLE_FLAVOR^}Release" --no-daemon

OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/bundle/release/android-build-release.aab"
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/bundle/${GRADLE_FLAVOR}Release/android-build-${GRADLE_FLAVOR}-release.aab"
if [[ ! -f "$OUTPUT_FILE" ]]; then
echo "Error: Could not find generated AAB file at $OUTPUT_FILE"
echo "Looking for AAB files..."
find "$BUILD_DIR/android-build/build/outputs" -name "*.aab" 2>/dev/null
Comment on lines +120 to +121
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like debug code.

exit 1
fi

# Sign the AAB file (androiddeployqt --sign does not work for AAB files)
"$CWD/android/sign.sh" "$OUTPUT_FILE"

ANDROID_OUTPUT_DIR="bin/android/qt6"
BIN_DIR_ANDROID=${BIN_DIR:-"$CWD/$ANDROID_OUTPUT_DIR"}
mkdir -p "$BIN_DIR_ANDROID"
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/Status.aab"
echo "Build succeeded. Signed AAB is available at $BIN_DIR_ANDROID/Status.aab"
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/${OUTPUT_NAME}.aab"
echo "Build succeeded. AAB is available at $BIN_DIR_ANDROID/${OUTPUT_NAME}.aab"
else
# APK build
NEEDS_SIGNING="false"
if [[ -n "$KEYSTORE_PATH" && -n "$KEYSTORE_PASSWORD" && -n "$KEY_ALIAS" && -n "$KEY_PASSWORD" ]]; then
if [[ -f "$KEYSTORE_PATH" ]]; then
NEEDS_SIGNING="true"
fi
fi
echo "Building APK with flavor: $GRADLE_FLAVOR..."
echo "Contents of android-build directory:"
ls -la
gradle "assemble${GRADLE_FLAVOR^}Release" --no-daemon --info --stacktrace

if [[ "$NEEDS_SIGNING" == "true" ]]; then
echo "Building signed APK..."
androiddeployqt \
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
--output "$BUILD_DIR/android-build" \
--apk "$BUILD_DIR/android-build/Status.apk" \
--release \
--android-platform "$ANDROID_PLATFORM" \
--sign "$KEYSTORE_PATH" "$KEY_ALIAS" \
--storepass "$KEYSTORE_PASSWORD" \
--keypass "$KEY_PASSWORD"
else
echo "Building unsigned APK..."
androiddeployqt \
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
--output "$BUILD_DIR/android-build" \
--apk "$BUILD_DIR/android-build/Status.apk" \
--android-platform "$ANDROID_PLATFORM"
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/apk/${GRADLE_FLAVOR}/release/android-build-${GRADLE_FLAVOR}-release.apk"
if [[ ! -f "$OUTPUT_FILE" ]]; then
# Try alternative path
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/apk/${GRADLE_FLAVOR}/release/android-build-${GRADLE_FLAVOR}-release-unsigned.apk"
fi
if [[ ! -f "$OUTPUT_FILE" ]]; then
echo "Error: Could not find generated APK file"
echo "Looking for APK files..."
find "$BUILD_DIR/android-build/build/outputs" -name "*.apk" 2>/dev/null
exit 1
fi

ANDROID_OUTPUT_DIR="bin/android/qt6"
BIN_DIR_ANDROID=${BIN_DIR:-"$CWD/$ANDROID_OUTPUT_DIR"}
mkdir -p "$BIN_DIR_ANDROID"
cp ./android-build/Status.apk "$BIN_DIR_ANDROID/Status.apk"

if [[ "$NEEDS_SIGNING" == "true" ]]; then
echo "Build succeeded. Signed APK is available at $BIN_DIR_ANDROID/Status.apk"
else
echo "Build succeeded. Unsigned APK is available at $BIN_DIR_ANDROID/Status.apk"
fi
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/${OUTPUT_NAME}.apk"
echo "Build succeeded. APK is available at $BIN_DIR_ANDROID/${OUTPUT_NAME}.apk"
fi
else
"$QMAKE_BIN" "$PRO_FILE" "$QMAKE_CONFIG" -spec macx-ios-clang CONFIG+="$SDK" VERSION="$DESKTOP_VERSION" -after
Expand Down
9 changes: 9 additions & 0 deletions mobile/wrapperApp/Status.pro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ TEMPLATE = app

QT += quick gui qml webview svg widgets multimedia

BUILD_VARIANT = $$(BUILD_VARIANT)
equals(BUILD_VARIANT, "pr") {
TARGET = StatusPR
message("Building PR variant: TARGET = StatusPR")
} else {
TARGET = Status
message("Building production variant: TARGET = Status")
}

equals(QT_MAJOR_VERSION, 6) {
message("qt 6 config!!")
QT += core5compat core
Expand Down
Loading