From 51496c319022a94c10a4db1e8670b541d63be250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 20 Jun 2026 14:16:19 -0400 Subject: [PATCH 1/4] modules: add the slim halp C++20 module (avnd_halp_module) include/halp/halp.cppm exports the halp convenience layer (import halp;) pulling in only halp's own deps -- no binding/score/ossia/Qt -- so it builds standalone. Built into avnd_halp_module (gated on CMAKE_CXX_SCAN_FOR_MODULES), reused by every backend. Validated: builds with gcc-16 via avendish's own CMake (FILE_SET CXX_MODULES). --- cmake/avendish.cmake | 1 + cmake/avendish.modules.cmake | 15 +++++ include/halp/halp.cppm | 120 +++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 cmake/avendish.modules.cmake create mode 100644 include/halp/halp.cppm diff --git a/cmake/avendish.cmake b/cmake/avendish.cmake index be0663ff..ad3a016d 100644 --- a/cmake/avendish.cmake +++ b/cmake/avendish.cmake @@ -259,6 +259,7 @@ add_library(avnd_dummy_lib OBJECT "${AVND_SOURCE_DIR}/src/dummy.cpp") include(avendish.dependencies) include(avendish.disableexceptions) include(avendish.sources) +include(avendish.modules) include(avendish.tools) include(avendish.ui.qt) diff --git a/cmake/avendish.modules.cmake b/cmake/avendish.modules.cmake new file mode 100644 index 00000000..ae5f7a61 --- /dev/null +++ b/cmake/avendish.modules.cmake @@ -0,0 +1,15 @@ +# The `halp` C++20 module (avnd_halp_module). Lets user objects `import halp;` +# instead of including the heavy halp headers; built once and reused by every +# backend target. Only available when the toolchain/generator scans for modules. +if(CMAKE_CXX_SCAN_FOR_MODULES AND NOT TARGET avnd_halp_module) + add_library(avnd_halp_module STATIC) + target_sources(avnd_halp_module + PUBLIC + FILE_SET CXX_MODULES + FILES + "${AVND_SOURCE_DIR}/include/halp/halp.cppm" + ) + # Avendish carries the avnd/halp include dirs plus boost / fmt / magic_enum. + target_link_libraries(avnd_halp_module PUBLIC Avendish::Avendish) + target_compile_features(avnd_halp_module PUBLIC cxx_std_23 INTERFACE cxx_std_23) +endif() diff --git a/include/halp/halp.cppm b/include/halp/halp.cppm new file mode 100644 index 00000000..b0a6fa2d --- /dev/null +++ b/include/halp/halp.cppm @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-3.0-or-later OR BSL-1.0 OR CC0-1.0 OR CC-PDCC OR 0BSD + +// The `halp` C++20 module: exports the halp convenience layer so user objects can +// `import halp;` instead of including the (heavy) halp headers. Only halp's own +// dependencies are pulled in -- no binding / score / ossia / Qt -- so this builds +// standalone. Built into the `avnd_halp_module` target (CMAKE_CXX_SCAN_FOR_MODULES). +// +// The global module fragment includes everything halp transitively needs so those +// declarations stay attached to the global module; the purview then includes the +// halp headers with HALP_MODULE_BUILD set, which turns their HALP_MODULE_EXPORT +// markers into real `export`s. + +module; + +// --- std --- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --- halp's third-party + avnd dependencies (top-level roots; each pulls its tree) --- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +export module halp; + +#define HALP_MODULE_BUILD 1 + +// --- the exported halp API --- +// Note: halp/gradient_port.hpp is intentionally omitted -- it instantiates a +// boost::container::flat_map in an exported context, which exposes a TU-local +// entity (ill-formed in a module interface). Gradient ports remain header-only. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include From 14fb5636e1988c82371d77cdb65f350fbb3c7d08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 20 Jun 2026 14:24:53 -0400 Subject: [PATCH 2/4] modules: shared prototype + MODULE path (dump backend) A user object written as a .cppm module (export module ; import halp;) now builds through the same per-backend prototype as a header object: the prototype's single "pull in the user code" line becomes @AVND_MAIN_IMPORT@, configured to either #include the header or import the module -- no duplicated module_prototype files. Helpers (avendish.modules.cmake): avnd_main_import (the prototype line), and avnd_add_object_to_base / avnd_add_object_to_backend, which compile a .cppm once in the object's base library (exported BMI) and have backend targets link it to import, or compile a header as before. Module-ness is auto-detected from the extension; the module name is the object's C_NAME by convention. Wired through avnd_register (base) + the dump backend. Validated with gcc-16: the non-module dump path is unchanged and a .cppm object builds, links, and runs. --- cmake/avendish.cmake | 2 +- cmake/avendish.dump.cmake | 3 +- cmake/avendish.modules.cmake | 48 ++++++++++++++++++++-- include/avnd/binding/dump/prototype.cpp.in | 2 +- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/cmake/avendish.cmake b/cmake/avendish.cmake index ad3a016d..74434f6d 100644 --- a/cmake/avendish.cmake +++ b/cmake/avendish.cmake @@ -287,7 +287,7 @@ function(avnd_register) add_library("${AVND_TARGET}" STATIC $) endif() - target_sources("${AVND_TARGET}" PRIVATE "${AVND_MAIN_FILE}") + avnd_add_object_to_base("${AVND_TARGET}" "${AVND_MAIN_FILE}") if(AVND_COMPILE_OPTIONS) target_compile_options("${AVND_TARGET}" PUBLIC "${AVND_COMPILE_OPTIONS}") endif() diff --git a/cmake/avendish.dump.cmake b/cmake/avendish.dump.cmake index 338beba1..15a29e2d 100644 --- a/cmake/avendish.dump.cmake +++ b/cmake/avendish.dump.cmake @@ -15,6 +15,7 @@ function(avnd_make_dump) string(MAKE_C_IDENTIFIER "${AVND_MAIN_CLASS}" MAIN_OUT_FILE) + avnd_main_import(AVND_MAIN_IMPORT "${AVND_MAIN_FILE}" "${AVND_C_NAME}") configure_file( "${AVND_SOURCE_DIR}/include/avnd/binding/dump/prototype.cpp.in" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_dump.cpp" @@ -31,10 +32,10 @@ function(avnd_make_dump) RUNTIME_OUTPUT_DIRECTORY dump ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_dump.cpp" ) diff --git a/cmake/avendish.modules.cmake b/cmake/avendish.modules.cmake index ae5f7a61..55e39f22 100644 --- a/cmake/avendish.modules.cmake +++ b/cmake/avendish.modules.cmake @@ -1,6 +1,47 @@ -# The `halp` C++20 module (avnd_halp_module). Lets user objects `import halp;` -# instead of including the heavy halp headers; built once and reused by every -# backend target. Only available when the toolchain/generator scans for modules. +# C++20 module support. +# +# A user object can be written as a module (`export module ; import halp;`) +# in a .cppm MAIN_FILE instead of a header. The same per-backend prototype then +# either `#include`s the header or `import`s the module -- only that one line +# differs -- and a .cppm is added to the backend target as a C++20 module source +# linking the shared halp module. Module-ness is auto-detected from the .cppm +# extension; the module name is the object's C_NAME by convention. + +# Line a prototype uses to pull in the user object. +function(avnd_main_import out main_file c_name) + if("${main_file}" MATCHES "\\.cppm$") + set("${out}" "import ${c_name};" PARENT_SCOPE) + else() + set("${out}" "#include <${main_file}>" PARENT_SCOPE) + endif() +endfunction() + +# Add the user object to its base library. A .cppm is compiled once here as an +# exported C++20 module (its BMI is shared with backend targets that link this lib); +# a header is a plain private source as before. +function(avnd_add_object_to_base base main_file) + if("${main_file}" MATCHES "\\.cppm$") + target_sources(${base} PUBLIC FILE_SET CXX_MODULES FILES "${main_file}") + if(TARGET avnd_halp_module) + target_link_libraries(${base} PUBLIC avnd_halp_module) + endif() + else() + target_sources(${base} PRIVATE "${main_file}") + endif() +endfunction() + +# Make a backend target consume the user object: for a module, link its base library +# (so the prototype's `import` resolves) instead of recompiling it; for a header, +# compile it into the backend TU as before. +function(avnd_add_object_to_backend backend base main_file) + if("${main_file}" MATCHES "\\.cppm$") + target_link_libraries(${backend} PRIVATE ${base}) + else() + target_sources(${backend} PRIVATE "${main_file}") + endif() +endfunction() + +# The slim halp module, built once and reused by every backend target. if(CMAKE_CXX_SCAN_FOR_MODULES AND NOT TARGET avnd_halp_module) add_library(avnd_halp_module STATIC) target_sources(avnd_halp_module @@ -9,7 +50,6 @@ if(CMAKE_CXX_SCAN_FOR_MODULES AND NOT TARGET avnd_halp_module) FILES "${AVND_SOURCE_DIR}/include/halp/halp.cppm" ) - # Avendish carries the avnd/halp include dirs plus boost / fmt / magic_enum. target_link_libraries(avnd_halp_module PUBLIC Avendish::Avendish) target_compile_features(avnd_halp_module PUBLIC cxx_std_23 INTERFACE cxx_std_23) endif() diff --git a/include/avnd/binding/dump/prototype.cpp.in b/include/avnd/binding/dump/prototype.cpp.in index cbeca65a..d1c438d4 100644 --- a/include/avnd/binding/dump/prototype.cpp.in +++ b/include/avnd/binding/dump/prototype.cpp.in @@ -13,7 +13,7 @@ struct config }; } -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ int main(int argc, char** argv) { using type = decltype(avnd::configure())::type; From 5243728a28d1fd610cc863dfd67628d6f3395e63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 20 Jun 2026 14:31:53 -0400 Subject: [PATCH 3/4] modules: wire the MODULE path through every backend Each backend prototype now uses the shared @AVND_MAIN_IMPORT@ line (computed once in _avnd_dispatch_backend, or explicitly for dump/ossia/example_host) and consumes the user object via avnd_add_object_to_backend (link the base library that compiled the .cppm, or source the header as before). No duplicated per-backend module prototypes. Full configure is clean across all backends; the non-module path is unchanged and a .cppm object builds + runs through the dump backend (gcc-16). Known limitation (next): GCC requires a module BMI's exception/rtti dialect to match its consumers, but avendish backends differ (dump uses exceptions, plugin backends use DisableExceptions / -fno-exceptions -fno-rtti), so one shared module can't yet serve both -- a dialect decision is needed. --- cmake/avendish.clap.cmake | 2 +- cmake/avendish.cmake | 4 ++++ cmake/avendish.example.cmake | 4 +++- cmake/avendish.fuzz.cmake | 2 +- cmake/avendish.godot.cmake | 2 +- cmake/avendish.gstreamer.cmake | 2 +- cmake/avendish.max.cmake | 2 +- cmake/avendish.ossia.cmake | 3 ++- cmake/avendish.pd.cmake | 2 +- cmake/avendish.python.cmake | 2 +- cmake/avendish.standalone.cmake | 2 +- cmake/avendish.touchdesigner.cmake | 2 +- cmake/avendish.vintage.cmake | 2 +- cmake/avendish.vst3.cmake | 2 +- cmake/avendish.wasm.cmake | 2 +- include/avnd/binding/clap/prototype.cpp.in | 2 +- include/avnd/binding/example/prototype.cpp.in | 2 +- include/avnd/binding/fuzz/prototype.cpp.in | 2 +- include/avnd/binding/godot/prototype.cpp.in | 2 +- include/avnd/binding/gstreamer/prototype.cpp.in | 2 +- include/avnd/binding/max/prototype.cpp.in | 2 +- include/avnd/binding/ossia/prototype.cpp.in | 2 +- include/avnd/binding/pd/prototype.cpp.in | 2 +- include/avnd/binding/python/prototype.cpp.in | 2 +- include/avnd/binding/standalone/prototype.cpp.in | 2 +- include/avnd/binding/vintage/prototype.cpp.in | 2 +- include/avnd/binding/vst3/prototype.cpp.in | 2 +- include/avnd/binding/wasm/prototype.cpp.in | 2 +- 28 files changed, 34 insertions(+), 27 deletions(-) diff --git a/cmake/avendish.clap.cmake b/cmake/avendish.clap.cmake index feac702b..623e5ce3 100644 --- a/cmake/avendish.clap.cmake +++ b/cmake/avendish.clap.cmake @@ -43,10 +43,10 @@ function(avnd_make_clap) NEWLINE_STYLE LF ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${AVND_C_NAME}_clap.cpp" ) diff --git a/cmake/avendish.cmake b/cmake/avendish.cmake index 74434f6d..5f81c88c 100644 --- a/cmake/avendish.cmake +++ b/cmake/avendish.cmake @@ -47,6 +47,10 @@ function(_avnd_dispatch_backend backend) if(DEFINED ${_flag} AND NOT ${${_flag}}) return() endif() + # Compute the prototype's object-pull-in line once; the called backend's + # configure_file picks up AVND_MAIN_IMPORT from this (calling) scope. + cmake_parse_arguments(AVND "" "MAIN_FILE;C_NAME" "" ${ARGN}) + avnd_main_import(AVND_MAIN_IMPORT "${AVND_MAIN_FILE}" "${AVND_C_NAME}") cmake_language(CALL "avnd_make_${backend}" ${ARGN}) endfunction() diff --git a/cmake/avendish.example.cmake b/cmake/avendish.example.cmake index df8a507f..33ec0fae 100644 --- a/cmake/avendish.example.cmake +++ b/cmake/avendish.example.cmake @@ -1,7 +1,7 @@ include(CTest) function(avnd_make_example_host) - cmake_parse_arguments(AVND "" "TARGET;MAIN_FILE;MAIN_CLASS" "" ${ARGN}) + cmake_parse_arguments(AVND "" "TARGET;MAIN_FILE;MAIN_CLASS;C_NAME" "" ${ARGN}) set(AVND_FX_TARGET "${AVND_TARGET}_example_host") if(TARGET "${AVND_FX_TARGET}") @@ -11,6 +11,7 @@ function(avnd_make_example_host) string(MAKE_C_IDENTIFIER "${AVND_MAIN_CLASS}" MAIN_OUT_FILE) + avnd_main_import(AVND_MAIN_IMPORT "${AVND_MAIN_FILE}" "${AVND_C_NAME}") configure_file( "${AVND_SOURCE_DIR}/include/avnd/binding/example/prototype.cpp.in" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_example_host.cpp" @@ -25,6 +26,7 @@ function(avnd_make_example_host) PROPERTIES RUNTIME_OUTPUT_DIRECTORY example ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE diff --git a/cmake/avendish.fuzz.cmake b/cmake/avendish.fuzz.cmake index b04b8cbb..42de63b9 100644 --- a/cmake/avendish.fuzz.cmake +++ b/cmake/avendish.fuzz.cmake @@ -47,10 +47,10 @@ function(avnd_make_fuzz) RUNTIME_OUTPUT_DIRECTORY fuzz ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_fuzz.cpp" ) diff --git a/cmake/avendish.godot.cmake b/cmake/avendish.godot.cmake index d984ca94..d193e9a8 100644 --- a/cmake/avendish.godot.cmake +++ b/cmake/avendish.godot.cmake @@ -173,10 +173,10 @@ function(avnd_make_godot) PUBLIC cxx_std_23 ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_godot.cpp" ${AVND_GODOT_SOURCES} ) diff --git a/cmake/avendish.gstreamer.cmake b/cmake/avendish.gstreamer.cmake index 11d2f0dc..c3d002dd 100644 --- a/cmake/avendish.gstreamer.cmake +++ b/cmake/avendish.gstreamer.cmake @@ -63,10 +63,10 @@ function(avnd_make_gstreamer) ARCHIVE_OUTPUT_DIRECTORY gstreamer ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${AVND_SOURCE_DIR}/include/avnd/binding/gstreamer/audio_source.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/gstreamer/audio_sink.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/gstreamer/audio_filter.hpp" diff --git a/cmake/avendish.max.cmake b/cmake/avendish.max.cmake index 44f5f622..3aed5481 100644 --- a/cmake/avendish.max.cmake +++ b/cmake/avendish.max.cmake @@ -123,10 +123,10 @@ function(avnd_make_max) AVND_C_NAME "${AVND_C_NAME}" ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_max.cpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/all.hpp" diff --git a/cmake/avendish.ossia.cmake b/cmake/avendish.ossia.cmake index f88962b7..956b8213 100644 --- a/cmake/avendish.ossia.cmake +++ b/cmake/avendish.ossia.cmake @@ -36,6 +36,7 @@ function(avnd_make_ossia) string(MAKE_C_IDENTIFIER "${AVND_MAIN_CLASS}" MAIN_OUT_FILE) + avnd_main_import(AVND_MAIN_IMPORT "${AVND_MAIN_FILE}" "${AVND_C_NAME}") configure_file( "${AVND_SOURCE_DIR}/include/avnd/binding/ossia/prototype.cpp.in" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_ossia.cpp" @@ -50,10 +51,10 @@ function(avnd_make_ossia) RUNTIME_OUTPUT_DIRECTORY ossia ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_ossia.cpp" ) diff --git a/cmake/avendish.pd.cmake b/cmake/avendish.pd.cmake index d14b62e9..aa4617d6 100644 --- a/cmake/avendish.pd.cmake +++ b/cmake/avendish.pd.cmake @@ -68,10 +68,10 @@ function(avnd_make_pd) ARCHIVE_OUTPUT_DIRECTORY pd ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_pd.cpp" ) diff --git a/cmake/avendish.python.cmake b/cmake/avendish.python.cmake index f48be6f9..bec26656 100644 --- a/cmake/avendish.python.cmake +++ b/cmake/avendish.python.cmake @@ -54,10 +54,10 @@ function(avnd_make_python) ) endif() + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_python.cpp" ) diff --git a/cmake/avendish.standalone.cmake b/cmake/avendish.standalone.cmake index d2c1d0ff..e22e5c7a 100644 --- a/cmake/avendish.standalone.cmake +++ b/cmake/avendish.standalone.cmake @@ -80,10 +80,10 @@ function(avnd_make_standalone) RUNTIME_OUTPUT_DIRECTORY standalone ) endif() + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_standalone.cpp" ) diff --git a/cmake/avendish.touchdesigner.cmake b/cmake/avendish.touchdesigner.cmake index 0408f167..b584bf82 100644 --- a/cmake/avendish.touchdesigner.cmake +++ b/cmake/avendish.touchdesigner.cmake @@ -69,10 +69,10 @@ function(avnd_make_touchdesigner) add_library(${AVND_FX_TARGET} MODULE) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_touchdesigner.cpp" ${AVND_TD_SOURCES} ) diff --git a/cmake/avendish.vintage.cmake b/cmake/avendish.vintage.cmake index 1768eade..2aa2c848 100644 --- a/cmake/avendish.vintage.cmake +++ b/cmake/avendish.vintage.cmake @@ -27,10 +27,10 @@ function(avnd_make_vintage) NEWLINE_STYLE LF ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${AVND_C_NAME}_vintage.cpp" ) diff --git a/cmake/avendish.vst3.cmake b/cmake/avendish.vst3.cmake index 27c9d501..8ced8789 100644 --- a/cmake/avendish.vst3.cmake +++ b/cmake/avendish.vst3.cmake @@ -50,10 +50,10 @@ function(avnd_make_vst3) NEWLINE_STYLE LF ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${AVND_C_NAME}_vst3.cpp" ) diff --git a/cmake/avendish.wasm.cmake b/cmake/avendish.wasm.cmake index 3659b5e9..f00865a8 100644 --- a/cmake/avendish.wasm.cmake +++ b/cmake/avendish.wasm.cmake @@ -69,10 +69,10 @@ function(avnd_make_wasm) AVND_C_NAME "${AVND_C_NAME}" ) + avnd_add_object_to_backend(${AVND_FX_TARGET} ${AVND_TARGET} "${AVND_MAIN_FILE}") target_sources( ${AVND_FX_TARGET} PRIVATE - "${AVND_MAIN_FILE}" "${CMAKE_BINARY_DIR}/${MAIN_OUT_FILE}_wasm.cpp" ) diff --git a/include/avnd/binding/clap/prototype.cpp.in b/include/avnd/binding/clap/prototype.cpp.in index 5698c61c..5c8063ce 100644 --- a/include/avnd/binding/clap/prototype.cpp.in +++ b/include/avnd/binding/clap/prototype.cpp.in @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include diff --git a/include/avnd/binding/example/prototype.cpp.in b/include/avnd/binding/example/prototype.cpp.in index 7bd68fc1..f5bacd4f 100644 --- a/include/avnd/binding/example/prototype.cpp.in +++ b/include/avnd/binding/example/prototype.cpp.in @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include using type = decltype(avnd::configure())::type; diff --git a/include/avnd/binding/fuzz/prototype.cpp.in b/include/avnd/binding/fuzz/prototype.cpp.in index bd4def02..78cd650b 100644 --- a/include/avnd/binding/fuzz/prototype.cpp.in +++ b/include/avnd/binding/fuzz/prototype.cpp.in @@ -12,7 +12,7 @@ struct config }; } -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ // libFuzzer provides main(); we only supply the per-input entry point. extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) diff --git a/include/avnd/binding/godot/prototype.cpp.in b/include/avnd/binding/godot/prototype.cpp.in index a4cc0f44..eb8dd868 100644 --- a/include/avnd/binding/godot/prototype.cpp.in +++ b/include/avnd/binding/godot/prototype.cpp.in @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include diff --git a/include/avnd/binding/gstreamer/prototype.cpp.in b/include/avnd/binding/gstreamer/prototype.cpp.in index 3974b778..f91db44c 100644 --- a/include/avnd/binding/gstreamer/prototype.cpp.in +++ b/include/avnd/binding/gstreamer/prototype.cpp.in @@ -3,7 +3,7 @@ // clang-format off #include #include -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ using type = decltype(avnd::configure())::type; diff --git a/include/avnd/binding/max/prototype.cpp.in b/include/avnd/binding/max/prototype.cpp.in index ab913458..9f071456 100644 --- a/include/avnd/binding/max/prototype.cpp.in +++ b/include/avnd/binding/max/prototype.cpp.in @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include #include diff --git a/include/avnd/binding/ossia/prototype.cpp.in b/include/avnd/binding/ossia/prototype.cpp.in index d385d1bf..6e044942 100644 --- a/include/avnd/binding/ossia/prototype.cpp.in +++ b/include/avnd/binding/ossia/prototype.cpp.in @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include diff --git a/include/avnd/binding/pd/prototype.cpp.in b/include/avnd/binding/pd/prototype.cpp.in index 5cefe039..25dab0e9 100644 --- a/include/avnd/binding/pd/prototype.cpp.in +++ b/include/avnd/binding/pd/prototype.cpp.in @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include #include diff --git a/include/avnd/binding/python/prototype.cpp.in b/include/avnd/binding/python/prototype.cpp.in index 2ff0863a..45c3d54d 100644 --- a/include/avnd/binding/python/prototype.cpp.in +++ b/include/avnd/binding/python/prototype.cpp.in @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include diff --git a/include/avnd/binding/standalone/prototype.cpp.in b/include/avnd/binding/standalone/prototype.cpp.in index c21a5fa5..fceb57b2 100644 --- a/include/avnd/binding/standalone/prototype.cpp.in +++ b/include/avnd/binding/standalone/prototype.cpp.in @@ -6,7 +6,7 @@ #include -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #if AVND_STANDALONE_QML #include diff --git a/include/avnd/binding/vintage/prototype.cpp.in b/include/avnd/binding/vintage/prototype.cpp.in index 5d4af621..7b535b66 100644 --- a/include/avnd/binding/vintage/prototype.cpp.in +++ b/include/avnd/binding/vintage/prototype.cpp.in @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include diff --git a/include/avnd/binding/vst3/prototype.cpp.in b/include/avnd/binding/vst3/prototype.cpp.in index 6e271b2a..b420c9e7 100644 --- a/include/avnd/binding/vst3/prototype.cpp.in +++ b/include/avnd/binding/vst3/prototype.cpp.in @@ -5,7 +5,7 @@ #include // clang-format off -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ // Needed everywhere bool InitModule() { return true; } diff --git a/include/avnd/binding/wasm/prototype.cpp.in b/include/avnd/binding/wasm/prototype.cpp.in index ce6d31c0..045e38a5 100644 --- a/include/avnd/binding/wasm/prototype.cpp.in +++ b/include/avnd/binding/wasm/prototype.cpp.in @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -#include <@AVND_MAIN_FILE@> +@AVND_MAIN_IMPORT@ #include #include From df690161f8cbe8e5af6319f0974799583dbd1705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 20 Jun 2026 15:07:31 -0400 Subject: [PATCH 4/4] modules: compile per-backend so every dialect works; fix import ordering GCC requires a module BMI's exception/rtti dialect to match every consumer exactly, so a single shared halp BMI cannot serve both the exception backends (dump/python/ossia) and the -fno-exceptions -fno-rtti plugin backends. Compile the slim halp module and the user object into each backend target instead, in that backend's own dialect; both are one small object, so the duplication is cheap and every backend stays self-contained. This replaces the shared avnd_halp_module STATIC library and its base-library compilation. Also move `import` after the avnd binding includes in the 9 prototypes that had it first. A module-linkage processor type whose `import` precedes the avnd introspection headers makes value-init concepts (inputs_is_value, ...) diverge, leaving effect_container incomplete and the example process() constraints unsatisfied; including avnd first -- the order dump/vst3/fuzz/standalone already used -- fixes it for both the header and module paths with one shared prototype. Requires a generator with module support (Ninja; the Makefiles generator drops -fmodules) and -DCMAKE_CXX_SCAN_FOR_MODULES=ON. Validated with gcc-16 on the same object across dialects: dump (exceptions) and example (no-exceptions) build and run, vintage (no-exceptions/no-rtti) builds its plugin .so. Co-Authored-By: Claude Opus 4.8 (1M context) --- cmake/avendish.modules.cmake | 67 ++++++++++--------- include/avnd/binding/clap/prototype.cpp.in | 3 +- include/avnd/binding/example/prototype.cpp.in | 3 +- include/avnd/binding/godot/prototype.cpp.in | 3 +- include/avnd/binding/max/prototype.cpp.in | 3 +- include/avnd/binding/ossia/prototype.cpp.in | 3 +- include/avnd/binding/pd/prototype.cpp.in | 3 +- include/avnd/binding/python/prototype.cpp.in | 3 +- include/avnd/binding/vintage/prototype.cpp.in | 3 +- include/avnd/binding/wasm/prototype.cpp.in | 3 +- 10 files changed, 54 insertions(+), 40 deletions(-) diff --git a/cmake/avendish.modules.cmake b/cmake/avendish.modules.cmake index 55e39f22..5019bb61 100644 --- a/cmake/avendish.modules.cmake +++ b/cmake/avendish.modules.cmake @@ -1,11 +1,21 @@ # C++20 module support. # # A user object can be written as a module (`export module ; import halp;`) -# in a .cppm MAIN_FILE instead of a header. The same per-backend prototype then -# either `#include`s the header or `import`s the module -- only that one line -# differs -- and a .cppm is added to the backend target as a C++20 module source -# linking the shared halp module. Module-ness is auto-detected from the .cppm -# extension; the module name is the object's C_NAME by convention. +# in a .cppm MAIN_FILE instead of a header. The per-backend prototype then either +# `#include`s the header or `import`s the module -- only that one line differs. +# +# GCC requires a module BMI's exception/rtti dialect to match every consumer +# exactly (a mismatch is a hard "Bad file data" error), and avendish backends +# disagree: dump/python/ossia build with exceptions+rtti, while the plugin +# backends (clap/pd/max/vst3/vintage/gstreamer/wasm/example) build with +# -fno-exceptions -fno-rtti. So a single shared halp BMI cannot serve all of +# them. Instead the slim halp module and the user object are compiled *into each +# backend target*, inheriting that backend's dialect. Both are one small object, +# so the duplication is cheap and every backend stays self-contained. +# +# Requirements for the module path: a generator with module support (Ninja -- +# the Makefiles generator drops -fmodules) and -DCMAKE_CXX_SCAN_FOR_MODULES=ON +# so the generated prototype's `import` is scanned for its BMI dependency. # Line a prototype uses to pull in the user object. function(avnd_main_import out main_file c_name) @@ -16,40 +26,35 @@ function(avnd_main_import out main_file c_name) endif() endfunction() -# Add the user object to its base library. A .cppm is compiled once here as an -# exported C++20 module (its BMI is shared with backend targets that link this lib); -# a header is a plain private source as before. +# Add the user object to its per-object base library. A header compiles once here +# as before; a .cppm is compiled per-backend instead (see below), so there is +# nothing to add to the base for the module case. function(avnd_add_object_to_base base main_file) - if("${main_file}" MATCHES "\\.cppm$") - target_sources(${base} PUBLIC FILE_SET CXX_MODULES FILES "${main_file}") - if(TARGET avnd_halp_module) - target_link_libraries(${base} PUBLIC avnd_halp_module) - endif() - else() + if(NOT "${main_file}" MATCHES "\\.cppm$") target_sources(${base} PRIVATE "${main_file}") endif() endfunction() -# Make a backend target consume the user object: for a module, link its base library -# (so the prototype's `import` resolves) instead of recompiling it; for a header, -# compile it into the backend TU as before. +# Make a backend target consume the user object. For a module, compile the slim +# halp module and the user .cppm into the backend itself, in the backend's own +# dialect, so the prototype's `import` resolves against a BMI built with matching +# exception/rtti flags. For a header, compile it into the backend TU as before. function(avnd_add_object_to_backend backend base main_file) if("${main_file}" MATCHES "\\.cppm$") - target_link_libraries(${backend} PRIVATE ${base}) + # The halp module lives in the avendish tree, the user object wherever the + # caller put it; a CXX_MODULES file set requires every file under one of its + # base dirs, so cover both. + get_filename_component(_avnd_mf_dir "${main_file}" DIRECTORY) + target_sources(${backend} + PRIVATE + FILE_SET avnd_modules TYPE CXX_MODULES + BASE_DIRS "${AVND_SOURCE_DIR}/include/halp" "${_avnd_mf_dir}" + FILES + "${AVND_SOURCE_DIR}/include/halp/halp.cppm" + "${main_file}" + ) + target_compile_features(${backend} PRIVATE cxx_std_23) else() target_sources(${backend} PRIVATE "${main_file}") endif() endfunction() - -# The slim halp module, built once and reused by every backend target. -if(CMAKE_CXX_SCAN_FOR_MODULES AND NOT TARGET avnd_halp_module) - add_library(avnd_halp_module STATIC) - target_sources(avnd_halp_module - PUBLIC - FILE_SET CXX_MODULES - FILES - "${AVND_SOURCE_DIR}/include/halp/halp.cppm" - ) - target_link_libraries(avnd_halp_module PUBLIC Avendish::Avendish) - target_compile_features(avnd_halp_module PUBLIC cxx_std_23 INTERFACE cxx_std_23) -endif() diff --git a/include/avnd/binding/clap/prototype.cpp.in b/include/avnd/binding/clap/prototype.cpp.in index 5c8063ce..46a06500 100644 --- a/include/avnd/binding/clap/prototype.cpp.in +++ b/include/avnd/binding/clap/prototype.cpp.in @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@AVND_MAIN_IMPORT@ #include #include +@AVND_MAIN_IMPORT@ + // clang-format off using plug_type = decltype(avnd::configure())::type; using effect_type = avnd_clap::SimpleAudioEffect; diff --git a/include/avnd/binding/example/prototype.cpp.in b/include/avnd/binding/example/prototype.cpp.in index f5bacd4f..9dce5458 100644 --- a/include/avnd/binding/example/prototype.cpp.in +++ b/include/avnd/binding/example/prototype.cpp.in @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@AVND_MAIN_IMPORT@ #include +@AVND_MAIN_IMPORT@ + using type = decltype(avnd::configure())::type; int main() diff --git a/include/avnd/binding/godot/prototype.cpp.in b/include/avnd/binding/godot/prototype.cpp.in index eb8dd868..b18fdc75 100644 --- a/include/avnd/binding/godot/prototype.cpp.in +++ b/include/avnd/binding/godot/prototype.cpp.in @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -@AVND_MAIN_IMPORT@ #include #include @@ -9,6 +8,8 @@ #include #include +@AVND_MAIN_IMPORT@ + using type = decltype(avnd::configure())::type; // clang-format on diff --git a/include/avnd/binding/max/prototype.cpp.in b/include/avnd/binding/max/prototype.cpp.in index 9f071456..c718ffc0 100644 --- a/include/avnd/binding/max/prototype.cpp.in +++ b/include/avnd/binding/max/prototype.cpp.in @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -@AVND_MAIN_IMPORT@ #include #include #include #include #include "commonsyms.h" + +@AVND_MAIN_IMPORT@ using type = decltype(avnd::configure())::type; // clang-format on diff --git a/include/avnd/binding/ossia/prototype.cpp.in b/include/avnd/binding/ossia/prototype.cpp.in index 6e044942..82f3c4ca 100644 --- a/include/avnd/binding/ossia/prototype.cpp.in +++ b/include/avnd/binding/ossia/prototype.cpp.in @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@AVND_MAIN_IMPORT@ #include #include +@AVND_MAIN_IMPORT@ + using type = decltype(avnd::configure())::type; void test_instantiate(ossia::exec_state_facade st) diff --git a/include/avnd/binding/pd/prototype.cpp.in b/include/avnd/binding/pd/prototype.cpp.in index 25dab0e9..88ab8e89 100644 --- a/include/avnd/binding/pd/prototype.cpp.in +++ b/include/avnd/binding/pd/prototype.cpp.in @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -@AVND_MAIN_IMPORT@ #include #include #include + +@AVND_MAIN_IMPORT@ using type = decltype(avnd::configure())::type; // clang-format on diff --git a/include/avnd/binding/python/prototype.cpp.in b/include/avnd/binding/python/prototype.cpp.in index 45c3d54d..a3f846e5 100644 --- a/include/avnd/binding/python/prototype.cpp.in +++ b/include/avnd/binding/python/prototype.cpp.in @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@AVND_MAIN_IMPORT@ #include #include +@AVND_MAIN_IMPORT@ + PYBIND11_MODULE(py@AVND_C_NAME@, m) { using type = decltype(avnd::configure())::type; diff --git a/include/avnd/binding/vintage/prototype.cpp.in b/include/avnd/binding/vintage/prototype.cpp.in index 7b535b66..5fc12f36 100644 --- a/include/avnd/binding/vintage/prototype.cpp.in +++ b/include/avnd/binding/vintage/prototype.cpp.in @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ -@AVND_MAIN_IMPORT@ #include #include +@AVND_MAIN_IMPORT@ + extern "C" AVND_EXPORTED_SYMBOL vintage::Effect* VSTPluginMain( vintage::HostCallback cb) diff --git a/include/avnd/binding/wasm/prototype.cpp.in b/include/avnd/binding/wasm/prototype.cpp.in index 045e38a5..14cce656 100644 --- a/include/avnd/binding/wasm/prototype.cpp.in +++ b/include/avnd/binding/wasm/prototype.cpp.in @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ // clang-format off -@AVND_MAIN_IMPORT@ #include #include +@AVND_MAIN_IMPORT@ + using type = decltype(avnd::configure())::type; // clang-format on