CMake: juce_add_module and modules containing pre-built libraries

The JUCE module format allows modules to contain pre-built libraries and adding such a module to a Projucer-managed project automatically sets the required linker flags to link to the library found in the module.

I now tried using such kind of module in a CMake based project like that:

juce_add_module (Ext/module_with_lib)

juce_add_plugin (Plug - all the flags here –)

target_link_libraries (Plug PRIVATE
    # JUCE Modules
    juce::foo
    juce::bar

    # My module
    module_with_lib

    # Recommended flags
    juce::juce_recommended_lto_flags
    juce::juce_recommended_warning_flags
    juce::juce_recommended_config_flags)

… and get a linker error because the lib is not found. I somehow assumed that adding the library from inside the module correctly is handled under the hood through some CMake & JUCE magic, but it doesn’t seem so. So what is missing here to make it work in a clean and cross platform style? And what about adding some lines on how to do that for other CMake beginners to the CMake Api docs?

Quick update, I got it working by adding

LINK_DIRECTORIES (Ext/module_with_lib/libs/MacOSX/x86_64)

after the juce_add_module line, but I would have to add the platform specific path for MacOS/Windows/Linux seperately. Can’t this be handled automatically inside juce_add_module?

1 Like

You’re right, the CMake support doesn’t currently support prebuilt libraries in modules. Unfortunately, as specified, the prebuilt library feature wouldn’t work well on Windows. According to the module format document, the module is allowed to hold multiple versions of the precompiled library, one for each type of runtime library (debug on/off and static/dynamic).

In the Projucer, it’s simple to select appropriate library search paths inside the module, based on the runtime that is selected for the project. However, in CMake, the module may be linked into multiple targets, each of which may use a different runtime library. As a result, we can’t set up the search paths when initially configuring the module. The best we can do is to create multiple targets for the module (one per runtime), and then let users pick which module variant to link based on the runtime that they want to use. This is kind of surprising, both to users accustomed to the Projucer and CMake, which is why such a feature hasn’t been added.

If you don’t mind my asking, what’s your reason for wanting this feature? Is it important that this particular module is compatible both with CMake and the Projucer? Are there alternative approaches that might work, such as adding the lib to your CMake build and building it from source, or adding it as an IMPORTED target?

Thank you @reuk for the detailed answer once again :slight_smile: I think I roughly understood the problem so far…

In the current state I see both CMake and traditional Projucer-based projects out there. For an example, we now try to use CMake for new projects but reworking existing more complex projects completely from a stable Projucer-based setup to a CMake based one seems quite time consuming and dangerous.

Now if there is some piece of shared in-house codebase that should work with both, the old Projucer-based projects and the new CMake-based projects, we need to find a format that fits both use-cases and the JUCE module format seems to be the easiest way to target both.

So I’m not sure what would be the best approach here, especially as my CMake knowledge is still very limited :wink:

I see. If you’re trying to ensure compatibility between CMake and Projucer projects, the simplest approach is likely to be something like what you’re already doing.

If you’re targeting CMake 3.15+, I’d suggest calling target_link_directories(my_module INTERFACE module/internal/path) after the call to juce_add_module. On Windows, you would need to pick the internal path to use based on the same predicate that determines the MSVC_RUNTIME_LIBRARY for your targets. If you’re not using the same MSVC_RUNTIME_LIBRARY in all targets, you may need to add some ‘wrappers’ for your module which set up the internal paths. That might look a bit like this:

juce_add_module(my_module)

add_library(my_module_MT INTERFACE)
target_link_libraries(my_module_MT INTERFACE my_module)
target_link_directories(my_module_MT INTERFACE my_module/libs/VisualStudio2019/x64/MT)

add_library(my_module_MTd INTERFACE)
target_link_libraries(my_module_MTd INTERFACE my_module)
target_link_directories(my_module_MTd INTERFACE my_module/libs/VisualStudio2019/x64/MTd)

# Now, depending on whether we're building in debug, we can link
# either my_module_MT or my_module_MTd
1 Like