Use Visual Studio Code with an autotools based C/C++ project

Willem de Jonge
6 min readApr 15, 2020

--

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.

--

--