Using CMake flow to build libraries, Android apps

I just started to use the new JUCE 6 distribution.
Reading the CMake documentation, I did not see any instructions or example on:

  • Building Android application (I saw examples for iOS application)
  • Building libraries (static/dynamic)

Can someone point me to how I can do that?

1 Like

At the moment, the CMake support focuses on building plugins, gui apps, and console apps, using the JUCE ā€˜build everything directly into the final binaryā€™ model. Unfortunately we donā€™t have android or library examples, yet.

There are good reasons in both cases:

  • The Android/Gradle build system expects to ā€˜ownā€™ the build, and will itself invoke CMake. That means itā€™s not really possible to write a CMake-only Android build - Gradle is required too. Thereā€™s also a lot of stuff that the Projucer normally does (like modifying and copying boilerplate Java files into the Gradle project) that we canā€™t easily recreate from a CMake build, because that stuff needs to happen before Gradle is invoked. Itā€™s a bit of a circular problem, with no clear solution at the moment. That being said, it should be possible to create a new ā€˜nativeā€™ project which includes a JUCE-dependent shared library in its CMake portion. This leads onto JUCE-dependent librariesā€¦
  • Due to the way JUCE modules work, itā€™s important that every module sees exactly the same set of JUCE_MODULE_AVAILABLE_* flags during the build, which in turn means that the final set of JUCE modules that end up in the build must be known ahead of time. When building JUCE into a library, itā€™s very easy to run into situations where a JUCE module gets included twice in the build, sometimes with different flags, which is likely to introduce subtle bugs. We donā€™t really want to encourage this build model, because itā€™s too easy to get it wrong.

With that out of the way, it should be possible to build a JUCE staticlib using a setup a bit like this:

add_library(juce_plugin_modules STATIC)

target_link_libraries(juce_plugin_modules
    PRIVATE
        juce::juce_audio_utils
        juce::juce_dsp
    PUBLIC
        juce::juce_recommended_config_flags
        juce::juce_recommended_lto_flags
        juce::juce_recommended_warning_flags)

target_compile_definitions(juce_plugin_modules
    PUBLIC
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
    INTERFACE
        $<TARGET_PROPERTY:juce_plugin_modules,COMPILE_DEFINITIONS>)

target_include_directories(juce_plugin_modules
    INTERFACE
        $<TARGET_PROPERTY:juce_plugin_modules,INCLUDE_DIRECTORIES>)

set_target_properties(juce_plugin_modules PROPERTIES
    POSITION_INDEPENDENT_CODE TRUE
    VISIBILITY_INLINES_HIDDEN TRUE
    C_VISIBILITY_PRESET hidden
    CXX_VISIBILITY_PRESET hidden)

This will build the audio_utils and dsp modules (along with all their dependencies) into a staticlib, which exports the recommended warning/config/lto flags in its interface. Note that if you choose to use an approach like this, the staticlib should be the one and only source of JUCE modules in the project. That means, all juce-dependent targets in the project should link against the same juce_plugin_modules target, and they should avoid linking any additional JUCE modules directly.

2 Likes

Thanks for the detailed reply.

Regarding libraries: I would like to build libraries of my own code (not juce modules). My applications will use these libraries.
The projucer has static library project that could do that. How can this be replicated using the new CMke flow?

To create a custom library containing your own code, you can use the add_library function, which is a CMake built-in. A simple library might look like this:

# Tell CMake to add a static library target
add_library(custom_lib STATIC)
# Add sources to the target
target_sources(custom_lib PRIVATE
    src_0.cpp    src_0.hpp
    src_1.cpp    src_1.hpp) # etc.
# Tell CMake where our library's headers are located
target_include_directories(custom_lib PUBLIC
    "${CMAKE_CURRENT_SOURCE_DIR}")

If you plan to use the library in a plugin, you may also need to set the following properties on the library:

set_target_properties(custom_lib PROPERTIES
    POSITION_INDEPENDENT_CODE TRUE
    VISIBILITY_INLINES_HIDDEN TRUE
    C_VISIBILITY_PRESET hidden
    CXX_VISIBILITY_PRESET hidden)
1 Like

Thanks.
Last question: when building the GUI example using CMake, I notice that I see the JUCE splash screen on startup, even though I have an indie license.
How do I should set the project so this splash screen will not be shown?

If your license terms allow, you may add JUCE_DISPLAY_SPLASH_SCREEN=0 to your target_compile_definitions to hide the splashscreen.

Is there an example CMakeFile example to that can be used to build and Audio Application?
I tried starting from the GUI app, but I get many errors along the wayā€¦

There isnā€™t, but it shouldnā€™t be all too different to the gui app example. The AudioPluginHost also has a CMake build, so you could check that out to see how it works.

What kinds of errors are you seeing?

Sorry it took a bit of time to get back to you.
I was able to create an audio application using CMake. It took a bit.

