Linking to juce modules from CMake modules cause linker errors on template class

I have a project setup using Modern CMake like module system. It can be compiled for both an ESP32 and VST plugins.

To allow for DSP code to be efficient on the ESP32, I’m using an abstraction module called abstract_dsp. This is implemented with juce_dsp for the plugins.

I’ve linked the abstract_dsp module to juce_dsp like this:

add_library(abstract_dsp
            abstract_dsp_juce.cpp)

target_include_directories(abstract_dsp PUBLIC include)

target_link_libraries(abstract_dsp PRIVATE juce::juce_dsp)

set_target_properties(abstract_dsp PROPERTIES
    POSITION_INDEPENDENT_CODE TRUE)

This looks like it works, but I’ve been reading this thread. It seems linking directly to the juce target from my modules is not recommended, due to how JUCE modules work. However, if I try to use the workaround (link to juce_dsp using INTERFACE), I run into linking issues because of class templates.

juce::dsp::IIR::Coefficients<float> is used inside abstract_dsp, and causes linking errors. The errors go away if I add a new dummy member to my PluginProcessor with this declaration: juce::dsp::IIR::Coefficients<float> dummy;. Seems like using INTERFACE linking prevents the specialised class being created?

/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_biquad_design_lowpass':
abstract_dsp_juce.cpp:(.text+0x4a): undefined reference to `juce::dsp::IIR::Coefficients<float>::makeLowPass(double, float, float)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_iir_set_coeffs':
abstract_dsp_juce.cpp:(.text+0x19a): undefined reference to `juce::dsp::IIR::Coefficients<float>::Coefficients(float, float, float, float, float, float)'
...

I’m not sure what’s going on, I’m also using juce::dsp::IIR::Filter<float> and juce::dsp::DelayLine<float> inside abstract_dsp and there are no linking errors related to these.

source code is here. Note that I’ve only updated plugins/chorus to use the workaround.

Any pointers on how to set this up correctly?

Oops, got a bit confused, I am getting linker errors for all the class templates in abstract_dsp. Here is the full list of errors:

[ 74%] Linking CXX executable "AudioPluginExample_artefacts/Debug/Standalone/Shrapnel Chorus"
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_biquad_design_lowpass':
effects/abstract_dsp/abstract_dsp_juce.cpp:19: undefined reference to `juce::dsp::IIR::Coefficients<float>::makeLowPass(double, float, float)'
/usr/bin/ld: effects/abstract_dsp/abstract_dsp_juce.cpp:22: undefined reference to `juce::dsp::IIR::Coefficients<float>::getFilterOrder() const'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_iir_set_coeffs':
effects/abstract_dsp/abstract_dsp_juce.cpp:51: undefined reference to `juce::dsp::IIR::Coefficients<float>::Coefficients(float, float, float, float, float, float)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_delayline_create':
effects/abstract_dsp/abstract_dsp_juce.cpp:88: undefined reference to `juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>::DelayLine(int)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_delayline_set_delay':
effects/abstract_dsp/abstract_dsp_juce.cpp:94: undefined reference to `juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>::setDelay(float)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_delayline_set_buffer_size':
effects/abstract_dsp/abstract_dsp_juce.cpp:104: undefined reference to `juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>::prepare(juce::dsp::ProcessSpec const&)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_delayline_push_sample':
effects/abstract_dsp/abstract_dsp_juce.cpp:109: undefined reference to `juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>::pushSample(int, float)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `dspal_delayline_pop_sample':
effects/abstract_dsp/abstract_dsp_juce.cpp:114: undefined reference to `juce::dsp::DelayLine<float, juce::dsp::DelayLineInterpolationTypes::Linear>::popSample(int, float, bool)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `juce::dsp::IIR::Filter<float>::Filter()':
plugins/JUCE/modules/juce_dsp/processors/juce_IIRFilter_Impl.h:58: undefined reference to `juce::dsp::IIR::Coefficients<float>::Coefficients(float, float, float, float)'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `juce::dsp::IIR::Filter<float>::reset(float)':
plugins/JUCE/modules/juce_dsp/processors/juce_IIRFilter_Impl.h:72: undefined reference to `juce::dsp::IIR::Coefficients<float>::getFilterOrder() const'
/usr/bin/ld: abstract_dsp/libabstract_dsp.a(abstract_dsp_juce.cpp.o): in function `juce::dsp::IIR::Filter<float>::check()':
plugins/JUCE/modules/juce_dsp/processors/juce_IIRFilter_Impl.h:237: undefined reference to `juce::dsp::IIR::Coefficients<float>::getFilterOrder() const'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/AudioPluginExample_Standalone.dir/build.make:245: AudioPluginExample_artefacts/Debug/Standalone/Shrapnel Chorus] Error 1
make[1]: *** [CMakeFiles/Makefile2:225: CMakeFiles/AudioPluginExample_Standalone.dir/all] Error 2
make: *** [Makefile:136: all] Error 2

I wonder whether you need to clean your build folder and regenerate the project. I’m not seeing any linker errors here.

I’m still seeing them after deleting the build folder and regenerating.

Are you on this commit?

commit 87490540dee4f1e3945a0d4a81a36a7dce5e7b29 (HEAD -> feature/correct-juce-module-linking, origin/feature/correct-juce-module-linking)
Author: Barabas Raffai <barabas.raffai@gmail.com>
Date:   Mon Jan 17 21:29:15 2022 +0000

    Update chrous and abstract_dsp cmake file

The master branch is using the incorrect style, which builds OK. I’m trying to get the recommended style working on this branch.

You’re right, I was building the wrong branch. I switched to the correct branch and it builds fine on macOS, but not on Linux.

I think the issue is caused by a quirk of ld, which allows it to discard object files from inside static libraries if no symbol in the object file is required at that point. Because of the order of libraries on the link line, the linker doesn’t see any unresolved references to juce::dsp symbols at the point where it links the plugin’s shared code library, so it discards that object file. Later, it links in the abstract_dsp library, which resolves references to previously-undefined abstract_dsp symbols, but introduces new unresolved references to juce::dsp symbols.

I could get the link to succeed by switching the plugin’s shared code library to be an OBJECT library instead of a STATIC library. I’ll have a think about making that change officially. I can’t think of any downsides at the moment, but then I missed the gotcha with STATIC libraries first time round…

Another possibility might be to bundle all of the JUCE modules that you’ll use into their own static library, then have abstract_dsp depend publicly on the JUCE staticlib, and have your plugin depend on abstract_dsp. I think this would allow CMake to get the library ordering correct, although I haven’t tested this approach yet.

3 Likes

The most seamless workflow for making a code module that depends on juce modules is to create a juce module of your own. If you make abstract_dsp into a juce module, and in its module header, declare juce_dsp as a dependency, then in your CMake you don’t have to do anything, the juce CMake code will handle all of this for you and it should “just work”.

If you’re going to stick with creating the abstract_dsp library in your own CMake code, I would advise against using the add_library command without a type keyword (STATIC, OBJECT, INTERFACE, SHARED, etc). I think it’s best to be explicit here.

1 Like

This doesn’t work if the abstract_dsp module is used by another module (gate), which is then finally used by the plugin. I’m getting the same linking errors. Also some of the juce modules are getting built in build/gate/CMakeFiles/gate.dir.

The static library approach is looking more promising, I’ve managed to make all my projects build with it. However, I see a potential issue still, my build folder contains a build of juce_audio_plugin_client_utils module outside of the juce static library. All the other modules are in the static library folder as expected.

Is it going to cause any issues?

I’ve pushed to the same branch, latest commit is e526a87.

No, I don’t expect that to cause problems.