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_module_info_json(atest_module_info, project, verbose) 26""" 27 28import glob 29import json 30import logging 31import os 32import subprocess 33import sys 34 35from aidegen import constant 36from aidegen.lib.common_util import COLORED_INFO 37from aidegen.lib.common_util import time_logged 38from aidegen.lib.common_util import get_related_paths 39from aidegen.lib import errors 40 41_BLUEPRINT_JSONFILE_NAME = 'module_bp_java_deps.json' 42_KEY_CLS = 'class' 43_KEY_PATH = 'path' 44_KEY_INS = 'installed' 45_KEY_DEP = 'dependencies' 46_KEY_SRCS = 'srcs' 47_MERGE_NEEDED_ITEMS = [_KEY_CLS, _KEY_PATH, _KEY_INS, _KEY_DEP, _KEY_SRCS] 48_INTELLIJ_PROJECT_FILE_EXT = '*.iml' 49_LAUNCH_PROJECT_QUERY = ( 50 'There exists an IntelliJ project file: %s. Do you want ' 51 'to launch it (yes/No)?') 52_GENERATE_JSON_COMMAND = ('SOONG_COLLECT_JAVA_DEPS=false make nothing;' 53 'SOONG_COLLECT_JAVA_DEPS=true make nothing') 54 55 56@time_logged 57def generate_module_info_json(module_info, projects, verbose, skip_build=False): 58 """Generate a merged json dictionary. 59 60 Change directory to ANDROID_ROOT_PATH before making _GENERATE_JSON_COMMAND 61 to avoid command error: "make: *** No rule to make target 'nothing'. Stop." 62 and change back to current directory after command completed. 63 64 Linked functions: 65 _build_target(project, verbose) 66 _get_soong_build_json_dict() 67 _merge_json(mk_dict, bp_dict) 68 69 Args: 70 module_info: A ModuleInfo instance contains data of module-info.json. 71 projects: A list of project names. 72 verbose: A boolean, if true displays full build output. 73 skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if 74 it exists, otherwise build it. 75 76 Returns: 77 A tuple of Atest module info instance and a merged json dictionary. 78 """ 79 cwd = os.getcwd() 80 os.chdir(constant.ANDROID_ROOT_PATH) 81 _build_target([_GENERATE_JSON_COMMAND], projects[0], module_info, verbose, 82 skip_build) 83 os.chdir(cwd) 84 bp_dict = _get_soong_build_json_dict() 85 return _merge_json(module_info.name_to_module_info, bp_dict) 86 87 88def _build_target(cmd, main_project, module_info, verbose, skip_build=False): 89 """Make nothing to generate module_bp_java_deps.json. 90 91 We build without environment setting SOONG_COLLECT_JAVA_DEPS and then build 92 with environment setting SOONG_COLLECT_JAVA_DEPS. In this way we can trigger 93 the process of collecting dependencies and generating 94 module_bp_java_deps.json. 95 96 Args: 97 cmd: A string list, build command. 98 main_project: The main project name. 99 module_info: A ModuleInfo instance contains data of module-info.json. 100 verbose: A boolean, if true displays full build output. 101 skip_build: A boolean, if true skip building _BLUEPRINT_JSONFILE_NAME if 102 it exists, otherwise build it. 103 104 Build results: 105 1. Build successfully return. 106 2. Build failed: 107 1) There's no project file, raise BuildFailureError. 108 2) There exists a project file, ask users if they want to 109 launch IDE with the old project file. 110 a) If the answer is yes, return. 111 b) If the answer is not yes, sys.exit(1) 112 """ 113 json_path = _get_blueprint_json_path() 114 original_json_mtime = None 115 if os.path.isfile(json_path): 116 if skip_build: 117 logging.info('%s file exists, skipping build.', 118 _BLUEPRINT_JSONFILE_NAME) 119 return 120 original_json_mtime = os.path.getmtime(json_path) 121 try: 122 if verbose: 123 full_env_vars = os.environ.copy() 124 subprocess.check_call( 125 cmd, stderr=subprocess.STDOUT, env=full_env_vars, shell=True) 126 else: 127 subprocess.check_call(cmd, shell=True) 128 logging.info('Build successfully: %s.', cmd) 129 except subprocess.CalledProcessError: 130 if not _is_new_json_file_generated(json_path, original_json_mtime): 131 if os.path.isfile(json_path): 132 message = ('Generate new {0} failed, AIDEGen will proceed and ' 133 'reuse the old {0}.'.format(json_path)) 134 print('\n{} {}\n'.format(COLORED_INFO('Warning:'), message)) 135 else: 136 _, main_project_path = get_related_paths(module_info, main_project) 137 _build_failed_handle(main_project_path) 138 139 140def _is_new_json_file_generated(json_path, original_file_mtime): 141 """Check the new file is generated or not. 142 143 Args: 144 json_path: The path of the json file being to check. 145 original_file_mtime: the original file modified time. 146 """ 147 if not original_file_mtime: 148 return os.path.isfile(json_path) 149 return original_file_mtime != os.path.getmtime(json_path) 150 151 152def _build_failed_handle(main_project_path): 153 """Handle build failures. 154 155 Args: 156 main_project_path: The main project directory. 157 158 Handle results: 159 1) There's no project file, raise BuildFailureError. 160 2) There exists a project file, ask users if they want to 161 launch IDE with the old project file. 162 a) If the answer is yes, return. 163 b) If the answer is not yes, sys.exit(1) 164 """ 165 project_file = glob.glob( 166 os.path.join(main_project_path, _INTELLIJ_PROJECT_FILE_EXT)) 167 if project_file: 168 query = (_LAUNCH_PROJECT_QUERY) % project_file[0] 169 input_data = input(query) 170 if not input_data.lower() in ['yes', 'y']: 171 sys.exit(1) 172 else: 173 raise errors.BuildFailureError( 174 'Failed to generate %s.' % _get_blueprint_json_path()) 175 176 177def _get_soong_build_json_dict(): 178 """Load a json file from path and convert it into a json dictionary. 179 180 Returns: 181 A json dictionary. 182 """ 183 json_path = _get_blueprint_json_path() 184 try: 185 with open(json_path) as jfile: 186 json_dict = json.load(jfile) 187 return json_dict 188 except IOError as err: 189 raise errors.JsonFileNotExistError( 190 '%s does not exist, error: %s.' % (json_path, err)) 191 192 193def _get_blueprint_json_path(): 194 """Assemble the path of blueprint json file. 195 196 Returns: 197 Blueprint json path. 198 """ 199 return os.path.join(constant.SOONG_OUT_DIR_PATH, _BLUEPRINT_JSONFILE_NAME) 200 201 202def _merge_module_keys(m_dict, b_dict): 203 """Merge a module's json dictionary into another module's json dictionary. 204 205 Args: 206 m_dict: The module dictionary is going to merge b_dict into. 207 b_dict: Soong build system module dictionary. 208 """ 209 for key, b_modules in b_dict.items(): 210 m_dict[key] = sorted(list(set(m_dict.get(key, []) + b_modules))) 211 212 213def _copy_needed_items_from(mk_dict): 214 """Shallow copy needed items from Make build system part json dictionary. 215 216 Args: 217 mk_dict: Make build system json dictionary is going to be copyed. 218 219 Returns: 220 A merged json dictionary. 221 """ 222 merged_dict = dict() 223 for module in mk_dict.keys(): 224 merged_dict[module] = dict() 225 for key in mk_dict[module].keys(): 226 if key in _MERGE_NEEDED_ITEMS and mk_dict[module][key] != []: 227 merged_dict[module][key] = mk_dict[module][key] 228 return merged_dict 229 230 231def _merge_json(mk_dict, bp_dict): 232 """Merge two json dictionaries. 233 234 Linked function: 235 _merge_module_keys(m_dict, b_dict) 236 237 Args: 238 mk_dict: Make build system part json dictionary. 239 bp_dict: Soong build system part json dictionary. 240 241 Returns: 242 A merged json dictionary. 243 """ 244 merged_dict = _copy_needed_items_from(mk_dict) 245 for module in bp_dict.keys(): 246 if not module in merged_dict.keys(): 247 merged_dict[module] = dict() 248 _merge_module_keys(merged_dict[module], bp_dict[module]) 249 return merged_dict 250