Skip to content

Commit 2c367ca

Browse files
committed
ci: job to build QT6 Image and android APK
1 parent aba42f4 commit 2c367ca

File tree

5 files changed

+887
-52
lines changed

5 files changed

+887
-52
lines changed

ci/Jenkinsfile.android

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env groovy
2+
3+
4+
/* Options section can't access functions in objects. */
5+
def isPRBuild = utils.isPRBuild()
6+
def isNightlyBuild = utils.isNightlyBuild()
7+
8+
pipeline {
9+
agent {
10+
/* Image with Ubuntu 22.04, QT 6.9.0, Android SDK/NDK, Go, and Nim */
11+
docker {
12+
label 'linux'
13+
image 'statusteam/nim-status-client-build:1.0.1-qt6.9.0-android'
14+
alwaysPull true
15+
}
16+
}
17+
18+
parameters {
19+
booleanParam(
20+
name: 'RELEASE',
21+
description: 'Decides whether release credentials are used.',
22+
defaultValue: params.RELEASE ?: false
23+
)
24+
}
25+
26+
options {
27+
timestamps()
28+
/* Prevent Jenkins jobs from running forever */
29+
timeout(time: 45, unit: 'MINUTES')
30+
/* manage how many builds we keep */
31+
buildDiscarder(logRotator(
32+
numToKeepStr: '10',
33+
daysToKeepStr: '30',
34+
artifactNumToKeepStr: '3',
35+
))
36+
/* Allows combined build to copy */
37+
copyArtifactPermission('/status-desktop/*')
38+
/* Abort old PR builds. */
39+
disableConcurrentBuilds(abortPrevious: isPRBuild)
40+
disableRestartFromStage()
41+
}
42+
43+
environment {
44+
USE_SYSTEM_NIM = "1"
45+
GOCACHE = "/tmp/go-cache"
46+
GOMODCACHE = "/tmp/go-mod-cache"
47+
GOTMPDIR = "/tmp"
48+
STATUS_APK_ARTIFACT = "pkg/Status-tablet-${BUILD_NUMBER}.apk"
49+
PLATFORM = "android/arm64"
50+
QT_ANDROID_PATH = "/opt/qt/6.9.0/android_arm64_v8a"
51+
STATUS_APK = "${WORKSPACE}/mobile/bin/android/qt6/default/Status-tablet.apk"
52+
}
53+
54+
stages {
55+
stage('Prepare') {
56+
steps {
57+
sh 'git submodule update --init --recursive'
58+
}
59+
}
60+
61+
stage('Build Android APK') {
62+
steps {
63+
sh """
64+
# These exports MUST be in the build stage because the build system uses qmake to detect OS
65+
# The correct Android qmake must be first in PATH to return 'android-clang' instead of 'linux-g++'
66+
export ARCH="arm64"
67+
export PKG_CONFIG_PATH="${env.QT_ANDROID_PATH}/lib/pkgconfig"
68+
export PATH="${env.QT_ANDROID_PATH}/bin:/opt/qt/6.9.0/gcc_64/bin:/opt/qt/6.9.0/gcc_64/libexec:${env.QT_ANDROID_PATH}/libexec:/opt/android-sdk/emulator:/opt/android-sdk/tools:/opt/android-sdk/tools/bin:/opt/android-sdk/platform-tools:${env.PATH}"
69+
make mobile-clean
70+
make -j${utils.getProcCount()} mobile-build V=3 USE_SYSTEM_NIM=1
71+
"""
72+
}
73+
}
74+
75+
stage('Package APK') {
76+
steps {
77+
sh """
78+
mkdir -p pkg
79+
cp '${env.STATUS_APK}' '${env.STATUS_APK_ARTIFACT}'
80+
ls -la '${env.STATUS_APK_ARTIFACT}'
81+
"""
82+
}
83+
}
84+
85+
stage('Parallel Upload') {
86+
parallel {
87+
stage('Upload') {
88+
steps {
89+
script {
90+
env.PKG_URL = s5cmd.upload(env.STATUS_APK_ARTIFACT)
91+
jenkins.setBuildDesc(APK: env.PKG_URL)
92+
}
93+
}
94+
}
95+
stage('Archive') {
96+
steps {
97+
archiveArtifacts env.STATUS_APK_ARTIFACT
98+
}
99+
}
100+
}
101+
}
102+
}
103+
104+
post {
105+
success { script { github.notifyPR(true) } }
106+
failure { script { github.notifyPR(false) } }
107+
cleanup { sh './scripts/clean-git.sh' }
108+
}
109+
}
110+

