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
129 changes: 109 additions & 20 deletions Source/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "PluginTests.h"

#if JUCE_MAC
#include <CoreFoundation/CoreFoundation.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
Expand All @@ -26,18 +27,38 @@
#include <magic_enum/magic_enum.hpp>

//==============================================================================
static void exitWithError (const juce::String& error)
namespace
{
std::cout << error << std::endl << std::endl;
juce::JUCEApplication::getInstance()->setApplicationReturnValue (1);
juce::JUCEApplication::getInstance()->quit();
struct CommandLineExecutionContext
{
void requestQuit (int code)
{
returnValue = code;
shouldQuit = true;
}

[[nodiscard]] int getReturnValue() const noexcept { return returnValue.load(); }
[[nodiscard]] bool quitRequested() const noexcept { return shouldQuit.load(); }

private:
std::atomic<int> returnValue { 0 };
std::atomic<bool> shouldQuit { false };
};

CommandLineExecutionContext* currentExecutionContext = nullptr;

void requestCommandLineQuit (int returnValue)
{
if (currentExecutionContext != nullptr)
currentExecutionContext->requestQuit (returnValue);
}
}

static void hideDockIcon()
//==============================================================================
static void exitWithError (const juce::String& error)
{
#if JUCE_MAC
juce::Process::setDockIconVisible (false);
#endif
std::cout << error << std::endl << std::endl;
requestCommandLineQuit (1);
}

inline std::mutex& getCoutMutex()
Expand Down Expand Up @@ -84,7 +105,8 @@ static void setupSignalHandling()

