Native, built-in CMake Support in JUCE

At the moment, Android Studio support is still Projucer-only. Android Studio projects are managed by Gradle, which invokes CMake as a sort of ‘sub-build’, and there’s not a straight-forward way of having CMake generate the outer Gradle project.

If you have an existing Android Studio project, it should be possible to use JUCE’s CMake support to create a shared library target which is included in the Gradle project as an externalNativeBuild item. Unfortunately, not all JUCE features will work out-of-the-box with this approach (some features require supporting Java code which is normally generated by the Projucer, but which would have to be manually copied using this approach), but this may be acceptable for your use-case depending on the features you actually require for your project.

What would be the best way to have a custom name for each target (AU, VST, AAX) ? (for compatibility purposes with older build)

I thought that using
set_target_properties(${PROJECT_NAME}_VST PROPERTIES OUTPUT_NAME "vstname")
would do the trick but it doesn’t.

Or maybe it should and it’s a bug.
$<TARGET_FILE_DIR:${PROJECT_NAME}_VST>
print the right new path though

Any idea ?

Thanks !

Well, it looks like you need to set as well XCODE_ATTRIBUTE_PRODUCT_NAME.

Humm it’s trickier because the plist is malformed in that case so it will work fine on Windows but not on OSX.
Do you know if any host use the actual bundle name to reload a session ?
Still this would be annoying for the installer.

Would be great if there could be a custom name for each plugin format :smiley:

Here’s my way of doing it.

Instead of creating the plugin target directly, you create function that creates the plugin, and then you can call it each time with a new name and format:

project(MinimalPluginTemplate VERSION 0.0.1)

set (SharedCodeTarget MinimalPluginTemplate)

set (sharedSources
        Source/PluginProcessor.cpp
        Source/Helpers/AudioProcessorBase.cpp)

function (create_plugin_target PluginTarget Format)
    juce_add_plugin("${PluginTarget}"
            COMPANY_NAME "MyCompany"
            IS_SYNTH FALSE
            NEEDS_MIDI_INPUT TRUE
            NEEDS_MIDI_OUTPUT FALSE
            IS_MIDI_EFFECT FALSE
            EDITOR_WANTS_KEYBOARD_FOCUS FALSE
            COPY_PLUGIN_AFTER_BUILD TRUE
            PLUGIN_MANUFACTURER_CODE Juce
            PLUGIN_CODE MIpl
            FORMATS ${Format}
            PRODUCT_NAME "${PluginTarget}")

    target_compile_definitions(${PluginTarget}
            PUBLIC
            JUCE_WEB_BROWSER=0
            JUCE_USE_CURL=0
            JUCE_VST3_CAN_REPLACE_VST2=0)

    target_sources(${PluginTarget} PUBLIC ${sharedSources})

    target_link_libraries(${PluginTarget} PRIVATE
            juce_audio_utils
            juce_recommended_config_flags
            juce_recommended_lto_flags
            juce_recommended_warning_flags)

endfunction()

create_plugin_target("MyPlugin" VST3)
create_plugin_target("MyPluginNewName" Standalone)
create_plugin_target("MyPluginUniqueName" AU)

I wouldn’t mind a cleaner solution but at least it does the trick.

Happily I only have one plugin that does this on OSX.
On PC I suspect that OUTPUT_NAME will be enough.

Thanks a lot !

You can generate the gradle project with running cmake the first time (using configure_file to create the needed bits and pieces), then the same cmake can be invoked by android studio and controlled by a flag (so you know if you are generating the gradle files or actually running from within android studio). This works nicely for us.

Invoke cmake normally (not really important the generator):

cmake -G "Unix Makefiles" \
  -DJUCE_ANDROID_APPLICATION_ID=com.your.app \
  -DJUCE_ANDROID_COMPILE_SDK_VERSION=25 \
  -DJUCE_ANDROID_MIN_SDK_VERSION=14 \
  -DJUCE_ANDROID_TARGET_SDK_VERSION=25 \
  -DJUCE_ANDROID_ABI="armeabi-v7a" \
  -DJUCE_ANDROID_ARM_NEON=TRUE \
  -DJUCE_ANDROID_TOOLCHAIN=clang \
  -DJUCE_ANDROID_PLATFORM=android-14 \
  -DJUCE_ANDROID_STL=c++_static
  ../