ci/Jenkinsfile.qt-build

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
pipeline {
2+
agent {
3+
label 'linux && x86_64 && qt-builder'
4+
}
5+
6+
options {
7+
buildDiscarder(logRotator(numToKeepStr: '20'))
8+
timestamps()
9+
timeout(time: 12, unit: 'HOURS')
10+
}
11+
12+
parameters {
13+
string(
14+
name: 'QT_VERSION',
15+
defaultValue: '6.9.0',
16+
description: 'Qt version to build'
17+
)
18+
string(
19+
name: 'DOCKER_TAG',
20+
defaultValue: '1.0.1-qt6.9.0-android',
21+
description: 'Docker image tag for the final image'
22+
)
23+
booleanParam(
24+
name: 'PUSH_TO_DOCKERHUB',
25+
defaultValue: true,
26+
description: 'Push the final image to DockerHub'
27+
)
28+
booleanParam(
29+
name: 'BUILD_QT_FROM_SOURCE',
30+
defaultValue: true,
31+
description: 'Build Qt from source (takes several hours)'
32+
)
33+
}
34+
35+
environment {
36+
DOCKER_REGISTRY = 'statusteam'
37+
DOCKER_IMAGE_BASE = 'nim-status-client-build'
38+
MOBILE_BUILD_DIR = "${WORKSPACE}/mobile/docker"
39+
}
40+
41+
stages {
42+
43+
stage('Build Qt from Source') {
44+
when {
45+
expression { params.BUILD_QT_FROM_SOURCE == true }
46+
}
47+
steps {
48+
script {
49+
// Build qt-dev-base stage for Qt source compilation
50+
dir("${MOBILE_BUILD_DIR}") {
51+
def qtDevImage = "${DOCKER_REGISTRY}/qt-dev-base-temp:${BUILD_ID}"
52+
sh """
53+
docker build \
54+
--target qt-dev-base \
55+
--build-arg TARGETARCH=amd64 \
56+
--build-arg JAVA_VERSION=17 \
57+
--build-arg ANDROID_API_LEVEL=35 \
58+
--build-arg ANDROID_NDK_VERSION=27.2.12479018 \
59+
-t ${qtDevImage} .
60+
"""
61+
62+
// Run Qt build in the container
63+
docker.image(qtDevImage).inside("-u root -v ${WORKSPACE}/qt_export:/root/export -v ${WORKSPACE}/scripts:/root/scripts") {
64+
sh """
65+
cd /root
66+
/root/scripts/build_qt_android.sh
67+
"""
68+
}
69+
70+
// Clean up temp image
71+
sh "docker rmi ${qtDevImage} || true"
72+
}
73+
}
74+
}
75+
}
76+
77+
stage('Build Docker Images') {
78+
steps {
79+
script {
80+
dir("${MOBILE_BUILD_DIR}") {
81+
// Copy Qt exports if they exist
82+
sh """
83+
if [ -d ${WORKSPACE}/qt_export ]; then
84+
cp -r ${WORKSPACE}/qt_export .
85+
fi
86+
"""
87+
88+
// Build final mobile-build stage
89+
sh """
90+
docker build \
91+
--build-arg QTVER=${params.QT_VERSION} \
92+
--build-arg TARGETARCH=amd64 \
93+
--build-arg JAVA_VERSION=17 \
94+
--build-arg ANDROID_API_LEVEL=35 \
95+
--build-arg ANDROID_NDK_VERSION=27.2.12479018 \
96+
--build-arg GOLANG_VERSION=1.23.10 \
97+
--build-arg NIM_VERSION=2.0.12 \
98+
--build-arg NIX_VERSION=2.24.11 \
99+
--target mobile-build \
100+
-t ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BASE}:${params.DOCKER_TAG} .
101+
"""
102+
103+
// Clean up qt_export copy
104+
sh "rm -rf qt_export || true"
105+
}
106+
}
107+
}
108+
}
109+
110+
stage('Test Images') {
111+
parallel {
112+
stage('Test Mobile Build Image') {
113+
steps {
114+
script {
115+
def testImage = "${DOCKER_REGISTRY}/${DOCKER_IMAGE_BASE}:${params.DOCKER_TAG}"
116+
docker.image(testImage).inside('--entrypoint=""') {
117+
sh "qmake --version || echo 'qmake not found'"
118+
sh "nim --version"
119+
sh "go version"
120+
sh "protoc --version"
121+
sh "java -version"
122+
sh "ls -la /opt/qt/${params.QT_VERSION}/ || echo 'Qt directory not found'"
123+
}
124+
}
125+
}
126+
}
127+
128+
}
129+
}
130+
131+
stage('Push to DockerHub') {
132+
when {
133+
expression { params.PUSH_TO_DOCKERHUB == true }
134+
}
135+
steps {
136+
script {
137+
withCredentials([
138+
usernamePassword(
139+
credentialsId: 'dockerhub-statusteam-bot',
140+
usernameVariable: 'DOCKER_USER',
141+
passwordVariable: 'DOCKER_PASS'
142+
)
143+
]) {
144+
sh """
145+
echo \$DOCKER_PASS | docker login -u \$DOCKER_USER --password-stdin
146+
docker push ${DOCKER_REGISTRY}/${DOCKER_IMAGE_BASE}:${params.DOCKER_TAG}
147+
docker logout
148+
"""
149+
}
150+
}
151+
}
152+
}
153+
}
154+
155+
post {
156+
always { cleanWs() }
157+
success { script { github.notifyPR(true) } }
158+
failure { script { github.notifyPR(false) } }
159+
}
160+
}

0 commit comments

Comments
 (0)