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.
329 lines
11 KiB
329 lines
11 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.
|
|
|
|
"""module_info_util
|
|
|
|
This module receives a module path which is relative to its root directory and
|
|
makes a command to generate two json files, one for mk files and one for bp
|
|
files. Then it will load these two json files into two json dictionaries,
|
|
merge them into one dictionary and return the merged dictionary to its caller.
|
|
|
|
Example usage:
|
|
merged_dict = generate_merged_module_info()
|
|
"""
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
import sys
|
|
|
|
from aidegen import constant
|
|
from aidegen.lib import common_util
|
|
from aidegen.lib import errors
|
|
from aidegen.lib import project_config
|
|
|
|
from atest import atest_utils
|
|
|
|
_MERGE_NEEDED_ITEMS = [
|
|
constant.KEY_CLASS,
|
|
constant.KEY_PATH,
|
|
constant.KEY_INSTALLED,
|
|
constant.KEY_DEPENDENCIES,
|
|
constant.KEY_SRCS,
|
|
constant.KEY_SRCJARS,
|
|
constant.KEY_CLASSES_JAR,
|
|
constant.KEY_TAG,
|
|
constant.KEY_COMPATIBILITY,
|
|
constant.KEY_AUTO_TEST_CONFIG,
|
|
constant.KEY_MODULE_NAME,
|
|
constant.KEY_TEST_CONFIG
|
|
]
|
|
_INTELLIJ_PROJECT_FILE_EXT = '*.iml'
|
|
_LAUNCH_PROJECT_QUERY = (
|
|
'There exists an IntelliJ project file: %s. Do you want '
|
|
'to launch it (yes/No)?')
|
|
_BUILD_BP_JSON_ENV_ON = {
|
|
constant.GEN_JAVA_DEPS: 'true',
|
|
constant.GEN_CC_DEPS: 'true',
|
|
constant.GEN_COMPDB: 'true',
|
|
constant.GEN_RUST: 'true'
|
|
}
|
|
_GEN_JSON_FAILED = (
|
|
'Generate new {0} failed, AIDEGen will proceed and reuse the old {1}.')
|
|
_TARGET = 'nothing'
|
|
_LINKFILE_WARNING = (
|
|
'File {} does not exist and we can not make a symbolic link for it.')
|
|
_RUST_PROJECT_JSON = 'out/soong/rust-project.json'
|
|
|
|
|
|
# pylint: disable=dangerous-default-value
|
|
@common_util.back_to_cwd
|
|
@common_util.time_logged
|
|
def generate_merged_module_info(env_on=_BUILD_BP_JSON_ENV_ON):
|
|
"""Generate a merged dictionary.
|
|
|
|
Linked functions:
|
|
_build_bp_info(module_info, project, verbose, skip_build)
|
|
_get_soong_build_json_dict()
|
|
_merge_dict(mk_dict, bp_dict)
|
|
|
|
Args:
|
|
env_on: A dictionary of environment settings to be turned on, the
|
|
default value is _BUILD_BP_JSON_ENV_ON.
|
|
|
|
Returns:
|
|
A merged dictionary from module-info.json and module_bp_java_deps.json.
|
|
"""
|
|
config = project_config.ProjectConfig.get_instance()
|
|
module_info = config.atest_module_info
|
|
projects = config.targets
|
|
verbose = True
|
|
skip_build = config.is_skip_build
|
|
main_project = projects[0] if projects else None
|
|
_build_bp_info(
|
|
module_info, main_project, verbose, skip_build, env_on)
|
|
json_path = common_util.get_blueprint_json_path(
|
|
constant.BLUEPRINT_JAVA_JSONFILE_NAME)
|
|
bp_dict = common_util.get_json_dict(json_path)
|
|
return _merge_dict(module_info.name_to_module_info, bp_dict)
|
|
|
|
|
|
def _build_bp_info(module_info, main_project=None, verbose=False,
|
|
skip_build=False, env_on=_BUILD_BP_JSON_ENV_ON):
|
|
"""Make nothing to create module_bp_java_deps.json, module_bp_cc_deps.json.
|
|
|
|
Use atest build method to build the target 'nothing' by setting env config
|
|
SOONG_COLLECT_JAVA_DEPS to true to trigger the process of collecting
|
|
dependencies and generate module_bp_java_deps.json etc.
|
|
|
|
Args:
|
|
module_info: A ModuleInfo instance contains data of module-info.json.
|
|
main_project: A string of the main project name.
|
|
verbose: A boolean, if true displays full build output.
|
|
skip_build: A boolean, if true, skip building if
|
|
get_blueprint_json_path(file_name) file exists, otherwise
|
|
build it.
|
|
env_on: A dictionary of environment settings to be turned on, the
|
|
default value is _BUILD_BP_JSON_ENV_ON.
|
|
|
|
Build results:
|
|
1. Build successfully return.
|
|
2. Build failed:
|
|
1) There's no project file, raise BuildFailureError.
|
|
2) There exists a project file, ask users if they want to
|
|
launch IDE with the old project file.
|
|
a) If the answer is yes, return.
|
|
b) If the answer is not yes, sys.exit(1)
|
|
"""
|
|
file_paths = _get_generated_json_files(env_on)
|
|
files_exist = all([os.path.isfile(fpath) for fpath in file_paths])
|
|
files = '\n'.join(file_paths)
|
|
if skip_build and files_exist:
|
|
logging.info('Files:\n%s exist, skipping build.', files)
|
|
return
|
|
original_file_mtimes = {f: None for f in file_paths}
|
|
if files_exist:
|
|
original_file_mtimes = {f: os.path.getmtime(f) for f in file_paths}
|
|
|
|
logging.warning(
|
|
'\nGenerate files:\n %s by atest build method.', files)
|
|
build_with_on_cmd = atest_utils.build([_TARGET], verbose, env_on)
|
|
|
|
# For Android Rust projects, we need to create a symbolic link to the file
|
|
# out/soong/rust-project.json to launch the rust projects in IDEs.
|
|
_generate_rust_project_link()
|
|
|
|
if build_with_on_cmd:
|
|
logging.info('\nGenerate blueprint json successfully.')
|
|
else:
|
|
if not all([_is_new_json_file_generated(
|
|
f, original_file_mtimes[f]) for f in file_paths]):
|
|
if files_exist:
|
|
_show_files_reuse_message(file_paths)
|
|
else:
|
|
_show_build_failed_message(module_info, main_project)
|
|
|
|
|
|
def _get_generated_json_files(env_on=_BUILD_BP_JSON_ENV_ON):
|
|
"""Gets the absolute paths of the files which is going to be generated.
|
|
|
|
Determine the files which will be generated by the environment on dictionary
|
|
and the default blueprint json files' dictionary.
|
|
The generation of json files depends on env_on. If the env_on looks like,
|
|
_BUILD_BP_JSON_ENV_ON = {
|
|
'SOONG_COLLECT_JAVA_DEPS': 'true',
|
|
'SOONG_COLLECT_CC_DEPS': 'true',
|
|
'SOONG_GEN_COMPDB': 'true',
|
|
'SOONG_GEN_RUST_PROJECT': 'true'
|
|
}
|
|
We want to generate 4 files: module_bp_java_deps.json,
|
|
module_bp_cc_deps.json, compile_commands.json and rust-project.json. And in
|
|
get_blueprint_json_files_relative_dict function, there are 4 json files
|
|
by default and return a result list of the absolute paths of the existent
|
|
files.
|
|
|
|
Args:
|
|
env_on: A dictionary of environment settings to be turned on, the
|
|
default value is _BUILD_BP_JSON_ENV_ON.
|
|
|
|
Returns:
|
|
A list of the absolute paths of the files which is going to be
|
|
generated.
|
|
"""
|
|
json_files_dict = common_util.get_blueprint_json_files_relative_dict()
|
|
file_paths = []
|
|
for key in env_on:
|
|
if not env_on[key] == 'true' or key not in json_files_dict:
|
|
continue
|
|
file_paths.append(json_files_dict[key])
|
|
return file_paths
|
|
|
|
|
|
def _show_files_reuse_message(file_paths):
|
|
"""Shows the message of build failure but files existing and reusing them.
|
|
|
|
Args:
|
|
file_paths: A list of absolute file paths to be checked.
|
|
"""
|
|
failed_or_file = ' or '.join(file_paths)
|
|
failed_and_file = ' and '.join(file_paths)
|
|
message = _GEN_JSON_FAILED.format(failed_or_file, failed_and_file)
|
|
print(constant.WARN_MSG.format(
|
|
common_util.COLORED_INFO('Warning:'), message))
|
|
|
|
|
|
def _show_build_failed_message(module_info, main_project=None):
|
|
"""Show build failed message.
|
|
|
|
Args:
|
|
module_info: A ModuleInfo instance contains data of module-info.json.
|
|
main_project: A string of the main project name.
|
|
"""
|
|
if main_project:
|
|
_, main_project_path = common_util.get_related_paths(
|
|
module_info, main_project)
|
|
_build_failed_handle(main_project_path)
|
|
|
|
|
|
def _is_new_json_file_generated(json_path, original_file_mtime):
|
|
"""Check the new file is generated or not.
|
|
|
|
Args:
|
|
json_path: The path of the json file being to check.
|
|
original_file_mtime: the original file modified time.
|
|
|
|
Returns:
|
|
A boolean, True if the json_path file is new generated, otherwise False.
|
|
"""
|
|
if not os.path.isfile(json_path):
|
|
return False
|
|
return original_file_mtime != os.path.getmtime(json_path)
|
|
|
|
|
|
def _build_failed_handle(main_project_path):
|
|
"""Handle build failures.
|
|
|
|
Args:
|
|
main_project_path: The main project directory.
|
|
|
|
Handle results:
|
|
1) There's no project file, raise BuildFailureError.
|
|
2) There exists a project file, ask users if they want to
|
|
launch IDE with the old project file.
|
|
a) If the answer is yes, return.
|
|
b) If the answer is not yes, sys.exit(1)
|
|
"""
|
|
project_file = glob.glob(
|
|
os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT))
|
|
if project_file:
|
|
query = _LAUNCH_PROJECT_QUERY % project_file[0]
|
|
input_data = input(query)
|
|
if not input_data.lower() in ['yes', 'y']:
|
|
sys.exit(1)
|
|
else:
|
|
raise errors.BuildFailureError(
|
|
'Failed to generate %s.' % common_util.get_blueprint_json_path(
|
|
constant.BLUEPRINT_JAVA_JSONFILE_NAME))
|
|
|
|
|
|
def _merge_module_keys(m_dict, b_dict):
|
|
"""Merge a module's dictionary into another module's dictionary.
|
|
|
|
Merge b_dict module data into m_dict.
|
|
|
|
Args:
|
|
m_dict: The module dictionary is going to merge b_dict into.
|
|
b_dict: Soong build system module dictionary.
|
|
"""
|
|
for key, b_modules in b_dict.items():
|
|
m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules)))
|
|
|
|
|
|
def _copy_needed_items_from(mk_dict):
|
|
"""Shallow copy needed items from Make build system module info dictionary.
|
|
|
|
Args:
|
|
mk_dict: Make build system dictionary is going to be copied.
|
|
|
|
Returns:
|
|
A merged dictionary.
|
|
"""
|
|
merged_dict = dict()
|
|
for module in mk_dict.keys():
|
|
merged_dict[module] = dict()
|
|
for key in mk_dict[module].keys():
|
|
if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []:
|
|
merged_dict[module][key] = mk_dict[module][key]
|
|
return merged_dict
|
|
|
|
|
|
def _merge_dict(mk_dict, bp_dict):
|
|
"""Merge two dictionaries.
|
|
|
|
Linked function:
|
|
_merge_module_keys(m_dict, b_dict)
|
|
|
|
Args:
|
|
mk_dict: Make build system module info dictionary.
|
|
bp_dict: Soong build system module info dictionary.
|
|
|
|
Returns:
|
|
A merged dictionary.
|
|
"""
|
|
merged_dict = _copy_needed_items_from(mk_dict)
|
|
for module in bp_dict.keys():
|
|
if module not in merged_dict.keys():
|
|
merged_dict[module] = dict()
|
|
_merge_module_keys(merged_dict[module], bp_dict[module])
|
|
return merged_dict
|
|
|
|
|
|
def _generate_rust_project_link():
|
|
"""Generates out/soong/rust-project.json symbolic link in Android root."""
|
|
root_dir = common_util.get_android_root_dir()
|
|
rust_project = os.path.join(
|
|
root_dir, common_util.get_blueprint_json_path(
|
|
constant.RUST_PROJECT_JSON))
|
|
if not os.path.isfile(rust_project):
|
|
message = _LINKFILE_WARNING.format(_RUST_PROJECT_JSON)
|
|
print(constant.WARN_MSG.format(
|
|
common_util.COLORED_INFO('Warning:'), message))
|
|
return
|
|
link_rust = os.path.join(root_dir, constant.RUST_PROJECT_JSON)
|
|
if os.path.islink(link_rust):
|
|
os.remove(link_rust)
|
|
os.symlink(rust_project, link_rust)
|