How to Link/Install Library to Target Plugin Component using CMAKE

Hi Everyone,

Hopefully I’ll be able to explain what I’m struggling with - hear me out, I’m quite unfamiliar with build processes in general so there might be some things I’m totally missing.

I’m using a 3rd party library in my plugin and I’m trying to write a CMakeLists.txt file so that the .dylib files I’m using are installed in the plugin folder ({usr}/Library/Audio/Plug-ins/Component/lib) so that the AU plugin component refers to those copied library files instead of the .dylib files in my project folder. I’m under the impression that if I create a target during the build process, and the target is properly linked to the libraries, I can run the INSTALL step and copy over the target & library files to whatever directory (in my case, the plugin folder) and things should run smoothly.

What I’m confused with JUCE is that the target executable that’s created at the end of the build process is some kind of a SharedCode.a file, not the .au component file. So it seems like my libraries are linked to the SharedCode.a target file instead of the AU plugin file, which is why the plugin file still has a dependency on my .dylib files in my working directory.

The plugin that I’m creating is working fine, but I’d like to get rid of the dependency between the AU file and my .dylib files in my working directory. I’d like to either 1) install the .dylib files in the plugin folder and have the plugin file refer to those files or 2) create a static build and include all the .dylib contents in the plugin file itself so that it doesn’t have to depend on an external library at all.

Here is the entirety of my CMakeLists.txt file, I’d appreciate any kind of help. Thanks!

cmake_minimum_required(VERSION 3.15)

project(rtse-plugin VERSION 0.0.1)

set(CMAKE_CXX_STANDARD 11)

include(FetchContent)
FetchContent_Declare(
        JUCE
        GIT_REPOSITORY https://github.com/juce-framework/JUCE.git
        GIT_TAG 6.0.5
)
FetchContent_MakeAvailable(JUCE)

# add directories to be included when compiler searches for files
include_directories(${RTSE_DIR}/include)
# add directories where linker should search for libraries
link_directories(${RTSE_DIR}/lib)

# `juce_add_plugin` adds a static library target with the name passed as the first argument. This target is a normal CMake target, but has a lot of extra properties set
# up by default. As well as this shared code static library, this function adds targets for each of
# the formats specified by the FORMATS arguments. This function accepts many optional arguments.
# Check the readme at `docs/CMake API.md` in the JUCE repo for the full list.
juce_add_plugin(${PROJECT_NAME}
    VERSION 0.0.1                               # Set this if the plugin version is different to the project version
    COMPANY_NAME Supertone                          # Specify the name of the plugin's author
    IS_SYNTH FALSE                                  # Is this a synth or an effect?
    NEEDS_MIDI_INPUT FALSE                          # Does the plugin need midi input?
    NEEDS_MIDI_OUTPUT FALSE                         # Does the plugin need midi output?
    IS_MIDI_EFFECT FALSE                            # Is this plugin a MIDI effect?
    EDITOR_WANTS_KEYBOARD_FOCUS FALSE           # Does the editor need keyboard focus?
    COPY_PLUGIN_AFTER_BUILD TRUE                # Should the plugin be installed to a default location after building?
    # AU_COPY_DIR $ENV{HOME}/Library/Audio/Plug-Ins/Components
    PLUGIN_MANUFACTURER_CODE Sptn               # A four-character manufacturer id with at least one upper-case character
    PLUGIN_CODE Rtse                            # A unique four-character plugin id with exactly one upper-case character
                                                # GarageBand 10.3 requires the first letter to be upper-case, and the remaining letters to be lower-case
    FORMATS AU                                  # The formats to build. Other valid formats are: AAX Unity VST AU AUv3
    PRODUCT_NAME "RTSE Plugin")            # The name of the final executable, which can differ from the target name
    
# `juce_generate_juce_header` will create a JuceHeader.h for a given target, which will be generated
# into your build tree. This should be included with `#include <JuceHeader.h>`. The include path for
# this header will be automatically added to the target. The main function of the JuceHeader is to
# include all your JUCE module headers; if you're happy to include module headers directly, you
# probably don't need to call this.

juce_generate_juce_header(${PROJECT_NAME})

# `target_sources` adds source files to a target. We pass the target that needs the sources as the
# first argument, then a visibility parameter for the sources which should normally be PRIVATE.
# Finally, we supply a list of source files that will be built into the target. This is a standard
# CMake command.

target_sources(${PROJECT_NAME}
    PRIVATE
        Main.cpp
        RtsePlugin.h)

# `target_compile_definitions` adds some preprocessor definitions to our target. In a Projucer
# project, these might be passed in the 'Preprocessor Definitions' field. JUCE modules also make use
# of compile definitions to switch certain features on/off, so if there's a particular feature you
# need that's not on by default, check the module header for the correct flag to set here. These
# definitions will be visible both to your code, and also the JUCE module code, so for new
# definitions, pick unique names that are unlikely to collide! This is a standard CMake command.

target_compile_definitions(${PROJECT_NAME}
    PUBLIC
        # JUCE_WEB_BROWSER and JUCE_USE_CURL would be on by default, but you might not need them.
        JUCE_WEB_BROWSER=0  # If you remove this, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call
        JUCE_USE_CURL=0     # If you remove this, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call
        JUCE_VST3_CAN_REPLACE_VST2=0)

# `target_link_libraries` links libraries and JUCE modules to other libraries or executables. Here,
# we're linking our executable target to the `juce::juce_audio_utils` module. Inter-module
# dependencies are resolved automatically, so `juce_core`, `juce_events` and so on will also be
# linked automatically. If we'd generated a binary data target above, we would need to link to it
# here too. This is a standard CMake command.

target_link_libraries(${PROJECT_NAME}
    PRIVATE
        juce::juce_audio_utils
        rtse
    PUBLIC
        juce::juce_recommended_config_flags
        juce::juce_recommended_lto_flags
        juce::juce_recommended_warning_flags)

if(UNIX AND NOT APPLE)
target_link_libraries(${PROJECT_NAME} dl pthread m)
endif()

if(APPLE)
set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "@loader_path")
elseif(UNIX)
set_property(TARGET ${PROJECT_NAME} PROPERTY INSTALL_RPATH "$ORIGIN")
endif()

install(TARGETS ${PROJECT_NAME} DESTINATION $ENV{HOME}/Library/Audio/Plug-Ins/Components/lib)
install(
DIRECTORY ${RTSE_DIR}/lib/
DESTINATION $ENV{HOME}/Library/Audio/Plug-Ins/Components/lib
FILES_MATCHING
  PATTERN "*.dylib"
  PATTERN "*.so*"
  PATTERN "*.dll"
)

Linking a plugin to a dll is quite fiddly, and I wouldn’t recommend it except as a last resort. I think your suggestion 2) is sensible (i.e. link the library statically rather than dynamically). Normally it’s up to the library to provide a staticlib build - if you’re building the library from source, perhaps there is a build flag you can set in order to create a staticlib.