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        source_files = self.mod_info[constant.KEY_SRCS]
289        hfile.write(_LIST_APPEND_HEADER)
290        hfile.write(_SOURCE_FILES_LINE)
291        for src in source_files:
292            hfile.write(''.join([_build_cmake_path(src, '    '), '\n']))
293        hfile.write(_END_WITH_ONE_BLANK_LINE)
294
295    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
296    @common_util.io_error_handle
297    def _write_cmakelists_flags(self, hfile):
298        """Writes all kinds of flags in CLion project file.
299
300        Args:
301            hfile: A file handler instance.
302        """
303        self._write_flags(hfile, _KEY_GLOBAL_COMMON_FLAGS, True, True)
304        self._write_flags(hfile, _KEY_LOCAL_COMMON_FLAGS, True, True)
305        self._write_flags(hfile, _KEY_GLOBAL_CFLAGS, True, True)
306        self._write_flags(hfile, _KEY_LOCAL_CFLAGS, True, True)
307        self._write_flags(hfile, _KEY_GLOBAL_C_ONLY_FLAGS, True, False)
308        self._write_flags(hfile, _KEY_LOCAL_C_ONLY_FLAGS, True, False)
309        self._write_flags(hfile, _KEY_GLOBAL_CPP_FLAGS, False, True)
310        self._write_flags(hfile, _KEY_LOCAL_CPP_FLAGS, False, True)
311        self._write_flags(hfile, _KEY_SYSTEM_INCLUDE_FLAGS, True, True)
312
313    @common_util.check_args(hfile=(TextIOWrapper, StringIO))
314    @common_util.io_error_handle
315    def _write_tail(self, hfile):
316        """Writes CLion project file content with necessary info.
317
318        Args:
319            hfile: A file handler instance.
320        """
321        hfile.write(
322            _ADD_EXECUTABLE_HEADER.format(
323                _cleanup_executable_name(self.mod_name),
324                _add_dollar_sign(_SOURCE_FILES_HEADER)))
325
326    @common_util.check_args(
327        hfile=(TextIOWrapper, StringIO), key=str, cflags=bool, cppflags=bool)
328    @common_util.io_error_handle
329    def _write_flags(self, hfile, key, cflags, cppflags):
330        """Writes CMake compiler paths of C, Cpp for different kinds of flags.
331
332        Args:
333            hfile: A file handler instance.
334            key: A string of flag type, e.g., 'global_common_flags' flag.
335            cflags: A boolean for setting 'CMAKE_C_FLAGS' flag.
336            cppflags: A boolean for setting 'CMAKE_CXX_FLAGS' flag.
337        """
338        if key not in _FLAGS_DICT:
339            return
340        hfile.write(_FLAGS_DICT[key])
341        params_dict = self._parse_compiler_parameters(key)
342        if params_dict:
343            _translate_to_cmake(hfile, params_dict, cflags, cppflags)
344
345    @common_util.check_args(flag=str)
346    def _parse_compiler_parameters(self, flag):
347        """Parses the specific flag data from a module_info dictionary.
348
349        Args:
350            flag: The string of key flag, e.g.: _KEY_GLOBAL_COMMON_FLAGS.
351
352        Returns:
353            A dictionary with compiled parameters.
354        """
355        params = self.mod_info.get(flag, {})
356        if not params:
357            return None
358        params_dict = {
359            constant.KEY_HEADER: [],
360            constant.KEY_SYSTEM: [],
361            _KEY_FLAG: [],
362            _KEY_SYSTEM_ROOT: '',
363            _KEY_RELATIVE: {}
364        }
365        for key, value in params.items():
366            params_dict[key] = value
367        return params_dict
368
369
370@common_util.check_args(rel_project_path=str, mod_names=list)
371@common_util.io_error_handle
372def generate_base_cmakelists_file(cc_module_info, rel_project_path, mod_names):
373    """Generates base CLion project file for multiple CLion projects.
374
375    We create a multiple native project file:
376    {android_root}/development/ide/clion/{rel_project_path}/CMakeLists.txt
377    and use this project file to generate a link:
378    {android_root}/out/development/ide/clion/{rel_project_path}/CMakeLists.txt
379
380    Args:
381        cc_module_info: An instance of native_module_info.NativeModuleInfo.
382        rel_project_path: A string of the base project relative path. For
383                          example: frameworks/native/libs/ui.
384        mod_names: A list of module names whose project were created under
385                   rel_project_path.
386
387    Returns:
388        A symbolic link CLion project file path.
389    """
390    root_dir = common_util.get_android_root_dir()
391    cc_dir = os.path.join(root_dir, constant.RELATIVE_NATIVE_PATH,
392                          rel_project_path)
393    cc_out_dir = os.path.join(root_dir, common_util.get_android_out_dir(),
394                              constant.RELATIVE_NATIVE_PATH, rel_project_path)
395    if not os.path.exists(cc_dir):
396        os.makedirs(cc_dir)
397    dst_path = os.path.join(cc_out_dir, constant.CLION_PROJECT_FILE_NAME)
398    if os.path.islink(dst_path):
399        os.unlink(dst_path)
400    src_path = os.path.join(cc_dir, constant.CLION_PROJECT_FILE_NAME)
401    if os.path.isfile(src_path):
402        os.remove(src_path)
403    with open(src_path, 'w') as hfile:
404        _write_base_cmakelists_file(hfile, cc_module_info, src_path, mod_names)
405    os.symlink(src_path, dst_path)
406    return dst_path
407
408
409@common_util.check_args(
410    hfile=(TextIOWrapper, StringIO), abs_project_path=str, mod_names=list)
411@common_util.io_error_handle
412def _write_base_cmakelists_file(hfile, cc_module_info, abs_project_path,
413                                mod_names):
414    """Writes base CLion project file content.
415
416    When we write module info into base CLion project file, first check if the
417    module's CMakeLists.txt exists. If file exists, write content,
418        add_subdirectory({'relative_module_path'})
419
420    Args:
421        hfile: A file handler instance.
422        cc_module_info: An instance of native_module_info.NativeModuleInfo.
423        abs_project_path: A string of the base project absolute path.
424                          For example,
425                              ${ANDROID_BUILD_TOP}/frameworks/native/libs/ui.
426        mod_names: A list of module names whose project were created under
427                   abs_project_path.
428    """
429    hfile.write(_MINI_VERSION_SUPPORT.format(_MINI_VERSION))
430    project_dir = os.path.dirname(abs_project_path)
431    hfile.write(_PROJECT.format(os.path.basename(project_dir)))
432    root_dir = common_util.get_android_root_dir()
433    parent_dir = os.path.relpath(abs_project_path, root_dir)
434    for mod_name in mod_names:
435        mod_info = cc_module_info.get_module_info(mod_name)
436        mod_path = CLionProjectFileGenerator.get_module_path(
437            mod_info, parent_dir)
438        file_dir = CLionProjectFileGenerator.get_cmakelists_file_dir(
439            os.path.join(mod_path, mod_name))
440        file_path = os.path.join(file_dir, constant.CLION_PROJECT_FILE_NAME)
441        if not os.path.isfile(file_path):
442            logging.warning("%s the project file %s doesn't exist.",
443                            common_util.COLORED_INFO('Warning:'), file_path)
444            continue
445        link_project_dir = os.path.join(root_dir,
446                                        common_util.get_android_out_dir(),
447                                        os.path.relpath(project_dir, root_dir))
448        rel_mod_path = os.path.relpath(file_dir, link_project_dir)
449        hfile.write(_ADD_SUB.format(rel_mod_path))
450
451
452@common_util.check_args(
453    hfile=(TextIOWrapper, StringIO), params_dict=dict, cflags=bool,
454    cppflags=bool)
455def _translate_to_cmake(hfile, params_dict, cflags, cppflags):
456    """Translates parameter dict's contents into CLion project file format.
457
458    Args:
459        hfile: A file handler instance.
460        params_dict: A dict contains data to be translated into CLion
461                     project file format.
462        cflags: A boolean is to set 'CMAKE_C_FLAGS' flag.
463        cppflags: A boolean is to set 'CMAKE_CXX_FLAGS' flag.
464    """
465    _write_all_include_directories(
466        hfile, params_dict[constant.KEY_SYSTEM], True)
467    _write_all_include_directories(
468        hfile, params_dict[constant.KEY_HEADER], False)
469
470    if cflags:
471        _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE],
472                                            _CMAKE_C_FLAGS)
473        _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_C_FLAGS)
474
475    if cppflags:
476        _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE],
477                                            _CMAKE_CXX_FLAGS)
478        _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_CXX_FLAGS)
479
480    if params_dict[_KEY_SYSTEM_ROOT]:
481        path = os.path.join(params_dict[_KEY_SYSTEM_ROOT], _USR, _INCLUDE)
482        hfile.write(_INCLUDE_SYSTEM.format(_build_cmake_path(path)))
483
484
485@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool)
486@common_util.io_error_handle
487def _write_all_include_directories(hfile, includes, is_system):
488    """Writes all included directories' paths to the CLion project file.
489
490    Args:
491        hfile: A file handler instance.
492        includes: A list of included file paths.
493        is_system: A boolean of whether it's a system flag.
494    """
495    if not includes:
496        return
497    _write_all_includes(hfile, includes, is_system)
498    _write_all_headers(hfile, includes)
499
500
501@common_util.check_args(
502    hfile=(TextIOWrapper, StringIO), rel_paths_dict=dict, tag=str)
503@common_util.io_error_handle
504def _write_all_relative_file_path_flags(hfile, rel_paths_dict, tag):
505    """Writes all relative file path flags' parameters.
506
507    Args:
508        hfile: A file handler instance.
509        rel_paths_dict: A dict contains data of flag as a key and the relative
510                        path string as its value.
511        tag: A string of tag, such as 'CMAKE_C_FLAGS'.
512    """
513    for flag, path in rel_paths_dict.items():
514        hfile.write(
515            _SET_RELATIVE_PATH.format(tag, _add_dollar_sign(tag), flag,
516                                      _build_cmake_path(path)))
517
518
519@common_util.check_args(hfile=(TextIOWrapper, StringIO), flags=list, tag=str)
520@common_util.io_error_handle
521def _write_all_flags(hfile, flags, tag):
522    """Wrties all flags to the project file.
523
524    Args:
525        hfile: A file handler instance.
526        flags: A list of flag strings to be added.
527        tag: A string to be added a dollar sign.
528    """
529    for flag in flags:
530        hfile.write(_SET_ALL_FLAGS.format(tag, _add_dollar_sign(tag), flag))
531
532
533def _add_dollar_sign(tag):
534    """Adds dollar sign to a string, e.g.: 'ANDROID_ROOT' -> '${ANDROID_ROOT}'.
535
536    Args:
537        tag: A string to be added a dollar sign.
538
539    Returns:
540        A dollar sign added string.
541    """
542    return ''.join(['${', tag, '}'])
543
544
545def _build_cmake_path(path, tag=''):
546    """Joins tag, '${ANDROID_ROOT}' and path into a new string.
547
548    Args:
549        path: A string of a path.
550        tag: A string to be added in front of a dollar sign
551
552    Returns:
553        The composed string.
554    """
555    return ''.join([tag, _ANDROID_ROOT_SYMBOL, os.path.sep, path])
556
557
558@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool)
559@common_util.io_error_handle
560def _write_all_includes(hfile, includes, is_system):
561    """Writes all included directories' paths to the CLion project file.
562
563    Args:
564        hfile: A file handler instance.
565        includes: A list of included file paths.
566        is_system: A boolean of whether it's a system flag.
567    """
568    if not includes:
569        return
570    system = ''
571    if is_system:
572        system = _SYSTEM
573    hfile.write(_INCLUDE_DIR.format(system))
574    for include in includes:
575        hfile.write(_SET_INCLUDE_FORMAT.format(_build_cmake_path(include)))
576    hfile.write(_END_WITH_TWO_BLANK_LINES)
577
578
579@common_util.check_args(hfile=(TextIOWrapper, StringIO))
580@common_util.io_error_handle
581def _write_all_headers(hfile, includes):
582    """Writes all header directories' paths to the CLion project file.
583
584    Args:
585        hfile: A file handler instance.
586        includes: A list of included file paths.
587    """
588    if not includes:
589        return
590    hfile.write(_GLOB_RECURSE_TMP_HEADERS)
591    for include in includes:
592        hfile.write(_ALL_HEADER_FILES.format(_build_cmake_path(include)))
593    hfile.write(_END_WITH_ONE_BLANK_LINE)
594    hfile.write(_APPEND_SOURCE_FILES)
595
596
597def _cleanup_executable_name(mod_name):
598    """Clean up an executable name to be suitable for CMake.
599
600    Replace the last '@' of a module name with '-' and make it become a suitable
601    executable name for CMake.
602
603    Args:
604        mod_name: A string of module name to be cleaned up.
605
606    Returns:
607        A string of the executable name.
608    """
609    return mod_name[::-1].replace('@', '-', 1)[::-1]
610