• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright 2018 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""common_util
18
19This module has a collection of functions that provide helper functions to
20other modules.
21"""
22
23import logging
24import os
25import time
26
27from functools import partial
28from functools import wraps
29
30from aidegen import constant
31from aidegen.lib.errors import FakeModuleError
32from aidegen.lib.errors import NoModuleDefinedInModuleInfoError
33from aidegen.lib.errors import ProjectOutsideAndroidRootError
34from aidegen.lib.errors import ProjectPathNotExistError
35from atest import constants
36from atest import module_info
37from atest.atest_utils import colorize
38
39COLORED_INFO = partial(colorize, color=constants.MAGENTA, highlight=False)
40COLORED_PASS = partial(colorize, color=constants.GREEN, highlight=False)
41COLORED_FAIL = partial(colorize, color=constants.RED, highlight=False)
42FAKE_MODULE_ERROR = '{} is a fake module.'
43OUTSIDE_ROOT_ERROR = '{} is outside android root.'
44PATH_NOT_EXISTS_ERROR = 'The path {} doesn\'t exist.'
45NO_MODULE_DEFINED_ERROR = 'No modules defined at {}.'
46# Java related classes.
47JAVA_TARGET_CLASSES = ['APPS', 'JAVA_LIBRARIES', 'ROBOLECTRIC']
48# C, C++ related classes.
49NATIVE_TARGET_CLASSES = [
50    'HEADER_LIBRARIES', 'NATIVE_TESTS', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES'
51]
52TARGET_CLASSES = JAVA_TARGET_CLASSES
53TARGET_CLASSES.extend(NATIVE_TARGET_CLASSES)
54_REBUILD_MODULE_INFO = '%s We should rebuild module-info.json file for it.'
55
56
57def time_logged(func=None, *, message='', maximum=1):
58    """Decorate a function to find out how much time it spends.
59
60    Args:
61        func: a function is to be calculated its spending time.
62        message: the message the decorated function wants to show.
63        maximum: a interger, minutes. If time exceeds the maximum time show
64                 message, otherwise doesn't.
65
66    Returns:
67        The wrapper function.
68    """
69    if func is None:
70        return partial(time_logged, message=message, maximum=maximum)
71
72    @wraps(func)
73    def wrapper(*args, **kwargs):
74        """A wrapper function."""
75
76        start = time.time()
77        try:
78            return func(*args, **kwargs)
79        finally:
80            timestamp = time.time() - start
81            logging.debug('{}.{} takes: {:.2f}s'.format(
82                func.__module__, func.__name__, timestamp))
83            if message and timestamp > maximum * 60:
84                print(message)
85
86    return wrapper
87
88
89def get_related_paths(atest_module_info, target=None):
90    """Get the relative and absolute paths of target from module-info.
91
92    Args:
93        atest_module_info: A ModuleInfo instance.
94        target: A string user input from command line. It could be several cases
95                such as:
96                1. Module name, e.g. Settings
97                2. Module path, e.g. packages/apps/Settings
98                3. Relative path, e.g. ../../packages/apps/Settings
99                4. Current directory, e.g. . or no argument
100
101    Return:
102        rel_path: The relative path of a module, return None if no matching
103                  module found.
104        abs_path: The absolute path of a module, return None if no matching
105                  module found.
106    """
107    rel_path = None
108    abs_path = None
109    if target:
110        # User inputs a module name.
111        if atest_module_info.is_module(target):
112            paths = atest_module_info.get_paths(target)
113            if paths:
114                rel_path = paths[0]
115                abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
116        # User inputs a module path or a relative path of android root folder.
117        elif (atest_module_info.get_module_names(target) or os.path.isdir(
118                os.path.join(constant.ANDROID_ROOT_PATH, target))):
119            rel_path = target.strip(os.sep)
120            abs_path = os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
121        # User inputs a relative path of current directory.
122        else:
123            abs_path = os.path.abspath(os.path.join(os.getcwd(), target))
124            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
125    else:
126        # User doesn't input.
127        abs_path = os.getcwd()
128        if is_android_root(abs_path):
129            rel_path = ''
130        else:
131            rel_path = os.path.relpath(abs_path, constant.ANDROID_ROOT_PATH)
132    return rel_path, abs_path
133
134
135def is_target_android_root(atest_module_info, targets):
136    """Check if any target is the Android root path.
137
138    Args:
139        atest_module_info: A ModuleInfo instance contains data of
140                           module-info.json.
141        targets: A list of target modules or project paths from user input.
142
143    Returns:
144        True if target is Android root, otherwise False.
145    """
146    for target in targets:
147        _, abs_path = get_related_paths(atest_module_info, target)
148        if is_android_root(abs_path):
149            return True
150    return False
151
152
153def is_android_root(abs_path):
154    """Check if an absolute path is the Android root path.
155
156    Args:
157        abs_path: The absolute path of a module.
158
159    Returns:
160        True if abs_path is Android root, otherwise False.
161    """
162    return abs_path == constant.ANDROID_ROOT_PATH
163
164
165def has_build_target(atest_module_info, rel_path):
166    """Determine if a relative path contains buildable module.
167
168    Args:
169        atest_module_info: A ModuleInfo instance contains data of
170                           module-info.json.
171        rel_path: The module path relative to android root.
172
173    Returns:
174        True if the relative path contains a build target, otherwise false.
175    """
176    return any(
177        mod_path.startswith(rel_path)
178        for mod_path in atest_module_info.path_to_module_info)
179
180
181def _check_modules(atest_module_info, targets, raise_on_lost_module=True):
182    """Check if all targets are valid build targets.
183
184    Args:
185        atest_module_info: A ModuleInfo instance contains data of
186                           module-info.json.
187        targets: A list of target modules or project paths from user input.
188                When locating the path of the target, given a matched module
189                name has priority over path. Below is the priority of locating a
190                target:
191                1. Module name, e.g. Settings
192                2. Module path, e.g. packages/apps/Settings
193                3. Relative path, e.g. ../../packages/apps/Settings
194                4. Current directory, e.g. . or no argument
195        raise_on_lost_module: A boolean, pass to _check_module to determine if
196                ProjectPathNotExistError or NoModuleDefinedInModuleInfoError
197                should be raised.
198
199    Returns:
200        True if any _check_module return flip the True/False.
201    """
202    for target in targets:
203        if not _check_module(atest_module_info, target, raise_on_lost_module):
204            return False
205    return True
206
207
208def _check_module(atest_module_info, target, raise_on_lost_module=True):
209    """Check if a target is valid or it's a path containing build target.
210
211    Args:
212        atest_module_info: A ModuleInfo instance contains the data of
213                module-info.json.
214        target: A target module or project path from user input.
215                When locating the path of the target, given a matched module
216                name has priority over path. Below is the priority of locating a
217                target:
218                1. Module name, e.g. Settings
219                2. Module path, e.g. packages/apps/Settings
220                3. Relative path, e.g. ../../packages/apps/Settings
221                4. Current directory, e.g. . or no argument
222        raise_on_lost_module: A boolean, handles if ProjectPathNotExistError or
223                NoModuleDefinedInModuleInfoError should be raised.
224
225    Returns:
226        1. If there is no error _check_module always return True.
227        2. If there is a error,
228            a. When raise_on_lost_module is False, _check_module will raise the
229               error.
230            b. When raise_on_lost_module is True, _check_module will return
231               False if module's error is ProjectPathNotExistError or
232               NoModuleDefinedInModuleInfoError else raise the error.
233
234    Raises:
235        Raise ProjectPathNotExistError and NoModuleDefinedInModuleInfoError only
236        when raise_on_lost_module is True, others don't subject to the limit.
237        The rules for raising exceptions:
238        1. Absolute path of a module is None -> FakeModuleError
239        2. Module doesn't exist in repo root -> ProjectOutsideAndroidRootError
240        3. The given absolute path is not a dir -> ProjectPathNotExistError
241        4. If the given abs path doesn't contain any target and not repo root
242           -> NoModuleDefinedInModuleInfoError
243    """
244    rel_path, abs_path = get_related_paths(atest_module_info, target)
245    if not abs_path:
246        err = FAKE_MODULE_ERROR.format(target)
247        logging.error(err)
248        raise FakeModuleError(err)
249    if not abs_path.startswith(constant.ANDROID_ROOT_PATH):
250        err = OUTSIDE_ROOT_ERROR.format(abs_path)
251        logging.error(err)
252        raise ProjectOutsideAndroidRootError(err)
253    if not os.path.isdir(abs_path):
254        err = PATH_NOT_EXISTS_ERROR.format(rel_path)
255        if raise_on_lost_module:
256            logging.error(err)
257            raise ProjectPathNotExistError(err)
258        logging.debug(_REBUILD_MODULE_INFO, err)
259        return False
260    if (not has_build_target(atest_module_info, rel_path)
261            and not is_android_root(abs_path)):
262        err = NO_MODULE_DEFINED_ERROR.format(rel_path)
263        if raise_on_lost_module:
264            logging.error(err)
265            raise NoModuleDefinedInModuleInfoError(err)
266        logging.debug(_REBUILD_MODULE_INFO, err)
267        return False
268    return True
269
270
271def get_abs_path(rel_path):
272    """Get absolute path from a relative path.
273
274    Args:
275        rel_path: A string, a relative path to constant.ANDROID_ROOT_PATH.
276
277    Returns:
278        abs_path: A string, an absolute path starts with
279                  constant.ANDROID_ROOT_PATH.
280    """
281    if not rel_path:
282        return constant.ANDROID_ROOT_PATH
283    if rel_path.startswith(constant.ANDROID_ROOT_PATH):
284        return rel_path
285    return os.path.join(constant.ANDROID_ROOT_PATH, rel_path)
286
287
288def is_project_path_relative_module(data, project_relative_path):
289    """Determine if the given project path is relative to the module.
290
291    The rules:
292       1. If project_relative_path is empty, it's under Android root, return
293          True.
294       2. If module's path equals or starts with project_relative_path return
295          True, otherwise return False.
296
297    Args:
298        data: the module-info dictionary of the checked module.
299        project_relative_path: project's relative path
300
301    Returns:
302        True if it's the given project path is relative to the module, otherwise
303        False.
304    """
305    if 'path' not in data:
306        return False
307    path = data['path'][0]
308    if project_relative_path == '':
309        return True
310    if ('class' in data
311            and (path == project_relative_path
312                 or path.startswith(project_relative_path + os.sep))):
313        return True
314    return False
315
316
317def is_target(src_file, src_file_extensions):
318    """Check if src_file is a type of src_file_extensions.
319
320    Args:
321        src_file: A string of the file path to be checked.
322        src_file_extensions: A list of file types to be checked
323
324    Returns:
325        True if src_file is one of the types of src_file_extensions, otherwise
326        False.
327    """
328    return any(src_file.endswith(x) for x in src_file_extensions)
329
330
331def get_atest_module_info(targets):
332    """Get the right version of atest ModuleInfo instance.
333
334    The rules:
335        Check if the targets don't exist in ModuleInfo, we'll regain a new atest
336        ModleInfo instance by setting force_build=True and call _check_modules
337        again. If targets still don't exist, raise exceptions.
338
339    Args:
340        targets: A list of targets to be built.
341
342    Returns:
343        An atest ModuleInfo instance.
344    """
345    amodule_info = module_info.ModuleInfo()
346    if not _check_modules(amodule_info, targets, raise_on_lost_module=False):
347        amodule_info = module_info.ModuleInfo(force_build=True)
348        _check_modules(amodule_info, targets)
349    return amodule_info
350