• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2019 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""It is an AIDEGen sub task: generate the CLion project file.
18
19    Usage example:
20    json_path = common_util.get_blueprint_json_path(
21        constant.BLUEPRINT_CC_JSONFILE_NAME)
22    json_dict = common_util.get_soong_build_json_dict(json_path)
23    if 'modules' not in json_dict:
24        return
25    mod_info = json_dict['modules'].get('libui', {})
26    if not mod_info:
27        return
28    CLionProjectFileGenerator(mod_info).generate_cmakelists_file()
29"""
30
31import logging
32import os
33
34from io import StringIO
35from io import TextIOWrapper
36
37from aidegen import constant
38from aidegen import templates
39from aidegen.lib import common_util
40from aidegen.lib import errors
41from aidegen.lib import native_module_info
42
43# Flags for writing to CMakeLists.txt section.
44_GLOBAL_COMMON_FLAGS = '\n# GLOBAL ALL FLAGS:\n'
45_LOCAL_COMMON_FLAGS = '\n# LOCAL ALL FLAGS:\n'
46_GLOBAL_CFLAGS = '\n# GLOBAL CFLAGS:\n'
47_LOCAL_CFLAGS = '\n# LOCAL CFLAGS:\n'
48_GLOBAL_C_ONLY_FLAGS = '\n# GLOBAL C ONLY FLAGS:\n'
49_LOCAL_C_ONLY_FLAGS = '\n# LOCAL C ONLY FLAGS:\n'
50_GLOBAL_CPP_FLAGS = '\n# GLOBAL CPP FLAGS:\n'
51_LOCAL_CPP_FLAGS = '\n# LOCAL CPP FLAGS:\n'
52_SYSTEM_INCLUDE_FLAGS = '\n# GLOBAL SYSTEM INCLUDE FLAGS:\n'
53
54# Keys for writing in module_bp_cc_deps.json
55_KEY_GLOBAL_COMMON_FLAGS = 'global_common_flags'
56_KEY_LOCAL_COMMON_FLAGS = 'local_common_flags'
57_KEY_GLOBAL_CFLAGS = 'global_c_flags'
58_KEY_LOCAL_CFLAGS = 'local_c_flags'
59_KEY_GLOBAL_C_ONLY_FLAGS = 'global_c_only_flags'
60_KEY_LOCAL_C_ONLY_FLAGS = 'local_c_only_flags'
61_KEY_GLOBAL_CPP_FLAGS = 'global_cpp_flags'
62_KEY_LOCAL_CPP_FLAGS = 'local_cpp_flags'
63_KEY_SYSTEM_INCLUDE_FLAGS = 'system_include_flags'
64
65# Dictionary maps keys to sections.
66_FLAGS_DICT = {
67    _KEY_GLOBAL_COMMON_FLAGS: _GLOBAL_COMMON_FLAGS,
68    _KEY_LOCAL_COMMON_FLAGS: _LOCAL_COMMON_FLAGS,
69    _KEY_GLOBAL_CFLAGS: _GLOBAL_CFLAGS,
70    _KEY_LOCAL_CFLAGS: _LOCAL_CFLAGS,
71    _KEY_GLOBAL_C_ONLY_FLAGS: _GLOBAL_C_ONLY_FLAGS,
72    _KEY_LOCAL_C_ONLY_FLAGS: _LOCAL_C_ONLY_FLAGS,
73    _KEY_GLOBAL_CPP_FLAGS: _GLOBAL_CPP_FLAGS,
74    _KEY_LOCAL_CPP_FLAGS: _LOCAL_CPP_FLAGS,
75    _KEY_SYSTEM_INCLUDE_FLAGS: _SYSTEM_INCLUDE_FLAGS
76}
77
78# Keys for parameter types.
79_KEY_FLAG = 'flag'
80_KEY_SYSTEM_ROOT = 'system_root'
81_KEY_RELATIVE = 'relative_file_path'
82
83# Constants for CMakeLists.txt.
84_MIN_VERSION_TOKEN = '@MINVERSION@'
85_PROJECT_NAME_TOKEN = '@PROJNAME@'
86_ANDOIR_ROOT_TOKEN = '@ANDROIDROOT@'
87_MINI_VERSION_SUPPORT = 'cmake_minimum_required(VERSION {})\n'
88_MINI_VERSION = '3.5'
89_KEY_CLANG = 'clang'
90_KEY_CPPLANG = 'clang++'
91_SET_C_COMPILER = 'set(CMAKE_C_COMPILER \"{}\")\n'
92_SET_CXX_COMPILER = 'set(CMAKE_CXX_COMPILER \"{}\")\n'
93_LIST_APPEND_HEADER = 'list(APPEND\n'
94_SOURCE_FILES_HEADER = 'SOURCE_FILES'
95_SOURCE_FILES_LINE = '     SOURCE_FILES\n'
96_END_WITH_ONE_BLANK_LINE = ')\n'
97_END_WITH_TWO_BLANK_LINES = ')\n\n'
98_SET_RELATIVE_PATH = 'set({} "{} {}={}")\n'
99_SET_ALL_FLAGS = 'set({} "{} {}")\n'
100_ANDROID_ROOT_SYMBOL = '${ANDROID_ROOT}'
101_SYSTEM = 'SYSTEM'
102_INCLUDE_DIR = 'include_directories({} \n'
103_SET_INCLUDE_FORMAT = '    "{}"\n'
104_CMAKE_C_FLAGS = 'CMAKE_C_FLAGS'
105_CMAKE_CXX_FLAGS = 'CMAKE_CXX_FLAGS'
106_USR = 'usr'
107_INCLUDE = 'include'
108_INCLUDE_SYSTEM = 'include_directories(SYSTEM "{}")\n'
109_GLOB_RECURSE_TMP_HEADERS = 'file (GLOB_RECURSE TMP_HEADERS\n'
110_ALL_HEADER_FILES = '    "{}/**/*.h"\n'
111_APPEND_SOURCE_FILES = "list (APPEND SOURCE_FILES ${TMP_HEADERS})\n\n"
112_ADD_EXECUTABLE_HEADER = '\nadd_executable({} {})'
113_PROJECT = 'project({})\n'
114_ADD_SUB = 'add_subdirectory({})\n'
115_DICT_EMPTY = 'mod_info is empty.'
116_DICT_NO_MOD_NAME_KEY = "mod_info does not contain 'module_name' key."
117_DICT_NO_PATH_KEY = "mod_info does not contain 'path' key."
118_MODULE_INFO_EMPTY = 'The module info dictionary is empty.'
119
120
121class CLionProjectFileGenerator:
122    """CLion project file generator.
123
124    Attributes:
125        mod_info: A dictionary of the target module's info.
126        mod_name: A string of module name.
127        mod_path: A string of module's path.
128        cc_dir: A string of generated CLion project file's directory.
129        cc_path: A string of generated CLion project file's path.
130    """
131
132    def __init__(self, mod_info, parent_dir=None):
133        """ProjectFileGenerator initialize.
134
135        Args:
136            mod_info: A dictionary of native module's info.
137            parent_dir: The parent directory of this native module. The default
138                        value is None.
139        """
140        if not mod_info:
141            raise errors.ModuleInfoEmptyError(_MODULE_INFO_EMPTY)
142        self.mod_info = mod_info
143        self.mod_name = self._get_module_name()
144        self.mod_path = CLionProjectFileGenerator.get_module_path(
145            mod_info, parent_dir)
146        self.cc_dir = CLionProjectFileGenerator.get_cmakelists_file_dir(
147            os.path.join(self.mod_path, self.mod_name))
148        if not os.path.exists(self.cc_dir):
149            os.makedirs(self.cc_dir)
150        self.cc_path = os.path.join(self.cc_dir,
151                                    constant.CLION_PROJECT_FILE_NAME)
152
153    def _get_module_name(self):
154        """Gets the value of the 'module_name' key if it exists.
155
156        Returns:
157            A string of the module's name.
158
159        Raises:
160            NoModuleNameDefinedInModuleInfoError if no 'module_name' key in
161            mod_info.
162        """
163        mod_name = self.mod_info.get(constant.KEY_MODULE_NAME, '')
164        if not mod_name:
165            raise errors.NoModuleNameDefinedInModuleInfoError(
166                _DICT_NO_MOD_NAME_KEY)
167        return mod_name
168
169    @staticmethod
170    def get_module_path(mod_info, parent_dir=None):
171        """Gets the correct value of the 'path' key if it exists.
172
173        When a module with different paths, e.g.,
174            'libqcomvoiceprocessingdescriptors': {
175                'path': [
176                    'device/google/bonito/voice_processing',
177                    'device/google/coral/voice_processing',
178                    'device/google/crosshatch/voice_processing',
179                    'device/google/muskie/voice_processing',
180                    'device/google/taimen/voice_processing'
181                ],
182                ...
183            }
184        it might be wrong if we always choose the first path. For example, in
185        this case if users command 'aidegen -i c device/google/coral' the
186        correct path they need should be the second one.
187
188        Args:
189            mod_info: A module's info dictionary.
190            parent_dir: The parent directory of this native module. The default
191                        value is None.
192
193        Returns:
194            A string of the module's path.
195
196        Raises:
197            NoPathDefinedInModuleInfoError if no 'path' key in mod_info.
198        """
199        mod_paths = mod_info.get(constant.KEY_PATH, [])
200        if not mod_paths:
201            raise errors.NoPathDefinedInModuleInfoError(_DICT_NO_PATH_KEY)
202        mod_path = mod_paths[0]
203        if parent_dir and len(mod_paths) > 1:
204            for path in mod_paths:
205                if common_util.is_source_under_relative_path(path, parent_dir):
206                    mod_path = path
207        return mod_path
208
209    @staticmethod
210    @common_util.check_args(cc_path=str)
211    def get_cmakelists_file_dir(cc_path):
212        """Gets module's CMakeLists.txt file path to be created.
213
214        Return a string of $OUT/development/ide/clion/${cc_path}.
215        For example, if module name is 'libui'. The return path string would be:
216            out/development/ide/clion/frameworks/native/libs/ui/libui
217
218        Args:
219            cc_path: A string of absolute path of module's Android.bp file.
220
221        Returns:
222            A string of absolute path of module's CMakeLists.txt file to be
223            created.
224        """
225        return os.path.join(common_util.get_android_root_dir(),
226                            common_util.get_android_out_dir(),
227                            constant.RELATIVE_NATIVE_PATH, cc_path)
228
229    def generate_cmakelists_file(self):
230        """Generates CLion project file from the target module's info."""
231        with open(self.cc_path, 'w') as hfile:
232            self._write_cmakelists_file(hfile)
233
234    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
235    @common_util.io_error_handle
236    def _write_cmakelists_file(self, hfile):
237        """Writes CLion project file content with neccessary info.
238
239        Args:
240            hfile: A file handler instance.
241        """
242        self._write_header(hfile)
243        self._write_c_compiler_paths(hfile)
244        self._write_source_files(hfile)
245        self._write_cmakelists_flags(hfile)
246        self._write_tail(hfile)
247
248    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
249    @common_util.io_error_handle
250    def _write_header(self, hfile):
251        """Writes CLion project file's header messages.
252
253        Args:
254            hfile: A file handler instance.
255        """
256        content = templates.CMAKELISTS_HEADER.replace(
257            _MIN_VERSION_TOKEN, _MINI_VERSION)
258        content = content.replace(_PROJECT_NAME_TOKEN, self.mod_name)
259        content = content.replace(
260            _ANDOIR_ROOT_TOKEN, common_util.get_android_root_dir())
261        hfile.write(content)
262
263    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
264    @common_util.io_error_handle
265    def _write_c_compiler_paths(self, hfile):
266        """Writes CMake compiler paths for C and Cpp to CLion project file.
267
268        Args:
269            hfile: A file handler instance.
270        """
271        hfile.write(_SET_C_COMPILER.format(
272            native_module_info.NativeModuleInfo.c_lang_path))
273        hfile.write(_SET_CXX_COMPILER.format(
274            native_module_info.NativeModuleInfo.cpp_lang_path))
275
276    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
277    @common_util.io_error_handle
278    def _write_source_files(self, hfile):
279        """Writes source files' paths to CLion project file.
280
281        Args:
282            hfile: A file handler instance.
283        """
284        if constant.KEY_SRCS not in self.mod_info:
285            logging.warning("No source files in %s's module info.",
286                            self.mod_name)
287            return
288        root = common_util.get_android_root_dir()
289        source_files = self.mod_info[constant.KEY_SRCS]
290        hfile.write(_LIST_APPEND_HEADER)
291        hfile.write(_SOURCE_FILES_LINE)
292        for src in source_files:
293            if not os.path.exists(os.path.join(root, src)):
294                continue
295            hfile.write(''.join([_build_cmake_path(src, '    '), '\n']))
296        hfile.write(_END_WITH_ONE_BLANK_LINE)
297
298    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
299    @common_util.io_error_handle
300    def _write_cmakelists_flags(self, hfile):
301        """Writes all kinds of flags in CLion project file.
302
303        Args:
304            hfile: A file handler instance.
305        """
306        self._write_flags(hfile, _KEY_GLOBAL_COMMON_FLAGS, True, True)
307        self._write_flags(hfile, _KEY_LOCAL_COMMON_FLAGS, True, True)
308        self._write_flags(hfile, _KEY_GLOBAL_CFLAGS, True, True)
309        self._write_flags(hfile, _KEY_LOCAL_CFLAGS, True, True)
310        self._write_flags(hfile, _KEY_GLOBAL_C_ONLY_FLAGS, True, False)
311        self._write_flags(hfile, _KEY_LOCAL_C_ONLY_FLAGS, True, False)
312        self._write_flags(hfile, _KEY_GLOBAL_CPP_FLAGS, False, True)
313        self._write_flags(hfile, _KEY_LOCAL_CPP_FLAGS, False, True)
314        self._write_flags(hfile, _KEY_SYSTEM_INCLUDE_FLAGS, True, True)
315
316    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
317    @common_util.io_error_handle
318    def _write_tail(self, hfile):
319        """Writes CLion project file content with necessary info.
320
321        Args:
322            hfile: A file handler instance.
323        """
324        hfile.write(
325            _ADD_EXECUTABLE_HEADER.format(
326                _cleanup_executable_name(self.mod_name),
327                _add_dollar_sign(_SOURCE_FILES_HEADER)))
328
329    @common_util.check_args(
330        hfile=(TextIOWrapper, StringIO), key=str, cflags=bool, cppflags=bool)
331    @common_util.io_error_handle
332    def _write_flags(self, hfile, key, cflags, cppflags):
333        """Writes CMake compiler paths of C, Cpp for different kinds of flags.
334
335        Args:
336            hfile: A file handler instance.
337            key: A string of flag type, e.g., 'global_common_flags' flag.
338            cflags: A boolean for setting 'CMAKE_C_FLAGS' flag.
339            cppflags: A boolean for setting 'CMAKE_CXX_FLAGS' flag.
340        """
341        if key not in _FLAGS_DICT:
342            return
343        hfile.write(_FLAGS_DICT[key])
344        params_dict = self._parse_compiler_parameters(key)
345        if params_dict:
346            _translate_to_cmake(hfile, params_dict, cflags, cppflags)
347
348    @common_util.check_args(flag=str)
349    def _parse_compiler_parameters(self, flag):
350        """Parses the specific flag data from a module_info dictionary.
351
352        Args:
353            flag: The string of key flag, e.g.: _KEY_GLOBAL_COMMON_FLAGS.
354
355        Returns:
356            A dictionary with compiled parameters.
357        """
358        params = self.mod_info.get(flag, {})
359        if not params:
360            return None
361        params_dict = {
362            constant.KEY_HEADER: [],
363            constant.KEY_SYSTEM: [],
364            _KEY_FLAG: [],
365            _KEY_SYSTEM_ROOT: '',
366            _KEY_RELATIVE: {}
367        }
368        for key, value in params.items():
369            params_dict[key] = value
370        return params_dict
371
372
373@common_util.check_args(rel_project_path=str, mod_names=list)
374@common_util.io_error_handle
375def generate_base_cmakelists_file(cc_module_info, rel_project_path, mod_names):
376    """Generates base CLion project file for multiple CLion projects.
377
378    We create a multiple native project file:
379    {android_root}/development/ide/clion/{rel_project_path}/CMakeLists.txt
380    and use this project file to generate a link:
381    {android_root}/out/development/ide/clion/{rel_project_path}/CMakeLists.txt
382
383    Args:
384        cc_module_info: An instance of native_module_info.NativeModuleInfo.
385        rel_project_path: A string of the base project relative path. For
386                          example: frameworks/native/libs/ui.
387        mod_names: A list of module names whose project were created under
388                   rel_project_path.
389
390    Returns:
391        A symbolic link CLion project file path.
392    """
393    root_dir = common_util.get_android_root_dir()
394    cc_dir = os.path.join(root_dir, constant.RELATIVE_NATIVE_PATH,
395                          rel_project_path)
396    cc_out_dir = os.path.join(root_dir, common_util.get_android_out_dir(),
397                              constant.RELATIVE_NATIVE_PATH, rel_project_path)
398    if not os.path.exists(cc_dir):
399        os.makedirs(cc_dir)
400    dst_path = os.path.join(cc_out_dir, constant.CLION_PROJECT_FILE_NAME)
401    if os.path.islink(dst_path):
402        os.unlink(dst_path)
403    src_path = os.path.join(cc_dir, constant.CLION_PROJECT_FILE_NAME)
404    if os.path.isfile(src_path):
405        os.remove(src_path)
406    with open(src_path, 'w') as hfile:
407        _write_base_cmakelists_file(hfile, cc_module_info, src_path, mod_names)
408    os.symlink(src_path, dst_path)
409    return dst_path
410
411
412@common_util.check_args(
413    hfile=(TextIOWrapper, StringIO), abs_project_path=str, mod_names=list)
414@common_util.io_error_handle
415def _write_base_cmakelists_file(hfile, cc_module_info, abs_project_path,
416                                mod_names):
417    """Writes base CLion project file content.
418
419    When we write module info into base CLion project file, first check if the
420    module's CMakeLists.txt exists. If file exists, write content,
421        add_subdirectory({'relative_module_path'})
422
423    Args:
424        hfile: A file handler instance.
425        cc_module_info: An instance of native_module_info.NativeModuleInfo.
426        abs_project_path: A string of the base project absolute path.
427                          For example,
428                              ${ANDROID_BUILD_TOP}/frameworks/native/libs/ui.
429        mod_names: A list of module names whose project were created under
430                   abs_project_path.
431    """
432    hfile.write(_MINI_VERSION_SUPPORT.format(_MINI_VERSION))
433    project_dir = os.path.dirname(abs_project_path)
434    hfile.write(_PROJECT.format(os.path.basename(project_dir)))
435    root_dir = common_util.get_android_root_dir()
436    parent_dir = os.path.relpath(abs_project_path, root_dir)
437    for mod_name in mod_names:
438        mod_info = cc_module_info.get_module_info(mod_name)
439        mod_path = CLionProjectFileGenerator.get_module_path(
440            mod_info, parent_dir)
441        file_dir = CLionProjectFileGenerator.get_cmakelists_file_dir(
442            os.path.join(mod_path, mod_name))
443        file_path = os.path.join(file_dir, constant.CLION_PROJECT_FILE_NAME)
444        if not os.path.isfile(file_path):
445            logging.warning("%s the project file %s doesn't exist.",
446                            common_util.COLORED_INFO('Warning:'), file_path)
447            continue
448        link_project_dir = os.path.join(root_dir,
449                                        common_util.get_android_out_dir(),
450                                        os.path.relpath(project_dir, root_dir))
451        rel_mod_path = os.path.relpath(file_dir, link_project_dir)
452        hfile.write(_ADD_SUB.format(rel_mod_path))
453
454
455@common_util.check_args(
456    hfile=(TextIOWrapper, StringIO), params_dict=dict, cflags=bool,
457    cppflags=bool)
458def _translate_to_cmake(hfile, params_dict, cflags, cppflags):
459    """Translates parameter dict's contents into CLion project file format.
460
461    Args:
462        hfile: A file handler instance.
463        params_dict: A dict contains data to be translated into CLion
464                     project file format.
465        cflags: A boolean is to set 'CMAKE_C_FLAGS' flag.
466        cppflags: A boolean is to set 'CMAKE_CXX_FLAGS' flag.
467    """
468    _write_all_include_directories(
469        hfile, params_dict[constant.KEY_SYSTEM], True)
470    _write_all_include_directories(
471        hfile, params_dict[constant.KEY_HEADER], False)
472
473    if cflags:
474        _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE],
475                                            _CMAKE_C_FLAGS)
476        _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_C_FLAGS)
477
478    if cppflags:
479        _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE],
480                                            _CMAKE_CXX_FLAGS)
481        _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_CXX_FLAGS)
482
483    if params_dict[_KEY_SYSTEM_ROOT]:
484        path = os.path.join(params_dict[_KEY_SYSTEM_ROOT], _USR, _INCLUDE)
485        hfile.write(_INCLUDE_SYSTEM.format(_build_cmake_path(path)))
486
487
488@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool)
489@common_util.io_error_handle
490def _write_all_include_directories(hfile, includes, is_system):
491    """Writes all included directories' paths to the CLion project file.
492
493    Args:
494        hfile: A file handler instance.
495        includes: A list of included file paths.
496        is_system: A boolean of whether it's a system flag.
497    """
498    if not includes:
499        return
500    _write_all_includes(hfile, includes, is_system)
501    _write_all_headers(hfile, includes)
502
503
504@common_util.check_args(
505    hfile=(TextIOWrapper, StringIO), rel_paths_dict=dict, tag=str)
506@common_util.io_error_handle
507def _write_all_relative_file_path_flags(hfile, rel_paths_dict, tag):
508    """Writes all relative file path flags' parameters.
509
510    Args:
511        hfile: A file handler instance.
512        rel_paths_dict: A dict contains data of flag as a key and the relative
513                        path string as its value.
514        tag: A string of tag, such as 'CMAKE_C_FLAGS'.
515    """
516    for flag, path in rel_paths_dict.items():
517        hfile.write(
518            _SET_RELATIVE_PATH.format(tag, _add_dollar_sign(tag), flag,
519                                      _build_cmake_path(path)))
520
521
522@common_util.check_args(hfile=(TextIOWrapper, StringIO), flags=list, tag=str)
523@common_util.io_error_handle
524def _write_all_flags(hfile, flags, tag):
525    """Wrties all flags to the project file.
526
527    Args:
528        hfile: A file handler instance.
529        flags: A list of flag strings to be added.
530        tag: A string to be added a dollar sign.
531    """
532    for flag in flags:
533        hfile.write(_SET_ALL_FLAGS.format(tag, _add_dollar_sign(tag), flag))
534
535
536def _add_dollar_sign(tag):
537    """Adds dollar sign to a string, e.g.: 'ANDROID_ROOT' -> '${ANDROID_ROOT}'.
538
539    Args:
540        tag: A string to be added a dollar sign.
541
542    Returns:
543        A dollar sign added string.
544    """
545    return ''.join(['${', tag, '}'])
546
547
548def _build_cmake_path(path, tag=''):
549    """Joins tag, '${ANDROID_ROOT}' and path into a new string.
550
551    Args:
552        path: A string of a path.
553        tag: A string to be added in front of a dollar sign
554
555    Returns:
556        The composed string.
557    """
558    return ''.join([tag, _ANDROID_ROOT_SYMBOL, os.path.sep, path])
559
560
561@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool)
562@common_util.io_error_handle
563def _write_all_includes(hfile, includes, is_system):
564    """Writes all included directories' paths to the CLion project file.
565
566    Args:
567        hfile: A file handler instance.
568        includes: A list of included file paths.
569        is_system: A boolean of whether it's a system flag.
570    """
571    if not includes:
572        return
573    system = ''
574    if is_system:
575        system = _SYSTEM
576    hfile.write(_INCLUDE_DIR.format(system))
577    for include in includes:
578        hfile.write(_SET_INCLUDE_FORMAT.format(_build_cmake_path(include)))
579    hfile.write(_END_WITH_TWO_BLANK_LINES)
580
581
582@common_util.check_args(hfile=(TextIOWrapper, StringIO))
583@common_util.io_error_handle
584def _write_all_headers(hfile, includes):
585    """Writes all header directories' paths to the CLion project file.
586
587    Args:
588        hfile: A file handler instance.
589        includes: A list of included file paths.
590    """
591    if not includes:
592        return
593    hfile.write(_GLOB_RECURSE_TMP_HEADERS)
594    for include in includes:
595        hfile.write(_ALL_HEADER_FILES.format(_build_cmake_path(include)))
596    hfile.write(_END_WITH_ONE_BLANK_LINE)
597    hfile.write(_APPEND_SOURCE_FILES)
598
599
600def _cleanup_executable_name(mod_name):
601    """Clean up an executable name to be suitable for CMake.
602
603    Replace the last '@' of a module name with '-' and make it become a suitable
604    executable name for CMake.
605
606    Args:
607        mod_name: A string of module name to be cleaned up.
608
609    Returns:
610        A string of the executable name.
611    """
612    return mod_name[::-1].replace('@', '-', 1)[::-1]
613