Adding a New Compiler

This section will show how to start writing a filter to generate a basic CCT. See Auto CCTs for more information.

If the compiler is a GNU based compiler or similar to one already implemented, start by copying the filter file for an existing filter. The locations are covered in this topic. See Working with Auto CCT Scripts for more information about the configuration file that selects which filter file to use for a given process name.

There are some fundamental items required to generate a CCT:

Built-in Defines

Each compiler will define some symbols automatically. For example, well known ones are __WIN32 and __GNUC__. Most compilers will have a way of obtaining the list. Auto CCT will default to using the GNU options to ask the compiler to list all the defines (and include paths):

gcc -Wp,-v -Wp,-dM -E <filename>

Running that command will produce a long list of lines starting with #define which show what defines the compiler adds automatically. This list is parsed and added to the generated CCT.

Other compilers will require different options and may produce a different output format. To create the CCT, this information must be found in the compiler manual or by other means.

If there is no simple option to produce the list, the read_defines function can be used to obtain a list by other means such as a hard coded list. Refer to The filter_init Function and Filter Setup for more details.

Include Paths

The generated CCT also requires the list of folders that contain the compilers headers, such as stdio.h or iostream. In the case of GNU, the same command that lists the defines can also list the includes so the Auto CCT system defaults to working with one result and parsing the includes and defines from that output.

If your compiler requires a different set of options, this can be catered for in the read_includes function. Refer to The filter_init Function and Filter Setup for more details.

Version

The CCT will have the compiler version number in it for information. The version is obtained by running the compiler with the parameter provided in version_options and parsed from the resulting output with version_re. Refer to The filter_init Function and Filter Setup for more information.

Once this is known, a very basic CCT can be generated. As an example, this filter implements the basic functionality mentioned above for the Microchip xc8 compiler. Note that it produces the defines in a different format to GNU so read_defines was written to read the Microchip xc8 format. It uses a different format to GNU for include files also. There's a bit of boiler plate code to put in place - the CCT data class which will be covered later and a Configurator class which is required to set the _generates_cip_include value to True. This last class is required because we are finding the includes in the filter rather than relying on the CIP generation stage. There are two properties in the Configurator class that must be defined which just provide some information to the CIP file for display purposes.

import shlex

from emb import debug

from qa_cctdata import CCTData
from qa_gnu_kwinject_api import *
from qa_non_msvs_configurator import QANonMSVSConfigurator

import qa_util


class XC8CCTData(CCTData):
    def __init__(self):
        super(XC8CCTData, self).__init__()

    @property
    def size_type_map(self):
        return {}


class QAxc8Configurator(QANonMSVSConfigurator):
    def __init__(self, *args, **kwargs):
        super(QAxc8Configurator, self).__init__(*args, **kwargs)
        self._generates_cip_includes = True

    @property
    def target(self):
        return qa_util.get_option(r'--chip=([a-zA-Z0-9_]+)', self.conf.path)

    @property
    def hierarchy(self):
        return 'Microchip'


def _read_defines(buf, *discard_args):
    debug("xc8 read_defines")
    defines = []
    for line in buf.splitlines():
        m = re.match(r'.*(\[.*\])', line)
        if m:
            params = m.group(1)[1:-1]
            for opt in shlex.split(params):
                if opt[:2] == '-D':
                    if '=' in opt:
                        defines.append(opt)
                    else:
                        defines.append(opt + '=')
            return defines
    return defines


def _read_includes(buf, compiler_path, *discard_args):
    debug("xc8 read_includes " + str(compiler_path) + " " + buf)
    include_lines = []
    include_flags = ['-I']
    for line in buf.splitlines():
        if any(x in line for x in include_flags):
            for param in shlex.split(line):
                debug("checking " + param)
                for include_flag in include_flags:
                    if param.startswith(
                        include_flag
                    ) and param not in QAEnvStorage.get_command_args(compiler_path):
                        include_lines.append(param[len(include_flag) :])
    unique_include_lines = []
    for include in include_lines:
        if include not in unique_include_lines:
            unique_include_lines.append(include)
    return unique_include_lines


