JUCE CMake - Add juce modules files to IDE projects

I’m in the process of adding CMake support to pluginval.
I have pretty much everything working now except one minor niggle is that the JUCE module sources aren’t added to the IDE projects in a browsable way.

This is a bit of a pain to not be able to browse JUCE but when you’re working on a module, not having those sources available is pretty impossible.

Is there a way to add back the files like the PJ does?

4 Likes

Yes, this to me is the most annoying issue in the current (really great!) CMake implementation.

In Visual Studio, opening a generated project makes it impossible to browse the files, because VS immediately switches you back to ‘native cmake’ mode when you try doing that, which then exposes the missing features in that mode like not being able to debug a custom target (like VST).

I think the same thing happens in XCode. Only in Jetbrains IDEs (CLion/AppCode) I’m able to browse the source files correctly, which is good for my personal workflow but problematic for others.

P.S. IIRC, @McMartin has solved that problem well in his FRUT library, so he might be able to help with implementation?

I believe all that’s needed is adding all files under a module into the source group of the module during juce_add_module(), but excluding the files that have the same name as the module (such as juce_osc.cpp).

What you can do is add all the files from the modules and set all those (besides the main module.cpp .h and .mm) as

set_source_files_properties(${MODULES_FILES} PROPERTIES HEADER_FILE_ONLY TRUE)

Thanks for the heads up. This is useful info for my own code.
I think that this will need to be done internally by the JUCE module cmake code though as if the JUCE files change or are restructured anything I do will fall out of sync.

If we could have an option to the juce_add_gui_app or similar to add the module source files to the IDE projects that would be ideal.

1 Like

I’ve experimented with adding nicer source grouping a couple of times, and the main issue I’ve run into is that source grouping only seems to work when the source_group call happens in the same directory as the creation of the target using the groups. The same is true for properties on source files (the docs say “source file properties are visible only to targets added in the same directory”). Unfortunately the juce_add_module call will normally happen in its own directory, so neither adding a source_group, nor setting HEADER_FILE_ONLY TRUE on module sources from within juce_add_module will have any effect.

A possible alternative would be to wrap target_link_libraries with some extra machinery which tries to check whether we’re linking against a JUCE module and adds source groups at that point. I’m not really happy with that approach though - adding a whole new function to the API (along with a property on module targets to identify them as JUCE modules) just to enable source grouping feels like overkill, and I’d really like to keep the JUCE CMake API looking as much like ‘plain CMake’ as possible.

In short, I’d like to add this feature, but I’m not sure the solutions I’ve come up with so far are worth the trade-off in terms of the complexity they would introduce.

Hmm, tricky. Would it be possible to expose the list of source files that the juce modules adds as a variable?

Then maybe we can add them directly ourselves and use the set_source_files_properties call to add them to our own CMakeLists.txt file?

Something like:

target_sources(${APP_NAME} PRIVATE 
    ${JUCE_CORE_MODULE_FILES}
    ${JUCE_CRYPTOGRAPHY_MODULE_FILES})