In the juce cmake CMakeLists.txt:

if(ANDROID AND NOT JUCE_COMPILE_GRADLE)
    juce_prepare_gradle_android()
else()
    # normal juce stuff
endif()

The juce_prepare_gradle_android is more or less:

function(juce_prepare_gradle_android)
  # Eventually set defaults here
  # Then generate gradle
  configure_file(${ROOT_CMAKE_DIR}/files/android/build.gradle.in ${CMAKE_CURRENT_LIST_DIR}/build.gradle)
  configure_file(${ROOT_CMAKE_DIR}/files/android/settings.gradle.in ${CMAKE_CURRENT_LIST_DIR}/settings.gradle)
  configure_file(${ROOT_CMAKE_DIR}/files/android/module.build.gradle.in ${CMAKE_CURRENT_LIST_DIR}/android/build.gradle)
  configure_file(${ROOT_CMAKE_DIR}/files/android/gradlew.in ${CMAKE_CURRENT_LIST_DIR}/gradlew COPYONLY)
  configure_file(${ROOT_CMAKE_DIR}/files/android/gradlew.bat.in ${CMAKE_CURRENT_LIST_DIR}/gradlew.bat COPYONLY)
  configure_file(${ROOT_CMAKE_DIR}/files/android/gradle.properties.in ${CMAKE_CURRENT_LIST_DIR}/gradle.properties COPYONLY)
endfunction()

The interesting part is the module.build.gradle.in:

apply plugin: 'com.android.application'

android {
    compileSdkVersion @JUCE_ANDROID_COMPILE_SDK_VERSION@

    defaultConfig {
        applicationId "@JUCE_ANDROID_APPLICATION_ID@"
        minSdkVersion @JUCE_ANDROID_MIN_SDK_VERSION@
        targetSdkVersion @JUCE_ANDROID_TARGET_SDK_VERSION@
        versionCode @JUCE_ANDROID_VERSION_CODE@
        versionName "@JUCE_ANDROID_VERSION_NAME@"

        ndk {
            abiFilters @JUCE_ANDROID_ABI@
        }

        externalNativeBuild {
            cmake {
                arguments "-DANDROID_TOOLCHAIN=@JUCE_ANDROID_TOOLCHAIN@",
                    "-DANDROID_ARM_NEON=@JUCE_ANDROID_ARM_NEON@",
                    "-DANDROID_PLATFORM=@JUCE_ANDROID_PLATFORM@",
                    "-DANDROID_STL=@JUCE_ANDROID_STL@",
                    "-DANDROID_CPP_FEATURES=@JUCE_ANDROID_CPP_FEATURES@",
                    "-DANDROID_ALLOW_UNDEFINED_SYMBOLS=FALSE",
                    "-DANDROID_PIE=ON",
                    "-DCPP_STD_VERSION=@JUCE_CPP_STD_VERSION@",
                    "-DENABLE_ADDRESS_SANITIZER=@JUCE_ENABLE_ADDRESS_SANITIZER@",
                    "-DENABLE_THREAD_SANITIZER=@JUCE_ENABLE_THREAD_SANITIZER@",
                    "-DJUCE_COMPILE_GRADLE:BOOL=ON"
            }
        }
    }

    externalNativeBuild {
        cmake {
            buildStagingDirectory "../build/android"
            path "../CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:25.4.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
}

Eventually juce can allow subclass your own .in files in case you want to do special configs.

Humm in fact I have an issue because the actual plugin name do not contain the suffix. Only the bundle does…

@reuk The plist is generated at configuration time or build time ?
Because I can’t update XCODE_ATTRIBUTE_PRODUCT_NAME afterwards otherwise it seems it’s not taken into account in the plist.

Maybe the plist should use xcode preprocessing for those final steps ?

I’m confused, are you talking about the plugin name as it’s shown to the host? You can pass that via the function as well.

This could indeed help a lot.

Thanks !