Adding Visual Studio Code code completion

I recently started to use CPM.cmake

It basically a cmake file you copy/paste into your repo and invoke with one line:

CPMAddPackage(
        NAME JUCE
        GITHUB_REPOSITORY juce-framework/JUCE
        GIT_TAG origin/develop)

It will fetch JUCE from git while configuring the project (so doesn’t require an additional git command or external tool), but unlike git submodules it can also be configured to share the same version of JUCE you have locally, while still making sure you will pull a valid version from git if you didn’t.

Here’s an example:

3 Likes

Oh looks nice. Much to learn about cmake and its possibilities on my side.

Has anyone managed to get VSCode to play nicely with JUCE modules?

With everything set up properly as described above, VSCode still fails to recognise JUCE’s unity-build style where module files are included in the top-level header, and don’t include their own dependencies.

For example, opening juce_ValueTree.h in VSCode shows that all the JUCE symbols are undefined (e.g. Identifier, UndoManager, etc.).

Anyone know how to config that properly? Searching for unity build on Google just gives a whole bunch of results about using the Unity Engine…

1 Like

I’m struggling with the same thing too.
For me, the symbols in header files resolve fine, but resolving the symbols in the implementation files fails.
I also tried using the compile_commands.json, but (obviously) the source files, e.g. juce_ValueTree.cpp isn’t found in the compile commands, since the file that is actually passed to the compiler is juce_data_structures.cpp.

I tried all relevant options for the VS Code C++ plugin (e.g. forced include sounded promising), but i didn’t succeed…

However I’ve noticed that the resolving works if i include the module header in the .cpp which might be a solution for own modules, but of course doesn’t work for the original juce modules.

@reuk is there something that can be done here?

I think this simply comes down to the fact that while JUCE uses a unity-build method, its .cpp files don’t include header files. From the few examples I could find, it’s still typical to include the required headers in a .cpp - when you merge the translation units, the included headers are still only processed once.

I think the only way to get VSCode (or rather intellisense, which I think is the thing that’s actually tripping up) to work properly with unity-builds would be to have all source files include their required headers.

As you say, this is an easy fix for our own modules, but would require a lot of work to go through the entire JUCE codebase to fix this for JUCE modules.

1 Like

Yep, I agree. It sounds like to get this working, we’d need to go through and update all of the JUCE module sources with the correct includes. This is something I’ve been considering on and off for a while, but it’s a change I don’t want to make without appropriate tooling to check that the includes are correct and minimal. I think such tools do exist, but getting them set up and integrated into CI will take some time. At the moment we have quite a few higher-priority issues to look at, so I don’t see this happening in the short term.

3 Likes

Thanks for the clarification.
Would adding the appropriate includes also mean that JUCE will move away from the unity build scheme?

That would be an incredibly disruptive change, so it would be very unlikely to happen.

No need for compilicated toolings, as your compiler is enough.
Here’s how I’ve done it in my own modules and it works very well with VSCode and other IDEs:

Create a test project that links to a single module and then:

  1. Remove all the includes from the module header (juce_core.h, etc)
  2. Everything now will stop compiling.
  3. Start by slowly adding includes to each cpp until everything compiles again
  4. Only add to the main header file the headers that aren’t included in any other headers.

At this point verifying that the JUCE demos/unit tests compile should be enough to let you know you’ve done this correctly.

No, it’s not a breaking change. I’ve done this to all my modules (took a few hours of work for about 30 modules) and the ‘client’ code is unaffected if done correctly.

These steps don’t guarantee working code completion in VS Code, though. Because all of the .cpp files are built in one go, they will see transitive includes from .cpp files included earlier in the main module .cpp. Getting the module building does not guarantee that each inner .cpp actually includes all of the headers it requires in isolation.

Ideally we’d want some way of checking that each ‘inner’ .cpp file can build in isolation, but this would mean moving away from the unity build system (or at least having the option of a non-unity build that we can run in CI).

They are actually.

If you think of how VSCode and other text editors look at ‘regular’ C++ code, it can’t look at the cpp files because it doesn’t know much about them (those are connected to the build system). All it does is recursively parse the headers from the source file it’s looking at.

