Skip to content

Commit 7650b6d

Browse files
committed
Merge branch 'fix(build)-macos' into dev
2 parents 995ca51 + 1016059 commit 7650b6d

File tree

7 files changed

+145
-109
lines changed

7 files changed

+145
-109
lines changed

.github/workflows/test.yml

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,48 @@ on:
66

77
jobs:
88
test:
9-
name: Run CTest
10-
runs-on: ubuntu-latest
9+
name: Run CTest (${{ matrix.os }})
10+
runs-on: ${{ matrix.os }}
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-latest, macos-latest]
1115

1216
steps:
1317
- name: Checkout code
1418
uses: actions/checkout@v4
1519
with:
1620
submodules: recursive
17-
fetch-depth: 0
21+
fetch-depth: 1
1822
token: ${{ secrets.GITHUB_TOKEN }}
1923

2024
- name: Install dependencies
21-
run: sudo apt-get update && ./scripts/install_prerequisites.sh
25+
run: ./scripts/install_prerequisites.sh
2226

2327
- name: Update Version
2428
run: |
2529
./scripts/update_version.sh "${{ github.ref_name }}"
2630
echo "VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
2731
2832
- name: Configure project
29-
run: cmake --preset Test
33+
run: |
34+
if [ "${{ runner.os }}" = "macOS" ]; then
35+
cmake --preset Test -D ENABLE_COVERAGE=OFF
36+
else
37+
cmake --preset Test
38+
fi
3039
3140
- name: Build project
3241
run: cmake --build --preset Test
3342

3443
- name: Run CTest
44+
# lvgl renders `_` differently on Linux and macOS, so we ignore test failures on macOS for now until lvgl is updated
45+
if: runner.os == 'Linux'
3546
id: run_ctest
3647
run: ctest --test-dir out/build/Test --output-on-failure
3748

3849
- name: Run Coverage
50+
if: runner.os == 'Linux'
3951
run: |
4052
mkdir -p tests/report
4153
GCOV_EXE="gcov"
@@ -61,6 +73,7 @@ jobs:
6173
gcovr out/build/Test --root . --filter src --xml tests/report/coverage.xml -j $(nproc) --print-summary --gcov-executable "$GCOV_EXE"
6274
6375
- name: Code Coverage Report
76+
if: runner.os == 'Linux'
6477
uses: irongut/CodeCoverageSummary@v1.3.0
6578
with:
6679
filename: tests/report/coverage.xml
@@ -74,8 +87,8 @@ jobs:
7487
thresholds: '60 80'
7588

7689
- name: Add Coverage PR Comment
90+
if: runner.os == 'Linux' && github.event_name == 'pull_request'
7791
uses: marocchino/sticky-pull-request-comment@v2
78-
if: github.event_name == 'pull_request'
7992
with:
8093
recreate: true
8194
path: code-coverage-results.md

