CMake: Linking against a juce module from within an external CMake target

I have a collection of classes that inherit juce::Component which are currently a juce module. However now there are some more complicated build steps (–> compiling a rust library as dependency) which have to be performed as part of the build. As the whole project using this right now and probably all upcoming projects that will use it in future are CMake based anyway, I wanted to try moving away from the module format with its limited abilities and just create a CMake target that I can link against.

So in order to work with juce classes, my target needs to link against them. The former juce modules CMakeLists.txt now looks like that

add_library (my_module STATIC)
target_sources (my_module PRIVATE
        fooModule.cpp 
        barModule.cpp)
target_include_directories (my_module PUBLIC
        Include)
target_link_libraries (my_module 
        rustLibTargetBuiltAsDependency
        juce::juce_gui_basics)

In the top level plugin project CMakeLists.txt it looks like that

add_subdirectory (Ext/JUCE)
add_subdirectory (Ext/my_module)

juce_add_plugin (my_plugin ...)
juce_generate_juce_header (my_plugin)
target_sources (my_plugin PRIVATE
        fooPlugin.cpp 
        barPlugin.cpp)
target_link_libraries (my_plugin PRIVATE
        juce::juce_audio_utils
        juce::juce_dsp
        my_module)

This compiles, but gives me the following warning

path/to/my_plugin/Ext/JUCE/modules/juce_core/juce_core.cpp:114:2: warning: "Please re-save your project with the latest Projucer version to avoid this warning" [-W#pragma-messages]
 JUCE_COMPILER_WARNING ("Please re-save your project with the latest Projucer version to avoid this warning")

while compiling my_module which looks suspicious to me. The fact that it mentions a wrong Projucer version seem like some definitions are not set correctly while juce_core gets compiled trough the module. Likely because I’m linking against the juce::gui_basics module in my_module without having applied all the flags that would have been applied if I was building something with one of the juce_add functions. Did I get that right?

So what I want is to link against the juce_gui_basics module as it is configured by the plugin or standalone application target that links to my_module. This always was the case when working with the Projucer and I expect bad things to happen if this cannot be ensured. But I have no idea how to build something like that in CMake.

I hope that my question is clear enough, as I’m still trying to figure out how this whole CMake stuff and especially the JUCE CMake implementation works under the hood and I don’t feel like having a completely clear picture at this point… :wink:

I think the problem here is that juce_gui_basics is being built into my_module, which hasn’t been created using one of the juce_add_* functions. This is bad for a couple of reasons:

  • The standard JUCE preprocessor definitions aren’t added to the target, producing the warnings you’re seeing
  • Worse is that gui_basics and its dependencies will get built twice: once into my_module, and again into my_plugin. This will result in ODR violations when my_module is linked to my_plugin.

I think the best way forward is probably to link juce_gui_basics as an INTERFACE dependency to my_module, which will ensure that it won’t build as part of my_module. Unfortunately this will only update the interface properties of my_module, and won’t update the include dirs or preprocessor defs used to actually build it. As a result, you’ll need to manually copy the interface properties of gui_basics to the private properties of my_module:

target_include_directories(my_module
    PRIVATE
        $<TARGET_PROPERTY:juce::juce_gui_basics,INTERFACE_INCLUDE_DIRECTORIES>)

target_compile_definitions(my_module
    PRIVATE
        $<TARGET_PROPERTY:juce::juce_gui_basics,INTERFACE_COMPILE_DEFINITIONS>)

Thanks! I ended up making my_module still a juce-style module with dependencies to other modules declared in the module header, created the target with the juce_add_module function and then linked that target against my rust dependency and set the extra include directories etc. on it.

This works and seems to be a good way of integrating it in a juce-style build. I just wonder if you thought about creating some CMake function that sets up such a juce module target without the need to parse a module header but with the option to pass all the relevant information you find in the module header to that cmake function? This way, all relevant pieces of dependency information would be grouped in the CMakeLists.

this is a crucial tip to those of us trying to make normal CMake library structure and code sharing work when you have multiple libs depending on JUCE.

I would recommend this gets answer gets fronted into the CMake API documentation.

In any case, is there a way to make this pattern work on more than 1 layer of linkage?
Let’s say I have a Plugin project being compiled as Standalone with 1 extra library.

Imagine these targets:

  • MyPlugin_Standalone (depends on juce_gui_basics obviously)
  • MyPlugin (let’s say we have some custom controls here, so we depend on juce_gui_basics)
  • libCommonControls (company wide button, depends on juce_gui_basics).

So the way JUCE’s CMake API works, we have juce_gui_basics.cpp compiled into all three of these, causing an ODR problem as you state @reuk . Otherwise a normal CMake static lib would simply be built once and linked into the final executable.

Using your example, we can mark juce_gui_basics as INTERFACE on libCommonControls, and that solves this issue for libCommonControls, but CMake then causes a direct dependency in MyPlugin on juce_gui_basics, so the cpp is still compiled into MyPlugin and MyPlugin_Standalone

This is a slightly contrived example, but in my org we have many CMake libraries already and we are wrestling with this issue.