//==============================================================================
//==============================================================================
CommandLineValidator::CommandLineValidator()
CommandLineValidator::CommandLineValidator (std::function<void (int)> completionCallback)
: completion (std::move (completionCallback))
{
#if JUCE_MAC
setupSignalHandling();
Expand All @@ -102,12 +124,13 @@ void CommandLineValidator::validate (const juce::String& fileOrID, PluginTests::
{
logLine ("Started validating: " + id);
},
[] (auto, uint32_t exitCode)
[this] (auto, uint32_t exitCode)
{
if (exitCode > 0)
exitWithError ("*** FAILED");
else
juce::JUCEApplication::getInstance()->quit();

if (completion != nullptr)
completion (exitCode > 0 ? 1 : 0);
},
[] (auto m)
{
Expand Down Expand Up @@ -325,7 +348,7 @@ static juce::StringArray mergeEnvironmentVariables (juce::StringArray args, std:
//==============================================================================
static juce::String getHelpMessage()
{
const juce::String appName (juce::JUCEApplication::getInstance()->getApplicationName());
const juce::String appName ("pluginval");
const juce::String juceVersion (juce::SystemStats::getJUCEVersion());

return juce::String (R"(//==============================================================================
Expand Down Expand Up @@ -512,10 +535,38 @@ static juce::ArgumentList createCommandLineArgs (juce::String commandLine)
return argList;
}

static void performCommandLine (CommandLineValidator& validator, const juce::ArgumentList& args)
static juce::ArgumentList createCommandLineArgs (const juce::ArgumentList& args)
{
hideDockIcon();
juce::StringArray tokens;

for (const auto& argument : args.arguments)
tokens.add (argument.text);

tokens = mergeEnvironmentVariables (tokens);
tokens.trim();

for (auto& token : tokens)
token = token.unquoted();

juce::ArgumentList argList (args.executableName, tokens);

if (argList.size() > 0)
{
const bool hasValidateOrOtherCommand = argList.containsOption ("--validate")
|| argList.containsOption ("--help|-h")
|| argList.containsOption ("--version")
|| argList.containsOption ("--run-tests");

if (! hasValidateOrOtherCommand)
if (isPluginArgument (argList.arguments.getLast().text))
argList.arguments.insert (argList.arguments.size() - 1, { "--validate" });
}

return argList;
}

static void performCommandLine (CommandLineValidator& validator, const juce::ArgumentList& args)
{
juce::ConsoleApplication cli;
cli.addVersionCommand ("--version", getVersionText());
cli.addHelpCommand ("--help|-h", getHelpMessage(), true);
Expand All @@ -534,24 +585,24 @@ static void performCommandLine (CommandLineValidator& validator, const juce::Arg
cli.addCommand ({ "--strictness-help",
"--strictness-help [level]",
"Lists all tests that run at the given strictness level.", juce::String(),
[] (const auto& args)
[] (const auto& strictnessArgs)
{
int level = 5;
auto arg = getArgumentAfterOption (args, "--strictness-help");
auto arg = getArgumentAfterOption (strictnessArgs, "--strictness-help");
if (arg.text.isNotEmpty() && ! arg.isShortOption() && ! arg.isLongOption())
level = arg.text.getIntValue();
printStrictnessHelp (level);
}});

if (const auto retValue = cli.findAndRunCommand (args); retValue != 0)
{
juce::JUCEApplication::getInstance()->setApplicationReturnValue (retValue);
juce::JUCEApplication::getInstance()->quit();
requestCommandLineQuit (retValue);
return;
}

// --validate runs async so will quit itself when done
if (! args.containsOption ("--validate"))
juce::JUCEApplication::getInstance()->quit();
requestCommandLineQuit (0);
}

//==============================================================================
Expand All @@ -570,6 +621,44 @@ bool shouldPerformCommandLine (const juce::String& commandLine)
|| args.containsOption ("--strictness-help");
}

bool shouldPerformCommandLine (int argc, char* argv[])
{
const auto args = createCommandLineArgs (juce::ArgumentList (argc, argv));
return args.containsOption ("--help|-h")
|| args.containsOption ("--version")
|| args.containsOption ("--validate")
|| args.containsOption ("--run-tests")
|| args.containsOption ("--strictness-help");
}

int runCommandLineApplication (int argc, char* argv[])
{
CommandLineExecutionContext executionContext;
const auto executionContextGuard = juce::ScopedValueSetter<CommandLineExecutionContext*> (currentExecutionContext, &executionContext, nullptr);
const auto args = createCommandLineArgs (juce::ArgumentList (argc, argv));

juce::ScopedJuceInitialiser_GUI juceInitialiser;
CommandLineValidator validator ([&executionContext] (int returnValue)
{
executionContext.requestQuit (returnValue);
});

// Avoid dock/presentation changes in CLI mode. On recent macOS versions these
// can force application registration and break headless execution.
performCommandLine (validator, args);

while (! executionContext.quitRequested())
{
#if JUCE_MAC
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.05, true);
#else
juce::MessageManager::getInstance()->runDispatchLoopUntil (50);
#endif
}

return executionContext.getReturnValue();
}

//==============================================================================
//==============================================================================
std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::ArgumentList& args)
Expand Down
5 changes: 4 additions & 1 deletion Source/CommandLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@
//==============================================================================
struct CommandLineValidator
{
CommandLineValidator();
explicit CommandLineValidator (std::function<void (int)> completionCallback = {});
~CommandLineValidator();

void validate (const juce::String&, PluginTests::Options);

private:
std::unique_ptr<ValidationPass> validator;
std::function<void (int)> completion;
};

//==============================================================================
void performCommandLine (CommandLineValidator&, const juce::String& commandLine);
bool shouldPerformCommandLine (const juce::String& commandLine);
bool shouldPerformCommandLine (int argc, char* argv[]);
int runCommandLineApplication (int argc, char* argv[]);

//==============================================================================
std::pair<juce::String, PluginTests::Options> parseCommandLine (const juce::String&);
Expand Down
16 changes: 16 additions & 0 deletions Source/CommandLineTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ struct CommandLineTests : public juce::UnitTest
expect (shouldPerformCommandLine (temp.getFile().getFullPathName()));
}

beginTest ("Raw argv version detection");
{
char arg0[] = "pluginval";
char arg1[] = "--version";
char* argv[] = { arg0, arg1 };
expect (shouldPerformCommandLine (2, argv));
}

beginTest ("Raw argv implicit validate detection");
{
char arg0[] = "pluginval";
char arg1[] = "MyPlugin.vst3";
char* argv[] = { arg0, arg1 };
expect (shouldPerformCommandLine (2, argv));
}

beginTest ("Allows for other options after explicit --validate");
{
const auto currentDir = juce::File::getCurrentWorkingDirectory();
Expand Down
30 changes: 15 additions & 15 deletions Source/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@


//==============================================================================
class PluginValidatorApplication : public juce::JUCEApplication,
private juce::AsyncUpdater
class PluginValidatorApplication : public juce::JUCEApplication
{
public:
//==============================================================================
Expand All @@ -41,11 +40,7 @@ class PluginValidatorApplication : public juce::JUCEApplication,
//==============================================================================
void initialise (const juce::String& commandLine) override
{
if (shouldPerformCommandLine (commandLine))
{
triggerAsyncUpdate();
return;
}
juce::ignoreUnused (commandLine);

#if JUCE_DEBUG
juce::UnitTestRunner testRunner;
Expand Down Expand Up @@ -129,7 +124,6 @@ class PluginValidatorApplication : public juce::JUCEApplication,
std::unique_ptr<juce::PropertiesFile> propertiesFile;
std::unique_ptr<MainWindow> mainWindow;
std::unique_ptr<juce::FileLogger> fileLogger;
std::unique_ptr<CommandLineValidator> commandLineValidator;

static juce::PropertiesFile::Options getPropertiesFileOptions()
{
Expand Down Expand Up @@ -166,16 +160,22 @@ class PluginValidatorApplication : public juce::JUCEApplication,
auto opts = getPropertiesFileOptions();
return new juce::PropertiesFile (opts.getDefaultFile(), opts);
}

void handleAsyncUpdate() override
{
commandLineValidator = std::make_unique<CommandLineValidator>();
performCommandLine (*commandLineValidator, juce::JUCEApplication::getCommandLineParameters());
}
};

//==============================================================================
START_JUCE_APPLICATION (PluginValidatorApplication)
static juce::JUCEApplicationBase* juce_CreateApplication()
{
return new PluginValidatorApplication();
}

int main (int argc, char* argv[])
{
if (shouldPerformCommandLine (argc, argv))
return runCommandLineApplication (argc, argv);

juce::JUCEApplicationBase::createInstance = &juce_CreateApplication;
return juce::JUCEApplicationBase::main (argc, (const char**) argv);
}

juce::PropertiesFile& getAppPreferences()
{
Expand Down