How-To's Tips & Tricks

Custom Compilers in CLion: A Quick Guide to Using an Arbitrary Compiler in CLion

Read this post in other languages:

CLion is an IDE that offers a lot of features to help developers. It shows documentation popups, detects mistakes on the fly, suggests fixes, and more. An internal code analyzer always works in the background, analyzing C and C++ code as you type. C and C++ are challenging languages for automatic analyzers because compiler-specific data is required to parse the code correctly. The engine needs to know the header search paths, predefined macro definitions, and some other details.

For a predefined set of known compilers, CLion runs the project’s compiler with special options and gathers the required information while parsing the project. Of course, that only works for a limited number of compilers, like GCC, Clang, MSVC, IAR, and their derivatives. If a project uses a specific custom compiler, a rarely used one, or a proprietary compiler that we don’t have access to in order to integrate it properly, then CLion can’t work with it via its standard procedures. In that case, you have the option of using the Custom compiler feature.

To use a custom compiler with CLion, you’ll need a YAML file containing the necessary compiler data. Such a file should contain one or more sections, each of which describes a particular compiler or compiler variant. Each section should have a description, one or more matching records, and several informational records.

When CLion parses a project and encounters a compiler for a project file, CLion scans the sections one by one and checks whether the compiler matches the records. The matching is done using one or more matching records. The available matching records include:

  • match-compiler-exe is a regexp, and the compiler name is checked against it. A good platform-independent example is "(.*/)?sdcc(.exe)?".
  • match-source is a regexp too, and it is matched against the name of the source file to be compiled. Usually it is ".*\\.c" for C, and ".*\\.cpp" for C++.
  • match-args is a single command line switch, a sequence of switches, or an unordered array of switches. For example, "-a" matches the argument -a, "-b -c" matches the arguments -b -c, and ["-b", "-c"] matches either -b -c or -c -b.
  • match-language may be C or CPP. For a CMake project, it is matched against the language detected by CMake; for other types of projects, the statement is simply ignored.

Once all of the matches are resolved successfully, the section information records are picked, and the rest of the YAML file is skipped. Compiler data is taken from informational records and then passed directly to CLion’s code analyzer; that is, the data is not collected during the compiler dry run.

If none of the sections provides information for the IDE to positively match the records to a specific compiler, it tries to collect the compiler data in its usual way – by guessing the compiler’s type and running the compiler.

How to write a custom compiler YAML file

To get started writing the custom compiler YAML file, you’ll need to identify the following details about your compiler:

  • The languages that you want supported. Every supported language or language variant requires at least one configuration section.
  • The compiler binary names. There can be more than one. Some toolchains have different compiler binaries for C and C++, different memory models, language variants, and so on. This information can be found in the toolchain documentation and has to be reflected in your custom compiler YAML file.
  • Directories where the standard include files are located. To identify these directories, read the compiler’s documentation or simply explore the installed toolchain folder tree.
  • The list of predefined macros. Some compilers can report a list of their predefined macros, while others can’t. Refer to your compiler’s documentation to learn how to get them for your compiler.

Practical example

Let’s put this process into practice by writing a custom compiler YAML file for Small Device C Compiler (SDCC). SDCC supports several 8-bit CPU architectures such as STM8, Z80, and others. The compiler is open-source, and it’s the only free compiler for several MCU families. In this example we’ll write a YAML file for its Microchip PIC-16 variant (port).

Here are the details about this compiler that we’ll need to know:

  • Only the C language is supported.
  • The compiler binary name is sdcc or sdcc.exe (under Windows).
  • The -mpic16 option is mandatory for PIC16 architecture support, and --use-non-free should be used, as well.
  • To report a list of the header search paths, the compiler should be run with the --print-search-dirs option.
  • To report predefined macros, the compiler should be run with the -E and -dM options and an empty source file.

We’ll start by creating a test project. The very minimal project contains a C file, the YAML file itself, and a build system file. We’ll use Makefile. If C++ is supposed to be used, then another source file written in C++ should be added, as well.

First of all, create a project folder and create an empty Makefile inside.

Open the folder in CLion and then click Cancel on the Load Project dialog that pops up.

Open Makefile in the CLion editor and add the minimal content into that Makefile:

clean:
    all: main.c
    sdcc -mpic16 --use-non-free -S main.c

The file now contains a single compilation command for main.c into assembly with no further steps.

Create a main.c file. Let’s make a classic “Hello, World” with additional SDCC-specific additions – the __code keyword and the predefined macro __SDCC_pic16.

#include <stdio.h>
int main() {
    printf("Hello, World!\n");
    return 0;
}
int __code i = 0;
#ifndef __SDCC_pic16
#  error "__SDCC_pic16 macro is expected to be defined"
#endif

