1#!/usr/bin/env python3 2# 3# Copyright 2019 - 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"""native_util 18 19This module has a collection of functions that provide helper functions for 20launching native projects in IDE. 21""" 22 23import os 24 25from aidegen import constant 26from aidegen.lib import clion_project_file_gen 27from aidegen.lib import common_util 28from aidegen.lib import native_module_info 29 30_RUST_JSON_NOT_EXIST = 'The json file: {} does not exist.' 31_RUST_DICT_BROKEN = 'The rust dictionary does not have "{}" key. It\'s broken.' 32_CRATES_KEY = 'crates' 33_ROOT_MODULE_KEY = 'root_module' 34 35 36def generate_clion_projects(targets): 37 """Generates CLion projects by targets. 38 39 Generates base CLion project file in the common parent directory of the 40 targets. 41 42 Usages: 43 >>>aidegen frameworks/native/libs/ui frameworks/native/lib/gui 44 the base will be generated in, 45 out/development/ide/clion/frameworks/native/libs/ 46 >>>aidegen frameworks/native/libs/ui art/libnativeloader 47 the base will be generated in, 48 out/development/ide/clion/ 49 but we expect normally native devs rarely use it in this way. 50 51 Args: 52 targets: A list of targets to check and generate their native projects. 53 54 Returns: 55 A symbolic link CLion project file path. 56 """ 57 cc_module_info = native_module_info.NativeModuleInfo() 58 parent_dir, targets = _get_merged_native_target(cc_module_info, targets) 59 rel_path = os.path.relpath(parent_dir, common_util.get_android_root_dir()) 60 # If the relative path is Android root, we won't show '.' in the path. 61 if rel_path == '.': 62 rel_path = '' 63 module_names = [] 64 for target in targets: 65 mod_info = cc_module_info.get_module_info(target) 66 clion_gen = clion_project_file_gen.CLionProjectFileGenerator( 67 mod_info, rel_path) 68 clion_gen.generate_cmakelists_file() 69 module_names.append(mod_info[constant.KEY_MODULE_NAME]) 70 return clion_project_file_gen.generate_base_cmakelists_file( 71 cc_module_info, rel_path, module_names) 72 73 74def _find_parent(abs_path, current_parent): 75 """Finds parent directory of two directories. 76 77 Args: 78 abs_path: A string of an absolute path of a directory. 79 current_parent: A string of the absolute path of current parent 80 directory. It could be None int the beginning. 81 82 Returns: 83 A string of new parent directory. 84 """ 85 if not current_parent: 86 return abs_path 87 if common_util.is_source_under_relative_path(abs_path, current_parent): 88 return current_parent 89 if common_util.is_source_under_relative_path(current_parent, abs_path): 90 return abs_path 91 return _find_parent( 92 os.path.dirname(abs_path), os.path.dirname(current_parent)) 93 94 95def _filter_out_modules(targets, filter_func): 96 """Filters out target from targets if it passes the filter function. 97 98 Args: 99 targets: A list of targets to be analyzed. 100 filter_func: A filter function reference. 101 102 Returns: 103 A tuple of a list of filtered module target and a list of lefted 104 targets. 105 """ 106 jtargets = [] 107 lefts = [] 108 for target in targets: 109 if filter_func(target): 110 jtargets.append(target) 111 continue 112 lefts.append(target) 113 return jtargets, lefts 114 115 116def _get_merged_native_target(cc_module_info, targets): 117 """Gets merged native parent target from original native targets. 118 119 If a target is a module, we put it directly into the new list. If a traget 120 is a path we put all the native modules under the path into the new list. 121 122 Args: 123 cc_module_info: A ModuleInfo instance contains the data of 124 module_bp_cc_deps.json. 125 targets: A list of targets to be merged. 126 127 Returns: 128 A tuple of a string of merged native project's relative path and a list 129 of new targets we have dealt with. 130 """ 131 parent_folder = None 132 new_targets = [] 133 for target in targets: 134 _, abs_path = common_util.get_related_paths(cc_module_info, target) 135 parent_folder = _find_parent(abs_path, parent_folder) 136 if cc_module_info.is_module(target): 137 new_targets.append(target) 138 continue 139 mod_names = cc_module_info.get_module_names_in_targets_paths([target]) 140 new_targets.extend(mod_names) 141 return parent_folder, new_targets 142 143 144def get_java_cc_and_rust_projects(atest_module_info, cc_module_info, targets): 145 """Gets native and java projects from targets. 146 147 Separates native and java projects from targets. 148 1. If it's a native module, add it to native projects. 149 2. If it's a java module, add it to java projects. 150 3. If it's a rust module, add it to rust targets. 151 152 Args: 153 atest_module_info: A ModuleInfo instance contains the merged data of 154 module-info.json and module_bp_java_deps.json. 155 cc_module_info: A ModuleInfo instance contains the data of 156 module_bp_cc_deps.json. 157 targets: A list of targets to be analyzed. 158 159 Returns: 160 A tuple of a list of java build targets, a list of C/C++ build 161 targets and a list of rust build targets. 162 """ 163 rtargets = _filter_out_rust_projects(targets) 164 ctargets, lefts = _filter_out_modules(targets, cc_module_info.is_module) 165 jtargets, lefts = _filter_out_modules(lefts, atest_module_info.is_module) 166 path_info = cc_module_info.path_to_module_info 167 jtars, ctars = _analyze_native_and_java_projects( 168 atest_module_info, path_info, lefts) 169 ctargets.extend(ctars) 170 jtargets.extend(jtars) 171 return jtargets, ctargets, rtargets 172 173 174def _analyze_native_and_java_projects(atest_module_info, path_info, targets): 175 """Analyzes native and java projects from targets. 176 177 Args: 178 atest_module_info: A ModuleInfo instance contains the merged data of 179 module-info.json and module_bp_java_deps.json. 180 path_info: A dictionary contains C/C++ projects' path as key 181 and module's info dictionary as value. 182 targets: A list of targets to be analyzed. 183 184 Returns: 185 A tuple of a list of java build targets and a list of C/C++ build 186 targets. 187 """ 188 jtargets = [] 189 ctargets = [] 190 for target in targets: 191 rel_path, abs_path = common_util.get_related_paths( 192 atest_module_info, target) 193 if common_util.check_java_or_kotlin_file_exists(abs_path): 194 jtargets.append(target) 195 if _check_native_project_exists(path_info, rel_path): 196 ctargets.append(target) 197 return jtargets, ctargets 198 199 200def _check_native_project_exists(path_to_module_info, rel_path): 201 """Checks if any C/C++ project exists in a rel_path directory. 202 203 Args: 204 path_to_module_info: A dictionary contains data of relative path as key 205 and module info dictionary as value. 206 rel_path: A string of relative path of a directory to be check. 207 208 Returns: 209 True if any C/C++ project exists otherwise False. 210 """ 211 for path in path_to_module_info: 212 if common_util.is_source_under_relative_path(path, rel_path): 213 return True 214 return False 215 216 217def _filter_out_rust_projects(targets): 218 """Filters out if the input targets contain any Rust project. 219 220 Args: 221 targets: A list of targets to be checked. 222 223 Returns: 224 A list of Rust projects. 225 """ 226 root_dir = common_util.get_android_root_dir() 227 rust_project_json = os.path.join( 228 root_dir, 229 common_util.get_blueprint_json_path(constant.RUST_PROJECT_JSON)) 230 if not os.path.isfile(rust_project_json): 231 message = _RUST_JSON_NOT_EXIST.format(rust_project_json) 232 print(constant.WARN_MSG.format( 233 common_util.COLORED_INFO('Warning:'), message)) 234 return None 235 rust_dict = common_util.get_json_dict(rust_project_json) 236 if _CRATES_KEY not in rust_dict: 237 message = _RUST_DICT_BROKEN.format(_CRATES_KEY) 238 print(constant.WARN_MSG.format( 239 common_util.COLORED_INFO('Warning:'), message)) 240 return None 241 return _get_rust_targets(targets, rust_dict[_CRATES_KEY], root_dir) 242 243 244def _get_rust_targets(targets, rust_modules_info, root_dir): 245 """Gets Rust targets by checking input targets with a rust info dictionary. 246 247 Args: 248 targets: A list of targets to be checked. 249 rust_modules_info: A list of the Android Rust modules info. 250 root_dir: A string of the Android root directory. 251 252 Returns: 253 A list of Rust targets. 254 """ 255 rtargets = [] 256 for target in targets: 257 # The Rust project can be expressed only in the path but not the module 258 # right now. 259 if not os.path.isdir(os.path.join(root_dir, target)): 260 continue 261 for mod_info in rust_modules_info: 262 if _ROOT_MODULE_KEY not in mod_info: 263 continue 264 path = mod_info[_ROOT_MODULE_KEY] 265 if common_util.is_source_under_relative_path(path, target): 266 rtargets.append(target) 267 return rtargets 268