Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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 Down Expand Up @@ -101,6 +104,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 +181,27 @@ struct Requirements {
| `LocaleTest.cpp` | Locale handling verification |
| `ExtremeTests.cpp` | Edge cases, stress tests |

### VST3 Validator Integration

The VST3 validator (Steinberg's vstvalidator) is embedded directly into pluginval when built with `PLUGINVAL_VST3_VALIDATOR=ON` (the default). This eliminates the need to provide an external validator binary path.

**Architecture:**
1. The VST3 SDK is fetched via CPM during CMake configure
2. `VST3ValidatorRunner` (`Source/vst3validator/`) wraps the SDK's validation functionality
3. When the `VST3validator` test runs, it spawns pluginval with `--vst3-validator-mode`
4. This subprocess runs the embedded validator code in isolation (crash protection)

**Internal CLI mode:**
```bash
# Used internally by the VST3validator test - not for direct use
pluginval --vst3-validator-mode /path/to/plugin.vst3 [-e] [-v]
```

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

## Adding New Tests

1. Create a subclass of `PluginTest`:
Expand Down Expand Up @@ -312,6 +337,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
94 changes: 94 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ if(PLUGINVAL_ENABLE_RTCHECK)
CPMAddPackage("gh:Tracktion/rtcheck#main")
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()

option(PLUGINVAL_FETCH_JUCE "Fetch JUCE along with pluginval" ON)

Expand Down Expand Up @@ -103,6 +123,49 @@ set(SourceFiles
target_sources(pluginval PRIVATE ${SourceFiles})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/Source PREFIX Source FILES ${SourceFiles})

# Add VST3 validator sources if enabled
if(PLUGINVAL_VST3_VALIDATOR)
set(VST3ValidatorFiles
Source/vst3validator/VST3ValidatorRunner.h
Source/vst3validator/VST3ValidatorRunner.cpp
)

# Add platform-specific module loading sources from the SDK
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND VST3ValidatorFiles
${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/hosting/module_linux.cpp)
elseif(APPLE)
set(VST3_MODULE_MAC_FILE ${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/hosting/module_mac.mm)
list(APPEND VST3ValidatorFiles ${VST3_MODULE_MAC_FILE})
# module_mac.mm requires ARC (Automatic Reference Counting)
set_source_files_properties(${VST3_MODULE_MAC_FILE} PROPERTIES
COMPILE_FLAGS "-fobjc-arc")
elseif(WIN32)
set(VST3_MODULE_WIN32_FILE ${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/hosting/module_win32.cpp)
list(APPEND VST3ValidatorFiles ${VST3_MODULE_WIN32_FILE})
# module_win32.cpp requires C++17 (not C++20) because generic_u8string() returns
# std::u8string in C++20 but std::string in C++17, and the SDK code expects std::string.
# Also set UNICODE definitions only for this file to avoid affecting JUCE's LV2/lilv code.
set_source_files_properties(${VST3_MODULE_WIN32_FILE} PROPERTIES
COMPILE_FLAGS "/std:c++17"
COMPILE_DEFINITIONS "NOMINMAX;WIN32_LEAN_AND_MEAN;_UNICODE;UNICODE")
endif()

target_sources(pluginval PRIVATE ${VST3ValidatorFiles})
source_group("Source/vst3validator" FILES ${VST3ValidatorFiles})

target_include_directories(pluginval PRIVATE
${vst3sdk_SOURCE_DIR}
${vst3sdk_SOURCE_DIR}/pluginterfaces
${vst3sdk_SOURCE_DIR}/base
${vst3sdk_SOURCE_DIR}/public.sdk
${vst3sdk_SOURCE_DIR}/public.sdk/source
${vst3sdk_SOURCE_DIR}/public.sdk/source/vst
${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/hosting
${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/utility
)
endif()

if (DEFINED ENV{VST2_SDK_DIR})
target_compile_definitions(pluginval PRIVATE
JUCE_PLUGINHOST_VST=1)
Expand All @@ -118,8 +181,19 @@ 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}")

# Windows-specific compile definitions for VST3 SDK compatibility
# Note: _UNICODE and UNICODE are NOT set here - they're set only for module_win32.cpp
# to avoid breaking JUCE's LV2/lilv code which uses ANSI Windows APIs
if(WIN32 AND PLUGINVAL_VST3_VALIDATOR)
target_compile_definitions(pluginval PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
_CRT_SECURE_NO_WARNINGS)
endif()

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 @@ -137,6 +211,26 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
-static-libstdc++)
endif()

if (PLUGINVAL_VST3_VALIDATOR)
target_link_libraries(pluginval PRIVATE
sdk_hosting
sdk)

# Windows needs additional libraries for the module loading code
if(WIN32)
target_link_libraries(pluginval PRIVATE
Ole32
Shell32)
endif()

# macOS needs Cocoa framework for module loading
if(APPLE)
find_library(COCOA_FRAMEWORK Cocoa)
target_link_libraries(pluginval PRIVATE
${COCOA_FRAMEWORK})
endif()
endif()

if (PLUGINVAL_ENABLE_RTCHECK)
target_link_libraries(pluginval PRIVATE
rtcheck)
Expand Down
53 changes: 44 additions & 9 deletions Source/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include "CrashHandler.h"
#include "PluginTests.h"

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

#if JUCE_MAC
#include <signal.h>
#include <sys/types.h>
Expand Down Expand Up @@ -286,7 +290,6 @@ static Option possibleOptions[] =
{ "--randomise", false },
{ "--sample-rates", true },
{ "--block-sizes", true },
{ "--vst3validator", true },
{ "--rtcheck", false },
};

Expand Down Expand Up @@ -380,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 @@ -501,7 +502,11 @@ static juce::ArgumentList createCommandLineArgs (juce::String commandLine)
const bool hasValidateOrOtherCommand = argList.containsOption ("--validate")
|| argList.containsOption ("--help|-h")
|| argList.containsOption ("--version")
|| argList.containsOption ("--run-tests");
|| argList.containsOption ("--run-tests")
#if PLUGINVAL_VST3_VALIDATOR
|| argList.containsOption ("--vst3-validator-mode")
#endif
;

if (! hasValidateOrOtherCommand)
if (isPluginArgument (argList.arguments.getLast().text))
Expand Down Expand Up @@ -542,6 +547,36 @@ static void performCommandLine (CommandLineValidator& validator, const juce::Arg
printStrictnessHelp (level);
}});

#if PLUGINVAL_VST3_VALIDATOR
cli.addCommand ({ "--vst3-validator-mode",
"--vst3-validator-mode [pathToPlugin] [-e] [-v]",
"Runs the embedded VST3 validator on the plugin (internal use).", juce::String(),
[] (const auto& args)
{
auto pluginPath = getOptionValue (args, "--vst3-validator-mode", "",
"Expected a plugin path for --vst3-validator-mode").toString();

if (pluginPath.isEmpty())
{
std::cerr << "Error: No plugin path specified\n";
juce::JUCEApplication::getInstance()->setApplicationReturnValue (1);
juce::JUCEApplication::getInstance()->quit();
return;
}

vst3validator::Options opts;
opts.pluginPath = pluginPath.toStdString();
opts.extendedMode = args.containsOption ("-e");
opts.verbose = args.containsOption ("-v");

auto result = vst3validator::runValidator (opts);

std::cout << result.output;
juce::JUCEApplication::getInstance()->setApplicationReturnValue (result.exitCode);
juce::JUCEApplication::getInstance()->quit();
}});
#endif

if (const auto retValue = cli.findAndRunCommand (args); retValue != 0)
{
juce::JUCEApplication::getInstance()->setApplicationReturnValue (retValue);
Expand All @@ -566,7 +601,11 @@ bool shouldPerformCommandLine (const juce::String& commandLine)
|| args.containsOption ("--version")
|| args.containsOption ("--validate")
|| args.containsOption ("--run-tests")
|| args.containsOption ("--strictness-help");
|| args.containsOption ("--strictness-help")
#if PLUGINVAL_VST3_VALIDATOR
|| args.containsOption ("--vst3-validator-mode")
#endif
;
}

//==============================================================================
Expand Down Expand Up @@ -595,7 +634,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 @@ -665,9 +703,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
57 changes: 56 additions & 1 deletion Source/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
#include "CommandLine.h"
#include "PluginvalLookAndFeel.h"

#if PLUGINVAL_VST3_VALIDATOR
#include "vst3validator/VST3ValidatorRunner.h"
#include <cstring>
#include <iostream>
#endif

//==============================================================================
class PluginValidatorApplication : public juce::JUCEApplication,
private juce::AsyncUpdater
Expand Down Expand Up @@ -174,8 +180,57 @@ class PluginValidatorApplication : public juce::JUCEApplication,
};

