CMake creating dynamic linked library with JUCE

Hi,

I am trying to set up a dynamic linked library project with CMake. The challenge that I am running into is getting juce_generate_juce_header() to run. Seems like I would have to use one of juce_add_* functions before generating the header but there’s non provided that outputs a .dll or .dylib currently. Am I missing something? or am I doing something incorrectly? Here is a small snippet of what I have:

add_library (${TargetName} SHARED Source/Main.cpp)

juce_generate_juce_header(${TargetName})

Error: CMake Error at out/build/x64-Debug/deps/juce-src/extras/Build/CMake/JUCEUtils.cmake:875 (message):
1> [CMake] Target TestProject does not have a generated sources directory. Ensure it
1> [CMake] was created with a juce_add
* function

Thank you

3 Likes

i got this and it works:

cmake_minimum_required(VERSION 3.12)

set(PROJECT_NAME juce_shared_library)
project(${PROJECT_NAME})

add_subdirectory("JUCE")

function(juce_add_shared_library target)
    add_library(${target} SHARED)

    target_compile_definitions(${target} PRIVATE JUCE_STANDALONE_APPLICATION=1)
    _juce_initialise_target(${target} ${ARGN})
    _juce_set_output_name(${target} "$<TARGET_PROPERTY:${target},JUCE_PRODUCT_NAME>.so")
    set_target_properties(${target} PROPERTIES JUCE_TARGET_KIND_STRING "App")
endfunction()

juce_add_shared_library(${PROJECT_NAME}
    VERSION     0.0.1)

set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 14)

target_compile_definitions(${PROJECT_NAME} PRIVATE
    JUCE_DISPLAY_SPLASH_SCREEN=0
    JUCE_ALLOW_STATIC_NULL_VARIABLES=0
    JUCE_LOG_ASSERTIONS=1
    JUCE_STRICT_REFCOUNTEDPOINTER=1)

target_link_libraries(${PROJECT_NAME} PRIVATE
    juce::juce_analytics
    juce::juce_audio_basics
    juce::juce_audio_devices
    juce::juce_audio_formats
    juce::juce_audio_processors
    juce::juce_audio_utils
    juce::juce_core
    juce::juce_cryptography
    juce::juce_data_structures
    juce::juce_dsp
    juce::juce_events
    juce::juce_graphics
    juce::juce_gui_basics
    juce::juce_gui_extra
    juce::juce_opengl
    juce::juce_osc
    juce::juce_video
    juce::juce_recommended_config_flags
    juce::juce_recommended_lto_flags
    juce::juce_recommended_warning_flags)

juce_generate_juce_header(${PROJECT_NAME})

hope this helps

1 Like

At the moment, the CMake support is centred around plugin, gui-app, and console-app targets, so we don’t have an out-of-the-box way to build JUCE shared libraries at the moment. What’s your end goal? Do you definitely need a shared library, specifically?

I’d also advise against using the JuceHeader in new projects. The original function of the JuceHeader was to include the AppConfig file before all of the JUCE module sources, but in CMake builds there’s no need for the AppConfig (all settings are injected directly via the build system) and therefore no need for the JuceHeader, either.

CMake functions starting with _juce are ‘private’ and do not form part of JUCE’s public API. These functions are liable to change without warning in future releases, so they should be avoided in user code. Additionally, writing to properties that begin with JUCE_ should be avoided, unless those properties are specifically documented, as those properties might be changed/removed in the future.

1 Like

then you should provide a way to build juce as a shared library without enforcing it being a plugin. we have use cases, you know…

as on the use of _juce functions, that’s a good workaround for making the job done in the absence of you supporting it because of unknown reasons: and as any workaround, one have to live with the fact of it possibly breaking in the future. the properties you are referring to are just referenced from juce cmake functions.

There’s some discussion of the reasoning in this thread and also here. There are a lot of potential footguns when building JUCE modules into library targets, and I haven’t been able to come up with a solution that’s safe and generally applicable. Shared libraries are especially difficult to get right, because some plugin hosts also use JUCE. If you build a plugin which depends on a JUCE shared library with public symbols, it’ll be very likely to crash hard in a host using a different JUCE version, because there will be multiple different versions of JUCE symbols all in the same process. These kinds of issues are especially insidious because they can appear to work most of the time, but then fail if a plugin is loaded in specific hosts.

