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

#!/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)