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