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 the 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 96 97def _parse_args(args): 98 """Parse command line arguments. 99 100 Args: 101 args: A list of arguments. 102 103 Returns: 104 An argparse.Namespace class instance holding parsed args. 105 """ 106 parser = argparse.ArgumentParser( 107 description=__doc__, 108 formatter_class=argparse.RawDescriptionHelpFormatter, 109 usage=('aidegen [module_name1 module_name2... ' 110 'project_path1 project_path2...]')) 111 parser.required = False 112 parser.add_argument( 113 'targets', 114 type=str, 115 nargs='*', 116 default=[''], 117 help=('Android module name or path.' 118 'e.g. Settings or packages/apps/Settings')) 119 parser.add_argument( 120 '-d', 121 '--depth', 122 type=int, 123 choices=range(10), 124 default=0, 125 help='The depth of module referenced by source.') 126 parser.add_argument( 127 '-v', 128 '--verbose', 129 action='store_true', 130 help='Display DEBUG level logging.') 131 parser.add_argument( 132 '-i', 133 '--ide', 134 default=['u'], 135 help=('Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse, ' 136 'c: CLion, v: VS Code. The default value is \'u\': undefined.')) 137 parser.add_argument( 138 '-p', 139 '--ide-path', 140 dest='ide_installed_path', 141 help='IDE installed path.') 142 parser.add_argument( 143 '-n', '--no_launch', action='store_true', help='Do not launch IDE.') 144 parser.add_argument( 145 '-r', 146 '--config-reset', 147 dest='config_reset', 148 action='store_true', 149 help='Reset all saved configurations, e.g., preferred IDE version.') 150 parser.add_argument( 151 '-s', 152 '--skip-build', 153 dest='skip_build', 154 action='store_true', 155 help=('Skip building jars or modules that create java files in build ' 156 'time, e.g. R/AIDL/Logtags.')) 157 parser.add_argument( 158 '-a', 159 '--android-tree', 160 dest='android_tree', 161 action='store_true', 162 help='Generate whole Android source tree project file for IDE.') 163 parser.add_argument( 164 '-e', 165 '--exclude-paths', 166 dest='exclude_paths', 167 nargs='*', 168 help='Exclude the directories in IDE.') 169 parser.add_argument( 170 '-V', 171 '--version', 172 action='store_true', 173 help='Print aidegen version string.') 174 parser.add_argument( 175 '-l', 176 '--language', 177 default=['u'], 178 help=('Launch IDE with a specific language, j: Java, c: C/C++. The ' 179 'default value is \'u\': undefined.')) 180 return parser.parse_args(args) 181 182 183def _generate_project_files(projects): 184 """Generate project files by IDE type. 185 186 Args: 187 projects: A list of ProjectInfo instances. 188 """ 189 config = project_config.ProjectConfig.get_instance() 190 if config.ide_name == constant.IDE_ECLIPSE: 191 eclipse_project_file_gen.EclipseConf.generate_ide_project_files( 192 projects) 193 else: 194 project_file_gen.ProjectFileGenerator.generate_ide_project_files( 195 projects) 196 197 198def _launch_ide(ide_util_obj, project_absolute_path): 199 """Launch IDE through ide_util instance. 200 201 To launch IDE, 202 1. Set IDE config. 203 2. For IntelliJ, use .idea as open target is better than .iml file, 204 because open the latter is like to open a kind of normal file. 205 3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched. 206 207 Args: 208 ide_util_obj: An ide_util instance. 209 project_absolute_path: A string of project absolute path. 210 """ 211 ide_util_obj.config_ide(project_absolute_path) 212 if ide_util_obj.ide_name() == constant.IDE_ECLIPSE: 213 launch_msg = ' '.join([_LAUNCH_SUCCESS_MSG, 214 _LAUNCH_ECLIPSE_SUCCESS_MSG.format( 215 PROJECT_PATH=project_absolute_path)]) 216 else: 217 launch_msg = _LAUNCH_SUCCESS_MSG 218 print('\n{} {}\n'.format(_CONGRATULATIONS, launch_msg)) 219 print('\n{} {}\n'.format(_INFO, _IDE_CACHE_REMINDER_MSG)) 220 # Send the end message to Clearcut server before launching IDE to make sure 221 # the execution time is correct. 222 aidegen_metrics.ends_asuite_metrics(constant.EXIT_CODE_EXCEPTION) 223 ide_util_obj.launch_ide() 224 225 226def _launch_native_projects(ide_util_obj, args, cmakelists): 227 """Launches C/C++ projects with IDE. 228 229 AIDEGen provides the IDE argument for CLion, but there's still a implicit 230 way to launch it. The rules to launch it are: 231 1. If no target IDE, we don't have to launch any IDE for C/C++ project. 232 2. If the target IDE is IntelliJ or Eclipse, we should launch C/C++ 233 projects with CLion. 234 235 Args: 236 ide_util_obj: An ide_util instance. 237 args: An argparse.Namespace class instance holding parsed args. 238 cmakelists: A list of CMakeLists.txt file paths. 239 """ 240 if not ide_util_obj: 241 return 242 native_ide_util_obj = ide_util_obj 243 ide_name = constant.IDE_NAME_DICT[args.ide[0]] 244 if ide_name in _LAUNCH_CLION_IDES: 245 native_ide_util_obj = ide_util.get_ide_util_instance('c') 246 if native_ide_util_obj: 247 _launch_ide(native_ide_util_obj, ' '.join(cmakelists)) 248 249 250def _create_and_launch_java_projects(ide_util_obj, targets): 251 """Launches Android of Java(Kotlin) projects with IDE. 252 253 Args: 254 ide_util_obj: An ide_util instance. 255 targets: A list of build targets. 256 """ 257 projects = project_info.ProjectInfo.generate_projects(targets) 258 project_info.ProjectInfo.multi_projects_locate_source(projects) 259 _generate_project_files(projects) 260 if ide_util_obj: 261 _launch_ide(ide_util_obj, projects[0].project_absolute_path) 262 263 264def _get_preferred_ide_from_user(all_choices): 265 """Provides the option list to get back users single choice. 266 267 Args: 268 all_choices: A list of string type for all options. 269 270 Return: 271 A string of the user's single choice item. 272 """ 273 if not all_choices: 274 return None 275 options = [] 276 items = [] 277 for index, option in enumerate(all_choices, 1): 278 options.append('{}. {}'.format(index, option)) 279 items.append(str(index)) 280 query = _CHOOSE_LANGUAGE_MSG.format(len(options), '\n'.join(options)) 281 input_data = input(query) 282 while input_data not in items: 283 input_data = input('Please select one.\t') 284 return all_choices[int(input_data) - 1] 285 286 287def _launch_ide_by_module_contents(args, ide_util_obj, jlist=None, clist=None, 288 both=False): 289 """Deals with the suitable IDE launch action. 290 291 The rules of AIDEGen launching IDE with languages are: 292 1. aidegen frameworks/base 293 aidegen frameworks/base -l j 294 launch Java projects of frameworks/base in IntelliJ. 295 2. aidegen frameworks/base -i c 296 aidegen frameworks/base -l c 297 launch C/C++ projects of frameworks/base in CLion. 298 3. aidegen frameworks/base -i s 299 launch Java projects of frameworks/base in Android Studio. 300 aidegen frameworks/base -i s -l c 301 launch C/C++ projects of frameworks/base in Android Studio. 302 4. aidegen frameworks/base -i j -l c 303 launch Java projects of frameworks/base in IntelliJ. 304 5. aidegen frameworks/base -i c -l j 305 launch C/C++ projects of frameworks/base in CLion. 306 307 Args: 308 args: A list of system arguments. 309 ide_util_obj: An ide_util instance. 310 jlist: A list of java build targets. 311 clist: A list of C/C++ build targets. 312 both: A boolean, True to launch both languages else False. 313 """ 314 if both: 315 _launch_vscode(ide_util_obj, project_info.ProjectInfo.modules_info, 316 jlist, clist) 317 return 318 if not jlist and not clist: 319 logging.warning('\nThere is neither java nor C/C++ module needs to be' 320 ' opened') 321 return 322 323 language, _ = common_util.determine_language_ide( 324 args.language[0], args.ide[0]) 325 if (jlist and not clist) or (language == constant.JAVA): 326 _create_and_launch_java_projects(ide_util_obj, jlist) 327 return 328 if (clist and not jlist) or (language == constant.C_CPP): 329 native_project_info.NativeProjectInfo.generate_projects(clist) 330 native_project_file = native_util.generate_clion_projects(clist) 331 if native_project_file: 332 _launch_native_projects(ide_util_obj, args, [native_project_file]) 333 334 335def _launch_vscode(ide_util_obj, atest_module_info, jtargets, ctargets): 336 """Launches targets with VSCode IDE. 337 338 Args: 339 ide_util_obj: An ide_util instance. 340 atest_module_info: A ModuleInfo instance contains the data of 341 module-info.json. 342 jtargets: A list of Java project targets. 343 ctargets: A list of C/C++ project targets. 344 """ 345 abs_paths = [] 346 for target in jtargets: 347 _, abs_path = common_util.get_related_paths(atest_module_info, target) 348 abs_paths.append(abs_path) 349 if ctargets: 350 cc_module_info = native_module_info.NativeModuleInfo() 351 native_project_info.NativeProjectInfo.generate_projects(ctargets) 352 vs_gen = vscode_native_project_file_gen.VSCodeNativeProjectFileGenerator 353 for target in ctargets: 354 _, abs_path = common_util.get_related_paths(cc_module_info, target) 355 vs_native = vs_gen(abs_path) 356 vs_native.generate_c_cpp_properties_json_file() 357 if abs_path not in abs_paths: 358 abs_paths.append(abs_path) 359 vs_path = vscode_workspace_file_gen.generate_code_workspace_file(abs_paths) 360 if not ide_util_obj: 361 return 362 _launch_ide(ide_util_obj, vs_path) 363 364 365@common_util.time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME) 366def main_with_message(args): 367 """Main entry with skip build message. 368 369 Args: 370 args: A list of system arguments. 371 """ 372 aidegen_main(args) 373 374 375@common_util.time_logged 376def main_without_message(args): 377 """Main entry without skip build message. 378 379 Args: 380 args: A list of system arguments. 381 """ 382 aidegen_main(args) 383 384 385# pylint: disable=broad-except 386def main(argv): 387 """Main entry. 388 389 Show skip build message in aidegen main process if users command skip_build 390 otherwise remind them to use it and include metrics supports. 391 392 Args: 393 argv: A list of system arguments. 394 """ 395 exit_code = constant.EXIT_CODE_NORMAL 396 launch_ide = True 397 ask_version = False 398 try: 399 args = _parse_args(argv) 400 if args.version: 401 ask_version = True 402 version_file = os.path.join(os.path.dirname(__file__), 403 constant.VERSION_FILE) 404 print(common_util.read_file_content(version_file)) 405 sys.exit(constant.EXIT_CODE_NORMAL) 406 407 launch_ide = not args.no_launch 408 common_util.configure_logging(args.verbose) 409 is_whole_android_tree = project_config.is_whole_android_tree( 410 args.targets, args.android_tree) 411 references = [constant.ANDROID_TREE] if is_whole_android_tree else [] 412 aidegen_metrics.starts_asuite_metrics(references) 413 if args.skip_build: 414 main_without_message(args) 415 else: 416 main_with_message(args) 417 except BaseException as err: 418 exit_code = constant.EXIT_CODE_EXCEPTION 419 _, exc_value, exc_traceback = sys.exc_info() 420 if isinstance(err, errors.AIDEgenError): 421 exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION 422 # Filter out sys.Exit(0) case, which is not an exception case. 423 if isinstance(err, SystemExit) and exc_value.code == 0: 424 exit_code = constant.EXIT_CODE_NORMAL 425 if exit_code is not constant.EXIT_CODE_NORMAL: 426 error_message = str(exc_value) 427 traceback_list = traceback.format_tb(exc_traceback) 428 traceback_list.append(error_message) 429 traceback_str = ''.join(traceback_list) 430 aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str, 431 error_message) 432 # print out the trackback message for developers to debug 433 print(traceback_str) 434 raise err 435 finally: 436 if not ask_version: 437 print('\n{0} {1}\n'.format(_INFO, AIDEGEN_REPORT_LINK)) 438 # Send the end message here on ignoring launch IDE case. 439 if not launch_ide and exit_code is constant.EXIT_CODE_NORMAL: 440 aidegen_metrics.ends_asuite_metrics(exit_code) 441 442 443def aidegen_main(args): 444 """AIDEGen main entry. 445 446 Try to generate project files for using in IDE. The process is: 447 1. Instantiate a ProjectConfig singleton object and initialize its 448 environment. After creating a singleton instance for ProjectConfig, 449 other modules can use project configurations by 450 ProjectConfig.get_instance(). 451 2. Get an IDE instance from ide_util, ide_util.get_ide_util_instance will 452 use ProjectConfig.get_instance() inside the function. 453 3. Setup project_info.ProjectInfo.modules_info by instantiate 454 AidegenModuleInfo. 455 4. Check if projects contain C/C++ projects and launch related IDE. 456 457 Args: 458 args: A list of system arguments. 459 """ 460 config = project_config.ProjectConfig(args) 461 config.init_environment() 462 targets = config.targets 463 # Called ide_util for pre-check the IDE existence state. 464 _, ide = common_util.determine_language_ide(args.language[0], args.ide[0]) 465 ide_util_obj = ide_util.get_ide_util_instance(constant.IDE_DICT[ide]) 466 project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo() 467 cc_module_info = native_module_info.NativeModuleInfo() 468 jtargets, ctargets = native_util.get_native_and_java_projects( 469 project_info.ProjectInfo.modules_info, cc_module_info, targets) 470 both = config.ide_name == constant.IDE_VSCODE 471 # Backward compatible strategy, when both java and C/C++ module exist, 472 # check the preferred target from the user and launch single one. 473 _launch_ide_by_module_contents(args, ide_util_obj, jtargets, ctargets, both) 474 475 476if __name__ == '__main__': 477 main(sys.argv[1:]) 478