示例#1
0
def parseFunctionSourceFiles():

    function_xml_files = OrderedDict()

    for i, func_name in enumerate(gb.functions_done):

        # Check that this function has a source file and get the path
        if func_name['long_templ_args'] not in gb.function_file_dict.keys():
            continue
        function_source_dir, function_source_fname = os.path.split(
            gb.function_file_dict[func_name['long_templ_args']])
        function_source_path = os.path.join(cfg.src_files_to,
                                            function_source_fname)

        # Construct file name for xml file produced by castxml
        # xml_output_path = os.path.join(gb.boss_temp_dir, function_source_path.replace('../','').replace('/','_').replace('.','_') + '.xml' )
        xml_output_path = os.path.join(
            gb.boss_temp_dir, 'function_tempfile_' + str(i) + '_' +
            function_source_fname.replace('.', '_') + '.xml')

        # Construct file name for xml file produced by castxml

        # List all include paths
        # include_paths_list = [cfg.include_path] + cfg.additional_include_paths

        # Timeout limit and process poll interval [seconds]
        timeout = 300.
        poll = 0.2

        # Run castxml
        try:
            utils.castxmlRunner(function_source_path,
                                cfg.include_paths,
                                xml_output_path,
                                timeout_limit=timeout,
                                poll_interval=poll)
        except:
            raise

        # Add factory xml file to dict
        function_xml_files[func_name['long_templ_args']] = xml_output_path

    return function_xml_files
示例#2
0
        xml_output_path = os.path.join(
            gb.boss_temp_dir, 'tempfile_' + str(i) + '_' +
            input_file_short_name.replace('.', '_') + '.xml')

        # List all include paths
        # include_paths_list = [cfg.include_path] + cfg.additional_include_paths

        # Timeout limit and process poll interval [seconds]
        timeout = 600.
        poll = 0.2

        # Run castxml
        try:
            utils.castxmlRunner(input_file_path,
                                cfg.include_paths,
                                xml_output_path,
                                timeout_limit=timeout,
                                poll_interval=poll)
        except:
            print
            print "  The initial CastXML command failed. Some problems can be solved by simply specifying"
            print "  a different C++ compiler in the 'castxml_cc' option in %s. It is currently set to '%s'." % (
                input_cfg_path, cfg.castxml_cc)
            print
            sys.exit(1)

        # Append xml file to list of xml files
        xml_files.append(xml_output_path)

    #
    # END: Run castxml on input files
