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