//==============================================================================
// This macro generates the main() routine that launches the app.
#if PLUGINVAL_VST3_VALIDATOR
// Custom main() to intercept --vst3-validator-mode before JUCE starts.
// This avoids the "Periodic events are already being generated" crash on macOS
// that occurs when JUCE's event loop conflicts with the validator subprocess.
int main (int argc, char* argv[])
{
// Check for --vst3-validator-mode before starting JUCE
for (int i = 1; i < argc; ++i)
{
if (std::strcmp (argv[i], "--vst3-validator-mode") == 0)
{
// Parse arguments for validator mode
vst3validator::Options opts;

// The plugin path should be the next argument
if (i + 1 < argc && argv[i + 1][0] != '-')
opts.pluginPath = argv[i + 1];

// Check for optional flags
for (int j = 1; j < argc; ++j)
{
if (std::strcmp (argv[j], "-e") == 0)
opts.extendedMode = true;
else if (std::strcmp (argv[j], "-v") == 0)
opts.verbose = true;
}

if (opts.pluginPath.empty())
{
std::cerr << "Error: No plugin path specified for --vst3-validator-mode\n";
return 1;
}

// Run the validator directly without JUCE
auto result = vst3validator::runValidator (opts);
std::cout << result.output;
return result.exitCode;
}
}

// Normal JUCE application startup
return juce::JUCEApplicationBase::main (argc, const_cast<const char**> (argv));
}

// Provide the JUCE application class
juce::JUCEApplicationBase* juce_CreateApplication() { return new PluginValidatorApplication(); }

#else
// Standard JUCE application macro when VST3 validator is disabled
START_JUCE_APPLICATION (PluginValidatorApplication)
#endif

juce::PropertiesFile& getAppPreferences()
{
Expand Down
Loading
Loading