filter_init(
    "Microchip XC8",
    version_re=r'V(\d+)\.(\d+)',
    version_options=['--help'],
    info_args=['-V', '-V', '--pass1'],
    read_defines=_read_defines,
    read_includes=_read_includes,
    configurator_cls=QAxc8Configurator,
    cctdata_selector={'c': XC8CCTData(), 'c++': None},
)

Using the Auto CCT sync method with this filter configured will produce a CCT file that has the list of include folders and the list of built-in defines. However, and unknown exception error will be reported because the CIP (Compiler Include Paths) generation will fail. This is because no CIP script has been specified.

CIP Generation

Prior to Auto CCT, the CIP script would locate the compilers include folders. The script is ran just before analysis. With Auto CCT, it makes more sense to do that work in the filter as shown here. The CIP script is still required as it will add project specific include locations to the information passed to the parsers. This information is not compiler specific so the same CIP script can be used for Auto CCT generated CCTs. To add the CIP script to the CCT, the following is added to the XC8CCTDataC class:

@property
def masterscript_include(self):
    return 'DATA/autocct/Script/master_script.py'

With the CIP generation script now specified, the generation of the CCT completes without error and analysis of basic code will work.

Compiler Options

There was no work required for Microchip as it is GNU based and Auto CCT generation will recognise GNU options by default. For other compilers, a Visitor class must be defined and a list of the compiler options specifying their parameter type is required. The qa_tasking_filter.py has an example of a Visitor class. The first part of the class is shown here:

class QATaskingVisitor(QAVisitor):
    _aliases = {
        'include_file': ['H', 'include-macros-file'],
    }

    def __init__(self):
        debug("QATaskingVisitor::__init__()")
        super(QATaskingVisitor, self).__init__()
        # Same parser_options as GNU but without -ex dollar -
        self._qac_parser_options = {'bits': False, 'u': False}
        self._qacpp_parser_options = {'bits': False, 'ifn': False, 'sig': False, 'sep': True}

    def visit_include_file(self, option):
        # "Forced include" support.
        self._set_option_always('fi', '"' + self.convert_to_posix_like(option.args[0]) + '"')

The aliases setting at the start allows -H or -include-macros-file to work as a forced include file option. They will both cause the visit_include_file function to be called.

The constructor defines some default values for the option dependent part of the CCT. A very simple one is the u flag which controls the signedness of the char type in C. See the Perforce QAC for C component manual for other settings.

A list of the options that Auto CCT can process has to be specified. For example, this is the list for the Tasking filter:

class TaskingOptions(QAOptions):
    def _get_options(self):
        return {
            'I': [1],
            'include_path': [1],
            'D': [1],
            'define': [1],
            'include-file': [1],
            'include-macros-file': [1],
            'H': [1],
            'user-defined-literals': [0],
            'nullptr': [0],
            'no-nullptr': [0],
            'no-auto-storage': [0],
            'signed-bitfields': [0],
            'ignore-std': [0],
            'friend-injection': [0],
            'no-dep-name': [0],
            'no-arg-dep-lookup': [0],
        }

The number in the square brackets indicates the number of parameters that the option takes. Each option will invoke a function of the same name in the Visitor class but with visit_ added to the start of the name.

Stub Files

As with the shipped CCTs, there is often a need for include files that emulate compiler features or built-in functions. The location of the folder can be specified in the additional_include function:

@property
def additional_includes(self):
    return 'DATA/autocct/Stub/Tasking_C'
Testing

To make sure that the CCT is complete, a good test is to analyse a file that includes all the compiler headers. This will often show problems due to non standard extensions that the compiler supports.

Other Settings

There are many more settings required to get a fully functional CCT. For example, the size_type_map function should be filled out to specify the sizes of various standard types. As mentioned earlier, use the existing filters as examples of how to add additional settings and the documentation on the Auto CCT classes for further information.