From 0e2100f38375dd6bc0b3a973465bbe850fbc3cb9 Mon Sep 17 00:00:00 2001 From: berrux_dev Date: Thu, 15 Oct 2026 20:43:46 +0200 Subject: [PATCH] reset Jenkins file --- .../jenkins-ci-rme/Jenkinsfile | 34 + .../jenkins-ci-rme/README.md | 54 ++ .../jenkins-ci-rme/jenkins/Jenkinsfile | 135 ++++ .../jenkins_builder_ubuntu26.sh | 85 +++ .../jenkins_publisher_ubuntu26.sh | 71 ++ .../jenkins-ci-rme/release-16-10-2026.sh | 77 +++ .../qt6-migrated-legatus/new_docker_build.sh | 647 ++++++++++++++++++ .../rpmbuild/make_rpm_ubuntu26.py | 527 ++++++++++++++ 8 files changed, 1630 insertions(+) create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/README.md create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins/Jenkinsfile create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_publisher_ubuntu26.sh create mode 100644 qt6-after/qt6-migrated-legatus/jenkins-ci-rme/release-16-10-2026.sh create mode 100644 qt6-after/qt6-migrated-legatus/new_docker_build.sh create mode 100644 qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py 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..3c69428b6 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/Jenkinsfile @@ -0,0 +1,34 @@ +// Entry point of the release pipeline for the Qt6-migrated legatus +// validation-scope branch (story/release-validation-scope). +// +// Lives in jenkins-ci-rme/ of the DLBO Qt6 repo. Declares the job properties, +// allocates the build node, checks out the repo with its submodules and +// loads the build body from jenkins-ci-rme/jenkins/Jenkinsfile. + +properties([ + disableConcurrentBuilds(), + parameters([ + choice(name: 'ProjectName', choices: ['guez'], description: 'LTS project flavor (passed to make_rpm_ubuntu26.py --project_name)'), + string(name: 'OsVersions', defaultValue: '26.04', description: 'Comma-separated Ubuntu base versions, built sequentially'), + 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 used for build'), + booleanParam(name: 'PublishArtifactRepo', defaultValue: true, description: 'Upload produced DEBs to the internal artifact repository'), + booleanParam(name: 'RunCoverage', defaultValue: true, description: 'Build the release scope with -O0 --coverage, run ctest, collect the lcov report and archive it') + ]) +]) + +node('jenkins-linux-node-2.sl.defence-elec.de') { + stage('Init submodule') { + 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 + ]) + } + load 'jenkins-ci-rme/jenkins/Jenkinsfile' +} 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..44e14ec68 --- /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 entry point (job script path `jenkins-ci-rme/Jenkinsfile`): job properties/parameters, build node, checkout with submodules, then `load`s `jenkins-ci-rme/jenkins/Jenkinsfile` | +| `jenkins/Jenkinsfile` | Build body (per OS version, sequentially): install the prebuilt image from `legatus-qt6_1.0.tar.gz` when missing, run the builder inside the container, coverage, 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 `legatus-qt6:1.0` ships in the repo as + `legatus-qt6_1.0.tar.gz` (repo root); Jenkins `docker load`s it only when + the image is not installed on the agent yet, and never builds it +- `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 "DLBO Qt6" +bash ./jenkins-ci-rme/release-16-10-2026.sh +# DEBs: /rpms/guez/26.04/ +``` + +## Jenkins setup + +The job SCM is the DLBO Qt6 repository; the script path is +`jenkins-ci-rme/Jenkinsfile`, which loads `jenkins-ci-rme/jenkins/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/Jenkinsfile b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins/Jenkinsfile new file mode 100644 index 000000000..fdf926219 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins/Jenkinsfile @@ -0,0 +1,135 @@ +// Build body of the 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. +// +// Loaded by jenkins-ci-rme/Jenkinsfile inside the allocated node after the +// checkout; job properties/parameters live there. +// +// Workspace layout (checked out by Jenkins, DLBO Qt6 repo): +// / +// jenkins-ci-rme/Jenkinsfile (entry point) +// 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 shipped in the repo as +// legatus-qt6_1.0.tar.gz; it is loaded onto the agent when not installed +// yet, never built here. + +def project = params.ProjectName +def imageName = 'legatus-qt6:1.0' +def imageArchive = 'legatus-qt6_1.0.tar.gz' + +env.GIT_TERMINAL_PROMPT = '0' +env.LTS_VERSION = "${params.LtsVersion}" +env.ARTIFACTREPO = 'https://internal-repo.sl.defence-elec.de' + +// OS versions this build covers, built one after another (the builder +// stages the workspace into a shared deb_stage directory); output is +// still isolated under rpms/${project}/${osVersion}. +def osVersions = params.OsVersions.split(',').collect { it.trim() }.findAll { it } + +def buildOsVersion = { String osVersion -> + def rpmsDir = "rpms/${project}/${osVersion}" + + stage("Prepare ${project} (${osVersion})") { + sh "rm -rf ${rpmsDir}" + sh "mkdir -p ${rpmsDir}" + } + + 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/jenkins-ci-rme/jenkins_builder_ubuntu26.sh" + } + } + + stage("Coverage ${project} (${osVersion})") { + if (!params.RunCoverage) { + echo "Coverage skipped for ${project} (OS ${osVersion}, RunCoverage=false)" + return + } + // The driver (run on the agent, same pattern as the image-build + // stage) builds the scope with -O0 --coverage, runs ctest and + // collects the lcov report into the workspace mount. + sh "bash ./new_docker_build.sh --docker-image ${imageName} --run-coverage --coverage-output-dir ${WORKSPACE}/coverage/${project}/${osVersion}" + archiveArtifacts artifacts: "coverage/${project}/${osVersion}/**", allowEmptyArchive: true + } + + 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 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") { + echo "Legatus Qt6 release build initialized — branch '${env.BRANCH_NAME}', project: ${project}, LTS ${env.LTS_VERSION}, OS version(s): ${osVersions.join(', ')}" + } + + stage("Install build image") { + // Reuse the prebuilt image when it is already installed on the agent; + // otherwise load it from the archive shipped in the repo. + def imageInstalled = sh(script: "docker image inspect '${imageName}' >/dev/null 2>&1", returnStatus: true) == 0 + if (imageInstalled) { + echo "Build image ${imageName} is already installed on the agent - reusing it" + } else { + if (!fileExists(imageArchive)) { + error "Build image ${imageName} not installed and image archive ${imageArchive} not found in the workspace" + } + echo "Build image ${imageName} not installed on the agent - loading it from ${imageArchive}" + sh "docker load -i '${imageArchive}'" + if (sh(script: "docker image inspect '${imageName}' >/dev/null 2>&1", returnStatus: true) != 0) { + error "Build image ${imageName} still not available after loading ${imageArchive}" + } + echo "Build image ${imageName} installed on the agent" + } + } + + for (osVersion in osVersions) { + buildOsVersion(osVersion) + } + + 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/jenkins_builder_ubuntu26.sh b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh new file mode 100644 index 000000000..5f83aa272 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/jenkins_builder_ubuntu26.sh @@ -0,0 +1,85 @@ +#!/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 +# (rpmbuild/ at the DLBO Qt6 repo root), 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}" + +# Stage workspace as a clean copy under PROJECT_BASE_DIR/deb_stage. +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' \ + --exclude='./legatus-qt6_1.0.tar.gz' \ + -cf - -C "${WORKSPACE}" . | tar -xf - -C "${STAGE_DIR}" + +export VERSION_DATE +VERSION_DATE=$(date +%Y-%m-%d) + +# CMake project root inside the staged tree (the DLBO Qt6 repo root) +SOURCE_DIR="${STAGE_DIR}" +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 + +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 100644 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 100644 index 000000000..c8483fd96 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/jenkins-ci-rme/release-16-10-2026.sh @@ -0,0 +1,77 @@ +#!/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 +# - collects the lcov code-coverage report (--run-coverage; set +# DRYRUN_COVERAGE=OFF to skip) +# +# 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="${LEGATUS_ROOT}" + +# 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:1.0}" + +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}" + +# Code coverage (lcov) - mirrors the Jenkins Coverage stage. Set +# DRYRUN_COVERAGE=OFF for a fast local DEB-only run. +DRYRUN_COVERAGE="${DRYRUN_COVERAGE:-ON}" +if [[ "${DRYRUN_COVERAGE}" == "ON" ]]; then + bash ./new_docker_build.sh \ + --docker-image "${DOCKER_IMAGE}" \ + --run-coverage \ + --coverage-output-dir "${WORKSPACE}/coverage/${PROJECT}/${OS_VERSION}" +else + log_info "Coverage skipped (DRYRUN_COVERAGE=OFF)" +fi + +log_info "=== Release Pipeline Complete ===" diff --git a/qt6-after/qt6-migrated-legatus/new_docker_build.sh b/qt6-after/qt6-migrated-legatus/new_docker_build.sh new file mode 100644 index 000000000..064b06313 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/new_docker_build.sh @@ -0,0 +1,647 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ----------------------------------------------------------------------------- +# Build helper for the release validation-scope branch of qt-migrated-legatus +# +# Builds only the release scope (TetraIF, Z/src, Lts/Common + +# Lts/Proxies/TrainingAreaBase) inside the prebuilt Docker image and +# optionally runs the Jenkins builder to produce the release DEB packages. +# +# - Resolves MIGRATE_PIPELINE_ROOT (exported variable, .envrc, or derived +# from the script location = repo root) for the workspace mount +# - Uses a prebuilt Docker image with all build dependencies installed +# (build it from ./Dockerfile via --build-prebuilt-image) +# - Optional deletion of the prebuilt Docker image via --delete-prebuilt-image +# - Optional DEB package production via the in-tree rpmbuild/ +# make_rpm_ubuntu26.py (self-contained - no outer-repo dependencies) +# - Uses a temporary container-side script for robust execution +# ----------------------------------------------------------------------------- + +usage() { + cat <<'EOF' +Usage: ./new_docker_build.sh [options] + +Builds the release validation scope inside the prebuilt Docker image: + RELEASE_BUILD=ON (fixed) - TetraIF, Z, Lts/Common (+ TrainingAreaBase) + ctest runs for the release scope test executables (Lts/Common + Z) + +Options: + --docker-image IMAGE Docker image name to use/build/delete + (default: legatus-qt6:1.0) + --build-prebuilt-image Build the prebuilt Docker image (from ./Dockerfile) + and exit. To run the build with it, run the script + again with --docker-image + --delete-prebuilt-image Delete the configured Docker image and exit + +Package production options (RPM/DEB): + --run-jenkins-builder Build the release DEB packages inside the container + using the in-tree rpmbuild/make_rpm_ubuntu26.py + --package-output-dir DIR Directory to mount for package output (default: ./packages-out) + --lts-version VERSION LTS version for package (default: 1.0) + --project NAME Project name for package (default: guez) + --build-number NUM Build number for package (default: 1) + --build-tag TAG Build tag for package (default: manual-build) + +Coverage options (lcov): + --run-coverage Build the release scope with -O0 --coverage, run ctest + and collect an lcov coverage report (mutually + exclusive with --run-jenkins-builder) + --coverage-output-dir DIR Directory to mount for coverage output + (default: ./coverage-out) + +Environment variable overrides: + BUILD_PREBUILT_IMAGE=ON|OFF + DELETE_PREBUILT_IMAGE=ON|OFF + DOCKER_IMAGE= + RUN_JENKINS_BUILDER=ON|OFF + PACKAGE_OUTPUT_DIR= + LTS_VERSION= + PROJECT= + GUEZ_MODE=ON|OFF + BUILD_NUMBER= + BUILD_TAG= + RUN_COVERAGE=ON|OFF + COVERAGE_OUTPUT_DIR= + +MIGRATE_PIPELINE_ROOT resolution (highest precedence first): + 1. exported MIGRATE_PIPELINE_ROOT (e.g. from a Jenkinsfile) + 2. MIGRATE_PIPELINE_ROOT set by .envrc in this directory + 3. derived from the script location (the DLBO Qt6 repo root) + +Examples: + ./new_docker_build.sh --build-prebuilt-image --docker-image legatus-qt6:1.0 + ./new_docker_build.sh --docker-image legatus-qt6:1.0 + ./new_docker_build.sh --delete-prebuilt-image --docker-image legatus-qt6:1.0 + ./new_docker_build.sh --docker-image legatus-qt6:1.0 \ + --run-jenkins-builder \ + --package-output-dir "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" + ./new_docker_build.sh --docker-image legatus-qt6:1.0 \ + --run-coverage \ + --coverage-output-dir "${WORKSPACE}/coverage/${PROJECT}/${OS_VERSION}/" +EOF +} + +log_info() { + printf 'INFO: %s\n' "$*" +} + +log_error() { + printf 'ERROR: %s\n' "$*" >&2 +} + +die() { + log_error "$*" + exit 1 +} + +require_command() { + local cmd="$1" + command -v "$cmd" >/dev/null 2>&1 || die "Required command not found: ${cmd}" +} + +require_file() { + local path="$1" + local label="$2" + [[ -f "$path" ]] || die "${label} not found: ${path}" +} + +require_directory() { + local path="$1" + local label="$2" + [[ -d "$path" ]] || die "${label} directory not found: ${path}" +} + +require_non_empty() { + local value="$1" + local name="$2" + [[ -n "$value" ]] || die "Required variable ${name} is not set or empty" +} + +normalize_on_off() { + local value="$1" + local name="$2" + + case "${value^^}" in + ON|OFF) + printf '%s\n' "${value^^}" + ;; + *) + die "Invalid value for ${name}: ${value}. Expected ON or OFF" + ;; + esac +} + +join_by() { + local delimiter="$1" + shift || true + local first=1 + local arg + + for arg in "$@"; do + if [[ $first -eq 1 ]]; then + printf '%s' "$arg" + first=0 + else + printf '%s%s' "$delimiter" "$arg" + fi + done +} + +cleanup_temp_dir() { + local dir="${1:-}" + if [[ -n "$dir" && -d "$dir" ]]; then + rm -rf "$dir" + fi +} + +delete_prebuilt_image() { + local image_name="$1" + + if docker image inspect "$image_name" >/dev/null 2>&1; then + log_info "Deleting Docker image: ${image_name}" + docker rmi "$image_name" + log_info "Docker image deleted successfully: ${image_name}" + else + log_info "Docker image does not exist, nothing to delete: ${image_name}" + fi +} + +APT_PACKAGES=( + build-essential + cmake + libboost-dev + libboost-all-dev + libjson-glib-dev + libgeographiclib-dev + ninja-build + libkf6texttemplate-dev + libpoppler-dev + libpoppler-qt6-dev + libqt6core6 + libqt6gui6 + libqt6widgets6 + libqt6sql6 + libqt6sql6-psql + libqt6test6 + qt6-base-dev + qt6-l10n-tools + qt6-serialport-dev + qt6-svg-dev + qt6-tools-dev-tools + qt6-tools-dev + qt6-websockets-dev + libgtest-dev + libpq-dev + postgresql-server-dev-all + git + python3-pip + # Code coverage (genhtml ships in the same package) + lcov + # Packaging tools for RPM/DEB build + rpm + debhelper + dpkg-dev + fakeroot + devscripts +) + +# Release scope targets: TetraIF + Z components + Lts/Common libraries +# and the release-scope test executables (Lts/Common + Z) +RELEASE_BUILD_TARGETS=( + # TetraIF module targets (CoPrtCnv / LogView / PrtCnv) + CoPrtCnv + LogViewLogger + LogView + PrtCnvLib + PrtCnv + # Z module targets + NGLLogger + nglapplication + nglcompression + nglcoreapplication + ngldb + nglguiapplication + nglsettings + nglsingleapplication + nglsinglecoreapplication + ngltools + ngltranslator + nglwidgets + qtsingleapplication + qtsinglecoreapplication + wms_json_ds + tcpserver + # Lts/Common library targets (for tests) + LtsCoordTrans + LtsLicence + MockCoordTrans + # Test executables for release scope modules + # Note: grantlee_parser_smoke excluded - requires ktexttemplate_defaulttags plugin (not in release scope) + aes_crypt_test + ngltools_qt_smoke + lts_coord_trans_unit_test + test_nglpdfwidget + # TetraIF unit tests (TetraIF/unit-test) + coprtcnv_unit_test + logview_unit_test + prtcnv_unit_test +) + +APT_PACKAGES_STRING="$(join_by ' ' "${APT_PACKAGES[@]}")" + +build_prebuilt_image() { + local image_name="$1" + local dockerfile="Dockerfile" + + require_file "${dockerfile}" "Prebuilt-image Dockerfile" + + # The Dockerfile has no COPY/ADD, so an empty build context is fine - + # this avoids tarring the source tree into the build context. + local tmp_dir + tmp_dir="$(mktemp -d)" + + trap 'cleanup_temp_dir "$tmp_dir"' RETURN + + log_info "Building prebuilt Docker image: ${image_name} (Dockerfile: ${dockerfile})" + docker build -t "${image_name}" \ + -f "${dockerfile}" \ + --build-arg APT_PACKAGES="${APT_PACKAGES_STRING}" \ + --build-arg QT_VERSION="${QT_VERSION}" \ + --build-arg QT_ARCH="${QT_ARCH}" \ + --build-arg QT_BASE_DIR="${QT_BASE_DIR}" \ + "${tmp_dir}" + log_info "Prebuilt Docker image created successfully: ${image_name}" +} + +require_command docker + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MIGRATE_PIPELINE_ROOT_DEFAULT="${SCRIPT_DIR}" +PIPELINE_ROOT_EXPORTED="${MIGRATE_PIPELINE_ROOT:-}" + +if [[ -f ".envrc" ]]; then + set -a + source ".envrc" + set +a +fi + +# An explicitly exported MIGRATE_PIPELINE_ROOT (Jenkinsfile) wins over .envrc +if [[ -n "${PIPELINE_ROOT_EXPORTED}" ]]; then + MIGRATE_PIPELINE_ROOT="${PIPELINE_ROOT_EXPORTED}" +else + MIGRATE_PIPELINE_ROOT="${MIGRATE_PIPELINE_ROOT:-${MIGRATE_PIPELINE_ROOT_DEFAULT}}" +fi + +# Fixed configuration for the release validation-scope branch +RELEASE_BUILD=ON +RUN_CTEST=ON +BUILD_PREBUILT_IMAGE="${BUILD_PREBUILT_IMAGE:-OFF}" +DELETE_PREBUILT_IMAGE="${DELETE_PREBUILT_IMAGE:-OFF}" +# Jenkins builder options for RPM/DEB package production +RUN_JENKINS_BUILDER="${RUN_JENKINS_BUILDER:-OFF}" +PACKAGE_OUTPUT_DIR="${PACKAGE_OUTPUT_DIR:-./packages-out}" +# Code coverage (lcov) +RUN_COVERAGE="${RUN_COVERAGE:-OFF}" +COVERAGE_OUTPUT_DIR="${COVERAGE_OUTPUT_DIR:-./coverage-out}" +LTS_VERSION="${LTS_VERSION:-1.0}" +PROJECT="${PROJECT:-guez}" +GUEZ_MODE="${GUEZ_MODE:-ON}" +BUILD_NUMBER="${BUILD_NUMBER:-1}" +BUILD_TAG="${BUILD_TAG:-manual-build}" +export QT_VERSION="${QT_VERSION:-6.10.2}" +export QT_ARCH="${QT_ARCH:-linux_gcc_64}" +# aqtinstall uses 'linux_gcc_64' but creates directory 'gcc_64' +export QT_ARCH_DIR="${QT_ARCH_DIR:-gcc_64}" +export QT_BASE_DIR="${QT_BASE_DIR:-/opt/Qt}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --build-prebuilt-image) + BUILD_PREBUILT_IMAGE=ON + ;; + --delete-prebuilt-image) + DELETE_PREBUILT_IMAGE=ON + ;; + --docker-image) + shift + [[ $# -gt 0 ]] || die "--docker-image requires an image name" + DOCKER_IMAGE="$1" + ;; + --run-jenkins-builder) + RUN_JENKINS_BUILDER=ON + ;; + --package-output-dir) + shift + [[ $# -gt 0 ]] || die "--package-output-dir requires a directory path" + PACKAGE_OUTPUT_DIR="$1" + ;; + --run-coverage) + RUN_COVERAGE=ON + ;; + --coverage-output-dir) + shift + [[ $# -gt 0 ]] || die "--coverage-output-dir requires a directory path" + COVERAGE_OUTPUT_DIR="$1" + ;; + --lts-version) + shift + [[ $# -gt 0 ]] || die "--lts-version requires a version string" + LTS_VERSION="$1" + ;; + --project) + shift + [[ $# -gt 0 ]] || die "--project requires a project name" + PROJECT="$1" + ;; + --build-number) + shift + [[ $# -gt 0 ]] || die "--build-number requires a build number" + BUILD_NUMBER="$1" + ;; + --build-tag) + shift + [[ $# -gt 0 ]] || die "--build-tag requires a build tag" + BUILD_TAG="$1" + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage + die "Unknown option: $1" + ;; + esac + shift +done + +BUILD_PREBUILT_IMAGE="$(normalize_on_off "$BUILD_PREBUILT_IMAGE" "BUILD_PREBUILT_IMAGE")" +DELETE_PREBUILT_IMAGE="$(normalize_on_off "$DELETE_PREBUILT_IMAGE" "DELETE_PREBUILT_IMAGE")" +RUN_JENKINS_BUILDER="$(normalize_on_off "$RUN_JENKINS_BUILDER" "RUN_JENKINS_BUILDER")" +RUN_COVERAGE="$(normalize_on_off "$RUN_COVERAGE" "RUN_COVERAGE")" + +if [[ "${RUN_COVERAGE}" == "ON" && "${RUN_JENKINS_BUILDER}" == "ON" ]]; then + die "--run-coverage is mutually exclusive with --run-jenkins-builder" +fi + +# Default for the Docker image name +DOCKER_IMAGE="${DOCKER_IMAGE:-legatus-qt6:1.0}" + +# Export variables for Docker container +export RELEASE_BUILD RUN_CTEST BUILD_PREBUILT_IMAGE DELETE_PREBUILT_IMAGE DOCKER_IMAGE RUN_JENKINS_BUILDER PACKAGE_OUTPUT_DIR RUN_COVERAGE COVERAGE_OUTPUT_DIR LTS_VERSION PROJECT GUEZ_MODE BUILD_NUMBER BUILD_TAG + +require_non_empty "${DOCKER_IMAGE}" "DOCKER_IMAGE" + +if [[ "${DELETE_PREBUILT_IMAGE}" == "ON" ]]; then + delete_prebuilt_image "${DOCKER_IMAGE}" + exit 0 +fi + +require_non_empty "${MIGRATE_PIPELINE_ROOT:-}" "MIGRATE_PIPELINE_ROOT" +require_directory "${MIGRATE_PIPELINE_ROOT}" "MIGRATE_PIPELINE_ROOT" + +WORKSPACE_ROOT_HOST="${MIGRATE_PIPELINE_ROOT}" +PROJECT_DIR_HOST="${WORKSPACE_ROOT_HOST}" + +require_directory "${PROJECT_DIR_HOST}" "Project directory" + +log_info "RELEASE_BUILD=${RELEASE_BUILD}" +log_info "RUN_CTEST=${RUN_CTEST}" +log_info "BUILD_PREBUILT_IMAGE=${BUILD_PREBUILT_IMAGE}" +log_info "DELETE_PREBUILT_IMAGE=${DELETE_PREBUILT_IMAGE}" +log_info "DOCKER_IMAGE=${DOCKER_IMAGE}" +log_info "RUN_JENKINS_BUILDER=${RUN_JENKINS_BUILDER}" +log_info "PACKAGE_OUTPUT_DIR=${PACKAGE_OUTPUT_DIR}" +log_info "RUN_COVERAGE=${RUN_COVERAGE}" +log_info "COVERAGE_OUTPUT_DIR=${COVERAGE_OUTPUT_DIR}" +log_info "LTS_VERSION=${LTS_VERSION}" +log_info "PROJECT=${PROJECT}" +log_info "GUEZ_MODE=${GUEZ_MODE}" +log_info "BUILD_NUMBER=${BUILD_NUMBER}" +log_info "BUILD_TAG=${BUILD_TAG}" +log_info "MIGRATE_PIPELINE_ROOT=${MIGRATE_PIPELINE_ROOT}" +log_info "PROJECT_DIR_HOST=${PROJECT_DIR_HOST}" + +if [[ "${BUILD_PREBUILT_IMAGE}" == "ON" ]]; then + build_prebuilt_image "${DOCKER_IMAGE}" + + log_info "Prebuilt image was built successfully." + log_info "To run the build with it, run:" + printf ' ./new_docker_build.sh --docker-image %q\n' "${DOCKER_IMAGE}" + exit 0 +fi + +RELEASE_BUILD_TARGETS_STRING="$(join_by ' ' "${RELEASE_BUILD_TARGETS[@]}")" + +HOST_TEMP_DIR="$(mktemp -d)" +CONTAINER_SCRIPT_HOST="${HOST_TEMP_DIR}/run-build-inside-container.sh" +cleanup_all() { + cleanup_temp_dir "${HOST_TEMP_DIR}" +} +trap cleanup_all EXIT + +cat > "${CONTAINER_SCRIPT_HOST}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +# Fixed configuration for the release validation-scope branch +RELEASE_BUILD="${RELEASE_BUILD:-ON}" +RUN_CTEST="${RUN_CTEST:-ON}" +# Packaging variables +RUN_JENKINS_BUILDER="${RUN_JENKINS_BUILDER:-OFF}" +RUN_COVERAGE="${RUN_COVERAGE:-OFF}" +LTS_VERSION="${LTS_VERSION:-1.0}" +PROJECT="${PROJECT:-guez}" +GUEZ_MODE="${GUEZ_MODE:-ON}" +BUILD_NUMBER="${BUILD_NUMBER:-1}" +BUILD_TAG="${BUILD_TAG:-manual-build}" +RELEASE_BUILD_TARGETS="${RELEASE_BUILD_TARGETS:-}" +QT_VERSION="${QT_VERSION:-6.10.2}" +QT_ARCH="${QT_ARCH:-linux_gcc_64}" +QT_ARCH_DIR="${QT_ARCH_DIR:-gcc_64}" +QT_BASE_DIR="${QT_BASE_DIR:-/opt/Qt}" + +log_info() { + printf 'INFO: %s\n' "$*" +} + +log_error() { + printf 'ERROR: %s\n' "$*" >&2 +} + +log_info "Using prebuilt Docker image: all build dependencies are baked in" + +export QT_QPA_PLATFORM=offscreen + +cd /workspace + +rm -rf build4 +mkdir -p build4 +cd build4 + +COV_CMAKE_ARGS=() +if [[ "${RUN_COVERAGE}" == "ON" ]]; then + COV_CMAKE_ARGS=( + "-DCMAKE_C_FLAGS_RELEASE=-O0 -g --coverage -DNDEBUG -fPIC" + "-DCMAKE_CXX_FLAGS_RELEASE=-O0 -g --coverage -DNDEBUG -fPIC" + "-DCMAKE_EXE_LINKER_FLAGS=--coverage" + "-DCMAKE_SHARED_LINKER_FLAGS=--coverage" + ) +fi +cmake .. \ + -DBUILD_TESTING=ON \ + -DRELEASE_BUILD="${RELEASE_BUILD}" \ + -DCMAKE_CXX_STANDARD=17 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="${QT_BASE_DIR}/${QT_VERSION}/${QT_ARCH_DIR}:/usr/local" \ + "${COV_CMAKE_ARGS[@]}" + +read -r -a RELEASE_TARGETS_ARRAY <<< "${RELEASE_BUILD_TARGETS}" + +log_info "Building release scope targets: ${RELEASE_TARGETS_ARRAY[*]}" +make -j8 "${RELEASE_TARGETS_ARRAY[@]}" 2>&1 | tee release_build.log + +if [[ "${RUN_CTEST}" == "ON" ]]; then + log_info "Running CTest" + ctest --test-dir . --output-on-failure || CTEST_RESULT=$? + if [[ "${CTEST_RESULT:-0}" -ne 0 ]]; then + log_info "CTest completed with failures (exit code: ${CTEST_RESULT})" + fi +else + log_info "Skipping CTest" +fi + +if [[ "${RUN_COVERAGE}" == "ON" ]]; then + log_info "=== Code coverage (lcov) ===" + COVOUT=/workspace/coverage-out + SRCROOT=/workspace + mkdir -p "${COVOUT}" + + COV_IGNORE=(--ignore-errors mismatch,inconsistent,unused) + lcov --capture --directory . --external "${COV_IGNORE[@]}" \ + --rc branch_coverage=1 \ + --output-file "${COVOUT}/raw.info" + lcov --extract "${COVOUT}/raw.info" \ + "${SRCROOT}/TetraIF/*" \ + "${SRCROOT}/Z/src/*" \ + "${SRCROOT}/Lts/Common/*" \ + "${SRCROOT}/Lts/Proxies/TrainingAreaBase/*" \ + "${COV_IGNORE[@]}" \ + --rc branch_coverage=1 \ + --output-file "${COVOUT}/scoped.info" + lcov --remove "${COVOUT}/scoped.info" \ + "*/unit-test/*" \ + "*CommonUnitTestsPlaceholder*" \ + "*/UnitTests/*" \ + "*/tests/*" \ + "*/test_*" \ + "*_test.cpp" \ + "*_test.h" \ + "*_unit_test*" \ + "${COV_IGNORE[@]}" \ + --rc branch_coverage=1 \ + --output-file "${COVOUT}/coverage.info" + lcov --summary "${COVOUT}/coverage.info" "${COV_IGNORE[@]}" --rc branch_coverage=1 \ + > "${COVOUT}/coverage_summary.txt" + genhtml "${COVOUT}/coverage.info" \ + --output-directory "${COVOUT}/html" \ + "${COV_IGNORE[@]}" \ + --branch-coverage --legend \ + --rc branch_coverage=1 + log_info "Coverage report written to ${COVOUT} (host: ${COVERAGE_OUTPUT_DIR})" + cat "${COVOUT}/coverage_summary.txt" +fi + +# DEB package production with the in-tree rpmbuild script +if [[ "${RUN_JENKINS_BUILDER}" == "ON" ]]; then + log_info "=== DEB Package Production (in-tree rpmbuild) ===" + + OS_VERSION=$(cat /etc/os-release | grep "^VERSION_ID=" | cut -d'"' -f2) + STAGE_DIR=/workspace/deb_stage + STAGED_SOURCE_DIR="${STAGE_DIR}" + + log_info "Staging workspace to ${STAGE_DIR}" + 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' \ + --exclude='./legatus-qt6_1.0.tar.gz' \ + -cf - -C /workspace . | tar -xf - -C "${STAGE_DIR}" + + if [[ ! -f "${STAGED_SOURCE_DIR}/CMakeLists.txt" || ! -f "${STAGED_SOURCE_DIR}/rpmbuild/make_rpm_ubuntu26.py" ]]; then + log_error "Staged source incomplete under ${STAGED_SOURCE_DIR} (CMakeLists.txt or rpmbuild/make_rpm_ubuntu26.py missing)" + exit 1 + fi + + log_info "Running ${STAGED_SOURCE_DIR}/rpmbuild/make_rpm_ubuntu26.py with:" + log_info " PROJECT=${PROJECT}" + log_info " OS_VERSION=${OS_VERSION}" + log_info " LTS_VERSION=${LTS_VERSION}" + log_info " GUEZ_MODE=${GUEZ_MODE}" + log_info " BUILD_NUMBER=${BUILD_NUMBER}" + log_info " BUILD_TAG=${BUILD_TAG}" + + cd "${STAGED_SOURCE_DIR}/rpmbuild" + if python3 make_rpm_ubuntu26.py -c LTS \ + --project_name "${PROJECT}" \ + --version "${LTS_VERSION}" \ + --output_dir "/workspace/rpms/${PROJECT}/${OS_VERSION}/" \ + --source_dir "${STAGED_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}" \ + --release_scope; then + log_info "Package build completed successfully" + else + log_error "Package build failed (exit code: $?)" + exit 1 + fi + + # Copy produced DEB packages to the packages-out + DEST_DIR=/workspace/packages-out + mkdir -p "${DEST_DIR}" + shopt -s nullglob + for deb in "/workspace/rpms/${PROJECT}/${OS_VERSION}/"*.deb; do + base="$(basename "${deb}")" + if [[ -e "${DEST_DIR}/${base}" ]] && + [[ "$(stat -c '%d:%i' "${deb}")" == "$(stat -c '%d:%i' "${DEST_DIR}/${base}")" ]]; then + continue + fi + cp -f "${deb}" "${DEST_DIR}/" + done + shopt -u nullglob + log_info "DEB packages available in ${DEST_DIR} (host: ${PACKAGE_OUTPUT_DIR:-unknown}):" + ls -lh "${DEST_DIR}/" +else + log_info "Skipping DEB package production" +fi +EOF + +chmod +x "${CONTAINER_SCRIPT_HOST}" + +docker run --rm \ + -e MIGRATE_PIPELINE_ROOT \ + -e LANG \ + -e LC_ALL \ + -e RELEASE_BUILD \ + -e RUN_CTEST \ + -e RUN_JENKINS_BUILDER \ + -e RUN_COVERAGE \ + -e COVERAGE_OUTPUT_DIR \ + -e PACKAGE_OUTPUT_DIR \ + -e LTS_VERSION \ + -e PROJECT \ + -e GUEZ_MODE \ + -e BUILD_NUMBER \ + -e BUILD_TAG \ + -e QT_VERSION \ + -e QT_ARCH \ + -e QT_ARCH_DIR \ + -e QT_BASE_DIR \ + -e RELEASE_BUILD_TARGETS="${RELEASE_BUILD_TARGETS_STRING}" \ + -v "${WORKSPACE_ROOT_HOST}:/workspace" \ + -v "${CONTAINER_SCRIPT_HOST}:/tmp/run-build-inside-container.sh:ro" \ + -v "${PACKAGE_OUTPUT_DIR}:/workspace/packages-out" \ + -v "${COVERAGE_OUTPUT_DIR}:/workspace/coverage-out" \ + "${DOCKER_IMAGE}" \ + bash /tmp/run-build-inside-container.sh diff --git a/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py b/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py new file mode 100644 index 000000000..562960818 --- /dev/null +++ b/qt6-after/qt6-migrated-legatus/rpmbuild/make_rpm_ubuntu26.py @@ -0,0 +1,527 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +""" +DEB package builder for Ubuntu 26.04 - Qt6 Release +Creates 5 separate DEB packages as per customer requirements: +1. qt6-release-runtime - binaries and libraries from S +2. qt6-release-sources - Qt6 C++ sources from S +3. qt6-release-ctest-runtime - CTest executables +4. qt6-release-ctest-sources - CTest source files +5. qt6-release-build-pipeline - Dockerfiles, build scripts, CI/CD files +""" + +import argparse +import fnmatch +import os +import shutil +import subprocess +import sys +from email.utils import formatdate + +SKIP_DIRS = {'build', 'cmake-build-debug', 'cmake-build-release', 'deb_build_tmp'} +SOURCE_EXTENSIONS = ('.cpp', '.h', '.hpp', '.cc', '.cxx', '.hxx') +PIPELINE_FILES = [ + "new_docker_build.sh", + "Dockerfile", + "jenkins-ci-rme/Jenkinsfile", + "jenkins-ci-rme/jenkins/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", +] + + +def verbose(): + verb = os.getenv("EMERGE_VERBOSE") + if verb is not None and verb.isdigit() and int(verb) > 0: + return int(verb) + return 0 + + +def debug(message): + if verbose() >= 1: + print(f"debug: {message}") + return True + + +def info(message): + print(f"info: {message}") + return True + + +def warning(message): + print(f"warning: {message}") + return True + + +def error(message): + die(message) + + +def die(message): + print(f"emerge fatal error: {message}", file=sys.stderr) + sys.exit(1) + + +def run(cmd, label, env=None): + """Run a shell command, print its output and die on failure.""" + debug(f"Executing: {cmd}") + ret = subprocess.run(cmd, shell=True, env=env, capture_output=True, text=True) + if ret.returncode != 0: + print(ret.stdout) + print(ret.stderr) + die(f"{label} with code {ret.returncode}") + + +def remove_directory(path): + if os.path.exists(path): + shutil.rmtree(path) + + +def is_dds_file(filepath): + """True if the path is DDS-related and must be excluded.""" + return 'dds' in filepath.lower() + + +def prune_dirs(dirs, root, file_filter): + """Prune hidden/build dirs in-place (and dirs matching file_filter).""" + dirs[:] = [d for d in dirs + if not d.startswith('.') + and d not in SKIP_DIRS + and (file_filter is None or not file_filter(os.path.join(root, d)))] + + +def copy_src_dir_to_dest_dir(src_dir, dest_dir, file_filter=None): + """Copy the source tree, skipping hidden/build dirs and filtered files.""" + os.makedirs(dest_dir, exist_ok=True) + for root, dirs, files in os.walk(src_dir): + prune_dirs(dirs, root, file_filter) + for file in files: + src_file = os.path.join(root, file) + if file_filter and file_filter(src_file): + continue + dest_file = os.path.join(dest_dir, os.path.relpath(src_file, src_dir)) + os.makedirs(os.path.dirname(dest_file), exist_ok=True) + shutil.copy2(src_file, dest_file) + + +def find_test_module_source(pkg_dir, test_name, release_dirs): + """Module dir (relative to pkg_dir) whose CMakeLists defines test_name, or None.""" + for rel_dir in release_dirs: + module_dir = os.path.join(pkg_dir, rel_dir) + if not os.path.exists(module_dir): + continue + for root, dirs, files in os.walk(module_dir): + prune_dirs(dirs, root, None) + if 'CMakeLists.txt' not in files: + continue + try: + with open(os.path.join(root, 'CMakeLists.txt')) as f: + content = f.read() + except Exception: + continue + if f'ADD_EXECUTABLE({test_name}' in content or f'ADD_TEST(NAME {test_name}' in content: + return os.path.relpath(root, pkg_dir) + return None + + +def find_test_executables(pkg_dir, release_dirs): + """(executable, module) pairs for the test executables in build4/bin/.""" + test_executables = [] + build_bin_dir = os.path.join(pkg_dir, "build4", "bin") + if not os.path.exists(build_bin_dir): + warning(f"Build bin directory not found: {build_bin_dir}") + return test_executables + for filename in os.listdir(build_bin_dir): + file_path = os.path.join(build_bin_dir, filename) + if not (os.path.isfile(file_path) and os.access(file_path, os.X_OK)): + continue + if 'test' not in filename.lower(): + continue + module_path = find_test_module_source(pkg_dir, filename, release_dirs) + if module_path: + test_executables.append((file_path, module_path)) + debug(f" Found test executable: {filename} from {module_path}") + else: + debug(f" Skipping test executable (no module found): {filename}") + return test_executables + + +def find_test_sources(pkg_dir, release_dirs): + """Relative paths of test source files (name containing 'test', .cpp/.h).""" + test_sources = [] + for rel_dir in release_dirs: + module_dir = os.path.join(pkg_dir, rel_dir) + if not os.path.exists(module_dir): + continue + for root, dirs, files in os.walk(module_dir): + prune_dirs(dirs, root, None) + for filename in files: + if 'test' in filename.lower() and filename.endswith(('.cpp', '.h')): + file_path = os.path.join(root, filename) + rel_path = os.path.relpath(file_path, pkg_dir) + test_sources.append(rel_path) + debug(f" Found test source: {rel_path}") + return test_sources + + +def write_deb_file(deb_dir, name, content, executable=False): + """Write a debian/* file and log its creation.""" + path = os.path.join(deb_dir, name) + with open(path, "w") as f: + f.write(content) + if executable: + os.chmod(path, 0o755) + info(f"Created {path}") + + +def create_debian_control(deb_dir, package_name, version, build_number, description, + depends="", section="devel"): + write_deb_file(deb_dir, "control", f"""Source: {package_name} +Section: {section} +Priority: optional +Maintainer: Legatus Team +Build-Depends: debhelper (>= 13), cmake, qt6-base-dev, qt6-tools-dev +Standards-Version: 4.6.0 + +Package: {package_name} +Architecture: any +Depends: ${{shlibs:Depends}}, ${{misc:Depends}}{", " + depends if depends else ""} +Description: {description} + Legatus Qt6 Release Package + This package is part of the Legatus Qt6 migration release. +""") + + +def create_debian_changelog(deb_dir, package_name, version, build_number): + write_deb_file(deb_dir, "changelog", f"""{package_name} ({version}-{build_number}) unstable; urgency=medium + + * Qt6 Release Build + + -- Legatus Team {formatdate(localtime=True)} +""") + + +def create_debian_rules(deb_dir, cmake_flags="", skip_tests=False): + test_override = """ +override_dh_auto_test: + # Tests skipped for this package type +""" if skip_tests else "" + write_deb_file(deb_dir, "rules", f"""#!/usr/bin/make -f +%: + dh $@ --buildsystem=cmake + +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_BUILD_TYPE=Release {cmake_flags} + +override_dh_auto_build: + dh_auto_build --parallel + +override_dh_auto_install: + dh_auto_install --destdir=debian/tmp +{test_override}""", executable=True) + + +def create_debian_rules_nobuild(deb_dir, skip_shlibdeps=False): + content = """#!/usr/bin/make -f +%: + dh $@ + +override_dh_auto_build: + # No build step needed - pre-built files only + +override_dh_auto_configure: + # No configure step needed - pre-built files only + +override_dh_auto_install: + dh_auto_install +""" + if skip_shlibdeps: + content += """ +override_dh_shlibdeps: + # Skip shlibdeps for test executables - Qt6 deps are external system packages +""" + write_deb_file(deb_dir, "rules", content, executable=True) + + +def create_debian_install(deb_dir, install_lines): + install_file = os.path.join(deb_dir, "install") + with open(install_file, "w") as f: + for line in install_lines: + f.write(line + "\n") + info(f"Created {install_file} with {len(install_lines)} entries") + + +def create_manifest(output_dir, package_name, pkg_dir): + """Write the manifest of package contents (from the DEB, or the pkg dir).""" + manifest_file = os.path.join(output_dir, "manifests", f"{package_name}.manifest") + os.makedirs(os.path.dirname(manifest_file), exist_ok=True) + deb_file = next((os.path.join(output_dir, f) for f in os.listdir(output_dir) + if f.startswith(package_name) and f.endswith(".deb")), None) + manifest_lines = [] + if deb_file and os.path.exists(deb_file): + result = subprocess.run(["dpkg-deb", "--contents", deb_file], + capture_output=True, text=True) + if result.returncode == 0: + # dpkg-deb --contents: "-rw-r--r-- root/root 1234 2024-01-01 00:00 ./path" + for line in result.stdout.strip().split("\n"): + parts = line.split() + if len(parts) >= 6: + file_path = parts[-1] + if file_path.startswith("./"): + file_path = file_path[2:] + elif file_path.startswith("/"): + file_path = file_path[1:] + if file_path: + manifest_lines.append(file_path) + elif os.path.exists(pkg_dir): + info(f"Warning: DEB file not found for {package_name}, scanning directory instead") + for root, dirs, files in os.walk(pkg_dir): + for file in files: + manifest_lines.append(os.path.relpath(os.path.join(root, file), pkg_dir)) + manifest_lines.sort() + with open(manifest_file, "w") as f: + for line in manifest_lines: + f.write(line + "\n") + info(f"Created manifest: {manifest_file} ({len(manifest_lines)} entries)") + return manifest_lines + + +def validate_package_contents(package_name, manifest_lines, allowed_patterns, forbidden_patterns=None): + """Validate package contents against the allowlist/forbidden list.""" + errors = [] + warnings = [] + for file_path in manifest_lines: + if file_path.endswith('/'): + continue + if forbidden_patterns: + for pattern in forbidden_patterns: + if fnmatch.fnmatch(file_path, pattern): + errors.append(f"FORBIDDEN file in {package_name}: {file_path} (matches {pattern})") + allowed = bool(allowed_patterns) and any(fnmatch.fnmatch(file_path, p) for p in allowed_patterns) + if not allowed and allowed_patterns: + warnings.append(f"Unexpected file in {package_name}: {file_path}") + for err in errors: + error(f"Validation error: {err}") + if errors: + return False + for warn in warnings: + warning(f"Validation warning: {warn}") + return True + + +def build_deb(pkg_name, pkg_dir, output_dir, version, build_number, + cmake_flags="", nobuild=False, skip_tests=False, skip_shlibdeps=False, + install_lines=None, description="", section="devel", depends=""): + """Build one DEB package (with or without a cmake build) and move its artifacts.""" + info(f"Building DEB package {'(no-build): ' if nobuild else ''}{pkg_name}") + deb_dir = os.path.join(pkg_dir, "debian") + os.makedirs(deb_dir) + create_debian_control(deb_dir, pkg_name, version, build_number, description, depends, section) + create_debian_changelog(deb_dir, pkg_name, version, build_number) + if nobuild: + create_debian_rules_nobuild(deb_dir, skip_shlibdeps=skip_shlibdeps) + else: + create_debian_rules(deb_dir, cmake_flags, skip_tests) + os.makedirs(os.path.join(deb_dir, "source")) + with open(os.path.join(deb_dir, "source", "format"), "w") as f: + f.write("3.0 (quilt)\n") + with open(os.path.join(deb_dir, "compat"), "w") as f: + f.write("13\n") + if install_lines: + create_debian_install(deb_dir, install_lines) + + info(f"Running debuild for {pkg_name}...") + os.chdir(pkg_dir) + env = os.environ.copy() + env["DEB_BUILD_OPTIONS"] = "parallel=$(nproc)" + run("debuild -us -uc -b", f"debuild failed for {pkg_name}", env=env) + + parent_dir = os.path.dirname(pkg_dir) + for f in os.listdir(parent_dir): + if f.endswith((".deb", ".dsc", ".changes", ".buildinfo")): + src = os.path.join(parent_dir, f) + dst = os.path.join(output_dir, f) + if os.path.exists(dst): + os.remove(dst) + shutil.move(src, output_dir) + info(f"Moved {f} to {output_dir}") + return True + + +def source_install_lines(pkg_dir, release_dirs): + lines = [] + for rel_dir in release_dirs: + src_dir = os.path.join(pkg_dir, rel_dir) + if not os.path.exists(src_dir): + continue + for root, dirs, files in os.walk(src_dir): + dirs[:] = [d for d in dirs if not d.startswith('.')] + for file in files: + if os.path.splitext(file)[1].lower() in SOURCE_EXTENSIONS or file == "CMakeLists.txt": + rel_path = os.path.relpath(os.path.join(root, file), pkg_dir) + lines.append(f"{rel_path} {os.path.join('usr/src', os.path.dirname(rel_path))}/") + return lines + + +def ctest_runtime_install_lines(pkg_dir, release_dirs): + lines = [] + for exe_path, module_path in find_test_executables(pkg_dir, release_dirs): + lines.append(f"{os.path.relpath(exe_path, pkg_dir)} usr/lib/ctest/") + info(f" Including test executable: {os.path.basename(exe_path)} from {module_path}") + for ctest_file in ("build4/CTestTestfile.cmake", "build4/DartConfiguration.tcl", "build4/Testing/TAG"): + if os.path.exists(os.path.join(pkg_dir, ctest_file)): + lines.append(f"{ctest_file} usr/lib/ctest/") + return lines + + +def test_sources_install_lines(pkg_dir, release_dirs): + return [f"{rel_path} {os.path.dirname(rel_path)}/" for rel_path in find_test_sources(pkg_dir, release_dirs)] + + +def pipeline_install_lines(pkg_dir): + lines = [] + for pipeline_file in PIPELINE_FILES: + if os.path.exists(os.path.join(pkg_dir, pipeline_file)): + lines.append(f"{pipeline_file} {os.path.join('usr/src/pipeline', os.path.dirname(pipeline_file))}/") + info(f" Added pipeline file: {pipeline_file}") + return lines + + +def build_nobuild_package(name, source_dir, build_base, output_dir, version, build_number, + description, prepare, allowed_patterns, forbidden_patterns, + file_filter=None, skip_shlibdeps=False, section="devel"): + """Stage the source tree, build a no-build DEB and validate its contents.""" + info(f"\n=== Building {name} ===") + pkg_dir = os.path.join(build_base, f"{name}-{version}") + os.makedirs(pkg_dir) + copy_src_dir_to_dest_dir(source_dir, pkg_dir, file_filter=file_filter) + build_deb(pkg_name=name, pkg_dir=pkg_dir, output_dir=output_dir, + version=version, build_number=build_number, nobuild=True, + skip_shlibdeps=skip_shlibdeps, install_lines=prepare(pkg_dir), + description=description, section=section) + manifest = create_manifest(output_dir, name, pkg_dir) + validate_package_contents(name, manifest, allowed_patterns, forbidden_patterns) + + +def build_all_debs(source_dir, output_dir, version, build_number, cmake_flags): + """Build the 5 DEB packages: build once from the source tree, then package.""" + info("========================================") + info("Building 5 DEB packages for Qt6 Release") + info("========================================") + os.makedirs(output_dir, exist_ok=True) + os.makedirs(os.path.join(output_dir, "manifests"), exist_ok=True) + build_base = os.path.join(source_dir, "deb_build_tmp") + remove_directory(build_base) + os.makedirs(build_base) + release_dirs = ["TetraIF", "Z/src", "Lts/Common"] + + # Step 1: build once from the full source tree + info("\n=== Step 1: Building from full source tree ===") + build_dir = os.path.join(source_dir, "build") + os.makedirs(build_dir, exist_ok=True) + os.chdir(build_dir) + info("Running CMake configuration...") + run(f"cmake -DCMAKE_BUILD_TYPE=Release {cmake_flags} ..", "CMake configuration failed") + info("Running build...") + run(f"make -j$(nproc)", "Build failed") + info("Build completed successfully") + + # Package 1: qt6-release-runtime (binaries and libraries, built via debuild) + info("\n=== Building qt6-release-runtime ===") + pkg1_dir = os.path.join(build_base, f"qt6-release-runtime-{version}") + os.makedirs(pkg1_dir) + # FULL source tree (needed for debian/rules cmake reference) + copy_src_dir_to_dest_dir(source_dir, pkg1_dir) + # Install from all component install paths: ALL -> bin/, lib/ ; SV/ ; WS/ + install_lines_1 = [ + "usr/bin/* usr/bin/", + "usr/lib/*.so* usr/lib/", + "usr/SV/bin/* usr/bin/", + "usr/SV/lib/*.so* usr/lib/", + "usr/WS/bin/* usr/bin/", + "usr/WS/lib/*.so* usr/lib/", + ] + build_deb(pkg_name="qt6-release-runtime", pkg_dir=pkg1_dir, output_dir=output_dir, + version=version, build_number=build_number, cmake_flags=cmake_flags, + skip_tests=True, install_lines=install_lines_1, + description="Qt6 Release Runtime - binaries and libraries from release scope S", + section="libs") + manifest_1 = create_manifest(output_dir, "qt6-release-runtime", pkg1_dir) + validate_package_contents("qt6-release-runtime", manifest_1, + allowed_patterns=["usr/bin/*", "usr/lib/*", "usr/SV/*", "usr/WS/*", "usr/share/doc/*", "debian/*"], + forbidden_patterns=["*.cpp", "*.h", "*.hpp", "tests/*", "jenkins/*", "Dockerfile*"]) + + # Package 2: qt6-release-sources (release-scope sources, DDS excluded) + build_nobuild_package( + "qt6-release-sources", source_dir, build_base, output_dir, version, build_number, + description="Qt6 Release Sources - C++ source files from release scope S", + prepare=lambda pkg_dir: source_install_lines(pkg_dir, release_dirs), + allowed_patterns=["usr/src/*", "usr/share/doc/*", "debian/*"], + forbidden_patterns=["build/*", "tests/*", "jenkins/*"], + file_filter=is_dds_file) + + # Package 3: qt6-release-ctest-runtime (test executables from release scope) + build_nobuild_package( + "qt6-release-ctest-runtime", source_dir, build_base, output_dir, version, build_number, + description="Qt6 Release CTest Runtime - executable test programs from release scope", + prepare=lambda pkg_dir: ctest_runtime_install_lines(pkg_dir, release_dirs), + allowed_patterns=["usr/lib/ctest/*", "usr/share/doc/*", "debian/*"], + forbidden_patterns=["*.cpp", "*.h", "jenkins/*"], + skip_shlibdeps=True) + + # Package 4: qt6-release-ctest-sources (test source files from release scope) + build_nobuild_package( + "qt6-release-ctest-sources", source_dir, build_base, output_dir, version, build_number, + description="Qt6 Release CTest Sources - test source files from release scope", + prepare=lambda pkg_dir: test_sources_install_lines(pkg_dir, release_dirs), + allowed_patterns=["Lts/*", "TetraIF/*", "Z/*", "usr/share/doc/*", "debian/*"], + forbidden_patterns=["build/*", "jenkins/*"]) + + # Package 5: qt6-release-build-pipeline (Docker, Jenkins, CMake, packaging scripts) + build_nobuild_package( + "qt6-release-build-pipeline", source_dir, build_base, output_dir, version, build_number, + description="Qt6 Release Build Pipeline - Docker, Jenkins, CMake, and packaging scripts", + prepare=pipeline_install_lines, + allowed_patterns=["usr/src/pipeline/*", "usr/share/doc/*", "debian/*"], + forbidden_patterns=["*.cpp", "*.h", "tests/*"]) + + # Cleanup + os.chdir(source_dir) + remove_directory(build_base) + + info("\n========================================") + info(f"DEB package build completed: {output_dir}") + info("Generated packages:") + for f in os.listdir(output_dir): + if f.endswith(".deb"): + info(f" - {f}") + info("========================================") + + +def main(): + parser = argparse.ArgumentParser(description="Build DEB packages for Legatus Qt6 Release") + parser.add_argument("-c", "--config", required=True, help="Config type (LTS)") + parser.add_argument("--project_name", required=True, help="Project name (qatar|ahtc|guez)") + parser.add_argument("--version", required=True, help="Version (Major.minor)") + parser.add_argument("--output_dir", required=True, help="Output directory for packages") + parser.add_argument("--source_dir", required=True, help="Source directory path") + parser.add_argument("--cmake_build_flags", default="", help="CMake build flags") + parser.add_argument("--build_number", default="1", help="Build number") + parser.add_argument("--release_scope", action="store_true", help="Only include release scope files") + + args = parser.parse_args() + info(f"Building {args.config} package for {args.project_name} v{args.version}") + info(f"Output directory: {args.output_dir}") + info(f"Source directory: {args.source_dir}") + info(f"CMake flags: {args.cmake_build_flags}") + info(f"Release scope: {args.release_scope}") + build_all_debs(source_dir=args.source_dir, output_dir=args.output_dir, + version=args.version, build_number=args.build_number, + cmake_flags=args.cmake_build_flags) + + +if __name__ == "__main__": + main()