#!/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.aidegen_metrics import starts_asuite_metrics
|
from aidegen.lib.aidegen_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.
|
"""
|
# 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))
|