Repository navigation
Expand file tree
/
Copy pathCMakeLists.txt
More file actions
512 lines (447 loc) · 16.4 KB
/
Copy pathCMakeLists.txt
File metadata and controls
512 lines (447 loc) · 16.4 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
cmake_minimum_required(VERSION 3.16)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD
20
CACHE STRING "The C++ standard to use")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Enable ccache if available for faster rebuilds
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
message(STATUS "Using ccache: ${CCACHE_PROGRAM}")
endif()
# Generate ctags if available
find_program(CTAGS_PROGRAM ctags)
if(CTAGS_PROGRAM)
add_custom_target(
tags
COMMAND ${CTAGS_PROGRAM} -R ${CMAKE_SOURCE_DIR}/src
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Regenerating ctags index")
message(
STATUS "ctags found: ${CTAGS_PROGRAM} (tags regenerated on each build)")
else()
message(STATUS "ctags not found, tag generation skipped")
endif()
# Find required packages
find_package(PkgConfig REQUIRED)
# macOS Homebrew support - add paths for both Intel and Apple Silicon
if(APPLE)
# Apple Silicon Homebrew paths
if(EXISTS "/opt/homebrew")
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
list(APPEND CMAKE_LIBRARY_PATH "/opt/homebrew/lib")
list(APPEND CMAKE_INCLUDE_PATH "/opt/homebrew/include")
include_directories("/opt/homebrew/include")
link_directories("/opt/homebrew/lib")
endif()
# Intel Mac Homebrew paths
if(EXISTS "/usr/local/Cellar")
list(APPEND CMAKE_PREFIX_PATH "/usr/local")
list(APPEND CMAKE_LIBRARY_PATH "/usr/local/lib")
list(APPEND CMAKE_INCLUDE_PATH "/usr/local/include")
endif()
endif()
# Use FetchContent for external dependencies
include(FetchContent)
# Disable Ableton Link tests and examples to speed up builds
set(LINK_BUILD_TESTS
OFF
CACHE BOOL "Build Ableton Link tests" FORCE)
set(LINK_BUILD_UTIL
OFF
CACHE BOOL "Build Ableton Link utilities" FORCE)
set(LINK_BUILD_APPS
OFF
CACHE BOOL "Build Ableton Link applications" FORCE)
set(LINK_BUILD_EXAMPLES
OFF
CACHE BOOL "Build Ableton Link examples" FORCE)
set(LINK_BUILD_VIA_PACKAGE_MANAGER
ON
CACHE BOOL "Build via package manager" FORCE)
# Fetch Ableton Link with submodules
FetchContent_Declare(
ableton_link
GIT_REPOSITORY https://github.com/Ableton/link.git
GIT_TAG Link-3.1.3
GIT_SUBMODULES_RECURSE ON)
# Fetch Link manually to modify its CMakeLists.txt
FetchContent_Populate(ableton_link)
# Patch the main CMakeLists.txt to exclude examples (only if not already
# patched)
file(READ "${ableton_link_SOURCE_DIR}/CMakeLists.txt" ABLETON_CMAKE_CONTENT)
if(NOT ABLETON_CMAKE_CONTENT MATCHES "# Disabled by SoundB0ard")
string(REPLACE "add_subdirectory(examples)"
"# add_subdirectory(examples) # Disabled by SoundB0ard"
ABLETON_CMAKE_CONTENT "${ABLETON_CMAKE_CONTENT}")
file(WRITE "${ableton_link_SOURCE_DIR}/CMakeLists.txt"
"${ABLETON_CMAKE_CONTENT}")
message(STATUS "Patched Ableton Link to exclude examples")
endif()
# Now add the subdirectory manually
add_subdirectory(${ableton_link_SOURCE_DIR} ${ableton_link_BINARY_DIR})
# Configure Ableton Link target
include(${ableton_link_SOURCE_DIR}/cmake_include/ConfigureAbletonLink.cmake)
configureabletonlink(${ableton_link_SOURCE_DIR})
# Fetch PerlinNoise
FetchContent_Declare(
perlin_noise
GIT_REPOSITORY https://github.com/Reputeless/PerlinNoise.git
GIT_TAG v3.0.0)
FetchContent_MakeAvailable(perlin_noise)
# nlohmann/json: prefer system install (brew/apt), fall back to FetchContent
find_package(nlohmann_json 3.11 QUIET)
if(NOT nlohmann_json_FOUND)
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3)
FetchContent_MakeAvailable(nlohmann_json)
endif()
# WebSocket uses a custom POSIX-socket implementation (no WebSocketPP/ASIO
# needed)
# Try to find system libraries using pkg-config first, then fallback to
# find_library
pkg_check_modules(PORTAUDIO portaudio-2.0)
if(NOT PORTAUDIO_FOUND)
find_library(
PORTAUDIO_LIB portaudio
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib64
/opt/homebrew/lib # Apple Silicon Homebrew
NO_DEFAULT_PATH)
if(NOT PORTAUDIO_LIB)
# Fallback to default search
find_library(PORTAUDIO_LIB portaudio)
endif()
if(NOT PORTAUDIO_LIB)
message(
FATAL_ERROR
"PortAudio library not found. Install with: "
"brew install portaudio (macOS) or "
"sudo apt install portaudio19-dev (Ubuntu/Debian)")
endif()
endif()
# PortMidi doesn't always provide pkg-config files, so try find_library first
find_library(
PORTMIDI_LIB portmidi
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib64
/opt/homebrew/lib # Apple Silicon Homebrew
NO_DEFAULT_PATH)
if(NOT PORTMIDI_LIB)
# Fallback to default search
find_library(PORTMIDI_LIB portmidi)
endif()
if(NOT PORTMIDI_LIB)
message(
FATAL_ERROR
"PortMidi library not found. Install with: "
"brew install portmidi (macOS) or "
"sudo apt install libportmidi-dev (Ubuntu/Debian)")
endif()
pkg_check_modules(SNDFILE sndfile)
if(NOT SNDFILE_FOUND)
find_library(SNDFILE_LIB sndfile)
if(NOT SNDFILE_LIB)
message(
FATAL_ERROR
"libsndfile library not found. Install with: "
"sudo apt install libsndfile1-dev (Ubuntu/Debian) or equivalent")
endif()
endif()
project(
Sbsh
VERSION 3.0
DESCRIPTION "Soundb0ard - Interactive Music Making Environment"
LANGUAGES CXX)
# Find Threads after project declaration
find_package(Threads REQUIRED)
# Find GNU readline + history. On macOS the system "readline" is really libedit
# and is missing functions we use (rl_event_hook, append_history, ...), so we
# require Homebrew's readline. Look it up through Homebrew's stable opt/ link
# rather than the versioned Cellar/ dir (or its pkg-config file, which also
# hardcodes the Cellar path), so a `brew upgrade` doesn't leave existing build
# dirs pointing at a deleted dir.
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
set(HOMEBREW_READLINE_PREFIXES /opt/homebrew/opt/readline
/usr/local/opt/readline)
find_path(
READLINE_INCLUDE_DIR readline/readline.h
PATHS ${HOMEBREW_READLINE_PREFIXES}
PATH_SUFFIXES include
NO_DEFAULT_PATH)
find_library(
READLINE_LIB readline
PATHS ${HOMEBREW_READLINE_PREFIXES}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
find_library(
HISTORY_LIB history
PATHS ${HOMEBREW_READLINE_PREFIXES}
PATH_SUFFIXES lib
NO_DEFAULT_PATH)
if(NOT READLINE_INCLUDE_DIR
OR NOT READLINE_LIB
OR NOT HISTORY_LIB)
message(FATAL_ERROR "GNU readline not found. Install with: "
"brew install readline")
endif()
message(STATUS "Using Homebrew readline: ${READLINE_LIB}")
else()
# Linux and other platforms
find_library(
READLINE_LIB readline
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib64
NO_DEFAULT_PATH)
if(NOT READLINE_LIB)
find_library(READLINE_LIB readline)
endif()
find_library(
HISTORY_LIB history
PATHS /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu /usr/lib64
NO_DEFAULT_PATH)
if(NOT HISTORY_LIB)
find_library(HISTORY_LIB history)
endif()
if(NOT READLINE_LIB OR NOT HISTORY_LIB)
message(FATAL_ERROR "Readline/history library not found. Install with: "
"sudo apt install libreadline-dev (Ubuntu/Debian)")
endif()
endif()
# Explicitly set platform definitions for Link (now that CMAKE_SYSTEM_NAME is
# available)
message(STATUS "System name: ${CMAKE_SYSTEM_NAME}")
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
message(STATUS "Setting LINK_PLATFORM_LINUX=1")
set_property(
TARGET Ableton::Link
APPEND
PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_LINUX=1)
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
message(STATUS "Setting LINK_PLATFORM_MACOSX=1")
set_property(
TARGET Ableton::Link
APPEND
PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_MACOSX=1)
else()
message(STATUS "System name '${CMAKE_SYSTEM_NAME}' - "
"no additional Link platform definition needed")
endif()
file(GLOB_RECURSE Sbsh_SOURCES CONFIGURE_DEPENDS "src/*.cpp")
file(GLOB_RECURSE Sbsh_HEADERS CONFIGURE_DEPENDS "src/*.h")
# WebSocket sources use custom POSIX implementation (no ASIO conflicts)
set(SBSH_INCLUDE_DIRS "")
foreach(header_file ${Sbsh_HEADERS})
get_filename_component(header_dir ${header_file} PATH)
list(APPEND SBSH_INCLUDE_DIRS ${header_dir})
endforeach()
list(REMOVE_DUPLICATES SBSH_INCLUDE_DIRS)
# Create shared library from all sources except main.cpp This is used by both
# the main executable and tests
set(SBSH_SOURCES_NO_MAIN ${Sbsh_SOURCES})
list(FILTER SBSH_SOURCES_NO_MAIN EXCLUDE REGEX "src/main\\.cpp$")
add_library(sbsh_lib STATIC ${SBSH_SOURCES_NO_MAIN})
# Set include directories on sbsh_lib (PUBLIC so they propagate to dependents)
target_include_directories(sbsh_lib PUBLIC ${SBSH_INCLUDE_DIRS})
target_include_directories(sbsh_lib PUBLIC ${perlin_noise_SOURCE_DIR})
if(PORTAUDIO_FOUND)
target_include_directories(sbsh_lib PUBLIC ${PORTAUDIO_INCLUDE_DIRS})
endif()
if(SNDFILE_FOUND)
target_include_directories(sbsh_lib PUBLIC ${SNDFILE_INCLUDE_DIRS})
endif()
# Add readline include directories (macOS/Homebrew only; Linux uses system
# headers)
if(READLINE_INCLUDE_DIR)
target_include_directories(sbsh_lib PUBLIC ${READLINE_INCLUDE_DIR})
endif()
# Set compiler flags on sbsh_lib
target_compile_options(
sbsh_lib PUBLIC $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic>
$<$<CONFIG:Release>:-O3> $<$<CONFIG:Debug>:-g -O0>)
# Add sanitizers for debug builds
option(ENABLE_SANITIZERS
"Enable AddressSanitizer and UBSanitizer for debug builds" ON)
if(ENABLE_SANITIZERS AND CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Enabling sanitizers for debug build")
target_compile_options(
sbsh_lib PUBLIC -fsanitize=address -fsanitize=undefined
-fno-omit-frame-pointer -fno-optimize-sibling-calls)
target_link_options(sbsh_lib PUBLIC -fsanitize=address -fsanitize=undefined)
endif()
# Link libraries to sbsh_lib
target_link_libraries(sbsh_lib PUBLIC Ableton::Link Threads::Threads
nlohmann_json::nlohmann_json)
# Link readline libraries
target_link_libraries(sbsh_lib PUBLIC ${READLINE_LIB} ${HISTORY_LIB})
# Link audio libraries
if(PORTAUDIO_FOUND)
target_link_libraries(sbsh_lib PUBLIC ${PORTAUDIO_LIBRARIES})
target_compile_options(sbsh_lib PUBLIC ${PORTAUDIO_CFLAGS_OTHER})
else()
target_link_libraries(sbsh_lib PUBLIC ${PORTAUDIO_LIB})
endif()
target_link_libraries(sbsh_lib PUBLIC ${PORTMIDI_LIB})
if(SNDFILE_FOUND)
target_link_libraries(sbsh_lib PUBLIC ${SNDFILE_LIBRARIES})
target_compile_options(sbsh_lib PUBLIC ${SNDFILE_CFLAGS_OTHER})
else()
target_link_libraries(sbsh_lib PUBLIC ${SNDFILE_LIB})
endif()
# macOS frameworks
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
target_link_libraries(
sbsh_lib PUBLIC "-framework CoreFoundation" "-framework CoreAudio"
"-framework CoreMIDI")
endif()
# Main executable - just main.cpp linking against sbsh_lib
add_executable(Sbsh src/main.cpp)
target_link_libraries(Sbsh sbsh_lib)
if(CTAGS_PROGRAM)
add_dependencies(Sbsh tags)
endif()
# Installation rules
install(TARGETS Sbsh RUNTIME DESTINATION bin)
install(
DIRECTORY wavs/
DESTINATION share/sbsh/wavs
PATTERN "*.wav"
PATTERN "*.aiff"
PATTERN "*.aif"
PATTERN "*.mp3")
install(DIRECTORY settings/ DESTINATION share/sbsh/settings)
install(FILES startup.sb DESTINATION share/sbsh/)
# Optional tests
option(BUILD_TESTS "Build tests" OFF)
if(BUILD_TESTS)
enable_testing()
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.13.0)
FetchContent_MakeAvailable(googletest)
# sbsh_lib is already built above, tests just link against it
include(GoogleTest)
# Test globals file provides global variables needed by sbsh_lib
set(TEST_GLOBALS_SOURCE ${CMAKE_SOURCE_DIR}/tests/test_globals.cpp)
# Helper function to create test executables
function(add_sbsh_test test_name test_source)
add_executable(${test_name} ${test_source} ${TEST_GLOBALS_SOURCE})
target_link_libraries(${test_name} sbsh_lib GTest::gtest_main)
gtest_discover_tests(${test_name})
endfunction()
# Simple unit tests
add_sbsh_test(stepper_test tests/stepper_test.cpp)
add_sbsh_test(object_test tests/object_test.cpp)
add_sbsh_test(euclidean_test tests/euclidean.cpp)
add_sbsh_test(audioutils_test tests/audioutils.cpp)
add_sbsh_test(ramp_test tests/ramp_test.cpp)
add_sbsh_test(sbfft_test tests/sbfft_test.cpp)
# Interpreter tests
add_sbsh_test(lexer_test tests/lexer_test.cpp)
# TODO: parser_test needs updates for new ProcessStatement API
# add_sbsh_test(parser_test tests/parser_test.cpp)
add_sbsh_test(evaluator_test tests/evaluator_test.cpp)
add_sbsh_test(help_test tests/help_test.cpp)
# Pattern parser tests
add_sbsh_test(pattern_parser_test tests/pattern_parser_test.cpp)
add_sbsh_test(pattern_tokenizer_test tests/pattern_tokenizer_test.cpp)
# TODO: pattern_functions_test needs updates for new API
# add_sbsh_test(pattern_functions_test tests/pattern_functions.cpp)
endif()
# Development and linting targets
option(BUILD_LINTING_TARGETS "Build linting and development targets" ON)
if(BUILD_LINTING_TARGETS)
# Find source files for linting
file(GLOB_RECURSE ALL_CPP_FILES "${CMAKE_SOURCE_DIR}/src/*.cpp"
"${CMAKE_SOURCE_DIR}/src/*.c")
file(GLOB_RECURSE ALL_HEADER_FILES "${CMAKE_SOURCE_DIR}/src/*.h"
"${CMAKE_SOURCE_DIR}/src/*.hpp")
set(ALL_SOURCE_FILES ${ALL_CPP_FILES} ${ALL_HEADER_FILES})
# Format target
add_custom_target(
format
COMMAND clang-format -i ${ALL_SOURCE_FILES}
COMMENT "Formatting C++ code with clang-format"
VERBATIM)
# Static analysis target
add_custom_target(
cppcheck
COMMAND
cppcheck --enable=warning,style,performance --std=c++20
--suppress=missingIncludeSystem --suppress=unusedFunction
--error-exitcode=1 --inline-suppr ${ALL_CPP_FILES}
COMMENT "Running static analysis with cppcheck"
VERBATIM)
# Memory pattern check target
add_custom_target(
check-memory-patterns
COMMAND ${CMAKE_COMMAND} -P
${CMAKE_SOURCE_DIR}/cmake/CheckMemoryPatterns.cmake
COMMENT "Checking for dangerous memory patterns"
VERBATIM)
# Pre-commit hooks target
add_custom_target(
run-hooks
COMMAND pre-commit run --all-files
COMMENT "Running pre-commit hooks on all files"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM)
# Install pre-commit hooks target
add_custom_target(
install-hooks
COMMAND pre-commit install
COMMENT "Installing pre-commit hooks"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
VERBATIM)
# Comprehensive lint target
add_custom_target(
lint
DEPENDS format cppcheck check-memory-patterns
COMMENT "Running all linting tools")
# Development target (format + build)
add_custom_target(
dev
DEPENDS format Sbsh
COMMENT "Development cycle: format + build")
# Sanitizer test target (only available in debug builds)
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND ENABLE_SANITIZERS)
add_custom_target(
test-sanitizers
COMMAND
${CMAKE_COMMAND} -E env
ASAN_OPTIONS=check_initialization_order=1:strict_init_order=1
$<TARGET_FILE:Sbsh> --version || echo "Sanitizer test completed"
DEPENDS Sbsh
COMMENT "Running with AddressSanitizer enabled"
VERBATIM)
endif()
# All checks target
add_custom_target(
check-all
DEPENDS lint Sbsh
COMMENT "Running all checks: lint + build + test")
if(BUILD_TESTS)
add_dependencies(check-all stepper_test)
endif()
message(STATUS "Development targets enabled:")
message(STATUS " make format - Format code with clang-format")
message(STATUS " make cppcheck - Run static analysis")
message(STATUS " make lint - Run all linting tools")
message(STATUS " make dev - Quick development cycle")
message(STATUS " make install-hooks - Install pre-commit hooks")
message(STATUS " make run-hooks - Run pre-commit hooks")
message(STATUS " make check-all - Run all checks")
endif()
# CPack configuration for packaging
include(CPack)
set(CPACK_PACKAGE_NAME "Soundb0ard")
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Interactive Music Making Environment")
set(CPACK_PACKAGE_VENDOR "Soundb0ard Project")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")