Skip to content

Commit 95bede6

Browse files
drowaudioclaude
andauthored
Integrate VST3 validator directly into pluginval binary (#162)
* Integrate VST3 validator directly into pluginval binary - Add VST3 SDK as a dependency via CPM (fetched during cmake configure) - Create VST3ValidatorRunner wrapper to call SDK validation APIs - Add --vst3-validator-mode CLI option for running embedded validator - Modify VST3validator test to use embedded validator instead of external binary - Remove --vst3validator CLI option and related UI code (no longer needed) - Update CLAUDE.md documentation with new build options and architecture The embedded validator eliminates the need for users to supply a separate vstvalidator binary path. When built with PLUGINVAL_VST3_VALIDATOR=ON (default), the validator runs automatically for VST3 plugins at strictness level 5+. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Fix VST3 validator build and runtime issues - Add platform-specific module loading sources from SDK (module_linux.cpp, etc.) - Fix VST3ValidatorRunner to use correct SDK APIs: - Use template createInstance<T>() method - Use FUnknownPtr for interface casting - Remove incompatible ITestResult implementation - Add sdk library to link dependencies - Fix argument parsing to not auto-insert --validate when using --vst3-validator-mode https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Add ARC compile flag for macOS module_mac.mm The VST3 SDK's module_mac.mm requires Automatic Reference Counting (ARC) to be enabled. Add -fobjc-arc compile flag for this specific source file. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Add platform-specific library linking for VST3 validator - Add Ole32 and Shell32 libraries for Windows module loading - Add Cocoa framework for macOS module loading - Add hosting directory to include paths https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Add Windows-specific compile definitions for module_win32.cpp Add NOMINMAX and WIN32_LEAN_AND_MEAN definitions to avoid conflicts with JUCE's Windows header handling. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Add exception handling and better error output to VST3 validator - Add try-catch around validation logic to handle exceptions - Add stderr output for errors to improve visibility in CI logs - This helps diagnose validation failures on macOS and Windows https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Use COMPILE_FLAGS for Windows VST3 module definitions - Use MSVC-specific /D flags for compile definitions - Add _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING - This ensures definitions are applied correctly for MSVC builds https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Use wrapper file for Windows VST3 module compilation Instead of adding compile flags to the SDK's module_win32.cpp directly, use a wrapper source file that sets up the necessary preprocessor definitions (NOMINMAX, WIN32_LEAN_AND_MEAN, _UNICODE, etc.) before including the SDK source. This ensures proper compilation environment when building outside the VST3 SDK's CMake context. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Use target-level compile definitions for Windows VST3 module Replace wrapper file approach with target-level compile definitions. This adds the necessary Windows macros (NOMINMAX, WIN32_LEAN_AND_MEAN, _UNICODE, _CRT_SECURE_NO_WARNINGS, etc.) to the pluginval target when building on Windows with VST3 validator enabled. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Add SMTG_USE_STATIC_CRT and additional include directories - Set SMTG_USE_STATIC_CRT=ON to match pluginval's static CRT linking - Add vst and utility include directories for VST3 SDK module compilation https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Create OBJECT library for Windows VST3 module compilation Isolate the Windows module_win32.cpp compilation into a separate OBJECT library with its own compile settings, avoiding potential conflicts with JUCE's compilation environment. The object library: - Uses C++17 (matching SDK's validator) - Has SDK-specific include directories - Sets Windows compatibility definitions - Uses static CRT to match pluginval https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Simplify OBJECT library include directories for Windows VST3 module Use PUBLIC link to sdk_hosting to inherit its include directories automatically, and only add the minimal additional includes needed. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Simplify Windows VST3 module - use direct file inclusion Remove the OBJECT library approach and add module_win32.cpp directly to the pluginval sources like on other platforms. The Windows-specific compile definitions (NOMINMAX, WIN32_LEAN_AND_MEAN, etc.) are already set at the target level. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Fix Windows build: compile module_win32.cpp with C++17 The VST3 SDK's module_win32.cpp uses std::filesystem::path::generic_u8string() which returns std::u8string in C++20 but std::string in C++17. Since the SDK code expects std::string, we compile this specific file with /std:c++17. Also move _UNICODE and UNICODE definitions from target-level to file-level (only for module_win32.cpp) to avoid breaking JUCE's LV2/lilv code which uses ANSI Windows APIs. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Bypass JUCE startup for --vst3-validator-mode subprocess When running with --vst3-validator-mode, intercept the flag in main() before JUCE initializes. This avoids the macOS "Periodic events are already being generated" crash that occurred when the VST3 validator subprocess tried to use JUCE's event loop. The VST3 validator now runs as a pure C++ process without JUCE, which is appropriate since it only uses the VST3 SDK APIs. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Document CI run logs and setup instructions Added CI run logs section with configuration and workflow details. * Fix JUCE app initialization in custom main() Add forward declaration for juce_CreateApplication and set JUCEApplicationBase::createInstance before calling main(). This is required for JUCE to properly create the application instance. Previous commit caused segfaults because createInstance wasn't set. https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * Trigger CI re-run https://claude.ai/code/session_01AY9chvBEmsCVjNZSUkNcbw * VST3: Build validator from sdk and extract to a temp file at run time * CI: Added -DCMAKE_BUILD_TYPE to as-depenedency job * Updated gitignore --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent a5d5b08 commit 95bede6

File tree

12 files changed

+232
-59
lines changed

12 files changed

+232
-59
lines changed

.github/workflows/build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ jobs:
187187
sudo apt-get install -y libasound2-dev libfreetype6-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev libwebkit2gtk-4.1-dev ladspa-sdk
188188
189189
- name: Configure
190-
run: cmake -B build -S tests/pluginval_as_dependency -DJUCE_VERSION="${{ matrix.juce }}"
190+
run: cmake -B build -S tests/pluginval_as_dependency -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE}} -DJUCE_VERSION="${{ matrix.juce }}"
191191

