1# Copyright 2018, The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15""" 16Module Info class used to hold cached module-info.json. 17""" 18 19import json 20import logging 21import os 22 23import atest_utils 24import constants 25 26# JSON file generated by build system that lists all buildable targets. 27_MODULE_INFO = 'module-info.json' 28 29 30class ModuleInfo(object): 31 """Class that offers fast/easy lookup for Module related details.""" 32 33 def __init__(self, force_build=False, module_file=None): 34 """Initialize the ModuleInfo object. 35 36 Load up the module-info.json file and initialize the helper vars. 37 38 Args: 39 force_build: Boolean to indicate if we should rebuild the 40 module_info file regardless if it's created or not. 41 module_file: String of path to file to load up. Used for testing. 42 """ 43 module_info_target, name_to_module_info = self._load_module_info_file( 44 force_build, module_file) 45 self.name_to_module_info = name_to_module_info 46 self.module_info_target = module_info_target 47 self.path_to_module_info = self._get_path_to_module_info( 48 self.name_to_module_info) 49 50 @staticmethod 51 def _discover_mod_file_and_target(force_build): 52 """Find the module file. 53 54 Args: 55 force_build: Boolean to indicate if we should rebuild the 56 module_info file regardless if it's created or not. 57 58 Returns: 59 Tuple of module_info_target and path to module file. 60 """ 61 module_info_target = None 62 root_dir = os.environ.get(constants.ANDROID_BUILD_TOP, '/') 63 out_dir = os.environ.get(constants.ANDROID_OUT, root_dir) 64 module_file_path = os.path.join(out_dir, _MODULE_INFO) 65 66 # Check for custom out dir. 67 out_dir_base = os.environ.get(constants.ANDROID_OUT_DIR) 68 if out_dir_base is None or not os.path.isabs(out_dir_base): 69 # Make target is simply file path relative to root 70 module_info_target = os.path.relpath(module_file_path, root_dir) 71 else: 72 # Chances are a custom absolute out dir is used, use 73 # ANDROID_PRODUCT_OUT instead. 74 module_file_path = os.path.join( 75 os.environ.get('ANDROID_PRODUCT_OUT'), _MODULE_INFO) 76 module_info_target = module_file_path 77 if not os.path.isfile(module_file_path) or force_build: 78 logging.info('Generating %s - this is required for ' 79 'initial runs.', _MODULE_INFO) 80 atest_utils.build([module_info_target], 81 logging.getLogger().isEnabledFor(logging.DEBUG)) 82 return module_info_target, module_file_path 83 84 def _load_module_info_file(self, force_build, module_file): 85 """Load the module file. 86 87 Args: 88 force_build: Boolean to indicate if we should rebuild the 89 module_info file regardless if it's created or not. 90 module_file: String of path to file to load up. Used for testing. 91 92 Returns: 93 Tuple of module_info_target and dict of json. 94 """ 95 # If module_file is specified, we're testing so we don't care if 96 # module_info_target stays None. 97 module_info_target = None 98 file_path = module_file 99 if not file_path: 100 module_info_target, file_path = self._discover_mod_file_and_target( 101 force_build) 102 with open(file_path) as json_file: 103 mod_info = json.load(json_file) 104 return module_info_target, mod_info 105 106 @staticmethod 107 def _get_path_to_module_info(name_to_module_info): 108 """Return the path_to_module_info dict. 109 110 Args: 111 name_to_module_info: Dict of module name to module info dict. 112 113 Returns: 114 Dict of module path to module info dict. 115 """ 116 path_to_module_info = {} 117 for mod_name, mod_info in name_to_module_info.iteritems(): 118 for path in mod_info.get(constants.MODULE_PATH, []): 119 mod_info[constants.MODULE_NAME] = mod_name 120 # There could be multiple modules in a path. 121 if path in path_to_module_info: 122 path_to_module_info[path].append(mod_info) 123 else: 124 path_to_module_info[path] = [mod_info] 125 return path_to_module_info 126 127 def is_module(self, name): 128 """Return True if name is a module, False otherwise.""" 129 return name in self.name_to_module_info 130 131 def get_paths(self, name): 132 """Return paths of supplied module name, Empty list if non-existent.""" 133 info = self.name_to_module_info.get(name) 134 if info: 135 return info.get(constants.MODULE_PATH, []) 136 return [] 137 138 def get_module_names(self, rel_module_path): 139 """Get the modules that all have module_path. 140 141 Args: 142 rel_module_path: path of module in module-info.json 143 144 Returns: 145 List of module names. 146 """ 147 return [m.get(constants.MODULE_NAME) 148 for m in self.path_to_module_info.get(rel_module_path, [])] 149 150 def get_module_info(self, mod_name): 151 """Return dict of info for given module name, None if non-existent.""" 152 return self.name_to_module_info.get(mod_name) 153