| #!/usr/bin/env python3 |
| # |
| # Copyright 2018 - The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """AIDEgen |
| |
| This CLI generates project files for using in IntelliJ, such as: |
| - iml |
| - .idea/compiler.xml |
| - .idea/misc.xml |
| - .idea/modules.xml |
| - .idea/vcs.xml |
| - .idea/.name |
| - .idea/copyright/Apache_2.xml |
| - .idea/copyright/progiles_settings.xml |
| |
| - Sample usage: |
| - Change directory to AOSP root first. |
| $ cd /user/home/aosp/ |
| - Generating project files under packages/apps/Settings folder. |
| $ aidegen packages/apps/Settings |
| or |
| $ aidegen Settings |
| or |
| $ cd packages/apps/Settings;aidegen |
| """ |
| |
| from __future__ import absolute_import |
| |
| import argparse |
| import logging |
| import os |
| import sys |
| import traceback |
| |
| from aidegen import constant |
| from aidegen.lib.android_dev_os import AndroidDevOS |
| from aidegen.lib import common_util |
| from aidegen.lib.common_util import COLORED_INFO |
| from aidegen.lib.common_util import COLORED_PASS |
| from aidegen.lib.common_util import is_android_root |
| from aidegen.lib.common_util import time_logged |
| from aidegen.lib.errors import AIDEgenError |
| from aidegen.lib.errors import IDENotExistError |
| from aidegen.lib.ide_util import IdeUtil |
| from aidegen.lib.metrics import log_usage |
| from aidegen.lib.metrics import starts_asuite_metrics |
| from aidegen.lib.metrics import ends_asuite_metrics |
| from aidegen.lib.module_info_util import generate_module_info_json |
| from aidegen.lib.project_file_gen import generate_eclipse_project_files |
| from aidegen.lib.project_file_gen import generate_ide_project_files |
| from aidegen.lib.project_info import ProjectInfo |
| from aidegen.lib.source_locator import multi_projects_locate_source |
| |
| AIDEGEN_REPORT_LINK = ('To report the AIDEGen tool problem, please use this ' |
| 'link: https://goto.google.com/aidegen-bug') |
| _NO_LAUNCH_IDE_CMD = """ |
| Can not find IDE in path: {}, you can: |
| - add IDE executable to your $PATH |
| or - specify the exact IDE executable path by "aidegen -p" |
| or - specify "aidegen -n" to generate project file only |
| """ |
| |
| _CONGRATULATION = COLORED_PASS('CONGRATULATION:') |
| _LAUNCH_SUCCESS_MSG = ( |
| 'IDE launched successfully. Please check your IDE window.') |
| _IDE_CACHE_REMINDER_MSG = ( |
| 'To prevent the existed IDE cache from impacting your IDE dependency ' |
| 'analysis, please consider to clear IDE caches if necessary. To do that, in' |
| ' IntelliJ IDEA, go to [File > Invalidate Caches / Restart...].') |
| |
| _SKIP_BUILD_INFO = ('If you are sure the related modules and dependencies have ' |
| 'been already built, please try to use command {} to skip ' |
| 'the building process.') |
| _MAX_TIME = 1 |
| _SKIP_BUILD_INFO_FUTURE = ''.join([ |
| 'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME), |
| _SKIP_BUILD_INFO.rstrip('.'), ' in the future.' |
| ]) |
| _SKIP_BUILD_CMD = 'aidegen {} -s' |
| _INFO = COLORED_INFO('INFO:') |
| _SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format( |
| COLORED_INFO('aidegen [ module(s) ] -s')) |
| _TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG) |
| _LOG_FORMAT = '%(asctime)s %(filename)s:%(lineno)s:%(levelname)s: %(message)s' |
| _DATE_FORMAT = '%Y-%m-%d %H:%M:%S' |
| |
| |
| def _parse_args(args): |
| """Parse command line arguments. |
| |
| Args: |
| args: A list of arguments. |
| |
| Returns: |
| An argparse.Namespace class instance holding parsed args. |
| """ |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| usage=('aidegen [module_name1 module_name2... ' |
| 'project_path1 project_path2...]')) |
| parser.required = False |
| parser.add_argument( |
| 'targets', |
| type=str, |
| nargs='*', |
| default=[''], |
| help=('Android module name or path.' |
| 'e.g. Settings or packages/apps/Settings')) |
| parser.add_argument( |
| '-d', |
| '--depth', |
| type=int, |
| choices=range(10), |
| default=0, |
| help='The depth of module referenced by source.') |
| parser.add_argument( |
| '-v', |
| '--verbose', |
| action='store_true', |
| help='Display DEBUG level logging.') |
| parser.add_argument( |
| '-i', |
| '--ide', |
| default=['j'], |
| help='Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse.') |
| parser.add_argument( |
| '-p', |
| '--ide-path', |
| dest='ide_installed_path', |
| help='IDE installed path.') |
| parser.add_argument( |
| '-n', '--no_launch', action='store_true', help='Do not launch IDE.') |
| parser.add_argument( |
| '-r', |
| '--config-reset', |
| dest='config_reset', |
| action='store_true', |
| help='Reset all saved configurations, e.g., preferred IDE version.') |
| parser.add_argument( |
| '-s', |
| '--skip-build', |
| dest='skip_build', |
| action='store_true', |
| help=('Skip building jar or modules that create java files in build ' |
| 'time, e.g. R/AIDL/Logtags.')) |
| parser.add_argument( |
| '-a', |
| '--android-tree', |
| dest='android_tree', |
| action='store_true', |
| help='Generate whole Android source tree project file for IDE.') |
| return parser.parse_args(args) |
| |
| |
| def _configure_logging(verbose): |
| """Configure the logger. |
| |
| Args: |
| verbose: A boolean. If true, display DEBUG level logs. |
| """ |
| log_format = _LOG_FORMAT |
| datefmt = _DATE_FORMAT |
| level = logging.DEBUG if verbose else logging.INFO |
| logging.basicConfig(level=level, format=log_format, datefmt=datefmt) |
| |
| |
| def _get_ide_util_instance(args): |
| """Get an IdeUtil class instance for launching IDE. |
| |
| Args: |
| args: A list of arguments. |
| |
| Returns: |
| A IdeUtil class instance. |
| """ |
| if args.no_launch: |
| return None |
| ide_util_obj = IdeUtil(args.ide_installed_path, args.ide[0], |
| args.config_reset, |
| AndroidDevOS.MAC == AndroidDevOS.get_os_type()) |
| if not ide_util_obj.is_ide_installed(): |
| ipath = args.ide_installed_path or ide_util_obj.get_default_path() |
| err = _NO_LAUNCH_IDE_CMD.format(ipath) |
| logging.error(err) |
| raise IDENotExistError(err) |
| return ide_util_obj |
| |
| |
| def _check_skip_build(args): |
| """Check if users skip building target, display the warning message. |
| |
| Args: |
| args: A list of arguments. |
| """ |
| if not args.skip_build: |
| msg = _SKIP_BUILD_INFO.format( |
| COLORED_INFO(_SKIP_BUILD_CMD.format(' '.join(args.targets)))) |
| print('\n{} {}\n'.format(_INFO, msg)) |
| |
| |
| def _generate_project_files(ide, projects): |
| """Generate project files by IDE type. |
| |
| Args: |
| ide: A character to represent IDE type. |
| projects: A list of ProjectInfo instances. |
| """ |
| if ide.lower() == 'e': |
| generate_eclipse_project_files(projects) |
| else: |
| generate_ide_project_files(projects) |
| |
| |
| def _compile_targets_for_whole_android_tree(atest_module_info, targets, cwd): |
| """Compile a list of targets to include whole Android tree in the project. |
| |
| Adding the whole Android tree to the project will do two things, |
| 1. If current working directory is not Android root, change the target to |
| its relative path to root and change current working directory to root. |
| If we don't change directory it's hard to deal with the whole Android |
| tree together with the sub-project. |
| 2. If the whole Android tree target is not in the target list, insert it to |
| the first one. |
| |
| Args: |
| atest_module_info: A instance of atest module-info object. |
| targets: A list of targets to be built. |
| cwd: A path of current working directory. |
| |
| Returns: |
| A list of targets after adjustment. |
| """ |
| new_targets = [] |
| if is_android_root(cwd): |
| new_targets = list(targets) |
| else: |
| for target in targets: |
| _, abs_path = common_util.get_related_paths(atest_module_info, |
| target) |
| rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH) |
| new_targets.append(rel_path) |
| os.chdir(constant.ANDROID_ROOT_PATH) |
| |
| if new_targets[0] != '': |
| new_targets.insert(0, '') |
| return new_targets |
| |
| |
| def _launch_ide(ide_util_obj, project_absolute_path): |
| """Launch IDE through ide_util instance. |
| |
| To launch IDE, |
| 1. Set IDE config. |
| 2. For IntelliJ, use .idea as open target is better than .iml file, |
| because open the latter is like to open a kind of normal file. |
| 3. Show _LAUNCH_SUCCESS_MSG to remind users IDE being launched. |
| |
| Args: |
| ide_util_obj: An ide_util instance. |
| project_absolute_path: A string of project absolute path. |
| """ |
| ide_util_obj.config_ide() |
| ide_util_obj.launch_ide(project_absolute_path) |
| print('\n{} {}\n'.format(_CONGRATULATION, _LAUNCH_SUCCESS_MSG)) |
| |
| |
| def _check_whole_android_tree(atest_module_info, targets, android_tree): |
| """Check if it's a building project file for the whole Android tree. |
| |
| The rules: |
| 1. If users command aidegen under Android root, e.g., |
| root$ aidegen |
| that implies users would like to launch the whole Android tree, AIDEGen |
| should set the flag android_tree True. |
| 2. If android_tree is True, add whole Android tree to the project. |
| |
| Args: |
| atest_module_info: A instance of atest module-info object. |
| targets: A list of targets to be built. |
| android_tree: A boolean, True if it's a whole Android tree case, |
| otherwise False. |
| |
| Returns: |
| A list of targets to be built. |
| """ |
| cwd = os.getcwd() |
| if not android_tree and is_android_root(cwd) and targets == ['']: |
| android_tree = True |
| new_targets = targets |
| if android_tree: |
| new_targets = _compile_targets_for_whole_android_tree( |
| atest_module_info, targets, cwd) |
| return new_targets |
| |
| |
| @time_logged(message=_TIME_EXCEED_MSG, maximum=_MAX_TIME) |
| def main_with_message(args): |
| """Main entry with skip build message. |
| |
| Args: |
| args: A list of system arguments. |
| """ |
| aidegen_main(args) |
| |
| |
| @time_logged |
| def main_without_message(args): |
| """Main entry without skip build message. |
| |
| Args: |
| args: A list of system arguments. |
| """ |
| aidegen_main(args) |
| |
| |
| # pylint: disable=broad-except |
| def main(argv): |
| """Main entry. |
| |
| Try to generates project files for using in IDE. |
| |
| Args: |
| argv: A list of system arguments. |
| """ |
| exit_code = constant.EXIT_CODE_NORMAL |
| try: |
| args = _parse_args(argv) |
| _configure_logging(args.verbose) |
| starts_asuite_metrics() |
| if args.skip_build: |
| main_without_message(args) |
| else: |
| main_with_message(args) |
| except BaseException as err: |
| exit_code = constant.EXIT_CODE_EXCEPTION |
| _, exc_value, exc_traceback = sys.exc_info() |
| if isinstance(err, AIDEgenError): |
| exit_code = constant.EXIT_CODE_AIDEGEN_EXCEPTION |
| # Filter out sys.Exit(0) case, which is not an exception case. |
| if isinstance(err, SystemExit) and exc_value.code == 0: |
| exit_code = constant.EXIT_CODE_NORMAL |
| finally: |
| if exit_code is not constant.EXIT_CODE_NORMAL: |
| error_message = str(exc_value) |
| traceback_list = traceback.format_tb(exc_traceback) |
| traceback_list.append(error_message) |
| traceback_str = ''.join(traceback_list) |
| # print out the trackback message for developers to debug |
| print(traceback_str) |
| ends_asuite_metrics(exit_code, traceback_str, error_message) |
| else: |
| ends_asuite_metrics(exit_code) |
| |
| |
| def aidegen_main(args): |
| """AIDEGen main entry. |
| |
| Try to generates project files for using in IDE. |
| |
| Args: |
| args: A list of system arguments. |
| """ |
| log_usage() |
| # Pre-check for IDE relevant case, then handle dependency graph job. |
| ide_util_obj = _get_ide_util_instance(args) |
| _check_skip_build(args) |
| atest_module_info = common_util.get_atest_module_info(args.targets) |
| targets = _check_whole_android_tree(atest_module_info, args.targets, |
| args.android_tree) |
| ProjectInfo.modules_info = generate_module_info_json( |
| atest_module_info, targets, args.verbose, args.skip_build) |
| projects = ProjectInfo.generate_projects(atest_module_info, targets) |
| multi_projects_locate_source(projects, args.verbose, args.depth, |
| constant.IDE_NAME_DICT[args.ide[0]], |
| args.skip_build) |
| _generate_project_files(args.ide[0], projects) |
| if ide_util_obj: |
| _launch_ide(ide_util_obj, projects[0].project_absolute_path) |
| |
| |
| if __name__ == '__main__': |
| try: |
| main(sys.argv[1:]) |
| finally: |
| print('\n{0} {1}\n\n{0} {2}\n'.format(_INFO, AIDEGEN_REPORT_LINK, |
| _IDE_CACHE_REMINDER_MSG)) |