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