diff --git a/Makefile b/Makefile index 1584f0a43f..30e4204d72 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ LINK_PCRE=0 # nimbus-build-system links `pcre` by default which is not needed tests-nim-linux \ benches-nim \ status-go \ + qrcodegen \ status-keycard-qt \ keycard-simulator \ keycard-simulator-bundle \ @@ -621,6 +622,9 @@ $(QRCODEGEN): | deps platform-cleanup + cd vendor/QR-Code-generator/c && \ $(MAKE) $(QRCODEGEN_MAKE_PARAMS) $(HANDLE_OUTPUT) +# Named alias so callers (ci/Jenkinsfile.linux) don't hardcode the archive path. +qrcodegen: $(QRCODEGEN) + # When modifying files that are not tracked in UI_SOURCES (see below), # e.g. ui/shared/img/*.svg, REBUILD_UI=true can be supplied to `make` to ensure diff --git a/ci/Dockerfile b/ci/Dockerfile index 957bd78b5f..9c5c6131f7 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -3,7 +3,7 @@ ARG QT_MODULES="qtwebchannel qtwebview qtwebsockets qt5compat qtmultimedia qtweb ARG LINUXDEPLOYQT_VERSION="20250615-0393b84" ARG PCSCLITE_VERSION="2.2.3" ARG PCSCLITE_SHA256="6e0205a47ec0dd10c000965ee88e08e60c2f7f3ce7280ce0e624915821ea2402" -ARG NIM_VERSION="2.2.4" +ARG NIM_VERSION="2.2.10" # QT Installation Image -------------------------------------------------------- FROM ubuntu:22.04 AS qt-install @@ -89,6 +89,7 @@ RUN apt update -yq && apt install -yq software-properties-common \ libcups2 libcups2-dev \ libgtk-3-0 libgtk-3-dev \ libgdk-pixbuf-2.0-0 libgdk-pixbuf2.0-dev \ + xvfb mesa-common-dev libglu1-mesa-dev libxft-dev \ && apt-get -qq clean # Install PCSC Lite diff --git a/ci/Jenkinsfile.android b/ci/Jenkinsfile.android index c22fb1e0d2..e1f35da601 100644 --- a/ci/Jenkinsfile.android +++ b/ci/Jenkinsfile.android @@ -14,7 +14,7 @@ pipeline { /* Image with Ubuntu 22.04, QT ${QT_VERSION}, Android SDK/NDK, Go, and Nim */ docker { label 'linuxcontainer' - image "harbor.status.im/status-im/status-desktop-build:1.0.1-qt${QT_VERSION}-android" + image "harbor.status.im/status-im/status-desktop-build:1.0.2-qt${QT_VERSION}-android" alwaysPull true args '--entrypoint="" ' + '--volume=/nix:/nix ' + @@ -73,7 +73,7 @@ pipeline { /* Control output the filename */ APP_TYPE = "${utils.getAppType()}" PLATFORM = "android/arm64" - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" QT_VERSION = "${QT_VERSION}" QT_ANDROID_PATH = "/opt/qt/${QT_VERSION}/android_arm64_v8a" QMAKE = "/opt/qt/${QT_VERSION}/android_arm64_v8a/bin/qmake" @@ -107,7 +107,7 @@ pipeline { stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh --mobile' } } diff --git a/ci/Jenkinsfile.flatpak b/ci/Jenkinsfile.flatpak index 7bba459426..76fe2e8b54 100644 --- a/ci/Jenkinsfile.flatpak +++ b/ci/Jenkinsfile.flatpak @@ -74,7 +74,7 @@ pipeline { environment { PLATFORM = "linux-flatpak/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* nim-sds source directory: writable, isolated per build, used by status-go's clone-nim-sds */ @@ -110,7 +110,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh' } } stage('Deps') { diff --git a/ci/Jenkinsfile.ios b/ci/Jenkinsfile.ios index bc4e816349..2a4bad69b1 100644 --- a/ci/Jenkinsfile.ios +++ b/ci/Jenkinsfile.ios @@ -62,7 +62,7 @@ pipeline { GOTMPDIR = "${env.WORKSPACE_TMP}" PLATFORM = "ios/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" QMAKE = "/Users/admin/${QT_VERSION}/ios/bin/qmake" QT_HOST_PATH = "/Users/admin/${QT_VERSION}/macos" /* QMAKE = sh(script: "which qmake", returnStdout: true).trim() */ @@ -107,7 +107,7 @@ pipeline { stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh --mobile' } } diff --git a/ci/Jenkinsfile.linux b/ci/Jenkinsfile.linux index fabfbe0987..4456a69b5c 100644 --- a/ci/Jenkinsfile.linux +++ b/ci/Jenkinsfile.linux @@ -3,16 +3,18 @@ library 'status-jenkins-lib@v1.9.48' QT_VERSION = "6.11.0" +STATUSQ_TESTS_BUILD_PATH = "ui/StatusQ/build/ci-tests-Qt${QT_VERSION}" + /* Options section can't access functions in objects. */ def isPRBuild = utils.isPRBuild() def isNightlyBuild = utils.isNightlyBuild() pipeline { agent { - /* Image with Ubuntu 22.04 and QT ${QT_VERSION} */ + /* Image with Ubuntu 22.04, QT ${QT_VERSION} and Xvfb (for the UI tests). */ docker { label 'linuxcontainer' - image "harbor.status.im/status-im/status-desktop-build:1.0.8-qt${QT_VERSION}" + image "harbor.status.im/status-im/status-desktop-build:1.0.12-qt${QT_VERSION}" /* allows jenkins use cat and mounts '/dev/fuse' for linuxdeployqt */ args '--entrypoint="" ' + '--cap-add=SYS_ADMIN ' + @@ -54,12 +56,22 @@ pipeline { description: 'Override vendor/status-go submodule to this SHA/ref.', defaultValue: '' ) + booleanParam( + name: 'RUN_NIM_TESTS', + description: 'Run the Nim unit tests and benchmarks alongside the package build.', + defaultValue: params.RUN_NIM_TESTS != null ? params.RUN_NIM_TESTS : true + ) + booleanParam( + name: 'RUN_UI_TESTS', + description: 'Run QML lint, translation checks, StatusQ and Storybook tests alongside the package build.', + defaultValue: params.RUN_UI_TESTS != null ? params.RUN_UI_TESTS : true + ) } options { timestamps() - /* Prevent Jenkins jobs from running forever */ - timeout(time: 30, unit: 'MINUTES') + /* Prevent Jenkins jobs from running forever. */ + timeout(time: 150, unit: 'MINUTES') /* manage how many builds we keep */ buildDiscarder(logRotator( numToKeepStr: '10', @@ -79,7 +91,7 @@ pipeline { environment { PLATFORM = "linux/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* nim-sds source directory */ @@ -101,8 +113,8 @@ pipeline { USE_SIMULATED_KEYCARD = "${params.USE_SIMULATED_KEYCARD}" /* FIXME: figure out why NIMFLAGS are not respected */ XDG_CACHE_HOME = "${env.WORKSPACE_TMP}" - /* Ensure nimcache is not shared between builds. */ - NIMFLAGS = "${params.NIMFLAGS} --colors:off --nimcache:${env.WORKSPACE_TMP}/nimcache" + /* Ensure nimcache is not shared between builds. Note: per-test nimcache is set in nim-tests.mk */ + NIMFLAGS = "${params.NIMFLAGS} --colors:off" } stages { @@ -113,7 +125,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh' } } stage('Override status-go ref') { @@ -135,40 +147,84 @@ pipeline { } } - stage('Package') { - steps { script { - status.buildSigned(platform='linux', target='tgz-linux') - } } - } - - stage('Parallel Upload') { - parallel { - stage('Upload') { - steps { script { - env.PKG_URL = s5cmd.upload(env.STATUS_CLIENT_TARBALL) - jenkins.setBuildDesc(AppImage: env.PKG_URL) - } } - } - stage('Archive') { - steps { script { - archiveArtifacts("${env.STATUS_CLIENT_TARBALL}*") - } } - } + stage('Shared artifacts') { + steps { + sh 'make statusq' + sh 'make qrcodegen' } } - stage('E2E') { - when { expression { utils.isPRBuild() } } - steps { script { - build( - job: 'status-app/e2e/prs', - wait: false, - parameters: jenkins.mapToParams([ - GIT_REF: env.CHANGE_BRANCH ?: env.BRANCH_NAME, - BUILD_SOURCE: env.JOB_NAME, - ]), - ) - } } + stage('Build') { + parallel { + stage('Package') { + steps { script { + status.buildSigned(platform='linux', target='tgz-linux') + env.PKG_URL = s5cmd.upload(env.STATUS_CLIENT_TARBALL) + jenkins.setBuildDesc(AppImage: env.PKG_URL) + archiveArtifacts("${env.STATUS_CLIENT_TARBALL}*") + if (utils.isPRBuild()) { + build( + job: 'status-app/e2e/prs', + wait: false, + parameters: jenkins.mapToParams([ + GIT_REF: env.CHANGE_BRANCH ?: env.BRANCH_NAME, + BUILD_SOURCE: env.JOB_NAME, + ]), + ) + } + } } + } + + stage('Nim Tests') { + when { expression { params.RUN_NIM_TESTS } } + steps { script { + sh 'make tests-nim-linux V=1' + } } + } + + stage('UI Tests') { + when { expression { params.RUN_UI_TESTS } } + stages { + stage('Check Translations') { + steps { script { checkTranslations() } } + } + stage('QML Lint') { + steps { script { + sh 'make qml-lint' + } } + } + /* Xvfb is needed for QGuiApplication to import QtQuick.Dialogs. */ + stage('Check StatusQ Tests') { + steps { script { + wrap([$class: 'Xvfb', autoDisplayName: true, parallelBuild: true, screen: '2560x1440x24']) { + sh "make run-statusq-tests STATUSQ_BUILD_PATH=${STATUSQ_TESTS_BUILD_PATH}" + } + } } + } + stage('Check StatusQ Sanity Checker') { + steps { script { + wrap([$class: 'Xvfb', autoDisplayName: true, parallelBuild: true, screen: '2560x1440x24']) { + sh "make run-statusq-sanity-checker STATUSQ_BUILD_PATH=${STATUSQ_TESTS_BUILD_PATH}" + } + } } + } + stage('Check Storybook Tests') { + steps { script { + wrap([$class: 'Xvfb', autoDisplayName: true, parallelBuild: true, screen: '2560x1440x24']) { + sh 'make run-storybook-tests' + } + } } + } + stage('Check Storybook Pages Validator') { + steps { script { + wrap([$class: 'Xvfb', autoDisplayName: true, parallelBuild: true, screen: '2560x1440x24']) { + sh 'make run-storybook-pages-validator' + } + } } + } + } + } + } } } post { @@ -181,10 +237,17 @@ pipeline { } } +def checkTranslations() { + sh 'make update-translations' + def changes = sh(script: 'git diff --name-only ui/i18n/*.ts', returnStdout: true).trim() + if (changes) { + error("Translation files out of date.\n\nModified files:\n${changes}\n\nTo fix: run 'make update-translations' and commit changes.") + } +} + def getArch() { def tokens = Thread.currentThread().getName().split('/') for (def arch in ['x86_64', 'aarch64']) { if (tokens.contains(arch)) { return arch } } } - diff --git a/ci/Jenkinsfile.linux-nix b/ci/Jenkinsfile.linux-nix index 1c7e84aa39..dfa17e7600 100644 --- a/ci/Jenkinsfile.linux-nix +++ b/ci/Jenkinsfile.linux-nix @@ -89,7 +89,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh' } } stage('Deps') { diff --git a/ci/Jenkinsfile.macos b/ci/Jenkinsfile.macos index 51a5051194..2c6bba4743 100644 --- a/ci/Jenkinsfile.macos +++ b/ci/Jenkinsfile.macos @@ -3,11 +3,15 @@ library 'status-jenkins-lib@v1.9.48' QT_VERSION = "6.11.0" +/* Object to store public URLs for the build description. Both platforms append here */ +descLinks = [:] + +NIM_BIN = "/run/current-system/sw/bin" + /* Options section can't access functions in objects. */ def isPRBuild = utils.isPRBuild() pipeline { - /* This way we run the same Jenkinsfile on different platforms. */ agent { label "${getAgentLabels().join(' && ')} && qt-${QT_VERSION} && go-1.26 && xcode-26.4" } @@ -26,7 +30,7 @@ pipeline { choice( name: 'VERBOSE', description: 'Level of verbosity based on nimbus-build-system setup.', - choices: ['0', '1', '2'] + choices: ['0', '1', '2', '3'] ) booleanParam( name: 'USE_SIMULATED_KEYCARD', @@ -48,12 +52,32 @@ pipeline { description: 'Override vendor/status-go submodule to this SHA/ref.', defaultValue: '' ) + string( + name: 'TESTFLIGHT_POLL_TIMEOUT', + description: 'TestFlight build polling timeout in minutes.', + defaultValue: '30' + ) + string( + name: 'TESTFLIGHT_POLL_INTERVAL', + description: 'TestFlight build polling interval in seconds.', + defaultValue: '30' + ) + booleanParam( + name: 'BUILD_MACOS', + description: 'Build and package the macOS desktop app.', + defaultValue: params.BUILD_MACOS != null ? params.BUILD_MACOS : true + ) + booleanParam( + name: 'BUILD_IOS', + description: 'Build and package the iOS app after the macOS build.', + defaultValue: params.BUILD_IOS != null ? params.BUILD_IOS : true + ) } options { timestamps() - /* Prevent Jenkins jobs from running forever */ - timeout(time: 45, unit: 'MINUTES') + /* Prevent Jenkins jobs from running forever.*/ + timeout(time: 100, unit: 'MINUTES') /* manage how many builds we keep */ buildDiscarder(logRotator( numToKeepStr: '10', @@ -71,15 +95,12 @@ pipeline { } environment { - PLATFORM = "macos/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" - QMAKE = "/Users/admin/${QT_VERSION}/macos/bin/qmake" - /* QMAKE = sh(script: "which qmake", returnStdout: true).trim() */ - QTDIR = sh(script:"${env.QMAKE} -query QT_INSTALL_PREFIX", returnStdout: true).trim() - /* Enforce Go version installed infra-role-golang. */ - /* to fix missing rcc, since QT6 rcc is located at ${QTDIR}/libexec/rcc and nix */ - PATH = "/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin:${env.QTDIR}/bin:${env.QTDIR}/libexec:${env.HOME}/go/bin:/usr/local/go/bin:${env.PATH}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" + NIMFLAGS = "${params.NIMFLAGS} --colors:off --nimcache:${env.WORKSPACE_TMP}/nimcache" + GOCACHE = "${env.WORKSPACE_TMP}/go-cache" + GOMODCACHE = "${env.WORKSPACE_TMP}/go-mod-cache" + GOTMPDIR = "${env.WORKSPACE_TMP}" /* Avoid weird bugs caused by stale cache. */ QML_DISABLE_DISK_CACHE = "true" /* nim-sds source directory */ @@ -88,9 +109,6 @@ pipeline { VERSION = "${utils.getVersion()}" /* Control output the filename */ APP_TYPE = "${utils.getAppType()}" - STATUS_CLIENT_DMG = "pkg/${utils.pkgFilename(ext: 'dmg', arch: getArch(), version: env.VERSION, type: env.APP_TYPE)}" - /* Apple Team ID for Notarization */ - MACOS_NOTARIZE_TEAM_ID = "8B5X2M6H2Y" /* prevent sharing cache dir across different jobs */ GO_GENERATE_FAST_DIR = "${env.WORKSPACE_TMP}/go-generate-fast" SENTRY_PRODUCTION = "${utils.isReleaseBuild() ? 'true' : 'false'}" @@ -100,10 +118,10 @@ pipeline { INCLUDE_DEBUG_SYMBOLS = "${params.INCLUDE_DEBUG_SYMBOLS}" VERBOSE = "${params.VERBOSE}" USE_SIMULATED_KEYCARD = "${params.USE_SIMULATED_KEYCARD}" + TESTFLIGHT_POLL_TIMEOUT = "${params.TESTFLIGHT_POLL_TIMEOUT}" + TESTFLIGHT_POLL_INTERVAL = "${params.TESTFLIGHT_POLL_INTERVAL}" /* FIXME: figure out why NIMFLAGS are not respected */ XDG_CACHE_HOME = "${env.WORKSPACE_TMP}/.cache" - /* Ensure nimcache is not shared between builds. */ - NIMFLAGS = "${params.NIMFLAGS} --colors:off --nimcache:${env.WORKSPACE_TMP}/nimcache" // Workaround for cmake 4.x dropping compatibility with cmake_minimum_required < 3.5 // Required by vendor/QR-Code-generator (scodes) until upstream bumps their CMakeLists.txt CMAKE_POLICY_VERSION_MINIMUM = 3.5 @@ -117,7 +135,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh --mobile' } } stage('Override status-go ref') { @@ -126,57 +144,150 @@ pipeline { sh "./scripts/override-status-go-ref.sh ${params.STATUS_GO_REF}" } } - stage('Deps') { - steps { - sh 'make update' - withCredentials([ - usernamePassword( /* For fetching HomeBrew bottles. */ - credentialsId: "status-im-auto-pkgs", - usernameVariable: 'GITHUB_USER', - passwordVariable: 'GITHUB_TOKEN' - ) - ]) { - sh 'make deps' + + stage('macOS') { + when { expression { params.BUILD_MACOS } } + environment { + QMAKE = "/Users/admin/${QT_VERSION}/macos/bin/qmake" + QTDIR = "/Users/admin/${QT_VERSION}/macos" + PLATFORM = "macos/${getArch()}" + PATH = "${NIM_BIN}:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin:${QTDIR}/bin:${QTDIR}/libexec:${env.HOME}/go/bin:/usr/local/go/bin:${env.PATH}" + STATUS_CLIENT_DMG = "pkg/${utils.pkgFilename(ext: 'dmg', arch: getArch(), version: env.VERSION, type: env.APP_TYPE)}" + MACOS_NOTARIZE_TEAM_ID = "8B5X2M6H2Y" + } + stages { + stage('Deps') { + steps { script { + sh 'make update' + withCredentials([ + usernamePassword( /* For fetching HomeBrew bottles. */ + credentialsId: "status-im-auto-pkgs", + usernameVariable: 'GITHUB_USER', + passwordVariable: 'GITHUB_TOKEN' + ) + ]) { + sh 'make deps' + } + } } + } + + stage('status-go') { + steps { script { + sh 'make status-go' + } } + } + + stage('Package') { + steps { script { + status.buildSigned(platform='macos', target='pkg-macos') + } } + } + + stage('Notarize') { + when { expression { utils.isReleaseBuild() } } + steps { script { + status.notarize() + } } + } + + stage('Parallel Upload') { + parallel { + stage('Upload') { + steps { script { + env.PKG_URL = s5cmd.upload(env.STATUS_CLIENT_DMG) + descLinks.Dmg = env.PKG_URL + } } + } + stage('Archive') { + steps { script { + archiveArtifacts(env.STATUS_CLIENT_DMG) + } } + } + } } } } - stage('status-go') { - steps { - sh 'make status-go' + stage('iOS') { + when { expression { params.BUILD_IOS } } + environment { + QMAKE = "/Users/admin/${QT_VERSION}/ios/bin/qmake" + QTDIR = "/Users/admin/${QT_VERSION}/ios" + QT_HOST_PATH = "/Users/admin/${QT_VERSION}/macos" + PLATFORM = "ios/arm64" + PATH = "${NIM_BIN}:${QTDIR}/bin:${QTDIR}/libexec:${env.HOME}/go/bin:/usr/local/go/bin:${env.PATH}:/run/current-system/sw/bin" + IPHONE_SDK = "iphoneos" + ARCH = "arm64" + QMAKE_EXTRA_CONFIG = "fastlane" + BUILD_VARIANT = "${utils.isReleaseBuild() ? 'release' : 'pr'}" + FLAG_BUY_ENABLED = "${utils.isReleaseBuild() ? 0 : 1}" + FLAG_SWAP_ENABLED = "${utils.isReleaseBuild() ? 0 : 1}" + FLAG_BRIDGE_ENABLED = "${utils.isReleaseBuild() ? 0 : 1}" + STATUS_BUILD_PROXY_STAGE_NAME = "${utils.isReleaseBuild() ? 'prod' : 'test'}" } - } - - stage('Package') { - steps { script { - status.buildSigned(platform='macos', target='pkg-macos') - } } - } - - stage('Notarize') { - when { expression { utils.isReleaseBuild() } } - steps { script { - status.notarize() - } } - } - - stage('Parallel Upload') { - parallel { - stage('Upload') { + stages { + stage('status-go') { steps { script { - env.PKG_URL = s5cmd.upload(env.STATUS_CLIENT_DMG) - jenkins.setBuildDesc(Dmg: env.PKG_URL) + sh 'make status-go' } } } - stage('Archive') { + + stage('Build iOS App') { steps { script { - archiveArtifacts(env.STATUS_CLIENT_DMG) + status.buildSigned(platform='ios', target='mobile-build', verbose=params.VERBOSE) } } } + + stage('Package iOS App') { + steps { script { + sh 'mkdir -p pkg' + // Set as global env variable for upload stages + env.STATUS_IOS_APP_ARTIFACT = "pkg/${utils.pkgFilename(ext: 'ipa', arch: 'arm64', version: env.VERSION, type: env.APP_TYPE)}" + sh "cp ${WORKSPACE}/mobile/bin/ios/qt6/Status${utils.isReleaseBuild() ? '' : 'PR'}.ipa ${env.STATUS_IOS_APP_ARTIFACT}" + sh "ls -lh ${env.STATUS_IOS_APP_ARTIFACT}" + } } + } + + stage('Parallel Upload') { + parallel { + stage('Upload to TestFlight') { + when { expression { utils.isReleaseBuild() } } + steps { script { + def changelog = sh(script: './scripts/generate-changelog.sh', returnStdout: true).trim() + status.uploadToTestFlight( + ipaPath="${WORKSPACE}/${env.STATUS_IOS_APP_ARTIFACT}", + changelog=changelog, + pollTimeout=env.TESTFLIGHT_POLL_TIMEOUT, + pollInterval=env.TESTFLIGHT_POLL_INTERVAL + ) + } } + } + stage('Upload to Diawi') { + when { expression { !utils.isReleaseBuild() } } + steps { script { + def comment = "status-desktop PR build ${env.VERSION} ${git.commit(8)}" + env.DIAWI_URL = status.uploadToDiawi(env.STATUS_IOS_APP_ARTIFACT, comment) + descLinks.Ipa = env.DIAWI_URL + } } + } + stage('Upload to S3') { + steps { script { + env.IPA_URL = s5cmd.upload(env.STATUS_IOS_APP_ARTIFACT) + } } + } + stage('Archive') { + steps { script { + archiveArtifacts(env.STATUS_IOS_APP_ARTIFACT) + } } + } + } + } } } } + post { + always { script { jenkins.setBuildDesc(descLinks) } } success { script { github.notifyPR(true) } } failure { script { github.notifyPR(false) } } cleanup { @@ -186,6 +297,13 @@ pipeline { } } +def getArch() { + def tokens = Thread.currentThread().getName().split('/') + for (def arch in ['x86_64', 'aarch64']) { + if (tokens.contains(arch)) { return arch } + } +} + /* This allows us to use one Jenkinsfile and run * jobs on different platforms based on job name. */ def getAgentLabels() { @@ -200,12 +318,3 @@ def getAgentLabels() { } return labels } - -def getArch() { - def tokens = Thread.currentThread().getName().split('/') - for (def arch in ['x86_64', 'aarch64']) { - if (tokens.contains(arch)) { return arch } - } -} - - diff --git a/ci/Jenkinsfile.qt-build b/ci/Jenkinsfile.qt-build index dff3c9e845..a3c45ff85d 100644 --- a/ci/Jenkinsfile.qt-build +++ b/ci/Jenkinsfile.qt-build @@ -20,12 +20,12 @@ pipeline { ) string( name: 'DOCKER_TAG_ANDROID', - defaultValue: '1.0.1-qt6.11.0-android', + defaultValue: '1.0.2-qt6.11.0-android', description: 'Docker image tag for the Andorid image' ) string( name: 'DOCKER_TAG_LINUX', - defaultValue: '1.0.10-qt6.11.0', + defaultValue: '1.0.12-qt6.11.0', description: 'Docker image tag for the Linux image' ) string( @@ -66,7 +66,7 @@ pipeline { string( name: 'NIM_VERSION', description: 'Nim language version', - defaultValue: '2.2.4', + defaultValue: '2.2.10', ) string( name: 'NIX_VERSION', diff --git a/ci/Jenkinsfile.tests-e2e b/ci/Jenkinsfile.tests-e2e index 9e448dba38..d2a0bfd88b 100644 --- a/ci/Jenkinsfile.tests-e2e +++ b/ci/Jenkinsfile.tests-e2e @@ -139,7 +139,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh' } } stage('Prep') { diff --git a/ci/Jenkinsfile.tests-nim b/ci/Jenkinsfile.tests-nim deleted file mode 100644 index 1be442925c..0000000000 --- a/ci/Jenkinsfile.tests-nim +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env groovy -library 'status-jenkins-lib@v1.9.46' - -QT_VERSION = "6.11.0" - -/* Options section can't access functions in objects. */ -def isPRBuild = utils.isPRBuild() - -pipeline { - agent { - docker { - label 'linuxcontainer' - image "harbor.status.im/status-im/status-desktop-build:1.0.8-qt${QT_VERSION}" - } - } - - parameters { - choice( - name: 'VERBOSE', - description: 'Level of verbosity based on nimbus-build-system setup.', - choices: ['0', '1', '2'] - ) - string( - name: 'NIMFLAGS', - description: 'Extra Nim flags. Examples: --verbosity:2 --passL:"-v" --passC:"-v"', - defaultValue: '--colors:off' - ) - } - - options { - timestamps() - /* Prevent Jenkins jobs from running forever */ - timeout(time: 35, unit: 'MINUTES') - /* manage how many builds we keep */ - buildDiscarder(logRotator( - numToKeepStr: '10', - daysToKeepStr: '30', - artifactNumToKeepStr: '1', - )) - /* Abort old PR builds. */ - disableConcurrentBuilds( - abortPrevious: isPRBuild - ) - disableRestartFromStage() - } - - environment { - PLATFORM = 'tests/nim' - /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" - /* Makefile assumes the compiler folder is included */ - QTDIR = "/opt/qt/${QT_VERSION}/gcc_64" - /* Avoid weird bugs caused by stale cache. */ - QML_DISABLE_DISK_CACHE = "true" - /* Include library in order to compile the project */ - LD_LIBRARY_PATH = "$QTDIR/lib" - /* Hack fix for params not being set in job on first run */ - NIMFLAGS = "${params.NIMFLAGS}" - VERBOSE = "${params.VERBOSE}" - /* FIXME: figure out why NIMFLAGS are not respected */ - XDG_CACHE_HOME = "${env.WORKSPACE_TMP}/.cache" - /* nim-sds source directory */ - NIM_SDS_SOURCE_DIR = "${env.WORKSPACE_TMP}/nim-sds" - } - - stages { - stage('Cleanup Workspace') { - steps { - sh './scripts/clean-git.sh' - } - } - stage('Fetch submodules') { - steps { - sh 'git submodule update --init --recursive' - } - } - stage('Deps') { - steps { - sh 'make update' - sh 'make deps' - } - } - - stage('Tests') { - steps { - sh 'make tests-nim-linux V=1' - } - } - } - - post { - success { script { github.notifyPR(true) } } - failure { script { github.notifyPR(false) } } - always { script { env.PKG_URL = "${currentBuild.absoluteUrl}/consoleText" } } - cleanup { - cleanWs(disableDeferredWipeout: true) - dir(env.WORKSPACE_TMP) { deleteDir() } - } - } -} diff --git a/ci/Jenkinsfile.tests-ui b/ci/Jenkinsfile.tests-ui deleted file mode 100644 index 63b32ab606..0000000000 --- a/ci/Jenkinsfile.tests-ui +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env groovy -library 'status-jenkins-lib@v1.9.46' - -QT_VERSION = "6.11.0" - -/* Options section can't access functions in objects. */ -def isPRBuild = utils.isPRBuild() - -pipeline { - agent { - dockerfile { - label 'linuxcontainer' - filename 'tests.Dockerfile' - dir 'ci' - args '--cap-add SYS_ADMIN ' + - '--security-opt apparmor:unconfined ' + - '--device /dev/fuse ' + - '--group-add docker ' + - '--volume=/var/run/docker.sock:/var/run/docker.sock ' + - '--network=host ' + - '--user jenkins' - } - } - - parameters { - choice( - name: 'VERBOSE', - description: 'Level of verbosity based on nimbus-build-system setup.', - choices: ['0', '1', '2'] - ) - } - - options { - timestamps() - /* Prevent Jenkins jobs from running forever */ - timeout(time: 120, unit: 'MINUTES') - /* manage how many builds we keep */ - buildDiscarder(logRotator( - numToKeepStr: '10', - daysToKeepStr: '30', - artifactNumToKeepStr: '1', - )) - /* Abort old PR builds. */ - disableConcurrentBuilds( - abortPrevious: isPRBuild - ) - disableRestartFromStage() - } - - environment { - PLATFORM = 'tests/ui' - /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" - /* Makefile assumes the compiler folder is included */ - QTDIR = "/opt/qt/${QT_VERSION}/gcc_64" - PATH = "${env.QTDIR}/bin:${env.PATH}" - /* Hack fix for params not being set in job on first run */ - VERBOSE = "${params.VERBOSE}" - } - - stages { - stage('Cleanup Workspace') { - steps { - sh './scripts/clean-git.sh' - } - } - - stage('Fetch submodules') { - steps { - sh 'git submodule update --init --recursive' - } - } - - stage('Check Translations') { - steps { - script { - checkTranslations() - } - } - } - - stage('QML Lint') { - steps { - sh 'make qml-lint' - } - } - - stage('Build StatusQ Tests') { - steps { - sh 'make statusq-tests' - } - } - - stage('Build StatusQ Sanity Checker') { - steps { - sh 'make statusq-sanity-checker' - } - } - - stage('Build Storybook') { - steps { - sh 'make storybook-build' - } - } - - stage('Check StatusQ Tests') { - steps { script { - /* Needed for QGuiApplication to import QtQuick.Dialogs */ - wrap([ - $class: 'Xvfb', - autoDisplayName: true, - parallelBuild: true, - screen: '2560x1440x24', - ]) { - sh('make run-statusq-tests') - } - } } - } - - stage('Check StatusQ Sanity Checker') { - steps { script { - /* Needed for QGuiApplication to import QtQuick.Dialogs */ - wrap([ - $class: 'Xvfb', - autoDisplayName: true, - parallelBuild: true, - screen: '2560x1440x24', - ]) { - sh('make run-statusq-sanity-checker') - } - } } - } - - stage('Check Storybook Tests') { - steps { script { - /* Needed for QGuiApplication to import QtQuick.Dialogs */ - wrap([ - $class: 'Xvfb', - autoDisplayName: true, - parallelBuild: true, - screen: '2560x1440x24', - ]) { - sh('make run-storybook-tests') - } - } } - } - - stage('Check Storybook Pages Validator') { - steps { script { - /* Needed for QGuiApplication to import QtQuick.Dialogs */ - wrap([ - $class: 'Xvfb', - autoDisplayName: true, - parallelBuild: true, - screen: '2560x1440x24', - ]) { - sh('make run-storybook-pages-validator') - } - } } - } - - stage('Upload') { - steps { script { - env.PKG_URL = "${currentBuild.absoluteUrl}/consoleText" - } } - } - } - - post { - success { script { github.notifyPR(true) } } - failure { script { github.notifyPR(false) } } - cleanup { - cleanWs(disableDeferredWipeout: true) - dir(env.WORKSPACE_TMP) { deleteDir() } - } - } -} - -def checkTranslations() { - sh 'make update-translations' - def changes = sh(script: 'git diff --name-only ui/i18n/*.ts', returnStdout: true).trim() - if (changes) { - error("Translation files out of date.\n\nModified files:\n${changes}\n\nTo fix: run 'make update-translations' and commit changes.") - } -} diff --git a/ci/Jenkinsfile.windows b/ci/Jenkinsfile.windows index 26d3c08158..7213644617 100644 --- a/ci/Jenkinsfile.windows +++ b/ci/Jenkinsfile.windows @@ -76,7 +76,7 @@ pipeline { environment { PLATFORM = "windows/${getArch()}" /* Improve make performance */ - MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE}" + MAKEFLAGS = "-j${utils.getProcCount()} V=${params.VERBOSE} USE_SYSTEM_NIM=1" /* Explicitly set the QT version */ QTDIR = "/c/Qt/${QT_VERSION}/msvc2022_64" GOPATH = "${WORKSPACE_TMP}/go" @@ -120,7 +120,7 @@ pipeline { } stage('Fetch submodules') { steps { - sh 'git submodule update --init --recursive' + sh './scripts/ci-fetch-submodules.sh' } } stage('Override status-go ref') { @@ -212,7 +212,7 @@ def assembePath(List extraPaths) { 'C:/Windows/system32', 'C:/Windows', 'C:/ProgramData/scoop/apps/go/1.24.7/bin', - 'C:/ProgramData/scoop/apps/nim/2.2.6/bin', + 'C:/ProgramData/scoop/apps/nim/2.2.10/bin', 'C:/ProgramData/scoop/apps/cmake/3.31.6/bin', 'C:/ProgramData/scoop/apps/python/3.13.5', 'C:/ProgramData/scoop/apps/python/3.13.5/Scripts', diff --git a/makefiles/nim-tests.mk b/makefiles/nim-tests.mk index 22a87e3893..3889d7b43a 100644 --- a/makefiles/nim-tests.mk +++ b/makefiles/nim-tests.mk @@ -54,6 +54,10 @@ ifneq ($(mkspecs),win32) nim-test-run/%: NIM_PARAMS += --passL:"$(QT_SEAQT_EXTRA_LIBS)" endif +# Use per-test nimcache to avoid race conditions when tests build in parallel +NIMCACHE_BASE ?= $(or $(WORKSPACE_TMP),build)/nimcache +nim-test-run/%: NIM_PARAMS += --nimcache:$(NIMCACHE_BASE)-$(notdir $(basename $@)) + nim-test-run/%: | qt-pkgconfig $(STATUSGO) $(QRCODEGEN) LD_LIBRARY_PATH="$(QT_LIBDIR)":"$(NIMSDS_LIBDIR)":"$(STATUSGO_LIBDIR)":"$(EXTRA_LIBS_PATH)":"$(LD_LIBRARY_PATH)" $(ENV_SCRIPT) \ nim c $(NIM_PARAMS) $(NIM_EXTRA_PARAMS) --mm:orc --passL:"-L$(STATUSGO_LIBDIR)" --passL:"-lstatus" --passL:"$(QRCODEGEN)" -r $(subst nim-test-run/,,$@) diff --git a/mobile/docker/Dockerfile b/mobile/docker/Dockerfile index 07108986f0..a694d6ddc9 100644 --- a/mobile/docker/Dockerfile +++ b/mobile/docker/Dockerfile @@ -9,7 +9,7 @@ ARG JAVA_VERSION=17 ARG ANDROID_API_LEVEL=36 ARG ANDROID_NDK_VERSION=27.2.12479018 ARG GOLANG_VERSION="1.24.7" -ARG NIM_VERSION="2.2.4" +ARG NIM_VERSION="2.2.10" ARG NIX_VERSION="2.24.11" # ============================================================================= diff --git a/scripts/ci-fetch-submodules.sh b/scripts/ci-fetch-submodules.sh new file mode 100755 index 0000000000..56edfd89d1 --- /dev/null +++ b/scripts/ci-fetch-submodules.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +MOBILE=0 +[[ "${1:-}" == "--mobile" ]] && MOBILE=1 + +git submodule update --init vendor/nimbus-build-system +# Skip fetching the vendored Nim compiler to save time when using system Nim +if [[ "${USE_SYSTEM_NIM:-0}" == "1" ]]; then + git -C vendor/nimbus-build-system config submodule.vendor/Nim.update none +fi + +if [[ "$MOBILE" == "1" ]]; then + git submodule update --init mobile/vendors/openssl + # Disable all nested openssl submodules to prevent recursive fetching + # This avoids pulling in unnecessary dependencies that aren't needed for the build + git -C mobile/vendors/openssl config -f .gitmodules --name-only --get-regexp '^submodule\..*\.path$' \ + | sed -e 's/^submodule\.//' -e 's/\.path$//' \ + | while read -r name; do + git -C mobile/vendors/openssl config "submodule.${name}.update" none + done +else + git config submodule.mobile/vendors/openssl.update none +fi + +git submodule update --init --recursive diff --git a/scripts/validate-qml.sh b/scripts/validate-qml.sh index 9829c39a39..9af8acae3f 100755 --- a/scripts/validate-qml.sh +++ b/scripts/validate-qml.sh @@ -24,8 +24,8 @@ echo "qmllint: $QMLLINT" echo "Qt modules: $QT_QML_PATH" echo "" -# Collect all QML files -mapfile -d '' QML_FILES < <(find "$ROOT_DIR/ui" -name "*.qml" -print0) +# Collect all QML files. Skip build trees: CMake drops third-party sources +mapfile -d '' QML_FILES < <(find "$ROOT_DIR/ui" -type d -name build -prune -o -name "*.qml" -print0) echo "Checking ${#QML_FILES[@]} files..." # Run qmllint with JSON output for precise filtering diff --git a/scripts/windows_build_setup.ps1 b/scripts/windows_build_setup.ps1 index 24e91728c7..c1f833e31d 100644 --- a/scripts/windows_build_setup.ps1 +++ b/scripts/windows_build_setup.ps1 @@ -67,7 +67,7 @@ function Install-Dependencies { run scoop update --global 7zip innounp # WARNING: Remember to update PATH in ci/Jenkinsfile.windows. Scoop-Install 'status/go' '1.24.7' - Scoop-Install 'status/nim' '2.2.6' + Scoop-Install 'status/nim' '2.2.10' Scoop-Install 'status/cmake' '3.31.6' Scoop-Install 'status/python' '3.13.5' Scoop-Install 'status/mingw-winlibs' '15.2.0-13.0.0-r5'