Skip to content

Commit 6d6918f

Browse files
committed
ci: fix android signing
- points to `fix-android-signing` branch in status-jenkins-lib - use gradle build flavours for PR and Release. - signing config moved to gradle
1 parent 482c9b9 commit 6d6918f

File tree

10 files changed

+226
-64
lines changed

10 files changed

+226
-64
lines changed

ci/Jenkinsfile.android

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env groovy
2-
library 'status-jenkins-lib@v1.9.31'
2+
library 'status-jenkins-lib@fix-android-signing'
33

44
/* Options section can't access functions in objects. */
55
def isPRBuild = utils.isPRBuild()
@@ -11,7 +11,7 @@ pipeline {
1111
/* Image with Ubuntu 22.04, QT 6.9.2, Android SDK/NDK, Go, and Nim */
1212
docker {
1313
label 'linuxcontainer'
14-
image 'harbor.status.im/status-im/status-desktop-build:1.0.6-qt6.9.2-android'
14+
image 'harbor.status.im/status-im/status-desktop-build:1.0.7-qt6.9.2-android'
1515
alwaysPull true
1616
args '--entrypoint="" ' +
1717
'--volume=/nix:/nix ' +
@@ -70,8 +70,11 @@ pipeline {
7070
QMAKE = "/opt/qt/${env.QT_VERSION}/android_arm64_v8a/bin/qmake"
7171
/* override package type if set, otherwise auto-select based on branch */
7272
PACKAGE_TYPE = "${params.PACKAGE_TYPE != 'auto' ? params.PACKAGE_TYPE : (isReleaseBranch ? 'aab' : 'apk')}"
73+
/* BUILD_VARIANT controls package name and signing: pr = app.status.mobile.pr, release = app.status.mobile */
74+
BUILD_VARIANT = "${utils.isReleaseBuild() ? 'release' : 'pr'}"
75+
STATUS_ANDROID_APP_NAME = "${utils.isReleaseBuild() ? 'Status' : 'StatusPR'}"
7376
STATUS_ARTIFACT = "pkg/${utils.pkgFilename(ext: env.PACKAGE_TYPE, arch: 'arm64', version: env.VERSION, type: env.APP_TYPE)}"
74-
STATUS_BINARY = "${WORKSPACE}/mobile/bin/android/qt6/Status.${env.PACKAGE_TYPE}"
77+
STATUS_BINARY = "${WORKSPACE}/mobile/bin/android/qt6/${env.STATUS_ANDROID_APP_NAME}.${env.PACKAGE_TYPE}"
7578
NIM_SDS_SOURCE_DIR = "${env.WORKSPACE_TMP}/nim-sds"
7679
}
7780

ci/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,48 @@ It also expects the presence of the following credentials:
2626
* `macos-keychain-file` - Keychain file with the MacOS signing certificate.
2727

2828
You can read about how to create such a keychain [here](https://github.com/status-im/infra-docs/blob/master/articles/macos_signing_keychain.md).
29+
30+
## Android
31+
32+
Android builds use separate signing configurations for PR and release builds to ensure proper app differentiation.
33+
34+
### Bundle Identifiers (Application IDs)
35+
36+
| Build Type | Application ID | Output Name |
37+
|------------|------------------------|--------------|
38+
| PR builds | `app.status.mobile.pr` | `StatusPR` |
39+
| Release | `app.status.mobile` | `Status` |
40+
41+
### Signing Configuration
42+
43+
| Build Type | Keystore Credential | Purpose |
44+
|------------|-----------------------------------|--------------------------------|
45+
| PR builds | `status-app-pr-keystore` | Testing on any Android device |
46+
| Release | `status-app-release-keystore` | Google Play Store releases |
47+
48+
### Environment Variables
49+
50+
The `BUILD_VARIANT` environment variable controls which Gradle product flavor is built:
51+
- `BUILD_VARIANT=pr` - Builds `prRelease` flavor with app name "Status PR"
52+
- `BUILD_VARIANT=release` (default) - Builds `productionRelease` flavor with app name "Status"
53+
54+
Gradle reads signing credentials from these environment variables:
55+
56+
| PR Build Variables | Release Build Variables |
57+
|----------------------------|--------------------------------|
58+
| `PR_KEYSTORE_PATH` | `RELEASE_KEYSTORE_PATH` |
59+
| `PR_KEYSTORE_PASSWORD` | `RELEASE_KEYSTORE_PASSWORD` |
60+
| `PR_KEY_ALIAS` | `RELEASE_KEY_ALIAS` |
61+
| `PR_KEY_PASSWORD` | `RELEASE_KEY_PASSWORD` |
62+
63+
### Jenkins Credentials Required
64+
65+
For PR builds:
66+
- `status-app-pr-keystore` - Keystore file for PR signing → `PR_KEYSTORE_PATH`
67+
- `status-app-pr-keystore-password` - Keystore password → `PR_KEYSTORE_PASSWORD`
68+
- `status-app-pr-keystore-key` - Key alias/password → `PR_KEY_ALIAS` / `PR_KEY_PASSWORD`
69+
70+
For Release builds:
71+
- `status-app-release-keystore` - Keystore file for release signing → `RELEASE_KEYSTORE_PATH`
72+
- `status-app-release-keystore-password` - Keystore password → `RELEASE_KEYSTORE_PASSWORD`
73+
- `status-app-release-keystore-key` - Key alias/password → `RELEASE_KEY_ALIAS` / `RELEASE_KEY_PASSWORD`

mobile/android/qt6/AndroidManifest.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
<?xml version="1.0"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="app.status.mobile"
4-
android:installLocation="auto"
5-
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
6-
android:versionName="-- %%INSERT_VERSION_NAME%% --">
4+
android:installLocation="auto">
75
<!-- non-dangerous permissions -->
86
<uses-permission android:name="android.permission.INTERNET"/>
97
<uses-permission android:name="android.permission.NFC"/>
@@ -60,6 +58,19 @@
6058
<meta-data
6159
android:name="android.app.arguments"
6260
android:value="" />
61+
62+
<!-- Required for Qt6 to properly detect and load native libraries -->
63+
<meta-data
64+
android:name="android.app.system_libs_prefix"
65+
android:value="/system/lib64/" />
66+
67+
<meta-data
68+
android:name="android.app.use_local_qt_libs"
69+
android:value="1" />
70+
71+
<meta-data
72+
android:name="android.app.bundle_local_qt_libs"
73+
android:value="1" />
6374
</activity>
6475

6576
<provider

mobile/android/qt6/build.gradle

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,42 @@ android {
9191
versionCode = (System.currentTimeMillis() / 60000).toInteger()
9292
}
9393

94+
signingConfigs {
95+
pr {
96+
storeFile file(System.getenv("PR_KEYSTORE_PATH") ?: "/dev/null")
97+
storePassword System.getenv("PR_KEYSTORE_PASSWORD") ?: ""
98+
keyAlias System.getenv("PR_KEY_ALIAS") ?: ""
99+
keyPassword System.getenv("PR_KEY_PASSWORD") ?: ""
100+
}
101+
production {
102+
storeFile file(System.getenv("RELEASE_KEYSTORE_PATH") ?: "/dev/null")
103+
storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD") ?: ""
104+
keyAlias System.getenv("RELEASE_KEY_ALIAS") ?: ""
105+
keyPassword System.getenv("RELEASE_KEY_PASSWORD") ?: ""
106+
}
107+
}
108+
109+
flavorDimensions "variant"
110+
111+
productFlavors {
112+
pr {
113+
dimension "variant"
114+
applicationId "app.status.mobile.pr"
115+
resValue "string", "app_name", "StatusPR"
116+
if (System.getenv("PR_KEYSTORE_PATH") && file(System.getenv("PR_KEYSTORE_PATH")).exists()) {
117+
signingConfig signingConfigs.pr
118+
}
119+
}
120+
production {
121+
dimension "variant"
122+
applicationId "app.status.mobile"
123+
resValue "string", "app_name", "Status"
124+
if (System.getenv("RELEASE_KEYSTORE_PATH") && file(System.getenv("RELEASE_KEYSTORE_PATH")).exists()) {
125+
signingConfig signingConfigs.production
126+
}
127+
}
128+
}
129+
94130
buildTypes {
95131
release {
96132
debuggable = false
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
android.useAndroidX=true
2+
qtGradlePluginType=com.android.application
3+
androidPackageName=app.status.mobile
4+
androidBuildToolsVersion=35.0.0
5+
androidCompileSdkVersion=android-35
6+
androidNdkVersion=27.2.12479018
7+
qtAndroidDir=/opt/qt/6.9.2/android_arm64_v8a/src/android/java
8+
qtMinSdkVersion=28
9+
qtTargetSdkVersion=35
10+
qtTargetAbiList=arm64-v8a
11+
org.gradle.jvmargs=-Xmx8704M -XX:+UseParallelGC
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<paths>
3+
<external-path name="external_files" path="." />
4+
<cache-path name="cache" path="." />
5+
<files-path name="files" path="." />
6+
</paths>

mobile/android/qt6/settings.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pluginManagement {
2+
repositories {
3+
google()
4+
mavenCentral()
5+
gradlePluginPortal()
6+
}
7+
}
8+
9+
rootProject.name = 'android-build'

mobile/docker/Dockerfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,13 @@ RUN apt update -yq && apt install -yq software-properties-common \
394394
--slave /usr/bin/g++ g++ /usr/bin/g++-9 \
395395
&& apt-get -qq clean
396396

397-
ENV PATH="/root/go/bin:${PATH}"
397+
ARG GRADLE_VERSION=8.11.1
398+
RUN wget -q https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip \
399+
&& unzip -q /tmp/gradle.zip -d /opt \
400+
&& ln -s /opt/gradle-${GRADLE_VERSION} /opt/gradle \
401+
&& rm /tmp/gradle.zip
402+
403+
ENV GRADLE_HOME=/opt/gradle
404+
ENV PATH="/opt/gradle/bin:/root/go/bin:${PATH}"
398405

399406
LABEL description="Build image for the Status Android APK."

mobile/scripts/buildApp.sh

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ ANDROID_ABI=${ANDROID_ABI:-"arm64-v8a"}
1212
BUILD_TYPE=${BUILD_TYPE:-"apk"}
1313
SIGN_IOS=${SIGN_IOS:-"false"}
1414

15+
# BUILD_VARIANT controls package name and signing:
16+
# - "pr" = app.status.mobile.pr with PR keystore
17+
# - "release" or unset = app.status.mobile with release keystore
18+
BUILD_VARIANT=${BUILD_VARIANT:-"release"}
19+
export BUILD_VARIANT
20+
1521
QMAKE_BIN="${QMAKE:-qmake}"
1622
QMAKE_CONFIG="CONFIG+=device CONFIG+=release"
1723

1824
echo "Building wrapperApp for ${OS}, ${ANDROID_ABI}"
25+
echo "Build variant: ${BUILD_VARIANT}"
1926

2027
mkdir -p "${BUILD_DIR}"
2128
cd "${BUILD_DIR}"
@@ -26,9 +33,9 @@ DESKTOP_VERSION=$(eval cd "$STATUS_DESKTOP" && git describe --tags --dirty="-dir
2633
TIMESTAMP=$(($(date +%s) * 1000 / 60000))
2734

2835
if [[ -n "${CHANGE_ID:-}" ]]; then
29-
BUILD_VERSION="${CHANGE_ID}.${TIMESTAMP}"
36+
BUILD_VERSION="${CHANGE_ID}.${TIMESTAMP}"
3037
else
31-
BUILD_VERSION="${TIMESTAMP}"
38+
BUILD_VERSION="${TIMESTAMP}"
3239
fi
3340

3441
echo "Using version: $DESKTOP_VERSION; build version: $BUILD_VERSION"
@@ -42,84 +49,102 @@ if [[ "${OS}" == "android" ]]; then
4249
echo "Building for Android 35"
4350
ANDROID_PLATFORM=android-35
4451

52+
# Map BUILD_VARIANT to gradle flavor
53+
if [[ "$BUILD_VARIANT" == "pr" ]]; then
54+
GRADLE_FLAVOR="pr"
55+
OUTPUT_NAME="StatusPR"
56+
else
57+
GRADLE_FLAVOR="production"
58+
OUTPUT_NAME="Status"
59+
fi
60+
61+
echo "Using Gradle flavor: $GRADLE_FLAVOR"
62+
echo "Target name: $OUTPUT_NAME"
63+
4564
"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" "$QMAKE_CONFIG" -spec android-clang ANDROID_ABIS="$ANDROID_ABI" APP_VARIANT="${APP_VARIANT}" VERSION="$DESKTOP_VERSION" -after
4665

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

50-
if [[ "$BUILD_TYPE" == "aab" ]]; then
51-
if [[ -z "$KEYSTORE_PATH" || -z "$KEYSTORE_PASSWORD" || -z "$KEY_ALIAS" || -z "$KEY_PASSWORD" ]]; then
52-
echo "Error: AAB builds require signing credentials"
53-
echo "Required: KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD"
54-
exit 1
55-
fi
69+
# aux-mode copies dependencies AND generates libs.xml
70+
echo "Preparing Android build..."
71+
androiddeployqt \
72+
--input "$BUILD_DIR/android-${OUTPUT_NAME}-deployment-settings.json" \
73+
--output "$BUILD_DIR/android-build" \
74+
--android-platform "$ANDROID_PLATFORM" \
75+
--verbose \
76+
--aux-mode
77+
78+
# Build with gradle directly to use product flavors
79+
CUSTOM_ANDROID="$CWD/../android/qt${QT_MAJOR}"
80+
ANDROID_BUILD="$BUILD_DIR/android-build"
81+
82+
# Override with custom Android files (manifest, gradle for flavors, src)
83+
# Keep Qt-generated res/ (including libs.xml) but add our custom resources
84+
cp "$CUSTOM_ANDROID/AndroidManifest.xml" "$ANDROID_BUILD/"
85+
cp "$CUSTOM_ANDROID/build.gradle" "$ANDROID_BUILD/"
86+
cp "$CUSTOM_ANDROID/settings.gradle" "$ANDROID_BUILD/"
87+
cp "$CUSTOM_ANDROID/gradle.properties" "$ANDROID_BUILD/"
88+
89+
# Copy custom res files (icons, styles, etc.) but preserve Qt-generated libs.xml
90+
if [[ -d "$CUSTOM_ANDROID/res" ]]; then
91+
# Copy everything except values/libs.xml (Qt generates that)
92+
find "$CUSTOM_ANDROID/res" -type f ! -name "libs.xml" -exec sh -c '
93+
src="$1"
94+
rel="${src#'"$CUSTOM_ANDROID/res/"'}"
95+
dest="'"$ANDROID_BUILD/res/"'$rel"
96+
mkdir -p "$(dirname "$dest")"
97+
cp "$src" "$dest"
98+
' _ {} \;
99+
fi
56100

57-
if [[ ! -f "$KEYSTORE_PATH" ]]; then
58-
echo "Error: Keystore file not found at $KEYSTORE_PATH"
59-
exit 1
60-
fi
101+
if [[ -d "$CUSTOM_ANDROID/src" ]]; then
102+
mkdir -p "$ANDROID_BUILD/src"
103+
cp -r "$CUSTOM_ANDROID/src/"* "$ANDROID_BUILD/src/"
104+
fi
105+
106+
cd "$BUILD_DIR/android-build"
61107

62-
echo "Building AAB..."
63-
androiddeployqt \
64-
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
65-
--output "$BUILD_DIR/android-build" \
66-
--aab \
67-
--release \
68-
--android-platform "$ANDROID_PLATFORM"
108+
if [[ "$BUILD_TYPE" == "aab" ]]; then
109+
echo "Building AAB with flavor: $GRADLE_FLAVOR..."
110+
gradle "bundle${GRADLE_FLAVOR^}Release" --no-daemon
69111

70-
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/bundle/release/android-build-release.aab"
112+
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/bundle/${GRADLE_FLAVOR}Release/android-build-${GRADLE_FLAVOR}-release.aab"
71113
if [[ ! -f "$OUTPUT_FILE" ]]; then
72114
echo "Error: Could not find generated AAB file at $OUTPUT_FILE"
115+
echo "Looking for AAB files..."
116+
find "$BUILD_DIR/android-build/build/outputs" -name "*.aab" 2>/dev/null
73117
exit 1
74118
fi
75119

76-
# Sign the AAB file (androiddeployqt --sign does not work for AAB files)
77-
"$CWD/android/sign.sh" "$OUTPUT_FILE"
78-
79120
ANDROID_OUTPUT_DIR="bin/android/qt6"
80121
BIN_DIR_ANDROID=${BIN_DIR:-"$CWD/$ANDROID_OUTPUT_DIR"}
81122
mkdir -p "$BIN_DIR_ANDROID"
82-
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/Status.aab"
83-
echo "Build succeeded. Signed AAB is available at $BIN_DIR_ANDROID/Status.aab"
123+
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/${OUTPUT_NAME}.aab"
124+
echo "Build succeeded. AAB is available at $BIN_DIR_ANDROID/${OUTPUT_NAME}.aab"
84125
else
85-
# APK build
86-
NEEDS_SIGNING="false"
87-
if [[ -n "$KEYSTORE_PATH" && -n "$KEYSTORE_PASSWORD" && -n "$KEY_ALIAS" && -n "$KEY_PASSWORD" ]]; then
88-
if [[ -f "$KEYSTORE_PATH" ]]; then
89-
NEEDS_SIGNING="true"
90-
fi
91-
fi
126+
echo "Building APK with flavor: $GRADLE_FLAVOR..."
127+
echo "Contents of android-build directory:"
128+
ls -la
129+
gradle "assemble${GRADLE_FLAVOR^}Release" --no-daemon --info --stacktrace
92130

93-
if [[ "$NEEDS_SIGNING" == "true" ]]; then
94-
echo "Building signed APK..."
95-
androiddeployqt \
96-
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
97-
--output "$BUILD_DIR/android-build" \
98-
--apk "$BUILD_DIR/android-build/Status.apk" \
99-
--release \
100-
--android-platform "$ANDROID_PLATFORM" \
101-
--sign "$KEYSTORE_PATH" "$KEY_ALIAS" \
102-
--storepass "$KEYSTORE_PASSWORD" \
103-
--keypass "$KEY_PASSWORD"
104-
else
105-
echo "Building unsigned APK..."
106-
androiddeployqt \
107-
--input "$BUILD_DIR/android-Status-deployment-settings.json" \
108-
--output "$BUILD_DIR/android-build" \
109-
--apk "$BUILD_DIR/android-build/Status.apk" \
110-
--android-platform "$ANDROID_PLATFORM"
131+
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/apk/${GRADLE_FLAVOR}/release/android-build-${GRADLE_FLAVOR}-release.apk"
132+
if [[ ! -f "$OUTPUT_FILE" ]]; then
133+
# Try alternative path
134+
OUTPUT_FILE="$BUILD_DIR/android-build/build/outputs/apk/${GRADLE_FLAVOR}/release/android-build-${GRADLE_FLAVOR}-release-unsigned.apk"
135+
fi
136+
if [[ ! -f "$OUTPUT_FILE" ]]; then
137+
echo "Error: Could not find generated APK file"
138+
echo "Looking for APK files..."
139+
find "$BUILD_DIR/android-build/build/outputs" -name "*.apk" 2>/dev/null
140+
exit 1
111141
fi
112142

113143
ANDROID_OUTPUT_DIR="bin/android/qt6"
114144
BIN_DIR_ANDROID=${BIN_DIR:-"$CWD/$ANDROID_OUTPUT_DIR"}
115145
mkdir -p "$BIN_DIR_ANDROID"
116-
cp ./android-build/Status.apk "$BIN_DIR_ANDROID/Status.apk"
117-
118-
if [[ "$NEEDS_SIGNING" == "true" ]]; then
119-
echo "Build succeeded. Signed APK is available at $BIN_DIR_ANDROID/Status.apk"
120-
else
121-
echo "Build succeeded. Unsigned APK is available at $BIN_DIR_ANDROID/Status.apk"
122-
fi
146+
cp "$OUTPUT_FILE" "$BIN_DIR_ANDROID/${OUTPUT_NAME}.apk"
147+
echo "Build succeeded. APK is available at $BIN_DIR_ANDROID/${OUTPUT_NAME}.apk"
123148
fi
124149
else
125150
"$QMAKE_BIN" "$CWD/../wrapperApp/Status.pro" "$QMAKE_CONFIG" -spec macx-ios-clang CONFIG+="$SDK" VERSION="$DESKTOP_VERSION" -after

mobile/wrapperApp/Status.pro

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ TEMPLATE = app
22

33
QT += quick gui qml webview svg widgets multimedia
44

5+
BUILD_VARIANT = $$(BUILD_VARIANT)
6+
equals(BUILD_VARIANT, "pr") {
7+
TARGET = StatusPR
8+
message("Building PR variant: TARGET = StatusPR")
9+
} else {
10+
TARGET = Status
11+
message("Building production variant: TARGET = Status")
12+
}
13+
514
equals(QT_MAJOR_VERSION, 6) {
615
message("qt 6 config!!")
716
QT += core5compat core

0 commit comments

Comments
 (0)