Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0d509f7
Integrate VST3 validator directly into pluginval binary
claude Jan 31, 2026
e2c5ad7
Fix VST3 validator build and runtime issues
claude Jan 31, 2026
cb59faa
Merge branch 'develop' into claude/plan-vstvalidator-integration-4wOI7
drowaudio Jan 31, 2026
956dc9f
Add ARC compile flag for macOS module_mac.mm
claude Jan 31, 2026
eb7b3d4
Add platform-specific library linking for VST3 validator
claude Feb 1, 2026
22cfb9e
Add Windows-specific compile definitions for module_win32.cpp
claude Feb 1, 2026
c786777
Add exception handling and better error output to VST3 validator
claude Feb 1, 2026
1218b64
Use COMPILE_FLAGS for Windows VST3 module definitions
claude Feb 1, 2026
92593df
Use wrapper file for Windows VST3 module compilation
claude Feb 1, 2026
214500d
Use target-level compile definitions for Windows VST3 module
claude Feb 1, 2026
2ee3182
Add SMTG_USE_STATIC_CRT and additional include directories
claude Feb 1, 2026
6c1095d
Create OBJECT library for Windows VST3 module compilation
claude Feb 1, 2026
278ea09
Simplify OBJECT library include directories for Windows VST3 module
claude Feb 1, 2026
ab21528
Simplify Windows VST3 module - use direct file inclusion
claude Feb 1, 2026
9795fbd
Fix Windows build: compile module_win32.cpp with C++17
claude Feb 2, 2026
945e977
Bypass JUCE startup for --vst3-validator-mode subprocess
claude Feb 2, 2026
3ebb512
Document CI run logs and setup instructions
drowaudio Feb 2, 2026
2c20362
Fix JUCE app initialization in custom main()
claude Feb 2, 2026
d40a868
Trigger CI re-run
claude Feb 2, 2026
c67cafa
Merge branch 'develop' into claude/plan-vstvalidator-integration-4wOI7
drowaudio Feb 8, 2026
622c1d5
VST3: Build validator from sdk and extract to a temp file at run time
drowaudio Feb 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,44 @@ Follow these guidelines when working on this codebase:

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.

## Getting CI Run Logs

### Configuration

- **Organisation:** `<organisation>`
- **Repository:** `<repo>`

For this project:
- **Organisation:** `Tracktion`
- **Repository:** `pluginval`

### Setup

Install the GitHub CLI:
```bash
brew install gh # macOS
# or
sudo apt install gh # Ubuntu/Debian
```

Authentication is handled via the `GH_TOKEN` environment variable (already configured).

### Workflow

1. **List recent workflow runs:**
```bash
gh run list -R <organisation>/<repo>
```

2. **Find the most recent run for your branch** from the output above.

3. **View failed log details:**
```bash
gh run view -R <organisation>/<repo> <run_id> --log-failed
```

Replace `<run_id>` with the ID from step 2.

## Directory Structure

