Build a standalone application that integrates an ARA plugin via static linkage

I’m setting up a CMake based project that contains a plugin target, added via juce_add_plugin and a standalone application, added via juce_add_gui_app. The standalone application should integrate the plugin, so I simply tried linking against it like:

target_link_libraries (my_standalone
    PUBLIC
        juce::juce_gui_extra
    PRIVATE
        my_plugin)

The standalone code then calls createPluginFilter() to instantiate the plugin. This worked well until we enabled ARA for the plugin. While building e.g. the VST3 target for the plugin still works as expected and produces a running ARA enabled VST3 plugin, compiling the standalone target now fails with these linker errors:

ld: Undefined symbols:
  ARA::PlugIn::EditorView::setEditorOpen(bool), referenced from:
      juce::AudioProcessorEditorARAExtension::AudioProcessorEditorARAExtension(juce::AudioProcessor*) in libmy_plugin_SharedCode.a[14](juce_audio_processors.mm.o)
      juce::AudioProcessorEditorARAExtension::~AudioProcessorEditorARAExtension() in libmy_plugin_SharedCode.a[14](juce_audio_processors.mm.o)
  ARA::PlugIn::PlugInExtension::~PlugInExtension(), referenced from:
      juce::AudioProcessorARAExtension::~AudioProcessorARAExtension() in libmy_plugin_SharedCode.a[9](PluginWrapper.cpp.o)
  ARA::PlugIn::PlugInExtensionInstance::PlugInExtensionInstance(ARA::PlugIn::PlaybackRendererInterface*, ARA::PlugIn::EditorRendererInterface*, ARA::PlugIn::EditorViewInterface*), referenced from:
      ARA::PlugIn::PlugInExtension::PlugInExtension() in libmy_plugin_SharedCode.a[9](PluginWrapper.cpp.o)
  typeinfo for ARA::PlugIn::PlugInExtension, referenced from:
      typeinfo for juce::AudioProcessorARAExtension in libmy_plugin_SharedCode.a[14](juce_audio_processors.mm.o)
  typeinfo for ARA::PlugIn::PlaybackRenderer, referenced from:
      typeinfo for juce::ARAPlaybackRenderer in libmy_plugin_SharedCode.a[14](juce_audio_processors.mm.o)
  typeinfo for ARA::PlugIn::DocumentController, referenced from:
      MyPluginAudioProcessor::didBindToARA() in libmy_plugin_SharedCode.a[9](PluginWrapper.cpp.o)
  vtable for ARA::PlugIn::PlugInExtension, referenced from:
      ARA::PlugIn::PlugInExtension::PlugInExtension() in libmy_plugin_SharedCode.a[9](PluginWrapper.cpp.o)
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Note that PluginWrapper.cpp referenced here is a source file added to the plugin target which implements the juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() function. Removing the call to that function from the standalone code leads to an error free build.

I’m having a hard time understanding what’s going wrong here, given that the auto generated plugin type targets (e.g. myPlugin_VST3) also “just” link against the myPlugin, so technically link against libmy_plugin_SharedCode.a and create a working dynamic library or in case of the Standalone plugin type even an executable. I see that the juce code uses a plain add_executable rather than juce_add_gui_app so I tried that as well, just to get a lot more undefined symbol errors in that case (ARA related ones and symbols from other dependencies the plugin links against). I also see that the juce code does quite a bit more and I’m not sure if I’m missing an important step, I tried adapting that include directories propagation that is done here but I don’t see that making any difference.

Can anyone explain to me what is going wrong here and make suggestions about how to do all that properly? By the way, my goal is not a simple standalone version of a plugin but integrating the plugin into a standalone tool with a much more sophisticated feature set, so I don’t think just using the standalone version will be the solution.

Alright, I managed to get it building, but I’m not sure if I did the right thing. Would love to get some confirmation from the JUCE team if that is the intended thing.

I found a multitude of things that I needed to add to CMake in order to make it build. The CMakeLists for the gui app now looks like this

juce_add_gui_app (my_standalone
        COMPANY_NAME                me
        PRODUCT_NAME                my_product
        BUNDLE_ID                   com.me.myproduct)

target_include_directories (my_standalone PRIVATE $<TARGET_PROPERTY:my_plugin,INCLUDE_DIRECTORIES>)

target_compile_definitions (my_standalone
    PRIVATE
        JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1)

target_link_libraries (my_standalone
    PUBLIC
        juce::juce_audio_utils
        juce::juce_audio_devices
        juce::juce_gui_extra
        juce::juce_audio_plugin_client_Standalone
    PRIVATE
        my_plugin)

I got a lot of hints from inspecting the _juce_link_plugin_wrapper function found in extras/Build/CMake/JUCEUtils.cmake. What was most surprising to me was that I needed to link against juce:: juce_audio_plugin_client_Standalone. After finding out that the ARA sources are compiled in the juce_audio_plugin_client module, I first tried linking against juce::juce_audio_plugin_client but that failed and I could actually prove that modules/juce_audio_plugin_client/juce_audio_plugin_client_ARA.cpp is not even compiled at all. I spotted that _Standalone target suffix in the _juce_link_plugin_wrapper code and I’m not quite sure where that target comes from. I could do some detective work but at this point I’m just happy that it works.

Still I wonder if the existence and meaning of the juce_audio_plugin_client_Standalone is documented somewhere and if I’m relying on a private JUCE implementation detail here or if I can consider this to be part of the official JUCE CMake API? In case this is a private API what would be the official approach using documented APIs? In case this is a public API, where is all that documented? Maybe @reuk as the main author of the JUCE CMake API can give some insights here?

To create a standalone application with an ARA plug-in, I just added:

target_compile_definitions(MySuperAraPlugin PUBLIC 
    JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1
    JUCE_PLUGINHOST_ARA=1 
    JUCE_PLUGINHOST_VST3=1
)

With JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP, I can adapt the standalone to ARA features (this requires a few tricks to initialize the plug-in’s various ARA roles) and with JUCE_PLUGINHOST_ARA (and JUCE_PLUGINHOST_VST3, even though there is no VST3 in this case), I can use ARAHostModel methods.

P.S. I’m hoping to publish a Git directory with a set of classes for the development of ARA plug-ins and standalone apps, but I still need to clean up a lot of things and especially remove private dependencies :sweat:

Thanks for chiming in!

So just to get it right, MySuperAraPlugin in your example is the plugin target or the standalone application target?

And do you mean that you just add those compile definitions to the target and do nothing else to make it work? Because for me this does not really do the trick unfortunately. But I might need a bit more of context :wink:

In general it’s an extremely confusing to me that the juce_audio_processors module includes and uses the ARA headers but the ARA translation units are compiled by the juce_audio_plugin_client module, without that module being a dependency to juce_audio_processors. What’s the reason for that choice?

Indeed, this is for the standalone and is sufficient to compile the standalone. However, it won’t work with the VST3 version, and you’ll get duplicated symbols because of JUCE_PLUGINHOST_VST3. To avoid this, either don’t use JUCE’s ARAHost classes, create another plug-in for the standalone without the VST3 format, or modify JUCE to include ARAHost without defining JUCE_PLUGINHOST_VST3 (perhaps this would be the best solution?).