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_WARN_MSG = '\n{} {}\n' 67_TARGET = 'nothing' 68_LINKFILE_WARNING = ( 69 'File {} does not exist and we can not make a symbolic link for it.') 70_RUST_PROJECT_JSON = 'out/soong/rust-project.json' 71 72 73# pylint: disable=dangerous-default-value 74@common_util.back_to_cwd 75@common_util.time_logged 76def generate_merged_module_info(env_on=_BUILD_BP_JSON_ENV_ON): 77 """Generate a merged dictionary. 78 79 Linked functions: 80 _build_bp_info(module_info, project, verbose, skip_build) 81 _get_soong_build_json_dict() 82 _merge_dict(mk_dict, bp_dict) 83 84 Args: 85 env_on: A dictionary of environment settings to be turned on, the 86 default value is _BUILD_BP_JSON_ENV_ON. 87 88 Returns: 89 A merged dictionary from module-info.json and module_bp_java_deps.json. 90 """ 91 config = project_config.ProjectConfig.get_instance() 92 module_info = config.atest_module_info 93 projects = config.targets 94 verbose = True 95 skip_build = config.is_skip_build 96 main_project = projects[0] if projects else None 97 _build_bp_info( 98 module_info, main_project, verbose, skip_build, env_on) 99 json_path = common_util.get_blueprint_json_path( 100 constant.BLUEPRINT_JAVA_JSONFILE_NAME) 101 bp_dict = common_util.get_json_dict(json_path) 102 return _merge_dict(module_info.name_to_module_info, bp_dict) 103 104 105def _build_bp_info(module_info, main_project=None, verbose=False, 106 skip_build=False, env_on=_BUILD_BP_JSON_ENV_ON): 107 """Make nothing to create module_bp_java_deps.json, module_bp_cc_deps.json. 108 109 Use atest build method to build the target 'nothing' by setting env config 110 SOONG_COLLECT_JAVA_DEPS to true to trigger the process of collecting 111 dependencies and generate module_bp_java_deps.json etc. 112 113 Args: 114 module_info: A ModuleInfo instance contains data of module-info.json. 115 main_project: A string of the main project name. 116 verbose: A boolean, if true displays full build output. 117 skip_build: A boolean, if true, skip building if 118 get_blueprint_json_path(file_name) file exists, otherwise 119 build it. 120 env_on: A dictionary of environment settings to be turned on, the 121 default value is _BUILD_BP_JSON_ENV_ON. 122 123 Build results: 124 1. Build successfully return. 125 2. Build failed: 126 1) There's no project file, raise BuildFailureError. 127 2) There exists a project file, ask users if they want to 128 launch IDE with the old project file. 129 a) If the answer is yes, return. 130 b) If the answer is not yes, sys.exit(1) 131 """ 132 file_paths = _get_generated_json_files(env_on) 133 files_exist = all([os.path.isfile(fpath) for fpath in file_paths]) 134 files = '\n'.join(file_paths) 135 if skip_build and files_exist: 136 logging.info('Files:\n%s exist, skipping build.', files) 137 return 138 original_file_mtimes = {f: None for f in file_paths} 139 if files_exist: 140 original_file_mtimes = {f: os.path.getmtime(f) for f in file_paths} 141 142 logging.warning( 143 '\nGenerate files:\n %s by atest build method.', files) 144 build_with_on_cmd = atest_utils.build([_TARGET], verbose, env_on) 145 146 # For Android Rust projects, we need to create a symbolic link to the file 147 # out/soong/rust-project.json to launch the rust projects in IDEs. 148 _generate_rust_project_link() 149 150 if build_with_on_cmd: 151 logging.info('\nGenerate blueprint json successfully.') 152 else: 153 if not all([_is_new_json_file_generated( 154 f, original_file_mtimes[f]) for f in file_paths]): 155 if files_exist: 156 _show_files_reuse_message(file_paths) 157 else: 158 _show_build_failed_message(module_info, main_project) 159 160 161def _get_generated_json_files(env_on=_BUILD_BP_JSON_ENV_ON): 162 """Gets the absolute paths of the files which is going to be generated. 163 164 Determine the files which will be generated by the environment on dictionary 165 and the default blueprint json files' dictionary. 166 The generation of json files depends on env_on. If the env_on looks like, 167 _BUILD_BP_JSON_ENV_ON = { 168 'SOONG_COLLECT_JAVA_DEPS': 'true', 169 'SOONG_COLLECT_CC_DEPS': 'true', 170 'SOONG_GEN_COMPDB': 'true', 171 'SOONG_GEN_RUST_PROJECT': 'true' 172 } 173 We want to generate 4 files: module_bp_java_deps.json, 174 module_bp_cc_deps.json, compile_commands.json and rust-project.json. And in 175 get_blueprint_json_files_relative_dict function, there are 4 json files 176 by default and return a result list of the absolute paths of the existent 177 files. 178 179 Args: 180 env_on: A dictionary of environment settings to be turned on, the 181 default value is _BUILD_BP_JSON_ENV_ON. 182 183 Returns: 184 A list of the absolute paths of the files which is going to be 185 generated. 186 """ 187 json_files_dict = common_util.get_blueprint_json_files_relative_dict() 188 file_paths = [] 189 for key in env_on: 190 if not env_on[key] == 'true' or key not in json_files_dict: 191 continue 192 file_paths.append(json_files_dict[key]) 193 return file_paths 194 195 196def _show_files_reuse_message(file_paths): 197 """Shows the message of build failure but files existing and reusing them. 198 199 Args: 200 file_paths: A list of absolute file paths to be checked. 201 """ 202 failed_or_file = ' or '.join(file_paths) 203 failed_and_file = ' and '.join(file_paths) 204 message = _GEN_JSON_FAILED.format(failed_or_file, failed_and_file) 205 print(_WARN_MSG.format(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(_WARN_MSG.format(common_util.COLORED_INFO('Warning:'), message)) 323 return 324 link_rust = os.path.join(root_dir, constant.RUST_PROJECT_JSON) 325 if os.path.islink(link_rust): 326 os.remove(link_rust) 327 os.symlink(rust_project, link_rust) 328