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): 133 """ProjectFileGenerator initialize. 134 135 Args: 136 mod_info: A dictionary of native module's info. 137 """ 138 if not mod_info: 139 raise errors.ModuleInfoEmptyError(_MODULE_INFO_EMPTY) 140 self.mod_info = mod_info 141 self.mod_name = self._get_module_name() 142 self.mod_path = CLionProjectFileGenerator.get_module_path(mod_info) 143 self.cc_dir = CLionProjectFileGenerator.get_cmakelists_file_dir( 144 os.path.join(self.mod_path, self.mod_name)) 145 if not os.path.exists(self.cc_dir): 146 os.makedirs(self.cc_dir) 147 self.cc_path = os.path.join(self.cc_dir, 148 constant.CLION_PROJECT_FILE_NAME) 149 150 def _get_module_name(self): 151 """Gets the value of the 'module_name' key if it exists. 152 153 Returns: 154 A string of the module's name. 155 156 Raises: 157 NoModuleNameDefinedInModuleInfoError if no 'module_name' key in 158 mod_info. 159 """ 160 mod_name = self.mod_info.get(constant.KEY_MODULE_NAME, '') 161 if not mod_name: 162 raise errors.NoModuleNameDefinedInModuleInfoError( 163 _DICT_NO_MOD_NAME_KEY) 164 return mod_name 165 166 @staticmethod 167 def get_module_path(mod_info): 168 """Gets the first value of the 'path' key if it exists. 169 170 Args: 171 mod_info: A module's info dictionary. 172 173 Returns: 174 A string of the module's path. 175 176 Raises: 177 NoPathDefinedInModuleInfoError if no 'path' key in mod_info. 178 """ 179 mod_paths = mod_info.get(constant.KEY_PATH, []) 180 if not mod_paths: 181 raise errors.NoPathDefinedInModuleInfoError(_DICT_NO_PATH_KEY) 182 return mod_paths[0] 183 184 @staticmethod 185 @common_util.check_args(cc_path=str) 186 def get_cmakelists_file_dir(cc_path): 187 """Gets module's CMakeLists.txt file path to be created. 188 189 Return a string of $OUT/development/ide/clion/${cc_path}. 190 For example, if module name is 'libui'. The return path string would be: 191 out/development/ide/clion/frameworks/native/libs/ui/libui 192 193 Args: 194 cc_path: A string of absolute path of module's Android.bp file. 195 196 Returns: 197 A string of absolute path of module's CMakeLists.txt file to be 198 created. 199 """ 200 return os.path.join(common_util.get_android_root_dir(), 201 common_util.get_android_out_dir(), 202 constant.RELATIVE_NATIVE_PATH, cc_path) 203 204 def generate_cmakelists_file(self): 205 """Generates CLion project file from the target module's info.""" 206 with open(self.cc_path, 'w') as hfile: 207 self._write_cmakelists_file(hfile) 208 209 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 210 @common_util.io_error_handle 211 def _write_cmakelists_file(self, hfile): 212 """Writes CLion project file content with neccessary info. 213 214 Args: 215 hfile: A file handler instance. 216 """ 217 self._write_header(hfile) 218 self._write_c_compiler_paths(hfile) 219 self._write_source_files(hfile) 220 self._write_cmakelists_flags(hfile) 221 self._write_tail(hfile) 222 223 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 224 @common_util.io_error_handle 225 def _write_header(self, hfile): 226 """Writes CLion project file's header messages. 227 228 Args: 229 hfile: A file handler instance. 230 """ 231 content = templates.CMAKELISTS_HEADER.replace( 232 _MIN_VERSION_TOKEN, _MINI_VERSION) 233 content = content.replace(_PROJECT_NAME_TOKEN, self.mod_name) 234 content = content.replace( 235 _ANDOIR_ROOT_TOKEN, common_util.get_android_root_dir()) 236 hfile.write(content) 237 238 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 239 @common_util.io_error_handle 240 def _write_c_compiler_paths(self, hfile): 241 """Writes CMake compiler paths for C and Cpp to CLion project file. 242 243 Args: 244 hfile: A file handler instance. 245 """ 246 hfile.write(_SET_C_COMPILER.format( 247 native_module_info.NativeModuleInfo.c_lang_path)) 248 hfile.write(_SET_CXX_COMPILER.format( 249 native_module_info.NativeModuleInfo.cpp_lang_path)) 250 251 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 252 @common_util.io_error_handle 253 def _write_source_files(self, hfile): 254 """Writes source files' paths to CLion project file. 255 256 Args: 257 hfile: A file handler instance. 258 """ 259 if constant.KEY_SRCS not in self.mod_info: 260 logging.warning("No source files in %s's module info.", 261 self.mod_name) 262 return 263 source_files = self.mod_info[constant.KEY_SRCS] 264 hfile.write(_LIST_APPEND_HEADER) 265 hfile.write(_SOURCE_FILES_LINE) 266 for src in source_files: 267 hfile.write(''.join([_build_cmake_path(src, ' '), '\n'])) 268 hfile.write(_END_WITH_ONE_BLANK_LINE) 269 270 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 271 @common_util.io_error_handle 272 def _write_cmakelists_flags(self, hfile): 273 """Writes all kinds of flags in CLion project file. 274 275 Args: 276 hfile: A file handler instance. 277 """ 278 self._write_flags(hfile, _KEY_GLOBAL_COMMON_FLAGS, True, True) 279 self._write_flags(hfile, _KEY_LOCAL_COMMON_FLAGS, True, True) 280 self._write_flags(hfile, _KEY_GLOBAL_CFLAGS, True, True) 281 self._write_flags(hfile, _KEY_LOCAL_CFLAGS, True, True) 282 self._write_flags(hfile, _KEY_GLOBAL_C_ONLY_FLAGS, True, False) 283 self._write_flags(hfile, _KEY_LOCAL_C_ONLY_FLAGS, True, False) 284 self._write_flags(hfile, _KEY_GLOBAL_CPP_FLAGS, False, True) 285 self._write_flags(hfile, _KEY_LOCAL_CPP_FLAGS, False, True) 286 self._write_flags(hfile, _KEY_SYSTEM_INCLUDE_FLAGS, True, True) 287 288 @common_util.check_args(hfile=(TextIOWrapper, StringIO)) 289 @common_util.io_error_handle 290 def _write_tail(self, hfile): 291 """Writes CLion project file content with necessary info. 292 293 Args: 294 hfile: A file handler instance. 295 """ 296 hfile.write( 297 _ADD_EXECUTABLE_HEADER.format( 298 _cleanup_executable_name(self.mod_name), 299 _add_dollar_sign(_SOURCE_FILES_HEADER))) 300 301 @common_util.check_args( 302 hfile=(TextIOWrapper, StringIO), key=str, cflags=bool, cppflags=bool) 303 @common_util.io_error_handle 304 def _write_flags(self, hfile, key, cflags, cppflags): 305 """Writes CMake compiler paths of C, Cpp for different kinds of flags. 306 307 Args: 308 hfile: A file handler instance. 309 key: A string of flag type, e.g., 'global_common_flags' flag. 310 cflags: A boolean for setting 'CMAKE_C_FLAGS' flag. 311 cppflags: A boolean for setting 'CMAKE_CXX_FLAGS' flag. 312 """ 313 if key not in _FLAGS_DICT: 314 return 315 hfile.write(_FLAGS_DICT[key]) 316 params_dict = self._parse_compiler_parameters(key) 317 if params_dict: 318 _translate_to_cmake(hfile, params_dict, cflags, cppflags) 319 320 @common_util.check_args(flag=str) 321 def _parse_compiler_parameters(self, flag): 322 """Parses the specific flag data from a module_info dictionary. 323 324 Args: 325 flag: The string of key flag, e.g.: _KEY_GLOBAL_COMMON_FLAGS. 326 327 Returns: 328 A dictionary with compiled parameters. 329 """ 330 params = self.mod_info.get(flag, {}) 331 if not params: 332 return None 333 params_dict = { 334 constant.KEY_HEADER: [], 335 constant.KEY_SYSTEM: [], 336 _KEY_FLAG: [], 337 _KEY_SYSTEM_ROOT: '', 338 _KEY_RELATIVE: {} 339 } 340 for key, value in params.items(): 341 params_dict[key] = value 342 return params_dict 343 344 345@common_util.check_args(rel_project_path=str, mod_names=list) 346@common_util.io_error_handle 347def generate_base_cmakelists_file(cc_module_info, rel_project_path, mod_names): 348 """Generates base CLion project file for multiple CLion projects. 349 350 We create a multiple native project file: 351 {android_root}/development/ide/clion/{rel_project_path}/CMakeLists.txt 352 and use this project file to generate a link: 353 {android_root}/out/development/ide/clion/{rel_project_path}/CMakeLists.txt 354 355 Args: 356 cc_module_info: An instance of native_module_info.NativeModuleInfo. 357 rel_project_path: A string of the base project relative path. For 358 example: frameworks/native/libs/ui. 359 mod_names: A list of module names whose project were created under 360 rel_project_path. 361 362 Returns: 363 A symbolic link CLion project file path. 364 """ 365 root_dir = common_util.get_android_root_dir() 366 cc_dir = os.path.join(root_dir, constant.RELATIVE_NATIVE_PATH, 367 rel_project_path) 368 cc_out_dir = os.path.join(root_dir, common_util.get_android_out_dir(), 369 constant.RELATIVE_NATIVE_PATH, rel_project_path) 370 if not os.path.exists(cc_dir): 371 os.makedirs(cc_dir) 372 dst_path = os.path.join(cc_out_dir, constant.CLION_PROJECT_FILE_NAME) 373 if os.path.islink(dst_path): 374 os.unlink(dst_path) 375 src_path = os.path.join(cc_dir, constant.CLION_PROJECT_FILE_NAME) 376 if os.path.isfile(src_path): 377 os.remove(src_path) 378 with open(src_path, 'w') as hfile: 379 _write_base_cmakelists_file(hfile, cc_module_info, src_path, mod_names) 380 os.symlink(src_path, dst_path) 381 return dst_path 382 383 384@common_util.check_args( 385 hfile=(TextIOWrapper, StringIO), abs_project_path=str, mod_names=list) 386@common_util.io_error_handle 387def _write_base_cmakelists_file(hfile, cc_module_info, abs_project_path, 388 mod_names): 389 """Writes base CLion project file content. 390 391 When we write module info into base CLion project file, first check if the 392 module's CMakeLists.txt exists. If file exists, write content, 393 add_subdirectory({'relative_module_path'}) 394 395 Args: 396 hfile: A file handler instance. 397 cc_module_info: An instance of native_module_info.NativeModuleInfo. 398 abs_project_path: A string of the base project absolute path. 399 For example, 400 ${ANDROID_BUILD_TOP}/frameworks/native/libs/ui. 401 mod_names: A list of module names whose project were created under 402 abs_project_path. 403 """ 404 hfile.write(_MINI_VERSION_SUPPORT.format(_MINI_VERSION)) 405 project_dir = os.path.dirname(abs_project_path) 406 hfile.write(_PROJECT.format(os.path.basename(project_dir))) 407 root_dir = common_util.get_android_root_dir() 408 for mod_name in mod_names: 409 mod_info = cc_module_info.get_module_info(mod_name) 410 mod_path = CLionProjectFileGenerator.get_module_path(mod_info) 411 file_dir = CLionProjectFileGenerator.get_cmakelists_file_dir( 412 os.path.join(mod_path, mod_name)) 413 file_path = os.path.join(file_dir, constant.CLION_PROJECT_FILE_NAME) 414 if not os.path.isfile(file_path): 415 logging.warning("%s the project file %s doesn't exist.", 416 common_util.COLORED_INFO('Warning:'), file_path) 417 continue 418 link_project_dir = os.path.join(root_dir, 419 common_util.get_android_out_dir(), 420 os.path.relpath(project_dir, root_dir)) 421 rel_mod_path = os.path.relpath(file_dir, link_project_dir) 422 hfile.write(_ADD_SUB.format(rel_mod_path)) 423 424 425@common_util.check_args( 426 hfile=(TextIOWrapper, StringIO), params_dict=dict, cflags=bool, 427 cppflags=bool) 428def _translate_to_cmake(hfile, params_dict, cflags, cppflags): 429 """Translates parameter dict's contents into CLion project file format. 430 431 Args: 432 hfile: A file handler instance. 433 params_dict: A dict contains data to be translated into CLion 434 project file format. 435 cflags: A boolean is to set 'CMAKE_C_FLAGS' flag. 436 cppflags: A boolean is to set 'CMAKE_CXX_FLAGS' flag. 437 """ 438 _write_all_include_directories( 439 hfile, params_dict[constant.KEY_SYSTEM], True) 440 _write_all_include_directories( 441 hfile, params_dict[constant.KEY_HEADER], False) 442 443 if cflags: 444 _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE], 445 _CMAKE_C_FLAGS) 446 _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_C_FLAGS) 447 448 if cppflags: 449 _write_all_relative_file_path_flags(hfile, params_dict[_KEY_RELATIVE], 450 _CMAKE_CXX_FLAGS) 451 _write_all_flags(hfile, params_dict[_KEY_FLAG], _CMAKE_CXX_FLAGS) 452 453 if params_dict[_KEY_SYSTEM_ROOT]: 454 path = os.path.join(params_dict[_KEY_SYSTEM_ROOT], _USR, _INCLUDE) 455 hfile.write(_INCLUDE_SYSTEM.format(_build_cmake_path(path))) 456 457 458@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool) 459@common_util.io_error_handle 460def _write_all_include_directories(hfile, includes, is_system): 461 """Writes all included directories' paths to the CLion project file. 462 463 Args: 464 hfile: A file handler instance. 465 includes: A list of included file paths. 466 is_system: A boolean of whether it's a system flag. 467 """ 468 if not includes: 469 return 470 _write_all_includes(hfile, includes, is_system) 471 _write_all_headers(hfile, includes) 472 473 474@common_util.check_args( 475 hfile=(TextIOWrapper, StringIO), rel_paths_dict=dict, tag=str) 476@common_util.io_error_handle 477def _write_all_relative_file_path_flags(hfile, rel_paths_dict, tag): 478 """Writes all relative file path flags' parameters. 479 480 Args: 481 hfile: A file handler instance. 482 rel_paths_dict: A dict contains data of flag as a key and the relative 483 path string as its value. 484 tag: A string of tag, such as 'CMAKE_C_FLAGS'. 485 """ 486 for flag, path in rel_paths_dict.items(): 487 hfile.write( 488 _SET_RELATIVE_PATH.format(tag, _add_dollar_sign(tag), flag, 489 _build_cmake_path(path))) 490 491 492@common_util.check_args(hfile=(TextIOWrapper, StringIO), flags=list, tag=str) 493@common_util.io_error_handle 494def _write_all_flags(hfile, flags, tag): 495 """Wrties all flags to the project file. 496 497 Args: 498 hfile: A file handler instance. 499 flags: A list of flag strings to be added. 500 tag: A string to be added a dollar sign. 501 """ 502 for flag in flags: 503 hfile.write(_SET_ALL_FLAGS.format(tag, _add_dollar_sign(tag), flag)) 504 505 506def _add_dollar_sign(tag): 507 """Adds dollar sign to a string, e.g.: 'ANDROID_ROOT' -> '${ANDROID_ROOT}'. 508 509 Args: 510 tag: A string to be added a dollar sign. 511 512 Returns: 513 A dollar sign added string. 514 """ 515 return ''.join(['${', tag, '}']) 516 517 518def _build_cmake_path(path, tag=''): 519 """Joins tag, '${ANDROID_ROOT}' and path into a new string. 520 521 Args: 522 path: A string of a path. 523 tag: A string to be added in front of a dollar sign 524 525 Returns: 526 The composed string. 527 """ 528 return ''.join([tag, _ANDROID_ROOT_SYMBOL, os.path.sep, path]) 529 530 531@common_util.check_args(hfile=(TextIOWrapper, StringIO), is_system=bool) 532@common_util.io_error_handle 533def _write_all_includes(hfile, includes, is_system): 534 """Writes all included directories' paths to the CLion project file. 535 536 Args: 537 hfile: A file handler instance. 538 includes: A list of included file paths. 539 is_system: A boolean of whether it's a system flag. 540 """ 541 if not includes: 542 return 543 system = '' 544 if is_system: 545 system = _SYSTEM 546 hfile.write(_INCLUDE_DIR.format(system)) 547 for include in includes: 548 hfile.write(_SET_INCLUDE_FORMAT.format(_build_cmake_path(include))) 549 hfile.write(_END_WITH_TWO_BLANK_LINES) 550 551 552@common_util.check_args(hfile=(TextIOWrapper, StringIO)) 553@common_util.io_error_handle 554def _write_all_headers(hfile, includes): 555 """Writes all header directories' paths to the CLion project file. 556 557 Args: 558 hfile: A file handler instance. 559 includes: A list of included file paths. 560 """ 561 if not includes: 562 return 563 hfile.write(_GLOB_RECURSE_TMP_HEADERS) 564 for include in includes: 565 hfile.write(_ALL_HEADER_FILES.format(_build_cmake_path(include))) 566 hfile.write(_END_WITH_ONE_BLANK_LINE) 567 hfile.write(_APPEND_SOURCE_FILES) 568 569 570def _cleanup_executable_name(mod_name): 571 """Clean up an executable name to be suitable for CMake. 572 573 Replace the last '@' of a module name with '-' and make it become a suitable 574 executable name for CMake. 575 576 Args: 577 mod_name: A string of module name to be cleaned up. 578 579 Returns: 580 A string of the executable name. 581 """ 582 return mod_name[::-1].replace('@', '-', 1)[::-1] 583