#!/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_module_info_json(atest_module_info, project, verbose)
|
"""
|
|
import glob
|
import json
|
import logging
|
import os
|
import subprocess
|
import sys
|
|
from aidegen import constant
|
from aidegen.lib.common_util import COLORED_INFO
|
from aidegen.lib.common_util import time_logged
|
from aidegen.lib.common_util import get_related_paths
|
from aidegen.lib import errors
|
|
_BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json'
|
_KEY_CLS = 'class'
|
_KEY_PATH = 'path'
|
_KEY_INS = 'installed'
|
_KEY_DEP = 'dependencies'
|
_KEY_SRCS = 'srcs'
|
_MERGE_NEEDED_ITEMS = [_KEY_CLS, _KEY_PATH, _KEY_INS, _KEY_DEP, _KEY_SRCS]
|
_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;'
|
'SOONG_COLLECT_JAVA_DEPS=true make nothing')
|
|
|
@time_logged
|
def generate_module_info_json(module_info, projects, verbose, skip_build=False):
|
"""Generate a merged json 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_json(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 _BLUEPRINT_JSONFILE_NAME if
|
it exists, otherwise build it.
|
|
Returns:
|
A tuple of Atest module info instance and a merged json dictionary.
|
"""
|
cwd = os.getcwd()
|
os.chdir(constant.ANDROID_ROOT_PATH)
|
_build_target([_GENERATE_JSON_COMMAND], projects[0], module_info, verbose,
|
skip_build)
|
os.chdir(cwd)
|
bp_dict = _get_soong_build_json_dict()
|
return _merge_json(module_info.name_to_module_info, bp_dict)
|
|
|
def _build_target(cmd, main_project, module_info, verbose, 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:
|
cmd: A string list, build command.
|
main_project: The main project name.
|
module_info: A ModuleInfo instance contains data of module-info.json.
|
verbose: A boolean, if true displays full build output.
|
skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if
|
it 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 = _get_blueprint_json_path()
|
original_json_mtime = None
|
if os.path.isfile(json_path):
|
if skip_build:
|
logging.info('%s file exists, skipping build.',
|
_BLUEPRINT_JSONFILE_NAME)
|
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(COLORED_INFO('Warning:'), message))
|
else:
|
_, main_project_path = 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.
|
"""
|
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.' % _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 json dictionary.
|
"""
|
json_path = _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 _get_blueprint_json_path():
|
"""Assemble the path of blueprint json file.
|
|
Returns:
|
Blueprint json path.
|
"""
|
return os.path.join(constant.SOONG_OUT_DIR_PATH, _BLUEPRINT_JSONFILE_NAME)
|
|
|
def _merge_module_keys(m_dict, b_dict):
|
"""Merge a module's json dictionary into another module's json dictionary.
|
|
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 part json dictionary.
|
|
Args:
|
mk_dict: Make build system json dictionary is going to be copyed.
|
|
Returns:
|
A merged json 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_json(mk_dict, bp_dict):
|
"""Merge two json dictionaries.
|
|
Linked function:
|
_merge_module_keys(m_dict, b_dict)
|
|
Args:
|
mk_dict: Make build system part json dictionary.
|
bp_dict: Soong build system part json dictionary.
|
|
Returns:
|
A merged json 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
|