Now let’s write a custom compiler config stub:

  1. Create a file named clion-custom-compiler-sdcc-pic16.yaml.
  2. Assign a JSON schema, “Custom Compiler Definition”, to the file.
    Custom compiler scheme validatedThis way, CLion can help you with on-the-fly file structure validation and typing hints. The schema name is shown in the IDE status bar and can be selected by clicking on the name.

The stub is:

compilers:
 - description: SDCC for PIC-16
   match-compiler-exe: "(.*/)?sdcc(.exe)?"

For now, we only match by name. It is a regular expression matching both Linux and Windows compiler binary names regardless of the containing folder.

The next step is to use our custom compiler configuration in CLion. Open Settings/Preferences | Build, Execution, Deployment | Toolchains | Custom Compiler. Check Use custom compiler config and select the project’s YAML file as the config file.

Custom compiler settings

In the Tools menu select Makefile | Clean and Reload Makefile Project. If everything is OK, the Build tool window will show that the project has been loaded successfully.

Project loaded

If the project is not loaded correctly, you should double-check the match-compiler-exe statement in the YAML file. The record value is a regular expression, and it is the most fragile part of the process. Please refer to these examples in case of problems.

Now let’s check whether CLion has indeed picked up on SDCC and our project works as expected.

Check that the custom compiler works

Open main.c in the editor. It’s OK to see some errors at this point. Go to Help | Diagnostics Tools and select Show Compiler Info. This opens a diagnostics page with compiler information for the file being edited. Here you can see the compiler kind (“Custom Defined Compiler”) and the detected compiler description (“SDCC for PIC-16”). This means CLion is aware of the project structure, but the code analyzer doesn’t yet have the required compiler data and therefore shows various errors in main.c.

Custom compiler works

Collect missing compiler information

Our main.c file looks broken at the moment. stdio.h is not found, printf is undefined, the __code modifier is wrong, and no predefined macro from the documentation is defined.

stdio not found

Let’s fix all of that by providing the correct compiler information.

Fortunately, SDCC can print both header locations and predefined macros. Adding the following lines to Makefile will do the trick:

gather_info:
#   List directories
    sdcc -mpic16 --use-non-free --print-search-dirs
#   Create a void C file
    echo //void > void.c
#   List predefined macros
    sdcc -mpic16 --use-non-free -E -dM void.c

Now we need to build the gather_info target and look at the output. First, there is a list of header search paths:

…
includedir:
C:\Program Files\SDCC\bin\..\include\pic16
C:\Program Files\SDCC\bin\..\include
C:\Program Files\SDCC\bin\..\non-free\include\pic16
C:\Program Files\SDCC\bin\..\non-free\include
…

The only issue is that those paths are absolute paths. For portability reasons, let’s make them relative to the compiler location and add them to the compiler definition as an include-dirs array.

Next up are predefined macros. They are printed at the very bottom of the output. They can be added to the YAML file via defines-text.

Last but not least, we need to make our compiler matching a bit more specific by using match-args: -mpic16 and match-language: C statements, and then add SDCC language extension words as empty defines. Once we do, the final YAML file will look as follows:

compilers:
  - description: SDCC for PIC-16
    match-compiler-exe: "(.*/)?sdcc(.exe)?"
    match-args: -mpic16
    match-language: C
    include-dirs:
      - ${compiler-exe-dir}/../include/pic16
      - ${compiler-exe-dir}/../include
      - ${compiler-exe-dir}/../non-free/include/pic16
      - ${compiler-exe-dir}/../non-free/include
    defines-text: "
#define __SDCC_USE_NON_FREE 1
#define __SDCC_PIC18F452 1
#define __18f452 1
#define __STACK_MODEL_SMALL 1
#define __SDCC_pic16 1
#define __SDCC_ALL_CALLEE_SAVES 1
#define __STDC_VERSION__ 201112L
#define __STDC_HOSTED__ 0
#define __SDCCCALL 0
#define __STDC_UTF_16__ 1
#define __SDCC_VERSION_MINOR 2
#define __STDC_ISO_10646__ 201409L
#define __SDCC_VERSION_PATCH 0
#define __SDCC_VERSION_MAJOR 4
#define __STDC_NO_VLA__ 1
#define __SDCC 4_2_0
#define __STDC_UTF_32__ 1
#define __STDC_NO_THREADS__ 1
#define __SDCC_CHAR_UNSIGNED 1
#define __STDC_NO_ATOMICS__ 1
#define __STDC__ 1
#define __SDCC_REVISION 13081
#define __STDC_NO_COMPLEX__ 1

#define __interrupt
#define __code
#define __at
"

Now we can reload the project (Tools | Makefile | Reload Makefile Project) and check the main.c file again.

Parsing is working

The errors are gone, and it’s possible to navigate to stdio.h. The Show Compiler Info window displays the correct information – both predefined macros, language features, and header search paths.

This final YAML file can be found on GitHub.

That’s it! Try out the Custom Compiler feature and tell us how it worked for you. Pull requests to our compilers support collection are more than welcome!

image description