One unit-test/ directory per module/target, mirroring the existing TetraIF/unit-test/ layout (one QtTest executable per module, guarded by BUILD_TESTING and the module target, each registered with ctest): Lts/Common/CodeGen codegen_unit_test Lts/Common/LtsCoordinate ltscoordinate_unit_test Lts/Common/LtsSession ltssession_unit_test Lts/DDS/Core ddscore_unit_test Lts/DDS/QosProfile ddsqos_unit_test Lts/Tools/LogViewer logviewer_unit_test Lts/Tools/SqlBrowser sqlbrowser_unit_test Z/src/NGLLogger ngllogger_unit_test Z/src/WMS_Interface wms_json_ds_unit_test Z/src/nglapplication nglcoreapplication_unit_test Z/src/nglcompression nglcompression_unit_test Z/src/nglsettings nglsettings_unit_test Z/src/ngltranslator ngltranslator_unit_test Where a module is built with hidden visibility and has no export macro (LogViewer, SqlBrowser, ngltranslator, NGLLogger, nglcompression, nglsettings), its sources are compiled directly into the test executable, the same pattern as prtcnv_unit_test. ctest baseline in the container (QT_QPA_PLATFORM=offscreen): 20/23 passed (13 new + 7 pre-existing in-scope tests), 3 Not Run as before (nglcomp_test, GrantleeParserSmoke, utils_test). README: ctest baseline table extended to the 23 registered tests and section 2.4 now documents the per-module test layout. ddsview has no unit test (GUI tool, XmlHelper needs the DDS dynamic-xtypes runtime).
qt6-migrated-legatus — Release 2 Build Guide
Build guide for the Qt6-migrated legatus tree for Release 2 (guez project). Everything runs inside a prebuilt Docker image; the host only needs Docker.
The source tree on this branch is pruned to the Release 2 build closure: only the modules the 46 scope targets compile (plus their transitive service dependencies, the codegen inputs, and the build-pipeline files) are present — out-of-scope modules have been removed.
Release 2 Scope
46 targets across 4 build groups (GROUP1, GROUP3, GROUP5, GROUP6):
| Group | Area | Count | Targets |
|---|---|---|---|
| GROUP1 | AP4 — DDS middleware | 6 | ddscore, ddsftccore, ddsfts, ddsgen, ddsQoS, ddsconnector |
| GROUP3 | AP3 — shared libraries, TetraIF, Lts/Common, tests | 36 | see breakdown below |
| GROUP5 | AP9 — viewers | 3 | ddsview, LogViewer, sqlbrowser |
| GROUP6 | AP9 — tools | 1 | ngl_codegen |
GROUP3 breakdown
| Area | Targets |
|---|---|
| NGL stack (14) | NGLLogger, nglapplication, nglcompression, nglcoreapplication, ngldb, nglguiapplication, nglsettings, nglsingleapplication, nglsinglecoreapplication, ngltools, ngltranslator, nglwidgets, qtsingleapplication, qtsinglecoreapplication |
| Services (6) | wms_json_ds, tcpserver, LtsCoordinate, LtsCoordTrans, LtsSession, LtsMultiMonitor |
| Plugin interfaces (2) | DataPluginInterface, GuiPluginInterface |
| TetraIF (5) | CoPrtCnv, LogViewLogger, LogView, PrtCnvLib, PrtCnv |
| Additional (2) | LtsLicence, MockCoordTrans |
| Tests (7) | aes_crypt_test, ngltools_qt_smoke, lts_coord_trans_unit_test, test_nglpdfwidget, coprtcnv_unit_test, logview_unit_test, prtcnv_unit_test |
The group target lists are the GROUP*_TARGETS arrays in
new_docker_build_release2.sh.
Entries at the tree root
new_docker_build_release2.sh— Release 2 build driver (compile, ctest, DEB packaging, coverage)Dockerfile— recipe of the build image (Ubuntu 26.04 + Qt 6.10.2 + deps)rpmbuild/make_rpm_ubuntu26_release2.py— Release 2 DEB package producer (5 packages)jenkins-ci-rme/— in-tree copy of the Jenkins CI flow (see its ownREADME.md)build4_<timestamp>_<pid>/— per-run build tree, created by the driver (git-ignored viabuild4_*)
Toolchain inside the image: Ubuntu 26.04, Qt 6.10.2 (/opt/Qt/6.10.2/gcc_64),
Ninja, C++17. Qt is installed via aqt install-qt (no apt Qt6 packages).
Prerequisites
- Docker available on the host.
- A prebuilt build image. The driver default is
qt6-migrated-legatus-release2; any equivalent prebuilt image can be selected with--docker-image(orDOCKER_IMAGE).
The driver resolves the pipeline root with this precedence:
exported MIGRATE_PIPELINE_ROOT → MIGRATE_PIPELINE_ROOT from the .envrc
in this directory → derived from the script location. The pipeline root is
mounted in the container at /repo (override with --docker-mount-root), so
the tree is reachable at
/repo/qt-migration-legatus/qt6-after/qt6-migrated-legatus.
All commands below are run from this directory
(qt6-migrated-legatus/).
Step 1 — Compile the Release 2 scope
./new_docker_build_release2.sh --use-prebuilt-image --docker-image <image>
What the driver does inside the container:
-
Creates a fresh build tree
build4_<timestamp>_<pid>(unique per run). -
Configures with CMake (Ninja generator):
cmake -B build4_<ts> \ -G Ninja \ -DBUILD_TESTING=ON \ -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=ON -DBUILD_GROUP3=ON -DBUILD_GROUP5=ON -DBUILD_GROUP6=ON \ .BUILD_RTI=ONis required: RTI codegen consumers (e.g.svcPdmCommon) need the generated headers, and theBUILD_RTI=OFFstubs are not part of this tree. The scope is selected by theBUILD_GROUP*flags. -
Builds the 46 scope targets with
cmake --build ... --target <t>(Ninja, per target, in group order). -
Runs ctest (see Step 2).
Output to inspect after the run (in the run's build4_* dir):
bin/— executables (apps + test binaries)lib/— shared libraries
To rebuild incrementally without a fresh tree, run ninja in an interactive
container against the existing build dir:
B=$(ls -dt build4_* | head -1)
docker run --rm -it -v "$MIGRATE_PIPELINE_ROOT":/repo <image> \
bash -c "cd /repo/qt-migration-legatus/qt6-after/qt6-migrated-legatus/${B} && ninja -j$(nproc)"
Run an executable directly
To run a built executable interactively — e.g. the GUI apps under
bin/ (LogView, LogViewer, PrtCnv) against your local display — start a
shell inside the container with X11 forwarding:
B=$(ls -dt build4_* | head -1)
docker run --rm -it \
-e DISPLAY \
-e LC_ALL=C.UTF-8 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$MIGRATE_PIPELINE_ROOT":/repo \
<image> \
bash -c "cd /repo/qt-migration-legatus/qt6-after/qt6-migrated-legatus/${B} && exec bash"
Step 2 — Run the tests
2.1 ctest (automatic)
The driver runs ctest --output-on-failure -j$(nproc) (with
QT_QPA_PLATFORM=offscreen — the container is headless) after the compile.
Expected baseline (23 registered, 20 in-scope tests passed, 3 Not Run):
| # | Test | Module | Result |
|---|---|---|---|
| 1 | coprtcnv_unit_test |
TetraIF (CoPrtCnv) | Passed |
| 2 | logview_unit_test |
TetraIF (LogView) | Passed |
| 3 | prtcnv_unit_test |
TetraIF (PrtCnv) | Passed |
| 4 | wms_json_ds_unit_test |
WMS_Interface | Passed |
| 5 | nglcoreapplication_unit_test |
nglapplication | Passed |
| 6 | nglcomp_test |
nglcompression | Not Run |
| 7 | nglcompression_unit_test |
nglcompression | Passed |
| 8 | nglsettings_unit_test |
nglsettings | Passed |
| 9 | ngltranslator_unit_test |
ngltranslator | Passed |
| 10 | NGLToolsQtSmoke |
NGLToolsQt | Passed |
| 11 | test_nglpdfwidget |
NGLPDFWidget | Passed |
| 12 | ngllogger_unit_test |
NGLLogger | Passed |
| 13 | logviewer_unit_test |
LogViewer | Passed |
| 14 | sqlbrowser_unit_test |
SqlBrowser | Passed |
| 15 | ddsqos_unit_test |
DDS/QosProfile | Passed |
| 16 | ddscore_unit_test |
DDS/Core | Passed |
| 17 | GrantleeParserSmoke |
Grantlee | Not Run |
| 18 | codegen_unit_test |
CodeGen | Passed |
| 19 | lts_coord_trans_unit_test |
LtsCoordTrans | Passed |
| 20 | ltscoordinate_unit_test |
LtsCoordinate | Passed |
| 21 | aes_crypt_test |
Common (AES crypt) | Passed |
| 22 | ltssession_unit_test |
LtsSession | Passed |
| 23 | utils_test |
Common (LtsUtils) | Not Run |
A few tests registered by in-scope CMakeLists are not part of the 46
built targets and show up as ***Not Run (nglcomp_test,
GrantleeParserSmoke, utils_test) — that is expected and the driver
continues. (Out-of-scope modules were pruned from this branch, so the
tests they registered are gone with them.)
2.2 Build the unit tests only
B=$(ls -dt build4_* | head -1)
docker run --rm -v "$MIGRATE_PIPELINE_ROOT":/repo <image> \
bash -c "cd /repo/qt-migration-legatus/qt6-after/qt6-migrated-legatus/${B} \
&& ninja coprtcnv_unit_test logview_unit_test prtcnv_unit_test \
codegen_unit_test ddscore_unit_test ddsqos_unit_test \
logviewer_unit_test sqlbrowser_unit_test \
ltscoordinate_unit_test ltssession_unit_test \
wms_json_ds_unit_test nglcompression_unit_test \
nglsettings_unit_test ngltranslator_unit_test \
ngllogger_unit_test nglcoreapplication_unit_test"
2.3 Run a test executable directly
GUI-free tests need the offscreen Qt platform in a headless container:
B=$(ls -dt build4_* | head -1)
docker run --rm -it -v "$MIGRATE_PIPELINE_ROOT":/repo <image> \
bash -c "export QT_QPA_PLATFORM=offscreen
cd /repo/qt-migration-legatus/qt6-after/qt6-migrated-legatus/${B}
./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.
2.4 Unit tests per module
Each in-scope module has its own unit-test/ directory (the original
TetraIF/unit-test/ layout, one test executable per module, each
registered with ctest):
| Test | Module | Covers |
|---|---|---|
coprtcnv_unit_test |
TetraIF | CoPrtCnv — telegram header fields, payload access, length checks, fragmentation/reassembly roundtrip, id→string helpers |
logview_unit_test |
TetraIF | LogView — IntegerCalculator (arithmetic, precedence, error codes, variables) |
prtcnv_unit_test |
TetraIF | PrtCnv — PrtCnvMember state + fragmented-telegram add/reassembly |
codegen_unit_test |
Common/CodeGen | name transforms, member/data/function/IDL wrappers, Grantlee wrapper getters (incl. JSON↔variant fallbacks), base data classes (MemberInformation/CppData toString) |
ddscore_unit_test |
DDS/Core | ddsview-independent DDS core helpers |
ddsqos_unit_test |
DDS/QosProfile | DDS_DefaultQos — default profile load, SHM/UDP/TCP participant QoS, default datawriter/datareader QoS, print() |
logviewer_unit_test |
Tools/LogViewer | LogViewTableModel (dimensions, roles, invalid index, header data, level colors, add/set data) + SortFilterModel (string/date filters, reset, date/level sort, figureFilterOut) |
sqlbrowser_unit_test |
Tools/SqlBrowser | DataModel (swap lists, role handling, tooltip timestamp, editability, set-data slots, delete lines) + AddModel (row creation, value stringification, remove/clear) |
ltscoordinate_unit_test |
Common/LtsCoordinate | constructors/geo accessors, validity checks, MGRS formatting, geo↔UTM roundtrips, UTM distances, angle degrees, elevation callback, LtsCoordVector |
ltssession_unit_test |
Common/LtsSession | session API roundtrips |
wms_json_ds_unit_test |
WMS_Interface | JSON dataset roundtrips (exercises, requirements, units) — toJson/fromJson fidelity |
nglcompression_unit_test |
nglcompression | nglcomp archive/tar filter helpers |
nglsettings_unit_test |
nglsettings | NGL::Settings + NGL::Connections (port offset, server hostname) |
ngltranslator_unit_test |
ngltranslator | Translator — catalog + translate (missing context, plural, disambiguation), JSON catalog import (valid/invalid, md5 cache, compressed), locale handling |
ngllogger_unit_test |
NGLLogger | LoggerHierarchyHelper naming/instance caching, log level ordering/enabled checks, bootstrap create/get, AlreadyCreatedError |
nglcoreapplication_unit_test |
nglapplication | LoggerHandler — createLogger naming (prefix/postfix), duplicate-root AlreadyCreatedError |
Hidden-visibility note: the tree builds with CMAKE_CXX_VISIBILITY_PRESET hidden, so classes without an export macro are not in the shared
library's dynamic symbol table. Where a module has no export macro
(LogViewer, SqlBrowser, ngltranslator, NGLLogger, nglcompression,
nglsettings), its test compiles the module source files directly into
the test executable (same pattern as prtcnv_unit_test).
ddsview has no unit test: it is a GUI tool whose XmlHelper needs the
DDS dynamic-xtypes runtime and a GUI model.
Sources are registered with ctest via each module's
unit-test/CMakeLists.txt (guarded by BUILD_TESTING and by the module
target existing).
Step 3 — Produce the Release 2 DEB packages
./new_docker_build_release2.sh --use-prebuilt-image --docker-image <image> \
--run-jenkins-builder \
--package-output-dir ./packages-out \
> /path/to/build.log 2>&1
The driver compiles the Release 2 scope (46 targets), runs ctest, then stages
the tree and runs rpmbuild/make_rpm_ubuntu26_release2.py, which builds the
scope again (Release configuration, Ninja, scoped target list) and produces
5 DEBs in ${MIGRATE_PIPELINE_ROOT}/rpms/${PROJECT}/${OS_VERSION}/
(default rpms/guez/26.04/). A copy is also placed in --package-output-dir
(default ./packages-out):
| DEB | Contents |
|---|---|
qt6-release-runtime_1.0-1_amd64.deb |
Release 2 executables + libraries, staged from the scoped build's bin/ and lib/ |
qt6-release-sources_1.0-1_amd64.deb |
Release 2 scope sources (source files + CMakeLists under the release dirs) |
qt6-release-ctest-runtime_1.0-1_amd64.deb |
Test executables under /usr/lib/ctest/ (currently empty — the producer looks for a build4/bin/ dir, which the driver's timestamped build4_<ts>/ dirs do not match) |
qt6-release-ctest-sources_1.0-1_amd64.deb |
Test sources (files with "test" in the name under release dirs) |
qt6-release-build-pipeline_1.0-1_amd64.deb |
In-tree pipeline files (the producer's PIPELINE_FILES: new_docker_build_release2.sh, Dockerfile, jenkins-ci-rme/*, rpmbuild/make_rpm_ubuntu26_release2.py, CMakeLists.txt) under /usr/src/pipeline/ |
(Version string = --lts-version + - + --build-number, default 1.0-1.)
Verify a package:
dpkg -c "${MIGRATE_PIPELINE_ROOT}/rpms/guez/26.04/qt6-release-runtime_1.0-1_amd64.deb"
dpkg -i "${MIGRATE_PIPELINE_ROOT}/rpms/guez/26.04/"*.deb # install locally, if wanted
Step 4 — 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:
jenkins-ci-rme/release-16-10-2026.sh
See jenkins-ci-rme/README.md for the pipeline details.
Step 5 — Code coverage (lcov)
./new_docker_build_release2.sh --use-prebuilt-image --docker-image <image> \
--run-coverage \
--coverage-output-dir ./coverage-out \
> /path/to/coverage.log 2>&1
The driver builds the Release 2 scope with -O0 --coverage, runs the same
ctest suite, then captures the data with lcov: the report covers the Release 2
scope (AP3, AP4, AP9, TetraIF, Z/src, Lts/Common), 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); 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_release2.sh --help prints the full option list. The main
ones:
| Option | Default | Purpose |
|---|---|---|
--group1 / --group3 / --group5 / --group6 (or --no-…) |
all on | enable/disable the build groups (AP4 / AP3 / AP9 / AP9) |
--no-ctest |
off | skip ctest |
--tests-only |
off | run ctest only, skip the build groups |
--use-prebuilt-image / --no-prebuilt-image |
off | use a prebuilt image vs. apt install in-container |
--docker-image IMAGE |
qt6-migrated-legatus-release2 |
image to use |
--cmake_build_flags FLAGS |
— | extra -D flags for the in-container configure |
--run-jenkins-builder |
off | produce the DEB packages (Step 3) |
--package-output-dir DIR |
./packages-out |
host dir that receives the DEB copy |
--lts-version VERSION |
1.0 |
package version prefix |
--project NAME |
guez |
project name for package naming |
--build-number NUM / --build-tag TAG |
1 / manual-build |
package metadata |
--run-coverage |
off | coverage build + lcov report (Step 5) |
--coverage-output-dir DIR |
./coverage-out |
where the coverage report is written |
--docker-mount-root PATH |
/repo |
container-side mount root |
Environment overrides: BUILD_GROUP1..6, RUN_CTEST, TESTS_ONLY,
USE_PREBUILT_IMAGE, DOCKER_IMAGE, CMAKE_BUILD_FLAGS,
DOCKER_MOUNT_ROOT, RUN_JENKINS_BUILDER, PACKAGE_OUTPUT_DIR,
LTS_VERSION, PROJECT, BUILD_NUMBER, BUILD_TAG, RUN_COVERAGE,
COVERAGE_OUTPUT_DIR, GUEZ_MODE, 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 creates a fresh
build4_<timestamp>_<pid>dir per run; old dirs are git-ignored scratch areas and can be deleted freely. - DEBs not on the host → the output dir must be a path that exists
on the host; check the
--package-output-dirargument and therpms/<project>/<os>/dir under the pipeline root. - Build fails with missing Qt modules → the image must be a prebuilt
Release 2 image (Qt 6.10.2 installed via
aqt install-qt); select it with--docker-image. - DDS build fails on the license →
rti_license.datmust be present at the source tree root; the driver exportsRTI_LICENSEfor in-container use.