forked from prestonbrown/helixscreen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
1090 lines (993 loc) · 47.8 KB
/
Copy pathMakefile
File metadata and controls
1090 lines (993 loc) · 47.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright (c) 2025 Preston Brown <pbrown@brown-house.net>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# HelixScreen UI Prototype - Main Makefile
# LVGL 9 + SDL2 simulator with modular build system
#
# ⚠️ CRITICAL: Always use 'make' - NEVER invoke gcc/g++ directly!
# The build system handles:
# - Dependency management (libhv, lvgl, SDL2)
# - Platform detection (macOS vs Linux)
# - Parallel builds (auto-fixes 'make -j' to use correct core count)
# - Patch application for multi-display support
#
# Common commands:
# make -j # Parallel incremental build (auto-detects cores)
# make build # Clean build from scratch
# make help # Show all available targets
#
# See: DEVELOPMENT.md for complete build instructions
# Use bash for all shell commands (needed for [[ ]] and read -n)
SHELL := /bin/bash
# Delete a target if its recipe fails. Without this, a failed link can leave a
# truncated/0-byte output (e.g. helix-tests) on disk with a newer mtime than its
# prerequisites — the next `make` then treats the broken artifact as up-to-date
# and never rebuilds it, so the only recovery is a manual `rm`. Deleting the
# half-written target on failure makes the next invocation retry cleanly.
.DELETE_ON_ERROR:
# Build verbosity control
# Use 'make V=1' to see full compiler commands
V ?= 0
ifeq ($(V),0)
Q := @
ECHO := @echo
else
Q :=
ECHO := @true
endif
# Color output - auto-detect terminal capabilities
# Disable with NO_COLOR=1 or when running in non-terminal environments
ifndef NO_COLOR
# Auto-detect if terminal supports colors
# Check if Make is running interactively (stdin is a TTY) AND stderr is a TTY
# This disables colors when output is piped (stdout redirect) or in non-interactive contexts
# Note: We can't check stdout directly from $(shell) because it's redirected to capture output
TERM_SUPPORTS_COLOR := $(shell \
if [ -t 0 ] && [ -t 2 ] && [ -n "$$TERM" ] && [ "$$TERM" != "dumb" ]; then \
echo 1; \
else \
echo 0; \
fi)
ifeq ($(TERM_SUPPORTS_COLOR),1)
BOLD := $(shell printf '\033[1m')
RED := $(shell printf '\033[31m')
GREEN := $(shell printf '\033[32m')
YELLOW := $(shell printf '\033[33m')
BLUE := $(shell printf '\033[34m')
MAGENTA := $(shell printf '\033[35m')
CYAN := $(shell printf '\033[36m')
RESET := $(shell printf '\033[0m')
else
# Terminal doesn't support colors - disable
BOLD :=
RED :=
GREEN :=
YELLOW :=
BLUE :=
MAGENTA :=
CYAN :=
RESET :=
endif
else
# NO_COLOR=1 explicitly set
BOLD :=
RED :=
GREEN :=
YELLOW :=
BLUE :=
MAGENTA :=
CYAN :=
RESET :=
endif
# Compilers - auto-detect or use environment variables
# Priority: environment variables > clang (if working) > gcc
#
# On some Linux distros (e.g., Arch with GCC 15), Clang may fail to find
# GCC's libstdc++ headers, causing "#include_next <stdlib.h>" errors.
# We test-compile to detect this and auto-fallback to GCC.
# Helper to test if a C++ compiler can use the standard library
# Returns "ok" on success, empty on failure
# We test #include <cstdlib> because that's where clang+libstdc++ breaks on some systems
HASH := \#
define test_cxx_stdlib
$(shell printf '$(HASH)include <cstdlib>\n' | $(1) -x c++ -std=c++17 -fsyntax-only - 2>/dev/null && echo ok)
endef
# Yocto mode: bitbake passes CC/CXX/AR/LD/STRIP/RANLIB on the make command line
# with full paths and machine flags baked in. Skip host-toolchain autodetect
# and ccache wrapping — bitbake manages its own caching upstream.
ifneq ($(PLATFORM_TARGET),yocto)
ifeq ($(origin CC),default)
ifneq ($(shell command -v clang 2>/dev/null),)
CC := clang
else ifneq ($(shell command -v gcc 2>/dev/null),)
CC := gcc
else
$(error No C compiler found. Install clang or gcc)
endif
endif
ifeq ($(origin CXX),default)
# Try clang++ first
ifneq ($(shell command -v clang++ 2>/dev/null),)
# Test if clang++ can actually compile C++ with stdlib
ifeq ($(call test_cxx_stdlib,clang++),ok)
CXX := clang++
else ifneq ($(shell command -v g++ 2>/dev/null),)
# Clang has stdlib issues, fall back to g++
CXX := g++
CC := gcc
$(info Note: clang++ has stdlib issues on this system, using g++ instead)
else
# No g++ available, try clang++ anyway and let it fail with a clear error
CXX := clang++
endif
else ifneq ($(shell command -v g++ 2>/dev/null),)
CXX := g++
else
$(error No C++ compiler found. Install clang++ or g++)
endif
endif
# Set RANLIB if not defined (needed for wpa_supplicant build on Linux)
ifeq ($(origin RANLIB),undefined)
RANLIB := ranlib
endif
# Ccache integration - auto-detect and use if available (10x faster rebuilds)
CCACHE := $(shell command -v ccache 2>/dev/null)
ifneq ($(CCACHE),)
# Avoid double-wrapping when CC/CXX already include ccache (common in CI env).
ifneq ($(notdir $(firstword $(CC))),ccache)
CC := ccache $(CC)
endif
ifneq ($(notdir $(firstword $(CXX))),ccache)
CXX := ccache $(CXX)
endif
endif
endif # PLATFORM_TARGET != yocto
# Dependency generation flags for proper header tracking
# -MMD: Generate .d dependency files for user headers (not system headers)
# -MP: Add phony targets for headers (prevents errors when headers are deleted)
# Note: -MF path is computed in the pattern rules to get the correct output path
DEPFLAGS = -MMD -MP
# Optimization level: -O2 by default, override with OPT=0 or OPT=1 for faster builds
# 'make dev' sets OPT=0 automatically (~2x faster compilation)
OPT ?= 2
# Project source flags - warnings enabled, strict mode optional
# Use WERROR=1 to treat warnings as errors (for CI or `make strict`)
# -D_FORTIFY_SOURCE=2: compile-time + runtime bounds checking for memcpy/sprintf/etc
# -fstack-protector-strong: stack canaries on functions with local arrays/alloca
# Note: _FORTIFY_SOURCE requires optimization (-O1+), so disable it at -O0
#
# Yocto mode: bitbake's CFLAGS/CXXFLAGS already include --sysroot, -march,
# optimization, and security flags. Append our project flags with += instead
# of clobbering. -Wno-psabi silences the ARM parameter-passing ABI-change
# notes that appear on cortex-a7 hard-float cross builds.
ifeq ($(PLATFORM_TARGET),yocto)
CFLAGS += -std=c11 -Wall -Wextra -D_GNU_SOURCE -Wno-psabi
CXXFLAGS += -std=c++17 -Wall -Wextra -Wno-psabi
else
CFLAGS := -std=c11 -Wall -Wextra -O$(OPT) -g -D_GNU_SOURCE -fno-omit-frame-pointer -fstack-protector-strong
CXXFLAGS := -std=c++17 -Wall -Wextra -O$(OPT) -g -fno-omit-frame-pointer -fstack-protector-strong
ifneq ($(OPT),0)
CFLAGS += -D_FORTIFY_SOURCE=2
CXXFLAGS += -D_FORTIFY_SOURCE=2
endif
endif
# Version information (read from VERSION.txt file)
# Format: MAJOR.MINOR.PATCH following Semantic Versioning 2.0.0
# NOTE: Must use .txt extension to avoid shadowing C++20 <version> header on macOS
# (macOS filesystem is case-insensitive, so VERSION would match <version>)
HELIX_VERSION := $(shell cat VERSION.txt 2>/dev/null || echo "0.0.0")
HELIX_VERSION_MAJOR := $(word 1,$(subst ., ,$(HELIX_VERSION)))
HELIX_VERSION_MINOR := $(word 2,$(subst ., ,$(HELIX_VERSION)))
HELIX_VERSION_PATCH := $(word 3,$(subst ., ,$(HELIX_VERSION)))
HELIX_GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
# Installer script filename (single source of truth for Makefile packaging + C++ extraction)
INSTALLER_FILENAME := install.sh
# Add version defines to compiler flags
VERSION_DEFINES := -DHELIX_VERSION=\"$(HELIX_VERSION)\" \
-DHELIX_VERSION_MAJOR=$(HELIX_VERSION_MAJOR) \
-DHELIX_VERSION_MINOR=$(HELIX_VERSION_MINOR) \
-DHELIX_VERSION_PATCH=$(HELIX_VERSION_PATCH) \
-DHELIX_GIT_HASH=\"$(HELIX_GIT_HASH)\" \
-DINSTALLER_FILENAME=\"$(INSTALLER_FILENAME)\"
CFLAGS += $(VERSION_DEFINES)
CXXFLAGS += $(VERSION_DEFINES)
# Strict mode: -Werror plus additional useful warnings
ifeq ($(WERROR),1)
CFLAGS += -Werror -Wconversion -Wshadow -Wno-error=deprecated-declarations
CXXFLAGS += -Werror -Wconversion -Wshadow -Wno-error=deprecated-declarations
endif
# Address Sanitizer support for debugging heap corruption
# Usage: make SANITIZE=address (native), or `make pi-asan-docker` (cross).
# We only DEFINE the flag variable here; mk/cross.mk applies it after
# TARGET_CFLAGS and SUBMODULE_CFLAGS have been merged so LVGL, helix-xml,
# and other in-tree subprojects get instrumented too. _FORTIFY_SOURCE is
# disabled in this mode because it conflicts with ASAN's libc wrappers.
ifeq ($(SANITIZE),address)
SANITIZE_FLAGS := -fsanitize=address -fno-omit-frame-pointer
CFLAGS := $(filter-out -D_FORTIFY_SOURCE=2,$(CFLAGS))
CXXFLAGS := $(filter-out -D_FORTIFY_SOURCE=2,$(CXXFLAGS))
endif
# Submodule flags - suppress warnings from third-party code we don't control
# Uses -w to completely silence warnings (cleaner build output)
# Note: No DEPFLAGS for submodules - we don't track their internal dependencies
SUBMODULE_CFLAGS := -std=c11 -O2 -g -D_GNU_SOURCE -w
SUBMODULE_CXXFLAGS := -std=c++17 -O2 -g -w
# Platform detection (needed early for conditional compilation)
UNAME_S := $(shell uname -s)
# Cross-compilation support (must come early to override CC/CXX)
# Use TARGET=pi or TARGET=ad5m for cross-compilation
include mk/cross.mk
# Font tier lists (must come after cross.mk sets FONT_TIERS, before FONT_SRCS)
include mk/fonts.mk
# Remote build support (build on fast Linux host, retrieve binaries)
include mk/remote.mk
# Directories
SRC_DIR := src
INC_DIR := include
# BUILD_DIR, BIN_DIR, OBJ_DIR may be set by cross.mk for cross-compilation
# Only set defaults if not already defined
BUILD_DIR ?= build
BIN_DIR ?= $(BUILD_DIR)/bin
OBJ_DIR ?= $(BUILD_DIR)/obj
# LVGL
LVGL_DIR := lib/lvgl
# LVGL config discovery. Defined here (not further down) so it can travel inside
# $(LVGL_INC): every flag set that compiles LVGL-dependent code then reaches
# lv_conf.h via -I. (project root) + -DLV_CONF_INCLUDE_SIMPLE, instead of LVGL's
# fragile '#include "../../lv_conf.h"' fallback. That fallback only resolves by
# accident in a normal checkout and BREAKS when lib/lvgl is a symlink (git
# worktrees — see scripts/setup-worktree.sh), which silently broke every
# cross-compile from a worktree (splash + display-backend + watchdog sub-builds).
# Carrying it in LVGL_INC means new LVGL sub-builds inherit correct discovery
# automatically rather than each having to remember to append $(LV_CONF).
LV_CONF := -DLV_CONF_INCLUDE_SIMPLE
# Use -isystem to suppress warnings from third-party headers in strict mode
LVGL_INC := -isystem $(LVGL_DIR) -isystem $(LVGL_DIR)/src -I. $(LV_CONF)
# Add GLAD include path for desktop OpenGL ES (SDL) builds only.
# DRM+EGL builds use system EGL/GLES2 headers directly — GLAD's stub headers
# shadow them (define include guards but emit no types when LV_USE_OPENGLES=0).
ifeq ($(ENABLE_OPENGLES),yes)
ifneq ($(DISPLAY_BACKEND),drm)
LVGL_INC += -isystem $(LVGL_DIR)/src/drivers/opengles/glad/include
endif
endif
# Exclude XML and expat sources from LVGL — those are now in lib/helix-xml
LVGL_SRCS := $(filter-out $(wildcard $(LVGL_DIR)/src/xml/*.c $(LVGL_DIR)/src/xml/parsers/*.c $(LVGL_DIR)/src/libs/expat/*.c),$(shell find $(LVGL_DIR)/src -name "*.c" 2>/dev/null))
LVGL_OBJS := $(patsubst $(LVGL_DIR)/%.c,$(OBJ_DIR)/lvgl/%.o,$(LVGL_SRCS))
# When OpenGL ES is enabled, the shader asset file uses C++11 raw string literals
# (R"(...)") that can't compile as C. Filter only that file from C sources and compile
# it as C++ separately. All other opengles sources stay as C.
ifeq ($(ENABLE_OPENGLES),yes)
LVGL_OPENGLES_CXX_SRCS := $(filter $(LVGL_DIR)/src/drivers/opengles/assets/%,$(LVGL_SRCS))
LVGL_SRCS := $(filter-out $(LVGL_DIR)/src/drivers/opengles/assets/%,$(LVGL_SRCS))
LVGL_OBJS := $(patsubst $(LVGL_DIR)/%.c,$(OBJ_DIR)/lvgl/%.o,$(LVGL_SRCS))
LVGL_OPENGLES_OBJS := $(patsubst $(LVGL_DIR)/%.c,$(OBJ_DIR)/lvgl/%.o,$(LVGL_OPENGLES_CXX_SRCS))
else
LVGL_OPENGLES_OBJS :=
endif
# Helix XML engine (extracted from LVGL, with our patches baked in)
HELIX_XML_DIR := lib/helix-xml
HELIX_XML_SRCS := $(wildcard $(HELIX_XML_DIR)/src/xml/*.c $(HELIX_XML_DIR)/src/xml/parsers/*.c $(HELIX_XML_DIR)/src/libs/expat/*.c)
HELIX_XML_OBJS := $(patsubst $(HELIX_XML_DIR)/%.c,$(OBJ_DIR)/helix-xml/%.o,$(HELIX_XML_SRCS))
# lv_markdown (LVGL markdown widget)
LV_MARKDOWN_DIR := lib/lv_markdown
LV_MARKDOWN_INC := -isystem $(LV_MARKDOWN_DIR)/src -isystem $(LV_MARKDOWN_DIR)/deps/md4c
LV_MARKDOWN_SRCS := $(wildcard $(LV_MARKDOWN_DIR)/src/*.c) $(LV_MARKDOWN_DIR)/deps/md4c/md4c.c
LV_MARKDOWN_OBJS := $(patsubst $(LV_MARKDOWN_DIR)/%.c,$(OBJ_DIR)/lv_markdown/%.o,$(LV_MARKDOWN_SRCS))
# quirc (QR code decoder library)
QUIRC_DIR := lib/quirc
QUIRC_INC := -isystem $(QUIRC_DIR)/lib
QUIRC_SRCS := $(wildcard $(QUIRC_DIR)/lib/*.c)
QUIRC_OBJS := $(patsubst $(QUIRC_DIR)/%.c,$(OBJ_DIR)/quirc/%.o,$(QUIRC_SRCS))
# ThorVG sources (.cpp files for SVG support)
THORVG_SRCS := $(shell find $(LVGL_DIR)/src/libs/thorvg -name "*.cpp" 2>/dev/null)
THORVG_OBJS := $(patsubst $(LVGL_DIR)/%.cpp,$(OBJ_DIR)/lvgl/%.o,$(THORVG_SRCS))
# cpp-terminal (modern TUI library)
CPP_TERMINAL_DIR := lib/cpp-terminal
# Use -isystem to suppress warnings from third-party headers in strict mode
CPP_TERMINAL_INC := -isystem $(CPP_TERMINAL_DIR)
CPP_TERMINAL_SRCS := $(wildcard $(CPP_TERMINAL_DIR)/cpp-terminal/*.cpp) \
$(wildcard $(CPP_TERMINAL_DIR)/cpp-terminal/private/*.cpp)
CPP_TERMINAL_OBJS := $(patsubst $(CPP_TERMINAL_DIR)/%.cpp,$(OBJ_DIR)/cpp-terminal/%.o,$(CPP_TERMINAL_SRCS))
# lv_markdown (markdown viewer widget + md4c parser)
LV_MARKDOWN_DIR := lib/lv_markdown
LV_MARKDOWN_INC := -isystem $(LV_MARKDOWN_DIR)/src -isystem $(LV_MARKDOWN_DIR)/deps/md4c
LV_MARKDOWN_SRCS := $(wildcard $(LV_MARKDOWN_DIR)/src/*.c) $(LV_MARKDOWN_DIR)/deps/md4c/md4c.c
LV_MARKDOWN_OBJS := $(patsubst $(LV_MARKDOWN_DIR)/%.c,$(OBJ_DIR)/lv_markdown/%.o,$(LV_MARKDOWN_SRCS))
# LVGL Demos (separate target)
LVGL_DEMO_SRCS := $(shell find $(LVGL_DIR)/demos -name "*.c" 2>/dev/null)
LVGL_DEMO_OBJS := $(patsubst $(LVGL_DIR)/%.c,$(OBJ_DIR)/lvgl/%.o,$(LVGL_DEMO_SRCS))
# 3D G-code Rendering default (must be set before APP_SRCS filtering below)
# EGL/GLES is only available on Linux — disable on macOS
ifeq ($(UNAME_S),Darwin)
ENABLE_GLES_3D ?= no
else
ENABLE_GLES_3D ?= yes
endif
# Screensaver default (enabled on desktop/Pi, disabled on constrained targets)
ENABLE_SCREENSAVER ?= yes
# Application C sources
APP_C_SRCS := $(wildcard $(SRC_DIR)/*.c)
APP_C_OBJS := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(APP_C_SRCS))
# Application C++ sources (exclude test binaries, splash binary, and lvgl-demo)
# Include all subdirectories: ui/, api/, rendering/, printer/, print/, system/, application/
APP_SRCS := $(filter-out $(SRC_DIR)/test_dynamic_cards.cpp $(SRC_DIR)/test_responsive_theme.cpp $(SRC_DIR)/test_gcode_geometry.cpp $(SRC_DIR)/test_gcode_analysis.cpp $(SRC_DIR)/test_sdf_reconstruction.cpp $(SRC_DIR)/test_sparse_grid.cpp $(SRC_DIR)/test_partial_extraction.cpp $(SRC_DIR)/test_render_comparison.cpp $(SRC_DIR)/test_network_tester.cpp $(SRC_DIR)/helix_splash.cpp $(SRC_DIR)/helix_watchdog.cpp $(SRC_DIR)/lvgl-demo/main.cpp,$(wildcard $(SRC_DIR)/*.cpp) $(wildcard $(SRC_DIR)/*/*.cpp) $(wildcard $(SRC_DIR)/*/*/*.cpp))
# Exclude src/tools/ — standalone build tools have their own rules in tools.mk
APP_SRCS := $(filter-out $(wildcard $(SRC_DIR)/tools/*.cpp),$(APP_SRCS))
# Exclude src/bluetooth/ — built as separate shared library (mk/bluetooth.mk)
APP_SRCS := $(filter-out $(wildcard $(SRC_DIR)/bluetooth/*.cpp),$(APP_SRCS))
# Exclude GLES renderer when not enabled
ifneq ($(ENABLE_GLES_3D),yes)
APP_SRCS := $(filter-out $(SRC_DIR)/rendering/gcode_gles_renderer.cpp,$(APP_SRCS))
endif
# Exclude screensaver when not enabled
ifneq ($(ENABLE_SCREENSAVER),yes)
APP_SRCS := $(filter-out $(SRC_DIR)/ui/ui_screensaver.cpp,$(APP_SRCS))
APP_SRCS := $(filter-out $(SRC_DIR)/ui/screensaver_manager.cpp,$(APP_SRCS))
APP_SRCS := $(filter-out $(SRC_DIR)/ui/screensaver_starfield.cpp,$(APP_SRCS))
APP_SRCS := $(filter-out $(SRC_DIR)/ui/screensaver_pipes.cpp,$(APP_SRCS))
endif
# Mock backends (enabled by default, disable with ENABLE_MOCKS=no for production)
ENABLE_MOCKS ?= yes
ifneq ($(ENABLE_MOCKS),yes)
APP_SRCS := $(filter-out $(wildcard $(SRC_DIR)/api/*_mock*.cpp),$(APP_SRCS))
APP_SRCS := $(filter-out $(SRC_DIR)/printer/ams_backend_mock.cpp,$(APP_SRCS))
APP_SRCS := $(filter-out $(SRC_DIR)/api/moonraker_api_mock.cpp,$(APP_SRCS))
endif
APP_OBJS := $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(APP_SRCS))
# libhv dns_resolv.c — needed by safe_resolve.h on statically-linked ARM/MIPS
# targets (avoids glibc __check_pf SIGSEGV). Only compiled for cross builds.
# Uses APP_ prefix to avoid collision with DNS_RESOLV_OBJ in tests.mk.
ifneq ($(CROSS_COMPILE),)
APP_DNS_RESOLV_OBJ := $(OBJ_DIR)/dns_resolv.o
else
APP_DNS_RESOLV_OBJ :=
endif
# Objective-C++ sources (macOS only - .mm files)
# Only include on macOS, exclude on Linux to avoid linking errors
ifeq ($(UNAME_S),Darwin)
OBJCPP_SRCS := $(wildcard $(SRC_DIR)/*.mm)
OBJCPP_OBJS := $(patsubst $(SRC_DIR)/%.mm,$(OBJ_DIR)/%.o,$(OBJCPP_SRCS))
else
OBJCPP_SRCS :=
OBJCPP_OBJS :=
endif
# Font sources — assembled per-platform from mk/fonts.mk tier lists
# FONT_TIERS is set per-platform in mk/cross.mk (default: all)
FONT_SRCS := $(TIER_FONT_SRCS)
FONT_OBJS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(FONT_SRCS))
# Material Design Icons - REMOVED
# Icons are now font-based using MDI font glyphs (mdi_icons_*.c)
# See include/ui_icon_codepoints.h for icon mapping
# SDL2 - Only needed for native desktop builds (not embedded targets)
# Cross-compilation targets use framebuffer/DRM instead
ifeq ($(ENABLE_SDL),yes)
SDL2_SYSTEM_AVAILABLE := $(shell command -v sdl2-config 2>/dev/null)
ifneq ($(SDL2_SYSTEM_AVAILABLE),)
# System SDL2 found - use it
SDL2_INC := $(shell sdl2-config --cflags)
SDL2_LIBS := $(shell sdl2-config --libs)
SDL2_LIB :=
else
# No system SDL2 - build from submodule
SDL2_DIR := lib/sdl2
SDL2_BUILD_DIR := $(SDL2_DIR)/build
SDL2_LIB := $(SDL2_BUILD_DIR)/libSDL2.a
SDL2_INC := -I$(SDL2_DIR)/include -I$(SDL2_BUILD_DIR)/include -I$(SDL2_BUILD_DIR)/include-config-release
SDL2_LIBS := $(SDL2_LIB)
endif
else
# Embedded target - no SDL2
SDL2_INC :=
SDL2_LIBS :=
SDL2_LIB :=
endif
# libhv (WebSocket client for Moonraker) - Use system version if available, otherwise build from submodule
# LIBHV_DIR always points to the submodule — needed for internal headers (e.g. base/dns_resolv.h)
LIBHV_DIR := lib/libhv
LIBHV_PKG_CONFIG := $(shell pkg-config --exists libhv 2>/dev/null && echo "yes")
ifeq ($(YOCTO_BUILD),yes)
# Yocto: libhv comes from DEPENDS (sysroot include/link paths baked into CC).
# Submodule path supplies internal headers (base/dns_resolv.h, cpputil/ifconfig.h)
# not shipped by the libhv public install.
LIBHV_INC := -isystem $(LIBHV_DIR)/include -isystem $(LIBHV_DIR)/cpputil -isystem $(LIBHV_DIR)
LIBHV_LIBS := -lhv
LIBHV_LIB :=
else ifeq ($(LIBHV_PKG_CONFIG),yes)
# System libhv found via pkg-config (pkg-config already returns system include paths)
# Also add submodule path for internal headers used by tests
LIBHV_INC := $(shell pkg-config --cflags libhv) -isystem $(LIBHV_DIR)
LIBHV_LIBS := $(shell pkg-config --libs libhv)
LIBHV_LIB :=
else
# No system libhv - build from submodule to $(BUILD_DIR)/lib/ for architecture isolation
# This allows concurrent native/pi/ad5m builds without conflicts
# Use -isystem to suppress warnings from third-party headers in strict mode
LIBHV_INC := -isystem $(LIBHV_DIR)/include -isystem $(LIBHV_DIR)/cpputil -isystem $(LIBHV_DIR)
LIBHV_LIB := $(BUILD_DIR)/lib/libhv.a
LIBHV_LIBS := $(LIBHV_LIB)
endif
# libhv generates include/hv headers during libhv-build. Track json.hpp so a
# stale archive cannot be reused when generated headers are missing.
ifneq ($(LIBHV_LIB),)
LIBHV_JSON_HEADER := $(LIBHV_DIR)/include/hv/json.hpp
else
LIBHV_JSON_HEADER :=
endif
# spdlog (logging library) - Use system version if available, otherwise use submodule
# Check for actual header file to avoid $(dir) path issues with directory paths
SPDLOG_SYSTEM_HEADER_PATHS := /usr/include/spdlog/spdlog.h /usr/local/include/spdlog/spdlog.h /opt/homebrew/include/spdlog/spdlog.h
SPDLOG_SYSTEM_HEADER := $(firstword $(wildcard $(SPDLOG_SYSTEM_HEADER_PATHS)))
ifneq ($(SPDLOG_SYSTEM_HEADER),)
# System spdlog found
# /usr/include and /usr/local/include are in compiler defaults - no -isystem needed
# /opt/homebrew/include is NOT in macOS compiler defaults - needs explicit -isystem
ifeq ($(SPDLOG_SYSTEM_HEADER),/opt/homebrew/include/spdlog/spdlog.h)
SPDLOG_INC := -isystem /opt/homebrew/include
else
# /usr/include or /usr/local/include - compiler already searches these
SPDLOG_INC :=
endif
else
# No system spdlog - use submodule
SPDLOG_DIR := lib/spdlog
# Use -isystem to suppress warnings from third-party headers in strict mode
SPDLOG_INC := -isystem $(SPDLOG_DIR)/include
endif
# fmt (formatting library required by header-only spdlog)
# For cross-compilation, we can't use host pkg-config - must detect target library directly
ifeq ($(YOCTO_BUILD),yes)
# Yocto: libfmt from DEPENDS, sysroot-aware linker resolves -lfmt.
FMT_LIBS := -lfmt
else ifneq ($(CROSS_COMPILE),)
# Cross-compiling: check if target fmt library exists (installed via libfmt-dev:arm64 etc.)
FMT_TARGET_LIB := $(shell ls /usr/lib/$(TARGET_TRIPLE)/libfmt.so 2>/dev/null || ls /usr/lib/$(TARGET_TRIPLE)/libfmt.a 2>/dev/null)
ifneq ($(FMT_TARGET_LIB),)
ifeq ($(PLATFORM_TARGET),pi)
# Pi: skip external fmt — spdlog bundles its own, and linking against
# system libfmt causes soname mismatches across Debian versions
# (Bullseye ships libfmt.so.7, Bookworm ships libfmt.so.9)
FMT_LIBS :=
else
FMT_LIBS := -lfmt
endif
else
# No target fmt - build will fail if spdlog requires it
FMT_LIBS :=
endif
else
# Native build: use pkg-config normally
FMT_PKG_CONFIG := $(shell pkg-config --exists fmt 2>/dev/null && echo "yes")
ifeq ($(FMT_PKG_CONFIG),yes)
FMT_LIBS := $(shell pkg-config --libs fmt)
else
FMT_LIBS :=
endif
endif
# libsystemd (for systemd journal logging on Linux)
# Only check on Linux (native or cross-compile), not macOS
SYSTEMD_LIBS :=
SYSTEMD_CXXFLAGS :=
ifneq ($(UNAME_S),Darwin)
ifneq ($(CROSS_COMPILE),)
# Cross-compiling: check if target libsystemd exists
SYSTEMD_TARGET_LIB := $(shell ls /usr/lib/$(TARGET_TRIPLE)/libsystemd.so 2>/dev/null || ls /usr/lib/$(TARGET_TRIPLE)/libsystemd.a 2>/dev/null)
ifneq ($(SYSTEMD_TARGET_LIB),)
SYSTEMD_CXXFLAGS := -DHELIX_HAS_SYSTEMD
SYSTEMD_LIBS := -lsystemd
endif
else
# Native Linux build: use pkg-config
SYSTEMD_PKG_CONFIG := $(shell pkg-config --exists libsystemd 2>/dev/null && echo "yes")
ifeq ($(SYSTEMD_PKG_CONFIG),yes)
SYSTEMD_CXXFLAGS := -DHELIX_HAS_SYSTEMD
SYSTEMD_LIBS := $(shell pkg-config --libs libsystemd)
endif
endif
endif
# 3D G-code Rendering
# ENABLE_GLES_3D: GPU-accelerated OpenGL ES 2.0 via EGL (Pi + desktop Linux)
# (default set earlier, before APP_SRCS filtering)
ifeq ($(ENABLE_GLES_3D),yes)
GLES3D_DEFINES := -DENABLE_GLES_3D
else
GLES3D_DEFINES :=
endif
# Flying Toasters screensaver (desktop/Pi only)
ifeq ($(ENABLE_SCREENSAVER),yes)
SCREENSAVER_DEFINES := -DHELIX_ENABLE_SCREENSAVER
else
SCREENSAVER_DEFINES :=
endif
# Mock backend defines
ifeq ($(ENABLE_MOCKS),yes)
MOCK_DEFINES := -DHELIX_ENABLE_MOCKS
else
MOCK_DEFINES :=
endif
# wpa_supplicant (WiFi control via wpa_ctrl interface)
WPA_DIR := lib/wpa_supplicant
# Output to $(BUILD_DIR)/lib/ for architecture isolation (native/pi/ad5m)
WPA_CLIENT_LIB := $(BUILD_DIR)/lib/libwpa_client.a
# Use -isystem to suppress warnings from third-party headers in strict mode
WPA_INC := -isystem $(WPA_DIR)/src/common -isystem $(WPA_DIR)/src/utils
# Precompiled header for LVGL (30-50% faster clean builds)
# Only supported by gcc and clang (not MSVC)
PCH_HEADER := $(INC_DIR)/lvgl_pch.h
PCH := $(BUILD_DIR)/lvgl_pch.h.gch
PCH_FLAGS := -include $(PCH_HEADER)
# Include paths
# Project includes use -I (warnings enabled), library includes use -isystem (warnings suppressed)
# This allows `make strict` to catch issues in project code while ignoring third-party header warnings
# stb_image headers (used for thumbnail processing)
STB_INC := -isystem lib/stb
INCLUDES := -I. -I$(INC_DIR) -Isrc/generated -I$(BUILD_DIR)/generated -isystem lib -isystem lib/glm $(LVGL_INC) $(LIBHV_INC) $(SPDLOG_INC) $(STB_INC) $(LV_MARKDOWN_INC) $(QUIRC_INC) $(WPA_INC) $(SDL2_INC)
# Common linker flags (used by both macOS and Linux)
LDFLAGS_COMMON := $(SDL2_LIBS) $(LIBHV_LIBS) $(FMT_LIBS) -lz -lm -lpthread
# Platform-specific configuration
# Cross-compilation targets (pi, ad5m, k1) are Linux-based embedded systems
ifeq ($(YOCTO_BUILD),yes)
# Yocto/bitbake: CC already carries sysroot + mcpu/mfpu flags. All dependencies
# come from DEPENDS in the .bb recipe (libhv, openssl, spdlog, fmt, alsa-lib,
# libusb1, wpa-supplicant, libnl). Do not build any submodule library here —
# link against system libraries resolved by the sysroot-aware toolchain.
NPROC := $(shell nproc 2>/dev/null || echo 4)
LIBNL_LIBS := -lnl-genl-3 -lnl-3
# Preserve bitbake's env LDFLAGS (--hash-style=gnu, -Wl,-z,relro, etc.) —
# do not clobber. Prepending $(LDFLAGS) lets Yocto's security/packaging
# flags flow through the final link.
LDFLAGS := $(LDFLAGS) $(LIBHV_LIBS) $(FMT_LIBS) -lwpa_client $(LIBNL_LIBS) -ldl -lz -lm -lpthread
ifeq ($(ENABLE_SSL),yes)
LDFLAGS += -lssl -lcrypto
endif
LDFLAGS += $(TARGET_LDFLAGS)
PLATFORM := Linux-yocto
# No submodule wpa_client to depend on — wpa-supplicant recipe installs libwpa_client.
WPA_DEPS :=
WPA_CLIENT_LIB :=
else ifneq ($(CROSS_COMPILE)$(filter x86 x86-fbdev x86-both,$(PLATFORM_TARGET)),)
# Platform builds (cross-compilation or x86 Docker native)
NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
# libnl libraries: cross-compiled targets use static archives from submodule,
# x86 native uses system libnl via pkg-config
ifneq (,$(filter x86 x86-fbdev x86-both,$(PLATFORM_TARGET)))
LIBNL_LIBS := -lnl-genl-3 -lnl-3
else
LIBNL_LIBS := $(BUILD_DIR)/lib/libnl-genl-3.a $(BUILD_DIR)/lib/libnl-3.a
endif
# Embedded targets link against libhv and wpa_supplicant
# No SDL2 - display handled by framebuffer/DRM
# SSL is optional - only needed if connecting to remote Moonraker over HTTPS
# Note: libnl must come AFTER wpa_client (static linking order matters)
# Note: -L path only for glibc targets (Pi, AD5M) - musl targets (K1) are self-contained
ifeq ($(PLATFORM_TARGET),k1-dynamic)
# K1 Dynamic: Mixed static/dynamic linking
# Project libraries linked statically, system libraries linked dynamically
# -lstdc++fs: GCC 7.5 requires separate library for <experimental/filesystem>
LDFLAGS := -Wl,-Bstatic \
$(LIBHV_LIBS) $(FMT_LIBS) $(WPA_CLIENT_LIB) $(LIBNL_LIBS) -lstdc++fs \
-Wl,-Bdynamic \
-lstdc++ -lz -lm -lpthread -lrt -ldl -latomic -lgcc_s
else ifneq ($(filter mips k1 ad5x,$(PLATFORM_TARGET)),)
# MIPS targets (K1, AD5X) use musl - fully static, no system library paths needed
# -latomic: Required for 64-bit atomics on 32-bit MIPS (std::atomic<int64_t>)
LDFLAGS := $(LIBHV_LIBS) $(FMT_LIBS) $(WPA_CLIENT_LIB) $(LIBNL_LIBS) -latomic -ldl -lz -lm -lpthread
else ifeq ($(PLATFORM_TARGET),k2)
# K2 uses musl - fully static, no system library paths needed (same as K1)
LDFLAGS := $(LIBHV_LIBS) $(FMT_LIBS) $(WPA_CLIENT_LIB) $(LIBNL_LIBS) -ldl -lz -lm -lpthread
else
LDFLAGS := -L/usr/lib/$(TARGET_TRIPLE) $(LIBHV_LIBS) $(FMT_LIBS) $(WPA_CLIENT_LIB) $(LIBNL_LIBS) $(SYSTEMD_LIBS) -ldl -lz -lm -lpthread
# libusb only available on Pi/x86 targets (ad5m/cc1 use standalone ARM toolchains without it)
ifneq (,$(filter pi pi-fbdev pi-both pi32 pi32-fbdev pi32-both x86 x86-fbdev x86-both,$(PLATFORM_TARGET)))
LDFLAGS += -lusb-1.0
CXXFLAGS += -isystem /usr/include/libusb-1.0 -DHELIX_HAS_LIBUSB=1
endif
endif
ifeq ($(ENABLE_SSL),yes)
ifneq (,$(filter pi pi-fbdev pi-both pi32 pi32-fbdev pi32-both x86 x86-fbdev x86-both k1-dynamic,$(PLATFORM_TARGET)))
# Static-link OpenSSL to avoid soname mismatch across OS versions
# Pi: Bullseye has libssl.so.1.1, Bookworm has libssl.so.3
# K1-dynamic: K1 firmware may not have OpenSSL shared libs
LDFLAGS += -Wl,-Bstatic -lssl -lcrypto -Wl,-Bdynamic
else
LDFLAGS += -lssl -lcrypto
endif
endif
# Add target-specific linker flags (e.g., -lstdc++fs for GCC 8)
LDFLAGS += $(TARGET_LDFLAGS)
# DRM backend requires libdrm for display; libinput for LVGL input drivers
ifeq ($(DISPLAY_BACKEND),drm)
LDFLAGS += -ldrm
ifneq ($(SNAPMAKER_SKIP_LIBINPUT),yes)
LDFLAGS += -linput
endif
# GPU-accelerated rendering via EGL/OpenGL ES
ifeq ($(ENABLE_OPENGLES),yes)
LDFLAGS += -lEGL -lGLESv2 -lgbm
endif
endif
PLATFORM := Linux-$(TARGET_ARCH)
WPA_DEPS := $(WPA_CLIENT_LIB)
# Strip embedded binaries for size (CI extracts symbols first, then strips)
ifeq ($(STRIP_BINARY),yes)
STRIP_CMD := $(CROSS_COMPILE)strip
NM_CMD := $(CROSS_COMPILE)nm
endif
else ifeq ($(UNAME_S),Darwin)
# macOS native build - Uses CoreWLAN framework for WiFi (with fallback to mock)
NPROC := $(shell sysctl -n hw.ncpu 2>/dev/null || echo 4)
# Set minimum macOS version (10.15 Catalina for CoreWLAN/CoreLocation modern APIs)
MACOS_MIN_VERSION := 10.15
MACOS_DEPLOYMENT_TARGET := -mmacosx-version-min=$(MACOS_MIN_VERSION)
CFLAGS += $(MACOS_DEPLOYMENT_TARGET)
CXXFLAGS += $(MACOS_DEPLOYMENT_TARGET)
# macOS has no gettid() — override libhv's hconfig.h which incorrectly assumes it
CFLAGS += -DHAVE_GETTID=0
CXXFLAGS += -DHAVE_GETTID=0
SUBMODULE_CFLAGS += $(MACOS_DEPLOYMENT_TARGET)
SUBMODULE_CXXFLAGS += $(MACOS_DEPLOYMENT_TARGET)
# libusb detection via pkg-config (Homebrew paths aren't in default search path)
LIBUSB_CFLAGS := $(shell pkg-config --cflags libusb-1.0 2>/dev/null)
LIBUSB_LIBS := $(shell pkg-config --libs libusb-1.0 2>/dev/null)
ifneq ($(LIBUSB_LIBS),)
CFLAGS += $(LIBUSB_CFLAGS)
CXXFLAGS += $(LIBUSB_CFLAGS) -DHELIX_HAS_LIBUSB=1
endif
# -Wl,-w suppresses linker warnings about macOS version mismatches between
# our 10.15 deployment target and libraries built for newer versions
LDFLAGS := -Wl,-w $(LDFLAGS_COMMON) -framework Foundation -framework CoreFoundation -framework Security -framework CoreWLAN -framework CoreLocation -framework Cocoa -framework IOKit -framework CoreVideo -framework AudioToolbox -framework ForceFeedback -framework Carbon -framework CoreAudio -framework Metal -liconv $(LIBUSB_LIBS)
PLATFORM := macOS
WPA_DEPS :=
else
# Linux native build - Include libwpa_client.a for WiFi control
NPROC := $(shell nproc 2>/dev/null || echo 4)
# Note: -lstdc++fs needed for std::experimental::filesystem on GCC < 9
LDFLAGS := $(LDFLAGS_COMMON) $(WPA_CLIENT_LIB) $(SYSTEMD_LIBS) -lusb-1.0 -lssl -lcrypto -ldl -lstdc++fs
CXXFLAGS += -isystem /usr/include/libusb-1.0 -DHELIX_HAS_LIBUSB=1
# GPU-accelerated 3D G-code rendering via OpenGL ES 2.0 (SDL GL context on desktop)
ifeq ($(ENABLE_GLES_3D),yes)
LDFLAGS += -lGLESv2
endif
PLATFORM := Linux
WPA_DEPS := $(WPA_CLIENT_LIB)
endif
# Add 3D renderer defines to compiler flags
CFLAGS += $(GLES3D_DEFINES)
CXXFLAGS += $(GLES3D_DEFINES)
# Add screensaver defines to compiler flags
CFLAGS += $(SCREENSAVER_DEFINES)
CXXFLAGS += $(SCREENSAVER_DEFINES)
# Add mock defines to compiler flags
CFLAGS += $(MOCK_DEFINES)
CXXFLAGS += $(MOCK_DEFINES)
# Add systemd defines to C++ compiler flags (for logging_init.cpp)
CXXFLAGS += $(SYSTEMD_CXXFLAGS)
# ALSA audio backend — real waveform synthesis on Linux targets with libasound2
# Enabled for: Pi (all variants), x86 SBCs
# Not available on: macOS (no ALSA), AD5M (no sound card), K1/K2/MIPS (musl, no ALSA)
ALSA_LIBS :=
ALSA_CXXFLAGS :=
ifneq (,$(filter pi pi-fbdev pi-both pi32 pi32-fbdev pi32-both x86 x86-fbdev x86-both,$(PLATFORM_TARGET)))
ALSA_CXXFLAGS := -DHELIX_HAS_ALSA
ALSA_LIBS := -lasound
else ifeq ($(PLATFORM_TARGET),native)
ifeq ($(UNAME_S),Linux)
ALSA_PKG := $(shell pkg-config --exists alsa 2>/dev/null && echo "yes")
ifeq ($(ALSA_PKG),yes)
ALSA_CXXFLAGS := -DHELIX_HAS_ALSA $(shell pkg-config --cflags alsa 2>/dev/null)
ALSA_LIBS := $(shell pkg-config --libs alsa 2>/dev/null)
endif
endif
endif
CXXFLAGS += $(ALSA_CXXFLAGS)
LDFLAGS += $(ALSA_LIBS)
# Re-apply AddressSanitizer linker flags AFTER the per-platform LDFLAGS
# composition above (lines 580-694) — those use `LDFLAGS :=` which clobbers
# anything cross.mk's SANITIZE block injected. Must come last so platform
# library lists are present alongside ASAN runtime.
ifeq ($(SANITIZE),address)
LDFLAGS += $(SANITIZE_FLAGS)
ifneq ($(CROSS_COMPILE),)
LDFLAGS += -static-libasan
endif
endif
# Sound system — synth, sequencer, backends (PWM/M300/SDL/ALSA), themes
# Tracker player — MOD/MED file playback with PCM samples (requires HELIX_HAS_SOUND)
#
# HELIX_HAS_SOUND: Pi, x86, AD5M, native — any platform with audio output
# HELIX_HAS_TRACKER: Pi, x86, native — platforms with multi-core CPU + audio
# AD5M/AD5X: sound only (no tracker — single-core busy-wait kills prints)
# Disabled entirely: K1, K2, MIPS — no audio hardware at all
SOUND_CXXFLAGS :=
TRACKER_CXXFLAGS :=
ifneq (,$(filter pi pi-fbdev pi-both pi32 pi32-fbdev pi32-both x86 x86-fbdev x86-both,$(PLATFORM_TARGET)))
SOUND_CXXFLAGS := -DHELIX_HAS_SOUND
TRACKER_CXXFLAGS := -DHELIX_HAS_TRACKER
else ifneq (,$(filter ad5m ad5m-br ad5x,$(PLATFORM_TARGET)))
# AD5M/AD5X: PWM buzzer for tone-mode SFX only.
# Tracker (MOD/MED) DISABLED — the PCM render thread's busy-wait loop
# starves the single-core CPU, killing active prints and blocking
# Moonraker commands (including firmware_restart).
SOUND_CXXFLAGS := -DHELIX_HAS_SOUND
else ifeq ($(PLATFORM_TARGET),native)
SOUND_CXXFLAGS := -DHELIX_HAS_SOUND
TRACKER_CXXFLAGS := -DHELIX_HAS_TRACKER
endif
# K1, K2, MIPS — no sound at all
CXXFLAGS += $(SOUND_CXXFLAGS) $(TRACKER_CXXFLAGS)
# Feature gates — default ON for all platforms.
# Disabled per-platform in mk/cross.mk for memory-constrained targets.
HELIX_HAS_LABEL_PRINTER ?= 1
HELIX_HAS_CFS ?= 1
HELIX_HAS_IFS ?= 1
CXXFLAGS += -DHELIX_HAS_LABEL_PRINTER=$(HELIX_HAS_LABEL_PRINTER) \
-DHELIX_HAS_CFS=$(HELIX_HAS_CFS) \
-DHELIX_HAS_IFS=$(HELIX_HAS_IFS)
# Parallel build control
# Auto-parallelizes builds: plain 'make' automatically uses -j$(NPROC).
#
# Detection method (see mk/rules.mk):
# - 'make': No jobserver → auto-add -j$(NPROC)
# - 'make -j': No jobserver → auto-fix to -j$(NPROC) with warning
# - 'make -j8': Has jobserver → pass through unchanged
#
# MAKEFLAGS format:
# - 'make' or 'make -j': No 'jobserver' in MAKEFLAGS
# - 'make -jN': MAKEFLAGS contains '--jobserver-fds=X,Y' or '--jobserver-auth'
JOBS ?= $(NPROC)
# Output synchronization for parallel builds (requires make 4.0+, ignored on 3.81)
ifneq ($(JOBS),1)
MAKEFLAGS += --output-sync=target
endif
# Binaries
TARGET := $(BIN_DIR)/helix-screen
MOONRAKER_INSPECTOR := $(BIN_DIR)/moonraker-inspector
# Test configuration
TEST_DIR := tests
TEST_UNIT_DIR := $(TEST_DIR)/unit
TEST_MOCK_DIR := $(TEST_DIR)/mocks
TEST_BIN := $(BIN_DIR)/helix-tests
TEST_INTEGRATION_BIN := $(BIN_DIR)/run_integration_tests
# Unit tests (use real LVGL) - exclude mock example
# Include tests from unit/ directory and unit/application/ subdirectory
TEST_SRCS := $(filter-out $(TEST_UNIT_DIR)/test_mock_example.cpp,$(wildcard $(TEST_UNIT_DIR)/*.cpp))
TEST_APP_SRCS := $(wildcard $(TEST_UNIT_DIR)/application/*.cpp)
TEST_OBJS := $(patsubst $(TEST_UNIT_DIR)/%.cpp,$(OBJ_DIR)/tests/%.o,$(TEST_SRCS))
TEST_APP_OBJS_EXTRA := $(patsubst $(TEST_UNIT_DIR)/application/%.cpp,$(OBJ_DIR)/tests/application/%.o,$(TEST_APP_SRCS))
# Integration tests (use mocks instead of real LVGL)
TEST_INTEGRATION_SRCS := $(TEST_UNIT_DIR)/test_mock_example.cpp
TEST_INTEGRATION_OBJS := $(patsubst $(TEST_UNIT_DIR)/%.cpp,$(OBJ_DIR)/tests/%.o,$(TEST_INTEGRATION_SRCS))
TEST_MAIN_OBJ := $(OBJ_DIR)/tests/test_main.o
CATCH2_OBJ := $(OBJ_DIR)/tests/catch_amalgamated.o
UI_TEST_UTILS_OBJ := $(OBJ_DIR)/tests/ui_test_utils.o
LVGL_TEST_FIXTURE_OBJ := $(OBJ_DIR)/tests/lvgl_test_fixture.o
HELIX_TEST_FIXTURE_OBJ := $(OBJ_DIR)/tests/helix_test_fixture.o
TEST_FIXTURES_OBJ := $(OBJ_DIR)/tests/test_fixtures.o
LVGL_UI_TEST_FIXTURE_OBJ := $(OBJ_DIR)/tests/lvgl_ui_test_fixture.o
# Mock objects for integration testing
MOCK_SRCS := $(wildcard $(TEST_MOCK_DIR)/*.cpp)
MOCK_OBJS := $(patsubst $(TEST_MOCK_DIR)/%.cpp,$(OBJ_DIR)/tests/mocks/%.o,$(MOCK_SRCS))
# Default target
.DEFAULT_GOAL := all
.PHONY: all build clean run test tests test-integration test-cards test-print-select test-size-content demo compile_commands compile_commands_full libhv-build apply-patches generate-fonts validate-fonts regen-fonts update-mdi-cache verify-mdi-codepoints help check-deps install-deps venv-setup icon format format-staged screenshots tools moonraker-inspector strict quality setup translations symbols strip dev install
# Fast development build: -O0 skips optimization passes (~2x faster compilation)
# Library code still builds at -O2 (via SUBMODULE_CFLAGS) since it rarely changes
dev:
$(Q)$(MAKE) OPT=0 -j
# Developer setup - configure git hooks and commit template
setup:
@git config core.hooksPath .githooks
@git config commit.template .githooks/commit-template
@echo "✓ Git configured:"
@echo " - Pre-commit hook enabled (.githooks/)"
@echo " - Commit template enabled (.githooks/commit-template)"
# Help target - shows common commands, references topic-specific help
help:
@if [ -t 1 ] && [ -n "$(TERM)" ] && [ "$(TERM)" != "dumb" ]; then \
B='$(BOLD)'; R='$(RED)'; G='$(GREEN)'; Y='$(YELLOW)'; BL='$(BLUE)'; M='$(MAGENTA)'; C='$(CYAN)'; X='$(RESET)'; D='$(shell printf "\033[2m")'; \
else \
B=''; R=''; G=''; Y=''; BL=''; M=''; C=''; X=''; D=''; \
fi; \
echo "$${B}HelixScreen Build System$${X}"; \
echo ""; \
echo "$${C}Quick Start:$${X}"; \
echo " $${G}make setup$${X} - Configure git hooks and commit template"; \
echo " $${G}make -j$${X} - Build (parallel, auto-detects cores)"; \
echo " $${G}make dev$${X} - Fast build (-O0, ~2x faster compilation)"; \
echo " $${G}make run$${X} - Build and run the UI"; \
echo " $${G}make test$${X} - Run unit tests"; \
echo " $${G}make clean$${X} - Remove build artifacts"; \
echo " $${G}make strict$${X} - Build with -Werror (warnings = errors)"; \
echo ""; \
echo "$${C}Common Tasks:$${X}"; \
echo " $${G}check-deps$${X} - Verify dependencies are installed"; \
echo " $${G}install-deps$${X} - Auto-install missing dependencies"; \
echo " $${G}format$${X} - Auto-format C/C++ and XML files"; \
echo " $${G}compile_commands$${X} - Generate compile_commands.json for IDE"; \
echo ""; \
echo "$${C}Tools:$${X}"; \
echo " $${G}tools$${X} - Build diagnostic tools"; \
echo " $${G}moonraker-inspector$${X} - Query Moonraker printer metadata"; \
echo " $${G}validate-fonts$${X} - Check all icons are in compiled fonts"; \
echo " $${G}regen-fonts$${X} - Regenerate MDI icon fonts"; \
echo " $${G}quality$${X} - Run all quality checks"; \
echo " $${G}icon$${X} - Generate app icon from logo"; \
echo ""; \
echo "$${C}More Help:$${X} $${D}(use these for detailed target lists)$${X}"; \
echo " $${Y}make help-build$${X} - Build system, dependencies, patches"; \
echo " $${Y}make help-test$${X} - All test targets and options"; \
echo " $${Y}make help-cross$${X} - Cross-compilation and Pi deployment"; \
echo " $${Y}make help-all$${X} - Show everything"; \
echo ""; \
echo "$${C}Platform:$${X} $(PLATFORM) $${D}($(NPROC) cores)$${X}"
# Show all help topics combined
.PHONY: help-all
help-all: help-build help-test help-cross help-remote
@echo ""
@if [ -t 1 ] && [ -n "$(TERM)" ] && [ "$(TERM)" != "dumb" ]; then \
echo "$(CYAN)Material Icons:$(RESET)"; \
else \
echo "Material Icons:"; \
fi
@echo " material-icons-list - List registered icons"
@echo " material-icons-add - Download and add icons (ICONS=...)"
@echo " material-icons-convert - Convert SVGs to C arrays (SVGS=...)"
# Documentation screenshot generation
screenshots: $(BIN)
$(Q)$(ECHO) "$(CYAN)Generating documentation screenshots...$(RESET)"
$(Q)./scripts/generate-screenshots.sh
$(Q)$(ECHO) "$(GREEN)✓ Documentation screenshots generated in docs/images/$(RESET)"
# =============================================================================
# Symbol extraction and stripping (for crash backtrace resolution)
# Runs automatically for cross-compiled builds (STRIP_BINARY=yes).
# Extracts symbol maps first, then strips — preserving debug info for
# offline crash resolution while keeping the deployed binary small.
#
# Artifacts produced (uploaded to R2 by CI):
# .sym — nm -nC output for fast function-name lookup
# .debug — DWARF debug info for addr2line (file:line, inlined frames)
# =============================================================================
OBJCOPY_CMD := $(CROSS_COMPILE)objcopy
symbols: $(TARGET)
ifeq ($(STRIP_BINARY),yes)
$(NM_CMD) -nC $(TARGET) > $(TARGET).sym
@echo "Symbol map: $(TARGET).sym"
$(OBJCOPY_CMD) --only-keep-debug $(TARGET) $(TARGET).debug
@echo "Debug info: $(TARGET).debug ($(shell du -h $(TARGET).debug 2>/dev/null | cut -f1 || echo '?'))"
else
@echo "STRIP_BINARY not set — skipping symbol extraction"
endif
strip: symbols
ifeq ($(STRIP_BINARY),yes)
$(STRIP_CMD) $(TARGET)
$(STRIP_CMD) $(SPLASH_BIN)
$(STRIP_CMD) $(WATCHDOG_BIN)
@echo "Stripped: $(TARGET) $(SPLASH_BIN) $(WATCHDOG_BIN)"
else
@echo "STRIP_BINARY not set — skipping strip"
endif
# Strict build - treat warnings as errors (for CI)
# This catches issues that would otherwise slip through
strict:
@echo "$(CYAN)$(BOLD)Building with strict warnings (-Werror)...$(RESET)"
$(Q)$(MAKE) WERROR=1 all
# Run all quality checks (same as CI and pre-commit)
quality:
@echo "$(CYAN)$(BOLD)Running quality checks...$(RESET)"
$(Q)./scripts/quality-checks.sh
# Generated contributors header — sourced from CONTRIBUTORS.txt (committed)
# so cross-compile Docker builds and shallow CI checkouts produce correct output.
CONTRIBUTORS_H := $(BUILD_DIR)/generated/contributors.h
$(CONTRIBUTORS_H): CONTRIBUTORS.txt scripts/gen-contributors.sh
$(Q)BUILD_DIR=$(BUILD_DIR) ./scripts/gen-contributors.sh
# Refresh CONTRIBUTORS.txt from git history (respects .mailmap).
# Unions primary authors (%aN) with Co-authored-by trailer names so pair- and
# co-authored work is credited too. Run before release to pick up new
# contributors, then commit the result.
.PHONY: update-contributors
update-contributors:
@{ \
git -c safe.directory='*' log --format='%aN'; \
git -c safe.directory='*' log --format='%(trailers:key=Co-authored-by,valueonly,unfold)' \
| sed '/^$$/d' \
| git -c safe.directory='*' check-mailmap --stdin 2>/dev/null \
| sed -E 's/ *<[^>]*>//'; \
} | sort -u \
| grep -ivE 'bot\b|\[bot\]|dependabot|github-actions|claude' \
| awk 'length >= 2' > CONTRIBUTORS.txt
@echo "$(GREEN)✓ CONTRIBUTORS.txt updated ($$(wc -l < CONTRIBUTORS.txt) contributors)$(RESET)"
@echo " Review the diff and commit: git diff CONTRIBUTORS.txt"
# Include modular makefiles
include mk/deps.mk
include mk/patches.mk
include mk/translations.mk
include mk/tests.mk
include mk/images.mk