blob: 1d14fc97c1369d1fc2d7bc1778bcd6fcc2811d87 [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(atest_module_info, project, verbose)
"""
import glob
import json
import logging
import os
import subprocess
import sys
from aidegen import constant
from aidegen.lib import common_util
from aidegen.lib import errors
_BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
_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
]
_INTELLIJ_PROJECT_FILE_EXT = '*.iml'
_LAUNCH_PROJECT_QUERY = (
'There exists an IntelliJ project file: %s. Do you want '
'to launch it (yes/No)?')
_GENERATE_JSON_COMMAND = ('SOONG_COLLECT_JAVA_DEPS=false make nothing -C {DIR};'
'SOONG_COLLECT_JAVA_DEPS=true make nothing -C {DIR}')
@common_util.time_logged
def generate_merged_module_info(module_info, projects=None, verbose=False,
skip_build=False):
"""Generate a merged dictionary.
Change directory to Android root path before making _GENERATE_JSON_COMMAND
to avoid command error: "make: *** No rule to make target 'nothing'. Stop."
and change back to current directory after command completed.
Linked functions:
_build_target(project, verbose)
_get_soong_build_json_dict()
_merge_dict(mk_dict, bp_dict)
Args:
module_info: A ModuleInfo instance contains data of module-info.json.
projects: A list of project names.
verbose: A boolean, if true displays full build output.
skip_build: A boolean, if true skip building
get_blueprint_json_path() if it exists, otherwise
build it.
Returns:
A merged dictionary from module-info.json and module_bp_java_deps.json.
"""
main_project = projects[0] if projects else None
cmd = [_GENERATE_JSON_COMMAND.format(
DIR=common_util.get_android_root_dir())]
_build_target(module_info, cmd, main_project, verbose, skip_build)
bp_dict = _get_soong_build_json_dict()
return _merge_dict(module_info.name_to_module_info, bp_dict)
def _build_target(module_info, cmd, main_project=None, verbose=False,
skip_build=False):
"""Make nothing to generate module_bp_java_deps.json.
We build without environment setting SOONG_COLLECT_JAVA_DEPS and then build
with environment setting SOONG_COLLECT_JAVA_DEPS. In this way we can trigger
the process of collecting dependencies and generating
module_bp_java_deps.json.
Args:
module_info: A ModuleInfo instance contains data of module-info.json.
cmd: A string list, build command.
main_project: 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 exists, otherwise
build it.
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)
"""
json_path = common_util.get_blueprint_json_path()
original_json_mtime = None
if os.path.isfile(json_path):
if skip_build:
logging.info('%s file exists, skipping build.',
common_util.get_blueprint_json_path())
return
original_json_mtime = os.path.getmtime(json_path)
try:
if verbose:
full_env_vars = os.environ.copy()
subprocess.check_call(
cmd, stderr=subprocess.STDOUT, env=full_env_vars, shell=True)
else:
subprocess.check_call(cmd, shell=True)
logging.info('Build successfully: %s.', cmd)
except subprocess.CalledProcessError:
if not _is_new_json_file_generated(json_path, original_json_mtime):
if os.path.isfile(json_path):
message = ('Generate new {0} failed, AIDEGen will proceed and '
'reuse the old {0}.'.format(json_path))
print('\n{} {}\n'.format(common_util.COLORED_INFO('Warning:'),
message))
else:
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 original_file_mtime:
return os.path.isfile(json_path)
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())
def _get_soong_build_json_dict():
"""Load a json file from path and convert it into a json dictionary.
Returns:
A dictionary loaded from the blueprint json file.
"""
json_path = common_util.get_blueprint_json_path()
try:
with open(json_path) as jfile:
json_dict = json.load(jfile)
return json_dict
except IOError as err:
raise errors.JsonFileNotExistError(
'%s does not exist, error: %s.' % (json_path, err))
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 not module in merged_dict.keys():
merged_dict[module] = dict()
_merge_module_keys(merged_dict[module], bp_dict[module])
return merged_dict