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