It’s difficult to design for use-cases we don’t know about! For example, I don’t understand why a JuceHeader would be desirable for a library target. When consuming a library, generally I’d want to keep my includes as granular as possible, rather than including every symbol in that library everywhere. That being said, an approach like that mentioned here should result in a usable JuceHeader-less library target (although you’ll need to replace STATIC with SHARED and change the visibility settings so that you can actually pick up symbols from the library). And to reiterate, I’d strongly advise against using such a library in a plugin target.

2 Likes

people have been been building shared libraries since the beginning of time. saying juce shouldn’t be build as shared library is a bit strange, plugins are shared libraries after all. imagine i have my own plugin format that juce does not have a way to build yet. imagine people have it’s own app, and needs juce as an opt in feature to extend their existing app (silly example, but i’ve been talking with someone with an already existing app in Qt that wanted to optionally extend it with the juce audio classes). i personally need it as a library to be loaded and used via other languages, and it needs to be a shared library or i need to provide my own recompiled interpreters with juce linked in statically, not exactly an agile approach.

Well, this got… weirdly antagonistic… but @kunitoki is right that there are users out there who have a use case for building shared libs out of JUCE code.

(Though that should be obvious, given that one of the old Projucer default project templates was “Dynamic Library: This project type is useful to create re-usable software libraries that build on top of JUCE. Use this for dynamic library linking.” If the JUCE team were under the impression that nobody was using that template, let me disabuse you of that notion.)

In fact, we’ve been building JUCE modules into a shared library for a number of years, now. Confusingly, though, the things @reuk cites as potential problems with shared libraries are exactly the things we avoid by building that way.

All of the JUCE code in our library is sequestered into its own, separate shared lib, “the audio library”. That gets built as a .so, .dll, or .dylib depending on the platform, and then exported along with the headers for the relevant modules. Consumers of the library (including our own parent video-processing library) link only with those headers and that shared audio library — they don’t directly consume the JUCE code themselves, so there are no versioning issues or ODR violations.

I suppose if we were using plugins, or a more modular design, we might run into trouble. But for the moment, we’re not, and all of the JUCE API gets nicely encapsulated into the audio library build.

Another reason a shared library is the only viable choice is that our primary application is written in Python (with a PyQt5 interface). We create a set of SWIG-generated bindings as an extension module for Python (and Ruby). The extension is dynamically loaded by the Python interpreter, and in turn is linked to the shared libraries — very much the situation @kunitoki described in their last comment.

(Though AFAIK not the same situation, so I guess there’s at least 2 sets of us out there with very similar needs.)

We’ve even been building with CMake for all of those years, already. So, I can tell you how at least one project did it, and across all three major desktop platforms (via MSYS2, on Windows):

set(JUCE_MODULES_PATH "${CMAKE_CURRENT_SOURCE_DIR}/JuceLibraryCode/modules" CACHE PATH
    "Location of the JUCE source code 'modules' directory")

# Default extension for source files
if(UNIX AND APPLE)
  set(SOURCE_EXTENSION "mm")
else ()
  set(SOURCE_EXTENSION "cpp")
endif()

# List of modules to build
set(JUCE_MODULES
	juce_audio_basics
	juce_audio_devices
	juce_audio_formats
	juce_core
	juce_data_structures
	juce_events )
# Convert to list of source files (extension based on OS)
foreach(j_module IN LISTS JUCE_MODULES)
	list(APPEND JUCE_SOURCES
	 
 JuceLibraryCode/include_${j_module}.${SOURCE_EXTENSION} )
endforeach()

add_library(audio SHARED ${JUCE_SOURCES} )

# Include header directories
target_include_directories(audio PUBLIC
	$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>
	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/JuceLibraryCode/modules>
	$<BUILD_INTERFACE:${JUCE_MODULES_PATH}>
	$<INSTALL_INTERFACE:include/audio> )

Followed by, eventually:

# Install Header Files
foreach(j_module IN LISTS JUCE_MODULES)
  install(DIRECTORY ${JUCE_MODULES_PATH}/${j_module}/
    DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/audio/${j_module}
    FILES_MATCHING PATTERN "*.h" )
endforeach()

# Install library
INSTALL(TARGETS audio
	LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
	ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
	RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )

It works well enough, but when I heard JUCE would be debuting CMake support in JUCE 6, I was very much looking forward to mothballing our own build in favor of just using the official tooling. However, as things stand it’s not clear to me whether that can happen, as the provided infrastructure feels very narrowly focused to me, and that focus seems to be on a very different set of user priorities than our own.

5 Likes