blob: bc447f374fc70fd75277be5409e8a4a4a5e0173f [file] [log] [blame]
#!/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_OFF = {
constant.GEN_JAVA_DEPS: 'false',
constant.GEN_CC_DEPS: 'false',
constant.GEN_COMPDB: 'false'
}
_BUILD_BP_JSON_ENV_ON = {
constant.GEN_JAVA_DEPS: 'true',
constant.GEN_CC_DEPS: 'true',
constant.GEN_COMPDB: 'true'
}
_GEN_JSON_FAILED = (
'Generate new {0} failed, AIDEGen will proceed and reuse the old {1}.')
_WARN_MSG = '\n{} {}\n'
_TARGET = 'nothing'
# pylint: disable=dangerous-default-value
@common_util.back_to_cwd
@common_util.time_logged
def generate_merged_module_info(env_off=_BUILD_BP_JSON_ENV_OFF,
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_off: A dictionary of environment settings to be turned off, the
default value is _BUILD_BP_JSON_ENV_OFF.
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_off, 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_off=_BUILD_BP_JSON_ENV_OFF,
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 false then true. By this way, we can trigger the
process of collecting dependencies and generate module_bp_java_deps.json.
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_off: A dictionary of environment settings to be turned off, the
default value is _BUILD_BP_JSON_ENV_OFF.
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_off_cmd = atest_utils.build([_TARGET], verbose, env_off)
build_with_on_cmd = atest_utils.build([_TARGET], verbose, env_on)
if build_with_off_cmd and 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'
}
We want to generate only two files: module_bp_java_deps.json and
module_bp_cc_deps.json. And in get_blueprint_json_files_relative_dict
function, there are three json files by default. We get the result list by
comparsing with these two dictionaries.
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(_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