Repository navigation
Expand file tree
/
Copy pathmeson.build
More file actions
406 lines (358 loc) · 18 KB
/
Copy pathmeson.build
File metadata and controls
406 lines (358 loc) · 18 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
# FastLED Examples - Meson Build Integration
#
# This file provides two types of example compilation targets:
#
# 1. HOST-BASED COMPILATION (Fast, for testing/development):
# - Compiles examples for host machine using STUB platform
# - Ultra-fast: ~0.24s for all 96 examples with PCH
# - Targets: ninja -C builddir examples-host, example-Blink, etc.
# - See: examples/host/meson.build
#
# 2. HARDWARE BOARD COMPILATION (PlatformIO-based):
# - Compiles examples for actual Arduino boards
# - Targets: example-blink-uno, examples-all-uno, etc.
# - Uses PlatformIO for cross-compilation
#
# Meson targets:
# ninja -C builddir examples-host - Compile ALL examples for host (fast!)
# ninja -C builddir example-Blink - Compile Blink for host
# ninja -C builddir example-blink-uno - Compile Blink for Arduino Uno
# ninja -C builddir example-blink-esp32 - Compile Blink for ESP32
# ninja -C builddir examples-all-uno - Compile all examples for Uno
# ninja -C builddir examples-default - Compile all examples for default boards
# Get project root and filesystem module
project_root = meson.project_source_root()
fs = import('fs')
# NOTE (#2328): Absolute forward-slash -I args (path_norm_I_*) come from
# ci/meson/shared/meson.build. We do NOT use include_directories() for
# project-wide includes because Meson propagates them as backslash + relative
# -I flags on Windows, which defeats clang's #pragma once file-identity check.
# ============================================================================
# HOST-BASED EXAMPLE COMPILATION (Fast, using STUB platform)
# ============================================================================
# Compiles all Arduino examples for the host platform using STUB backend.
# Discovers all .ino files and uses their parent directory as the sketch directory.
# Discover all .ino files using Python helper scripts with caching
cache_script = project_root / 'ci' / 'meson' / 'example_metadata_cache.py'
discover_all_script = project_root / 'ci' / 'meson' / 'discover_examples_all.py'
examples_dir = project_root / 'examples'
examples_build_dir = meson.current_build_dir()
# Check if cache is valid
cache_check = run_command(
'python', cache_script, '--check', examples_dir, examples_build_dir,
check: false
)
if cache_check.returncode() == 0
# Cache hit - use cached metadata
example_metadata_output = cache_check.stdout().strip()
message('Using cached example metadata (no changes detected)')
else
# Cache miss - run full discovery
message('Running example discovery (cache invalid or missing)')
discovery_result = run_command(
'python', discover_all_script, examples_dir,
check: true
)
example_metadata_output = discovery_result.stdout().strip()
# Update cache
run_command(
'python', cache_script, '--update',
examples_dir, examples_build_dir, example_metadata_output,
check: true
)
endif
# Parse consolidated metadata in single pass
# Format: EXAMPLE|<name>|<dir1>|..., SOURCES|<name>|<src1>|..., SKIP|<name>|<reason>
# Note: Using | delimiter instead of : to avoid Windows path issues (C:\...)
stub_incompatible_examples = []
nopch_examples = [] # Examples with #define before #include FastLED (PCH-incompatible)
example_includes = {}
example_paths = {} # Map example name to its root directory path
example_sources = {}
example_filters = {} # Map example name to its @filter string (for runtime guard)
foreach line : example_metadata_output.split('\n')
if line != ''
parts = line.split('|')
if parts.length() >= 2
line_type = parts[0]
example_name = parts[1]
if line_type == 'EXAMPLE' and parts.length() >= 3
# Parse EXAMPLE|<name>|<dir1>|<dir2>|...
# First include dir is the example's root directory (sketch directory)
example_root = parts[2]
example_paths += {example_name: example_root}
# Collect all include dirs
include_dirs = []
foreach i : range(2, parts.length())
include_dirs += [parts[i]]
endforeach
example_includes += {example_name: include_dirs}
elif line_type == 'SOURCES'
# Parse SOURCES|<name>|<source1>|<source2>|...
# Collect additional .cpp source files (beyond the .ino)
source_files = []
foreach i : range(2, parts.length())
if parts[i] != ''
source_files += [parts[i]]
endif
endforeach
example_sources += {example_name: source_files}
elif line_type == 'SKIP' and parts.length() >= 3
# Parse SKIP|<name>|<reason>
reason = parts[2]
stub_incompatible_examples += [example_name]
elif line_type == 'FILTER' and parts.length() >= 3
# Parse FILTER|<name>|<filter_string>
# Store the raw filter string for compile-time platform guard
example_filters += {example_name: parts[2]}
elif line_type == 'NOPCH' and parts.length() >= 3
# Parse NOPCH|<name>|<reason>
# Example has #define before #include FastLED — PCH-incompatible
nopch_examples += [example_name]
endif
endif
endif
endforeach
# ============================================================================
# BUILD EXAMPLE RUNNER (DLL LOADER)
# ============================================================================
# Build example_runner.exe (static, generic DLL loader)
# This executable loads an example DLL and calls its run_example() function
# It will be copied to each example directory as example-<name>.exe
#
# IMPORTANT: The runner intentionally does NOT link against fastled_lib.
# Only the example DLLs should link FastLED to avoid duplicate vtables/symbols
# which cause UBSAN "invalid vptr" errors when objects cross library boundaries.
# The runner uses std:: types instead of fl:: types to stay lightweight.
#
# System libraries (pthread, dbghelp, psapi) are linked here (once)
# instead of in each example-X.dll (96 times), significantly reducing build time.
# The example_runner.exe provides these symbols to the loaded DLL at runtime.
# Crash handler is also setup in example_runner.exe before loading any DLL.
#
# NOTE: runner_link_args, runner_cpp_args, and crash_handler_lib are defined
# in root meson.build and shared with tests/meson.build for consistency.
# libunwind is conditionally linked via -lunwind in runner_link_args when available.
# Absolute forward-slash -I args (issue #2328) — DO NOT use include_directories()
# here. Meson emits -I..\..\src on Windows which breaks #pragma once.
example_runner_exe = executable('example_runner',
files('../tests/shared/example_runner.cpp'),
cpp_args: runner_cpp_args + [path_norm_I_src, path_norm_I_stub, path_norm_I_tests],
link_args: runner_link_args,
link_with: [crash_handler_lib], # Shared crash handler from root meson.build
implicit_include_directories: false,
install: false
)
# ============================================================================
# FASTLED PCH ARGS FOR EXAMPLES
# ============================================================================
# Examples use the shared fastled_pch (FastLED.h only) to avoid re-parsing
# ~986 transitive headers for every example DLL. Examples with #define before
# #include FastLED are PCH-incompatible and skip this.
#
# Absolute include paths must match those used to compile fastled_pch so that
# #pragma once recognises headers from both contexts as the same file.
example_abs_include_args = [
path_norm_I_src,
path_norm_I_stub,
]
example_pch_usage_args = [
'-include-pch', fastled_pch_dir / 'FastLED.h.pch',
'-Werror=invalid-pch',
'-fpch-validate-input-files-content',
]
# List to store all example executables for the alias target
example_executables = []
# DLL-only list for the all-with-examples alias (avoids building runner copies
# which each take ~1.5s of file-copy overhead on Windows, saving ~10s total)
example_dlls = []
# Create executable target for each discovered example
foreach example_name : example_paths.keys()
# Skip blacklisted examples (platform-specific or incompatible with STUB)
if stub_incompatible_examples.contains(example_name)
continue
endif
# Get the example's root directory path (sketch directory)
example_root = example_paths[example_name]
# Get include directories for this example
example_include_dirs = example_includes.get(example_name, [])
# Build absolute forward-slash -I args for each per-example include dir
# (issue #2328). DO NOT use include_directories() — Meson emits these as
# backslash + relative paths on Windows, which defeats #pragma once dedup.
# inc_dir is project-relative (e.g. "examples/Blink" or "examples\\Blink");
# normalize to path_norm_root / <rest> via forward-slash '/' operator.
per_example_I_args = []
foreach inc_dir : example_include_dirs
# Canonicalize any backslashes the discovery script may have emitted
inc_dir_fwd = inc_dir.replace('\\', '/')
per_example_I_args += ['-I' + path_norm_root / inc_dir_fwd]
endforeach
# Construct proper path: <example_root>/<example_name>.ino
ino_file = project_root / example_root / (example_name + '.ino')
# Use static wrapper template instead of generating wrapper at build time
# This eliminates custom_target overhead and Python script dependency
wrapper_cpp = files(project_root / 'tests' / 'shared' / 'example_dll_wrapper_template.cpp')
# Absolute forward-slash -I args for FastLED + tests + per-example includes
# (issue #2328). Used via cpp_args, NOT include_directories.
all_I_args = [
path_norm_I_root, path_norm_I_src, path_norm_I_stub, path_norm_I_tests,
] + per_example_I_args
# Get additional source files for this example (e.g., src/*.cpp)
additional_sources = []
example_source_list = example_sources.get(example_name, [])
if example_source_list.length() > 0
foreach src_file : example_source_list
additional_sources += files(project_root / src_file)
endforeach
endif
# DEBUG FLAGS: Special case for Downscale example - add debug symbols for investigation
# TODO: Consider generalizing this mechanism for per-example compile flags if needed
debug_cpp_args = []
# if example_name == 'Downscale'
# debug_cpp_args = ['-g3', '-O0', '-DFASTLED_MULTITHREADING=0']
# endif
# Pass .ino path via preprocessor define to the static wrapper template
# This eliminates the need for dynamic wrapper generation
# Add -fvisibility=hidden to make all symbols private by default
# Only explicitly exported symbols (run_example) will be visible
example_specific_cpp_args = [
'-DEXAMPLE_INO_PATH="' + ino_file + '"',
'-fvisibility=hidden'
]
# Pass @filter annotation as preprocessor define for runtime platform guard
# This allows the wrapper template to detect platform-incompatible examples
# even when the meson build cache is stale, compiling a skip-stub instead
# of failing with a mysterious compile error
if example_filters.has_key(example_name)
example_filter_str = example_filters[example_name]
example_specific_cpp_args += [
'-DEXAMPLE_PLATFORM_FILTER="' + example_filter_str + '"'
]
# Check if this filter indicates the example should NOT run on stub/native
# If it contains "platform is X" where X is not "native", mark as stub-incompatible
# This is a defense-in-depth guard - normally these are filtered at configure time
if example_filter_str.contains('platform is') and not example_filter_str.contains('platform is native')
example_specific_cpp_args += ['-DEXAMPLE_STUB_INCOMPATIBLE=1']
endif
endif
# Collect all source files: wrapper + any additional .cpp files from subdirectories
all_sources = [wrapper_cpp] + additional_sources
# Conditionally apply FastLED PCH for examples without pre-FastLED defines
this_example_pch_args = []
this_example_extra_sources = []
if not nopch_examples.contains(example_name)
# PCH-compatible: use shared fastled_pch + absolute include paths
this_example_pch_args = example_abs_include_args + example_pch_usage_args
this_example_extra_sources = [fastled_pch] # Source dependency for build ordering
endif
# Build example as a shared library (DLL)
# NOTE: System dependencies moved to example_runner.exe
# NOTE (#2328): includes passed via cpp_args (abs forward-slash), NOT
# include_directories, to avoid backslash + relative -I propagation.
example_dll = shared_library(
'example-' + example_name,
all_sources + this_example_extra_sources, # Wrapper + additional .cpp files + PCH dep
link_with: fastled_lib,
cpp_args: example_compile_args + all_I_args + debug_cpp_args + example_specific_cpp_args + this_example_pch_args + ['-DEXAMPLE_DLL_MODE'],
link_args: dll_link_args, # Minimal linking (defined in root meson.build)
name_prefix: '', # Remove 'lib' prefix on Windows (produce 'example-Blink.dll' not 'libexample-Blink.dll')
build_by_default: false, # Only build when explicitly requested
implicit_include_directories: false,
install: false
)
# Copy example_runner.exe to example directory as example-<name>.exe
# This allows running `example-Blink.exe` which auto-loads `example-Blink.dll`
example_runner_copy = custom_target(
'example-' + example_name + '_runner',
input: example_runner_exe,
output: 'example-' + example_name + '.exe',
command: ['cp', '@INPUT@', '@OUTPUT@'],
depends: [example_dll], # Ensure DLL is built first
build_by_default: false
)
# Register as test target: runner loads the DLL and executes it
# Add to 'examples' suite for easy filtering
# Environment ensures fastled.dll and runtime DLLs (ASan, MinGW) are discoverable
example_test_env = environment()
example_test_env.prepend('PATH', fastled_lib_dir)
# Add clang-tool-chain runtime dirs so ASan/MinGW DLLs are found even when
# zccache caches the link and skips ctc-clang++'s post-link deployment.
foreach _rtdll_dir : runtime_dll_paths
example_test_env.prepend('PATH', _rtdll_dir)
endforeach
test('example-' + example_name, example_runner_exe,
args: [example_dll.full_path()],
suite: 'examples',
timeout: 60,
env: example_test_env,
)
# Track both DLL and runner for alias target
example_executables += [example_dll, example_runner_copy]
example_dlls += [example_dll]
endforeach
# Create alias target to build all examples (only if we have examples)
if example_executables.length() > 0
alias_target('examples-host', example_executables)
endif
example_count = example_executables.length()
total_discovered = example_paths.keys().length()
skipped_count = stub_incompatible_examples.length()
message('Host-based example compilation: ' + example_count.to_string() + '/' + total_discovered.to_string() + ' examples (' + skipped_count.to_string() + ' platform-specific examples skipped)')
# ============================================================================
# HARDWARE BOARD COMPILATION (PlatformIO-based)
# ============================================================================
# Find uv executable
uv_exe = find_program('uv', required: false)
if not uv_exe.found()
warning('uv not found - PlatformIO example compilation targets will not be available. Install with: pip install uv')
subdir_done()
endif
# Path to compile script (relative to project root)
compile_script = meson.project_source_root() / 'ci' / 'ci-compile.py'
# Common boards for quick testing
common_boards = ['uno', 'esp32dev', 'esp8266', 'esp32s3', 'teensy41']
# Common examples
common_examples = ['Blink', 'DemoReel100', 'ColorPalette', 'Cylon', 'Fire2012']
# Create individual example + board targets
# Usage: ninja -C builddir example-blink-uno
foreach board : common_boards
foreach example : common_examples
target_name = 'example-@0@-@1@'.format(example.to_lower(), board)
run_target(target_name,
command: [uv_exe, 'run', compile_script, board, example, '--local'],
depends: [fastled_lib] # Ensure libfastled.a is built first
)
endforeach
endforeach
# Create "all examples for board" targets
# Usage: ninja -C builddir examples-all-uno
foreach board : common_boards
target_name = 'examples-all-@0@'.format(board)
run_target(target_name,
command: [uv_exe, 'run', compile_script, board, '--local'],
depends: [fastled_lib] # Ensure libfastled.a is built first
)
endforeach
# Create target to compile default examples on default boards
# Usage: ninja -C builddir examples-default
run_target('examples-default',
command: [uv_exe, 'run', compile_script, '--local'],
depends: [fastled_lib] # Ensure libfastled.a is built first
)
# Create target to compile a single example for all common boards
# Usage: ninja -C builddir examples-blink-all-boards
foreach example : common_examples
target_name = 'examples-@0@-all-boards'.format(example.to_lower())
board_list = ','.join(common_boards)
run_target(target_name,
command: [uv_exe, 'run', compile_script, board_list, example, '--local'],
depends: [fastled_lib] # Ensure libfastled.a is built first
)
endforeach
message('PlatformIO example compilation targets configured:')
message(' - Individual: example-<name>-<board> (e.g., example-blink-uno)')
message(' - All examples: examples-all-<board> (e.g., examples-all-uno)')
message(' - Single example, all boards: examples-<name>-all-boards (e.g., examples-blink-all-boards)')
message(' - Default: examples-default')
message(' Available boards: ' + ', '.join(common_boards))
message(' Available examples: ' + ', '.join(common_examples))