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.android_dev_os import AndroidDevOS 50from aidegen.lib import common_util 51from aidegen.lib.common_util import COLORED_INFO 52from aidegen.lib.common_util import COLORED_PASS 53from aidegen.lib.common_util import is_android_root 54from aidegen.lib.common_util import time_logged 55from aidegen.lib.errors import AIDEgenError 56from aidegen.lib.errors import IDENotExistError 57from aidegen.lib.ide_util import IdeUtil 58from aidegen.lib.aidegen_metrics import starts_asuite_metrics 59from aidegen.lib.aidegen_metrics import ends_asuite_metrics 60from aidegen.lib.module_info_util import generate_module_info_json 61from aidegen.lib.project_file_gen import generate_eclipse_project_files 62from aidegen.lib.project_file_gen import generate_ide_project_files 63from aidegen.lib.project_info import ProjectInfo 64from aidegen.lib.source_locator import multi_projects_locate_source 65 66AIDEGEN_REPORT_LINK = ('To report the AIDEGen tool problem, please use this ' 67 'link: https://goto.google.com/aidegen-bug') 68_NO_LAUNCH_IDE_CMD = """ 69Can not find IDE in path: {}, you can: 70 - add IDE executable to your $PATH 71or - specify the exact IDE executable path by "aidegen -p" 72or - specify "aidegen -n" to generate project file only 73""" 74 75_CONGRATULATION = COLORED_PASS('CONGRATULATION:') 76_LAUNCH_SUCCESS_MSG = ( 77 'IDE launched successfully. Please check your IDE window.') 78_IDE_CACHE_REMINDER_MSG = ( 79 'To prevent the existed IDE cache from impacting your IDE dependency ' 80 'analysis, please consider to clear IDE caches if necessary. To do that, in' 81 ' IntelliJ IDEA, go to [File > Invalidate Caches / Restart...].') 82 83_SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have ' 84 'been already built, please try to use command {} to skip ' 85 'the building process.') 86_MAX_TIME = 1 87_SKIP_BUILD_INFO_FUTURE = ''.join([ 88 'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME), 89 _SKIP_BUILD_INFO.rstrip('.'), ' in the future.' 90]) 91_SKIP_BUILD_CMD = 'aidegen {} -s' 92_INFO = COLORED_INFO('INFO:') 93_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format( 94 COLORED_INFO('aidegen [ module(s) ] -s')) 95_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG) 96_LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s' 97_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' 98 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=['j'], 138 help='Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse.') 139 parser.add_argument( 140 '-p', 141 '--ide-path', 142 dest='ide_installed_path', 143 help='IDE installed path.') 144 parser.add_argument( 145 '-n', '--no_launch', action='store_true', help='Do not launch IDE.') 146 parser.add_argument( 147 '-r', 148 '--config-reset', 149 dest='config_reset', 150 action='store_true', 151 help='Reset all saved configurations, e.g., preferred IDE version.') 152 parser.add_argument( 153 '-s', 154 '--skip-build', 155 dest='skip_build', 156 action='store_true', 157 help=('Skip building jar or modules that create java files in build ' 158 'time, e.g. R/AIDL/Logtags.')) 159 parser.add_argument( 160 '-a', 161 '--android-tree', 162 dest='android_tree', 163 action='store_true', 164 help='Generate whole Android source tree project file for IDE.') 165 return parser.parse_args(args) 166 167 168def _configure_logging(verbose): 169 """Configure the logger. 170 171 Args: 172 verbose: A boolean. If true, display DEBUG level logs. 173 """ 174 log_format = _LOG_FORMAT 175 datefmt = _DATE_FORMAT 176 level = logging.DEBUG if verbose else logging.INFO 177 logging.basicConfig(level=level, format=log_format, datefmt=datefmt) 178 179 180def _get_ide_util_instance(args): 181 """Get an IdeUtil class instance for launching IDE. 182 183 Args: 184 args: A list of arguments. 185 186 Returns: 187 A IdeUtil class instance. 188 """ 189 if args.no_launch: 190 return None 191 ide_util_obj = IdeUtil(args.ide_installed_path, args.ide[0], 192 args.config_reset, 193 AndroidDevOS.MAC == AndroidDevOS.get_os_type()) 194 if not ide_util_obj.is_ide_installed(): 195 ipath = args.ide_installed_path or ide_util_obj.get_default_path() 196 err = _NO_LAUNCH_IDE_CMD.format(ipath) 197 logging.error(err) 198 raise IDENotExistError(err) 199 return ide_util_obj 200 201 202def _check_skip_build(args): 203 """Check if users skip building target, display the warning message. 204 205 Args: 206 args: A list of arguments. 207 """ 208 if not args.skip_build: 209 msg = _SKIP_BUILD_INFO.format( 210 COLORED_INFO(_SKIP_BUILD_CMD.format(' '.join(args.targets)))) 211 print('\n{} {}\n'.format(_INFO, msg)) 212 213 214def _generate_project_files(ide, projects): 215 """Generate project files by IDE type. 216 217 Args: 218 ide: A character to represent IDE type. 219 projects: A list of ProjectInfo instances. 220 """ 221 if ide.lower() == 'e': 222 generate_eclipse_project_files(projects) 223 else: 224 generate_ide_project_files(projects) 225 226 227def _compile_targets_for_whole_android_tree(atest_module_info, targets, cwd): 228 """Compile a list of targets to include whole Android tree in the project. 229 230 Adding the whole Android tree to the project will do two things, 231 1. If current working directory is not Android root, change the target to 232 its relative path to root and change current working directory to root. 233 If we don't change directory it's hard to deal with the whole Android 234 tree together with the sub-project. 235 2. If the whole Android tree target is not in the target list, insert it to 236 the first one. 237 238 Args: 239 atest_module_info: A instance of atest module-info object. 240 targets: A list of targets to be built. 241 cwd: A path of current working directory. 242 243 Returns: 244 A list of targets after adjustment. 245 """ 246 new_targets = [] 247 if is_android_root(cwd): 248 new_targets = list(targets) 249 else: 250 for target in targets: 251 _, abs_path = common_util.get_related_paths(atest_module_info, 252 target) 253 rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH) 254 new_targets.append(rel_path) 255 os.chdir(constant.ANDROID_ROOT_PATH) 256 257 if new_targets[0] != '': 258 new_targets.insert(0, '') 259 return new_targets 260 261 262def _launch_ide(ide_util_obj, project_absolute_path): 263 """Launch IDE through ide_util instance. 264 265 To launch IDE, 266 1. Set IDE config. 267 2. For IntelliJ, use .idea as open target is better than .iml file, 268 because open the latter is like to open a kind of normal file. 269 3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched. 270 271 Args: 272 ide_util_obj: An ide_util instance. 273 project_absolute_path: A string of project absolute path. 274 """ 275 ide_util_obj.config_ide() 276 ide_util_obj.launch_ide(project_absolute_path) 277 print('\n{} {}\n'.format(_CONGRATULATION, _LAUNCH_SUCCESS_MSG)) 278 279 280def _check_whole_android_tree(atest_module_info, targets, android_tree): 281 """Check if it's a building project file for the whole Android tree. 282 283 The rules: 284 1. If users command aidegen under Android root, e.g., 285 root$ aidegen 286 that implies users would like to launch the whole Android tree, AIDEGen 287 should set the flag android_tree True. 288 2. If android_tree is True, add whole Android tree to the project. 289 290 Args: 291 atest_module_info: A instance of atest module-info object. 292 targets: A list of targets to be built. 293 android_tree: A boolean, True if it's a whole Android tree case, 294 otherwise False. 295 296 Returns: 297 A list of targets to be built. 298 """ 299 cwd = os.getcwd() 300 if not android_tree and is_android_root(cwd) and targets == ['']: 301 android_tree = True 302 new_targets = targets 303 if android_tree: 304 new_targets = _compile_targets_for_whole_android_tree( 305 atest_module_info, targets, cwd) 306 return new_targets 307 308 309@time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME) 310def main_with_message(args): 311 """Main entry with skip build message. 312 313 Args: 314 args: A list of system arguments. 315 """ 316 aidegen_main(args) 317 318 319@time_logged 320def main_without_message(args): 321 """Main entry without skip build message. 322 323 Args: 324 args: A list of system arguments. 325 """ 326 aidegen_main(args) 327 328 329# pylint: disable=broad-except 330def main(argv): 331 """Main entry. 332 333 Try to generates project files for using in IDE. 334 335 Args: 336 argv: A list of system arguments. 337 """ 338 exit_code = constant.EXIT_CODE_NORMAL 339 try: 340 args = _parse_args(argv) 341 _configure_logging(args.verbose) 342 starts_asuite_metrics() 343 if args.skip_build: 344 main_without_message(args) 345 else: 346 main_with_message(args) 347 except BaseException as err: 348 exit_code = constant.EXIT_CODE_EXCEPTION 349 _, exc_value, exc_traceback = sys.exc_info() 350 if isinstance(err, AIDEgenError): 351 exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION 352 # Filter out sys.Exit(0) case, which is not an exception case. 353 if isinstance(err, SystemExit) and exc_value.code == 0: 354 exit_code = constant.EXIT_CODE_NORMAL 355 finally: 356 if exit_code is not constant.EXIT_CODE_NORMAL: 357 error_message = str(exc_value) 358 traceback_list = traceback.format_tb(exc_traceback) 359 traceback_list.append(error_message) 360 traceback_str = ''.join(traceback_list) 361 # print out the trackback message for developers to debug 362 print(traceback_str) 363 ends_asuite_metrics(exit_code, traceback_str, error_message) 364 else: 365 ends_asuite_metrics(exit_code) 366 367 368def aidegen_main(args): 369 """AIDEGen main entry. 370 371 Try to generates project files for using in IDE. 372 373 Args: 374 args: A list of system arguments. 375 """ 376 # Pre-check for IDE relevant case, then handle dependency graph job. 377 ide_util_obj = _get_ide_util_instance(args) 378 _check_skip_build(args) 379 atest_module_info = common_util.get_atest_module_info(args.targets) 380 targets = _check_whole_android_tree(atest_module_info, args.targets, 381 args.android_tree) 382 ProjectInfo.modules_info = generate_module_info_json( 383 atest_module_info, targets, args.verbose, args.skip_build) 384 projects = ProjectInfo.generate_projects(atest_module_info, targets) 385 multi_projects_locate_source(projects, args.verbose, args.depth, 386 constant.IDE_NAME_DICT[args.ide[0]], 387 args.skip_build) 388 _generate_project_files(args.ide[0], projects) 389 if ide_util_obj: 390 _launch_ide(ide_util_obj, projects[0].project_absolute_path) 391 392 393if __name__ == '__main__': 394 try: 395 main(sys.argv[1:]) 396 finally: 397 print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK, 398 _IDE_CACHE_REMINDER_MSG)) 399