set_source_files_properties(${JUCE_CORE_MODULE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE)
set_source_files_properties(${JUCE_CRYPTOGRAPHY_MODULE_FILES} PROPERTIES HEADER_FILE_ONLY TRUE)

Or some more concise variant?

I don’t think you need a custom function - just add all the sources for all possible modules during juce_add_xxx. It doesn’t matter if you’re linking against that module or not.

1 Like

I could have a look at adding a JUCE_MODULE_HEADER_FILES property to modules which would contain all the module files that weren’t added as TUs. I’ll check how viable that would be (at the moment the module sources contain generator expressions, but I’m not sure whether they’re strictly required any more, so this might just work…).

This is not as straightforward as it sounds. We can’t know about all possible modules at this point (what if the user has added their own modules?). Even then, we would need some property on each module describing which files are safe to mark as header-only. If we mark all files header-only at this point CMake will just skip building the modules entirely.

It sounds to me like the main missing piece is some way of retrieving files that are ‘part of a module’ even though they’re not directly built, so I’ll try to add something like that.

The only parts in a module that are built are named exactly as the module name, sometimes with a format extension like in juce_audio_plugin_client - that’s by design and well defined in the module API AFAIK.

You can write a simple text search function to filter those out.

As for custom modules - those are added (or needed to be added) anyway before the call to juce_add_xxx, and that part can be added to documentation, otherwise other things might not work either.

There are other rules (for example, we never build .mm files on windows or linux), and some modules (like juce_audio_plugin_client) break the established rules. Duplicating this logic feels like the wrong approach (if nothing else, doing the same module globbing in two different places is twice as expensive as necessary).

I’m not sure about this. Having functions behave differently depending on where they’re called is not intuitive behaviour. This would also require having some kind of global module registry, which is not something I really want to add to the API. Users would inevitably come to depend on this module registry variable, which would in turn affect our implementation flexibility in the future.

Uhm… not really an expert in CMake here, but maybe a viable route is one that results in the creation of a dummy target/project in the IDE, one that’s not intended to be built but simply to contain the source code of modules.

That way, it wouldn’t be a problem whether the “main” juce_module_xxx.* files are also present in that target/project, nor whether the sources are marked to be header only or not

All these rules all follow the one rule which is “only files named module_name_xxx.* are a candidate for building”. Just glob everything and remove all candidates without specifying the system specific rules which really don’t matter for this particular feature.

The way JUCE is built is unintuitive no matter how you look at it, and the module system is as unintuitive as it gets.

However, there’s nothing unintuitive about declaring the shared libraries you use (modules) before you declare the plugins that use them, that’s just common sense.

The ‘intuitiveness’ advantages of simple browsable module sources without additional user intervention (such as custom function or specifying each module you’re using - most of them the user isn’t even aware of) outweigh the disadvantage of needing to declare a library before the targets that use it, in my humble opinion.

I’m not really a fan of globbing based on file name, that sounds like a recipe for future disaster to me if the module format ever does change.

Perhaps a better question would be how do other CMake based projects that have unity builds deal with this?


My first experience with CMake was last week so my knowledge is very poor in this area but I guess my ideal workflow would be to simply tell CMake to add all the files in a directory and then tell it that these files are not to be built.

Hopefully, doing that before any of the JUCE stuff wouldn’t stop those files being built. But maybe this is an orthogonal question (one motivation for this is that it would make creating my own CMakeLists.txt files much quicker).

The idea is that the globbing part logic (really looking at all files under a directory as you mentioned) is done by JUCE, not the user.

If the module format changes in the future, the globbing function would then change to work with the new format. As long as your code calls juce_add_module (or it’s called by JUCE with the built in modules) and you later add a JUCE target by using juce_add_xxx, that process will be executed.

It’s important to mention that the entire module format is not very CMake/IDE friendly.
Ideally modules would be replaced by CMake interface targets - in that case the IDE would know all about the internals of the module, which would also significantly improve build times because you could avoid the massive dependencies in the module system.

I’ve added an option which should hopefully fix this issue:

The solution is pretty much what @yfede suggested above, so thanks for that suggestion!

2 Likes

Thank you so much @reuk for putting an effort into it!

So far I’ve tested it in an Xcode generated project and it looks great.
For future reference, I needed to add the following to my top level CMakeLists.txt, which is OK but personally I think should be opt-out rather than opt-in:

#Adds all the module sources so they appear correctly in the IDE
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
option(JUCE_ENABLE_MODULE_SOURCE_GROUPS "Enable Module Source Groups" ON)

P.S. I know this is more of ‘my use case’ territory, but the current implementation only adds the sources to the ‘global’ (top) target.

That means that if I have many nested targets (such as a “superbuild” CMakeLists.txt that joins many projects together), the generated projects of the individual plugins don’t show the modules, and I only see them when I’m opening the ‘master’ target.

It would be great to have the option to at least optionally add the module sources to each plugin ‘shared code’ target which is how the Projucer does it.

Cool, glad it’s working! I chose to make the feature opt-in as it isn’t zero-cost - it makes projects bigger, and may slow down configuration a bit too.

Also, to work really well, users need to manually add the set_property call in their own CMakeLists. If this was made the default behaviour, users who don’t currently have the set_property call in their top-level CMakeLists would get a bunch of extra top-level folders that they may not want, which wouldn’t be very friendly.

I don’t think this is feasible at the moment. Using the current approach, this would add loads of dummy targets (number of modules * number of targets) which would become unwieldy very quickly. These targets show up in other places in the IDE, and I doubt users would be happy to sift through lists containing hundreds of no-op targets.

Good point. That’s why I preferred the Projucer/FRUT approach that just added all modules as sources to each plugin shared code target VS adding each module as individual targets.

Comparing projects in Visual Studio, it also seems like VS Intellisense is much happier with the Projucer approach as it can parse the module sources in context better.

Checking the VS generated projects more thoroughly, it seems like Intellisense just can’t parse anything in JUCE in this configuration ,even though it builds fine.

The project throws out tons of false positives, can’t go to definition, etc. Even turning on ReSharper C++ doesn’t seem to save the day here.

Using both the Projucer generated version and (surprisingly) generating a VS project from the CLion-exporter of the Projucer gives great Intellisense results. The native VS exporter obviously looks much cleaner and easier to browse.