1#!/usr/bin/env python3 2# 3# Copyright 2018 - 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"""AIDEgen 18 19This CLI generates project files for using in IntelliJ, such as: 20 - iml 21 - .idea/compiler.xml 22 - .idea/misc.xml 23 - .idea/modules.xml 24 - .idea/vcs.xml 25 - .idea/.name 26 - .idea/copyright/Apache_2.xml 27 - .idea/copyright/profiles_settings.xml 28 29- Sample usage: 30 - Change directory to AOSP root first. 31 $ cd /user/home/aosp/ 32 - Generating project files under packages/apps/Settings folder. 33 $ aidegen packages/apps/Settings 34 or 35 $ aidegen Settings 36 or 37 $ cd packages/apps/Settings;aidegen 38""" 39 40from __future__ import absolute_import 41 42import argparse 43import os 44import sys 45import traceback 46 47from pathlib import Path 48 49from aidegen import constant 50from aidegen.lib import aidegen_metrics 51from aidegen.lib import common_util 52from aidegen.lib import eclipse_project_file_gen 53from aidegen.lib import errors 54from aidegen.lib import ide_util 55from aidegen.lib import module_info 56from aidegen.lib import native_module_info 57from aidegen.lib import native_project_info 58from aidegen.lib import native_util 59from aidegen.lib import project_config 60from aidegen.lib import project_file_gen 61from aidegen.lib import project_info 62from aidegen.vscode import vscode_native_project_file_gen 63from aidegen.vscode import vscode_workspace_file_gen 64 65AIDEGEN_REPORT_LINK = ('To report an AIDEGen tool problem, please use this ' 66 'link: https://goto.google.com/aidegen-bug') 67_CONGRATULATIONS = common_util.COLORED_PASS('CONGRATULATIONS:') 68_LAUNCH_SUCCESS_MSG = ( 69 'IDE launched successfully. Please check your IDE window.') 70_LAUNCH_ECLIPSE_SUCCESS_MSG = ( 71 'The project files .classpath and .project are generated under ' 72 '{PROJECT_PATH} and AIDEGen doesn\'t import the project automatically, ' 73 'please import the project manually by steps: File -> Import -> select \'' 74 'General\' -> \'Existing Projects into Workspace\' -> click \'Next\' -> ' 75 'Choose the root directory -> click \'Finish\'.') 76_IDE_CACHE_REMINDER_MSG = ( 77 'To prevent the existed IDE cache from impacting your IDE dependency ' 78 'analysis, please consider to clear IDE caches if necessary. To do that, ' 79 'in IntelliJ IDEA, go to [File > Invalidate Caches -> ' 80 'Invalidate and Restart].') 81 82_MAX_TIME = 1 83_SKIP_BUILD_INFO_FUTURE = ''.join([ 84 'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME), 85 project_config.SKIP_BUILD_INFO.rstrip('.'), ' in the future.' 86]) 87_INFO = common_util.COLORED_INFO('INFO:') 88_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format( 89 common_util.COLORED_INFO('aidegen [ module(s) ] -s')) 90_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG) 91_LAUNCH_CLION_IDES = [ 92 constant.IDE_CLION, constant.IDE_INTELLIJ, constant.IDE_ECLIPSE] 93_CHOOSE_LANGUAGE_MSG = ('The scope of your modules contains {} different ' 94 'languages as follows:\n{}\nPlease select the one you ' 95 'would like to implement.\t') 96_LANGUAGE_OPTIONS = [constant.JAVA, constant.C_CPP] 97_NO_ANY_PROJECT_EXIST = 'There is no Java, C/C++ or Rust target.' 98_NO_LANGUAGE_PROJECT_EXIST = 'There is no {} target.' 99_NO_IDE_LAUNCH_PATH = 'Can not find the IDE path : {}' 100 101 102def _parse_args(args): 103 """Parse command line arguments. 104 105 Args: 106 args: A list of arguments. 107 108 Returns: 109 An argparse.Namespace class instance holding parsed args. 110 """ 111 parser = argparse.ArgumentParser( 112 description=__doc__, 113 formatter_class=argparse.RawDescriptionHelpFormatter, 114 usage=('aidegen [module_name1 module_name2... ' 115 'project_path1 project_path2...]')) 116 parser.required = False 117 parser.add_argument( 118 'targets', 119 type=str, 120 nargs='*', 121 default=[''], 122 help=('Android module name or path.' 123 'e.g. Settings or packages/apps/Settings')) 124 parser.add_argument( 125 '-d', 126 '--depth', 127 type=int, 128 choices=range(10), 129 default=0, 130 help='The depth of module referenced by source.') 131 parser.add_argument( 132 '-v', 133 '--verbose', 134 action='store_true', 135 help='Display DEBUG level logging.') 136 parser.add_argument( 137 '-i', 138 '--ide', 139 default=['u'], 140 help=('Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse, ' 141 'c: CLion, v: VS Code. The default value is \'u\': undefined.')) 142 parser.add_argument( 143 '-p', 144 '--ide-path', 145 dest='ide_installed_path', 146 help='IDE installed path.') 147 parser.add_argument( 148 '-n', '--no_launch', action='store_true', help='Do not launch IDE.') 149 parser.add_argument( 150 '-r', 151 '--config-reset', 152 dest='config_reset', 153 action='store_true', 154 help='Reset all saved configurations, e.g., preferred IDE version.') 155 parser.add_argument( 156 '-s', 157 '--skip-build', 158 dest='skip_build', 159 action='store_true', 160 help=('Skip building jars or modules that create java files in build ' 161 'time, e.g. R/AIDL/Logtags.')) 162 parser.add_argument( 163 '-a', 164 '--android-tree', 165 dest='android_tree', 166 action='store_true', 167 help='Generate whole Android source tree project file for IDE.') 168 parser.add_argument( 169 '-e', 170 '--exclude-paths', 171 dest='exclude_paths', 172 nargs='*', 173 help='Exclude the directories in IDE.') 174 parser.add_argument( 175 '-V', 176 '--version', 177 action='store_true', 178 help='Print aidegen version string.') 179 parser.add_argument( 180 '-l', 181 '--language', 182 default=['u'], 183 help=('Launch IDE with a specific language, j: Java, c: C/C++, r: ' 184 'Rust. The default value is \'u\': undefined.')) 185 return parser.parse_args(args) 186 187 188def _generate_project_files(projects): 189 """Generate project files by IDE type. 190 191 Args: 192 projects: A list of ProjectInfo instances. 193 """ 194 config = project_config.ProjectConfig.get_instance() 195 if config.ide_name == constant.IDE_ECLIPSE: 196 eclipse_project_file_gen.EclipseConf.generate_ide_project_files( 197 projects) 198 else: 199 project_file_gen.ProjectFileGenerator.generate_ide_project_files( 200 projects) 201 202 203def _launch_ide(ide_util_obj, project_absolute_path): 204 """Launch IDE through ide_util instance. 205 206 To launch IDE, 207 1. Set IDE config. 208 2. For IntelliJ, use .idea as open target is better than .iml file, 209 because open the latter is like to open a kind of normal file. 210 3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched. 211 212 Args: 213 ide_util_obj: An ide_util instance. 214 project_absolute_path: A string of project absolute path. 215 """ 216 ide_util_obj.config_ide(project_absolute_path) 217 if ide_util_obj.ide_name() == constant.IDE_ECLIPSE: 218 launch_msg = ' '.join([_LAUNCH_SUCCESS_MSG, 219 _LAUNCH_ECLIPSE_SUCCESS_MSG.format( 220 PROJECT_PATH=project_absolute_path)]) 221 else: 222 launch_msg = _LAUNCH_SUCCESS_MSG 223 print('\n{} {}\n'.format(_CONGRATULATIONS, launch_msg)) 224 print('\n{} {}\n'.format(_INFO, _IDE_CACHE_REMINDER_MSG)) 225 # Send the end message to Clearcut server before launching IDE to make sure 226 # the execution time is correct. 227 aidegen_metrics.ends_asuite_metrics(constant.EXIT_CODE_EXCEPTION) 228 ide_util_obj.launch_ide() 229 230 231def _launch_native_projects(ide_util_obj, args, cmakelists): 232 """Launches C/C++ projects with IDE. 233 234 AIDEGen provides the IDE argument for CLion, but there's still an implicit 235 way to launch it. The rules to launch it are: 236 1. If no target IDE, we don't have to launch any IDE for C/C++ project. 237 2. If the target IDE is IntelliJ or Eclipse, we should launch C/C++ 238 projects with CLion. 239 240 Args: 241 ide_util_obj: An ide_util instance. 242 args: An argparse.Namespace class instance holding parsed args. 243 cmakelists: A list of CMakeLists.txt file paths. 244 """ 245 if not ide_util_obj: 246 return 247 native_ide_util_obj = ide_util_obj 248 ide_name = constant.IDE_NAME_DICT[args.ide[0]] 249 if ide_name in _LAUNCH_CLION_IDES: 250 native_ide_util_obj = ide_util.get_ide_util_instance('c') 251 if native_ide_util_obj: 252 _launch_ide(native_ide_util_obj, ' '.join(cmakelists)) 253 254 255def _create_and_launch_java_projects(ide_util_obj, targets): 256 """Launches Android of Java(Kotlin) projects with IDE. 257 258 Args: 259 ide_util_obj: An ide_util instance. 260 targets: A list of build targets. 261 """ 262 projects = project_info.ProjectInfo.generate_projects(targets) 263 264 project_info.ProjectInfo.multi_projects_locate_source(projects) 265 _generate_project_files(projects) 266 if ide_util_obj: 267 _launch_ide(ide_util_obj, projects[0].project_absolute_path) 268 269 270def _launch_ide_by_module_contents(args, ide_util_obj, language, 271 lang_targets, all_langs=False): 272 """Deals with the suitable IDE launch action. 273 274 The rules of AIDEGen launching IDE with languages are: 275 1. If no IDE or language is specific, the priority of the language is: 276 a) Java 277 aidegen frameworks/base 278 launch Java projects of frameworks/base in IntelliJ. 279 b) C/C++ 280 aidegen hardware/interfaces/vibrator/aidl/default 281 launch C/C++ project of hardware/interfaces/vibrator/aidl/default 282 in CLion. 283 c) Rust 284 aidegen external/rust/crates/protobuf 285 launch Rust project of external/rust/crates/protobuf in VS Code. 286 2. If the IDE is specific, launch related projects in the IDE. 287 a) aidegen frameworks/base -i j 288 launch Java projects of frameworks/base in IntelliJ. 289 aidegen frameworks/base -i s 290 launch Java projects of frameworks/base in Android Studio. 291 aidegen frameworks/base -i e 292 launch Java projects of frameworks/base in Eclipse. 293 b) aidegen frameworks/base -i c 294 launch C/C++ projects of frameworks/base in CLion. 295 c) aidegen external/rust/crates/protobuf -i v 296 launch Rust project of external/rust/crates/protobuf in VS Code. 297 3. If the language is specific, launch relative language projects in the 298 relative IDE. 299 a) aidegen frameworks/base -l j 300 launch Java projects of frameworks/base in IntelliJ. 301 b) aidegen frameworks/base -l c 302 launch C/C++ projects of frameworks/base in CLion. 303 c) aidegen external/rust/crates/protobuf -l r 304 launch Rust projects of external/rust/crates/protobuf in VS Code. 305 4. Both of the IDE and language are specific, launch the IDE with the 306 relative language projects. If the IDE conflicts with the language, the 307 IDE is prior to the language. 308 a) aidegen frameworks/base -i j -l j 309 launch Java projects of frameworks/base in IntelliJ. 310 b) aidegen frameworks/base -i s -l c 311 launch C/C++ projects of frameworks/base in Android Studio. 312 c) aidegen frameworks/base -i c -l j 313 launch Java projects of frameworks/base in CLion. 314 315 Args: 316 args: A list of system arguments. 317 ide_util_obj: An ide_util instance. 318 language: A string of the language to be edited in the IDE. 319 lang_targets: A dict contains None or list of targets of different 320 languages. E.g. 321 { 322 'Java': ['modules1', 'modules2'], 323 'C/C++': ['modules3'], 324 'Rust': None, 325 ... 326 } 327 all_langs: A boolean, True to launch all languages else False. 328 """ 329 if lang_targets is None: 330 print(constant.WARN_MSG.format( 331 common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST)) 332 return 333 334 targets = lang_targets 335 java_list = targets[constant.JAVA] if constant.JAVA in targets else None 336 c_cpp_list = targets[constant.C_CPP] if constant.C_CPP in targets else None 337 rust_list = targets[constant.RUST] if constant.RUST in targets else None 338 339 if all_langs: 340 _launch_vscode(ide_util_obj, project_info.ProjectInfo.modules_info, 341 java_list, c_cpp_list, rust_list) 342 return 343 if not (java_list or c_cpp_list or rust_list): 344 print(constant.WARN_MSG.format( 345 common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST)) 346 return 347 if language == constant.JAVA: 348 if not java_list: 349 print(constant.WARN_MSG.format( 350 common_util.COLORED_INFO('Warning:'), 351 _NO_LANGUAGE_PROJECT_EXIST.format(constant.JAVA))) 352 return 353 _create_and_launch_java_projects(ide_util_obj, java_list) 354 return 355 if language == constant.C_CPP: 356 if not c_cpp_list: 357 print(constant.WARN_MSG.format( 358 common_util.COLORED_INFO('Warning:'), 359 _NO_LANGUAGE_PROJECT_EXIST.format(constant.C_CPP))) 360 return 361 native_project_info.NativeProjectInfo.generate_projects(c_cpp_list) 362 native_project_file = native_util.generate_clion_projects(c_cpp_list) 363 if native_project_file: 364 _launch_native_projects(ide_util_obj, args, [native_project_file]) 365 366 367def _launch_vscode(ide_util_obj, atest_module_info, jtargets, ctargets, 368 rtargets): 369 """Launches targets with VSCode IDE. 370 371 Args: 372 ide_util_obj: An ide_util instance. 373 atest_module_info: A ModuleInfo instance contains the data of 374 module-info.json. 375 jtargets: A list of Java project targets. 376 ctargets: A list of C/C++ project targets. 377 rtargets: A list of Rust project targets. 378 """ 379 abs_paths = [] 380 if jtargets: 381 abs_paths.extend(_get_java_project_paths(jtargets, atest_module_info)) 382 if ctargets: 383 abs_paths.extend(_get_cc_project_paths(ctargets)) 384 if rtargets: 385 root_dir = common_util.get_android_root_dir() 386 abs_paths.extend(_get_rust_project_paths(rtargets, root_dir)) 387 if not (jtargets or ctargets or rtargets): 388 print(constant.WARN_MSG.format( 389 common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST)) 390 return 391 vs_path = vscode_workspace_file_gen.generate_code_workspace_file(abs_paths) 392 if not ide_util_obj: 393 return 394 _launch_ide(ide_util_obj, vs_path) 395 396 397def _get_java_project_paths(jtargets, atest_module_info): 398 """Gets the Java absolute project paths from the input Java targets. 399 400 Args: 401 jtargets: A list of strings of Java targets. 402 atest_module_info: A ModuleInfo instance contains the data of 403 module-info.json. 404 405 Returns: 406 A list of the Java absolute project paths. 407 """ 408 abs_paths = [] 409 for target in jtargets: 410 _, abs_path = common_util.get_related_paths(atest_module_info, target) 411 if abs_path: 412 abs_paths.append(abs_path) 413 return abs_paths 414 415 416def _get_cc_project_paths(ctargets): 417 """Gets the C/C++ absolute project paths from the input C/C++ targets. 418 419 Args: 420 ctargets: A list of strings of C/C++ targets. 421 422 Returns: 423 A list of the C/C++ absolute project paths. 424 """ 425 abs_paths = [] 426 cc_module_info = native_module_info.NativeModuleInfo() 427 native_project_info.NativeProjectInfo.generate_projects(ctargets) 428 vs_gen = vscode_native_project_file_gen.VSCodeNativeProjectFileGenerator 429 for target in ctargets: 430 _, abs_path = common_util.get_related_paths(cc_module_info, target) 431 if not abs_path: 432 continue 433 vs_native = vs_gen(abs_path) 434 vs_native.generate_c_cpp_properties_json_file() 435 if abs_path not in abs_paths: 436 abs_paths.append(abs_path) 437 return abs_paths 438 439 440def _get_rust_project_paths(rtargets, root_dir): 441 """Gets the Rust absolute project paths from the input Rust targets. 442 443 Args: 444 rtargets: A list of strings of Rust targets. 445 root_dir: A string of the Android root directory. 446 447 Returns: 448 A list of the Rust absolute project paths. 449 """ 450 abs_paths = [] 451 for rtarget in rtargets: 452 path = rtarget 453 # If rtarget is not an absolute path, make it an absolute one. 454 if not common_util.is_source_under_relative_path(rtarget, root_dir): 455 path = os.path.join(root_dir, rtarget) 456 abs_paths.append(path) 457 return abs_paths 458 459 460def _get_targets_from_args(targets, android_tree): 461 """Gets targets for specific argument. 462 463 For example: 464 $aidegen : targets = ['.'] 465 $aidegen -a : targets = [] 466 $aidegen . : targets = ['.'] 467 $aidegen . -a: targets = [] 468 469 Args: 470 targets: A list of strings of targets. 471 android_tree: A boolean, True with '-a' argument else False. 472 473 Returns: 474 A list of the Rust absolute project paths. 475 """ 476 if targets == [''] and not android_tree: 477 return ['.'] 478 if android_tree: 479 return [] 480 return targets 481 482 483@common_util.time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME) 484def main_with_message(args): 485 """Main entry with skip build message. 486 487 Args: 488 args: A list of system arguments. 489 """ 490 aidegen_main(args) 491 492 493@common_util.time_logged 494def main_without_message(args): 495 """Main entry without skip build message. 496 497 Args: 498 args: A list of system arguments. 499 """ 500 aidegen_main(args) 501 502 503# pylint: disable=broad-except 504def main(argv): 505 """Main entry. 506 507 Show skip build message in aidegen main process if users command skip_build 508 otherwise remind them to use it and include metrics supports. 509 510 Args: 511 argv: A list of system arguments. 512 """ 513 exit_code = constant.EXIT_CODE_NORMAL 514 launch_ide = True 515 ask_version = False 516 try: 517 args = _parse_args(argv) 518 args.targets = _get_targets_from_args(args.targets, args.android_tree) 519 if args.version: 520 ask_version = True 521 version_file = os.path.join(os.path.dirname(__file__), 522 constant.VERSION_FILE) 523 print(common_util.read_file_content(version_file)) 524 sys.exit(constant.EXIT_CODE_NORMAL) 525 if args.ide_installed_path: 526 if not Path(args.ide_installed_path).exists(): 527 print(_NO_IDE_LAUNCH_PATH.format(args.ide_installed_path)) 528 sys.exit(constant.EXIT_CODE_NORMAL) 529 530 launch_ide = not args.no_launch 531 common_util.configure_logging(args.verbose) 532 is_whole_android_tree = project_config.is_whole_android_tree( 533 args.targets, args.android_tree) 534 references = [constant.ANDROID_TREE] if is_whole_android_tree else [] 535 aidegen_metrics.starts_asuite_metrics(references) 536 if args.skip_build: 537 main_without_message(args) 538 else: 539 main_with_message(args) 540 except BaseException as err: 541 exit_code = constant.EXIT_CODE_EXCEPTION 542 _, exc_value, exc_traceback = sys.exc_info() 543 if isinstance(err, errors.AIDEgenError): 544 exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION 545 # Filter out sys.Exit(0) case, which is not an exception case. 546 if isinstance(err, SystemExit) and exc_value.code == 0: 547 exit_code = constant.EXIT_CODE_NORMAL 548 if exit_code is not constant.EXIT_CODE_NORMAL: 549 error_message = str(exc_value) 550 traceback_list = traceback.format_tb(exc_traceback) 551 traceback_list.append(error_message) 552 traceback_str = ''.join(traceback_list) 553 aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str, 554 error_message) 555 # print out the traceback message for developers to debug 556 print(traceback_str) 557 raise err 558 finally: 559 if not ask_version: 560 print('\n{0} {1}\n'.format(_INFO, AIDEGEN_REPORT_LINK)) 561 # Send the end message here on ignoring launch IDE case. 562 if not launch_ide and exit_code is constant.EXIT_CODE_NORMAL: 563 aidegen_metrics.ends_asuite_metrics(exit_code) 564 565 566def aidegen_main(args): 567 """AIDEGen main entry. 568 569 Try to generate project files for using in IDE. The process is: 570 1. Instantiate a ProjectConfig singleton object and initialize its 571 environment. After creating a singleton instance for ProjectConfig, 572 other modules can use project configurations by 573 ProjectConfig.get_instance(). 574 2. Get an IDE instance from ide_util, ide_util.get_ide_util_instance will 575 use ProjectConfig.get_instance() inside the function. 576 3. Setup project_info.ProjectInfo.modules_info by instantiate 577 AidegenModuleInfo. 578 4. Check if projects contain C/C++ projects and launch related IDE. 579 580 Args: 581 args: A list of system arguments. 582 """ 583 config = project_config.ProjectConfig(args) 584 config.init_environment() 585 targets = config.targets 586 project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo() 587 cc_module_info = native_module_info.NativeModuleInfo() 588 jtargets, ctargets, rtargets = native_util.get_java_cc_and_rust_projects( 589 project_info.ProjectInfo.modules_info, cc_module_info, targets) 590 config.language, config.ide_name = common_util.determine_language_ide( 591 args.language[0], args.ide[0], jtargets, ctargets, rtargets) 592 # Called ide_util for pre-check the IDE existence state. 593 ide_util_obj = ide_util.get_ide_util_instance( 594 constant.IDE_DICT[config.ide_name]) 595 all_langs = config.ide_name == constant.IDE_VSCODE 596 # Backward compatible strategy, when both java and C/C++ module exist, 597 # check the preferred target from the user and launch single one. 598 language_targets = {constant.JAVA: jtargets, 599 constant.C_CPP: ctargets, 600 constant.RUST: rtargets} 601 _launch_ide_by_module_contents(args, ide_util_obj, config.language, 602 language_targets, all_langs) 603 604 605if __name__ == '__main__': 606 main(sys.argv[1:]) 607