blob: 804ae3b4cb8b73f4a1c438107bbd37318a7bd7e6 [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.
"""common_util
This module has a collection of functions that provide helper functions to
other modules.
"""
import logging
import os
import sys
import time
from functools import partial
from functools import wraps
from aidegen import constant
from aidegen.lib import errors
from atest import constants
from atest import module_info
from atest.atest_utils import colorize
COLORED_INFO = partial(colorize, color=constants.MAGENTA, highlight=False)
COLORED_PASS = partial(colorize, color=constants.GREEN, highlight=False)
COLORED_FAIL = partial(colorize, color=constants.RED, highlight=False)
FAKE_MODULE_ERROR = '{} is a fake module.'
OUTSIDE_ROOT_ERROR = '{} is outside android root.'
PATH_NOT_EXISTS_ERROR = 'The path {} doesn\'t exist.'
NO_MODULE_DEFINED_ERROR = 'No modules defined at {}.'
_REBUILD_MODULE_INFO = '%s We should rebuild module-info.json file for it.'
_ENVSETUP_NOT_RUN = ('Please run "source build/envsetup.sh" and "lunch" before '
'running aidegen.')
def time_logged(func=None, *, message='', maximum=1):
"""Decorate a function to find out how much time it spends.
Args:
func: a function is to be calculated its spending time.
message: the message the decorated function wants to show.
maximum: a interger, minutes. If time exceeds the maximum time show
message, otherwise doesn't.
Returns:
The wrapper function.
"""
if func is None:
return partial(time_logged, message=message, maximum=maximum)
@wraps(func)
def wrapper(*args, **kwargs):
"""A wrapper function."""
start = time.time()
try:
return func(*args, **kwargs)
finally:
timestamp = time.time() - start
logging.debug('{}.{} takes: {:.2f}s'.format(
func.__module__, func.__name__, timestamp))
if message and timestamp > maximum * 60:
print(message)
return wrapper
def get_related_paths(atest_module_info, target=None):
"""Get the relative and absolute paths of target from module-info.
Args:
atest_module_info: A ModuleInfo instance.
target: A string user input from command line. It could be several cases
such as:
1. Module name, e.g. Settings
2. Module path, e.g. packages/apps/Settings
3. Relative path, e.g. ../../packages/apps/Settings
4. Current directory, e.g. . or no argument
Return:
rel_path: The relative path of a module, return None if no matching
module found.
abs_path: The absolute path of a module, return None if no matching
module found.
"""
rel_path = None
abs_path = None
if target:
# User inputs a module name.
if atest_module_info.is_module(target):
paths = atest_module_info.get_paths(target)
if paths:
rel_path = paths[0]
abs_path = os.path.join(get_android_root_dir(), rel_path)
# User inputs a module path or a relative path of android root folder.
elif (atest_module_info.get_module_names(target) or os.path.isdir(
os.path.join(get_android_root_dir(), target))):
rel_path = target.strip(os.sep)
abs_path = os.path.join(get_android_root_dir(), rel_path)
# User inputs a relative path of current directory.
else:
abs_path = os.path.abspath(os.path.join(os.getcwd(), target))
rel_path = os.path.relpath(abs_path, get_android_root_dir())
else:
# User doesn't input.
abs_path = os.getcwd()
if is_android_root(abs_path):
rel_path = ''
else:
rel_path = os.path.relpath(abs_path, get_android_root_dir())
return rel_path, abs_path
def is_target_android_root(atest_module_info, targets):
"""Check if any target is the Android root path.
Args:
atest_module_info: A ModuleInfo instance contains data of
module-info.json.
targets: A list of target modules or project paths from user input.
Returns:
True if target is Android root, otherwise False.
"""
for target in targets:
_, abs_path = get_related_paths(atest_module_info, target)
if is_android_root(abs_path):
return True
return False
def is_android_root(abs_path):
"""Check if an absolute path is the Android root path.
Args:
abs_path: The absolute path of a module.
Returns:
True if abs_path is Android root, otherwise False.
"""
return abs_path == get_android_root_dir()
def has_build_target(atest_module_info, rel_path):
"""Determine if a relative path contains buildable module.
Args:
atest_module_info: A ModuleInfo instance contains data of
module-info.json.
rel_path: The module path relative to android root.
Returns:
True if the relative path contains a build target, otherwise false.
"""
return any(
mod_path.startswith(rel_path)
for mod_path in atest_module_info.path_to_module_info)
def _check_modules(atest_module_info, targets, raise_on_lost_module=True):
"""Check if all targets are valid build targets.
Args:
atest_module_info: A ModuleInfo instance contains data of
module-info.json.
targets: A list of target modules or project paths from user input.
When locating the path of the target, given a matched module
name has priority over path. Below is the priority of locating a
target:
1. Module name, e.g. Settings
2. Module path, e.g. packages/apps/Settings
3. Relative path, e.g. ../../packages/apps/Settings
4. Current directory, e.g. . or no argument
raise_on_lost_module: A boolean, pass to _check_module to determine if
ProjectPathNotExistError or NoModuleDefinedInModuleInfoError
should be raised.
Returns:
True if any _check_module return flip the True/False.
"""
for target in targets:
if not _check_module(atest_module_info, target, raise_on_lost_module):
return False
return True
def _check_module(atest_module_info, target, raise_on_lost_module=True):
"""Check if a target is valid or it's a path containing build target.
Args:
atest_module_info: A ModuleInfo instance contains the data of
module-info.json.
target: A target module or project path from user input.
When locating the path of the target, given a matched module
name has priority over path. Below is the priority of locating a
target:
1. Module name, e.g. Settings
2. Module path, e.g. packages/apps/Settings
3. Relative path, e.g. ../../packages/apps/Settings
4. Current directory, e.g. . or no argument
raise_on_lost_module: A boolean, handles if ProjectPathNotExistError or
NoModuleDefinedInModuleInfoError should be raised.
Returns:
1. If there is no error _check_module always return True.
2. If there is a error,
a. When raise_on_lost_module is False, _check_module will raise the
error.
b. When raise_on_lost_module is True, _check_module will return
False if module's error is ProjectPathNotExistError or
NoModuleDefinedInModuleInfoError else raise the error.
Raises:
Raise ProjectPathNotExistError and NoModuleDefinedInModuleInfoError only
when raise_on_lost_module is True, others don't subject to the limit.
The rules for raising exceptions:
1. Absolute path of a module is None -> FakeModuleError
2. Module doesn't exist in repo root -> ProjectOutsideAndroidRootError
3. The given absolute path is not a dir -> ProjectPathNotExistError
4. If the given abs path doesn't contain any target and not repo root
-> NoModuleDefinedInModuleInfoError
"""
rel_path, abs_path = get_related_paths(atest_module_info, target)
if not abs_path:
err = FAKE_MODULE_ERROR.format(target)
logging.error(err)
raise errors.FakeModuleError(err)
if not abs_path.startswith(get_android_root_dir()):
err = OUTSIDE_ROOT_ERROR.format(abs_path)
logging.error(err)
raise errors.ProjectOutsideAndroidRootError(err)
if not os.path.isdir(abs_path):
err = PATH_NOT_EXISTS_ERROR.format(rel_path)
if raise_on_lost_module:
logging.error(err)
raise errors.ProjectPathNotExistError(err)
logging.debug(_REBUILD_MODULE_INFO, err)
return False
if (not has_build_target(atest_module_info, rel_path)
and not is_android_root(abs_path)):
err = NO_MODULE_DEFINED_ERROR.format(rel_path)
if raise_on_lost_module:
logging.error(err)
raise errors.NoModuleDefinedInModuleInfoError(err)
logging.debug(_REBUILD_MODULE_INFO, err)
return False
return True
def get_abs_path(rel_path):
"""Get absolute path from a relative path.
Args:
rel_path: A string, a relative path to get_android_root_dir().
Returns:
abs_path: A string, an absolute path starts with
get_android_root_dir().
"""
if not rel_path:
return get_android_root_dir()
if rel_path.startswith(get_android_root_dir()):
return rel_path
return os.path.join(get_android_root_dir(), rel_path)
def is_project_path_relative_module(data, project_relative_path):
"""Determine if the given project path is relative to the module.
The rules:
1. If project_relative_path is empty, it's under Android root, return
True.
2. If module's path equals or starts with project_relative_path return
True, otherwise return False.
Args:
data: the module-info dictionary of the checked module.
project_relative_path: project's relative path
Returns:
True if it's the given project path is relative to the module, otherwise
False.
"""
if 'path' not in data:
return False
path = data['path'][0]
if project_relative_path == '':
return True
if ('class' in data
and (path == project_relative_path
or path.startswith(project_relative_path + os.sep))):
return True
return False
def is_target(src_file, src_file_extensions):
"""Check if src_file is a type of src_file_extensions.
Args:
src_file: A string of the file path to be checked.
src_file_extensions: A list of file types to be checked
Returns:
True if src_file is one of the types of src_file_extensions, otherwise
False.
"""
return any(src_file.endswith(x) for x in src_file_extensions)
def get_atest_module_info(targets=None):
"""Get the right version of atest ModuleInfo instance.
The rules:
1. If targets is None:
We just want to get an atest ModuleInfo instance.
2. If targets isn't None:
Check if the targets don't exist in ModuleInfo, we'll regain a new
atest ModleInfo instance by setting force_build=True and call
_check_modules again. If targets still don't exist, raise exceptions.
Args:
targets: A list of targets to be built.
Returns:
An atest ModuleInfo instance.
"""
amodule_info = module_info.ModuleInfo()
if targets and not _check_modules(
amodule_info, targets, raise_on_lost_module=False):
amodule_info = module_info.ModuleInfo(force_build=True)
_check_modules(amodule_info, targets)
return amodule_info
def read_file_content(path):
"""Read file's content.
Args:
path: Path of input file.
Returns:
String: Content of the file.
"""
with open(path) as template:
return template.read()
def file_generate(path, content):
"""Generate file from content.
Args:
path: Path of target file.
content: String content of file.
"""
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
with open(path, 'w') as target:
target.write(content)
def get_blueprint_json_path():
"""Assemble the path of blueprint json file.
Returns:
module_bp_java_deps.json path.
"""
return os.path.join(get_soong_out_path(),
constant.BLUEPRINT_JSONFILE_NAME)
def back_to_cwd(func):
"""Decorate a function change directory back to its original one.
Args:
func: a function is to be changed back to its original directory.
Returns:
The wrapper function.
"""
@wraps(func)
def wrapper(*args, **kwargs):
"""A wrapper function."""
cwd = os.getcwd()
try:
return func(*args, **kwargs)
finally:
os.chdir(cwd)
return wrapper
def get_android_out_dir():
"""Get out directory in different conditions.
Returns:
Android out directory path.
"""
android_root_path = get_android_root_dir()
android_out_dir = os.environ.get(constants.ANDROID_OUT_DIR)
out_dir_common_base = os.getenv(constant.OUT_DIR_COMMON_BASE_ENV_VAR)
android_out_dir_common_base = (os.path.join(
out_dir_common_base, os.path.basename(android_root_path))
if out_dir_common_base else None)
return (android_out_dir or android_out_dir_common_base or
constant.ANDROID_DEFAULT_OUT)
def get_android_root_dir():
"""Get Android root directory.
If the path doesn't exist show message to ask users to run envsetup.sh and
lunch first.
Returns:
Android root directory path.
"""
android_root_path = os.environ.get(constants.ANDROID_BUILD_TOP)
if not android_root_path:
_show_env_setup_msg_and_exit()
return android_root_path
def get_aidegen_root_dir():
"""Get AIDEGen root directory.
Returns:
AIDEGen root directory path.
"""
return os.path.join(get_android_root_dir(), constant.AIDEGEN_ROOT_PATH)
def _show_env_setup_msg_and_exit():
"""Show message if users didn't run envsetup.sh and lunch."""
print(_ENVSETUP_NOT_RUN)
sys.exit(0)
def get_soong_out_path():
"""Assemble out directory's soong path.
Returns:
Out directory's soong path.
"""
return os.path.join(get_android_root_dir(), get_android_out_dir(), 'soong')