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"""module_info_util 18 19This module receives a module path which is relative to its root directory and 20makes a command to generate two json files, one for mk files and one for bp 21files. Then it will load these two json files into two json dictionaries, 22merge them into one dictionary and return the merged dictionary to its caller. 23 24Example usage: 25merged_dict = generate_merged_module_info() 26""" 27 28import glob 29import logging 30import os 31import sys 32 33from aidegen import constant 34from aidegen.lib import common_util 35from aidegen.lib import errors 36from aidegen.lib import project_config 37 38from atest import atest_utils 39 40_MERGE_NEEDED_ITEMS = [ 41 constant.KEY_CLASS, 42 constant.KEY_PATH, 43 constant.KEY_INSTALLED, 44 constant.KEY_DEPENDENCIES, 45 constant.KEY_SRCS, 46 constant.KEY_SRCJARS, 47 constant.KEY_CLASSES_JAR, 48 constant.KEY_TAG, 49 constant.KEY_COMPATIBILITY, 50 constant.KEY_AUTO_TEST_CONFIG, 51 constant.KEY_MODULE_NAME, 52 constant.KEY_TEST_CONFIG 53] 54_INTELLIJ_PROJECT_FILE_EXT = '*.iml' 55_LAUNCH_PROJECT_QUERY = ( 56 'There exists an IntelliJ project file: %s. Do you want ' 57 'to launch it (yes/No)?') 58_BUILD_BP_JSON_ENV_OFF = { 59 constant.GEN_JAVA_DEPS: 'false', 60 constant.GEN_CC_DEPS: 'false', 61 constant.GEN_COMPDB: 'false' 62} 63_BUILD_BP_JSON_ENV_ON = { 64 constant.GEN_JAVA_DEPS: 'true', 65 constant.GEN_CC_DEPS: 'true', 66 constant.GEN_COMPDB: 'true' 67} 68_GEN_JSON_FAILED = ( 69 'Generate new {0} failed, AIDEGen will proceed and reuse the old {1}.') 70_WARN_MSG = '\n{} {}\n' 71_TARGET = 'nothing' 72 73 74# pylint: disable=dangerous-default-value 75@common_util.back_to_cwd 76@common_util.time_logged 77def generate_merged_module_info(env_off=_BUILD_BP_JSON_ENV_OFF, 78 env_on=_BUILD_BP_JSON_ENV_ON): 79 """Generate a merged dictionary. 80 81 Linked functions: 82 _build_bp_info(module_info, project, verbose, skip_build) 83 _get_soong_build_json_dict() 84 _merge_dict(mk_dict, bp_dict) 85 86 Args: 87 env_off: A dictionary of environment settings to be turned off, the 88 default value is _BUILD_BP_JSON_ENV_OFF. 89 env_on: A dictionary of environment settings to be turned on, the 90 default value is _BUILD_BP_JSON_ENV_ON. 91 92 Returns: 93 A merged dictionary from module-info.json and module_bp_java_deps.json. 94 """ 95 config = project_config.ProjectConfig.get_instance() 96 module_info = config.atest_module_info 97 projects = config.targets 98 verbose = True 99 skip_build = config.is_skip_build 100 main_project = projects[0] if projects else None 101 _build_bp_info( 102 module_info, main_project, verbose, skip_build, env_off, env_on) 103 json_path = common_util.get_blueprint_json_path( 104 constant.BLUEPRINT_JAVA_JSONFILE_NAME) 105 bp_dict = common_util.get_json_dict(json_path) 106 return _merge_dict(module_info.name_to_module_info, bp_dict) 107 108 109def _build_bp_info(module_info, main_project=None, verbose=False, 110 skip_build=False, env_off=_BUILD_BP_JSON_ENV_OFF, 111 env_on=_BUILD_BP_JSON_ENV_ON): 112 """Make nothing to create module_bp_java_deps.json, module_bp_cc_deps.json. 113 114 Use atest build method to build the target 'nothing' by setting env config 115 SOONG_COLLECT_JAVA_DEPS to false then true. By this way, we can trigger the 116 process of collecting dependencies and generate module_bp_java_deps.json. 117 118 Args: 119 module_info: A ModuleInfo instance contains data of module-info.json. 120 main_project: A string of the main project name. 121 verbose: A boolean, if true displays full build output. 122 skip_build: A boolean, if true, skip building if 123 get_blueprint_json_path(file_name) file exists, otherwise 124 build it. 125 env_off: A dictionary of environment settings to be turned off, the 126 default value is _BUILD_BP_JSON_ENV_OFF. 127 env_on: A dictionary of environment settings to be turned on, the 128 default value is _BUILD_BP_JSON_ENV_ON. 129 130 Build results: 131 1. Build successfully return. 132 2. Build failed: 133 1) There's no project file, raise BuildFailureError. 134 2) There exists a project file, ask users if they want to 135 launch IDE with the old project file. 136 a) If the answer is yes, return. 137 b) If the answer is not yes, sys.exit(1) 138 """ 139 file_paths = _get_generated_json_files(env_on) 140 files_exist = all([os.path.isfile(fpath) for fpath in file_paths]) 141 files = '\n'.join(file_paths) 142 if skip_build and files_exist: 143 logging.info('Files:\n%s exist, skipping build.', files) 144 return 145 original_file_mtimes = {f: None for f in file_paths} 146 if files_exist: 147 original_file_mtimes = {f: os.path.getmtime(f) for f in file_paths} 148 149 logging.warning( 150 '\nGenerate files:\n %s by atest build method.', files) 151 build_with_off_cmd = atest_utils.build([_TARGET], verbose, env_off) 152 build_with_on_cmd = atest_utils.build([_TARGET], verbose, env_on) 153 154 if build_with_off_cmd and build_with_on_cmd: 155 logging.info('\nGenerate blueprint json successfully.') 156 else: 157 if not all([_is_new_json_file_generated( 158 f, original_file_mtimes[f]) for f in file_paths]): 159 if files_exist: 160 _show_files_reuse_message(file_paths) 161 else: 162 _show_build_failed_message(module_info, main_project) 163 164 165def _get_generated_json_files(env_on=_BUILD_BP_JSON_ENV_ON): 166 """Gets the absolute paths of the files which is going to be generated. 167 168 Determine the files which will be generated by the environment on dictionary 169 and the default blueprint json files' dictionary. 170 The generation of json files depends on env_on. If the env_on looks like, 171 _BUILD_BP_JSON_ENV_ON = { 172 'SOONG_COLLECT_JAVA_DEPS': 'true', 173 'SOONG_COLLECT_CC_DEPS': 'true' 174 } 175 We want to generate only two files: module_bp_java_deps.json and 176 module_bp_cc_deps.json. And in get_blueprint_json_files_relative_dict 177 function, there are three json files by default. We get the result list by 178 comparsing with these two dictionaries. 179 180 Args: 181 env_on: A dictionary of environment settings to be turned on, the 182 default value is _BUILD_BP_JSON_ENV_ON. 183 184 Returns: 185 A list of the absolute paths of the files which is going to be 186 generated. 187 """ 188 json_files_dict = common_util.get_blueprint_json_files_relative_dict() 189 file_paths = [] 190 for key in env_on: 191 if not env_on[key] == 'true' or key not in json_files_dict: 192 continue 193 file_paths.append(json_files_dict[key]) 194 return file_paths 195 196 197def _show_files_reuse_message(file_paths): 198 """Shows the message of build failure but files existing and reusing them. 199 200 Args: 201 file_paths: A list of absolute file paths to be checked. 202 """ 203 failed_or_file = ' or '.join(file_paths) 204 failed_and_file = ' and '.join(file_paths) 205 message = _GEN_JSON_FAILED.format(failed_or_file, failed_and_file) 206 print(_WARN_MSG.format(common_util.COLORED_INFO('Warning:'), message)) 207 208 209def _show_build_failed_message(module_info, main_project=None): 210 """Show build failed message. 211 212 Args: 213 module_info: A ModuleInfo instance contains data of module-info.json. 214 main_project: A string of the main project name. 215 """ 216 if main_project: 217 _, main_project_path = common_util.get_related_paths( 218 module_info, main_project) 219 _build_failed_handle(main_project_path) 220 221 222def _is_new_json_file_generated(json_path, original_file_mtime): 223 """Check the new file is generated or not. 224 225 Args: 226 json_path: The path of the json file being to check. 227 original_file_mtime: the original file modified time. 228 229 Returns: 230 A boolean, True if the json_path file is new generated, otherwise False. 231 """ 232 if not os.path.isfile(json_path): 233 return False 234 return original_file_mtime != os.path.getmtime(json_path) 235 236 237def _build_failed_handle(main_project_path): 238 """Handle build failures. 239 240 Args: 241 main_project_path: The main project directory. 242 243 Handle results: 244 1) There's no project file, raise BuildFailureError. 245 2) There exists a project file, ask users if they want to 246 launch IDE with the old project file. 247 a) If the answer is yes, return. 248 b) If the answer is not yes, sys.exit(1) 249 """ 250 project_file = glob.glob( 251 os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT)) 252 if project_file: 253 query = _LAUNCH_PROJECT_QUERY % project_file[0] 254 input_data = input(query) 255 if not input_data.lower() in ['yes', 'y']: 256 sys.exit(1) 257 else: 258 raise errors.BuildFailureError( 259 'Failed to generate %s.' % common_util.get_blueprint_json_path( 260 constant.BLUEPRINT_JAVA_JSONFILE_NAME)) 261 262 263def _merge_module_keys(m_dict, b_dict): 264 """Merge a module's dictionary into another module's dictionary. 265 266 Merge b_dict module data into m_dict. 267 268 Args: 269 m_dict: The module dictionary is going to merge b_dict into. 270 b_dict: Soong build system module dictionary. 271 """ 272 for key, b_modules in b_dict.items(): 273 m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules))) 274 275 276def _copy_needed_items_from(mk_dict): 277 """Shallow copy needed items from Make build system module info dictionary. 278 279 Args: 280 mk_dict: Make build system dictionary is going to be copied. 281 282 Returns: 283 A merged dictionary. 284 """ 285 merged_dict = dict() 286 for module in mk_dict.keys(): 287 merged_dict[module] = dict() 288 for key in mk_dict[module].keys(): 289 if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []: 290 merged_dict[module][key] = mk_dict[module][key] 291 return merged_dict 292 293 294def _merge_dict(mk_dict, bp_dict): 295 """Merge two dictionaries. 296 297 Linked function: 298 _merge_module_keys(m_dict, b_dict) 299 300 Args: 301 mk_dict: Make build system module info dictionary. 302 bp_dict: Soong build system module info dictionary. 303 304 Returns: 305 A merged dictionary. 306 """ 307 merged_dict = _copy_needed_items_from(mk_dict) 308 for module in bp_dict.keys(): 309 if module not in merged_dict.keys(): 310 merged_dict[module] = dict() 311 _merge_module_keys(merged_dict[module], bp_dict[module]) 312 return merged_dict 313