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_ON = { 59 constant.GEN_JAVA_DEPS: 'true', 60 constant.GEN_CC_DEPS: 'true', 61 constant.GEN_COMPDB: 'true', 62 constant.GEN_RUST: 'true' 63} 64_GEN_JSON_FAILED = ( 65 'Generate new {0} failed, AIDEGen will proceed and reuse the old {1}.') 66_TARGET = 'nothing' 67_LINKFILE_WARNING = ( 68 'File {} does not exist and we can not make a symbolic link for it.') 69_RUST_PROJECT_JSON = 'out/soong/rust-project.json' 70 71 72# Generators are slightly more inefficient when all the values have to be 73# traversed most of the time. 74# pylint: disable=use-a-generator 75# pylint: disable=dangerous-default-value 76@common_util.back_to_cwd 77@common_util.time_logged 78def generate_merged_module_info(env_on=_BUILD_BP_JSON_ENV_ON): 79 """Generate a merged dictionary. 80 81 Linked functions: 82 _build_bp_info(module_info, project, skip_build) 83 _get_soong_build_json_dict() 84 _merge_dict(mk_dict, bp_dict) 85 86 Args: 87 env_on: A dictionary of environment settings to be turned on, the 88 default value is _BUILD_BP_JSON_ENV_ON. 89 90 Returns: 91 A merged dictionary from module-info.json and module_bp_java_deps.json. 92 """ 93 config = project_config.ProjectConfig.get_instance() 94 module_info = config.atest_module_info 95 projects = config.targets 96 verbose = True 97 skip_build = config.is_skip_build 98 main_project = projects[0] if projects else None 99 _build_bp_info( 100 module_info, main_project, skip_build, env_on) 101 json_path = common_util.get_blueprint_json_path( 102 constant.BLUEPRINT_JAVA_JSONFILE_NAME) 103 bp_dict = common_util.get_json_dict(json_path) 104 return _merge_dict(module_info.name_to_module_info, bp_dict) 105 106 107def _build_bp_info(module_info, main_project=None, 108 skip_build=False, env_on=_BUILD_BP_JSON_ENV_ON): 109 """Make nothing to create module_bp_java_deps.json, module_bp_cc_deps.json. 110 111 Use atest build method to build the target 'nothing' by setting env config 112 SOONG_COLLECT_JAVA_DEPS to true to trigger the process of collecting 113 dependencies and generate module_bp_java_deps.json etc. 114 115 Args: 116 module_info: A ModuleInfo instance contains data of module-info.json. 117 main_project: A string of the main project name. 118 skip_build: A boolean, if true, skip building if 119 get_blueprint_json_path(file_name) file exists, otherwise 120 build it. 121 env_on: A dictionary of environment settings to be turned on, the 122 default value is _BUILD_BP_JSON_ENV_ON. 123 124 Build results: 125 1. Build successfully return. 126 2. Build failed: 127 1) There's no project file, raise BuildFailureError. 128 2) There exists a project file, ask users if they want to 129 launch IDE with the old project file. 130 a) If the answer is yes, return. 131 b) If the answer is not yes, sys.exit(1) 132 """ 133 file_paths = _get_generated_json_files(env_on) 134 files_exist = all([os.path.isfile(fpath) for fpath in file_paths]) 135 files = '\n'.join(file_paths) 136 if skip_build and files_exist: 137 logging.info('Files:\n%s exist, skipping build.', files) 138 return 139 original_file_mtimes = {f: None for f in file_paths} 140 if files_exist: 141 original_file_mtimes = {f: os.path.getmtime(f) for f in file_paths} 142 143 logging.warning( 144 '\nGenerate files:\n %s by atest build method.', files) 145 atest_utils.update_build_env(env_on) 146 build_with_on_cmd = atest_utils.build([_TARGET]) 147 148 # For Android Rust projects, we need to create a symbolic link to the file 149 # out/soong/rust-project.json to launch the rust projects in IDEs. 150 _generate_rust_project_link() 151 152 if build_with_on_cmd: 153 logging.info('Generate blueprint json successfully.') 154 else: 155 if not all([_is_new_json_file_generated( 156 f, original_file_mtimes[f]) for f in file_paths]): 157 if files_exist: 158 _show_files_reuse_message(file_paths) 159 else: 160 _show_build_failed_message(module_info, main_project) 161 162 163def _get_generated_json_files(env_on=_BUILD_BP_JSON_ENV_ON): 164 """Gets the absolute paths of the files which is going to be generated. 165 166 Determine the files which will be generated by the environment on dictionary 167 and the default blueprint json files' dictionary. 168 The generation of json files depends on env_on. If the env_on looks like, 169 _BUILD_BP_JSON_ENV_ON = { 170 'SOONG_COLLECT_JAVA_DEPS': 'true', 171 'SOONG_COLLECT_CC_DEPS': 'true', 172 'SOONG_GEN_COMPDB': 'true', 173 'SOONG_GEN_RUST_PROJECT': 'true' 174 } 175 We want to generate 4 files: module_bp_java_deps.json, 176 module_bp_cc_deps.json, compile_commands.json and rust-project.json. And in 177 get_blueprint_json_files_relative_dict function, there are 4 json files 178 by default and return a result list of the absolute paths of the existent 179 files. 180 181 Args: 182 env_on: A dictionary of environment settings to be turned on, the 183 default value is _BUILD_BP_JSON_ENV_ON. 184 185 Returns: 186 A list of the absolute paths of the files which is going to be 187 generated. 188 """ 189 json_files_dict = common_util.get_blueprint_json_files_relative_dict() 190 file_paths = [] 191 for key in env_on: 192 if not env_on[key] == 'true' or key not in json_files_dict: 193 continue 194 file_paths.append(json_files_dict[key]) 195 return file_paths 196 197 198def _show_files_reuse_message(file_paths): 199 """Shows the message of build failure but files existing and reusing them. 200 201 Args: 202 file_paths: A list of absolute file paths to be checked. 203 """ 204 failed_or_file = ' or '.join(file_paths) 205 failed_and_file = ' and '.join(file_paths) 206 message = _GEN_JSON_FAILED.format(failed_or_file, failed_and_file) 207 print(constant.WARN_MSG.format( 208 common_util.COLORED_INFO('Warning:'), message)) 209 210 211def _show_build_failed_message(module_info, main_project=None): 212 """Show build failed message. 213 214 Args: 215 module_info: A ModuleInfo instance contains data of module-info.json. 216 main_project: A string of the main project name. 217 """ 218 if main_project: 219 _, main_project_path = common_util.get_related_paths( 220 module_info, main_project) 221 _build_failed_handle(main_project_path) 222 223 224def _is_new_json_file_generated(json_path, original_file_mtime): 225 """Check the new file is generated or not. 226 227 Args: 228 json_path: The path of the json file being to check. 229 original_file_mtime: the original file modified time. 230 231 Returns: 232 A boolean, True if the json_path file is new generated, otherwise False. 233 """ 234 if not os.path.isfile(json_path): 235 return False 236 return original_file_mtime != os.path.getmtime(json_path) 237 238 239def _build_failed_handle(main_project_path): 240 """Handle build failures. 241 242 Args: 243 main_project_path: The main project directory. 244 245 Handle results: 246 1) There's no project file, raise BuildFailureError. 247 2) There exists a project file, ask users if they want to 248 launch IDE with the old project file. 249 a) If the answer is yes, return. 250 b) If the answer is not yes, sys.exit(1) 251 """ 252 project_file = glob.glob( 253 os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT)) 254 if project_file: 255 query = _LAUNCH_PROJECT_QUERY % project_file[0] 256 input_data = input(query) 257 if not input_data.lower() in ['yes', 'y']: 258 sys.exit(1) 259 else: 260 raise errors.BuildFailureError( 261 'Failed to generate %s.' % common_util.get_blueprint_json_path( 262 constant.BLUEPRINT_JAVA_JSONFILE_NAME)) 263 264 265def _merge_module_keys(m_dict, b_dict): 266 """Merge a module's dictionary into another module's dictionary. 267 268 Merge b_dict module data into m_dict. 269 270 Args: 271 m_dict: The module dictionary is going to merge b_dict into. 272 b_dict: Soong build system module dictionary. 273 """ 274 for key, b_modules in b_dict.items(): 275 m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules))) 276 277 278def _copy_needed_items_from(mk_dict): 279 """Shallow copy needed items from Make build system module info dictionary. 280 281 Args: 282 mk_dict: Make build system dictionary is going to be copied. 283 284 Returns: 285 A merged dictionary. 286 """ 287 merged_dict = {} 288 for module in mk_dict.keys(): 289 merged_dict[module] = {} 290 for key in mk_dict[module].keys(): 291 if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []: 292 merged_dict[module][key] = mk_dict[module][key] 293 return merged_dict 294 295 296def _merge_dict(mk_dict, bp_dict): 297 """Merge two dictionaries. 298 299 Linked function: 300 _merge_module_keys(m_dict, b_dict) 301 302 Args: 303 mk_dict: Make build system module info dictionary. 304 bp_dict: Soong build system module info dictionary. 305 306 Returns: 307 A merged dictionary. 308 """ 309 merged_dict = _copy_needed_items_from(mk_dict) 310 for module in bp_dict.keys(): 311 if module not in merged_dict: 312 merged_dict[module] = {} 313 _merge_module_keys(merged_dict[module], bp_dict[module]) 314 return merged_dict 315 316 317def _generate_rust_project_link(): 318 """Generates out/soong/rust-project.json symbolic link in Android root.""" 319 root_dir = common_util.get_android_root_dir() 320 rust_project = os.path.join( 321 root_dir, common_util.get_blueprint_json_path( 322 constant.RUST_PROJECT_JSON)) 323 if not os.path.isfile(rust_project): 324 message = _LINKFILE_WARNING.format(_RUST_PROJECT_JSON) 325 print(constant.WARN_MSG.format( 326 common_util.COLORED_INFO('Warning:'), message)) 327 return 328 link_rust = os.path.join(root_dir, constant.RUST_PROJECT_JSON) 329 if os.path.islink(link_rust): 330 os.remove(link_rust) 331 os.symlink(rust_project, link_rust) 332