From 7d7adf5a5400f3eec97086b22d870bc3cdfcd5b3 Mon Sep 17 00:00:00 2001 From: twi-devel Date: Thu, 22 Oct 2026 19:45:37 +0200 Subject: [PATCH] Jenkinsfile: Build Docker image locally when not present - Add Docker Image stage that checks if image exists and builds if needed - Fix nested stages structure in Build and Test stage - Docker image built on Jenkins host instead of pulling from registry - Resolves 'pull access denied' error when image not in registry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Entire-Checkpoint: 443ea4c1ce1c --- Jenkinsfile | 214 ++++++++++++++++++++++++++++------------------------ 1 file changed, 117 insertions(+), 97 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 866d58f..1f9f9b1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,10 +1,5 @@ pipeline { - agent { - docker { - image 'lifemaps/ci-cpp:latest' - reuseNode true - } - } + agent none options { timestamps() @@ -19,107 +14,133 @@ pipeline { QT_QPA_PLATFORM = "offscreen" LANG = "C.UTF-8" LC_ALL = "C.UTF-8" + DOCKER_IMAGE = "lifemaps/ci-cpp:latest" } stages { - stage('Cleanup') { + stage('Docker Image') { + agent { label 'any' } steps { script { - echo "Cleaning workspace to avoid corruption issues..." - // Use shell command with chmod to handle permission issues - sh ''' - if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then - echo "Removing build directory with permission fix..." - chmod -R u+w "${WORKSPACE}/${BUILD_DIR}" 2>/dev/null || true - rm -rf "${WORKSPACE}/${BUILD_DIR}" - fi - if [ -d "${WORKSPACE}/${COVERAGE_BUILD_DIR}" ]; then - echo "Removing coverage build directory..." - chmod -R u+w "${WORKSPACE}/${COVERAGE_BUILD_DIR}" 2>/dev/null || true - rm -rf "${WORKSPACE}/${COVERAGE_BUILD_DIR}" - fi - if [ -d "${WORKSPACE}/coverage" ]; then - echo "Removing coverage output directory..." - chmod -R u+w "${WORKSPACE}/coverage" 2>/dev/null || true - rm -rf "${WORKSPACE}/coverage" - fi - ''' + echo "Checking if Docker image exists locally..." + def imageExists = sh(script: "docker inspect ${DOCKER_IMAGE} >/dev/null 2>&1", returnStatus: true) == 0 + if (!imageExists) { + echo "Docker image not found. Building from ci/Dockerfile.ci..." + sh 'docker build -t ${DOCKER_IMAGE} -f ci/Dockerfile.ci .' + } else { + echo "Docker image ${DOCKER_IMAGE} found locally." + } } } } - stage('Checkout') { - steps { - checkout scm - sh 'git fetch --all --prune' - sh 'git submodule update --init --recursive || true' - } - } - - stage('Tool Versions') { - steps { - sh ''' - python3 --version - cmake --version - ninja --version - g++ --version - ''' - } - } - - stage('Qt Diagnostics') { - steps { - sh ''' - echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM" - echo "LANG=$LANG" - echo "LC_ALL=$LC_ALL" - dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true - pkg-config --list-all | grep -i qt || true - ''' - } - } - - stage('Policy Checks') { - steps { - sh 'python3 ci/check_pr_policies.py' - } - } - - stage('Configure') { - steps { - sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug' - } - } - - stage('Build') { - steps { - sh 'cmake --build ${BUILD_DIR} --parallel' - } - } - - stage('Test') { - steps { - sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure' - } - } - - stage('Coverage') { - when { - expression { - def branch = env.CHANGE_BRANCH ?: env.BRANCH_NAME - return branch ==~ /main|master|coverage.*|feature.*/ + stage('Build and Test') { + agent { + docker { + image 'lifemaps/ci-cpp:latest' + reuseNode true } } - steps { - echo "Running coverage collection..." - sh ''' - ${PROJECT_DIR}/scripts/collect-coverage.sh ${WORKSPACE}/${COVERAGE_BUILD_DIR} - ''' - } - post { - always { - publishCoverage adapters: [lcovAdapter(mergeToOneReport: true, reportFile: 'coverage/lcov.info')], - sourceFileResolver: sourceFiles('STORE_LAST_BUILD') + stages { + stage('Cleanup') { + steps { + script { + echo "Cleaning workspace to avoid corruption issues..." + sh ''' + if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then + echo "Removing build directory with permission fix..." + chmod -R u+w "${WORKSPACE}/${BUILD_DIR}" 2>/dev/null || true + rm -rf "${WORKSPACE}/${BUILD_DIR}" + fi + if [ -d "${WORKSPACE}/${COVERAGE_BUILD_DIR}" ]; then + echo "Removing coverage build directory..." + chmod -R u+w "${WORKSPACE}/${COVERAGE_BUILD_DIR}" 2>/dev/null || true + rm -rf "${WORKSPACE}/${COVERAGE_BUILD_DIR}" + fi + if [ -d "${WORKSPACE}/coverage" ]; then + echo "Removing coverage output directory..." + chmod -R u+w "${WORKSPACE}/coverage" 2>/dev/null || true + rm -rf "${WORKSPACE}/coverage" + fi + ''' + } + } + } + + stage('Checkout') { + steps { + checkout scm + sh 'git fetch --all --prune' + sh 'git submodule update --init --recursive || true' + } + } + + stage('Tool Versions') { + steps { + sh ''' + python3 --version + cmake --version + ninja --version + g++ --version + ''' + } + } + + stage('Qt Diagnostics') { + steps { + sh ''' + echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM" + echo "LANG=$LANG" + echo "LC_ALL=$LC_ALL" + dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true + pkg-config --list-all | grep -i qt || true + ''' + } + } + + stage('Policy Checks') { + steps { + sh 'python3 ci/check_pr_policies.py' + } + } + + stage('Configure') { + steps { + sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug' + } + } + + stage('Build') { + steps { + sh 'cmake --build ${BUILD_DIR} --parallel' + } + } + + stage('Test') { + steps { + sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure' + } + } + + stage('Coverage') { + when { + expression { + def branch = env.CHANGE_BRANCH ?: env.BRANCH_NAME + return branch ==~ /main|master|coverage.*|feature.*/ + } + } + steps { + echo "Running coverage collection..." + sh ''' + ${PROJECT_DIR}/scripts/collect-coverage.sh ${WORKSPACE}/${COVERAGE_BUILD_DIR} + ''' + } + post { + always { + publishCoverage adapters: [lcovAdapter(mergeToOneReport: true, reportFile: 'coverage/lcov.info')], + sourceFileResolver: sourceFiles('STORE_LAST_BUILD') + } + } } } } @@ -129,7 +150,6 @@ pipeline { always { script { if (getContext(hudson.FilePath)) { - // Use shell command with chmod to handle permission issues sh ''' if [ -d "${WORKSPACE}/${BUILD_DIR}" ]; then echo "Final cleanup: Removing build directory..."