Iā€™m building now quite a large application, and it makes sense to ma to partition it to several static libraries, test console apps, and the final application (this is the method I have been using a lot to develop large scale programs using other frameworks, like Qt.

I believe it would be very useful if the JUCE CMake could have additional commands to add static or dynamic library targets. Just using add_library() command, requires to add a lot of other CMake directives to allow it to build correctly and link agains the other applications.

Just a suggestionā€¦

I agree it would be great to simplify the flow of static library creation with Cmake.

I do have to say, @YairAtRoli, that developing using JUCE modules can achieve a similar flow to what youā€™re after - this is exactly how weā€™re structuring things and have quite a lot of modules as well as apps/plugins/tests that call functions from those shared libs.

Indeed, using JUCE modules (or user modules) can achieve the same thing. However, for my application, Iā€™m using proprietary (US Patented) algorithm we have developed. I assume that at a certain point, Iā€™ll have to use some third party developers/company to work on the UI (as Iā€™m not the greatest UI designer in the world ;-).
Having all the IP code as library, can enable that in a secure way (as I can not expose the source code).

So, what Iā€™d do in your case is to build libraries that link with your juce-based code but donā€™t export any of the JUCE functions.

@reuk correct me if Iā€™m wrong, but I think if youā€™re only exporting your own interface (and not JUCE) via DLL/static libs you shouldnā€™t have any problems, similar to how multiple DLL/VST files can live in a host environment while each one links to a different implementation of JUCE.

Am I right to assume that?

Yes, this is exactly what I want to do.
My libraries might use JUCE API, but the JUCE modules/API should only link to the final application.

Thatā€™s not what I meant - what youā€™re suggesting will not work (unless you use JUCE modules/interface libraries and expose your code).

What I think you should do is link your closed source part privately with JUCE, not exposing to the caller your internal library use.

Instead, only export your own functions like process() or calculateAlgorithm(). I believe that way you can accomplish what you wanted and hide code from your users (which could also link with a different version of JUCE if they want to).

1 Like

Can you suggest a CMake file that will do that (assuming my source files are: my_class1.h, my class1.cpp, my_class2.h, my_class2.cpp)?

I believe that this would be true for a dynamic library built with hidden symbol visibility. I think itā€™s untrue for static libraries. Static libraries only support visibility in the plain C++ sense (internal/external linkage), and the bulk of JUCEā€™s functions have external linkage. Statically linking two copies of JUCE into the same binary will result in ODR violations for this reason.

1 Like

The recipe at the top of this post for adding a static lib of juce modules looks very handy, but Iā€™m not sure how downstream dependencies should be configured w.r.t the juce headers. For example, if I create another static library which depends on juce_plugin_modules, how should I generate a juce header file for it? Generating a header works for juce_add_*-specific targets created using juce_generate_juce_header(), but if a target has been created with add_library(), itā€™s not possible to use juce_generate_juce_header(). My current CMake recipe for a static lib is:

add_library(my_juce_lib STATIC)

# try to initialise the target as a JUCE target; also used for the juce_add_* targets, probably not the right way to do it though
_juce_initialise_target(my_juce_lib)

# this does work, but it doesn't detect the dependencies from the juce_modules linker target, so the header doesn't #include any juce headers
juce_generate_juce_header(my_juce_lib)

target_sources(my_juce_lib PRIVATE mysrc.cpp)

target_include_directories(my_juce_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(my_juce_lib INTERFACE juce_modules)

My use case is I want to break my logic out from my GUI code into its own lib so I can write tests against the lib (using gtest), but also still use juce code in my lib. Itā€™s hard/impossible to do that if the logic is contained in the GUI app, because I canā€™t link the tests to an executable target.

Youā€™re right that itā€™s currently not possible to generate a JuceHeader for a library. Iā€™d recommend against directly calling any CMake function that starts with _juce - these should be treated as ā€œprivateā€ functions, and they might change or disappear between releases.

Could you just include the necessary module headers directly, instead of using a JuceHeader? If you are using the ProjectInfo declarations, perhaps you can isolate these dependencies to the app target itself, which can generate a JuceHeader.

Thanks for the reply. I think for now Iā€™m going to just try and remove JUCE from my lib targets, that seems like the easiest approach.

FWIW Iā€™ve got something like this working (staticlibs depending on JUCE) with the following sort of setup:

# Set up JUCE library
add_library(juce_plugin_modules OBJECT)

target_link_libraries(juce_plugin_modules
    PRIVATE
        juce::juce_audio_utils
        juce::juce_dsp
    PUBLIC
        juce::juce_recommended_config_flags
        juce::juce_recommended_warning_flags)

target_compile_definitions(juce_plugin_modules
    PUBLIC
        JucePlugin_Build_Standalone=1
        JUCE_STANDALONE_APPLICATION=JucePlugin_Build_Standalone
    INTERFACE
        $<TARGET_PROPERTY:juce_plugin_modules,COMPILE_DEFINITIONS>)

target_include_directories(juce_plugin_modules
    INTERFACE
        $<TARGET_PROPERTY:juce_plugin_modules,INCLUDE_DIRECTORIES>)

set_default_properties(juce_plugin_modules) # function defined elsewhere. enables PIC, hidden symbols

# Add dependent library
# Library includes JUCE module headers directly, rather than using a JuceHeader.h

add_library(foo STATIC)
target_sources(foo PRIVATE ${sources}) # sources set previously
target_include_directories(foo PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(foo PUBLIC juce_plugin_modules)
set_default_properties(foo) # function defined elsewhere. enables PIC, hidden symbols

# Do something similar for library 'bar'...

# Add plugin target

juce_add_plugin(plug
    # etc.
    )

target_sources(plug PRIVATE ${plug_sources})
target_include_directories(plug PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(plug PRIVATE foo bar)
target_compile_definitions(plug PUBLIC JUCE_VST3_CAN_REPLACE_VST2=0)
2 Likes