示例#3
0
def main():

    # print()
    # print()
    # print('  ========================================')
    # print('  ||                                    ||')
    # print('  ||  BOSS - Backend-On-a-Stick-Script  ||')
    # print('  ||                                    ||')
    # print('  ========================================')
    # print()
    # print()

    # Parse command line arguments and options
    usage_string = "Usage: %prog [options] <config module>"
    usage_string += "\n\n"
    usage_string += "<config module> refers to a config file configs/<config module>.py."
    usage_string += "\n\n"
    usage_string += "Example: '%prog example_1_234' will use the config file configs/example_1_234.py."

    parser = OptionParser(usage=usage_string, version="%prog 0.1")
    parser.add_option("-i",
                      "--castxml-cc-id",
                      dest="castxml_cc_id",
                      default="",
                      help="Set castxml-cc-id to ID.",
                      metavar="ID")
    parser.add_option("-c",
                      "--castxml-cc",
                      dest="castxml_cc",
                      default="",
                      help="Set castxml-cc to COMPILER.",
                      metavar="COMPILER")
    parser.add_option(
        "-l",
        "--list",
        action="store_true",
        dest="list_flag",
        default=False,
        help="Output a list of the available classes and functions.")
    parser.add_option("-g",
                      "--generate-only",
                      action="store_true",
                      dest="generate_only_flag",
                      default=False,
                      help="Stop BOSS after code generation step.")
    parser.add_option(
        "-t",
        "--types-header",
        action="store_true",
        dest="types_header_flag",
        default=False,
        help=
        "Generate loaded_types.hpp. (BOSS continues from a previous saved state, no other input required.)"
    )
    parser.add_option(
        "-r",
        "--reset-source",
        dest="reset_info_file_name",
        default="",
        help=
        "Reset source code that has been mangled by BOSS. Requires a RESET_INFO_FILE generated by BOSS.",
        metavar="RESET_INFO_FILE")
    parser.add_option(
        "-I",
        "--include",
        action="append",
        dest="cmdline_include_paths",
        help=
        "Add PATH to the list of paths used by CastXML to search for included header files.",
        metavar="PATH")
    # parser.add_option("-G", "--to-gambit",
    #                   dest="main_gambit_path",
    #                   default="",
    #                   help="Copy all the code needed by GAMBIT to the correct locations within the main GAMBIT path GAMBIT_PATH.",
    #                   metavar="GAMBIT_PATH")

    (options, args) = parser.parse_args()

    # Print banner
    msg = '''\033[1m

  ========================================
  ||                                    ||
  ||  BOSS - Backend-On-a-Stick-Script  ||
  ||                                    ||
  ========================================
    \033[0m'''
    # msg = utils.modifyText(msg,'bold')
    print(msg)

    # Check for conflicting options
    if options.generate_only_flag and options.types_header_flag:
        print()
        print('Conflicting flags: --generate-only and --types-header')
        print()

        sys.exit()

    # Check that arguments list is not empty
    if (len(args) < 1) and not (options.types_header_flag
                                or options.reset_info_file_name):

        print()
        print('Missing input arguments. For instructions, run: boss.py --help')
        print()

        sys.exit()

    # Check platform
    if not (sys.platform.startswith('linux') or sys.platform == 'darwin'):

        print()
        print('Platform "%s" is not supported.' % (sys.platform))
        print()

        sys.exit()

    # Check that CastXML is found and get the correct path
    # (system-wide executable or prebuilt binary in castxml/bin/castxml)
    boss_abs_dir = os.path.dirname(os.path.abspath(__file__))
    local_castxml_path = os.path.join(boss_abs_dir, "castxml/bin/castxml")
    has_castxml_system = True
    has_castxml_local = True
    try:
        subprocess.check_output(["which", "castxml"])
    except subprocess.CalledProcessError:
        print()
        print("Cannot find a system-wide 'castxml' executable.")
        print(
            "CastXML can be installed with 'apt install castxml' (Linux) or 'brew install castxml' (OS X)."
        )
        has_castxml_system = False
        print("Will now look for a prebuilt CastXML binary in %s" %
              (local_castxml_path))

    try:
        subprocess.check_output(["which", local_castxml_path])
    except subprocess.CalledProcessError:
        print()
        print("Cannot find 'castxml' binary in %s." % (local_castxml_path))
        print(
            "To get the prebuilt castxml binary for your system, download a tarball from "
        )
        print()
        print(
            "  https://midas3.kitware.com/midas/download/item/318227/castxml-linux.tar.gz (for Linux)"
        )
        print()
        print("or ")
        print()
        print(
            "  https://midas3.kitware.com/midas/download/item/318762/castxml-macosx.tar.gz (for OS X)"
        )
        print()
        print("and extract it in the main BOSS directory: %s/" %
              (boss_abs_dir))
        print()
        has_castxml_local = False

    # Quit if no version of CastXML is found
    if (not has_castxml_system) and (not has_castxml_local): sys.exit()

    # Get the config file name from command line. Import the correct config module.
    # If reset option is used, then skip this part and simply import configs.example_1_234.

    import modules.active_cfg as active_cfg
    if options.reset_info_file_name == '':

        # Get the config file name from command line input, unless reset option is used
        input_cfg_path = args[0]

        # Import the given config file as a module named 'cfg'.
        input_cfg_dir, input_cfg_filename = os.path.split(input_cfg_path)
        input_cfg_modulename = input_cfg_filename.rstrip('.py')

        active_cfg.module_name = input_cfg_modulename

    try:
        exec("import configs." + active_cfg.module_name + " as cfg", globals())
    except ImportError as e:
        print(
            "Failed to import config module '%s'. Are you sure the file %s exists?"
            % (input_cfg_modulename,
               os.path.join('configs', input_cfg_modulename + '.py')))
        print()
        sys.exit()

    import modules.gb as gb
    import modules.classutils as classutils
    import modules.classparse as classparse
    import modules.funcparse as funcparse
    import modules.funcutils as funcutils
    import modules.utils as utils
    import modules.filehandling as filehandling
    import modules.infomsg as infomsg

    # Write the result of the castxml checks to gb
    gb.has_castxml_system = has_castxml_system
    gb.has_castxml_local = has_castxml_local

    # Construct file name for the BOSS reset file to be created
    reset_info_file_name = 'reset_info.' + gb.gambit_backend_name_full + '.boss'

    # Convert all input and final output paths in the config file to absolute paths
    cfg.input_files = [(gb.boss_dir + '/' + x if not x.startswith('/') else x)
                       for x in cfg.input_files]
    cfg.include_paths = [
        (gb.boss_dir + '/' + x if not x.startswith('/') else x)
        for x in cfg.include_paths
    ]
    cfg.base_paths = [(gb.boss_dir + '/' + x if not x.startswith('/') else x)
                      for x in cfg.base_paths]
    if not cfg.header_files_to.startswith('/'):
        cfg.header_files_to = gb.boss_dir + '/' + cfg.header_files_to
    if not cfg.src_files_to.startswith('/'):
        cfg.src_files_to = gb.boss_dir + '/' + cfg.src_files_to

    # If castxml compiler setting are given as command line input,
    # update the variables in cfg
    if options.castxml_cc_id != '':
        cfg.castxml_cc_id = options.castxml_cc_id
    if options.castxml_cc != '':
        cfg.castxml_cc = options.castxml_cc

    # If additional include paths are given at the command line,
    # add them to the list given in cfg.
    if options.cmdline_include_paths:
        cfg.include_paths = cfg.include_paths + options.cmdline_include_paths

    #
    # If types_header_flag is True: Load saved variables, parse factory function files and generate loaded_types.hpp
    #
    if options.types_header_flag:
        with open('savefile.boss', 'rb') as f:
            gb.classes_done, gb.factory_info = pickle.load(f)

        print('(Continuing from saved state.)')

        print()
        print()
        print(
            utils.modifyText('Generating file loaded_types.hpp:', 'underline'))
        print()

        filehandling.createLoadedTypesHeader()

        print()
        print(utils.modifyText('Done!', 'bold'))
        print()

        sys.exit()

    #
    # If reset option is used: Run the reset function and then quit
    #
    if options.reset_info_file_name != '':
        print()
        print()
        print(utils.modifyText('Reset source code:', 'underline'))
        print()
        print('  Input file: ' + options.reset_info_file_name)
        print()

        filehandling.resetSourceCode(options.reset_info_file_name)

        sys.exit()

    #
    # Check if backend source tree has already been BOSSed. (Look for the backend_undefs.hpp header file.)
    #
    check_file = os.path.join(cfg.header_files_to, gb.gambit_backend_incl_dir,
                              'backend_undefs.hpp')
    if os.path.isfile(check_file):
        print()
        print(
            utils.modifyText(
                'The backend source tree seems to already have been BOSSed.',
                'yellow'))
        print()
        print(
            '  If you want to reBOSS the source tree you must first revert it to the original state with the command:'
        )
        print('  python boss.py -r ' + reset_info_file_name)
        print()

        sys.exit()

    # If the output directory is to be used, delete the current one if it exists.
    if (not options.list_flag) and (not options.types_header_flag):
        try:
            shutil.rmtree(gb.boss_output_dir)
        except OSError as e:
            if e.errno == 2:
                pass
            else:
                raise e

    #
    # Identify standard include paths
    #
    if cfg.auto_detect_stdlib_paths:
        print()
        print(
            utils.modifyText('Identifying standard include paths:',
                             'underline'))
        print()

        utils.identifyStdIncludePaths()

    #
    # Run castxml for all input header/source files
    #

    print()
    print(utils.modifyText('Parsing the input files:', 'underline'))
    print()

    # Create the temp output dir if it does not exist
    filehandling.createOutputDirectories(selected_dirs=['temp'])

    # Sort list of input files
    input_files = cfg.input_files
    input_files.sort()

    xml_files = []
    for i, input_file_path in enumerate(input_files):

        # Get path and filename for the input file
        input_file_dir, input_file_short_name = os.path.split(input_file_path)

        # Construct file name for xml file produced by castxml
        xml_output_path = os.path.join(
            gb.boss_temp_dir, 'tempfile_' + str(i) + '_' +
            input_file_short_name.replace('.', '_') + '.xml')

        # List all include paths
        # include_paths_list = [cfg.include_path] + cfg.additional_include_paths

        # Run castxml
        try:
            utils.castxmlRunner(input_file_path, cfg.include_paths,
                                xml_output_path)
        except Exception as e:
            print()
            print(
                "  The initial CastXML command failed. Some problems can be solved by simply specifying"
            )
            print(
                "  a different C++ compiler in the 'castxml_cc' option in %s. It is currently set to '%s'."
                % (input_cfg_path, cfg.castxml_cc))
            print()
            raise e
            sys.exit(1)

        # Append xml file to list of xml files
        xml_files.append(xml_output_path)

    #
    # END: Run castxml on input files
    #
    print()

    #
    # If -l option is given, printa list of all classes and functions, then exit.
    #

    if options.list_flag:

        all_class_names = []
        all_function_names = []

        for xml_file in xml_files:

            tree = ET.parse(xml_file)
            root = tree.getroot()

            # Set the global xml id dict. (Needed by the functions called from utils.)
            gb.id_dict = OrderedDict([(el.get('id'), el)
                                      for el in root.getchildren()])

            # Find all available classes
            for el in (root.findall('Class') + root.findall('Struct')):

                try:
                    class_name = classutils.getClassNameDict(el)
                except KeyError:
                    continue

                # Only list native classes
                if utils.isNative(el):
                    all_class_names.append(class_name['long_templ'])

            # Find all available functions
            for el in root.findall('Function'):

                try:
                    func_name = funcutils.getFunctionNameDict(el)
                except KeyError:
                    continue

                # Only list native functions
                if utils.isNative(el):
                    all_function_names.append(func_name['long_templ_args'])

        # END: Loop over xml files

        # Remove duplicates
        all_class_names = list(OrderedDict.fromkeys(all_class_names))
        all_function_names = list(OrderedDict.fromkeys(all_function_names))

        # Output lists
        print(utils.modifyText('Classes:', 'underline'))
        print()
        for demangled_class_name in all_class_names:
            print('  - ' + demangled_class_name)
        print()
        print(utils.modifyText('Functions:', 'underline'))
        print()
        for demangled_func_name in all_function_names:
            print('  - ' + demangled_func_name)
        print()

        # Exit
        sys.exit()

    #
    # Analyse types and functions
    #

    print(utils.modifyText('Analysing types and functions:', 'underline'))
    print()
    print('  (This may take a little while.)')
    print()
    #
    # Read all xml elements of all files and store in two dict of dicts:
    #
    # 1. all_id_dict:    file name --> xml id --> xml element
    # 2. all_name_dict:  file name --> name   --> xml element
    #
    utils.xmlFilesToDicts(xml_files)

    #
    # Look up potential parent classes and add to cfg.load_classes
    #

    if cfg.load_parent_classes:
        utils.addParentClasses()

    #
    # Remove from cfg.load_classes all classes that are not loadable (not found, incomplete, abstract, ...)
    #

    # Remove duplicates from cfg.load_classes
    cfg.load_classes = list(OrderedDict.fromkeys(cfg.load_classes))

    is_loadable = OrderedDict.fromkeys(cfg.load_classes, False)

    # Determine which requested classes are actually loadable
    for xml_file in xml_files:

        # Initialise global dicts
        gb.xml_file_name = xml_file
        utils.initGlobalXMLdicts(xml_file, id_and_name_only=True)

        # Loop over all named elements in the xml file
        for full_name, el in gb.name_dict.items():

            if el.tag in ['Class', 'Struct']:

                # If a requested class is loadable, set the entry in is_loadable to True
                if full_name in cfg.load_classes:

                    if utils.isLoadable(el, print_warning=False):
                        is_loadable[full_name] = True

    # Remove from cfg.load_classes those that are not loadable
    for full_name in is_loadable.keys():

        if not is_loadable[full_name]:

            cfg.load_classes.remove(full_name)

    # Output info on why classes are not loadable
    for xml_file in xml_files:

        # Initialise global dicts
        gb.xml_file_name = xml_file
        utils.initGlobalXMLdicts(xml_file, id_and_name_only=True)

        for full_name in is_loadable.keys():

            if not is_loadable[full_name]:

                # If the class exists, print reason why it is not loadable.
                # If the class is not found, say so.
                try:
                    el = gb.name_dict[full_name]
                except KeyError:
                    el = None

                if el is not None:
                    utils.isLoadable(el, print_warning=True)
                else:
                    reason = "Class not found."
                    infomsg.ClassNotLoadable(full_name, reason).printMessage()

    #
    # Fill the gb.parents_of_loaded_classes list
    #
    utils.fillParentsOfLoadedClassesList()

    #
    # Fill the gb.accepted_types list
    #
    utils.fillAcceptedTypesList()

    #
    # Remove from cfg.load_functions all functions that are not loadable
    #

    # Remove duplicates from cfg.load_functions
    cfg.load_functions = list(OrderedDict.fromkeys(cfg.load_functions))

    for xml_file in xml_files:

        # Initialise global dicts
        gb.xml_file_name = xml_file
        utils.initGlobalXMLdicts(xml_file, id_and_name_only=True)

        # Loop over all named elements in the xml file
        for full_name, el in gb.name_dict.items():

            if el.tag == 'Function':

                # Get function name
                try:
                    func_name = funcutils.getFunctionNameDict(el)
                    func_name_long_templ_args = func_name['long_templ_args']
                except KeyError:
                    func_name_long_templ_args = 'UNKNOWN_NAME'
                except:
                    print('  ERROR: Unexpected error!')
                    raise

                if func_name_long_templ_args in cfg.load_functions:

                    is_loadable = not funcutils.ignoreFunction(
                        el, limit_pointerness=True, print_warning=True)

                    if not is_loadable:

                        cfg.load_functions.remove(func_name_long_templ_args)

    #
    # Main loop over all xml files
    #

    # Check that we have something to do...
    if (len(cfg.load_classes) == 0) and (len(cfg.load_functions) == 0):
        print()
        print()
        print('  - No classes or functions to load!')
        print()
        print(utils.modifyText('Done!', 'bold'))

        sys.exit()

    print()
    print()
    print(utils.modifyText('Generating code:', 'underline'))

    for xml_file in xml_files:

        # Output xml file name
        print()
        print()
        print('  ' + utils.modifyText('Current XML file:', 'underline') + ' ' +
              xml_file)
        print()

        #
        # Initialise global dicts with the current XML file
        #

        gb.xml_file_name = xml_file
        utils.initGlobalXMLdicts(xml_file)

        #
        # Parse classes
        #

        classparse.run()

        #
        # Parse functions
        #

        funcparse.run()

        #
        # Create header with forward declarations of all abstract classes
        #

        abs_frwd_decls_header_path = os.path.join(
            gb.boss_output_dir, gb.frwd_decls_abs_fname + cfg.header_extension)
        utils.constrAbsForwardDeclHeader(abs_frwd_decls_header_path)

        #
        # Create header with forward declarations of all wrapper classes
        #

        wrp_frwd_decls_header_path = os.path.join(
            gb.boss_output_dir, gb.frwd_decls_wrp_fname + cfg.header_extension)
        utils.constrWrpForwardDeclHeader(wrp_frwd_decls_header_path)

        # #
        # # Create header with declarations of all enum types
        # #

        # enum_decls_header_path = os.path.join(gb.boss_output_dir, gb.enum_decls_wrp_fname + cfg.header_extension)
        # utils.constrEnumDeclHeader(root.findall('Enumeration'), enum_decls_header_path)

    #
    # END: loop over xml files
    #

    # Check that we have done something...
    if (len(gb.classes_done) == 0) and (len(gb.functions_done) == 0):
        print()
        print()
        print('  - No classes or functions loaded!')
        print()
        print(utils.modifyText('Done!', 'bold'))
        print()

        sys.exit()

    #
    # Clear global dicts
    #

    utils.clearGlobalXMLdicts()

    #
    # Write new files
    #

    # Create all output directories that do not exist.
    filehandling.createOutputDirectories()

    # File writing loop
    for src_file_name, code_dict in gb.new_code.items():

        add_include_guard = code_dict['add_include_guard']
        code_tuples = code_dict['code_tuples']

        code_tuples.sort(key=lambda x: x[0], reverse=True)

        new_src_file_name = os.path.join(gb.boss_output_dir,
                                         os.path.basename(src_file_name))

        if code_tuples == []:
            continue

        boss_backup_exists = False
        if os.path.isfile(src_file_name):
            try:
                f = open(src_file_name + '.backup.boss', 'r')
                boss_backup_exists = True
            except IOError as e:
                if e.errno != 2:
                    raise e
                f = open(src_file_name, 'r')

            f.seek(0)
            file_content = f.read()
            f.close()
            new_file_content = file_content
        else:
            new_file_content = ''

        if not boss_backup_exists and new_file_content:
            f = open(src_file_name + '.backup.boss', 'w')
            f.write(new_file_content)
            f.close()

        for pos, code in code_tuples:

            if pos == -1:
                new_file_content = new_file_content + code
            else:
                new_file_content = new_file_content[:
                                                    pos] + code + new_file_content[
                                                        pos:]

        # Add include guard where requested
        if add_include_guard:

            short_new_src_file_name = os.path.basename(new_src_file_name)

            try:
                prefix = code_dict['include_guard_prefix']
            except KeyError:
                prefix = ''

            new_file_content = utils.addIncludeGuard(
                new_file_content,
                short_new_src_file_name,
                prefix=prefix,
                suffix=gb.gambit_backend_name_full)

        # Do the writing!
        f = open(new_src_file_name, 'w')
        f.write(new_file_content)
        f.close()

    #
    # Copy files from common_headers/ and common_source_files/ and replace any code template tags
    #

    filehandling.createCommonHeaders()
    filehandling.createCommonSourceFiles()

    #
    # Move files to correct directories
    #

    filehandling.moveFilesAround()

    #
    # Run through all the generated files and use the code tags __START_GAMBIT_NAMESPACE__ and __END_GAMBIT_NAMESPACE__ to construct
    # the correct namespace.
    #

    construct_namespace_in_files = glob.glob(
        os.path.join(gb.backend_types_dir_complete, '*')) + glob.glob(
            os.path.join(gb.for_gambit_backend_types_dir_complete, '*'))

    filehandling.replaceNamespaceTags(construct_namespace_in_files,
                                      gb.gambit_backend_namespace,
                                      '__START_GAMBIT_NAMESPACE__',
                                      '__END_GAMBIT_NAMESPACE__')

    #
    # Run through all the generated files and remove tags that are no longer needed
    #

    all_generated_files = glob.glob(os.path.join(
        gb.boss_output_dir, '*')) + glob.glob(
            os.path.join(gb.backend_types_dir_complete, '*')) + glob.glob(
                os.path.join(gb.for_gambit_backend_types_dir_complete, '*'))
    remove_tags_list = [
        '__START_GAMBIT_NAMESPACE__', '__END_GAMBIT_NAMESPACE__',
        '__INSERT_CODE_HERE__'
    ]

    filehandling.removeCodeTagsFromFiles(all_generated_files, remove_tags_list)

    #
    # Copy files to the correct locations within the source tree of the original code
    #

    print()
    print()
    print(
        utils.modifyText('Copying generated files to original source tree:',
                         'underline'))
    print()

    manipulated_files, new_files, new_dirs = filehandling.copyFilesToSourceTree(
        verbose=True)

    # # Create the reset dir if it does not exist
    # filehandling.createOutputDirectories(selected_dirs=['reset'])

    # Save source_target_tuples to be able to undo the changes at a later time
    with open(reset_info_file_name, 'wb') as f:
        pickle.dump([manipulated_files, new_files, new_dirs], f)

    #
    # If generate_only_flag is True, save state and quit
    #

    if options.generate_only_flag:
        with open('savefile.boss', 'wb') as f:
            pickle.dump([gb.classes_done, gb.factory_info], f)
        print()
        print()
        print('Done with code generation. Program state saved.')
        print('To generate loaded_types.hpp, run: boss.py --types-header')
        print()
        sys.exit()

    #
    # Generate header file 'loaded_types.hpp'
    #

    print()
    print()
    print(utils.modifyText('Generating file loaded_types.hpp:', 'underline'))
    print()

    filehandling.createLoadedTypesHeader()

    #
    # Parse any source files for global functions using castxml
    #

    print()
    print()
    print(
        utils.modifyText('Parsing the generated function source files:',
                         'underline'))
    print()

    function_xml_files = filehandling.parseFunctionSourceFiles()

    #
    # Generate GAMBIT frontend header file ''
    #

    print()
    print()
    print(
        utils.modifyText('Generating GAMBIT frontend header file:',
                         'underline'))
    print()

    filehandling.createFrontendHeader(function_xml_files)

    #
    # Done!
    #

    print()
    print(utils.modifyText('Done!', 'bold'))
    print()
    print("  To prepare this backend for use with GAMBIT, do the following:")
    print()
    print(
        "    1. BOSS has added new source files to '%s' and new header files to '%s'."
        % (cfg.src_files_to, cfg.header_files_to))
    print("       Make sure that these are included when building '%s'." %
          (cfg.gambit_backend_name))
    print(
        "    2. Build a shared library (.so) from the '%s' source code that BOSS has edited."
        % (cfg.gambit_backend_name))
    print(
        "    3. Set the correct path to this library in the 'backends_locations.yaml' file in GAMBIT."
    )
    print(
        "    4. Copy the '%s' directory from '%s' to the 'backend_types' directory within GAMBIT."
        % (gb.gambit_backend_name_full,
           gb.for_gambit_backend_types_dir_complete))
    print(
        "    5. Copy the file '%s' from '%s' to the GAMBIT 'frontends' directory."
        % (gb.frontend_fname, gb.frontend_path))
    print()
    print()