192192
- name: Build
193193
run: cmake --build build --target pluginval --parallel 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@ pluginval.build/
7979
pluginval.xcodeproj/
8080
pluginval_artefacts/
8181

82+
.claude/*local*

CLAUDE.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,44 @@ Follow these guidelines when working on this codebase:
3333

3434
6. **Never speculate about unread code**: Never make claims about code you haven't opened. If the user references a specific file, you MUST read the file before answering. Investigate and read relevant files BEFORE answering questions about the codebase. Give grounded, hallucination-free answers based on actual file contents.
3535

36+
## Getting CI Run Logs
37+
38+
### Configuration
39+
40+
- **Organisation:** `<organisation>`
41+
- **Repository:** `<repo>`
42+
43+
For this project:
44+
- **Organisation:** `Tracktion`
45+
- **Repository:** `pluginval`
46+
47+
### Setup
48+
49+
Install the GitHub CLI:
50+
```bash
51+
brew install gh # macOS
52+
# or
53+
sudo apt install gh # Ubuntu/Debian
54+
```
55+
56+
Authentication is handled via the `GH_TOKEN` environment variable (already configured).
57+
58+
### Workflow
59+
60+
1. **List recent workflow runs:**
61+
```bash
62+
gh run list -R <organisation>/<repo>
63+
```
64+
65+
2. **Find the most recent run for your branch** from the output above.
66+
67+
3. **View failed log details:**
68+
```bash
69+
gh run view -R <organisation>/<repo> <run_id> --log-failed
70+
```
71+
72+
Replace `<run_id>` with the ID from step 2.
73+
3674
## Directory Structure
3775

3876
```
@@ -49,6 +87,9 @@ pluginval/
4987
│ ├── PluginvalLookAndFeel.h # Custom UI styling
5088
│ ├── StrictnessInfoPopup.h # Strictness level info UI
5189
│ ├── binarydata/ # Binary resources (icons)
90+
│ ├── vst3validator/ # Embedded VST3 validator integration
91+
│ │ ├── VST3ValidatorRunner.h
92+
│ │ └── VST3ValidatorRunner.cpp
5293
│ └── tests/ # Individual test implementations
5394
│ ├── BasicTests.cpp # Core plugin tests (info, state, audio)
5495
│ ├── BusTests.cpp # Audio bus configuration tests
@@ -59,7 +100,8 @@ pluginval/
59100
├── modules/
60101
│ └── juce/ # JUCE framework (git submodule)
61102
├── cmake/
62-
│ └── CPM.cmake # CMake Package Manager
103+
│ ├── CPM.cmake # CMake Package Manager
104+
│ └── GenerateBinaryHeader.cmake # Binary-to-C-header converter
63105
├── tests/
64106
│ ├── AddPluginvalTests.cmake # CMake module for CTest integration
65107
│ ├── test_plugins/ # Test plugin files
@@ -101,6 +143,7 @@ cmake --build Builds/Debug --config Debug
101143
| Option | Description | Default |
102144
|--------|-------------|---------|
103145
| `PLUGINVAL_FETCH_JUCE` | Fetch JUCE with pluginval | ON |
146+
| `PLUGINVAL_VST3_VALIDATOR` | Build with embedded VST3 validator | ON |
104147
| `WITH_ADDRESS_SANITIZER` | Enable AddressSanitizer | OFF |
105148
| `WITH_THREAD_SANITIZER` | Enable ThreadSanitizer | OFF |
106149
| `VST2_SDK_DIR` | Path to VST2 SDK (env var) | - |
@@ -177,6 +220,26 @@ struct Requirements {
177220
| `LocaleTest.cpp` | Locale handling verification |
178221
| `ExtremeTests.cpp` | Edge cases, stress tests |
179222
223+
### VST3 Validator Integration
224+
225+
The VST3 validator (Steinberg's vstvalidator) is embedded into pluginval when built with `PLUGINVAL_VST3_VALIDATOR=ON` (the default). This provides single-file distribution while keeping vstvalidator completely isolated from pluginval's link dependencies.
226+
227+
**Architecture:**
228+
1. The VST3 SDK is fetched via CPM during CMake configure
229+
2. The SDK's own `validator` target is built as a separate executable
230+
3. A CMake script (`cmake/GenerateBinaryHeader.cmake`) converts the compiled binary into a C byte array header
231+
4. `VST3ValidatorRunner` (`Source/vst3validator/`) extracts the embedded binary to a temp file on first use
232+
5. When the `VST3validator` test runs, it spawns the extracted validator as a subprocess
233+
234+
**Key files:**
235+
- `cmake/GenerateBinaryHeader.cmake` — binary-to-C-header conversion script
236+
- `Source/vst3validator/VST3ValidatorRunner.h/cpp` — extracts embedded binary, returns `juce::File`
237+
238+
**Disabling embedded validator:**
239+
```bash
240+
cmake -B Builds -DPLUGINVAL_VST3_VALIDATOR=OFF .
241+
```
242+
180243
## Adding New Tests
181244

182245
1. Create a subclass of `PluginTest`:
@@ -312,6 +375,7 @@ add_pluginval_tests(MyPluginTarget
312375
- **JUCE** (v8.0.x) - Audio application framework (git submodule)
313376
- **magic_enum** (v0.9.7) - Enum reflection (fetched via CPM)
314377
- **rtcheck** (optional, macOS) - Real-time safety checking (fetched via CPM)
378+
- **VST3 SDK** (v3.7.x) - Steinberg VST3 SDK for embedded validator (fetched via CPM, optional)
315379
316380
### System
317381
- macOS: CoreAudio, AudioUnit frameworks

CMakeLists.txt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ if (pluginval_IS_TOP_LEVEL)
6565
CPMAddPackage("gh:juce-framework/juce#8.0.3")
6666
endif()
6767

68+
69+
# VST3 Validator integration - embeds Steinberg's vstvalidator directly into pluginval
70+
option(PLUGINVAL_VST3_VALIDATOR "Build with embedded VST3 validator" ON)
71+
72+
if(PLUGINVAL_VST3_VALIDATOR)
73+
# Use static CRT on Windows to match pluginval's settings
74+
set(SMTG_USE_STATIC_CRT ON CACHE BOOL "" FORCE)
75+
76+
CPMAddPackage(
77+
NAME vst3sdk
78+
GITHUB_REPOSITORY steinbergmedia/vst3sdk
79+
GIT_TAG v3.7.14_build_55
80+
OPTIONS
81+
"SMTG_ENABLE_VST3_PLUGIN_EXAMPLES OFF"
82+
"SMTG_ENABLE_VST3_HOSTING_EXAMPLES OFF"
83+
"SMTG_ENABLE_VSTGUI_SUPPORT OFF"
84+
"SMTG_ADD_VSTGUI OFF"
85+
"SMTG_RUN_VST_VALIDATOR OFF"
86+
"SMTG_CREATE_BUNDLE_FOR_WINDOWS OFF"
87+
)
88+
endif()
89+
6890
if (DEFINED ENV{VST2_SDK_DIR})
6991
MESSAGE(STATUS "Building with VST2 SDK: $ENV{VST2_SDK_DIR}")
7092
juce_set_vst2_sdk_path($ENV{VST2_SDK_DIR})
@@ -109,6 +131,16 @@ set(SourceFiles
109131
target_sources(pluginval PRIVATE ${SourceFiles})
110132
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Source PREFIX Source FILES ${SourceFiles})
111133

134+
# Add VST3 validator runner sources to pluginval (extracts embedded binary at runtime)
135+
if(PLUGINVAL_VST3_VALIDATOR)
136+
set(VST3ValidatorFiles
137+
Source/vst3validator/VST3ValidatorRunner.h
138+
Source/vst3validator/VST3ValidatorRunner.cpp
139+
)
140+
target_sources(pluginval PRIVATE ${VST3ValidatorFiles})
141+
source_group("Source/vst3validator" FILES ${VST3ValidatorFiles})
142+
endif()
143+
112144
if (DEFINED ENV{VST2_SDK_DIR})
113145
target_compile_definitions(pluginval PRIVATE
114146
JUCE_PLUGINHOST_VST=1)
@@ -124,8 +156,10 @@ target_compile_definitions(pluginval PRIVATE
124156
JUCE_MODAL_LOOPS_PERMITTED=1
125157
JUCE_GUI_BASICS_INCLUDE_XHEADERS=1
126158
$<$<BOOL:${PLUGINVAL_ENABLE_RTCHECK}>:PLUGINVAL_ENABLE_RTCHECK=1>
159+
$<$<BOOL:${PLUGINVAL_VST3_VALIDATOR}>:PLUGINVAL_VST3_VALIDATOR=1>
127160
VERSION="${CURRENT_VERSION}")
128161

162+
129163
if(MSVC AND NOT CMAKE_MSVC_RUNTIME_LIBRARY)
130164
# Default to statically linking the runtime libraries
131165
set_property(TARGET pluginval PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
@@ -143,6 +177,22 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
143177
-static-libstdc++)
144178
endif()
145179

180+
# Embed the SDK's validator binary into pluginval
181+
if(PLUGINVAL_VST3_VALIDATOR)
182+
set(VSTVALIDATOR_HEADER "${CMAKE_BINARY_DIR}/generated/vstvalidator_data.h")
183+
add_custom_command(
184+
OUTPUT "${VSTVALIDATOR_HEADER}"
185+
COMMAND ${CMAKE_COMMAND}
186+
-DINPUT_FILE=$<TARGET_FILE:validator>
187+
-DOUTPUT_FILE=${VSTVALIDATOR_HEADER}
188+
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateBinaryHeader.cmake
189+
DEPENDS validator
190+
COMMENT "Embedding vstvalidator binary into header...")
191+
192+
target_sources(pluginval PRIVATE "${VSTVALIDATOR_HEADER}")
193+
target_include_directories(pluginval PRIVATE "${CMAKE_BINARY_DIR}/generated")
194+
endif()
195+
146196
if (PLUGINVAL_ENABLE_RTCHECK)
147197
target_link_libraries(pluginval PRIVATE
148198
rtcheck)

Source/CommandLine.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,6 @@ static Option possibleOptions[] =
290290
{ "--randomise", false },
291291
{ "--sample-rates", true },
292292
{ "--block-sizes", true },
293-
{ "--vst3validator", true },
294293
{ "--rtcheck", false },
295294
};
296295

@@ -384,8 +383,6 @@ static juce::String getHelpMessage()
384383
--disabled-tests [pathToFile]
385384
If specified, sets a path to a file that should have the names of disabled
386385
tests on each row.
387-
--vst3validator [pathToValidator]
388-
If specified, this will run the VST3 validator as part of the test process.
389386
390387
--output-dir [pathToDir]
391388
If specified, sets a directory to store the log files. This can be useful
@@ -599,7 +596,6 @@ std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::Argu
599596
options.disabledTests = getDisabledTests (args);
600597
options.sampleRates = getSampleRates (args);
601598
options.blockSizes = getBlockSizes (args);
602-
options.vst3Validator = getOptionValue (args, "--vst3validator", "", "Expected a path for the --vst3validator option");
603599
options.realtimeCheck = magic_enum::enum_cast<RealtimeCheck> (getOptionValue (args, "--rtcheck", "", "Expected one of [disabled, enabled, relaxed]").toString().toStdString())
604600
.value_or (RealtimeCheck::disabled);
605601

@@ -669,9 +665,6 @@ juce::StringArray createCommandLine (juce::String fileOrID, PluginTests::Options
669665
args.addArray ({ "--block-sizes", blockSizes.joinIntoString (",") });
670666
}
671667

672-
if (options.vst3Validator != juce::File())
673-
args.addArray ({ "--vst3validator", options.vst3Validator.getFullPathName().quoted() });
674-
675668
if (auto rtCheckMode = options.realtimeCheck;
676669
rtCheckMode != RealtimeCheck::disabled)
677670
{

Source/Main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "CommandLine.h"
1919
#include "PluginvalLookAndFeel.h"
2020

21+
2122
//==============================================================================
2223
class PluginValidatorApplication : public juce::JUCEApplication,
2324
private juce::AsyncUpdater
@@ -174,7 +175,6 @@ class PluginValidatorApplication : public juce::JUCEApplication,
174175
};
175176

176177
//==============================================================================
177-
// This macro generates the main() routine that launches the app.
178178
START_JUCE_APPLICATION (PluginValidatorApplication)
179179

180180
juce::PropertiesFile& getAppPreferences()

Source/MainComponent.cpp

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,6 @@ namespace
108108
return { 64, 128, 256, 512, 1024 };
109109
}
110110

111-
void setVST3Validator (juce::File f)
112-
{
113-
getAppPreferences().setValue ("vst3validator", f.getFullPathName());
114-
}
115-
116-
juce::File getVST3Validator()
117-
{
118-
return getAppPreferences().getValue ("vst3validator", juce::String());
119-
}
120-
121111
void setRealtimeCheckMode (RealtimeCheck rt)
122112
{
123113
getAppPreferences().setValue ("realtimeCheckMode", juce::String (std::string (magic_enum::enum_name (rt))));
@@ -210,7 +200,6 @@ namespace
210200
options.outputDir = getOutputDir();
211201
options.sampleRates = getSampleRates();
212202
options.blockSizes = getBlockSizes();
213-
options.vst3Validator = getVST3Validator();
214203
options.realtimeCheck = getRealtimeCheckMode();
215204

216205
return options;
@@ -296,34 +285,6 @@ namespace
296285
}
297286
}));
298287
}
299-
300-
void showVST3ValidatorDialog()
301-
{
302-
juce::String message = TRANS("Set the location of the VST3 validator app");
303-
auto app = getVST3Validator();
304-
305-
if (app.getFullPathName().isNotEmpty())
306-
message << "\n\n" << app.getFullPathName().quoted();
307-
else
308-
message << "\n\n" << "\"None set\"";
309-
310-
std::shared_ptr<juce::AlertWindow> aw (juce::LookAndFeel::getDefaultLookAndFeel().createAlertWindow (TRANS("Set VST3 validator"), message,
311-
TRANS("Choose"), TRANS("Don't use VST3 validator"), TRANS("Cancel"),
312-
juce::AlertWindow::QuestionIcon, 3, nullptr));
313-
aw->enterModalState (true, juce::ModalCallbackFunction::create ([aw] (int res)
314-
{
315-
if (res == 1)
316-
setVST3Validator ({});
317-
318-
if (res == 1)
319-
{
320-
juce::FileChooser fc (TRANS("Choose VST3 validator"), {});
321-
322-
if (fc.browseForFileToOpen())
323-
setVST3Validator (fc.getResult().getFullPathName());
324-
}
325-
}));
326-
}
327288
}
328289

329290
//==============================================================================
@@ -633,9 +594,6 @@ juce::PopupMenu MainComponent::createOptionsMenu()
633594
m.addItem (TRANS("Randomise tests"), true, getRandomiseTests(),
634595
[] { setRandomiseTests (! getRandomiseTests()); });
635596

636-
m.addItem (TRANS("Set VST3 validator location..."),
637-
[] { showVST3ValidatorDialog(); });
638-
639597
m.addSeparator();
640598

641599
m.addItem (TRANS("Show settings folder"),

Source/PluginTests.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ struct PluginTests : public juce::UnitTest
5050
juce::StringArray disabledTests; /**< List of disabled tests. */
5151
std::vector<double> sampleRates; /**< List of sample rates. */
5252
std::vector<int> blockSizes; /**< List of block sizes. */
53-
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
5453
RealtimeCheck realtimeCheck = RealtimeCheck::disabled; /**< The type of real-time safety checking to perform. */
5554
};
5655

0 commit comments

Comments
 (0)