Use Visual Studio Code with an autotools based C/C++ project
There is a huge number of existing projects using the GNU build system and for some new projects it will even be the tool of choice. When picking a good IDE to do C/C++ development on such a project choices seem to be limited. At least if you want a cross-platform experience. For example I’ve done a lot of development on Bitcoin Core derived projects, a large C++ code base with a complex autotools based build system. My main development platform is macOS, though with these projects I frequently need to switch to Linux or Windows. In search of a good modern cross-platfrom IDE for these projects I’ve been trying VSC, and I’m quite happy with the results so far. This article will describe how you can easily work on an autotools based project with VSC, including interactive debugging and build system aware IntelliSense.
If you don’t have VSC yet, download and install it. Next follow Using Clang in Visual Studio Code to install the C++ extension and get a “hello world” example running. You could of course skip the “hello world”, however it’s a great way to confirm that everything’s setup correctly. In addition the simple “hello world” can be nice for small code experiments, practise and challenges like on HackerRank.
Now for real real-worl C++ development there will be many source files, use of external libraries and complex build rules, all handled by the build system, autotools in this case.
First a minimallistic “hello autotools” example project is created. After that it is shown how to configure VSC to build, debug and get IntelliSense setup using a compilation database.
Hello autotools example project
Create a folder autotools-example
and create three files main.cpp
, configure.ac
and Makefile.am
as follows:
mkdir autotools-example
cd autotools-example
touch main.cpp
touch configure.ac
touch Makefile.am
code .
In the editor open main.cpp
and fill it with the "hello autotools" example below. For now ignore the ONLY_WITH_BUILDSYSTEM
define, that will be used to illustrate the IntelliSense later on.
// main.cpp
#include <iostream>
#include <vector>
#include <string>
#if defined(ONLY_WITH_BUILDSYSTEM)
using namespace std;
#endif
int main()
{
vector<string> msg {"Hello", "C++", "Autotools", "from", "VS Code", "and the C++ extension!"};
for (const string& word : msg)
{
cout << word << " ";
}
cout << endl;
}
Fill configure.ac
with the contents below:
# configure.ac
AC_INIT([helloworld], [1.0])
AM_INIT_AUTOMAKE([-Wall -Werror foreign])
AC_PROG_CXX
AX_CXX_COMPILE_STDCXX_17
AC_CONFIG_FILES([
Makefile
])
AC_OUTPUT
Finally fill Makefile.am
as follows:
# Makefile.am
bin_PROGRAMS = hello
hello_SOURCES = main.cpp
AM_CPPFLAGS = -DONLY_WITH_BUILDSYSTEM
You can now initialize and configure the build system using these commands in the terminal:
autoreconf --install
mkdir build
cd build
../configure CXXFLAGS="-g -O0"
The above commands are only need once. After this the project can be repeatedly build with make
. The CXXFLAGS="-g -O0"
is not strictly necessary to build, but it is needed because we want debug info to be included.
Configure Visual Studio Code to build with make
From the menu, choose Terminal > Configure Default Build Task. From the dropdown choose C/C++ clang++ build active file (this will only appear if you have the main.cpp
file currently open else you can choose Create tasks.json file from template and pick Other). We just do this as a quick way to create a tasks.json
file in the .vscode
folder so alternativly you can just create an empty file.
Open the tasks.json
and replace the contents with the following:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "make",
"command": "make",
"options": {
"cwd": "${workspaceFolder}/build"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
That’s it. You can now build your project from the menu using Terminal > Run Build Task…
Launching and debugging
Next you want to be able to launch your program and debug your code. From the menu choose Run > Add Configuration.. in the dropdown choose C++ (GDB/LLDB). Note this is just a quick way to create the needed launch.json
file, create an empty file if you prefer. Open that file in the editor. It will look something like this:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "enter program name, for example ${workspaceFolder}/a.out",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "lldb"
}
]
}
At the very least you want to set the actual program
to debug and also trigger a build first by setting preLaunchTask
.
After making these changes your launch.json
should look similar to this:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(lldb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/hello",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build",
"environment": [],
"externalConsole": false,
"MIMode": "lldb",
"preLaunchTask": "make"
}
]
}
You’re now ready to debug by choosing Run > Start Debugging from the menu. Remember that the build configuration was done using CXXFLAGS="-g -O0"
to get debug info. If there is no debug info you will not be able to interactivly debug your code, set breakpoints etc.
IntelliSense and autocompletion
Intelligent code completion is one of the key features in any modern programming environment. There is really no excuse not to have and use it. When programming you want to be thinking about what your code does and how it does it and not worry about spelling of function and variable names.
VSC comes with IntelliSense working out of the box for simple scenarios like “hello world”. When things get more complex it will fail because it doesn’t know what libraries are used, what defines affect your build etc.
Our autotools based example project is building and we can even debug it, however IntelliSense still has problems with our code. It reports 8 problems and shows a lot of squiggles where it can’t understand the code as shown below:
Obviously this is because it is unaware of the ONLY_WITH_BUILDSYSTEM
define set in the build system in Makefile.am
. This is solved by creating a compilation database that will have all the compiler options like defines, include paths etc. for each file in the project.
Configure VSC to use a compilation database in the c_cpp_properties.json
file. From the Command Palette (⇧⌘P) run C/C++: Edit Configurations (JSON) which will create the file if it does not exists yet and open it in the editor. Set the compileCommands
to the file with the compilation database like below:
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"macFrameworkPath": [
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}
Now the database itself still has to be generated.
Compilation database generation
Autotools does not have a built-in feature, like CMake for example, to generate the database. Instead an external package scan-build
can be used. From this package we use the intercept-build
tool. Install the package:
sudo pip install scan-build
Note: you can use a non-sudo install if you prefer but that will not make scan-build automatically available in your PATH.
Once the package is installed the build is configured from the terminal like before, but now using intercept-build
:
intercept-build ../configure CXXFLAGS="-g -O0"
And in the tasks.json
the intercept-build
is added to the make command:
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "make",
"command": "intercept-build make",
"options": {
"cwd": "${workspaceFolder}/build"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
Now build and voila, IntelliSense will now be build system aware, the errors and squiggles displayed will dissapear and autocomplete will find all libraries you include etc.
If you don’t get the IntelliSense right away and problems still appear when you open main.cpp
it is likely because your build didn't actaully build, and so no compile_commands.json
was generated (or an empty one). Force a rebuild by doing a make clean
before the build. Or it was generated, but VSC didn't load it, in that case touch c_cpp_properties.json
.
Note: on macOS because of System Integrity Protection (SIP) scan-build
cannot hook the compiler. It will use compiler wrappers instead. Information for these wrappers is passed through the environment. Because of this the intercept-build
has to be used for both the configuration step and the build step.
Note: for syntax highlighting when editting autotools releated files like .m4
, .am
and .am
I suggest you install the VSC extension autoconf.