diff --git a/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile new file mode 100644 index 000000000..21c84c093 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile @@ -0,0 +1,141 @@ +// Release pipeline for the Qt6-migrated legatus validation-scope branch +// (story/release-validation-scope): TetraIF, Z, Lts/Common (+ +// Lts/Proxies/TrainingAreaBase) -> 5 release DEB packages. +// +// Workspace layout (checked out by Jenkins, outer pipeline repo): +// / +// rti_license.dat +// external/ +// qt-migration-legatus/qt6-after/qt6-migrated-legatus/ <- LEGATUS_ROOT +// Dockerfile (prebuilt build image recipe) +// new_docker_build.sh (local build driver) +// rpmbuild/make_rpm_ubuntu26.py (in-tree packaging) +// jenkins-ci-rme/ (this pipeline) +// +// The build container image is the prebuilt image built from the in-tree +// Dockerfile; it is built on the agent when not present (RebuildImage +// forces a fresh build). + +def project = 'guez' +def legatusRoot = 'qt-migration-legatus/qt6-after/qt6-migrated-legatus' +def imageName = 'legatus-qt6-prebuilt:latest' + +properties([ + disableConcurrentBuilds(), + parameters([ + string(name: 'OsVersions', defaultValue: '26.04', description: 'Comma-separated Ubuntu base versions, all built in parallel'), + string(name: 'LtsVersion', defaultValue: '1.0', description: 'LTS version (major.minor) passed to make_rpm_ubuntu26.py --version'), + booleanParam(name: 'ClearWorkspace', defaultValue: false, description: 'Clear entire workspace on all nodes used for build'), + booleanParam(name: 'PublishArtifactRepo', defaultValue: true, description: 'Upload produced DEBs to the internal artifact repository'), + booleanParam(name: 'RebuildImage', defaultValue: false, description: 'Rebuild the prebuilt build image from the in-tree Dockerfile (default: reuse when present)') + ]) +]) + +env.GIT_TERMINAL_PROMPT = '0' +env.LTS_VERSION = "${params.LtsVersion}" +env.ARTIFACTREPO = 'https://internal-repo.sl.defence-elec.de' + +// OS versions this build covers; every build is isolated under +// rpms/${project}/${osVersion} so concurrent OS builds never collide. +def osVersions = params.OsVersions.split(',').collect { it.trim() }.findAll { it } + +def builds = [:] +osVersions.each { osVersion -> + def rpmsDir = "rpms/${project}/${osVersion}" + + builds["${project} (${osVersion})"] = { + stage("Prepare ${project} (${osVersion})") { + sh "rm -rf ${rpmsDir}" + sh "mkdir -p ${rpmsDir}" + } + + stage("Prepare build image ${project} (${osVersion})") { + if (params.RebuildImage) { + sh "docker rmi ${imageName} || true" + } + // The in-tree driver owns the Dockerfile build args (APT_PACKAGES, + // QT_VERSION, ...) - single source of truth. + sh """ + if docker image inspect ${imageName} >/dev/null 2>&1; then + echo "Reusing existing prebuilt image ${imageName}" + else + echo "Building prebuilt image ${imageName} from the in-tree Dockerfile..." + cd ${legatusRoot} && bash ./new_docker_build.sh --build-prebuilt-image --docker-image ${imageName} + fi + """ + } + + stage("Build DEB ${project} (${osVersion})") { + def buildContainer = docker.image("${imageName}") + def guezMode = (project == 'guez') ? 'ON' : 'OFF' + + def dockerOpts = "--init --shm-size=2g" + + " -v ${WORKSPACE}:/workspace" + + " -e WORKSPACE=/workspace" + + " -e PROJECT_BASE_DIR=/workspace" + + " -e PROJECT=${project}" + + " -e OS_VERSION=${osVersion}" + + " -e LTS_VERSION=${LTS_VERSION}" + + " -e GUEZ_MODE=${guezMode}" + + " -e BUILD_NUMBER=${env.BUILD_NUMBER}" + + " -e BUILD_TAG=${env.BUILD_TAG}" + + buildContainer.inside(dockerOpts) { + sh "bash /workspace/${legatusRoot}/jenkins-ci-rme/jenkins_builder_ubuntu26.sh" + } + } + + stage("Publish ${project} (${osVersion})") { + if (params.PublishArtifactRepo) { + withCredentials([usernamePassword(credentialsId: 'internal-repo-uploader', + usernameVariable: 'CURL_USER', + passwordVariable: 'CURL_PASS')]) { + sh """ + export CURL_CRED="\${CURL_USER}:\${CURL_PASS}" + export PROJECT='${project}' + export OS_VERSION='${osVersion}' + export LTS_VERSION='${LTS_VERSION}' + export BUILD_NUMBER='${env.BUILD_NUMBER}' + export ARTIFACTREPO='${env.ARTIFACTREPO}' + export ARTIFACTFOLDER='${project}' + bash ${legatusRoot}/jenkins-ci-rme/jenkins_publisher_ubuntu26.sh + """ + } + + def tdsUrl = readFile("url-${project}-${osVersion}.txt").trim() + def tdsVersion = readFile("tdsversion-${project}-${osVersion}.txt").trim() + def link = """${project} ${tdsVersion} (OS ${osVersion})""" + currentBuild.description = currentBuild.description ? "${currentBuild.description}
${link}" : link + } else { + echo "Artifact-repo publish skipped for ${project} (OS ${osVersion}, PublishArtifactRepo=false)" + } + } + } +} + +try { + stage("Initialization") { + if (params.ClearWorkspace) { + cleanWs() + checkout([ + $class: 'GitSCM', + branches: scm.branches, + doGenerateSubmoduleConfigurations: false, + extensions: [[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: false]], + userRemoteConfigs: scm.userRemoteConfigs + ]) + } + echo "Legatus Qt6 release build initialized — branch '${env.BRANCH_NAME}', project: ${project}, LTS ${env.LTS_VERSION}, OS version(s): ${osVersions.join(', ')}" + } + + parallel builds + + stage("Finalization") { + echo "Legatus Qt6 release build completed — project: ${project} (LTS ${env.LTS_VERSION}), OS version(s): ${osVersions.join(', ')}" + } + + currentBuild.result = 'SUCCESS' +} catch (e) { + currentBuild.result = 'FAILURE' + throw e +} diff --git a/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/README.md b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/README.md new file mode 100644 index 000000000..01429f39a --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/README.md @@ -0,0 +1,54 @@ +# jenkins-ci-rme — release pipeline for the Qt6 validation-scope branch + +In-tree copy of the Jenkins CI flow, adapted so the release pipeline depends +only on files inside this source tree: + +| File | Purpose | +|---|---| +| `Jenkinsfile` | Jenkins pipeline: prepare the prebuilt image (in-tree `Dockerfile`), run the builder inside the container, publish the DEBs to the artifact repo | +| `jenkins_builder_ubuntu26.sh` | Runs **inside** the build container: stages the workspace, runs the in-tree `rpmbuild/make_rpm_ubuntu26.py`, copies the DEBs to the packages-out mount | +| `jenkins_publisher_ubuntu26.sh` | Runs on the Jenkins agent: uploads the DEBs from `${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/` to the internal artifact repository | +| `release-16-10-2026.sh` | Local driver: the same flow without Jenkins, through the in-tree `new_docker_build.sh` | + +## Not included (superseded) + +- `jenkins_builder.sh` / `jenkins_publisher.sh` — openSUSE/RPM flow, not used + by this branch (DEB/Ubuntu 26.04 only) +- `Dockerfile-15.1` / `Dockerfile-ubuntu26.04` / `Dockerfile-ubuntu26-qt6` — + old image recipes; the prebuilt build image is built from the in-tree + `Dockerfile` (one level up) +- `jenkins_notifier.sh` — Bitbucket build-status notification, not part of + this project + +## Adaptations vs. the outer-repo copy + +- Packaging uses the **in-tree** `rpmbuild/make_rpm_ubuntu26.py` + (was `${WORKSPACE}/rpmbuild` from the outer repo) +- The prebuilt build image is built from the **in-tree** `Dockerfile` via + `new_docker_build.sh --build-prebuilt-image` (was a pre-pulled harbor + image) — build args stay in `new_docker_build.sh` (single source of truth) +- `MIGRATE_PIPELINE_ROOT` is no longer needed: `new_docker_build.sh` + derives it from its own location +- DEB output path: `${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/` (Jenkins + convention); the packages-out copy is skipped when that directory is the + make_rpm output dir (same device:inode guard as in `new_docker_build.sh`) +- Publisher uploads `${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/` directly — + `make_rpm_ubuntu26.py` writes the DEBs there without a TDS subfolder +- The post-publish Cleanup stage was dropped: the DEBs are kept under + `${WORKSPACE}/rpms/...` (the documented output location) + +## Local run + +```bash +cd qt6-migrated-legatus +bash ./jenkins-ci-rme/release-16-10-2026.sh +# DEBs: /rpms/guez/26.04/ +``` + +## Jenkins setup + +The job SCM must be the outer pipeline repository (the Jenkinsfile lives in +the `qt-migration-legatus` submodule; point the job's script path at +`qt-migration-legatus/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile`). +Required Jenkins credential: `internal-repo-uploader` (username/password for +the artifact repository). diff --git a/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh new file mode 100755 index 000000000..a36eade6c --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# +# Build the LTS DEB packages for the Qt6-migrated legatus release +# (validation scope: TetraIF, Z, Lts/Common + Lts/Proxies/TrainingAreaBase) +# for Ubuntu 26.04. +# +# Runs inside the prebuilt build container (built from the in-tree +# Dockerfile) with the pipeline workspace mounted at /workspace. +# Packaging is done by the IN-TREE rpmbuild copy +# (qt-migration-legatus/qt6-after/qt6-migrated-legatus/rpmbuild/), so the +# release flow needs no files outside the source tree. +# +# Required environment (set by the Jenkinsfile or `docker run -e ...`): +# PROJECT LTS flavor: guez +# OS_VERSION Ubuntu version, e.g. 26.04 +# LTS_VERSION Major.minor LTS version, e.g. 1.0 +# GUEZ_MODE ON | OFF — cmake -DGUEZ_MODE +# WORKSPACE Container workspace path (/workspace) +# PROJECT_BASE_DIR Staging root inside the container (/workspace) +# BUILD_NUMBER Build number (used as release in the DEB version) +# BUILD_TAG Build tag +# +# IMPORTANT: Source files under git management must NEVER be deleted. +# Only the temporary staging directory (deb_stage) is removed. + +set -eo pipefail + +: "${PROJECT:?PROJECT must be set}" +: "${OS_VERSION:?OS_VERSION must be set}" +: "${LTS_VERSION:?LTS_VERSION must be set}" +: "${GUEZ_MODE:?GUEZ_MODE must be set}" +: "${WORKSPACE:?WORKSPACE must be set}" +: "${PROJECT_BASE_DIR:?PROJECT_BASE_DIR must be set}" + +echo "Building LTS ${LTS_VERSION}-${BUILD_NUMBER:-?} DEB for project '${PROJECT}' (Ubuntu ${OS_VERSION})." +echo "PROJECT_BASE_DIR=${PROJECT_BASE_DIR}" + +export RTI_LICENSE_FILE="${WORKSPACE}/rti_license.dat" + +# Stage workspace as a clean copy under PROJECT_BASE_DIR/deb_stage. +# Only the staging subdirectory is removed — never the workspace itself, +# which contains git-managed source files that must not be deleted. +STAGE_DIR="${PROJECT_BASE_DIR}/deb_stage" +rm -rf "${STAGE_DIR}" +mkdir -p "${STAGE_DIR}" +tar --exclude='./rpms' --exclude='./.git' --exclude='./build' \ + --exclude='./deb_build_tmp' --exclude='./deb_stage' --exclude='./packages-out' \ + -cf - -C "${WORKSPACE}" . | tar -xf - -C "${STAGE_DIR}" + +export VERSION_DATE +VERSION_DATE=$(date +%Y-%m-%d) + +# Real CMake project root inside the staged tree (the in-tree source) +SOURCE_DIR="${STAGE_DIR}/qt-migration-legatus/qt6-after/qt6-migrated-legatus" +if [ ! -f "${SOURCE_DIR}/CMakeLists.txt" ] || [ ! -f "${SOURCE_DIR}/rpmbuild/make_rpm_ubuntu26.py" ]; then + echo "ERROR: staged source incomplete under ${SOURCE_DIR} (CMakeLists.txt or rpmbuild/make_rpm_ubuntu26.py missing)" >&2 + exit 1 +fi + +# Output is isolated per OS version so parallel OS builds of the same +# project never collide +cd "${SOURCE_DIR}/rpmbuild" +python3 make_rpm_ubuntu26.py -c LTS \ + --project_name "${PROJECT}" \ + --version "${LTS_VERSION}" \ + --output_dir "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" \ + --source_dir "${SOURCE_DIR}" \ + --cmake_build_flags "-DUSE_IPO=ON -DUSE_UNITY_BUILD=ON -DGUEZ_MODE=${GUEZ_MODE} -DBUILD_WLS=ON -DPostgreSQL_TYPE_INCLUDE_DIR=/usr/include/postgresql/ -DRELEASE_BUILD=ON" \ + --build_number "${BUILD_NUMBER:-1}" \ + --release_scope + +# Copy produced DEB packages to the packages-out mount (host: +# ${PACKAGE_OUTPUT_DIR}, container: /workspace/packages-out) so they are +# available on the host after the container exits. +# make_rpm already writes to ${WORKSPACE}/rpms/... (the host workspace +# mount), so when the host's PACKAGE_OUTPUT_DIR is that same directory the +# packages are already in place - skip copying onto themselves. +PACKAGE_OUT_DIR=/workspace/packages-out +mkdir -p "${PACKAGE_OUT_DIR}" +shopt -s nullglob +for deb in "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/"*.deb; do + base="$(basename "${deb}")" + if [[ -e "${PACKAGE_OUT_DIR}/${base}" ]] && + [[ "$(stat -c '%d:%i' "${deb}")" == "$(stat -c '%d:%i' "${PACKAGE_OUT_DIR}/${base}")" ]]; then + continue + fi + cp -f "${deb}" "${PACKAGE_OUT_DIR}/" +done +shopt -u nullglob + +echo "DEB packages produced for '${PROJECT}' (Ubuntu ${OS_VERSION}):" +ls -lhR "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" +echo "DEB packages copied to ${PACKAGE_OUT_DIR}:" +ls -lh "${PACKAGE_OUT_DIR}/" diff --git a/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_publisher_ubuntu26.sh b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_publisher_ubuntu26.sh new file mode 100755 index 000000000..494948ed6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_publisher_ubuntu26.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Upload the produced DEBs to the internal artifact repository and emit two +# helper files for the Jenkinsfile to consume (named per project + OS +# version): +# ${WORKSPACE}/url-${PROJECT}-${OS_VERSION}.txt — browseable URL of the uploaded folder +# ${WORKSPACE}/tdsversion-${PROJECT}-${OS_VERSION}.txt — full version string (LTS-build) +# +# Adapted for the in-tree flow: make_rpm_ubuntu26.py writes the DEBs +# directly under ${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/ (no TDS +# subfolder), so that directory is uploaded as-is. +# +# Required environment (set by the Jenkinsfile / withCredentials): +# PROJECT LTS flavor: guez +# OS_VERSION Ubuntu base version, e.g. 26.04 +# LTS_VERSION Major.minor LTS version, e.g. 1.0 +# BUILD_NUMBER Build number (release component of the artifact name) +# WORKSPACE Jenkins workspace path +# ARTIFACTREPO Base URL of the internal repo (no trailing slash) +# ARTIFACTFOLDER Subfolder under cts-rpms/ (project name, e.g. guez) +# CURL_CRED user:password for the upload (Jenkins credential) +# + +set -eo pipefail + +: "${PROJECT:?PROJECT must be set}" +: "${OS_VERSION:?OS_VERSION must be set}" +: "${LTS_VERSION:?LTS_VERSION must be set}" +: "${BUILD_NUMBER:?BUILD_NUMBER must be set}" +: "${WORKSPACE:?WORKSPACE must be set}" +: "${ARTIFACTREPO:?ARTIFACTREPO must be set}" +: "${ARTIFACTFOLDER:?ARTIFACTFOLDER must be set}" +: "${CURL_CRED:?CURL_CRED must be set}" + +tds_folder_name="${PROJECT}-TDS-${LTS_VERSION}-${BUILD_NUMBER}" +tds_folder_path="${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}" +tds_artifact_store="${ARTIFACTREPO}/repository/cts-rpms/${ARTIFACTFOLDER}/${OS_VERSION}/${tds_folder_name}" + +if [ ! -d "${tds_folder_path}" ]; then + echo "ERROR: expected package folder not found: ${tds_folder_path}" >&2 + exit 1 +fi + +shopt -s nullglob +uploaded=0 +for file in "${tds_folder_path}"/*; do + [ -f "${file}" ] || continue + # Upload .deb, .dsc, .changes, .buildinfo files + if [[ "${file}" == *.deb ]] || [[ "${file}" == *.dsc ]] || [[ "${file}" == *.changes ]] || [[ "${file}" == *.buildinfo ]]; then + name=$(basename "${file}") + echo "Uploading: ${name}" + curl --fail --silent --show-error \ + --user "${CURL_CRED}" \ + --upload-file "${file}" \ + "${tds_artifact_store}/${name}" + uploaded=$((uploaded + 1)) + fi +done +shopt -u nullglob + +if [ "${uploaded}" -eq 0 ]; then + echo "ERROR: no files uploaded from ${tds_folder_path}" >&2 + exit 1 +fi + +echo "Uploaded ${uploaded} file(s) to ${tds_artifact_store}/" + +# Per-(project, OS) filenames so concurrent publish stages don't clobber each +# other's helper files; the Jenkinsfile reads back the matching names. +echo "${ARTIFACTREPO}/service/rest/repository/browse/cts-rpms/${ARTIFACTFOLDER}/${OS_VERSION}/${tds_folder_name}/" > "${WORKSPACE}/url-${PROJECT}-${OS_VERSION}.txt" +echo "${LTS_VERSION}-${BUILD_NUMBER}" > "${WORKSPACE}/tdsversion-${PROJECT}-${OS_VERSION}.txt" diff --git a/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/release-16-10-2026.sh b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/release-16-10-2026.sh new file mode 100755 index 000000000..8f5d48266 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/release-16-10-2026.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# Local release pipeline for the Qt6-migrated legatus validation scope +# (TetraIF, Z, Lts/Common + Lts/Proxies/TrainingAreaBase). +# +# Runs the in-tree new_docker_build.sh: +# - builds the prebuilt Docker image from the in-tree Dockerfile when the +# image is not present yet +# - builds the release scope, runs ctest and produces the release DEB +# packages with the in-tree rpmbuild script +# +# The DEB packages land in ${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/ +# (Jenkins-style layout; locally WORKSPACE is the pipeline root). +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LEGATUS_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +PIPELINE_ROOT="$(cd "${LEGATUS_ROOT}/../../.." && pwd)" + +# Release build configuration (Jenkins-style variables) +export WORKSPACE="${PIPELINE_ROOT}" +export PROJECT="${PROJECT:-guez}" +export OS_VERSION="${OS_VERSION:-26.04}" +export LTS_VERSION="${LTS_VERSION:-1.0}" +export GUEZ_MODE="${GUEZ_MODE:-ON}" +export BUILD_NUMBER="${BUILD_NUMBER:-$(date +%Y%m%d-%H%M%S)}" +export BUILD_TAG="${BUILD_TAG:-release-qt6-first}" + +DOCKER_IMAGE="${DOCKER_IMAGE:-legatus-qt6-prebuilt:latest}" + +log_info() { + printf 'INFO: %s\n' "$*" +} + +log_info "=== Qt6 Release Pipeline ===" +log_info "Pipeline root: ${PIPELINE_ROOT}" +log_info "Legatus root: ${LEGATUS_ROOT}" +log_info "Docker image: ${DOCKER_IMAGE}" +log_info "Build number: ${BUILD_NUMBER}" +log_info "Build tag: ${BUILD_TAG}" +log_info "Package output: ${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" + +cd "${LEGATUS_ROOT}" + +# Build the prebuilt image from the in-tree Dockerfile when not present yet +# (--build-prebuilt-image builds the image and exits) +if ! docker image inspect "${DOCKER_IMAGE}" >/dev/null 2>&1; then + log_info "Prebuilt image not found - building it from the in-tree Dockerfile..." + bash ./new_docker_build.sh --build-prebuilt-image --docker-image "${DOCKER_IMAGE}" +fi + +# Release scope build + ctest + DEB production (in-tree rpmbuild) +bash ./new_docker_build.sh \ + --docker-image "${DOCKER_IMAGE}" \ + --run-jenkins-builder \ + --package-output-dir "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" \ + --project "${PROJECT}" \ + --lts-version "${LTS_VERSION}" \ + --build-number "${BUILD_NUMBER}" \ + --build-tag "${BUILD_TAG}" + +log_info "=== Release Pipeline Complete ===" diff --git a/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py b/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py index c1816f33b..12615ee13 100755 --- a/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py +++ b/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py @@ -839,6 +839,9 @@ def build_all_debs(source_dir, output_dir, project_name, version, build_number, "new_docker_build.sh", "Dockerfile", "Dockerfile.ubuntu26", + "jenkins-ci-rme/Jenkinsfile", + "jenkins-ci-rme/jenkins_builder_ubuntu26.sh", + "jenkins-ci-rme/jenkins_publisher_ubuntu26.sh", "jenkins-ci-rme/release-16-10-2026.sh", "rpmbuild/make_rpm_ubuntu26.py", "CMakeLists.txt",