```
Expand All @@ -49,6 +87,9 @@ pluginval/
│ ├── PluginvalLookAndFeel.h # Custom UI styling
│ ├── StrictnessInfoPopup.h # Strictness level info UI
│ ├── binarydata/ # Binary resources (icons)
│ ├── vst3validator/ # Embedded VST3 validator integration
│ │ ├── VST3ValidatorRunner.h
│ │ └── VST3ValidatorRunner.cpp
│ └── tests/ # Individual test implementations
│ ├── BasicTests.cpp # Core plugin tests (info, state, audio)
│ ├── BusTests.cpp # Audio bus configuration tests
Expand All @@ -59,7 +100,8 @@ pluginval/
├── modules/
│ └── juce/ # JUCE framework (git submodule)
├── cmake/
│ └── CPM.cmake # CMake Package Manager
│ ├── CPM.cmake # CMake Package Manager
│ └── GenerateBinaryHeader.cmake # Binary-to-C-header converter
├── tests/
│ ├── AddPluginvalTests.cmake # CMake module for CTest integration
│ ├── test_plugins/ # Test plugin files
Expand Down Expand Up @@ -101,6 +143,7 @@ cmake --build Builds/Debug --config Debug
| Option | Description | Default |
|--------|-------------|---------|
| `PLUGINVAL_FETCH_JUCE` | Fetch JUCE with pluginval | ON |
| `PLUGINVAL_VST3_VALIDATOR` | Build with embedded VST3 validator | ON |
| `WITH_ADDRESS_SANITIZER` | Enable AddressSanitizer | OFF |
| `WITH_THREAD_SANITIZER` | Enable ThreadSanitizer | OFF |
| `VST2_SDK_DIR` | Path to VST2 SDK (env var) | - |
Expand Down Expand Up @@ -177,6 +220,26 @@ struct Requirements {
| `LocaleTest.cpp` | Locale handling verification |
| `ExtremeTests.cpp` | Edge cases, stress tests |

### VST3 Validator Integration

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.

**Architecture:**
1. The VST3 SDK is fetched via CPM during CMake configure
2. The SDK's own `validator` target is built as a separate executable
3. A CMake script (`cmake/GenerateBinaryHeader.cmake`) converts the compiled binary into a C byte array header
4. `VST3ValidatorRunner` (`Source/vst3validator/`) extracts the embedded binary to a temp file on first use
5. When the `VST3validator` test runs, it spawns the extracted validator as a subprocess

**Key files:**
- `cmake/GenerateBinaryHeader.cmake` — binary-to-C-header conversion script
- `Source/vst3validator/VST3ValidatorRunner.h/cpp` — extracts embedded binary, returns `juce::File`

**Disabling embedded validator:**
```bash
cmake -B Builds -DPLUGINVAL_VST3_VALIDATOR=OFF .
```

## Adding New Tests

1. Create a subclass of `PluginTest`:
Expand Down Expand Up @@ -312,6 +375,7 @@ add_pluginval_tests(MyPluginTarget
- **JUCE** (v8.0.x) - Audio application framework (git submodule)
- **magic_enum** (v0.9.7) - Enum reflection (fetched via CPM)
- **rtcheck** (optional, macOS) - Real-time safety checking (fetched via CPM)
- **VST3 SDK** (v3.7.x) - Steinberg VST3 SDK for embedded validator (fetched via CPM, optional)

### System
- macOS: CoreAudio, AudioUnit frameworks
Expand Down
50 changes: 50 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ if (pluginval_IS_TOP_LEVEL)
CPMAddPackage("gh:juce-framework/juce#8.0.3")
endif()


# VST3 Validator integration - embeds Steinberg's vstvalidator directly into pluginval
option(PLUGINVAL_VST3_VALIDATOR "Build with embedded VST3 validator" ON)

if(PLUGINVAL_VST3_VALIDATOR)
# Use static CRT on Windows to match pluginval's settings
set(SMTG_USE_STATIC_CRT ON CACHE BOOL "" FORCE)

CPMAddPackage(
NAME vst3sdk
GITHUB_REPOSITORY steinbergmedia/vst3sdk
GIT_TAG v3.7.14_build_55
OPTIONS
"SMTG_ENABLE_VST3_PLUGIN_EXAMPLES OFF"
"SMTG_ENABLE_VST3_HOSTING_EXAMPLES OFF"
"SMTG_ENABLE_VSTGUI_SUPPORT OFF"
"SMTG_ADD_VSTGUI OFF"
"SMTG_RUN_VST_VALIDATOR OFF"
"SMTG_CREATE_BUNDLE_FOR_WINDOWS OFF"
)
endif()

if (DEFINED ENV{VST2_SDK_DIR})
MESSAGE(STATUS "Building with VST2 SDK: $ENV{VST2_SDK_DIR}")
juce_set_vst2_sdk_path($ENV{VST2_SDK_DIR})
Expand Down Expand Up @@ -109,6 +131,16 @@ set(SourceFiles
target_sources(pluginval PRIVATE ${SourceFiles})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Source PREFIX Source FILES ${SourceFiles})

# Add VST3 validator runner sources to pluginval (extracts embedded binary at runtime)
if(PLUGINVAL_VST3_VALIDATOR)
set(VST3ValidatorFiles
Source/vst3validator/VST3ValidatorRunner.h
Source/vst3validator/VST3ValidatorRunner.cpp
)
target_sources(pluginval PRIVATE ${VST3ValidatorFiles})
source_group("Source/vst3validator" FILES ${VST3ValidatorFiles})
endif()

if (DEFINED ENV{VST2_SDK_DIR})
target_compile_definitions(pluginval PRIVATE
JUCE_PLUGINHOST_VST=1)
Expand All @@ -124,8 +156,10 @@ target_compile_definitions(pluginval PRIVATE
JUCE_MODAL_LOOPS_PERMITTED=1
JUCE_GUI_BASICS_INCLUDE_XHEADERS=1
$<$<BOOL:${PLUGINVAL_ENABLE_RTCHECK}>:PLUGINVAL_ENABLE_RTCHECK=1>
$<$<BOOL:${PLUGINVAL_VST3_VALIDATOR}>:PLUGINVAL_VST3_VALIDATOR=1>
VERSION="${CURRENT_VERSION}")


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

# Embed the SDK's validator binary into pluginval
if(PLUGINVAL_VST3_VALIDATOR)
set(VSTVALIDATOR_HEADER "${CMAKE_BINARY_DIR}/generated/vstvalidator_data.h")
add_custom_command(
OUTPUT "${VSTVALIDATOR_HEADER}"
COMMAND ${CMAKE_COMMAND}
-DINPUT_FILE=$<TARGET_FILE:validator>
-DOUTPUT_FILE=${VSTVALIDATOR_HEADER}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/GenerateBinaryHeader.cmake
DEPENDS validator
COMMENT "Embedding vstvalidator binary into header...")

target_sources(pluginval PRIVATE "${VSTVALIDATOR_HEADER}")
target_include_directories(pluginval PRIVATE "${CMAKE_BINARY_DIR}/generated")
endif()

if (PLUGINVAL_ENABLE_RTCHECK)
target_link_libraries(pluginval PRIVATE
rtcheck)
Expand Down
7 changes: 0 additions & 7 deletions Source/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,6 @@ static Option possibleOptions[] =
{ "--randomise", false },
{ "--sample-rates", true },
{ "--block-sizes", true },
{ "--vst3validator", true },
{ "--rtcheck", false },
};

Expand Down Expand Up @@ -384,8 +383,6 @@ static juce::String getHelpMessage()
--disabled-tests [pathToFile]
If specified, sets a path to a file that should have the names of disabled
tests on each row.
--vst3validator [pathToValidator]
If specified, this will run the VST3 validator as part of the test process.

--output-dir [pathToDir]
If specified, sets a directory to store the log files. This can be useful
Expand Down Expand Up @@ -599,7 +596,6 @@ std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::Argu
options.disabledTests = getDisabledTests (args);
options.sampleRates = getSampleRates (args);
options.blockSizes = getBlockSizes (args);
options.vst3Validator = getOptionValue (args, "--vst3validator", "", "Expected a path for the --vst3validator option");
options.realtimeCheck = magic_enum::enum_cast<RealtimeCheck> (getOptionValue (args, "--rtcheck", "", "Expected one of [disabled, enabled, relaxed]").toString().toStdString())
.value_or (RealtimeCheck::disabled);

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

if (options.vst3Validator != juce::File())
args.addArray ({ "--vst3validator", options.vst3Validator.getFullPathName().quoted() });

if (auto rtCheckMode = options.realtimeCheck;
rtCheckMode != RealtimeCheck::disabled)
{
Expand Down
2 changes: 1 addition & 1 deletion Source/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "CommandLine.h"
#include "PluginvalLookAndFeel.h"


//==============================================================================
class PluginValidatorApplication : public juce::JUCEApplication,
private juce::AsyncUpdater
Expand Down Expand Up @@ -174,7 +175,6 @@ class PluginValidatorApplication : public juce::JUCEApplication,
};

//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (PluginValidatorApplication)

juce::PropertiesFile& getAppPreferences()
Expand Down
42 changes: 0 additions & 42 deletions Source/MainComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,6 @@ namespace
return { 64, 128, 256, 512, 1024 };
}

void setVST3Validator (juce::File f)
{
getAppPreferences().setValue ("vst3validator", f.getFullPathName());
}

juce::File getVST3Validator()
{
return getAppPreferences().getValue ("vst3validator", juce::String());
}

void setRealtimeCheckMode (RealtimeCheck rt)
{
getAppPreferences().setValue ("realtimeCheckMode", juce::String (std::string (magic_enum::enum_name (rt))));
Expand Down Expand Up @@ -210,7 +200,6 @@ namespace
options.outputDir = getOutputDir();
options.sampleRates = getSampleRates();
options.blockSizes = getBlockSizes();
options.vst3Validator = getVST3Validator();
options.realtimeCheck = getRealtimeCheckMode();

return options;
Expand Down Expand Up @@ -296,34 +285,6 @@ namespace
}
}));
}

void showVST3ValidatorDialog()
{
juce::String message = TRANS("Set the location of the VST3 validator app");
auto app = getVST3Validator();

if (app.getFullPathName().isNotEmpty())
message << "\n\n" << app.getFullPathName().quoted();
else
message << "\n\n" << "\"None set\"";

std::shared_ptr<juce::AlertWindow> aw (juce::LookAndFeel::getDefaultLookAndFeel().createAlertWindow (TRANS("Set VST3 validator"), message,
TRANS("Choose"), TRANS("Don't use VST3 validator"), TRANS("Cancel"),
juce::AlertWindow::QuestionIcon, 3, nullptr));
aw->enterModalState (true, juce::ModalCallbackFunction::create ([aw] (int res)
{
if (res == 1)
setVST3Validator ({});

if (res == 1)
{
juce::FileChooser fc (TRANS("Choose VST3 validator"), {});

if (fc.browseForFileToOpen())
setVST3Validator (fc.getResult().getFullPathName());
}
}));
}
}

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

m.addItem (TRANS("Set VST3 validator location..."),
[] { showVST3ValidatorDialog(); });

m.addSeparator();

m.addItem (TRANS("Show settings folder"),
Expand Down
1 change: 0 additions & 1 deletion Source/PluginTests.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ struct PluginTests : public juce::UnitTest
juce::StringArray disabledTests; /**< List of disabled tests. */
std::vector<double> sampleRates; /**< List of sample rates. */
std::vector<int> blockSizes; /**< List of block sizes. */
juce::File vst3Validator; /**< juce::File to use as the VST3 validator app. */
RealtimeCheck realtimeCheck = RealtimeCheck::disabled; /**< The type of real-time safety checking to perform. */
};

Expand Down
31 changes: 25 additions & 6 deletions Source/tests/BasicTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include "../TestUtilities.h"
#include "../RTCheck.h"

#if PLUGINVAL_VST3_VALIDATOR
#include "../vst3validator/VST3ValidatorRunner.h"
#endif

#include <future>
#include <thread>
#include <chrono>
Expand Down Expand Up @@ -875,6 +879,7 @@ static AUvalTest auvalTest;

//==============================================================================
/** Runs Steinberg's validator on the plugin if it's a VST3.
Uses the embedded VST3 validator when built with PLUGINVAL_VST3_VALIDATOR.
*/
struct VST3validator : public PluginTest
{
Expand All @@ -890,24 +895,33 @@ struct VST3validator : public PluginTest
if (desc.pluginFormatName != "VST3")
return;

auto vst3Validator = ut.getOptions().vst3Validator;
#if ! PLUGINVAL_VST3_VALIDATOR
ut.logMessage ("INFO: Skipping vst3 validator (not built with VST3 validator support)");
return;
#else
auto tempFile = vst3validator::getValidatorExecutable();
auto vstvalidatorExe = tempFile->getFile();

if (vst3Validator == juce::File())
if (! vstvalidatorExe.existsAsFile())
{
ut.logMessage ("INFO: Skipping vst3 validator as validator path hasn't been set");
ut.logMessage ("WARNING: Could not extract vstvalidator binary");
return;
}

juce::StringArray cmd (vst3Validator.getFullPathName());
juce::StringArray cmd;
cmd.add (vstvalidatorExe.getFullPathName());

if (ut.getOptions().strictnessLevel > 5)
cmd.add ("-e");

if (! ut.getOptions().verbose)
cmd.add ("-q");

cmd.add (ut.getFileOrID());

juce::ChildProcess cp;
const auto started = cp.start (cmd);
ut.expect (started, "VST3 validator app has been set but is unable to start");
ut.expect (started, "Failed to start VST3 validator mode");

if (! started)
return;
Expand Down Expand Up @@ -947,15 +961,20 @@ struct VST3validator : public PluginTest

if (! exitedCleanly && ! ut.getOptions().verbose)
ut.logMessage (outputBuffer.toString());
#endif
}

std::vector<TestDescription> getDescription (int level) const override
{
#if ! PLUGINVAL_VST3_VALIDATOR
return { { name, "Disabled (not built with VST3 validator support)" } };
#else
if (level > 5)
return { { name, "Runs Steinberg's vstvalidator with -e flag for extended validation (VST3 only)" } };

return { { name, "Runs Steinberg's vstvalidator on the plugin file (VST3 only)" } };
#endif
}
};

static VST3validator vst3validator;
static VST3validator vst3validatorTest;
Loading
Loading