You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
546 lines
20 KiB
546 lines
20 KiB
#!/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 import aidegen_metrics
|
|
from aidegen.lib import common_util
|
|
from aidegen.lib import eclipse_project_file_gen
|
|
from aidegen.lib import errors
|
|
from aidegen.lib import ide_util
|
|
from aidegen.lib import module_info
|
|
from aidegen.lib import native_module_info
|
|
from aidegen.lib import native_project_info
|
|
from aidegen.lib import native_util
|
|
from aidegen.lib import project_config
|
|
from aidegen.lib import project_file_gen
|
|
from aidegen.lib import project_info
|
|
from aidegen.vscode import vscode_native_project_file_gen
|
|
from aidegen.vscode import vscode_workspace_file_gen
|
|
|
|
AIDEGEN_REPORT_LINK = ('To report an AIDEGen tool problem, please use this '
|
|
'link: https://goto.google.com/aidegen-bug')
|
|
_CONGRATULATIONS = common_util.COLORED_PASS('CONGRATULATIONS:')
|
|
_LAUNCH_SUCCESS_MSG = (
|
|
'IDE launched successfully. Please check your IDE window.')
|
|
_LAUNCH_ECLIPSE_SUCCESS_MSG = (
|
|
'The project files .classpath and .project are generated under '
|
|
'{PROJECT_PATH} and AIDEGen doesn\'t import the project automatically, '
|
|
'please import the project manually by steps: File -> Import -> select \''
|
|
'General\' -> \'Existing Projects into Workspace\' -> click \'Next\' -> '
|
|
'Choose the root directory -> click \'Finish\'.')
|
|
_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...].')
|
|
|
|
_MAX_TIME = 1
|
|
_SKIP_BUILD_INFO_FUTURE = ''.join([
|
|
'AIDEGen build time exceeds {} minute(s).\n'.format(_MAX_TIME),
|
|
project_config.SKIP_BUILD_INFO.rstrip('.'), ' in the future.'
|
|
])
|
|
_INFO = common_util.COLORED_INFO('INFO:')
|
|
_SKIP_MSG = _SKIP_BUILD_INFO_FUTURE.format(
|
|
common_util.COLORED_INFO('aidegen [ module(s) ] -s'))
|
|
_TIME_EXCEED_MSG = '\n{} {}\n'.format(_INFO, _SKIP_MSG)
|
|
_LAUNCH_CLION_IDES = [
|
|
constant.IDE_CLION, constant.IDE_INTELLIJ, constant.IDE_ECLIPSE]
|
|
_CHOOSE_LANGUAGE_MSG = ('The scope of your modules contains {} different '
|
|
'languages as follows:\n{}\nPlease select the one you '
|
|
'would like to implement.\t')
|
|
_LANGUAGE_OPTIONS = [constant.JAVA, constant.C_CPP]
|
|
_NO_ANY_PROJECT_EXIST = 'There is no Java, C/C++ or Rust target.'
|
|
|
|
|
|
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=['u'],
|
|
help=('Launch IDE type, j: IntelliJ, s: Android Studio, e: Eclipse, '
|
|
'c: CLion, v: VS Code. The default value is \'u\': undefined.'))
|
|
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 jars 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.')
|
|
parser.add_argument(
|
|
'-e',
|
|
'--exclude-paths',
|
|
dest='exclude_paths',
|
|
nargs='*',
|
|
help='Exclude the directories in IDE.')
|
|
parser.add_argument(
|
|
'-V',
|
|
'--version',
|
|
action='store_true',
|
|
help='Print aidegen version string.')
|
|
parser.add_argument(
|
|
'-l',
|
|
'--language',
|
|
default=['u'],
|
|
help=('Launch IDE with a specific language, j: Java, c: C/C++, r: '
|
|
'Rust. The default value is \'u\': undefined.'))
|
|
return parser.parse_args(args)
|
|
|
|
|
|
def _generate_project_files(projects):
|
|
"""Generate project files by IDE type.
|
|
|
|
Args:
|
|
projects: A list of ProjectInfo instances.
|
|
"""
|
|
config = project_config.ProjectConfig.get_instance()
|
|
if config.ide_name == constant.IDE_ECLIPSE:
|
|
eclipse_project_file_gen.EclipseConf.generate_ide_project_files(
|
|
projects)
|
|
else:
|
|
project_file_gen.ProjectFileGenerator.generate_ide_project_files(
|
|
projects)
|
|
|
|
|
|
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(project_absolute_path)
|
|
if ide_util_obj.ide_name() == constant.IDE_ECLIPSE:
|
|
launch_msg = ' '.join([_LAUNCH_SUCCESS_MSG,
|
|
_LAUNCH_ECLIPSE_SUCCESS_MSG.format(
|
|
PROJECT_PATH=project_absolute_path)])
|
|
else:
|
|
launch_msg = _LAUNCH_SUCCESS_MSG
|
|
print('\n{} {}\n'.format(_CONGRATULATIONS, launch_msg))
|
|
print('\n{} {}\n'.format(_INFO, _IDE_CACHE_REMINDER_MSG))
|
|
# Send the end message to Clearcut server before launching IDE to make sure
|
|
# the execution time is correct.
|
|
aidegen_metrics.ends_asuite_metrics(constant.EXIT_CODE_EXCEPTION)
|
|
ide_util_obj.launch_ide()
|
|
|
|
|
|
def _launch_native_projects(ide_util_obj, args, cmakelists):
|
|
"""Launches C/C++ projects with IDE.
|
|
|
|
AIDEGen provides the IDE argument for CLion, but there's still a implicit
|
|
way to launch it. The rules to launch it are:
|
|
1. If no target IDE, we don't have to launch any IDE for C/C++ project.
|
|
2. If the target IDE is IntelliJ or Eclipse, we should launch C/C++
|
|
projects with CLion.
|
|
|
|
Args:
|
|
ide_util_obj: An ide_util instance.
|
|
args: An argparse.Namespace class instance holding parsed args.
|
|
cmakelists: A list of CMakeLists.txt file paths.
|
|
"""
|
|
if not ide_util_obj:
|
|
return
|
|
native_ide_util_obj = ide_util_obj
|
|
ide_name = constant.IDE_NAME_DICT[args.ide[0]]
|
|
if ide_name in _LAUNCH_CLION_IDES:
|
|
native_ide_util_obj = ide_util.get_ide_util_instance('c')
|
|
if native_ide_util_obj:
|
|
_launch_ide(native_ide_util_obj, ' '.join(cmakelists))
|
|
|
|
|
|
def _create_and_launch_java_projects(ide_util_obj, targets):
|
|
"""Launches Android of Java(Kotlin) projects with IDE.
|
|
|
|
Args:
|
|
ide_util_obj: An ide_util instance.
|
|
targets: A list of build targets.
|
|
"""
|
|
projects = project_info.ProjectInfo.generate_projects(targets)
|
|
project_info.ProjectInfo.multi_projects_locate_source(projects)
|
|
_generate_project_files(projects)
|
|
if ide_util_obj:
|
|
_launch_ide(ide_util_obj, projects[0].project_absolute_path)
|
|
|
|
|
|
def _launch_ide_by_module_contents(args, ide_util_obj, language, jlist=None,
|
|
clist=None, rlist=None, all_langs=False):
|
|
"""Deals with the suitable IDE launch action.
|
|
|
|
The rules of AIDEGen launching IDE with languages are:
|
|
1. If no IDE or language is specific, the priority of the language is:
|
|
a) Java
|
|
aidegen frameworks/base
|
|
launch Java projects of frameworks/base in IntelliJ.
|
|
b) C/C++
|
|
aidegen hardware/interfaces/vibrator/aidl/default
|
|
launch C/C++ project of hardware/interfaces/vibrator/aidl/default
|
|
in CLion.
|
|
c) Rust
|
|
aidegen external/rust/crates/protobuf
|
|
launch Rust project of external/rust/crates/protobuf in VS Code.
|
|
2. If the IDE is specific, launch related projects in the IDE.
|
|
a) aidegen frameworks/base -i j
|
|
launch Java projects of frameworks/base in IntelliJ.
|
|
aidegen frameworks/base -i s
|
|
launch Java projects of frameworks/base in Android Studio.
|
|
aidegen frameworks/base -i e
|
|
launch Java projects of frameworks/base in Eclipse.
|
|
b) aidegen frameworks/base -i c
|
|
launch C/C++ projects of frameworks/base in CLion.
|
|
c) aidegen external/rust/crates/protobuf -i v
|
|
launch Rust project of external/rust/crates/protobuf in VS Code.
|
|
3. If the launguage is specific, launch relative language projects in the
|
|
relative IDE.
|
|
a) aidegen frameworks/base -l j
|
|
launch Java projects of frameworks/base in IntelliJ.
|
|
b) aidegen frameworks/base -l c
|
|
launch C/C++ projects of frameworks/base in CLion.
|
|
c) aidegen external/rust/crates/protobuf -l r
|
|
launch Rust projects of external/rust/crates/protobuf in VS Code.
|
|
4. Both of the IDE and language are specific, launch the IDE with the
|
|
relative language projects. If the IDE conflicts with the language, the
|
|
IDE is prior to the language.
|
|
a) aidegen frameworks/base -i j -l j
|
|
launch Java projects of frameworks/base in IntelliJ.
|
|
b) aidegen frameworks/base -i s -l c
|
|
launch C/C++ projects of frameworks/base in Android Studio.
|
|
c) aidegen frameworks/base -i c -l j
|
|
launch C/C++ projects of frameworks/base in CLion.
|
|
|
|
Args:
|
|
args: A list of system arguments.
|
|
ide_util_obj: An ide_util instance.
|
|
language: A string of the language to be edited in the IDE.
|
|
jlist: A list of Java build targets.
|
|
clist: A list of C/C++ build targets.
|
|
rlist: A list of Rust build targets.
|
|
all_langs: A boolean, True to launch all languages else False.
|
|
"""
|
|
if all_langs:
|
|
_launch_vscode(ide_util_obj, project_info.ProjectInfo.modules_info,
|
|
jlist, clist, rlist)
|
|
return
|
|
if not (jlist or clist or rlist):
|
|
print(constant.WARN_MSG.format(
|
|
common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST))
|
|
return
|
|
if language == constant.JAVA:
|
|
_create_and_launch_java_projects(ide_util_obj, jlist)
|
|
return
|
|
if language == constant.C_CPP:
|
|
native_project_info.NativeProjectInfo.generate_projects(clist)
|
|
native_project_file = native_util.generate_clion_projects(clist)
|
|
if native_project_file:
|
|
_launch_native_projects(ide_util_obj, args, [native_project_file])
|
|
|
|
|
|
def _launch_vscode(ide_util_obj, atest_module_info, jtargets, ctargets,
|
|
rtargets):
|
|
"""Launches targets with VSCode IDE.
|
|
|
|
Args:
|
|
ide_util_obj: An ide_util instance.
|
|
atest_module_info: A ModuleInfo instance contains the data of
|
|
module-info.json.
|
|
jtargets: A list of Java project targets.
|
|
ctargets: A list of C/C++ project targets.
|
|
rtargets: A list of Rust project targets.
|
|
"""
|
|
abs_paths = []
|
|
if jtargets:
|
|
abs_paths.extend(_get_java_project_paths(jtargets, atest_module_info))
|
|
if ctargets:
|
|
abs_paths.extend(_get_cc_project_paths(ctargets))
|
|
if rtargets:
|
|
root_dir = common_util.get_android_root_dir()
|
|
abs_paths.extend(_get_rust_project_paths(rtargets, root_dir))
|
|
if not (jtargets or ctargets or rtargets):
|
|
print(constant.WARN_MSG.format(
|
|
common_util.COLORED_INFO('Warning:'), _NO_ANY_PROJECT_EXIST))
|
|
return
|
|
vs_path = vscode_workspace_file_gen.generate_code_workspace_file(abs_paths)
|
|
if not ide_util_obj:
|
|
return
|
|
_launch_ide(ide_util_obj, vs_path)
|
|
|
|
|
|
def _get_java_project_paths(jtargets, atest_module_info):
|
|
"""Gets the Java absolute project paths from the input Java targets.
|
|
|
|
Args:
|
|
jtargets: A list of strings of Java targets.
|
|
atest_module_info: A ModuleInfo instance contains the data of
|
|
module-info.json.
|
|
|
|
Returns:
|
|
A list of the Java absolute project paths.
|
|
"""
|
|
abs_paths = []
|
|
for target in jtargets:
|
|
_, abs_path = common_util.get_related_paths(atest_module_info, target)
|
|
if abs_path:
|
|
abs_paths.append(abs_path)
|
|
return abs_paths
|
|
|
|
|
|
def _get_cc_project_paths(ctargets):
|
|
"""Gets the C/C++ absolute project paths from the input C/C++ targets.
|
|
|
|
Args:
|
|
ctargets: A list of strings of C/C++ targets.
|
|
|
|
Returns:
|
|
A list of the C/C++ absolute project paths.
|
|
"""
|
|
abs_paths = []
|
|
cc_module_info = native_module_info.NativeModuleInfo()
|
|
native_project_info.NativeProjectInfo.generate_projects(ctargets)
|
|
vs_gen = vscode_native_project_file_gen.VSCodeNativeProjectFileGenerator
|
|
for target in ctargets:
|
|
_, abs_path = common_util.get_related_paths(cc_module_info, target)
|
|
if not abs_path:
|
|
continue
|
|
vs_native = vs_gen(abs_path)
|
|
vs_native.generate_c_cpp_properties_json_file()
|
|
if abs_path not in abs_paths:
|
|
abs_paths.append(abs_path)
|
|
return abs_paths
|
|
|
|
|
|
def _get_rust_project_paths(rtargets, root_dir):
|
|
"""Gets the Rust absolute project paths from the input Rust targets.
|
|
|
|
Args:
|
|
rtargets: A list of strings of Rust targets.
|
|
root_dir: A string of the Android root directory.
|
|
|
|
Returns:
|
|
A list of the Rust absolute project paths.
|
|
"""
|
|
abs_paths = []
|
|
for rtarget in rtargets:
|
|
path = rtarget
|
|
# If rtarget is not an absolute path, make it an absolute one.
|
|
if not common_util.is_source_under_relative_path(rtarget, root_dir):
|
|
path = os.path.join(root_dir, rtarget)
|
|
abs_paths.append(path)
|
|
return abs_paths
|
|
|
|
|
|
@common_util.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)
|
|
|
|
|
|
@common_util.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.
|
|
|
|
Show skip build message in aidegen main process if users command skip_build
|
|
otherwise remind them to use it and include metrics supports.
|
|
|
|
Args:
|
|
argv: A list of system arguments.
|
|
"""
|
|
exit_code = constant.EXIT_CODE_NORMAL
|
|
launch_ide = True
|
|
ask_version = False
|
|
try:
|
|
args = _parse_args(argv)
|
|
if args.version:
|
|
ask_version = True
|
|
version_file = os.path.join(os.path.dirname(__file__),
|
|
constant.VERSION_FILE)
|
|
print(common_util.read_file_content(version_file))
|
|
sys.exit(constant.EXIT_CODE_NORMAL)
|
|
|
|
launch_ide = not args.no_launch
|
|
common_util.configure_logging(args.verbose)
|
|
is_whole_android_tree = project_config.is_whole_android_tree(
|
|
args.targets, args.android_tree)
|
|
references = [constant.ANDROID_TREE] if is_whole_android_tree else []
|
|
aidegen_metrics.starts_asuite_metrics(references)
|
|
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, errors.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
|
|
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)
|
|
aidegen_metrics.ends_asuite_metrics(exit_code, traceback_str,
|
|
error_message)
|
|
# print out the trackback message for developers to debug
|
|
print(traceback_str)
|
|
raise err
|
|
finally:
|
|
if not ask_version:
|
|
print('\n{0} {1}\n'.format(_INFO, AIDEGEN_REPORT_LINK))
|
|
# Send the end message here on ignoring launch IDE case.
|
|
if not launch_ide and exit_code is constant.EXIT_CODE_NORMAL:
|
|
aidegen_metrics.ends_asuite_metrics(exit_code)
|
|
|
|
|
|
def aidegen_main(args):
|
|
"""AIDEGen main entry.
|
|
|
|
Try to generate project files for using in IDE. The process is:
|
|
1. Instantiate a ProjectConfig singleton object and initialize its
|
|
environment. After creating a singleton instance for ProjectConfig,
|
|
other modules can use project configurations by
|
|
ProjectConfig.get_instance().
|
|
2. Get an IDE instance from ide_util, ide_util.get_ide_util_instance will
|
|
use ProjectConfig.get_instance() inside the function.
|
|
3. Setup project_info.ProjectInfo.modules_info by instantiate
|
|
AidegenModuleInfo.
|
|
4. Check if projects contain C/C++ projects and launch related IDE.
|
|
|
|
Args:
|
|
args: A list of system arguments.
|
|
"""
|
|
config = project_config.ProjectConfig(args)
|
|
config.init_environment()
|
|
targets = config.targets
|
|
project_info.ProjectInfo.modules_info = module_info.AidegenModuleInfo()
|
|
cc_module_info = native_module_info.NativeModuleInfo()
|
|
jtargets, ctargets, rtargets = native_util.get_java_cc_and_rust_projects(
|
|
project_info.ProjectInfo.modules_info, cc_module_info, targets)
|
|
config.language, config.ide_name = common_util.determine_language_ide(
|
|
args.language[0], args.ide[0], jtargets, ctargets, rtargets)
|
|
# Called ide_util for pre-check the IDE existence state.
|
|
ide_util_obj = ide_util.get_ide_util_instance(
|
|
constant.IDE_DICT[config.ide_name])
|
|
all_langs = config.ide_name == constant.IDE_VSCODE
|
|
# Backward compatible strategy, when both java and C/C++ module exist,
|
|
# check the preferred target from the user and launch single one.
|
|
_launch_ide_by_module_contents(args, ide_util_obj, config.language,
|
|
jtargets, ctargets, rtargets, all_langs)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|