CMake Reference¶
Source: tools/cmake/PulpUtils.cmake
pulp_add_plugin¶
Status: usable
Create plugin targets for multiple formats from a single declaration.
pulp_add_plugin(<target>
FORMATS <format-list>
PLUGIN_NAME <string>
BUNDLE_ID <string>
MANUFACTURER <string>
VERSION <string>
CATEGORY <Effect|Instrument|MidiEffect>
ACCEPTS_MIDI
NATIVE_UI
NO_EDITOR
LITE_EDITOR
GPU_UI
FULL_EDITOR
PLUGIN_CODE <4-char>
MANUFACTURER_CODE <4-char>
AAX_PRODUCT_CODE <4-char>
AAX_NATIVE_CODE <4-char>
SOURCES <file-list>
PROCESSOR_FACTORY <function-name>
UI <ui-package-target|none>
UI_SCRIPT <path>
ICON <1024x1024 png>
ICNS <path>
CONTENT_CAPABILITIES <capability-list>
CONTENT_KINDS <presets|themes|samples|sample-banks|wavetables...>
CONTENT_HOT_RELOAD_KINDS <presets|themes|samples|sample-banks|wavetables...>
CONTENT_MANUAL_RESCAN_KINDS <presets|themes|samples|sample-banks|wavetables...>
SOURCE_GIT_SHA <commit>
SOURCE_GIT_DIRTY <TRUE|FALSE>
)
Parameters¶
An argument pulp_add_plugin() does not recognise (a misspelled keyword, a keyword newer than the SDK in use, or a stray value) stops configuration with unknown argument(s): …, and so does a FORMATS value outside the list below (names are case-sensitive). PRODUCES_MIDI keeps its own warning. pulp_add_plugin_bundle() applies the same checks.
| Parameter | Required | Default | Description |
|---|---|---|---|
FORMATS |
Yes | -- | Space-separated list: VST3, AU, AUv3, CLAP, LV2, AAX, Standalone |
PLUGIN_NAME |
No | <target> |
Display name for the plugin |
BUNDLE_ID |
No | -- | Reverse-DNS identifier (e.g., com.company.plugin) |
MANUFACTURER |
No | "Unknown" |
Manufacturer name |
VERSION |
No | "1.0.0" |
Plugin version string |
CATEGORY |
No | "Effect" |
Plugin category: Effect, Instrument, or MidiEffect |
ACCEPTS_MIDI |
No | Off | Mirrors PluginDescriptor::accepts_midi for package metadata. AU v2/AUv3 effects that accept MIDI need this so Pulp emits a MIDI-routable component type (aumf) instead of an audio-only effect (aufx). MIDI output is controlled by PluginDescriptor::produces_midi; there is no pulp_add_plugin(PRODUCES_MIDI) flag. |
NATIVE_UI |
No | Off | Link the native scripted Skia/Dawn view composition (Pulp::view-native and, for Standalone, Pulp::standalone-native) without WebView/WebKit. ScriptedUiSession, WidgetBridge, and @pulp/react remain available. Omit this for editors that instantiate WebViewPanel; the existing WebView-capable targets remain the default. |
NO_EDITOR |
No | Off | Editor-less (audio-only) plug-in. VST3, CLAP, AU v2 and LV2 modules link Pulp::format-core (CLAP also Pulp::format-clap-core, the adapter without its GUI extension) instead of the format + view umbrella, so the image carries no view, canvas, render, Skia, Dawn or wgpu code and stages no GPU runtime. The plug-in exposes no editor (Pulp's generated AutoUI editor is not built in); hosts show their generic parameter UI. The processor must not override create_view(). Cannot be combined with NATIVE_UI or UI_SCRIPT. UI <package> implies it (see UI Packages). Standalone, AUv3 and AAX outputs keep the default view-linked path. |
GPU_UI |
No | Off | The plug-in's C++ editor needs the GPU host (a view whose requires_gpu_host() is true): link the full editor stack. Without it, macOS plug-ins get the lite editor stack (see LITE_EDITOR). |
FULL_EDITOR |
No | Off | Link the full editor stack for any other reason (the escape hatch from the lite default). |
LITE_EDITOR |
No | Default on macOS | States the default explicitly. Unless the plug-in declares UI_SCRIPT, GPU_UI or FULL_EDITOR, its macOS VST3, CLAP, AU v2 and Standalone binaries leave out the GPU plug-in and window hosts and link lite replacements for the few library functions that reach Skia unconditionally (text measurement, font resolution, the emoji fallback, native GPU textures), so the VST3, CLAP and AU v2 modules carry no Skia, Dawn or Tint (the Standalone app still links Dawn through the standalone host). The editor opens on the CoreGraphics host, which an AutoUi or CPU-painted C++ editor already uses, and measures text through CoreText, which it already paints with. An editor that asks for the GPU host anyway logs one warning naming GPU_UI and opens on the CoreGraphics host. Combining LITE_EDITOR with UI_SCRIPT, GPU_UI or FULL_EDITOR is a configure error. Other platforms build the full stack. See Binary Footprint. |
PLUGIN_CODE |
AU/AUv3 only | -- | 4-character AU plugin code |
MANUFACTURER_CODE |
AU/AUv3/AAX only | -- | 4-character manufacturer code used by AU, AUv3, and AAX |
AAX_PRODUCT_CODE |
AAX only | -- | 4-character stable AAX product identifier |
AAX_NATIVE_CODE |
AAX only | -- | 4-character stable AAX Native identifier |
SOURCES |
No | -- | Source files for the core object library |
PROCESSOR_FACTORY |
No | -- | Factory function name (for generated entry points) |
UI |
No | -- | A UI package target that supplies the plug-in's default editor through the editor-host contract (pulp/format/editor_host.hpp). Every created format and standalone target links the package and compiles a generated source that installs the package's factory as that binary's default, so Processor::editor_factory() returns it unless the processor overrides it. A package implies NO_EDITOR: the VST3, CLAP, AU v2 and LV2 modules link no Pulp view, canvas or render code, and combining it with NATIVE_UI, UI_SCRIPT, GPU_UI, FULL_EDITOR or LITE_EDITOR is a configure error. none, or no UI argument, generates and links nothing, and editor_factory() returns null. See UI Packages. |
UI_SCRIPT |
No | -- | Path to a JavaScript UI entry file. Its bytes, the theme.json beside it, and the image, font, data and shader files beside it (png jpg jpeg gif webp bmp ico svg ttf otf woff woff2 json wgsl glsl sksl frag vert, skipping dot-directories, node_modules and nested build trees; the script must sit in its own directory, and one holding a CMakeLists.txt is refused at configure) are compiled into every created format target and registered with pulp::format at load, so the shipped image carries its editor and the files it loads relative to itself, and never reads the developer's source tree. A dev build (PULP_DEV_BUILD: defaults to PULP_BUILD_TESTS in the Pulp checkout and OFF for an installed-SDK consumer, where the pulp CLI's configure passes -DPULP_DEV_BUILD=ON) also records the absolute path; a hot-reloading editor (the Standalone host, or a DAW launched with PULP_DEV_HOT_RELOAD=1) then reads that file while it exists, which is what keeps the edit-and-see loop live. Every other open evaluates the embedded bytes, and pulp ship builds a build-ship/ that carries the dev build's whole configuration with the option off rather than shipping a dev build. A file added beside the script after configure re-runs CMake on the next build; an edit to an embedded file re-encodes it at build time. |
ICON |
No | -- | A 1024x1024 PNG, converted to an .icns and bundled into every macOS bundle the call creates (Standalone, VST3, AU, CLAP, AAX). Mutually exclusive with ICNS. |
ICNS |
No | -- | A finished .icns, bundled as-is into the same set. Prefer this when the asset is laid out per size; the ICON path downscales with sips, whose kernel rings small tiles. Scope: this fills CFBundleIconFile and ships the asset. macOS does not render that key for a non-.app bundle, so no icon appears in Finder for the plug-in formats; see the product-icon comment in PulpUtils.cmake. |
CONTENT_CAPABILITIES |
No | -- | Runtime content capabilities the plugin actually consumes, such as content.presets.v1. Use this when the plugin can load installed content packs. Must be paired with CONTENT_KINDS. |
CONTENT_KINDS |
No | -- | Content kinds accepted by the plugin: presets, themes, samples, sample-banks, wavetables. Use capability content.sample-banks.v1 for the generic pulp.sample-bank.v1 contract. This lets users and agents reject mismatched packs before install. Must be paired with CONTENT_CAPABILITIES. |
CONTENT_HOT_RELOAD_KINDS |
No | -- | Accepted content kinds the plugin can refresh immediately after install/update. Each value must also appear in CONTENT_KINDS. |
CONTENT_MANUAL_RESCAN_KINDS |
No | -- | Accepted content kinds that require an in-app rescan action but not a full restart. Each value must also appear in CONTENT_KINDS. |
SOURCE_GIT_SHA |
No | git HEAD at build time |
Product source commit recorded in every bundle's pulp-build-info.json. Overrides the project-wide PULP_PRODUCT_GIT_SHA; without either, the commit of the git checkout containing the calling CMakeLists.txt is read when the bundle is built, and "unknown" is recorded outside a checkout. Pass it explicitly for release builds from a source archive or a CI checkout without history. |
SOURCE_GIT_DIRTY |
No | git status at build time | Whether the product source had uncommitted tracked changes. Overrides PULP_PRODUCT_GIT_DIRTY; recorded as null when unknown. |
When CONTENT_CAPABILITIES and CONTENT_KINDS are present,
pulp_add_plugin() generates a pulp.plugin-runtime.json resource containing
the pulp.plugin-runtime.v1 manifest. Reload-policy fields are optional:
accepted kinds listed in CONTENT_HOT_RELOAD_KINDS can update immediately,
accepted kinds listed in CONTENT_MANUAL_RESCAN_KINDS should surface an in-app
rescan action, and all other accepted kinds are restart-required in content
preview/install UX. VST3/AU-style desktop bundles receive the manifest under
Contents/Resources/; AUv3/iOS flat bundles receive it under Resources/;
LV2 bundles receive it at the .lv2 bundle root; single file non-bundle formats
receive a sibling <plugin-stem>.pulp.plugin-runtime.json sidecar. Validate the emitted artifact with
ValidationHarness::validate_plugin_runtime_manifest(...).
Every bundle pulp_add_plugin() and pulp_add_plugin_bundle() create — VST3,
AU, CLAP, LV2, AAX, the AUv3 extension and its host app, and the Standalone
app — also carries a pulp-build-info.json build-identity record
(pulp.build-info.v1). Its layout, schema, and the SDK's
share/pulp/runtime-pins.json it embeds are documented in the
shipping guide.
Reviewed UI kits are consumed explicitly after pulp kit apply has generated
cmake/pulp-kits.cmake. Include that file, declare the plugin, then attach the
chosen kit script:
include(cmake/pulp-kits.cmake OPTIONAL)
pulp_add_plugin(MyPlugin
FORMATS CLAP Standalone
...)
pulp_use_kit_ui(MyPlugin pulp_kit_dev_pulp_fixtures_basic_ui_kit)
pulp_use_kit_ui(<plugin-target> <kit-target> [SCRIPT <exported-path>] [TOKENS <exported-path>])
reads the reviewed kit target's PULP_UI_SCRIPTS, PULP_DESIGN_TOKENS, and
PULP_ASSETS properties. The selected script and token/theme file, and the
files under the declared asset directories, are embedded into the existing
format targets the same way UI_SCRIPT is, and the kit's selection replaces
the plug-in's own UI_SCRIPT on those targets; a dev build (PULP_DEV_BUILD)
also records the on-disk paths as the PULP_UI_SCRIPT_PATH,
PULP_UI_THEME_PATH, and PULP_UI_ASSET_ROOTS target properties, which each
target's generated editor-asset source registers at load time
(pulp/format/editor_assets.hpp). Scripted editors can then use __loadAssetSync__ with
relative paths into those reviewed asset roots, embedded or on disk. Kits with multiple scripts or
token files require the matching SCRIPT / TOKENS argument so the project
makes the choice intentionally. The helper does not apply kits automatically
and does not run package code.
UI Packages¶
A UI package is any CMake target that declares two target properties:
| Property | Value |
|---|---|
PULP_EDITOR_FACTORY_FUNCTION |
Fully qualified name of a function ::pulp::format::EditorFactory* name() that returns the package's editor factory. The factory must outlive every editor it creates; a function-local static is the usual shape. It is called on the host's main thread, the first time a host asks for the editor, never at load. |
PULP_EDITOR_FACTORY_HEADER |
The header, on the package's include path, that declares that function. |
add_library(my-ui STATIC my_ui_editor.cpp)
target_link_libraries(my-ui PUBLIC Pulp::format-core)
target_include_directories(my-ui PUBLIC include)
set_target_properties(my-ui PROPERTIES
PULP_EDITOR_FACTORY_FUNCTION "my_ui::editor_factory"
PULP_EDITOR_FACTORY_HEADER "my_ui/editor_factory.hpp")
pulp_add_plugin(MyPlugin FORMATS CLAP VST3 Standalone UI my-ui ...)
An installed package lists both properties in its targets' EXPORT_PROPERTIES
so they survive install(EXPORT). The package target must exist when
pulp_add_plugin() runs; a missing target or property stops configuration.
A plug-in that declares a package gets NO_EDITOR's view-free link without
saying so: its VST3, CLAP, AU v2 and LV2 modules link Pulp::format-core
instead of the format and view umbrella, so they carry no Pulp view, canvas,
render, Skia or Dawn code. Its Standalone links Pulp::standalone-contract,
the standalone app without Pulp's view layer, so it also carries no Pulp view,
render or Dawn code and a package that draws with Dawn brings the only copy;
that app opens only the package's editor (no Settings tab, musical typing
keyboard or Audio Inspector, which are Pulp views). AUv3 and AAX outputs still
link the view stack. NATIVE_UI, UI_SCRIPT, GPU_UI,
FULL_EDITOR and LITE_EDITOR select Pulp's own view editor, so combining
any of them with a package stops configuration with
UI <package> cannot be combined with .... UI none selects no package and
leaves the link as it would be without UI.
The choice belongs to the plug-in binary, not to a process-wide registry. The
generated source defines the hook with internal linkage and installs it, at
static init, into a slot in pulp-format-core that has internal linkage and
hidden accessors (pulp/format/editor_package.hpp). pulp-format-core is
linked statically into each plug-in module and executable, so two plug-ins in
one host process each read their own package's factory and neither can replace
the other's. Every processor in one binary that does not override
editor_factory() shares that binary's package.
A format whose adapter does not open contract editors yet still builds, but
without an editor; pulp_add_plugin() names those formats in a configure
warning.
VST3 and CLAP open the editor through the contract whenever
editor_factory() is non-null, and fall back to create_view() otherwise. A
UI-package plug-in's AU v2 component serves a view-free Cocoa view factory
(au_v2_contract_view.mm) that opens the package's editor. The standalone app
hosts the contract editor in its own native window (macOS today; other
platforms fail the run), where a Space the editor forwards plays or stops the
standalone's transport.
pulp_add_plugin_bundle¶
Status: usable
Package MANY plugins into ONE binary per format — the multi-plugin counterpart
to pulp_add_plugin. One .clap / .vst3 exposes N
distinct plugins (Expert Sleepers Silent Way style). The single-plugin path is
unchanged and remains the default; see
PULP_PLUGIN_PACKAGING.
pulp_add_plugin_bundle(<target>
FORMATS CLAP VST3 # bundle-capable formats
BUNDLE_NAME <string> # binary/bundle name (default <target>)
BUNDLE_ID <string> # reverse-DNS id for the bundle
VERSION <string> # default "1.0.0"
MANUFACTURER <string> # default "Unknown"
SOURCES <file-list> # optional; union of the bundled plugins' sources
)
Each format's entry translation unit — clap_bundle_entry.cpp /
vst3_bundle_entry.cpp in the target's source dir — uses the bundle macros
(PULP_CLAP_BUNDLE_PLUGIN + PULP_CLAP_BUNDLE_ENTRY;
PULP_VST3_BUNDLE_PLUGIN + PULP_VST3_FACTORY_BEGIN/_BUNDLE_CLASS/_FACTORY_END)
to register N plugins into one binary. Per-plugin metadata (category, ids, and —
for AU — component codes) lives in those macro calls, not in CMake args. The
format functions automatically prefer a <fmt>_bundle_entry.cpp over the
single-plugin <fmt>_entry.cpp.
Supported formats: CLAP and VST3 (each emits one entry symbol whose
factory enumerates N plugins internally). AU/AUv3/AAX bundles additionally
need a multi-component Info.plist and multi-symbol export and are not yet
supported here — requesting one is a configure-time error rather than a
silently-single-plugin binary.
See the plugin-bundles guide for a full walk-through
and the examples/combined-bundle-demo project.
pulp_enable_midi_tuning_provider¶
Attach Pulp's provider-neutral optional microtuning wrappers to a plugin target.
Use this after pulp_add_plugin() and after including any package CMake emitted
by pulp add:
include(cmake/pulp-packages.cmake OPTIONAL)
pulp_add_plugin(MySynth
FORMATS CLAP Standalone
CATEGORY Instrument
ACCEPTS_MIDI
SOURCES src/PluginProcessor.cpp)
pulp_enable_midi_tuning_provider(MySynth MTS_ESP SCALA)
Supported providers are MTS_ESP and SCALA. MTS_ESP requires the
mts_esp_client target created by pulp add mts-esp; SCALA requires the
sst::tuning-library target created by pulp add sst-tuning-library.
When a project uses both, MtsEspFallbackTuningProvider can prefer the active
MTS-ESP session or prefer loaded local Scala tuning via
MtsEspFallbackPolicy::PreferLocalTuning.
If the Pulp SDK was already built with the matching provider enabled, the helper
only links pulp::midi and exposes the compile definition to the target. If an
installed SDK was built without the optional provider, the helper compiles the
small Pulp-owned wrapper source into your plugin target and links the package
target. The third-party package remains opt-in either way.
Created Targets¶
| Target | Type | Description |
|---|---|---|
${target}_Core |
OBJECT or INTERFACE | Shared processor code |
${target}_VST3 |
MODULE | VST3 bundle (.vst3) |
${target}_AU |
MODULE | AU v2 component (.component), macOS only |
${target}_AUv3Framework |
SHARED FRAMEWORK | AUv3 implementation framework, macOS only |
${target}_AUv3 |
EXECUTABLE/BUNDLE | AUv3 app extension (.appex), Apple platforms only |
${target}_AUv3Host |
EXECUTABLE/BUNDLE | macOS container app embedding the AUv3 extension |
${target}_CLAP |
MODULE | CLAP bundle (.clap) |
${target}_LV2 |
MODULE | LV2 bundle (.lv2) |
${target}_AAX |
MODULE | AAX Native bundle (.aaxplugin), macOS/Windows with developer-supplied SDK |
${target}_Standalone |
EXECUTABLE | Standalone app |
pulp-install-${target} |
CUSTOM | Copies built plugins to system folders |
File Conventions¶
Each format target looks for an entry-point source file by convention:
| Format | Expected file |
|---|---|
| VST3 | vst3_entry.cpp |
| CLAP | clap_entry.cpp |
| AU v2 | au_v2_entry.cpp |
| AUv3 | au_v3_entry.cpp |
| LV2 | lv2_entry.cpp |
| AAX | aax_entry.cpp |
| Standalone | main.cpp |
Info.plist files can be provided per-plugin or generated from templates:
| Format | Custom plist | Template |
|---|---|---|
| VST3 | Info.plist.vst3 |
tools/cmake/PulpInfoPlist.vst3.in |
| AU | Info.plist.au |
tools/cmake/PulpInfoPlist.au.in |
| AAX | generated | tools/cmake/PulpInfoPlist.aax.in |
Metadata Resolution¶
pulp_add_plugin() is the public entry point for plugin metadata. It resolves
per-format bundle metadata from the same declaration instead of asking projects
to include format-specific helper files directly. AU v2 and AUv3 share the same
component-type rules:
| Declaration | AU component type | Host tag |
|---|---|---|
CATEGORY Instrument |
aumu |
Synthesizer |
CATEGORY MidiEffect |
aumi |
MIDI |
CATEGORY Effect plus ACCEPTS_MIDI |
aumf |
Effects |
CATEGORY Effect without ACCEPTS_MIDI |
aufx |
Effects |
The component type has to agree with the AU entry macro in the plugin's
au_v2_entry.cpp, or the component loads and then silently ignores every MIDI
message the host sends it:
| AU component type | Entry macro | Header |
|---|---|---|
aufx |
PULP_AU_PLUGIN |
pulp/format/au_v2_entry.hpp |
aumf |
PULP_AU_MIDI_PLUGIN |
pulp/format/au_v2_entry.hpp |
aumi |
PULP_AU_MIDI_EFFECT |
pulp/format/au_v2_midi_effect_entry.hpp |
aumu |
PULP_AU_INSTRUMENT |
pulp/format/au_v2_instrument_entry.hpp |
PLUGIN_CODE and MANUFACTURER_CODE must be exactly four characters for AU
and AUv3 targets. VERSION is converted to the AU integer form from numeric
major[.minor[.patch]] components with an optional - or + suffix, so
1.2.3-beta.1 keeps the same AU version number as 1.2.3. Generated plist
and entitlement templates consume these resolved values; downstream projects
should keep using pulp_add_plugin() rather than calling the internal format
helpers.
Each AU/AUv3 target must claim a unique Audio Unit identity — the
(component type, PLUGIN_CODE, MANUFACTURER_CODE) triple macOS keys the
AudioComponent registry on. Two targets in one project that share it are one
component as far as a host is concerned, so only one of them can ever load;
configure fails with the conflicting target named. Declaring both AU and
AUv3 for the same target is fine — one plugin publishes one identity.
Format Availability¶
Format targets are only created when the corresponding SDK is available:
- VST3: requires
PULP_HAS_VST3(set whenexternal/vst3sdk/exists) - AU: requires
APPLEandPULP_HAS_AUSDK(set whenexternal/AudioUnitSDK/exists) - AUv3: requires
APPLE,PLUGIN_CODE, andMANUFACTURER_CODE; macOS creates a framework,.appex, and container.app, while iOS creates the.appex - CLAP: requires
PULP_HAS_CLAP(fetched via FetchContent) - LV2: requires
PULP_HAS_LV2(set when LV2 headers are available) - AAX: requires
APPLEorWIN32,PULP_ENABLE_AAX=ON, andPULP_AAX_SDK_DIRpointing to a developer-supplied out-of-tree AAX SDK - Standalone: always available
On Linux and Ubuntu, requesting AAX in FORMATS is a configure-time error.
Shipped Module Size¶
Three defaults keep shipped plug-in modules small. Each changes what a shipped binary contains, so each has an opt-out.
Dead stripping. In every non-Debug Apple build the VST3, CLAP, AU v2, LV2
and AAX modules link with -dead_strip, so code no entry point reaches is
removed. AU v3 targets are not dead-stripped. The linker keeps everything
reachable from an exported symbol (the format entry points), every static
initializer and what it reaches (self-registering factories survive),
Objective-C class and category metadata, and anything marked
__attribute__((used)). What it can drop is code reached only by a runtime
name lookup it cannot see: dlsym() of a symbol the module does not export,
or an Objective-C class found only through NSClassFromString(). Export such a
symbol (__attribute__((visibility("default")))), mark it used, or
reference it from code that is reached.
Opt out for every plug-in with -DPULP_DEAD_STRIP_PLUGINS=OFF, or for one
module after pulp_add_plugin():
Development-only shared runtime. -DPULP_DEV_SHARED_UI=ON (macOS, off by
default; pulp dev and pulp loop turn it on in SDK projects) links the VST3, CLAP, AU and
Standalone binaries of plug-ins with the default view stack against one
libpulp-dev-shared-ui.<version>.dylib in the build tree instead of the static
Pulp libraries, so a one-file edit relinks a module of about a megabyte. Those
artifacts load only from that build tree, and every ship path refuses them. See
Fast plug-in relinks.
Coverage: plugin-module-dead-strip-PulpGain-<FORMAT> checks the flag on
PulpGain's modules, cmake-plugin-dead-strip-switch checks that the option and
the property each remove it, and test/test_host.cpp loads and processes the
dead-stripped Release PulpGain.clap. Opening a dead-stripped module's editor
in every format is not covered by an automated test.
No script runtime unless the editor runs a script. Editor glue that every
plug-in links reaches a scripted editor only through
view::ScriptedEditorSession's virtual calls and a prewarm runner slot, so a
plug-in whose editor runs no script (the generated AutoUI editor, or native
views) links none of the JS engine, the widget bridge or the session code;
about 5 MB of a stripped module, plus the Skia text and canvas code the
widget bridge draws through. Linking follows code references, not a flag. A
target that declares UI_SCRIPT (or gets one from pulp_use_kit_ui()) calls
pulp::format::install_scripted_editor_builder() from its generated
editor-assets source, which links the code that builds the scripted editor and
with it the runtime. Any code in the plug-in that constructs a
view::ScriptedUiSession links the runtime too, and the session's static
initializer installs the prewarm runner; a plug-in that loads a script at run
time without declaring UI_SCRIPT opts back in that way. A multi-plug-in
bundle whose scripts come only from PluginRegistration::ui_script entries
calls install_scripted_editor_builder() itself; without it the editor logs
that the builder is missing and opens AutoUi. Reaching the runtime only by name
at run time (dlsym()) cannot work, because nothing links it. Coverage:
plugin-module-no-script-runtime-PulpGain-<FORMAT>.
Local symbols stripped at packaging. pulp ship package and
tools/scripts/build_combined_installer.sh ship a copy of each AU, VST3 and
CLAP bundle with strip -x applied to its main executable; the build output
keeps its symbols, and AAX is never touched. Before stripping, each writes a
dSYM of the unstripped executable (artifacts/symbols/<FORMAT>/<bundle>.dSYM,
or <out>/symbols/<kind>/ for the installer script) under the UUID the
stripped copy keeps, so crash reports from installed plug-ins still
symbolicate; archive it with the release. Stripping happens at packaging, not
at build time, so the opt-out is the packaging flag --keep-symbols, not a
CMake option. See the ship skill for the details.
Install Target¶
pulp-install-${target} copies supported built bundles to platform-standard locations:
| Platform | VST3 | CLAP | AU | AUv3 | AAX |
|---|---|---|---|---|---|
| macOS | ~/Library/Audio/Plug-Ins/VST3/ |
~/Library/Audio/Plug-Ins/CLAP/ |
~/Library/Audio/Plug-Ins/Components/ |
~/Applications/ plus PlugInKit registration |
/Library/Application Support/Avid/Audio/Plug-Ins/ |
| Windows | %COMMONPROGRAMFILES%/VST3/ |
%COMMONPROGRAMFILES%/CLAP/ |
-- | -- | %COMMONPROGRAMFILES%/Avid/Audio/Plug-Ins/ |
| Linux | ~/.vst3/ |
~/.clap/ |
-- | -- | -- |
LV2 build output is created under build/LV2/<plugin>.lv2/; pulp-install-${target}
does not currently copy LV2 bundles to a user plugin folder.
Binary Footprint¶
Skia's libskunicode_icu.a embeds a full ICU data package (10.9 MB), the
largest single part of a plug-in with Pulp's editor. With
PULP_ICU_DATA_SUBSET (default ON, Apple only), FindSkia links a copy whose
data keeps only what the linked ICU code reads: every break-iterator rule file
and dictionary, normalization, properties, the converter alias table and
locale resolution data. Collation, time-zone, currency, display-name, unit,
transliteration, number spell-out, IDNA, codepage-converter and spoof-check
data are removed (4.9 MB remains). The copy is generated at configure time
under build/pulp-skia-icu-subset/ by tools/cmake/scripts/icu_data_subset.py,
for every Skia consumer and for installed-SDK builds alike; any structural
surprise falls back to the unchanged archive with a warning, and the footprint
tests then fail on the size. Turn the option off only if your plug-in calls ICU
services Pulp does not use itself.
On macOS a plug-in gets the lite editor stack unless it declares UI_SCRIPT,
GPU_UI or FULL_EDITOR; this suits every editor that paints on the
CoreGraphics host (Pulp's generated AutoUi editor, or C++ views that never ask
for the GPU host). Its binaries compile their Objective-C cluster without the
GPU hosts and link pulp-canvas-lite-objects and pulp-view-lite-objects
(tools/cmake/PulpLiteEditorObjects.cmake): copies of text_shaper.cpp,
font_resolver.cpp, canvas_widget.cpp and asset_manager.cpp built with
PULP_LITE_EDITOR=1, which measure through CoreText and skip the Skia-only
paths. Objects on the link line take precedence over the library's archive
members, so the Skia versions are never loaded, and the binary links with
-dead_strip so nothing they would have reached survives. The editor opens on
the CoreGraphics host in both builds and draws the same glyphs, and captured
line breaks are reused in both, because the lite build resolves a face's
identity through CoreText. Text measured without a capture can differ by up to
1 logical pixel for a few system families at non-400 weights: the lite build
measures with CoreText, the face it paints, while the full build measures with
Skia, which can resolve a different face ("Helvetica Neue" 600 at 12 px: ascent
13.92 and width 116.7 in Skia, 13.69 and 120.0 for the painted face), and for
a face a design asks for that Pulp does not bundle, where Skia and CoreText fall
back to different faces (a bold JetBrains Mono: Menlo-Bold in Skia, the bundled
Regular in CoreText). AutoUi, Inter and design-imported panels whose faces are
bundled render the same in both builds.
A plug-in without a custom editor that does not need Pulp's generated parameter
editor should use NO_EDITOR: it links none of the view, canvas, Skia, Dawn or
JavaScript code. Measured on the pulp-gain examples (arm64, Release, Skia
m153), binary bytes per format:
| Build | VST3 | CLAP | AU |
|---|---|---|---|
PulpGain, full ICU data |
44.5 MB | 44.3 MB | 44.3 MB |
PulpGain, ICU subset |
38.4 MB | 38.2 MB | 38.3 MB |
PulpGain (lite editor stack, the default) |
2.58 MB | 2.46 MB | 2.54 MB |
PulpEffect (FULL_EDITOR) |
28.7 MB | ||
PulpGainHeadless (NO_EDITOR) |
0.83 MB | 0.61 MB | 0.67 MB |
tools/scripts/plugin_footprint_check.py measures a built binary against the
budgets in tools/scripts/plugin_footprint_budget.json (mapped code and data,
which strip settings do not move; ICU data; and, for an editor-less binary, the
symbols it must not contain). The plugin-footprint-PulpEffect (FULL_EDITOR,
profile editor), plugin-footprint-PulpGain (lite stack, profile lite-editor),
plugin-footprint-PulpGainHeadless and icu-data-subset-linked tests run it,
and pulp-test-icu-data-breaks pins the word, line, sentence and grapheme
boundaries the full data produces, so a subset that drops a rule file or
dictionary still in use fails.
pulp_add_app¶
Status: partial
Create a standalone application target.
Creates a basic executable target with compile definitions for the app name and bundle ID. Minimal functionality compared to pulp_add_plugin().
On Apple platforms the target is created as a bundle-capable executable so it
can accept pulp_app_icon(...).
pulp_declare_standalone_document_type¶
Status: usable
Declare a file type an app owns, so macOS can launch it by double-clicking one of its documents.
Call it after the target exists — after pulp_add_plugin(... FORMATS
standalone) or pulp_add_app() — and name the app target itself
(MyPlugin_Standalone, not MyPlugin).
pulp_add_plugin(MyApp
FORMATS standalone
PLUGIN_NAME "MyApp"
BUNDLE_ID "com.example.myapp"
)
pulp_declare_standalone_document_type(MyApp_Standalone
UTI com.example.myapp.project
EXTENSION myproj
MIME application/vnd.example.myapp+zip
DESCRIPTION "MyApp Project"
CONFORMS_TO public.data public.archive com.pkware.zip-archive
ROLE Editor
RANK Owner
)
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
UTI |
Yes | -- | Reverse-DNS uniform type identifier for the file type |
EXTENSION |
Yes | -- | One or more bare filename extensions; a leading . is normalized away |
DESCRIPTION |
No | the UTI |
Human-readable type name shown in Finder and Open panels |
MIME |
No | -- | One or more MIME types published in the exported declaration |
CONFORMS_TO |
No | public.data |
UTIs this type conforms to |
ROLE |
No | Editor |
Editor, Viewer, Shell, or None |
RANK |
No | Owner |
Owner, Default, Alternate, or None |
RANK None is the shape a helper or secondary app needs: it can open the type
but must never become the system's default handler for it.
What it emits¶
Each call adds one entry to both CFBundleDocumentTypes (the types this app
opens) and UTExportedTypeDeclarations (this app defines the type). The pair
is not separable here: an app that declares a document type for a UTI nothing
exports gets no document icon and unreliable Launch Services routing.
Call it more than once on one target to declare more than one file type; the
declarations accumulate. Declaring the same UTI twice is an error — pass every
extension of a single type in one call (EXTENSION proj projx).
Behavior notes¶
- Apple only. On Windows, Linux, and Android the call validates its
arguments and then does nothing, so a cross-platform
CMakeLists.txtneeds noif(APPLE)guard. Argument checking still runs on every platform, so a typo fails the same way everywhere. - It fails configure rather than dropping a declaration. A missing
UTIorEXTENSION, a non-reverse-DNSUTI, an unknownROLE/RANK, a target that is not a macOS application bundle, and a duplicateUTIare allFATAL_ERRORs — a silently ignored document type builds, installs, and then simply does nothing on double-click. - CMake cannot append keys to the
Info.plistit synthesizes, so the function pointsMACOSX_BUNDLE_INFO_PLISTat a generated plist built fromPulpInfoPlist.standalone.in. That template carries CMake's default key set unchanged, still as${MACOSX_BUNDLE_*}placeholders, so every existing bundle value — including the icon filepulp_app_icon()sets, in either call order — is preserved. An app that never calls this function is untouched. - If the target already has a hand-authored
MACOSX_BUNDLE_INFO_PLIST, the call fails rather than overwriting it.
Declaring the type is only half of opening a document¶
This function makes the OS route the file: Finder gives the app a document
icon, the double-click launches it, and the Open panel offers the type. It does
not make the app receive anything. macOS reports the file through the
NSApplicationDelegate message application:openURLs:, so an app that declares
the type and installs no handler launches and drops the file — which looks
exactly like the declaration not working.
The runtime half is WindowHost::set_open_files_handler(), or
StandaloneApp::set_open_files_handler() for a standalone plug-in target:
app.set_open_files_handler([&](const std::vector<std::string>& paths) {
for (const auto& path : paths) open_project(path);
});
app.run_with_editor();
Install it before the event loop starts. The file that launched the app is reported from inside the loop's own startup, so a handler wired up afterwards misses the common case; paths that arrive before a handler exists are held and delivered as soon as one is installed.
pulp_app_icon¶
Status: usable
Attach an icon source to an application, standalone, or plugin-format bundle
target. The plugin bundles pulp_add_plugin() generates — <target>_VST3,
<target>_AU, <target>_CLAP, <target>_AAX — are CFBundles and accept an
icon the same way an app bundle does.
Targets build normally if you never call pulp_app_icon(...). The helper is an
optional overlay, so removing the call cleanly removes the custom-icon
behavior.
SOURCE takes one PNG of at least 1024x1024 and derives every size from it
with sips. That is fine for soft artwork and poor for a mark with fine
detail: sips uses a Lanczos kernel, which overshoots on hard edges, so a
1-2px feature at 16x16 smears and a tile edge picks up a visible halo. When
the icon has crisp geometry, render each size on its own pixel grid, build the
.icns yourself, and pass it as ICNS — it is bundled verbatim. ICNS
applies to macOS only; keep SOURCE alongside it to cover Windows, Android,
and Linux.
pulp_app_icon(<target>
SOURCE assets/app-icon.png
MACOS assets/app-icon-mac.png
WINDOWS assets/app-icon-win.png
IOS assets/app-icon-ios.png
ANDROID assets/app-icon-android.png
LINUX assets/app-icon-linux.png
DEBUG_ICON assets/app-icon-debug.png
RELEASE_ICON assets/app-icon-release.png
)
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
SOURCE |
Yes, unless a platform override is present | -- | Default PNG used when a platform-specific override is absent; must be at least 1024×1024 |
MACOS |
No | SOURCE |
macOS override |
WINDOWS |
No | SOURCE |
Windows override |
IOS |
No | SOURCE |
iOS override, recorded for downstream packaging |
ANDROID |
No | SOURCE |
Android override |
LINUX |
No | SOURCE |
Linux override |
DEBUG_ICON |
No | -- | Debug-build variant, used when the active configure/build type is Debug |
RELEASE_ICON |
No | -- | Release-build variant |
Current Platform Outputs¶
- macOS: generates
AppIcon.icnsand bundles it into the app - Windows: generates an
.icoplus.rcand links it into the executable - Android: writes launcher PNGs under
android/app/src/main/res-generated/ - Linux: records the selected PNG as the target's canonical app-icon asset via the
PULP_LINUX_APP_ICONtarget property - iOS: records the selected PNG as
PULP_IOS_APP_ICONand warns that asset-catalog emission is not implemented
The selected source must be a PNG that is at least 1024×1024. Smaller inputs fail at configure time so a release doesn’t silently ship blurry assets.
Example:
pulp_add_plugin(MyPlugin
FORMATS Standalone
PLUGIN_NAME "MyPlugin"
BUNDLE_ID "com.example.myplugin"
)
pulp_app_icon(MyPlugin_Standalone
SOURCE assets/app-icon.png
)
pulp_add_sparkle¶
Embeds Sparkle 2 in a macOS standalone app so it
can offer signed updates. Call it after the app target exists, on the .app
target only; it refuses any target that is not a MACOSX_BUNDLE executable, so
an updater never ends up inside an AU, VST3 or CLAP bundle.
pulp_add_sparkle(MyPlugin_Standalone
FEED_URL "https://github.com/me/myplugin/releases/latest/download/appcast.xml"
PUBLIC_ED_KEY "<44-character base64 Ed25519 public key>"
[AUTOMATIC_CHECKS ON|OFF] # SUEnableAutomaticChecks; omit to let Sparkle ask
[CHECK_INTERVAL 86400] # SUScheduledCheckInterval, seconds
[RELEASES_URL <https page>] # linked in the Settings "Updates" note
[INSTALLER package|app] # what an update installs; the note says so
[AUTOMATIC_INSTALL ON|OFF] # SUAllowsAutomaticUpdates; default OFF
[KEEP_XPC_SERVICES] # keep Installer/Downloader.xpc (sandboxed apps only)
[VERSION 2.10.0 SHA256 <hash>] # override the pinned release
[DIST_DIR <extracted-dist>]) # offline: an already-extracted distribution
At configure time it downloads the pinned Sparkle-<version>.tar.xz release
asset (verifying its SHA-256) into <build>/_deps/sparkle-<version>/. After
each link it copies Sparkle.framework into Contents/Frameworks with ditto,
removes the XPC services a non-sandboxed app does not use, links the framework
with an @executable_path/../Frameworks rpath. At the end of configure it
writes SUFeedURL, SUPublicEDKey and the optional keys into the app's
Info.plist template, so they survive regeneration and combine with
pulp_declare_standalone_document_type() in either order.
Offline configures: DIST_DIR, or the cache variable PULP_SPARKLE_DIST_DIR,
names an extracted distribution, and PULP_SPARKLE_ARCHIVE names a local copy
of the release archive, which is verified against the same pinned SHA-256 before
it is extracted. With FETCHCONTENT_FULLY_DISCONNECTED ON and none of these,
configure stops with an error instead of downloading.
FEED_URL must be https://, or http://127.0.0.1:<port>/… for a local practice feed served from loopback (Sparkle 2 refuses file:// feeds at run time), and
PUBLIC_ED_KEY must be the public half of the key pair. The private key never
belongs in a build file: keep it outside the repository and pass it to
pulp ship appcast --sign-key-file when publishing.
At run time the standalone host finds Sparkle through the Objective-C runtime
and, with no further code, adds Check for Updates… directly under About
RELEASES_URL, INSTALLER and AUTOMATIC_INSTALL), and the
pulp::format::app_update_status() service a custom editor's own Settings can
read (see In-app updates). AUTOMATIC_INSTALL is
off unless set, so Sparkle never installs an update the user did not choose.
Scheduled background
checks start only when the app carries a Developer ID signature, so local and
CI launches stay quiet; PULP_STANDALONE_UPDATER=on starts them in a
development build, and =off disables the updater entirely.
build_combined_installer.sh signs the embedded framework inside-out with the
Developer ID identity before it signs the app.
pulp_add_binary_data¶
Status: usable
Embed binary assets as C++ arrays.
Generates a static library with:
namespace myresources {
extern const unsigned char logo_png[];
extern const std::size_t logo_png_size;
}
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
SOURCES |
Yes | -- | Files to embed |
NAMESPACE |
No | <target> |
C++ namespace for the generated symbols |
COMPRESS |
No | Off | Store each asset as a zlib stream (see below) |
Compressed embeds¶
COMPRESS stores each asset as a zlib (RFC 1950) stream at level 9. The
stream is produced by the build host's Python zlib, so it is deterministic per
zlib build: two hosts whose Python links different zlib builds (zlib 1.2.x and
zlib-ng, for example) can embed different, equally valid streams, and they
inflate to the same bytes. Leave COMPRESS off for a target whose binaries are
byte-compared across hosts or built as a reproducible release. Switching
COMPRESS on or off in an existing build directory needs no clean
reconfigure: CMake reruns the encoder because its command line changed, with
Makefile generators as well as Ninja. Verified with Unix Makefiles on CMake
3.24.3 (the minimum Pulp supports) and 4.3.3;
cmake-pulp-add-binary-data-compress-toggle checks it on every run. Text assets shrink the most: a materialized
design's runtime bundle to about a tenth, its document JSON to about two
fifths. Already-compressed formats (PNG, JPEG, WOFF2) gain nothing, so embed
those in a separate call without COMPRESS. The symbols change, so code
still reading the raw array fails to compile instead of reading compressed
bytes:
namespace myresources {
extern const unsigned char runtime_js_zlib[]; // compressed bytes
extern const std::size_t runtime_js_zlib_size;
extern const std::size_t runtime_js_size; // inflated bytes
}
// Inflate where the asset is used, and drop the copy once it is consumed.
// zlib_decompress_exact inflates into a string of exactly the recorded size,
// and returns nullopt for a truncated, oversized or corrupt stream.
auto text = pulp::runtime::zlib_decompress_exact(myresources::runtime_js_zlib,
myresources::runtime_js_zlib_size,
myresources::runtime_js_size);
The trade is a smaller binary against an inflate on each use and a transient
heap copy. zlib_decompress_exact allocates its output, so never call it on the
audio thread: inflate on the message thread or a background thread and hand
the result over. Inflating costs about 1 ms per megabyte of text on an Apple
silicon core. COMPRESS is not the default: the raw array is a public
symbol that consumers read directly, so a default change would break their
builds.
Implementation notes¶
The function delegates the byte-to-C-array encoding to the
tools/cmake/scripts/encode_binary_data.py helper through
add_custom_command(OUTPUT … DEPENDS …). As a result:
- Encoding runs at build time, not configure time. Editing a source
asset and re-running
cmake --buildregenerates the embedded.cppautomatically — nocmake -S/-Breconfigure required. - Encoding is fast: a 1 MB asset finishes in well under a second. The older in-CMake hex loop was effectively O(n²) and could pin a single reconfigure at 100 % CPU for 10–22 minutes.
- Python (
find_package(Python3 COMPONENTS Interpreter REQUIRED)) is the only added build-time dependency; Python is already required elsewhere in the Pulp build.
pulp_add_js_bytecode¶
Status: usable
Compile scripted-UI JavaScript to QuickJS bytecode at build time and embed it, so a fresh process reads the bytecode instead of parsing the script before its editor can open.
pulp_add_js_bytecode(my_ui_bytecode
SOURCES ui/runtime.js ui/design.js
NAMESPACE my_ui_bytecode
WIDGET_BRIDGE_SCRIPT)
target_link_libraries(MyPlugin PRIVATE my_ui_bytecode)
Generates a static library with, per script:
namespace my_ui_bytecode {
extern const unsigned char runtime_js_pjsbc[];
extern const std::size_t runtime_js_pjsbc_size;
}
Register each blob before the editor opens (the processor's constructor or
editor_prewarm()):
pulp::view::register_embedded_script_bytecode(my_ui_bytecode::runtime_js_pjsbc,
my_ui_bytecode::runtime_js_pjsbc_size);
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
SOURCES |
Yes | -- | Scripts to compile |
NAMESPACE |
No | <target> |
C++ namespace for the generated symbols |
WIDGET_BRIDGE_SCRIPT |
No | off | Compile each script as WidgetBridge::load_script() evaluates it |
Behavior¶
- A blob is used only for the exact source text it was compiled from (length and SHA-256) and only by the engine build that compiled it. A changed script, another Pulp version, or a damaged blob falls back to compiling from source.
- The generator is built from the same Pulp libraries the product links. When cross-compiling it cannot run; the library then holds placeholder blobs that registration rejects, and the scripts compile at run time.
script_bytecode_cache_stats().embeddedcounts scripts served from embedded bytecode.
pulp_add_wgsl_shaders¶
Status: usable
Embed .wgsl files in a target as std::string_view constants and validate
them at build time.
pulp_add_wgsl_shaders(MyPlugin
FILES shaders/glow.wgsl shaders/blur.wgsl
NAMESPACE my_plugin::shaders)
Each file becomes <stem>_wgsl.hpp on the target's private include path:
#include "glow_wgsl.hpp"
auto shader = device.create_shader_module(my_plugin::shaders::glow_wgsl, "glow.wgsl");
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
FILES |
Yes | -- | WGSL files, relative to the current source directory |
NAMESPACE |
No | <target>_wgsl |
C++ namespace for the constants |
NO_VALIDATE |
No | off | Embed only; skip build-time validation |
Behavior¶
- Headers are generated at build time and regenerated only when a shader changes. The source is stored as a byte array, so a shader of any length compiles on every toolchain; the array is NUL-terminated.
- Validation runs the
pulp-wgsl-validatehost tool, which compiles each file on Dawn's Null adapter. The Null backend draws nothing but runs the full WGSL compiler, so a failing shader stops the build withfile:line:column: error: message. The compiler is the one inside the pinned Dawn (BSD-3-Clause), reached only through Dawn's public WebGPU API. - Validation is skipped, with a configure-time status message, where the tool does not exist: cross-compiled builds (iOS, Android, the browser), builds without Dawn, and projects built against an installed SDK. If the host cannot create a Null adapter, the tool prints a warning and the build goes on unvalidated.
- At run time
GpuDevice::create_shader_module()reports the same diagnostics as aGpuError(see Your First WGSL Pass in a View).
pulp_add_cargo_staticlib¶
Status: experimental
Build a Rust (Cargo) staticlib and expose it as an IMPORTED static-library
target, for an opt-in native-component DSP core written in
Rust (or any language that compiles to a C-ABI staticlib). Available via
include(PulpCargoStaticlib). It requires a Rust toolchain (cargo + rustc,
install from rustup.rs) and is intended for the opt-in lane
gated behind PULP_BUILD_NATIVE_COMPONENT_RUST_TESTS — OFF by default, so a
default Pulp build never needs Rust.
include(PulpCargoStaticlib)
pulp_add_cargo_staticlib(
NAME MyGain_Core # IMPORTED target name to create
MANIFEST ${CMAKE_CURRENT_SOURCE_DIR}/rust-core/Cargo.toml
LIB_NAME my_gain_core # crate's [lib] name → libmy_gain_core.a
PROFILE release # optional: dev (default) | release
FEATURES simd # optional: forwarded as --features
)
# Link it into your component target; a direct symbol reference pulls the
# archive into the final module.
target_link_libraries(MyPlugin_Core PRIVATE MyGain_Core)
Parameters¶
| Parameter | Required | Default | Description |
|---|---|---|---|
NAME |
Yes | -- | Name of the IMPORTED target to create |
MANIFEST |
Yes | -- | Absolute path to the crate's Cargo.toml |
LIB_NAME |
Yes | -- | The crate's [lib] name (the archive is lib<LIB_NAME>.a) |
PROFILE |
No | dev |
dev or release cargo profile |
FEATURES |
No | -- | Cargo features, forwarded to --features |
Implementation notes¶
- The archive is built
-C relocation-model=pic -C panic=abort(PIC so it links into Pulp's globally-PIC build;panic=abortso a Rust panic can never unwind across theextern "C"boundary — also pinned in the crate'sCargo.tomlas belt-and-suspenders). - The custom command
DEPENDSon aCONFIGURE_DEPENDSglob of the crate'ssrc/*.rsplus itsCargo.toml, so editing Rust sources re-invokes cargo on the next build (cargo is itself incremental, so a no-op re-invoke is cheap). Without this, theOUTPUTrule would only fire when the archive was missing and could silently link a stale archive. - cargo runs with
--locked: the build uses the crate's committedCargo.lockexactly and fails rather than rewriting it or resolving a crate version nobody reviewed. The crate must commit aCargo.locknext to itsCargo.toml; configure refuses a crate without one (create it withcargo generate-lockfile --manifest-path <Cargo.toml>). WithFETCHCONTENT_FULLY_DISCONNECTEDON, cargo also runs--offlineand resolves only from the local Cargo registry cache. The Rust CLI (experimental/pulp-rs) follows the same rule for its build and itscargo testctests. - On Linux/Android the IMPORTED target's
INTERFACE_LINK_LIBRARIESpulls in the system libraries the Ruststdarchive needs (Threads::Threads,${CMAKE_DL_LIBS},m); macOS resolves these throughlibSystemautomatically.
pulp_faust_generate¶
Generate a developer-selected C++ artifact from a Faust .dsp source file with
an optional external compiler. When faust is unavailable, no codegen command is
registered. Pulp reference DSPs remain buildable and are not claimed to match
compiler output.
include(${PULP_ROOT}/tools/cmake/PulpFaust.cmake)
pulp_faust_generate(
${CMAKE_CURRENT_BINARY_DIR}/my_gain_faust.hpp
${CMAKE_CURRENT_SOURCE_DIR}/gain.dsp
FaustGainDsp
)
Parameters¶
| Parameter | Required | Description |
|---|---|---|
output_hpp |
Yes | Artifact path to generate; prefer the build tree unless provenance is pinned and verified |
input_dsp |
Yes | FAUST .dsp source file |
class_name |
Yes | C++ class name passed to FAUST via -cn |
pulp_add_faust_test¶
Add a Catch2 test target for a FAUST-backed processor.
Parameters¶
| Parameter | Required | Description |
|---|---|---|
target |
Yes | Test executable target name |
test_source |
Yes | Main test source file |
SOURCES |
No | Additional source/header files needed by the test target |
pulp_add_wam_plugin¶
Build a Pulp Processor into a WAMv2
(Web Audio Modules v2) WebAssembly AudioWorklet plugin. Experimental —
targets a stereo, single-instance canary, not full WAM-host conformance.
Requires the Emscripten toolchain (configure with emcmake cmake ...); under
any other toolchain the helper is a no-op, so it never breaks native builds.
include(${PULP_ROOT}/tools/cmake/PulpWam.cmake)
pulp_add_wam_plugin(PulpGain
ENTRY pulp_gain_wasm.cpp # required: the wam_* factory TU
INCLUDES ${PULP_ROOT}/examples/pulp-gain # plugin headers
# SOURCES extra_dsp.cpp # optional: extra DSP sources
# SINGLE_FILE # optional: BASE64-embed for AudioWorklet
)
Parameters¶
| Parameter | Required | Description |
|---|---|---|
ENTRY |
Yes | The plugin's factory translation unit. It defines std::unique_ptr<pulp::format::Processor> pulp_wam_make_processor(); the shared wam_* C ABI lives in core/format/src/wasm/wam_entry.cpp. |
SOURCES |
No | Additional plugin DSP source files. |
INCLUDES |
No | Additional include directories (plugin headers). |
SINGLE_FILE |
No | Emit a BASE64-embedded ES-module factory for the AudioWorklet (sync compile, no fetch in worklet scope). Without it, a separate .wasm is emitted for the Node runner and export inspection. |
NATIVE_EDITOR |
No | Record in the web-build report that the plugin's native create_view() editor is replaced by generated controls in the headless web build. |
Created targets¶
${NAME}-wam— emits${NAME}.js(+${NAME}.wasmunlessSINGLE_FILE).
Notes¶
- The helper compiles the headless DSP subset once into a shared
pulp-wam-dspobject library and owns thewam_*EXPORTED_FUNCTIONStable and Emscripten link flags. It does not link the desktoppulp::format/pulp::statelibraries, which publicly pullpulp::view/GPU and crypto/HTTP — none of which belongs in a headless browser DSP module. chocheaders are located via-DPULP_WAM_CHOC_INCLUDE=<dir containing choc/>(or a populated sibling build tree).
pulp_stage_runtime_dependencies¶
Stage every runtime file a plug-in or app module needs beside its binary.
Defined in PulpRuntimeStaging.cmake, which is included by both Pulp's own
source build (root CMakeLists.txt) and by PulpConfig.cmake for
find_package(Pulp) consumers — so it is always available in either. Pulp's
format helpers call it for every target they create, so a plug-in built with
pulp_add_plugin / pulp_add_app needs nothing extra. Call it directly only
when you create a loadable module yourself.
It delegates the wgpu runtime copy to target_copy_webgpu_binaries() —
upstream's wgpu FetchContent package supplies that in a source build, and
PulpWebGpuImportedTarget.cmake supplies a fallback for consumers. Pulp never
redefines or shadows the upstream function; it adds the parts upstream has no
concept of.
find_package(Pulp REQUIRED)
add_library(MyCustomModule MODULE my_module.cpp)
target_link_libraries(MyCustomModule PRIVATE Pulp::view)
pulp_stage_runtime_dependencies(MyCustomModule)
pulp_verify_runtime_dependencies_staged(MyCustomModule) # optional but cheap
What gets staged¶
| File | When | Why it is not optional |
|---|---|---|
wgpu-native runtime (libwgpu_native.* / wgpu_native.dll) |
whenever the webgpu imported target exists |
the module links against it |
icudtl.dat |
Windows, Skia builds | SkParagraph's ICU-backed SkUnicode returns null without it and deliberately traps on its fUnicode check during the first label render — the host process dies on first paint, it does not fall back to simpler text |
On Apple the helper also sets BUILD_WITH_INSTALL_RPATH and appends
@loader_path to INSTALL_RPATH, so a signed Developer ID bundle resolves the
staged runtime from inside itself rather than from the SDK prefix used at build
time (library validation rejects the latter).
ICU data resolution¶
One canonical Pulp-side variable, PULP_SKIA_ICUDTL_FILE, with SKIA_ICUDTL_FILE
(what FindSkia sets) as the fallback. A Windows Skia build that resolves
neither is a hard FATAL_ERROR rather than a silent omission — a module that
configures cleanly and then kills its host on first render is the worse outcome.
Related functions¶
| Function | Checks |
|---|---|
pulp_stage_runtime_dependencies(<target>) |
adds the staging commands |
pulp_verify_runtime_dependencies_staged(<target>) |
POST_BUILD: the files really landed beside the built binary |
pulp_assert_runtime_dependencies_staged(<target>) |
configure time: the staging call was made at all |
target_copy_webgpu_binaries(<target>) |
upstream/SDK primitive — copies only the wgpu runtime; pulp_stage_runtime_dependencies calls it |
Call pulp_stage_runtime_dependencies rather than target_copy_webgpu_binaries
in new code. The latter is a real upstream function that does exactly what its
name says — copy the wgpu runtime — and nothing else: no @loader_path rpath on
Apple, no ICU data on Windows. Using it alone produces a module that builds
clean, runs on the build machine, and fails once shared or once it renders text.