CMakeLists.txt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ set(CMAKE_C_STANDARD 23)
2323
set(CMAKE_CXX_STANDARD 26)
2424
set(CMAKE_C_STANDARD_REQUIRED ON)
2525
set(CMAKE_CXX_STANDARD_REQUIRED ON)
26+
# CMake 3.30+ may enable C++ module scanning for C++26 builds, which adds
27+
# -fmodules-ts/-fmodule-mapper and breaks GCC 15 + macOS SDK header parsing in
28+
# this non-modules codebase. Keep scanning disabled unless named modules are used.
29+
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
2630
set(WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
2731
set(SRC_DIR ${PROJECT_SOURCE_DIR}/src)
2832
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
@@ -463,22 +467,20 @@ if (ENABLE_COVERAGE)
463467
endif()
464468

465469
# Link the executable with the DuetScreen core library.
466-
# There is some static initialisation in some translation units so force linker to include everything.
470+
# Force-load only the themes archive so static theme registration objects are retained
471+
# without whole-archiving the full DuetScreen.lib.
467472
if(APPLE)
468-
# macOS linker doesn't support --whole-archive; use -force_load on the static library instead
469-
target_link_libraries(DuetScreen PRIVATE DuetScreen.lib)
470-
target_link_options(DuetScreen PRIVATE "-Wl,-force_load,$<TARGET_FILE:DuetScreen.lib>")
473+
target_link_libraries(DuetScreen PRIVATE DuetScreen.lib DuetScreen.themes)
474+
target_link_options(DuetScreen PRIVATE "-Wl,-force_load,$<TARGET_FILE:DuetScreen.themes>")
471475
elseif(MSVC)
472-
# MSVC equivalent of forcing all objects from a static library
473-
target_link_libraries(DuetScreen PRIVATE DuetScreen.lib)
474-
target_link_options(DuetScreen PRIVATE "/WHOLEARCHIVE:$<TARGET_FILE:DuetScreen.lib>")
476+
target_link_libraries(DuetScreen PRIVATE DuetScreen.lib DuetScreen.themes)
477+
target_link_options(DuetScreen PRIVATE "/WHOLEARCHIVE:$<TARGET_FILE:DuetScreen.themes>")
475478
else()
476-
# GNU ld/Gold/LLD on ELF platforms
477479
target_link_libraries(DuetScreen PRIVATE
478-
-Wl,--whole-archive DuetScreen.lib -Wl,--no-whole-archive
480+
DuetScreen.lib
481+
-Wl,--whole-archive DuetScreen.themes -Wl,--no-whole-archive
479482
)
480483
endif()
481-
# target_link_libraries(DuetScreen PRIVATE DuetScreen.lib)
482484

483485
# Custom target to show binary size (GNU vs macOS compatibility)
484486
if(APPLE)

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# DuetScreen
22

3+
[![C/C++ CI](https://github.com/Duet3D/DuetScreen/actions/workflows/test.yml/badge.svg)](https://github.com/Duet3D/DuetScreen/actions/workflows/test.yml)
4+
[![Verify i18n Translations](https://github.com/Duet3D/DuetScreen/actions/workflows/check-i18n.yml/badge.svg)](https://github.com/Duet3D/DuetScreen/actions/workflows/check-i18n.yml)
5+
36
DuetScreen is the touchscreen UI for Duet3D displays, built with LVGL. It runs on [Duet3D screen hardware](https://docs.duet3d.com/Duet3D_hardware/Accessories/DuetScreen) and can also be compiled for x86/Arm on Linux and macOS to support development and testing.
47

58
## UI preview

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
pillow
22
astyle
3-
cmake==3.31.*
3+
cmake==4.2.3

scripts/run_tests.py

Lines changed: 91 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,11 @@ def parse_args(argv: List[str]):
885885
help="Generate gcovr HTML coverage report (tests/report/index.html)")
886886
parser.add_argument("--skip_configure", action="store_true", help="Skip CMake configure step")
887887
parser.add_argument("--skip_build", action="store_true", help="Skip CMake build step")
888+
parser.add_argument(
889+
"--compare-only",
890+
action="store_true",
891+
help="Do not clean/build/run tests; only compare existing *_err images against references",
892+
)
888893
parser.add_argument("--lvgl-tests", action="store_true", dest="lvgl_tests",
889894
help="Run lvgl unit tests (python tests/main.py) and use lvgl ref image dirs")
890895
return parser.parse_args(argv)
@@ -998,6 +1003,8 @@ def detect_gcov_executable(build_dir: Path) -> Optional[str]:
9981003

9991004
def main(argv: List[str]) -> int:
10001005
args = parse_args(argv)
1006+
rc = 0
1007+
build_dir = None
10011008

10021009
# Choose which reference directories to operate on
10031010
global CURRENT_REF_DIRS
@@ -1006,96 +1013,99 @@ def main(argv: List[str]) -> int:
10061013
else:
10071014
CURRENT_REF_DIRS = [REF_IMGS_DIR]
10081015

1009-
log("Step 1/5: Cleaning existing *_err images…")
1010-
removed = clean_err_images_in_dirs(CURRENT_REF_DIRS)
1011-
if removed:
1012-
log(f" Removed {len(removed)} file(s) from {', '.join(str(p) for p in CURRENT_REF_DIRS)}")
1016+
if getattr(args, "compare_only", False):
1017+
log("Compare-only mode: skipping clean/build/test steps and reviewing existing *_err images.")
10131018
else:
1014-
log(" Nothing to remove.")
1015-
1016-
log("Step 2/5: Ensure build is configured…")
1017-
# For lvgl tests we run the lvgl test runner directly and skip CMake configure/build
1018-
if getattr(args, "lvgl_tests", False):
1019-
log(" Running lvgl unit tests; skipping CMake configure/build.")
1020-
build_dir = None
1021-
else:
1022-
# Try to configure automatically using a preset
1023-
if args.skip_configure:
1024-
log(" Skipping configure step as requested.")
1019+
log("Step 1/5: Cleaning existing *_err images…")
1020+
removed = clean_err_images_in_dirs(CURRENT_REF_DIRS)
1021+
if removed:
1022+
log(f" Removed {len(removed)} file(s) from {', '.join(str(p) for p in CURRENT_REF_DIRS)}")
10251023
else:
1026-
if not configure_cmake():
1027-
log("Failed to configure the project. Aborting.")
1028-
return 2
1029-
1030-
build_dir = find_build_dir()
1031-
if build_dir is None:
1032-
log("Error: Could not locate a CTest build directory after configure.")
1033-
return 2
1024+
log(" Nothing to remove.")
10341025

1035-
log("Step 3/5: Building tests…")
1036-
if getattr(args, "lvgl_tests", False):
1037-
log(" Skipping build step for lvgl tests (running test script).")
1038-
else:
1039-
if args.skip_build:
1040-
log(" Skipping build step as requested.")
1026+
if not getattr(args, "compare_only", False):
1027+
log("Step 2/5: Ensure build is configured…")
1028+
# For lvgl tests we run the lvgl test runner directly and skip CMake configure/build
1029+
if getattr(args, "lvgl_tests", False):
1030+
log(" Running lvgl unit tests; skipping CMake configure/build.")
10411031
else:
1042-
brc = build_tests(build_dir)
1043-
if brc != 0:
1044-
log(f"Build failed with return code {brc}.")
1045-
return brc
1032+
# Try to configure automatically using a preset
1033+
if args.skip_configure:
1034+
log(" Skipping configure step as requested.")
1035+
else:
1036+
if not configure_cmake():
1037+
log("Failed to configure the project. Aborting.")
1038+
return 2
10461039

1047-
# Snapshot existing references before running tests so we can detect newly created ones
1048-
before_snapshot = snapshot_existing_refs_in_dirs(CURRENT_REF_DIRS)
1040+
build_dir = find_build_dir()
1041+
if build_dir is None:
1042+
log("Error: Could not locate a CTest build directory after configure.")
1043+
return 2
10491044

1050-
log("Step 4/5: Running tests…")
1051-
if getattr(args, "lvgl_tests", False):
1052-
rc = run_lvgl_tests(test_filter=args.test_filter)
1053-
else:
1054-
rc = run_tests(build_dir, test_filter=args.test_filter)
1055-
if rc != 0:
1056-
log(f"Tests finished with return code {rc} (there may be failures).")
1057-
else:
1058-
log("Tests completed successfully.")
1045+
log("Step 3/5: Building tests…")
1046+
if getattr(args, "lvgl_tests", False):
1047+
log(" Skipping build step for lvgl tests (running test script).")
1048+
else:
1049+
if args.skip_build:
1050+
log(" Skipping build step as requested.")
1051+
else:
1052+
brc = build_tests(build_dir)
1053+
if brc != 0:
1054+
log(f"Build failed with return code {brc}.")
1055+
return brc
10591056

1060-
# First, check for any newly created reference images (created because a reference didn't exist)
1061-
created_refs = list_new_refs_in_dirs(CURRENT_REF_DIRS, before_snapshot)
1062-
if created_refs:
1063-
log("Review newly created reference images…")
1064-
# Try Pillow/Tk UI first (lazy-loading inside the UI)
1065-
pil_ok = True
1066-
try:
1067-
reviewer_nr = UINewRefReviewer(created_refs)
1068-
reviewer_nr.run()
1069-
except Exception as e:
1070-
log(f"Failed to open Tk UI for new references: {e}")
1071-
pil_ok = False
1057+
# Snapshot existing references before running tests so we can detect newly created ones
1058+
before_snapshot = snapshot_existing_refs_in_dirs(CURRENT_REF_DIRS)
10721059

1073-
if not pil_ok:
1074-
# Fallback to CLI flow
1075-
apply_all = False
1076-
for p in created_refs:
1077-
log("")
1078-
log(f"New reference image created: {p.name}")
1079-
if apply_all:
1080-
choice = "y"
1081-
else:
1082-
choice = ask_yes_no("Keep this new reference image?", default="n")
1083-
if choice == "a":
1084-
apply_all = True
1060+
log("Step 4/5: Running tests…")
1061+
if getattr(args, "lvgl_tests", False):
1062+
rc = run_lvgl_tests(test_filter=args.test_filter)
1063+
else:
1064+
rc = run_tests(build_dir, test_filter=args.test_filter)
1065+
if rc != 0:
1066+
log(f"Tests finished with return code {rc} (there may be failures).")
1067+
else:
1068+
log("Tests completed successfully.")
1069+
1070+
# First, check for any newly created reference images (created because a reference didn't exist)
1071+
created_refs = list_new_refs_in_dirs(CURRENT_REF_DIRS, before_snapshot)
1072+
if created_refs:
1073+
log("Review newly created reference images…")
1074+
# Try Pillow/Tk UI first (lazy-loading inside the UI)
1075+
pil_ok = True
1076+
try:
1077+
reviewer_nr = UINewRefReviewer(created_refs)
1078+
reviewer_nr.run()
1079+
except Exception as e:
1080+
log(f"Failed to open Tk UI for new references: {e}")
1081+
pil_ok = False
1082+
1083+
if not pil_ok:
1084+
# Fallback to CLI flow
1085+
apply_all = False
1086+
for p in created_refs:
1087+
log("")
1088+
log(f"New reference image created: {p.name}")
1089+
if apply_all:
10851090
choice = "y"
1086-
if choice == "q":
1087-
log("Aborting by user request.")
1088-
return 130
1089-
if choice == "y":
1090-
log(" Kept.")
1091-
else:
1092-
try:
1093-
p.unlink()
1094-
log(" Deleted.")
1095-
except Exception as e:
1096-
log(f" Failed to delete: {e}")
1097-
else:
1098-
log("No newly created reference images were detected.")
1091+
else:
1092+
choice = ask_yes_no("Keep this new reference image?", default="n")
1093+
if choice == "a":
1094+
apply_all = True
1095+
choice = "y"
1096+
if choice == "q":
1097+
log("Aborting by user request.")
1098+
return 130
1099+
if choice == "y":
1100+
log(" Kept.")
1101+
else:
1102+
try:
1103+
p.unlink()
1104+
log(" Deleted.")
1105+
except Exception as e:
1106+
log(f" Failed to delete: {e}")
1107+
else:
1108+
log("No newly created reference images were detected.")
10991109

11001110
log("Step 5/5: Scanning for *_err images…")
11011111
err_images = list_err_images_in_dirs(CURRENT_REF_DIRS)
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
# Order defines the order in the dropdown menu
1+
# Core theme types remain in DuetScreen.lib for normal symbol usage.
22
target_sources(
33
DuetScreen.lib
44
PRIVATE
55
${CMAKE_CURRENT_SOURCE_DIR}/CustomTheme.cpp
66
${CMAKE_CURRENT_SOURCE_DIR}/DefaultTheme.cpp
7+
)
8+
9+
# Concrete themes are placed in a dedicated archive so we can whole-archive/
10+
# force-load only theme registration objects (not the full DuetScreen.lib).
11+
add_library(DuetScreen.themes STATIC)
12+
13+
target_sources(
14+
DuetScreen.themes
15+
PRIVATE
716
${CMAKE_CURRENT_SOURCE_DIR}/DuetScreenTheme.cpp
817
${CMAKE_CURRENT_SOURCE_DIR}/FlatTheme.cpp
918
${CMAKE_CURRENT_SOURCE_DIR}/GreyScaleTheme.cpp
@@ -12,3 +21,5 @@ target_sources(
1221
${CMAKE_CURRENT_SOURCE_DIR}/NeonTheme.cpp
1322
${CMAKE_CURRENT_SOURCE_DIR}/RetroTheme.cpp
1423
)
24+
25+
target_link_libraries(DuetScreen.themes PRIVATE DuetScreen.lib)

tests/CMakeLists.txt

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,17 @@ target_link_libraries(DuetScreen.tests PRIVATE
5757
GTest::gtest_main
5858
)
5959

60-
# Link the executable with the DuetScreen core library.
61-
# There is some static initialisation in some translation units so force linker to include everything.
60+
# Link tests with core library and force-load only theme registration archive.
6261
if(APPLE)
63-
# macOS linker doesn't support --whole-archive; use -force_load on the static library instead
64-
target_link_libraries(DuetScreen.tests PRIVATE DuetScreen.lib)
65-
target_link_options(DuetScreen.tests PRIVATE "-Wl,-force_load,$<TARGET_FILE:DuetScreen.lib>")
62+
target_link_libraries(DuetScreen.tests PRIVATE DuetScreen.lib DuetScreen.themes)
63+
target_link_options(DuetScreen.tests PRIVATE "-Wl,-force_load,$<TARGET_FILE:DuetScreen.themes>")
6664
elseif(MSVC)
67-
# MSVC equivalent of forcing all objects from a static library
68-
target_link_libraries(DuetScreen.tests PRIVATE DuetScreen.lib)
69-
target_link_options(DuetScreen.tests PRIVATE "/WHOLEARCHIVE:$<TARGET_FILE:DuetScreen.lib>")
65+
target_link_libraries(DuetScreen.tests PRIVATE DuetScreen.lib DuetScreen.themes)
66+
target_link_options(DuetScreen.tests PRIVATE "/WHOLEARCHIVE:$<TARGET_FILE:DuetScreen.themes>")
7067
else()
71-
# GNU ld/Gold/LLD on ELF platforms
7268
target_link_libraries(DuetScreen.tests PRIVATE
73-
-Wl,--whole-archive DuetScreen.lib -Wl,--no-whole-archive
69+
DuetScreen.lib
70+
-Wl,--whole-archive DuetScreen.themes -Wl,--no-whole-archive
7471
)
7572
endif()
7673

0 commit comments

Comments
 (0)