Corrects a false unguarded-include claim, re-scopes hot-path evidence to what's actually linked, replaces a nonexistent Fork-A collision scenario, fixes several miscounted TUs/libraries, re-grades two Fork-B items from Blocker to Major/non-blocker, and adds missing owners/done-conditions.
57 KiB
Λ-W1 Track 1 — Linux build/toolchain audit (build-system lens)
Static analysis of the build surface only — the 23 CMakeLists.txt files, cmake/,
src/resource.rc/src/resource.h + the SWELL resgen pipeline, the three vendored submodules
as build inputs, the ctest harness, toolchain-level flags, and the packaging story in
docs/product/versioning-and-release.md. 2026-08-02, branch pl-w1-t1-build-toolchain-audit
(cut from dev at 6e937b9).
Source portability is explicitly NOT audited here — no Win32/GDI/LICE API usage, path handling, threading, drag/drop, dialog code, fonts, or instrument runtime code. A parallel track (T2) owns that; what I noticed in passing is listed under "Referred to T2" at the end and nowhere else.
Verification posture. I am on Windows and cannot build or run anything on Linux. Every
claim below is grounded in a file I opened in this worktree — the tree's own CMake, or a
vendored header/source under vendor/ — not asserted from memory of similar SDKs. Where a
claim depends on runtime behaviour I cannot observe (does REAPER's Linux build call
SWELL_dllMain? does GCC accept this TU?), it is marked [verify — Linux] with the exact
check, and it is not stated as fact. A subsequent citation pass caught several
line-number and attribution errors in this draft (corrected in place, disclosed at the point
each occurs) — grounded-in-a-file is a floor, not a guarantee against a mis-copied line range.
To get the vendored citations I ran git submodule update --init for all three submodules
inside this worktree; nothing in the tracked tree was modified.
Severity vocabulary, per the dispatch: Blocker = the Linux build cannot produce a loadable artifact; Major = it builds but is degraded or unshippable; Minor = ergonomics, warnings, CI polish. Effort bands are S (< half a day), M (1–3 days), L (a week+), and each names the reason for the band rather than asserting a number.
1. Baseline — what the build does today, per platform
Two loadable modules, 84 pure libraries declared under src/core/, 91 CTest targets,
three vendored submodules. (84 = the reasampler_pure_library + bare add_library count,
grep-verified library-by-library across the 18 src/core/** CMake files that declare any —
src/core/CMakeLists.txt and src/core/instrument/CMakeLists.txt declare none, only
add_subdirectory calls (plus one test in the latter); there are 20 CMake files under
src/core/** total, matching the 19 figure in §1d below, which counts test declarations
across the whole tree, not src/core/** alone. 46 of the 84 are on the extension's link
line.)
- Root
CMakeLists.txtis 95 lines and declares no targets at all: it owns the version string (:24), the channel fork (:40–54), theconfigure_fileforversion_generated.h(:58–61), the four vendor path variables (:65–68), the four-TULICE_SRClist (:78–83),enable_testing()(:90), and threeadd_subdirectorycalls (:92–94). - Language/toolchain settings are exactly three lines:
CMAKE_CXX_STANDARD 17,CMAKE_CXX_STANDARD_REQUIRED ON,CMAKE_POSITION_INDEPENDENT_CODE ON(CMakeLists.txt:28–30). There is noCMAKE_BUILD_TYPE, noCMAKE_CXX_FLAGS, noCMAKE_CXX_EXTENSIONS, no IPO/LTO setting, and notarget_compile_optionsanywhere in the tree — I grepped all 23 CMake files forcompile_options,find_package,pkg_check,link_directories,target_link_options, andinstall(and got zero hits outside the two files named below. - Platform conditionals exist in exactly two files. Grepping all 23
CMakeLists.txtforWIN32|APPLE|UNIX|LINUX|MSVC|GNU|Clang|CMAKE_SYSTEMreturns hits only insrc/app/CMakeLists.txt(:71if(WIN32),:76elseif(APPLE),:88else()) andsrc/shell/instrument/CMakeLists.txt(:9if(WIN32 AND EXISTS ...)). Every other CMake file is platform-neutral by construction: they call the two helpers incmake/reasampler_targets.cmake(reasampler_pure_libraryat:8–15,reasampler_testat:19–24) and nothing else.
1a. The REAPER extension (reaper_reasampler)
Declared as a MODULE library over 39 listed TUs (38 shell TUs + app/main.cpp itself)
plus ${LICE_SRC} (src/app/CMakeLists.txt:8–51), linking 46 pure static libraries (:52) with
${SDK_INC} ${WDL_INC} on the include path (:56). Output naming is
PREFIX "" + channel-derived OUTPUT_NAME, pinned to the top of the build tree by
LIBRARY_OUTPUT_DIRECTORY/ARCHIVE_OUTPUT_DIRECTORY (:65–69).
The three-way platform block (:71–98) is:
| Branch | State | What it does |
|---|---|---|
if(WIN32) :71–74 |
Live and shipped | adds src/resource.rc for the RC compiler |
elseif(APPLE) :76–86 |
Partly aspirational | adds swell-modstub.mm, defines SWELL_PROVIDED_BY_APP, links -framework AppKit, forces .dylib. The resgen target_sources line at :86 is commented out. |
else() (Linux) :88–97 |
Partly aspirational | adds swell-modstub-generic.cpp, defines SWELL_PROVIDED_BY_APP, forces .so. The resgen target_sources line at :97 is commented out. |
So on Linux the build system today produces reaper_reasampler.so with the SWELL modstub
compiled in and no dialog resource of any kind. README.md:67–68 states this is
"expected, not a bug" — the tree is honest about it.
Open question, macOS-only, unresolved by this doc. The APPLE branch adds
${SWELL}/swell-modstub.mm (:79), an Objective-C++ TU, but CMakeLists.txt:26 is
project(reaper_reasampler VERSION 1.4.0 LANGUAGES CXX) — OBJCXX is never enabled anywhere
in the tree. Whether CMake resolves a .mm source against a plain CXX-only project (some
Apple-toolchain configurations infer Objective-C++ from the extension regardless; others do
not) is unverified from this tree and untestable without a macOS box. [verify — macOS].
Three build-input facts I verified in vendor/ and that hold in the extension's favour:
vendor/WDL/WDL/swell/swell-modstub-generic.cppis entirely inside#ifdef SWELL_PROVIDED_BY_APP(:21, closed at:150). SinceSWELL_LOAD_SWELL_DYLIBis not defined anywhere in this build, the#elsebranch at:133applies: the file's whole contribution is the threeSWELL_curmodule_*resource_headglobals (:31–33), the API function-pointer table built fromswell.h(:37–49), and theSWELL_dllMainexport (:135) that resolves every SWELL symbol from a host-suppliedGetFunc. That is the correct stub for a REAPER extension, and the comment atsrc/app/CMakeLists.txt:90("With SWELL_PROVIDED_BY_APP you can drop pkg-config / -lX11 entirely") is correct: no SWELL implementation TU, no X11, no GDK is compiled or linked into the extension.- The four-TU LICE slice is self-contained on Linux.
lice.cppreaches SWELL only throughSWELL_DeleteGfxContext(:153,:218),SWELL_GetCtxFrameBuffer(:182,:226) andSWELL_CreateMemContext(:224);lice_textnew.cppreaches it throughSWELL_PushClipRegion/SWELL_SetClipRegion(:1027–1028) andSWELL_PopClipRegion(:1048). All six are entries in SWELL's API table (vendor/WDL/WDL/swell/swell-functions.h:857,:863,:877,:885–887), so the modstub resolves them from the host. No fifth LICE TU is implied: the_LICE_RGB2HSV_invtabtable lives insidelice.cppitself (:3040), and the one non-Win32-only helperutf8makecharis a file-static defined atlice_textnew.cpp:26and used at:241–242. This is a link-success inference, not a from-first-principles proof — the actual proof is V1, a successful build. One loose end: SWELL's ownMakefile:106–107listslice_colorspace.oamong the LICE objects its build needs — a fifth TU we don't compile. It stays unreconciled but harmless:lice_colorspace.cppdefines the realLICE_RGB2HSVfunction (declaredlice.h:577, distinct from the_invtabtable above), and grepping our four LICE_SRC TUs and all ofsrc/for a call to it returns nothing — SWELL needs the TU for its own generic-GDK backend, which our four-TU slice never invokes into. CMAKE_POSITION_INDEPENDENT_CODE ON(CMakeLists.txt:30) is already set. This is the single most commonly missed setting for aMODULEtarget fed bySTATIClibraries on Linux, and it is correct today.
1b. The VST3 instrument (reasampler_vst)
Hard-gated to Windows: if(WIN32 AND EXISTS "${VST3_SDK}/public.sdk/source/main/pluginfactory.cpp")
(src/shell/instrument/CMakeLists.txt:9). The gate is a conjunction — even with the
vendor/vst3sdk slice fully initialised, a Linux configure silently omits the target.
README.md:33–35 states this explicitly. Two module-level CLAUDE.md files record the
underlying product decision (D5) as settled, with "a mac/Linux build" named as a
non-goal: src/core/instrument/CLAUDE.md (Non-goals/guardrails) and
src/shell/instrument/CLAUDE.md (same). src/shell/panel/CLAUDE.md carries a third
instance ("Windows-only (D5) — font/GDI/HFONT choices assume Windows").
Inside the gate: a 20-source vst3_sdk static library (:16–38, grep-counted; lines 28/29/37
are comments) pinned to SDK tag
v3.7.9_build_61 (confirmed by git describe --tags in vendor/vst3sdk), and the
reasampler_vst MODULE (:44–81) which additionally compiles dllmain.cpp and
moduleinit.cpp directly (:78–79, with the linker-stripping rationale at :75–77) plus
${LICE_SRC} (:80). Output is a single file named <name>.vst3 (:106–111).
1c. Resources
src/resource.rc declares exactly one dialog — IDD_BANK_PANEL, a bare WS_CHILD
DIALOG with an empty BEGIN/END body and no controls (:18–22). src/resource.h
defines the single id IDD_BANK_PANEL 1000 (:8). It is consumed by
CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_BANK_PANEL), …) at
src/shell/panel/panel_window.cpp:135.
1d. Test harness
91 test declarations across 19 CMakeLists.txt files (grep count), matching 91 files in
tests/. 90 go through reasampler_test (cmake/reasampler_targets.cmake:19–24, which is
add_executable + target_link_libraries + add_test); one is longhand —
app_version_padding_tests (src/core/version/CMakeLists.txt:37–42), which recompiles
app_version.cpp against a second configure_file output so the include-dir substitution
works. Neither shape carries anything platform-specific.
Only two test files mention a platform at all: tests/test_capture_paths.cpp (five
#ifdef _WIN32 blocks at :19, :41, :87, :152, :205, each with a POSIX #else
branch already written — e.g. :158–168 asserts resolveBankFile("/home/d/proj", …)), and
tests/test_pitch_shift.cpp:3, where the string is inside a comment explaining why the
target links nothing that would drag <windows.h> in. No test includes reaper_plugin.h,
windows.h, or any SWELL header. The test harness is the healthiest part of this
surface; see §3 for the one generator-shape caveat.
1e. Packaging / release, as already committed
docs/product/versioning-and-release.md already commits to Linux as a first-class target:
:361–363— "Artifact per platform. The binary isreaper_*.dll|.dylib|.so; the macOS/Linux builds need the SWELL resgen step (CLAUDE.md §SWELL dialog resources) baked into the pipeline. Three platform artifacts per channel per release.":307–310— two named artifacts (stable + beta) per platform, "three platform artifacts each", explicitly handed off to dev-ops.:364–366— "Install is copy-in + restart… there is no server-side rollout."
What that document does not contain: any mention of code signing, macOS notarization, a
CI runner, or a Linux install path. I grepped it for sign|notariz|CI|platform|linux — one
hit is not cited above (:393, "artifacts (stable + beta) per platform", the same dev-ops
handoff as :307–310); the rest are the lines above. The substantive conclusion — no
signing/notarization/CI/Linux-install-path mention anywhere — holds regardless. There is no
CI configuration anywhere in the repo (ls -a on
the root shows .git, .gitignore, .gitmodules, CLAUDE.md, CMakeLists.txt, LICENSE,
README.md, cmake, docs, src, tests, vendor — no .github, no .gitlab-ci.yml),
and no install() rule in any of the 23 CMake files. So "the pipeline" is entirely notional
today, on every platform.
2. Findings
Λ-01 — the Linux/macOS dialog-resource path is broken in three independent ways, and the fix may not need resgen at all
Severity: Major. Effort: S (mechanical) to M (if the resgen route is taken and automated).
Citations. src/app/CMakeLists.txt:94–97 (the Linux comment block) and :83–86 (the
identical macOS one); vendor/WDL/WDL/swell/swell_resgen.php:313–314;
vendor/WDL/WDL/swell/swell-dlggen.h:22; vendor/WDL/WDL/swell/sample_project/myapp.cpp:192–193;
vendor/WDL/WDL/swell/swell-dlggen.h:214–218; vendor/WDL/WDL/swell/swell-modstub-generic.cpp:31–33;
vendor/WDL/WDL/swell/swell-functions.h:612–619; src/resource.rc:18–22;
src/shell/panel/panel_window.cpp:135.
What breaks. Three defects stack, and each alone would sink the step:
- The named script does not exist. Both comment blocks say
php ${WDL_INC}/swell/mac_resgen.php src/resource.rc(:85and:96). Listingvendor/WDL/WDL/swell/showsswell_resgen.php,swell_resgen.pl, andswell_resgen.sh— there is nomac_resgen.php.CLAUDE.mdandREADME.md:120both name the correct script; only the CMake comments are wrong, which is exactly the file an implementer will be reading when they do the work. - The named output file is not what the generator writes. Both commented
target_sourceslines (:86,:97) reference${REASAMPLER_SRC_DIR}/resource.rc_mac_dlg.h.swell_resgen.php:313–314composes its output names as$srcfn . "_mac_menu"and$srcfn . "_mac_dlg"— so the actual outputs aresrc/resource.rc_mac_dlgandsrc/resource.rc_mac_menu, with no.hsuffix. - Even the correct filename cannot be a
target_sourcesentry.swell-dlggen.h:22states the contract outright: "Use swell_resgen.pl or swell_resgen.php on the .rc file, which will produce .rc_mac_dlg, include that." The generated body is a run ofSWELL_DEFINE_DIALOG_RESOURCE_BEGIN(...)macro invocations (swell_resgen.php:117–129) that only expand afterswell-dlggen.hhas been included —swell-dlggen.h:214–218shows the macro emitting a staticSWELL_DialogRegHelperthat registers intoSWELL_curmodule_dialogresource_head. The canonical consumption pattern is the SDK's own sample:sample_project/myapp.cpp:192–193does#include "../WDL/swell/swell-dlggen.h"then#include "res.rc_mac_dlg". A CMaketarget_sourcesentry for an extensionless file would not even get a language assigned.
The runtime consequence, and why this is Major rather than Blocker. The .so still
builds and still loads: the extension's whole action family, capture pipeline, and persist
layer are untouched. What fails is the dock panel. swell-functions.h:615 defines
CreateDialog(hinst,resid,par,dlgproc) as
SWELL_CreateDialog(SWELL_curmodule_dialogresource_head,(resid),par,dlgproc,0).
swell-modstub-generic.cpp:32 defines that head but nothing ever populates it, because
the resgen'd registration TU does not exist — so it stays null and
panel_window.cpp:135's CreateDialogParam gets a null resource list. The panel is the
product's primary surface, so a Linux build without it is not shippable; it is not a
Blocker only because the binary loads and the actions work.
The route worth considering first. swell-functions.h:606–608 documents an escape
hatch: "If you are using CreateDialog() and creating a child window, you can use a resource
ID of 0, which creates an opaque child window. Instead of passing a DLGPROC, you should pass
a (WNDPROC) routine that returns LRESULT (and cast it to DLGPROC)." The header comment is
not just documentation — the implementation confirms both halves of it:
swell-dlg-generic.cpp:291–292 is SWELL_DialogResourceIndex *p=resById(reshead,resid); if (!p&&resid) return 0;, and resById (:34–43) walks from a (here, null) reshead and
returns 0 on a miss. That confirms the graceful-failure claim behind "Major rather than
Blocker" above (a null reshead plus a non-zero resid returns 0, not a crash — matching
panel_window.cpp:137's if (!g_panel.hwnd) return;), and that resid == 0 skips the
lookup entirely (&&resid short-circuits), which is the escape hatch this paragraph is about.
src/resource.rc:18–22
shows IDD_BANK_PANEL is precisely that: a WS_CHILD dialog with an empty body and zero
controls, whose comment at :8–10 says "the bank_panel shell owns every pixel and draws the
sample grid with LICE in WM_PAINT". If the panel is created with resource id 0 on
non-Windows, the entire resgen pipeline — the PHP dependency (README.md:54), the
generated-file-in-source-tree problem, and the per-platform regeneration story — disappears
from the Linux and macOS builds. The cost is a source change in panel_window.cpp and the
DLGPROC→WNDPROC return-type contract; that half is T2's call, not mine, and I am naming the
option rather than choosing it. This is a shared macOS/Linux fix either way.
If the resgen route is taken instead, the build work is: a find_program(PHP …), an
add_custom_command generating into ${CMAKE_CURRENT_BINARY_DIR} (not the source tree —
.gitignore has no pattern matching resource.rc_mac_dlg, so today's manual step would
leave an untracked generated file sitting in src/), a new one-line shim TU that includes
swell-dlggen.h then the generated file, and that shim added to target_sources. That is
the M-effort half of the band.
Λ-02 — --config Release is a silent no-op on Linux, so the documented ship command produces an unoptimized binary
Severity: Major. Effort: S — one if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) block plus a doc correction.
Citations. CMakeLists.txt:28–30 (the complete list of language settings — I read the
whole 95-line file and there is no CMAKE_BUILD_TYPE anywhere); README.md:106–109;
CLAUDE.md §"Build and test" and §"Install / reload".
What breaks. Both docs prescribe cmake --build build --config Release as the
optimize-and-ship incantation, and README.md:130–131 tells the user to copy from
build/Release/. --config is a multi-config-generator flag; on Ninja or Unix Makefiles it
is accepted and ignored. Because no CMAKE_BUILD_TYPE default is set, the resulting build
passes no -O flag at all to GCC/Clang — not -O0 explicitly, just the compiler's
unoptimized default. The failure is silent in both directions: the command succeeds, and the
artifact lands at build/reaper_reasampler.so — a path the docs never state for a
single-config generator (a gap, not an error; see Λ-08).
This matters more here than the usual "forgot to set the build type" — but only for peaks.
CLAUDE.md's §"Build and test" (not §"Performance guardrails") states, at CLAUDE.md:54,
that the header-inline hot paths "presume an optimizing build", with no LTO configured to
recover it if they don't. peaks' computeEnvelope/lastFrameAboveThreshold is one of
those paths and IS linked into the extension (src/app/CMakeLists.txt:52), so the finding
holds for Fork A as stated. It does not extend to Voice::advanceFrame or the three
envelopes.h evaluators (named at src/core/instrument/CLAUDE.md:342, not in root
CLAUDE.md) — those belong to sampler_core, which src/app/CMakeLists.txt:53–55 states is
deliberately NOT linked into the extension and which only enters a build at all inside the
Windows-gated reasampler_vst target (§1b). On Fork A that code never compiles into
reaper_reasampler in the first place; the unoptimized-build risk to it is a Fork B concern,
not this extension-only one. A Linux user following the README's --config Release would
ship an extension binary whose peaks hot path did not inline.
Done when: cmake --build build on Linux (Ninja/Make, no --config) produces a build
whose compile commands (compile_commands.json or verbose build log) show an explicit -O
flag, and the README/CLAUDE.md ship instruction names the single-config-generator path.
Sequencing note. This is not strictly Linux-specific — Ninja on Windows has the same shape — but it becomes load-bearing the moment Linux is a shipped platform, because Makefiles and Ninja are the default there.
Λ-03 — no symbol-visibility control, and two modules in one process both export a full copy of LICE
Severity: Major. Effort: S — one CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN pair, plus a [verify — Linux] load test.
Citations. CMakeLists.txt:78–83 (LICE_SRC); src/app/CMakeLists.txt:35 and
src/shell/instrument/CMakeLists.txt:80 (the same four LICE TUs compiled into both
modules — the rationale for the duplication is at CMakeLists.txt:75–77);
vendor/reaper-sdk/sdk/reaper_plugin.h:52; src/app/main.cpp:251;
vendor/vst3sdk/pluginterfaces/base/fplatform.h:164;
vendor/WDL/WDL/swell/Makefile:20.
What breaks. Nothing in the build sets a visibility preset, so GCC/Clang default to
-fvisibility=default and every non-static symbol in both modules — all of LICE, all the
WDL helpers, every pure library's exported functions, and the whole C++ standard-library
instantiation set — lands in the module's dynamic symbol table. On Windows this is a
non-issue: each DLL has its own symbol table and per-module resolution. [verify — Linux]
on ELF, symbols from dlopen'd objects CAN participate in global interposition — but whether
they actually do here depends on REAPER's own dlopen flags (RTLD_GLOBAL vs RTLD_LOCAL)
and whether REAPER's executable is linked --export-dynamic, neither of which this tree can
answer. The scenario this doc originally named — a hypothetical reasampler_9000.so — does
not exist on Fork A: per Λ-10 and src/shell/instrument/CMakeLists.txt:9, the VST3 target is
gated WIN32 AND EXISTS ... and is never built on Linux unless D1 selects Fork B. The
Fork-A-real risk is different: any second REAPER extension loaded in the same REAPER
process (a third-party .so in UserPlugins/) that links its own copy of LICE/WDL and also
defaults to -fvisibility=default is a candidate to interpose with reaper_reasampler.so's
LICE_FillRect and the rest of the exported LICE/WDL symbol set. That is a class of bug that
presents as "one extension's LICE draws with another extension's LICE build" and is invisible
on Windows, where each DLL keeps its own symbol table.
Why hiding is safe here. The two symbols that genuinely must be exported already say so
explicitly: reaper_plugin.h:52 defines
REAPER_PLUGIN_DLL_EXPORT __attribute__((visibility("default"))) on non-Windows, and
main.cpp:251 uses it (extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(...)).
On the VST3 side, fplatform.h:164 defines SMTG_EXPORT_SYMBOL the same way. SWELL's own
build already uses -fvisibility=hidden (Makefile:20), which is the precedent. The one
thing to confirm on a Linux box is that swell-modstub-generic.cpp:135's SWELL_dllMain
survives — it carries its own __attribute__ ((visibility ("default"))) at that line, so it
should, but the modstub is compiled by our build, not SWELL's.
Shared macOS/Linux — the visibility default and the LICE-TU duplication (CMakeLists.txt:75–77)
apply the same way under the APPLE branch's Mach-O two-level namespace, though the concrete
Fork-A-real interposition mechanism above is ELF-specific; macOS's own dynamic-linker symbol
resolution is a separate [verify] this doc does not attempt.
Λ-04 — nothing links a thread library, and <pthread.h> is in the extension's include graph
Severity: Major (VST3 target) / Minor (extension). Effort: S — find_package(Threads) + Threads::Threads.
Citations. src/shell/panel/panel_state.h:20–24;
vendor/vst3sdk/base/thread/source/flock.cpp:71–133;
src/shell/instrument/CMakeLists.txt:15–39; vendor/WDL/WDL/swell/Makefile:90.
What breaks. No CMake file in the tree calls find_package(Threads) or links
Threads::Threads/-lpthread/-ldl (grep-verified across all 23). Two separate exposures:
- Extension.
panel_state.h:23includes<pthread.h>on non-Win32 — and it is not the only entry point:vendor/reaper-sdk/sdk/reaper_plugin.h:50includes<pthread.h>unconditionally in its non-Windows branch, so it is in every shell TU's include graph (every.cppthat includesreaper_plugin.h/reaper_plugin_functions.h), not just the one panel file. This strengthens rather than changes the finding: I still did not find a pthread call insrc/shell/'s own code, so this may be an include-only dependency that needs no link flag on a modern glibc (where the pthread symbols are in libc proper). Stays Minor, and[verify — Linux]. - VST3 target.
flock.cppis unambiguous: on non-Windows it callspthread_mutexattr_init/_settype/_destroy(:71–76),pthread_mutex_init(:74),pthread_mutex_destroy(:89),_lock(:105),_unlock(:121) and_trylock(:133). It is unconditionally in thevst3_sdksource list (src/shell/instrument/CMakeLists.txt:27). Any Linux VST3 target must link a thread library. SWELL's own Makefile uses-lpthread -ldl(:90) for the same reason.
Adding find_package(Threads REQUIRED) + target_link_libraries(... Threads::Threads) is
correct on all three platforms and costs nothing on Windows.
Done when: the extension links with Threads::Threads removed (proving the include-only
half needs no flag, or naming the symbol that forces it — see V5), and, if Fork B is taken,
reasampler_vst links clean with Threads::Threads present.
Shared macOS/Linux — reaper_plugin.h:50's non-Windows <pthread.h> include and
flock.cpp's non-Windows pthread calls apply identically to the APPLE branch.
Λ-05 — reaper_plugin.h's SWELL include resolves only by coincidence of the -I line
Severity: Minor. Effort: S — a comment, or an INTERFACE target that carries both include dirs as one unit.
Citations. vendor/reaper-sdk/sdk/reaper_plugin.h:42–54; CMakeLists.txt:65–66;
src/app/CMakeLists.txt:56; src/shell/instrument/CMakeLists.txt:97.
What breaks — and, today, does not. On non-Windows, reaper_plugin.h:49 does
#include "../WDL/swell/swell.h". Relative to the including file's own directory
(vendor/reaper-sdk/sdk/) that resolves to vendor/reaper-sdk/WDL/swell/swell.h, which does
not exist — listing vendor/reaper-sdk/ shows only README, reaper-plugins, and sdk.
The include is nevertheless satisfiable, because the quoted-include fallback searches the
-I path, and WDL_INC is ${CMAKE_CURRENT_SOURCE_DIR}/vendor/WDL/WDL (CMakeLists.txt:66) —
so WDL_INC + ../WDL/swell/swell.h normalizes to vendor/WDL/WDL/swell/swell.h, which
exists. Both module targets pass ${WDL_INC} (src/app:56, src/shell/instrument:97), so it
holds today.
The hazard is that this is a load-bearing coincidence with no comment anywhere, and it is
invisible on Windows — reaper_plugin.h:43 takes <windows.h> instead, so dropping
${WDL_INC} from any target that includes reaper_plugin.h would pass CI on Windows and
fail only on Linux/macOS. Worth pinning before more targets are added.
Λ-06 — the build has no platform partition of its source list, so every T2 finding lands here as a CMake change
Severity: Minor (structural). Effort: S per TU once T2 says which ones need it.
Citations. src/app/CMakeLists.txt:8–51 and :71–98;
src/shell/actions/drag_out_win.cpp:8, :249, :282.
Mechanism. All 38 shell TUs (plus app/main.cpp, 39 listed TUs total) are listed unconditionally (:8–51); the platform block at
:71–98 only adds sources, never removes or substitutes any. Every TU therefore enters a
Linux compile, and the only thing standing between the build and a compile error is each
file's own #ifdef discipline. Today that discipline is uneven in a way worth recording:
drag_out_win.cppis the model —#ifdef _WIN32at:8with a real// ---- macOS / Linux (SWELL)branch at:249and#endifat:282.arrange_drop_win.cppandinstrument_drop_win.cppcontain zero occurrences of_WIN32(grep count 0 each) and include no<windows.h>— they are_win-suffixed for the surface they serve, not for a platform dependency, and appear portable by inspection.
This is not itself a defect; it is the reason the ordering sketch in §5 puts "read T2's output, then partition the source list" as a distinct step rather than folding it into the resgen work.
Λ-07 — std::filesystem in a pure library, with no stated minimum compiler
Severity: Minor. Effort: S — a cmake_minimum_required-adjacent compiler check or a documented floor.
Citations. src/core/capture/capture_paths.cpp:5 (#include <filesystem>) and :95
(std::filesystem::path(rppPath).parent_path().string()); README.md:50–54.
What breaks. capture_paths is a pure library with its own CTest target
(src/core/capture/CMakeLists.txt:1–2), so this is on the test-harness path as well as the
extension's. GCC 8 requires an explicit -lstdc++fs for std::filesystem; GCC ≥ 9 and
libstdc++ ≥ 9 do not. README.md:51 states only "A C++17 compiler" as the prerequisite, and
cmake_minimum_required(VERSION 3.19) (CMakeLists.txt:1) says nothing about the compiler.
On any current distro toolchain this is a non-issue; it becomes one on an older CI image.
Twelve other files in src/shell/ also use <filesystem> (grep-verified), but one —
src/shell/instrument/instrument_bake.cpp — is in the reasampler_vst target, not the
extension: eleven in reaper_reasampler, sharing the fix below; the twelfth shares it only
if/when Fork B builds that target on Linux.
Done when: capture_paths_tests links on the oldest toolchain the phase targets without
-lstdc++fs added explicitly, or that flag is added and documented as the floor.
Shared macOS/Linux — capture_paths and the eleven extension-side <filesystem> users
compile identically under the APPLE branch; whether Apple's libc++ needs the same kind of
minimum-toolchain floor as libstdc++ does is a separate [verify] this doc has not checked,
but the coverage-hole shape (undocumented compiler floor) is identical either way.
Λ-08 — the install instruction is silent about single-config generators, and no path is documented for Linux at all
Severity: Minor. Effort: S — a doc fix, or an install() rule if packaging is ever automated.
Citations. src/app/CMakeLists.txt:68 (LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}");
README.md:130–131; CLAUDE.md §"Install / reload";
docs/product/versioning-and-release.md:364–366.
What breaks — a gap, not an error. README.md:130–131 and CLAUDE.md §"Install /
reload" both already carry the multi-config qualifier verbatim ("build/Release/ on a
multi-config generator — not the default Debug/ output") — so the instruction is not
wrong, it is simply silent about the other case. On a multi-config generator CMake appends
the config directory, giving the documented build/Release/. On Ninja/Make the module lands
directly at build/reaper_reasampler.so, and neither doc names that path. Compounding it,
there is no install() rule anywhere in the 23 CMake files, and no target directory is
documented for Linux at all — CLAUDE.md and README.md:132 say "REAPER's UserPlugins/
folder (Options → Show REAPER resource path)", which is a correct user-facing instruction but
not a path a pipeline can use. I could not confirm REAPER's Linux resource-path root from the
SDK: grepping vendor/reaper-sdk/sdk/ for UserPlugins and reaper_*.so returns nothing —
the reaper_*.dll|dylib|so filename convention stated in CLAUDE.md is not documented in the
vendored headers. [verify — Linux].
Λ-09 — no warning flags anywhere, so the first GCC/Clang build surfaces a diagnostic set nobody has ever seen
Severity: Minor. Effort: S to add flags; unknowable to fix what they reveal — that is the honest band.
Citations. grep for target_compile_options across all 23 CMake files: zero hits (the
only compile_definitions hits are SWELL_PROVIDED_BY_APP at src/app/CMakeLists.txt:80,
:92 and the SDK's DEVELOPMENT/RELEASE selector at
src/shell/instrument/CMakeLists.txt:42). CMakeLists.txt:28–30 for the absence of
CMAKE_CXX_EXTENSIONS.
Mechanism. The codebase has been compiled by exactly one compiler family. MSVC's default
is /W1; GCC/Clang's default is essentially silent. Adding -Wall -Wextra on the first
Linux build is the right move, but the size of the resulting diagnostic set over ~39k LOC is
not estimable from Windows, which is why the effort band is split. Do not add -Werror
in the same change.
Secondary, in the same family: CMAKE_CXX_EXTENSIONS is unset, so it defaults to ON and
GCC/Clang compile as -std=gnu++17 rather than -std=c++17. Not a bug; a hygiene knob
worth flipping while the flags are being set, since it is the difference between "portable
C++17" and "whatever GNU extensions happened to be used".
Coverage hole: the link-succeeds-but-load-fails asymmetry is never stated. GNU ld does
not error on undefined symbols in a shared object by default — MSVC's linker does. For a
MODULE REAPER dlopen()s, that means a Linux build can link reaper_reasampler.so clean
and only discover a missing symbol when REAPER tries to load it (dlopen failing, or a
partially-resolved symbol table if a satisfying-by-coincidence symbol exists elsewhere in the
process — a real risk given Λ-03's global-interposition finding above). Neither Threads
(Λ-04) nor visibility (Λ-03) named this asymmetry, and it undercuts §5's framing of V1
(cmake --build build) as "the single highest-information action available" — a clean build
on Linux does not carry the same guarantee a clean Windows build does. -Wl,--no-undefined
(GNU ld) would restore the MSVC-equivalent fail-at-link-time behavior; whether it is needed
here specifically depends on whether SWELL_PROVIDED_BY_APP's function-pointer design (§1a)
already accounts for every symbol the modstub doesn't itself define — that is [verify — Linux], since it requires an actual GCC/Clang link to observe.
Done when: the Linux build either adds -Wl,--no-undefined (or the equivalent) so an
undefined-symbol build failure surfaces at link time, or a stated reason is recorded for why
SWELL_PROVIDED_BY_APP makes the gap moot.
Shared macOS/Linux — -Wall -Wextra, CMAKE_CXX_EXTENSIONS OFF, and the GNU-ld-vs-MSVC-ld
asymmetry (macOS's linker also does not default to erroring on undefined symbols) apply
identically to the APPLE branch.
Λ-10 — the submodule story costs a Linux developer a large download for a target that is gated off
Severity: Minor. Effort: S — a doc paragraph.
Citations. .gitmodules (three entries, no shallow, no branch);
README.md:70–86; src/shell/instrument/CMakeLists.txt:9; CLAUDE.md §"One-time submodule setup".
Mechanism. README.md:72–75 already warns that git clone --recursive pulls all of
vendor/vst3sdk's nested submodules (vstgui4, tutorials, doc, cmake) that this
project never links, and prescribes the narrow init instead. What neither the README nor
CLAUDE.md says is that on Linux the vendor/vst3sdk submodule is pure cost: the
WIN32 AND EXISTS gate at src/shell/instrument/CMakeLists.txt:9 omits the target
regardless, so git submodule update --init vendor/reaper-sdk vendor/WDL is the complete
Linux prerequisite. One sentence in the platform-support table would save every Linux
contributor the download.
3. Surfaces checked and found clean
Recorded so the plan author does not re-spend effort here. Each was read or grepped in this pass.
- All 23
CMakeLists.txtfiles were read in full, not sampled: root;src/app;src/core;src/core/{audio,capture,instrument,json,model,reclaim,tracking,ui,util,version,view,wire};src/core/instrument/{bake,engine,map,note,ui};src/core/instrument/engine/{filter,loop};src/shell/instrument. Pluscmake/reasampler_targets.cmake. Of these, 21 are entirely platform-neutral — they declare libraries and tests through the two helpers and nothing else. CMAKE_POSITION_INDEPENDENT_CODE ON(CMakeLists.txt:30) — the usual LinuxMODULE-fed-by-STATICtrap, already handled..sonaming.PREFIX ""(src/app/CMakeLists.txt:66) +SUFFIX ".so"(:93) + channel-derivedOUTPUT_NAME(:67) yieldsreaper_reasampler.so/reaper_reasampler_beta.so, matching thereaper_*convention.ARCHIVE_OUTPUT_DIRECTORY(:69) is a Windows import-lib concern and harmlessly inert on Linux.- The channel fork is platform-independent.
REASAMPLER_CHANNEL(CMakeLists.txt:40–54) threads throughconfigure_file(:58–61) into names only; nothing in it is Windows-specific, so beta builds fork identically on Linux. - The version padding canary (
src/core/version/CMakeLists.txt:21–42) uses a function-scopedset()+ secondconfigure_file+ longhandadd_executable. Portable; no platform assumption. - The whole test corpus is platform-neutral. 91 files in
tests/, 91 declarations in CMake. Onlytest_capture_paths.cppbranches on platform and it already has POSIX expectations written; onlytest_pitch_shift.cppmentionswindows.h, in a comment. - Generator-shape handling in the docs is already correct.
README.md:93–98explains that-C Debugis required on multi-config and unnecessary on Ninja/Make.ctest --test-dir buildworks bare on Linux; the-C Debugin the README's example is harmless there. - SWELL API coverage for the LICE slice — all six SWELL GDI entry points LICE needs are
in the modstub-resolved table (
swell-functions.h:857,:863,:877,:885–887). - The LICE slice needs no fifth TU on Linux (
lice.cpp:3040_LICE_RGB2HSV_invtabtable,lice_textnew.cpp:26file-staticutf8makechar) — an inference from V1's link success plus these two spot checks, not exhaustive; see §1a's fuller caveat, including the unreconciledlice_colorspace.oin SWELL's ownMakefile:106–107. swell-modstub-generic.cppcompiles against its own includes — it usesprintf(:61), which arrives viaswell.h→swell-types.h:29(#include <stdio.h>). Thedlopen/readlinkblock (:69–131) is behindSWELL_LOAD_SWELL_DYLIB, which this build never defines.- The Linux run-loop IIDs are already in the vendored VST3 slice.
public.sdk/source/common/commoniids.cpp:50–54definesLinux::IEventHandler,Linux::ITimerHandlerandLinux::IRunLoopunder#if SMTG_OS_LINUX, and that file is already in thevst3_sdksource list (src/shell/instrument/CMakeLists.txt:36). A Linux VST3 port needs no additional SDK translation unit for the run loop. SMTG_OS_LINUX/SMTG_EXPORT_SYMBOLare correct in the pinned SDK (pluginterfaces/base/fplatform.h:132,:164).- The MSVC-ABI caveat is Windows-scoped.
reaper_plugin.h:22warns that the C++ interfaces "require MSVC on win32, or at least the MSVC-compatible C++ ABI" — that is a constraint on Windows toolchains (mingw), not on Linux, where REAPER and the extension both use the Itanium ABI.
4. The VST3-on-Linux question, stated as a fork
Daniel rules on this. Below is the cost of each side with the evidence, and nothing else.
Fork A — extension only on Linux
Scope: everything in §2 (Λ-01 through Λ-10). The VST3 gate at
src/shell/instrument/CMakeLists.txt:9 stays exactly as it is; a Linux configure omits the
target and the vendor/vst3sdk submodule becomes optional there (Λ-10).
Cost profile: one Major that must be solved (Λ-01, the panel), two Majors that are one-line-ish but load-bearing (Λ-02 optimization, Λ-03 visibility), one Major/Minor (Λ-04 threads), and six Minors. No new third-party dependency. No new artifact format. No change to any product decision on record.
The unknown that dominates the band: none of the extension's source portability is audited here. The build-system work is small; whether the 38 shell TUs compile and behave against SWELL's Win32 subset is T2's answer, and it, not this list, sets the phase's size.
Fork B — extension + ReaSampler 9000 on Linux
Everything in Fork A, plus the following. Three of five are Blockers under the severity vocabulary defined above ("the Linux build cannot produce a loadable artifact"): B1, B2, B3. B4 downgrades to Major — the module still loads; only the editor fails to attach without it. B5 is not a technical blocker at all — it is a documentation act, though one that must precede the others in sequence (see §6 Stage 5).
-
B1 — the entry point is the wrong one, and the right one is already vendored.
src/shell/instrument/CMakeLists.txt:78compilespublic.sdk/source/main/dllmain.cppinto the module. That file does#include <windows.h>at:41with noSMTG_OS_*guard — grepping the file forSMTG_OSreturns nothing, and its only conditionals are#if defined(_MSC_VER) && defined(DEVELOPMENT)(:43,:96) and#ifdef __cplusplus(:58,:87). Linux needspublic.sdk/source/main/linuxmain.cpp, which is present in the slice and exportsModuleEntry/ModuleExit(extern "C"declarationslinuxmain.cpp:47–52; definitions:59–67and:72–82respectively). Both are mandatory — the SDK's own loader refuses the module without either (public.sdk/source/vst/hosting/module_linux.cpp:189–204, error strings "The shared library does not export the required 'ModuleEntry' function" and the same forModuleExit).GetPluginFactoryis checked at:205. Effort S — aCMakeLists.txtsource swap (dllmain.cpp→linuxmain.cpp) plus the platformif(); both files are already vendored, nothing to write from scratch. -
B2 — the artifact is a directory bundle, not a file. Today the target sets
SUFFIX ".vst3"on a MODULE (src/shell/instrument/CMakeLists.txt:106–111), producing a single file.module_linux.cpp:146–170(getSOPath) shows what a Linux host actually opens: the path must be a directory, containingContents/, containing<machine>-linux/where<machine>isuname().machine(:158–163, helper at:88–97), containing<stem>.so(:167–168). Soreasampler_9000.vst3/Contents/x86_64-linux/reasampler_9000.so. That isadd_custom_command(POST_BUILD)work plus an install rule. Install roots are$HOME/.vst3/,/usr/lib/vst3/,/usr/local/lib/vst3/and$APPFOLDER/vst3/(module_linux.cpp:282–302).Contents/Resources/moduleinfo.jsonis optional —getModuleInfoPath(:356–365) returns empty when the file is absent rather than failing. Effort M — mechanical, but it is a new artifact shape with an install story and a per-architecture directory name. -
B3 — the drawing stack is the real cost, and it is where Fork B stops being a build task. The VST3 module compiles the same four LICE TUs (
src/shell/instrument/CMakeLists.txt:80), and on non-Win32 LICE resolves its GDI through SWELL (§1a). But a VST3 plugin is not a REAPER extension:swell-modstub-generic.cpp's default branch exportsSWELL_dllMain(:135), which only REAPER's extension loader calls. Nothing hands a VST3 plugin the SWELL function table. That leaves two routes:- Route B3a —
SWELL_LOAD_SWELL_DYLIB(swell-modstub-generic.cpp:69–131): the stub reads/proc/self/exe, strips to the directory, anddlopenslibSwell.sobeside the host binary (:81–98), then callsSWELL_set_app_main(SWELLAppMain)(:112–114). That reference makesSWELLAppMaina required definition in the plugin — it is declared atswell-types.h:137as "to be implemented by app (if using swellappmain.mm)". Whether REAPER's Linux install ships alibSwell.sobeside its executable is[verify — Linux], and this route makes the VST3 load-bearing on being hosted by REAPER specifically, which is consistent with D5's REAPER-only stance but is a new runtime coupling to state explicitly. - Route B3b — build SWELL into the module.
vendor/WDL/WDL/swell/Makefile:100–104enumerates the 14 SWELL TUs (swell.o swell-ini.o swell-miscdlg-generic.o swell-wnd-generic.o swell-menu-generic.o swell-kb-generic.o swell-dlg-generic.o swell-gdi-generic.o swell-misc-generic.o swell-gdi-lice.o swell-generic-headless.o swell-generic-gdk.o swell-appstub-generic.o swell-modstub-generic.o) and:106–107the 8 LICE TUs it needs alongside. Link requirements at:90(-lpthread -ldl),:128–130(pkg-configgtk+-3.0orgdk-3.0),:145–151(freetype2+-lfontconfig) and:154(-lGL). This is a wholly new third-party dependency surface — GDK/GTK3, FreeType, Fontconfig, OpenGL — that the extension deliberately avoids viaSWELL_PROVIDED_BY_APP. Flagging it as a scope expansion, per the dispatch's constraint; it is not something to slip in. Effort L either way, and B3b is the larger of the two by a wide margin.
- Route B3a —
-
B4 — the editor's platform contract changes. The view must report
kPlatformTypeX11EmbedWindowID(pluginterfaces/gui/iplugview.h:79) instead ofkPlatformTypeHWND(:62), and timers/file-descriptors must go throughSteinberg::Linux::IRunLoop(iplugview.h:267–279) withIEventHandler(:223–229) andITimerHandler(:239–245) — the header states the reason at:255–256: "On Linux the host has to provide this interface to the plug-in as there's no global event run loop defined as on other platforms." The wholenamespace Linuxblock is#if SMTG_OS_LINUX(:209, closed:284), so it compiles in automatically. The build-side cost is nil (the IIDs are already vendored — see §3); the source-side cost iseditor_platform.cppand every timer in the editor, which is T2's surface. Effort: build S, source L — build is S because no new CMake source is needed (the SDK block compiles in automatically); source is L because it is a genuine window/run-loop rewrite (an X11 embed contract with no Win32 analogue, not a mechanical port) and outside this doc's own portability scope to size further. -
B5 — a settled product decision has to be reversed, in writing. D5 ("Windows-only, VST3-only, REAPER-only") is recorded as a locked decision with "a mac/Linux build" named as a non-goal in
src/core/instrument/CLAUDE.md(Invariants → the three locked decisions; and again under Non-goals/guardrails) andsrc/shell/instrument/CLAUDE.md(Non-goals/guardrails).src/shell/panel/CLAUDE.mdcarries a third dependent statement. Fork B is not just more work — it invalidates three recorded invariants and needs Daniel's explicit reversal before any code moves. Effort S — no code or CMake change of its own, a rewrite of the threeCLAUDE.mdpassages naming D5 plus Daniel's sign-off; the L-sized cost sits in B3/B4, not here.
The asymmetry worth naming. Fork A's build work is small and its unknown is source portability. Fork B's build work is genuinely large (B2, B3) and carries an unbounded source-portability tail (B3b's dependency surface, B4's window/run-loop rewrite) and requires reversing a settled decision. The two are not "the same phase, one bigger" — they are different-shaped commitments.
5. Open questions
[verify — Linux] — answerable by trying it on a Linux box
| # | Question | Exact check |
|---|---|---|
| V1 | Does the extension compile at all under GCC/Clang? | cmake -B build -S . -G Ninja && cmake --build build 2>&1 | tee build.log — the single highest-information action available and gates most of T2's list too, with one caveat (Λ-09): a clean link is not the same guarantee here as on MSVC, since GNU ld does not error on undefined symbols in a MODULE by default — V1 answers "does it compile and link", not "does it load". |
| V2 | Does ctest pass all 91 targets? |
cmake -B build -S . -G Ninja && cmake --build build && ctest --test-dir build --output-on-failure (no -C needed on Ninja). |
| V3 | Does REAPER's Linux build call SWELL_dllMain in a reaper_*.so, populating the API table? |
Drop the built .so in ~/.config/REAPER/UserPlugins/, restart, and check the extension's actions appear. A null table shows as immediate crash or dead actions. |
| V4 | Where exactly is UserPlugins/ on Linux, and is reaper_*.so the right filename glob? |
Options → Show REAPER resource path in a Linux REAPER. The SDK headers do not document either — grep of vendor/reaper-sdk/sdk/ for UserPlugins and reaper_*.so returns nothing. |
| V5 | Does panel_state.h:23's <pthread.h> actually require a link flag on the target glibc? |
Build without Threads::Threads; if it links, it does not. |
| V6 | Does -fvisibility=hidden leave ReaperPluginEntry and SWELL_dllMain exported? |
nm -D --defined-only reaper_reasampler.so | grep -E 'ReaperPluginEntry|SWELL_dllMain' after adding the preset. |
| V7 | With Λ-01 fixed by the resource-id-0 route, does SWELL_CreateDialog return a usable child HWND for the docker? |
Panel toggle action in a Linux REAPER. |
| V8 | Does std::filesystem need -lstdc++fs on the chosen toolchain? |
Build capture_paths_tests alone; the link error names it. |
| V9 | Does REAPER's Linux install ship a libSwell.so beside its executable (Fork B, route B3a)? |
ls $(dirname $(readlink -f $(which reaper)))/libSwell.so. |
| V10 | Which uname -m values must the VST3 bundle carry (Fork B, B2)? |
uname -m on each target. module_linux.cpp:158–163 keys the bundle directory off exactly this string (tree-grounded and verified); which architectures REAPER itself ships for Linux (x86_64, aarch64, armv7l is the claim) is an external fact with no source in this tree — [verify] against REAPER's own download page before relying on it. |
[Daniel] — genuine product/scope calls
| # | Question | Why it is not answerable by reading code |
|---|---|---|
| D1 | Fork A or Fork B? Extension only, or extension + ReaSampler 9000 on Linux? | §4 states both costs. The choice is scope, and D5 makes it a reversal of a recorded decision, not a technical lookup. |
| D2 | If Fork B: route B3a (dlopen the host's libSwell.so, requires defining SWELLAppMain) or B3b (vendor and build full SWELL, adds GDK/GTK3 + FreeType + Fontconfig + OpenGL)? |
B3b is a dependency-surface expansion; the dispatch requires that be flagged rather than chosen. B3a is cheaper but couples the plugin's UI to being hosted by REAPER specifically. |
| D3 | Is macOS in or out of the same phase? | Λ-01, Λ-03, Λ-04, Λ-07 and Λ-09 are all marked shared macOS/Linux at their own sections above (Λ-01: identical broken comment block, src/app/CMakeLists.txt:83–86 vs :94–97, and shared swell_resgen.php output per README.md:120; Λ-03/04/07/09: same mechanism under the APPLE branch, detailed at each). Doing both at once is cheaper than doing them serially — but that is a scope call, and macOS additionally implies signing/notarization (not mentioned anywhere in versioning-and-release.md) plus its own open question this doc does not resolve: whether swell-modstub.mm (src/app/CMakeLists.txt:79) even compiles under a CXX-only project() (CMakeLists.txt:26, no OBJCXX) — see §1a. |
| D4 | Does CI get built in this phase, and on what runner? | versioning-and-release.md:307–311 and :361–363 hand "the pipeline" to dev-ops and commit to three platform artifacts per channel, but no CI exists in the repo on any platform. Adding it is infrastructure and needs an explicit ask. |
| D6 | Is the Linux artifact shipped or developer-only for 1.x? | Determines whether Λ-02 (Release build) and Λ-08 (install path) are must-fix or nice-to-have, and whether a Linux install() rule is worth authoring. (Numbered D6, not D5, to avoid colliding with the settled product decision D5 — "Windows-only, VST3-only, REAPER-only" — cited above at §1b and B5.) |
| D7 | Λ-01's fork: resource-id-0 escape hatch, or the resgen route? §6 Stage 2 gates on T2's read of panel_window.cpp, and V7 presupposes the route is already picked. |
It is a source-code + build-pipeline tradeoff (a panel_window.cpp DLGPROC→WNDPROC change vs. a PHP build step + generated-file handling) with no code-only right answer — needs Daniel's call once T2 reports, same as D1/D2. |
Deliberately not listed as [Daniel], because reading more would settle them: whether
arrange_drop_win.cpp/instrument_drop_win.cpp need platform gating (read the files — T2);
whether the SDK's moduleinfo.json is required (it is not — module_linux.cpp:362–364);
whether the Linux run-loop IIDs need a new SDK TU (they do not — commoniids.cpp:50–54).
6. Ordering sketch
Dependency order only — this is sequencing input for a plan author, not the plan.
Stage 0 — the one action that reprices everything. Run V1 and V2 on a Linux box against the tree as-is. Configure will succeed (nothing in the CMake requires Windows); the compile will produce a diagnostic set that is the actual size of this phase. Every band below is provisional until this is done. Λ-10's doc fix rides along free, since the person doing this will hit it first.
Stage 1 — toolchain floor, no source dependency. Λ-02 (CMAKE_BUILD_TYPE default),
Λ-03 (visibility presets), Λ-04 (Threads::Threads), Λ-09 (warning flags + CXX_EXTENSIONS OFF), Λ-05 (pin the WDL_INC coincidence), Λ-07 (compiler floor). All are root- or
target-property edits with no ordering constraint among them and none touching source. Doing
them before Stage 2 means the resource work is done under warnings rather than retrofitted
into them. Ship as one change.
Stage 2 — the panel resource decision. Λ-01. This forks on the resource-id-0 question
(§2, Λ-01) and therefore needs T2's read on panel_window.cpp first — so it is gated on T2
output, not on Stage 1. Whichever route wins, it is a shared macOS/Linux fix (see D3).
Stage 3 — source partition. Λ-06. Once T2 names which TUs need a platform branch or a
non-Windows sibling, the source list in src/app/CMakeLists.txt:8–51 gains its
partition. Strictly after T2 and after Stage 2 (the resgen/shim TU, if that route is taken,
is one of the entries).
Stage 4 — packaging. Λ-08, plus whatever D6 decides. Depends on Stage 1 (there is no point documenting an install path for an unoptimized artifact) and on V4.
Stage 5 — Fork B, only if D1 says so. Internally ordered: B1 (entry point swap, S) → B3 (SWELL strategy per D2 — this gates everything downstream, because a module that cannot draw cannot be tested) → B2 (bundle layout + install, needs V10) → B4 (X11 embed + run loop, mostly T2's source work). B5 (the D5 reversal) precedes all of them as a documentation act.
7. Referred to T2
Source-level items I noticed while reading build files. Recorded, not analyzed — the parallel track owns each of these and I have deliberately not investigated further.
src/shell/panel/panel_window.cpp:135—CreateDialogParam+MAKEINTRESOURCE(IDD_BANK_PANEL). Under SWELL this isSWELL_CreateDialog(SWELL_curmodule_dialogresource_head, …). Relevant to Λ-01's resource-id-0 option, which requires the proc to be aWNDPROCreturningLRESULT, cast toDLGPROC(swell-functions.h:606–608).src/shell/actions/arrange_drop_win.cppandsrc/shell/actions/instrument_drop_win.cpp— zero_WIN32occurrences, no<windows.h>, yet_win-suffixed. Confirm they are genuinely portable, or add guards.src/shell/actions/drag_out_win.cpp:249–282— the#else"macOS / Linux (SWELL)" branch exists but has never been compiled. Whether the SWELL drag path is functionally equivalent to the Win32IDataObject/IDropSourcepath is unaudited.src/shell/persist/prune_fs.cpp:36—<windows.h>behind two_WIN32guards. Confirm the non-Windows deletion path exists and is equivalent (this file is on the only file-deletion path in the product, persrc/core/reclaim/CLAUDE.md).src/shell/panel/draw_kit.cpp:11–15,draw_kit.h:27–29,src/shell/panel/panel_state.h:18–26— thewindows.h-vs-swell/swell.hinclude switches. These look correct; whether the SWELL subset actually covers every GDI call the kit makes is the question.src/shell/panel/CLAUDE.mdrecords "Windows-only (D5) — font/GDI/HFONT choices assume Windows; no cross-platform font-fallback concern" as an invariant. Font selection on Linux SWELL (FreeType/Fontconfig-backed) is the concrete instance.src/core/capture/capture_paths.cpp—normalizeSlasheslowercases on_WIN32only, andtests/test_capture_paths.cpp:19–46already encodes the case-sensitive POSIX contract. The test is written; the behaviour under a case-sensitive filesystem is unverified.