# qt6-migrated-legatus — build and run, step by step Build guide for the Qt6-migrated legatus tree on the release **validation-scope** branch. Everything runs inside a prebuilt Docker image; the host only needs Docker. ## Scope What `new_docker_build.sh` builds and packages: | Area | Contents | |---|---| | `TetraIF/` | `CoPrtCnv`, `LogView`, `PrtCnv` (+ `unit-test/` — see Step 3) | | `Z/src/` | NGL application/core library stack (NGLLogger, nglcoreapplication, …) | | `Lts/Common/` | LTS common libraries (LtsCoordTrans, LtsLicence, LtsUtils, …) | | `Lts/Proxies/TrainingAreaBase` | `tabTelegrams` library, built as a TetraIF dependency | Entries at the tree root: - `new_docker_build.sh` — build driver (image build, compile, ctest, DEB packaging) - `Dockerfile` — recipe of the prebuilt build image (Ubuntu 26.04 + Qt 6.10.2 + deps) - `rpmbuild/make_rpm_ubuntu26.py` — in-tree DEB package producer (self-contained) - `jenkins-ci-rme/` — in-tree copy of the Jenkins CI flow (see its own `README.md`) - `build4/` — build tree, created by the driver (git-ignored scratch area) Toolchain inside the image: **Ubuntu 26.04, GCC 15.2.0, Qt 6.10.2** (`/opt/Qt/6.10.2/gcc_64`), C++17, Release build. No license file is required. ## Prerequisites 1. **Docker** available on the host. 2. The tree must be reachable inside the container at `/workspace/qt-migration-legatus/qt6-after/qt6-migrated-legatus`, i.e. mount the pipeline root on `/workspace` — this is how `new_docker_build.sh` invokes Docker, so running the driver as-is satisfies this. The driver resolves the pipeline root from its own location (override with the exported `MIGRATE_PIPELINE_ROOT` or the `.envrc` in this directory). All commands below are run **from this directory** (`qt6-migrated-legatus/`). ## Step 1 — Build the prebuilt Docker image (once) ```bash ./new_docker_build.sh --build-prebuilt-image --docker-image legatus-qt6:1.0 ``` This builds the image from the in-tree `Dockerfile` (Ubuntu 26.04 base, apt deps, Qt 6.10.2 via `aqtinstall`, KDReports from source). Re-run it only after changing the `Dockerfile` or the `APT_PACKAGES` list in `new_docker_build.sh`. To remove it later: ```bash ./new_docker_build.sh --delete-prebuilt-image --docker-image legatus-qt6:1.0 ``` ## Step 2 — Compile the release scope ```bash ./new_docker_build.sh --docker-image legatus-qt6:1.0 ``` What the driver does inside the container: 1. `rm -rf build4 && mkdir build4 && cd build4` (fresh build tree) 2. Configure with CMake: ```bash cmake .. \ -DBUILD_TESTING=ON \ -DRELEASE_BUILD=ON \ -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/opt/Qt/6.10.2/gcc_64:/usr/local ``` (`RELEASE_BUILD=ON` selects the release scope; everything else is at its CMake default.) 3. `make -j8` the release-scope targets (TetraIF modules, Z stack, Lts/Common libraries, all test executables — the list is `RELEASE_BUILD_TARGETS` in `new_docker_build.sh`). 4. Run **ctest** (see Step 3). Output to inspect after the run: - `build4/release_build.log` — full make output (the build must end with **0 warnings**) - `build4/bin/` — executables (apps + test binaries) - `build4/lib/` — shared libraries To rebuild incrementally without a fresh tree, run `make` directly in an interactive container against the existing `build4/`: ```bash docker run --rm -it -v /path/to/pipeline-root:/workspace legatus-qt6:1.0 \ bash -c 'cd /workspace/qt-migration-legatus/qt6-after/qt6-migrated-legatus/build4 \ && make -j8' ``` ## Run an executable directly To run a built executable interactively — e.g. the GUI apps under `build4/bin/` (`LogView`, `PrtCnv`) against your local display — start a shell inside the container with X11 forwarding: ```bash docker run --rm -it \ -e DISPLAY \ -e LC_ALL=C.UTF-8 \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -v /path/to/pipeline-root:/workspace \ legatus-qt6:1.0 \ bash -c 'cd /workspace/qt-migration-legatus/qt6-after/qt6-migrated-legatus/build4 && exec bash' ``` ## Step 3 — Run the tests ### 3.1 ctest (automatic) `new_docker_build.sh` already runs `ctest --test-dir build4 --output-on-failure` after the compile. Expected baseline (7 tests, all passed): | # | Test | Result | |---|---|---| | 1 | `coprtcnv_unit_test` | Passed | | 2 | `logview_unit_test` | Passed | | 3 | `prtcnv_unit_test` | Passed | | 4 | `NGLToolsQtSmoke` | Passed | | 5 | `test_nglpdfwidget` | Passed | | 6 | `lts_coord_trans_unit_test` | Passed | | 7 | `aes_crypt_test` | Passed | ### 3.2 Build the unit tests only ```bash docker run --rm -v /path/to/pipeline-root:/workspace legatus-qt6:1.0 \ bash -c 'cd /workspace/qt-migration-legatus/qt6-after/qt6-migrated-legatus/build4 \ && make -j8 coprtcnv_unit_test logview_unit_test prtcnv_unit_test' ``` ### 3.3 Run a test executable directly GUI-free tests need the offscreen Qt platform in a headless container: ```bash docker run --rm -it -v /path/to/pipeline-root:/workspace legatus-qt6:1.0 \ bash -c 'export QT_QPA_PLATFORM=offscreen cd /workspace/qt-migration-legatus/qt6-after/qt6-migrated-legatus/build4 ./bin/coprtcnv_unit_test # CoPrtCnv: telegrams, fragmentation, helpers ./bin/logview_unit_test # LogView: IntegerCalculator ./bin/prtcnv_unit_test # PrtCnv: PrtCnvMember fragmented-telegram handling ./bin/lts_coord_trans_unit_test ./bin/aes_crypt_test ./bin/test_nglpdfwidget # or everything registered: ctest --output-on-failure' ``` Each QtTest binary accepts a single slot name to run just one test, e.g. `./bin/coprtcnv_unit_test radioTelegramFragmentationRoundtrip`. ### 3.4 TetraIF unit tests (`TetraIF/unit-test/`) | Test | Module covered | Note | |---|---|---| | `coprtcnv_unit_test` | `CoPrtCnv` — central & radio telegram header fields, payload access, length checks, fragmentation/reassembly roundtrip, id→string helpers | links `CoPrtCnv` | | `logview_unit_test` | `LogView` — `IntegerCalculator` (arithmetic, precedence, error codes, variables) | compiles `LogView/IntegerCalculator.cpp` into the test (the class otherwise lives in the LogView executable) | | `prtcnv_unit_test` | `PrtCnv` — `PrtCnvMember` state + fragmented-telegram add/reassembly | compiles `PrtCnv/PrtCnvMember.cpp` into the test because `PrtCnvLib` is built with hidden visibility (its symbols are not linkable from outside) | Sources are registered with ctest via `TetraIF/unit-test/CMakeLists.txt` and packaged into the ctest DEBs (see Step 4). ## Step 4 — Produce the release DEB packages ```bash export WORKSPACE=/path/to/pipeline-root # e.g. ~/release/qt6-migration-pipeline export PROJECT=guez export OS_VERSION=26.04 ./new_docker_build.sh --docker-image legatus-qt6:1.0 \ --run-jenkins-builder \ --package-output-dir "${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/" \ > /path/to/build.log 2>&1 ``` (export the variables *before* the command — command-prefix variables are not expanded in the same command's arguments.) The driver compiles the release scope, runs ctest, then stages the tree and runs the in-tree `rpmbuild/make_rpm_ubuntu26.py`, which produces 5 DEBs in `${WORKSPACE}/rpms/${PROJECT}/${OS_VERSION}/`: | DEB | Contents | |---|---| | `qt6-release-runtime_*` | runtime libraries + the release-scope executables | | `qt6-release-sources_*` | release-scope sources | | `qt6-release-ctest-runtime_*` | test executables under `/usr/lib/ctest/` (incl. the TetraIF unit tests) | | `qt6-release-ctest-sources_*` | test sources (files with "test" in the name under the release dirs, incl. `TetraIF/unit-test/*.cpp`) | | `qt6-release-build-pipeline_*` | the in-tree pipeline files (`new_docker_build.sh`, `Dockerfile`, `CMakeLists.txt`, `rpmbuild/`, `jenkins-ci-rme/`) under `/usr/src/pipeline/` | Verify a package: ```bash dpkg -c "${WORKSPACE}/rpms/guez/26.04/qt6-release-ctest-runtime_1.0-1_amd64.deb" \ | grep unit_test dpkg -i "${WORKSPACE}/rpms/guez/26.04/"*.deb # install locally, if wanted ``` A copy of the DEBs is also placed in the container's `/workspace/packages-out` mount (same-file skip guarded by device:inode). ## Step 5 — CI / Jenkins flow (optional) The in-tree `jenkins-ci-rme/` directory contains the full Jenkins pipeline (`Jenkinsfile`, builder, publisher) adapted to this tree. For a local dry run of the same flow without Jenkins: ```bash jenkins-ci-rme/release-16-10-2026.sh ``` See `jenkins-ci-rme/README.md` for the pipeline details. ## Step 6 — Code coverage (lcov) ```bash export WORKSPACE=/path/to/pipeline-root # e.g. ~/release/qt6-migration-pipeline export PROJECT=guez export OS_VERSION=26.04 ./new_docker_build.sh --docker-image legatus-qt6:1.0 \ --run-coverage \ --coverage-output-dir "${WORKSPACE}/coverage/${PROJECT}/${OS_VERSION}/" \ > /path/to/coverage.log 2>&1 ``` (export the variables *before* the command — command-prefix variables are not expanded in the same command's arguments.) The driver builds the release scope with `-O0 --coverage`, runs the same ctest suite, then captures the data with lcov: the report covers the release scope (`TetraIF`, `Z/src`, `Lts/Common`, `Lts/Proxies/TrainingAreaBase`), and test sources are filtered out of the report, so the percentages reflect the module code. Artifacts in the coverage output directory: | File | Contents | |---|---| | `html/index.html` | browsable report (line + branch coverage per file) | | `coverage.info` | machine-readable lcov tracefile | | `coverage_summary.txt` | whole-scope totals | `--run-coverage` is mutually exclusive with `--run-jenkins-builder` (different instrumentation of the same build tree). The Jenkins pipeline runs the same step on every build (`RunCoverage` parameter, on by default) and archives the report as build artifacts under `coverage///`; the local dry run (`jenkins-ci-rme/release-16-10-2026.sh`) does the same, with `DRYRUN_COVERAGE=OFF` to skip coverage there. ## Options reference `./new_docker_build.sh --help` prints the full option list. The main ones: | Option | Default | Purpose | |---|---|---| | `--docker-image IMAGE` | `legatus-qt6:1.0` | image to use/build/delete | | `--build-prebuilt-image` | — | build the image from `./Dockerfile` and exit | | `--delete-prebuilt-image` | — | delete the image and exit | | `--run-jenkins-builder` | off | produce the DEB packages (Step 4) | | `--package-output-dir DIR` | `./packages-out` | where the DEBs are written | | `--lts-version / --project / --build-number / --build-tag` | `1.0` / `guez` / `1` / `manual-build` | package metadata | | `--run-coverage` | off | coverage build + lcov report (Step 6) | | `--coverage-output-dir DIR` | `./coverage-out` | where the coverage report is written | Environment overrides: `DOCKER_IMAGE`, `RUN_JENKINS_BUILDER`, `PACKAGE_OUTPUT_DIR`, `RUN_COVERAGE`, `COVERAGE_OUTPUT_DIR`, `PROJECT`, `LTS_VERSION`, `GUEZ_MODE`, `BUILD_NUMBER`, `BUILD_TAG`, `MIGRATE_PIPELINE_ROOT`. ## Troubleshooting - **"no Qt platform plugin could be initialized"** when running a test binary by hand → `export QT_QPA_PLATFORM=offscreen`. - **Stale build tree** → the driver always recreates `build4/`; for manual builds delete `build4/` and rerun Step 2 if CMake behaves oddly. - **DEBs not on the host** → the output dir must be a path that exists *on the host* (it is bind-mounted into the container as `/workspace/packages-out`); check the `package-output-dir` argument.