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