#!/usr/bin/env bash set -euo pipefail # ----------------------------------------------------------------------------- # Build helper for qt-migrated-legatus inside Docker - RELEASE 2 VERSION # # Release 2 Scope: # - AP3: Shared libraries (LtsUtils, Widgets, Session, Krypto, PluginManager) # - AP4: DDS middleware (Core, DDSConnector, Routing, QosProfile, RTI layer) # - AP9: Tools and translations (DdsView, LogViewer, SqlBrowser, generators, lupdate) # # This script is derived from new_docker_build.sh but focused on Release 2 targets only. # ----------------------------------------------------------------------------- usage() { cat <<'EOF' Usage: ./new_docker_build_release2.sh [options] Build groups (Release 2 scope): --group1 / --no-group1 Enable/disable DDS core libraries build (AP4) --group3 / --no-group3 Enable/disable Lts services build (AP3) --group5 / --no-group5 Enable/disable Lts/Viewer module targets and tests (AP9) --group6 / --no-group6 Enable/disable LTS Tools - generators and lupdate (AP9) Test execution: --no-ctest Skip ctest execution --tests-only Run only ctest, skip all make build groups Prebuilt Docker image usage: --use-prebuilt-image Use a Docker image with dependencies already installed --no-prebuilt-image Use base image and run apt-get install inside the container --docker-image IMAGE Docker image name to use/build/delete --cmake_build_flags FLAGS Extra CMake flags passed to the in-container configure (space-separated -D flags) Package production options (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 with -O0 --coverage, run ctest and collect lcov --coverage-output-dir DIR Directory to mount for coverage output (default: ./coverage-out) Docker mount configuration: --docker-mount-root PATH Container-side mount root path (default: /repo) Changes where host directories are mounted in container Environment variable overrides: BUILD_GROUP1=ON|OFF DDS core libraries (AP4) BUILD_GROUP3=ON|OFF Lts services (AP3) BUILD_GROUP5=ON|OFF Viewer module (AP9) BUILD_GROUP6=ON|OFF Tools/generators (AP9) RUN_CTEST=ON|OFF TESTS_ONLY=ON|OFF USE_PREBUILT_IMAGE=ON|OFF RUN_JENKINS_BUILDER=ON|OFF PACKAGE_OUTPUT_DIR= LTS_VERSION= PROJECT= 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_release2.sh --group1 --group3 --group5 --group6 BUILD_GROUP1=ON BUILD_GROUP3=ON ./new_docker_build_release2.sh ./new_docker_build_release2.sh --use-prebuilt-image --no-ctest ./new_docker_build_release2.sh --group3 --run-jenkins-builder \ --package-output-dir "${WORKSPACE}/rpms/guez/release2/" ./new_docker_build_release2.sh --group3 --run-coverage \ --coverage-output-dir "${WORKSPACE}/coverage/guez/release2/" EOF exit 1 } log_info() { echo "[INFO] $*" } die() { echo "[ERROR] $*" >&2 exit 1 } # Default values BUILD_GROUP1="${BUILD_GROUP1:-ON}" # AP4: DDS BUILD_GROUP3="${BUILD_GROUP3:-ON}" # AP3: Lts services BUILD_GROUP5="${BUILD_GROUP5:-ON}" # AP9: Viewer BUILD_GROUP6="${BUILD_GROUP6:-ON}" # AP9: Tools/generators RUN_CTEST="${RUN_CTEST:-ON}" TESTS_ONLY="${TESTS_ONLY:-OFF}" USE_PREBUILT_IMAGE="${USE_PREBUILT_IMAGE:-OFF}" DOCKER_IMAGE="${DOCKER_IMAGE:-qt6-migrated-legatus-release2}" CMAKE_BUILD_FLAGS="${CMAKE_BUILD_FLAGS:-}" DOCKER_MOUNT_ROOT="${DOCKER_MOUNT_ROOT:-/repo}" export DOCKER_MOUNT_ROOT # Jenkins builder options for DEB package production RUN_JENKINS_BUILDER="${RUN_JENKINS_BUILDER:-OFF}" PACKAGE_OUTPUT_DIR="${PACKAGE_OUTPUT_DIR:-./packages-out}" LTS_VERSION="${LTS_VERSION:-1.0}" PROJECT="${PROJECT:-guez}" BUILD_NUMBER="${BUILD_NUMBER:-1}" BUILD_TAG="${BUILD_TAG:-manual-build}" # Code coverage (lcov) RUN_COVERAGE="${RUN_COVERAGE:-OFF}" COVERAGE_OUTPUT_DIR="${COVERAGE_OUTPUT_DIR:-./coverage-out}" # Export variables for Docker container export BUILD_GROUP1 BUILD_GROUP3 BUILD_GROUP5 BUILD_GROUP6 RUN_CTEST TESTS_ONLY USE_PREBUILT_IMAGE export RUN_JENKINS_BUILDER PACKAGE_OUTPUT_DIR LTS_VERSION PROJECT BUILD_NUMBER BUILD_TAG export RUN_COVERAGE 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) 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 export MIGRATE_PIPELINE_ROOT # Parse arguments while [[ $# -gt 0 ]]; do case "$1" in --group1) BUILD_GROUP1="ON" shift ;; --no-group1) BUILD_GROUP1="OFF" shift ;; --group3) BUILD_GROUP3="ON" shift ;; --no-group3) BUILD_GROUP3="OFF" shift ;; --group5) BUILD_GROUP5="ON" shift ;; --no-group5) BUILD_GROUP5="OFF" shift ;; --group6) BUILD_GROUP6="ON" shift ;; --no-group6) BUILD_GROUP6="OFF" shift ;; --no-ctest) RUN_CTEST="OFF" shift ;; --tests-only) TESTS_ONLY="ON" shift ;; --use-prebuilt-image) USE_PREBUILT_IMAGE="ON" shift ;; --no-prebuilt-image) USE_PREBUILT_IMAGE="OFF" shift ;; --docker-image) DOCKER_IMAGE="$2" shift 2 ;; --cmake_build_flags) CMAKE_BUILD_FLAGS="$2" shift 2 ;; --docker-mount-root) DOCKER_MOUNT_ROOT="$2" shift 2 ;; --run-jenkins-builder) RUN_JENKINS_BUILDER="ON" shift ;; --package-output-dir) PACKAGE_OUTPUT_DIR="$2" shift 2 ;; --lts-version) LTS_VERSION="$2" shift 2 ;; --project) PROJECT="$2" shift 2 ;; --build-number) BUILD_NUMBER="$2" shift 2 ;; --build-tag) BUILD_TAG="$2" shift 2 ;; --run-coverage) RUN_COVERAGE="ON" shift ;; --coverage-output-dir) COVERAGE_OUTPUT_DIR="$2" shift 2 ;; -h|--help) usage ;; *) die "Unknown option: $1" ;; esac done # Validate mutually exclusive options if [[ "${RUN_JENKINS_BUILDER}" == "ON" ]] && [[ "${RUN_COVERAGE}" == "ON" ]]; then die "Error: --run-jenkins-builder and --run-coverage are mutually exclusive" fi require_command() { if ! command -v "$1" &> /dev/null; then die "Required command not found: $1" fi } require_command docker # Validate environment if [[ ! -f ".envrc" ]]; then die ".envrc not found in current directory. Please create .envrc with required environment variables (e.g., MIGRATE_PIPELINE_ROOT)" fi set -a # shellcheck disable=SC1091 source ".envrc" set +a if [[ -z "${MIGRATE_PIPELINE_ROOT:-}" ]]; then die "MIGRATE_PIPELINE_ROOT is not set in .envrc" fi if [[ ! -d "${MIGRATE_PIPELINE_ROOT}" ]]; then die "MIGRATE_PIPELINE_ROOT directory does not exist: ${MIGRATE_PIPELINE_ROOT}" fi # Validate rti_license.dat exists (required for DDS builds) if [[ "${BUILD_GROUP1}" == "ON" ]] && [[ ! -f "${MIGRATE_PIPELINE_ROOT}/rti_license.dat" ]]; then die "rti_license.dat not found in MIGRATE_PIPELINE_ROOT. Required for DDS builds." fi log_info "=== Release 2 Build Configuration ===" log_info "BUILD_GROUP1 (DDS/AP4): ${BUILD_GROUP1}" log_info "BUILD_GROUP3 (Lts/AP3): ${BUILD_GROUP3}" log_info "BUILD_GROUP5 (Viewer/AP9): ${BUILD_GROUP5}" log_info "BUILD_GROUP6 (Tools/AP9): ${BUILD_GROUP6}" log_info "RUN_CTEST: ${RUN_CTEST}" log_info "TESTS_ONLY: ${TESTS_ONLY}" log_info "USE_PREBUILT_IMAGE: ${USE_PREBUILT_IMAGE}" log_info "DOCKER_IMAGE: ${DOCKER_IMAGE}" log_info "DOCKER_MOUNT_ROOT: ${DOCKER_MOUNT_ROOT}" log_info "CMAKE_BUILD_FLAGS: ${CMAKE_BUILD_FLAGS:-none}" log_info "======================================" # Define Release 2 targets for each group # AP4: DDS middleware GROUP1_TARGETS=( ddscore svcCoreClient ddsftccore ddsfts ddsgen ddsQoS ddsconnector ) # AP3: LtsUtils, Widgets, Session, Krypto, PluginManager # These are the core Lts service libraries from GROUP3 GROUP3_TARGETS=( NGLLogger nglapplication nglcompression nglcoreapplication ngldb nglguiapplication nglsettings nglsingleapplication nglsinglecoreapplication ngltools ngltranslator nglwidgets qtsingleapplication qtsinglecoreapplication wms_json_ds tcpserver # Core libraries required by services LtsCoordinate LtsCoordTrans svcPdmCommon svcPdmClient svcPdmServer svcProjectDataManagement pdmTests svcAesClient svcAesCommon svcAesServer svcAsmClient svcAsmCommon svcAsmServer svcAssetManagement svcAudioVideoManagement svcAvmClient svcAvmCommon svcAvmServer svcBldClient svcBldCommon svcCentralEffectSimulation svcCesClient svcCesCommon svcCesDDSCommon svcCesGeoPairingImpl svcCesServer svcCodClient svcCodCommon svcCodServer svcEemClient svcEemCommon svcEemServer svcEffectShowClient svcEscClient svcEscCommon svcEscServer svcEsmClient svcEsmCommon svcEsmServer svcEvlClient svcEvlCommon svcEvlServer svcExerciseEventManagement svcExmClient svcExmCommon svcExmServer svcFileManagement svcFilterManagement svcFlmClient svcFlmCommon svcFlmServer svcFltClient svcFltCommon svcFltServer svcGimClient svcGimCommon svcGimServer svcGmpServer svcGuezIedManagement svcIsmClient svcIsmCommon svcIsmServer svcMapDummy svcMdmClient svcMdmCommon svcMdmServer svcMessaging svcMeteoDataManagement svcMrkClient svcMrkCommon svcMrkServer svcMsgClient svcMsgCommon svcMsgServer svcNomClient svcNomCommon svcNomServer svcNotificationManagement svcNmeClient svcNmeCommon svcNmeServer svcNmeaManagement svcObdClient svcObdCommon svcObdServer svcOcBaseDownload svcOcSituationClient svcOcSituationServer svcOperatorManagement svcOpmClient svcOpmCommon svcOpmServer svcOreDummy svcOscClient svcOscCommon svcOscServer svcOssClient svcOssCommon svcOssServer svcPedDummy svcPpmClient svcPpmCommon svcPpmServer svcPreClient svcPreCommon svcPreServer svcPwsCommon svcRecCommon svcRecordingManagement svcRepClient svcRepCommon svcRepCommonExecutable svcRepServer svcReportAnalyzer svcReportGenerator svcScmClient svcScmCommon svcScmServer svcScrDummy svcServiceManagement svcSituationReplay svcSmoDummy svcSomClient svcSomCommon svcSomServer svcSoundManagement svcSrmCommon svcSrpClient svcSrpCommon svcSrpServer svcTgtClient svcTgtCommon svcTgtServer svcVideoOverlayManagement svcWarehouseGuez svcWhgClient svcWhgCommon svcWhgServer asmTests avmFtTests avmTests avmVcaTests cesTests eemTests evlTests exmDtUnitTest exmTests flmTests gmpTests ismTests mrkTests mvsTests nomTests opmTests ppmTests preTests repTests scmDtUnitTest srpFtTests tgtTests whgTests ) # AP9: Viewer module (DdsView, LogViewer, SqlBrowser) GROUP5_TARGETS=( ddsView logViewer sqlBrowser ) # AP9: Tools and generators (DaoGenerator, lupdate toolchain) GROUP6_TARGETS=( DaoGenerator lupdate ) # Build prebuilt image if requested if [[ "${USE_PREBUILT_IMAGE}" == "ON" ]]; then if ! docker image inspect "${DOCKER_IMAGE}" &> /dev/null; then log_info "Prebuilt image ${DOCKER_IMAGE} not found. Building it now..." # Create temporary directory for Dockerfile tmp_dir="$(mktemp -d)" trap 'rm -rf "${tmp_dir}"' EXIT cat > "${tmp_dir}/Dockerfile" <<'DOCKERFILE' FROM ubuntu:26.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -qq && \ apt-get install -y -qq \ build-essential \ cmake \ ninja-build \ git \ python3-pip \ libboost-dev \ libkf6texttemplate-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 \ libgmock-dev \ libpq-dev \ postgresql-server-dev-all \ libvlc-dev \ vlc-plugin-base \ libssl-dev \ libxml2-dev \ libxerces-c-dev \ && \ rm -rf /var/lib/apt/lists/* # Install Qt6 packages via aqtinstall RUN pip3 install --break-system-packages --quiet aqtinstall && \ aqt install-qt linux desktop 6.10.2 linux_gcc_64 \ -m qtmultimedia qtserialport qtimageformats qtwebsockets qtwebengine \ qtpdf qtwebview qt5compat qtwebchannel qtpositioning qtlocation qtsensors \ --outputdir /opt/Qt && \ rm -rf /root/.cache/Aqt # Install KDReports from source RUN git clone --depth 1 https://github.com/KDAB/KDReports.git /tmp/KDReports && \ cd /tmp/KDReports && \ mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_PREFIX_PATH=/opt/Qt .. && \ make -j$(nproc) && \ make install && \ cd / && rm -rf /tmp/KDReports # Create symlink for KDReports headers RUN ln -sfn KDReports-qt6 /usr/local/include/KDReports CMD ["/bin/bash"] DOCKERFILE docker build -t "${DOCKER_IMAGE}" "${tmp_dir}" log_info "Prebuilt image created: ${DOCKER_IMAGE}" else log_info "Prebuilt image already exists: ${DOCKER_IMAGE}" fi fi # Prepare Docker run command DOCKER_RUN_ARGS=( --rm -e MIGRATE_PIPELINE_ROOT -e DOCKER_MOUNT_ROOT="${DOCKER_MOUNT_ROOT}" -e BUILD_GROUP1="${BUILD_GROUP1}" -e BUILD_GROUP3="${BUILD_GROUP3}" -e BUILD_GROUP5="${BUILD_GROUP5}" -e BUILD_GROUP6="${BUILD_GROUP6}" -e RUN_CTEST="${RUN_CTEST}" -e TESTS_ONLY="${TESTS_ONLY}" -e RUN_JENKINS_BUILDER="${RUN_JENKINS_BUILDER}" -e PACKAGE_OUTPUT_DIR="${PACKAGE_OUTPUT_DIR}" -e LTS_VERSION="${LTS_VERSION}" -e PROJECT="${PROJECT}" -e BUILD_NUMBER="${BUILD_NUMBER}" -e BUILD_TAG="${BUILD_TAG}" -e RUN_COVERAGE="${RUN_COVERAGE}" -e COVERAGE_OUTPUT_DIR="${COVERAGE_OUTPUT_DIR}" -v "${MIGRATE_PIPELINE_ROOT}:${DOCKER_MOUNT_ROOT}" -w "${DOCKER_MOUNT_ROOT}/qt-migration-legatus/qt6-after/qt6-migrated-legatus" ) if [[ "${USE_PREBUILT_IMAGE}" == "ON" ]]; then DOCKER_RUN_ARGS+=("${DOCKER_IMAGE}") else DOCKER_RUN_ARGS+=("ubuntu:26.04") fi # When not using prebuilt image, we need to install dependencies first if [[ "${USE_PREBUILT_IMAGE}" == "OFF" ]]; then # Prepend dependency installation to the build command INSTALL_DEPS=' set -e export DEBIAN_FRONTEND=noninteractive apt-get update -qq && \ apt-get install -y -qq \ build-essential \ cmake \ ninja-build \ git \ python3-pip \ libboost-dev \ libkf6texttemplate-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 \ libgmock-dev \ libpq-dev \ postgresql-server-dev-all \ libvlc-dev \ vlc-plugin-base \ libssl-dev \ libxml2-dev \ libxerces-c-dev \ && \ rm -rf /var/lib/apt/lists/* # Install Qt6 packages via aqtinstall pip3 install --break-system-packages --quiet aqtinstall && \ aqt install-qt linux desktop 6.10.2 linux_gcc_64 \ -m qtmultimedia qtserialport qtimageformats qtwebsockets qtwebengine \ qtpdf qtwebview qt5compat qtwebchannel qtpositioning qtlocation qtsensors \ --outputdir /opt/Qt && \ rm -rf /root/.cache/Aqt # Install KDReports from source git clone --depth 1 https://github.com/KDAB/KDReports.git /tmp/KDReports && \ cd /tmp/KDReports && \ mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_PREFIX_PATH=/opt/Qt .. && \ make -j$(nproc) && \ make install && \ cd / && rm -rf /tmp/KDReports # Create symlink for KDReports headers ln -sfn KDReports-qt6 /usr/local/include/KDReports export PATH="/opt/Qt/6.10.2/gcc_64/bin:$PATH" export CMAKE_PREFIX_PATH="/opt/Qt/6.10.2/gcc_64" log_info() { echo "[INFO] $*" } die() { echo "[ERROR] $*" >&2 exit 1 } log_info "Dependencies installed successfully" # Return to project directory cd "${DOCKER_MOUNT_ROOT}/qt-migration-legatus/qt6-after/qt6-migrated-legatus" ' else INSTALL_DEPS='' fi # Build command DOCKER_RUN_ARGS+=( /bin/bash -c ' set -e log_info() { echo "[INFO] $*" } die() { echo "[ERROR] $*" >&2 exit 1 } '"${INSTALL_DEPS}"' # Configure CMake log_info "Configuring CMake..." # Use unique build directory to avoid Docker mount caching issues BUILD_DIR="build4_$(date +%Y%m%d_%H%M%S)_$$" log_info "Using build directory: ${BUILD_DIR}" rm -rf "${BUILD_DIR}" mkdir -p "${BUILD_DIR}" cmake -B "${BUILD_DIR}" \ -G Ninja \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_PREFIX_PATH="/opt/Qt/6.10.2/gcc_64" \ -DCMAKE_CXX_FLAGS="-DRTI_LINUX -DRTI_UNIX" \ -DCMAKE_C_FLAGS="-DRTI_LINUX -DRTI_UNIX" \ -DBUILD_RTI=ON \ -DBUILD_GROUP1="${BUILD_GROUP1}" \ -DBUILD_GROUP3="${BUILD_GROUP3}" \ -DBUILD_GROUP5="${BUILD_GROUP5}" \ -DBUILD_GROUP6="${BUILD_GROUP6}" \ -DRUN_JENKINS_BUILDER="${RUN_JENKINS_BUILDER}" \ -DPACKAGE_OUTPUT_DIR="${PACKAGE_OUTPUT_DIR}" \ -DLTS_VERSION="${LTS_VERSION}" \ -DPROJECT="${PROJECT}" \ -DBUILD_NUMBER="${BUILD_NUMBER}" \ -DBUILD_TAG="${BUILD_TAG}" \ -DRUN_COVERAGE="${RUN_COVERAGE}" \ -DCOVERAGE_OUTPUT_DIR="${COVERAGE_OUTPUT_DIR}" \ '"${CMAKE_BUILD_FLAGS}"' \ . # Build targets log_info "Building Release 2 targets..." GROUP1_TARGETS=(ddscore svcCoreClient ddsftccore ddsfts ddsgen ddsQoS ddsconnector) GROUP3_TARGETS=(NGLLogger nglapplication nglcompression nglcoreapplication ngldb nglguiapplication nglsettings nglsingleapplication nglsinglecoreapplication ngltools ngltranslator nglwidgets qtsingleapplication qtsinglecoreapplication wms_json_ds tcpserver LtsCoordinate LtsCoordTrans pdmTests eemTests flmTests srpFtTests) GROUP5_TARGETS=(ddsView logViewer sqlBrowser) GROUP6_TARGETS=(DaoGenerator lupdate) build_group() { local group_name="$1" local build_flag="$2" shift 2 local targets=("$@") if [[ "${build_flag}" != "ON" ]]; then log_info "Skipping ${group_name} (disabled)" return fi log_info "Building ${group_name}..." for target in "${targets[@]}"; do log_info " Building: ${target}" cmake --build "${BUILD_DIR}" --target "${target}" -j$(nproc) || die "Failed to build ${target}" done } build_group "GROUP1 (DDS/AP4)" "${BUILD_GROUP1}" "${GROUP1_TARGETS[@]}" build_group "GROUP3 (Lts/AP3)" "${BUILD_GROUP3}" "${GROUP3_TARGETS[@]}" build_group "GROUP5 (Viewer/AP9)" "${BUILD_GROUP5}" "${GROUP5_TARGETS[@]}" build_group "GROUP6 (Tools/AP9)" "${BUILD_GROUP6}" "${GROUP6_TARGETS[@]}" # Run tests if enabled if [[ "${RUN_CTEST}" == "ON" ]] && [[ "${TESTS_ONLY}" != "ON" ]]; then log_info "Running ctest..." cd "${BUILD_DIR}" ctest --output-on-failure -j$(nproc) || log_info "Some tests failed (continuing)" cd .. fi log_info "Release 2 build completed successfully!" ' ) log_info "Starting Docker container..." docker run "${DOCKER_RUN_ARGS[@]}" log_info "Build script completed."