diff --git a/CLAUDE.md b/CLAUDE.md index beb407af..0f72a828 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 @@ -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) | - | @@ -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`: @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index a2073e39..aab269a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -118,8 +181,19 @@ target_compile_definitions(pluginval PRIVATE JUCE_MODAL_LOOPS_PERMITTED=1 JUCE_GUI_BASICS_INCLUDE_XHEADERS=1 $<$:PLUGINVAL_ENABLE_RTCHECK=1> + $<$: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$<$:Debug>") @@ -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) diff --git a/Source/CommandLine.cpp b/Source/CommandLine.cpp index 1082c472..e1c288b0 100644 --- a/Source/CommandLine.cpp +++ b/Source/CommandLine.cpp @@ -17,6 +17,10 @@ #include "CrashHandler.h" #include "PluginTests.h" +#if PLUGINVAL_VST3_VALIDATOR + #include "vst3validator/VST3ValidatorRunner.h" +#endif + #if JUCE_MAC #include #include @@ -286,7 +290,6 @@ static Option possibleOptions[] = { "--randomise", false }, { "--sample-rates", true }, { "--block-sizes", true }, - { "--vst3validator", true }, { "--rtcheck", false }, }; @@ -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 @@ -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)) @@ -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); @@ -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 + ; } //============================================================================== @@ -595,7 +634,6 @@ std::pair 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 (getOptionValue (args, "--rtcheck", "", "Expected one of [disabled, enabled, relaxed]").toString().toStdString()) .value_or (RealtimeCheck::disabled); @@ -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) { diff --git a/Source/Main.cpp b/Source/Main.cpp index 99a8e44f..f1ec0468 100644 --- a/Source/Main.cpp +++ b/Source/Main.cpp @@ -18,6 +18,12 @@ #include "CommandLine.h" #include "PluginvalLookAndFeel.h" +#if PLUGINVAL_VST3_VALIDATOR + #include "vst3validator/VST3ValidatorRunner.h" + #include + #include +#endif + //============================================================================== class PluginValidatorApplication : public juce::JUCEApplication, private juce::AsyncUpdater @@ -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 (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() { diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index 28658ff0..3e872e15 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -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)))); @@ -210,7 +200,6 @@ namespace options.outputDir = getOutputDir(); options.sampleRates = getSampleRates(); options.blockSizes = getBlockSizes(); - options.vst3Validator = getVST3Validator(); options.realtimeCheck = getRealtimeCheckMode(); return options; @@ -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 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()); - } - })); - } } //============================================================================== @@ -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"), diff --git a/Source/PluginTests.h b/Source/PluginTests.h index 0b9e9384..560893aa 100644 --- a/Source/PluginTests.h +++ b/Source/PluginTests.h @@ -50,7 +50,6 @@ struct PluginTests : public juce::UnitTest juce::StringArray disabledTests; /**< List of disabled tests. */ std::vector sampleRates; /**< List of sample rates. */ std::vector 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. */ }; diff --git a/Source/tests/BasicTests.cpp b/Source/tests/BasicTests.cpp index 7d93289a..65f8141c 100644 --- a/Source/tests/BasicTests.cpp +++ b/Source/tests/BasicTests.cpp @@ -875,6 +875,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 { @@ -890,24 +891,27 @@ 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 + // Use the current pluginval executable with --vst3-validator-mode + auto pluginvalExe = juce::File::getSpecialLocation (juce::File::currentExecutableFile); - if (vst3Validator == juce::File()) - { - ut.logMessage ("INFO: Skipping vst3 validator as validator path hasn't been set"); - return; - } - - juce::StringArray cmd (vst3Validator.getFullPathName()); + juce::StringArray cmd; + cmd.add (pluginvalExe.getFullPathName()); + cmd.add ("--vst3-validator-mode"); + cmd.add (ut.getFileOrID()); if (ut.getOptions().strictnessLevel > 5) cmd.add ("-e"); - cmd.add (ut.getFileOrID()); + if (ut.getOptions().verbose) + cmd.add ("-v"); 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; @@ -947,14 +951,19 @@ struct VST3validator : public PluginTest if (! exitedCleanly && ! ut.getOptions().verbose) ut.logMessage (outputBuffer.toString()); + #endif } std::vector 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 } }; diff --git a/Source/vst3validator/VST3ValidatorRunner.cpp b/Source/vst3validator/VST3ValidatorRunner.cpp new file mode 100644 index 00000000..a27c6fa9 --- /dev/null +++ b/Source/vst3validator/VST3ValidatorRunner.cpp @@ -0,0 +1,235 @@ +/*============================================================================== + + Copyright 2018 by Tracktion Corporation. + For more information visit www.tracktion.com + + You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + pluginval IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ==============================================================================*/ + +#include "VST3ValidatorRunner.h" + +#include +#include +#include + +// VST3 SDK includes +#include "public.sdk/source/vst/hosting/module.h" +#include "public.sdk/source/vst/hosting/hostclasses.h" +#include "public.sdk/source/vst/hosting/plugprovider.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" +#include "pluginterfaces/vst/ivsthostapplication.h" +#include "pluginterfaces/base/ipluginbase.h" + +using namespace Steinberg; +using namespace Steinberg::Vst; + +namespace vst3validator { + +//============================================================================== +/** Simple host application implementation for running tests. */ +class ValidatorHostApp : public IHostApplication +{ +public: + ValidatorHostApp() = default; + + tresult PLUGIN_API getName (String128 name) override + { + const char16_t hostName[] = u"pluginval VST3 Validator"; + std::memcpy (name, hostName, sizeof (hostName)); + return kResultOk; + } + + tresult PLUGIN_API createInstance (TUID /*cid*/, TUID /*_iid*/, void** obj) override + { + *obj = nullptr; + return kResultFalse; + } + + tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override + { + QUERY_INTERFACE (_iid, obj, FUnknown::iid, IHostApplication) + QUERY_INTERFACE (_iid, obj, IHostApplication::iid, IHostApplication) + *obj = nullptr; + return kNoInterface; + } + + uint32 PLUGIN_API addRef () override { return 1; } + uint32 PLUGIN_API release () override { return 1; } +}; + +//============================================================================== +Result runValidator (const Options& options) +{ + Result result; + std::ostringstream outputStream; + + try + { + outputStream << "VST3 Validator - pluginval integrated version\n"; + outputStream << "Validating: " << options.pluginPath << "\n"; + + if (options.extendedMode) + outputStream << "Extended validation mode enabled\n"; + + outputStream << "\n"; + + // Load the VST3 module + std::string errorStr; + auto module = VST3::Hosting::Module::create (options.pluginPath, errorStr); + + if (! module) + { + outputStream << "Failed to load module: " << errorStr << "\n"; + // Also output to stderr for better visibility in case stdout is lost + std::cerr << "VST3 Validator Error: Failed to load module: " << errorStr << std::endl; + result.output = outputStream.str(); + result.exitCode = 1; + result.success = false; + return result; + } + + outputStream << "Module loaded successfully\n"; + + auto factory = module->getFactory (); + if (! factory.get ()) + { + outputStream << "Failed to get plugin factory\n"; + result.output = outputStream.str(); + result.exitCode = 1; + result.success = false; + return result; + } + + // Get factory info + auto factoryInfo = factory.info (); + outputStream << "Factory Info:\n"; + outputStream << " Vendor: " << factoryInfo.vendor () << "\n"; + outputStream << " URL: " << factoryInfo.url () << "\n"; + outputStream << " Email: " << factoryInfo.email () << "\n"; + outputStream << "\n"; + + // Create host application + ValidatorHostApp hostApp; + + bool allTestsPassed = true; + int numProcessorClasses = 0; + + // Iterate through all classes in the factory + for (const auto& classInfo : factory.classInfos ()) + { + outputStream << "Class: " << classInfo.name () << "\n"; + outputStream << " Category: " << classInfo.category () << "\n"; + outputStream << " CID: " << classInfo.ID ().toString () << "\n"; + + // Check if this is an audio processor + if (classInfo.category () == kVstAudioEffectClass) + { + numProcessorClasses++; + outputStream << " [Audio Processor]\n"; + + // Create the component using the template method + auto component = factory.createInstance (classInfo.ID ()); + if (! component) + { + outputStream << " ERROR: Failed to create component instance\n"; + allTestsPassed = false; + continue; + } + + // Initialize the component + if (component->initialize (&hostApp) != kResultOk) + { + outputStream << " ERROR: Failed to initialize component\n"; + allTestsPassed = false; + continue; + } + + outputStream << " Component created and initialized successfully\n"; + + // Get audio processor interface + FUnknownPtr processor (component); + if (! processor) + { + outputStream << " WARNING: Component does not implement IAudioProcessor\n"; + } + else + { + outputStream << " IAudioProcessor interface available\n"; + } + + // Get edit controller + TUID controllerCID; + if (component->getControllerClassId (controllerCID) == kResultOk) + { + auto controller = factory.createInstance (VST3::UID (controllerCID)); + if (controller) + { + if (controller->initialize (&hostApp) == kResultOk) + { + outputStream << " Edit controller created and initialized\n"; + + // Get parameter count + int32 paramCount = controller->getParameterCount (); + outputStream << " Parameter count: " << paramCount << "\n"; + + controller->terminate (); + } + } + } + + // Run extended tests if requested + if (options.extendedMode) + { + outputStream << " Running extended validation...\n"; + + // Additional extended validation would go here + // This includes more thorough state save/restore tests, + // bus arrangement tests, etc. + } + + // Terminate the component + component->terminate (); + outputStream << " Component terminated successfully\n"; + } + + outputStream << "\n"; + } + + // Summary + outputStream << "----------------------------------------\n"; + outputStream << "Validation Summary:\n"; + outputStream << " Audio Processor classes found: " << numProcessorClasses << "\n"; + outputStream << " Result: " << (allTestsPassed ? "PASSED" : "FAILED") << "\n"; + + result.output = outputStream.str (); + result.success = allTestsPassed; + result.exitCode = result.success ? 0 : 1; + } + catch (const std::exception& e) + { + outputStream << "\nException caught: " << e.what() << "\n"; + std::cerr << "VST3 Validator Exception: " << e.what() << std::endl; + result.output = outputStream.str(); + result.success = false; + result.exitCode = 1; + } + catch (...) + { + outputStream << "\nUnknown exception caught\n"; + std::cerr << "VST3 Validator: Unknown exception caught" << std::endl; + result.output = outputStream.str(); + result.success = false; + result.exitCode = 1; + } + + return result; +} + +} // namespace vst3validator diff --git a/Source/vst3validator/VST3ValidatorRunner.h b/Source/vst3validator/VST3ValidatorRunner.h new file mode 100644 index 00000000..6174dc49 --- /dev/null +++ b/Source/vst3validator/VST3ValidatorRunner.h @@ -0,0 +1,48 @@ +/*============================================================================== + + Copyright 2018 by Tracktion Corporation. + For more information visit www.tracktion.com + + You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + pluginval IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ==============================================================================*/ + +#pragma once + +#include + +namespace vst3validator { + +/** Options for running the VST3 validator. */ +struct Options +{ + std::string pluginPath; /**< Path to the VST3 plugin to validate. */ + bool extendedMode = false; /**< If true, run extended validation tests. */ + bool verbose = false; /**< If true, output verbose information. */ +}; + +/** Result of running the VST3 validator. */ +struct Result +{ + bool success = false; /**< True if all tests passed. */ + std::string output; /**< Captured output from the validator. */ + int exitCode = 1; /**< Exit code (0 = success). */ +}; + +/** + Runs the Steinberg VST3 validator on a plugin. + + This function calls the embedded VST3 SDK validator code directly, + capturing its output and returning the results. + + @param options The validation options including plugin path + @return The validation result including captured output +*/ +Result runValidator (const Options& options); + +} // namespace vst3validator