That’s why the steps I suggest will (do) work.
I’m happy to send a PR of doing it on juce_core, if you want to take a look.

That would be a breaking change indeed and isn’t needed for this. Testing that the entire module cpp file can build is just enough, as this is the how the module builds.

Switching to another type of build system might be useful for other things, but you don’t need to test a build system you don’t use, IMO, and code completion doesn’t care about the cpp files.

Maybe I’m missing something.

Imagine that we have this setup:

a.cpp, uses symbols from
| x.h
| y.h

b.cpp, uses symbols from
| x.h
| y.h
| z.h

module.cpp, contains
#include "a.cpp"
#include "b.cpp"

Now, we try building the module. We start by adding the x.h and y.h headers to a.cpp and rebuild. There are no build errors in a.cpp, but we see some undefined symbols in b.cpp. It looks like the z.h header is missing from b.cpp, but we don’t get any warning that x.h and y.h are also used in that file, because they already got included in a.cpp.

I think in this scenario, after running the steps you suggested, completion would still be broken in b.cpp, (it would only include z.h) even though the build would work.

Did I miss a step that would guarantee b.cpp would actually get the required x.h and y.h includes?

That’s technically true, but it would still be about 50x better than it is now, because headers including x.h will get correct completion.

So for users of that module, life is now better, and you can now iterate on edge cases like the ones you mentioned.

In most cases, it’s actually a.h that would require x.h, and a.cpp just includes a.h, so fixing that header would fix 99% of problems.

1 Like

As a quick example, I forked JUCE on the develop branch and added explicit includes to juce_osc. Took about 10 minutes to do.

juce_core would probably be a better candidate but it’s a tad more work so I’d only do it if it’s something that has a chance of getting accepted by the JUCE team.

Notice that as a start, all the cpps just blindly include “mycppname.h”, to try and minimize (even though not avoid completely) the issue brought above.

Most of the compile errors then were on dependencies between headers, that are then easily resolved, like making sure juce_OSCReceiver.h included juce_OSCBundle.h, which then included juce_OSCMessage, etc.

2 Likes

Worth bearing in mind the sheer number of native source files in JUCE - it’d be a real pain to have to manually go through on each platform adding the required includes to every single file. Also makes it difficult to maintain in the future as new files need to be included.

As @reuk said, the best way to tackle that would be with a tool (something like include-what-you-use) that can fail a pipeline if a file isn’t including the right headers.

@eyalamir Checked out your example there and it does play nicely with VSCode (after also adding the modules folder to the include paths in c_cpp_properties.json).

1 Like

Most of it would be the same.
If you look at my example, many of the stuff in the module header are just copied to another header, like juce_osc_common.h and that includes all the external native headers in a very copy-paste way, as that part shouldn’t change.

While having a tool that makes sure your code has “good C++” includes is a nice idea, it’s really such a boring change that doesn’t require the complication of searching and configuring a tool. It’s really the most straightforward copy/paste job and using the compiler to help you find the errors.

From a practical sense, I suspect looking for some tool here will make this change not happen, and not using a tool can make it happen in a day. So as a pragmatic person I vote for just doing that, and then searching for a tool when there’s actual time to invest in a ‘perfect’ solution.

Thanks!
I believe if you have an app that links with that module, it should know to add the module include path automatically to that json file if you’re using CMAKE_EXPORT_COMPILE_COMMANDS=1)

I had the same problem with AppCode and Resharper. In the end after I moaned at Jetbrains enough and filled in a few support tickets it all got fixed :slight_smile:

I mean, the lights dim slightly when starting the IDE, but after that it all works :slight_smile:

1 Like

+1 for using CPM over git submodules, particularly when you are maintaining your own fork of JUCE and actively working on it. With submodules I found they were higher maintenance as checking out a specific commit would put it in a detached head state, which is not what you want if you are actively making changes to your fork.

With CPM you can do a side-by-side local clone of your JUCE fork next to your project repo folder, and then you can freely work on your JUCE fork without any fuss.

1 Like