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
90 changes: 90 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,44 @@ 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)
# Add module_win32.cpp directly - compile definitions are set at target level
list(APPEND VST3ValidatorFiles
${vst3sdk_SOURCE_DIR}/public.sdk/source/vst/hosting/module_win32.cpp)
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 +176,20 @@ 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
if(WIN32 AND PLUGINVAL_VST3_VALIDATOR)
target_compile_definitions(pluginval PRIVATE
NOMINMAX
WIN32_LEAN_AND_MEAN
_UNICODE
UNICODE
_CRT_SECURE_NO_WARNINGS
_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING)
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 +207,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
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 @@ -629,9 +590,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
Loading
Loading