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"""ide_common_util 18 19This module has a collection of helper functions of the ide_util module. 20""" 21 22import fnmatch 23import glob 24import logging 25import os 26import subprocess 27 28from aidegen import constant 29 30_IDEA_FOLDER = '.idea' 31_IML_EXTENSION = '.iml' 32 33 34def get_script_from_internal_path(ide_paths, ide_name): 35 """Get the studio.sh script path from internal path. 36 37 Args: 38 ide_paths: A list of IDE installed paths to be checked. 39 ide_name: The IDE name. 40 41 Returns: 42 The list of the full path of IDE or None if the IDE doesn't exist. 43 """ 44 for ide_path in ide_paths: 45 ls_output = glob.glob(ide_path, recursive=True) 46 ls_output = sorted(ls_output) 47 if ls_output: 48 logging.debug('The script%s for %s %s found.', 49 's' if len(ls_output) > 1 else '', ide_name, 50 'are' if len(ls_output) > 1 else 'is') 51 return ls_output 52 logging.error('There is not any script of %s found.', ide_name) 53 return None 54 55 56def _run_ide_sh(run_sh_cmd, project_path): 57 """Run IDE launching script with an IntelliJ project path as argument. 58 59 Args: 60 run_sh_cmd: The command to launch IDE. 61 project_path: The path of IntelliJ IDEA project content. 62 """ 63 assert run_sh_cmd, 'No suitable IDE installed.' 64 logging.debug('Run command: "%s" to launch project.', run_sh_cmd) 65 try: 66 subprocess.check_call(run_sh_cmd, shell=True) 67 except subprocess.CalledProcessError as err: 68 logging.error('Launch project path %s failed with error: %s.', 69 project_path, err) 70 71 72def _walk_tree_find_ide_exe_file(top, ide_script_name): 73 """Recursively descend the directory tree rooted at top and filter out the 74 IDE executable script we need. 75 76 Args: 77 top: the tree root to be checked. 78 ide_script_name: IDE file name such i.e. IdeIntelliJ._INTELLIJ_EXE_FILE. 79 80 Returns: 81 the IDE executable script file(s) found. 82 """ 83 logging.info('Searching IDE script %s in path: %s.', ide_script_name, top) 84 for root, _, files in os.walk(top): 85 logging.debug('Search all files under %s to get %s, %s.', top, root, 86 files) 87 for file_ in fnmatch.filter(files, ide_script_name): 88 exe_file = os.path.join(root, file_) 89 if os.access(exe_file, os.X_OK): 90 logging.debug('Use file name filter to find %s in path %s.', 91 file_, exe_file) 92 yield exe_file 93 94 95def get_run_ide_cmd(sh_path, project_file, new_process=True): 96 """Get the command to launch IDE. 97 98 Args: 99 sh_path: The idea.sh path where IDE is installed. 100 project_file: The path of IntelliJ IDEA project file. 101 new_process: Default is True, means to run command in a new process. 102 103 Returns: 104 A string: The IDE launching command. 105 """ 106 process_flag = '&' if new_process else '' 107 # In command usage, the space ' ' should be '\ ' for correctness. 108 return ' '.join([ 109 constant.NOHUP, sh_path.replace(' ', r'\ '), project_file, 110 constant.IGNORE_STD_OUT_ERR_CMD, process_flag 111 ]) 112 113 114def _get_scripts_from_file_path(input_path, ide_file_name): 115 """Get IDE executable script file from input file path. 116 117 Args: 118 input_path: the file path to be checked. 119 ide_file_name: the IDE executable script file name. 120 121 Returns: 122 A list of the IDE executable script path if exists otherwise None. 123 """ 124 if os.path.basename(input_path).startswith(ide_file_name): 125 files_found = glob.glob(input_path) 126 if files_found: 127 return sorted(files_found) 128 return None 129 130 131def get_scripts_from_dir_path(input_path, ide_file_name): 132 """Get an IDE executable script file from input directory path. 133 134 Args: 135 input_path: the directory to be searched. 136 ide_file_name: the IDE executable script file name. 137 138 Returns: 139 A list of an IDE executable script paths if exist otherwise None. 140 """ 141 logging.debug('Call get_scripts_from_dir_path with %s, and %s', input_path, 142 ide_file_name) 143 files_found = list(_walk_tree_find_ide_exe_file(input_path, 144 ide_file_name + '*')) 145 if files_found: 146 return sorted(files_found) 147 return None 148 149 150def launch_ide(project_path, run_ide_cmd, ide_name): 151 """Launches relative IDE by opening the passed project file. 152 153 Args: 154 project_path: The full path of the IDE project content. 155 run_ide_cmd: The command to launch IDE. 156 ide_name: the IDE name is to be launched. 157 """ 158 assert project_path, 'Empty content path is not allowed.' 159 if ide_name == constant.IDE_ECLIPSE: 160 logging.info( 161 'Launch %s with workspace: %s.', ide_name, constant.ECLIPSE_WS) 162 else: 163 logging.info('Launch %s for project content path: %s.', ide_name, 164 project_path) 165 _run_ide_sh(run_ide_cmd, project_path) 166 167 168def is_intellij_project(project_path): 169 """Checks if the path passed in is an IntelliJ project content. 170 171 Args: 172 project_path: The full path of IDEA project content, which contains 173 .idea folder and .iml file(s). 174 175 Returns: 176 True if project_path is an IntelliJ project, False otherwise. 177 """ 178 if not os.path.isfile(project_path): 179 return os.path.isdir(project_path) and os.path.isdir( 180 os.path.join(project_path, _IDEA_FOLDER)) 181 182 _, ext = os.path.splitext(os.path.basename(project_path)) 183 if ext and _IML_EXTENSION == ext.lower(): 184 path = os.path.dirname(project_path) 185 logging.debug('Extracted path is: %s.', path) 186 return os.path.isdir(os.path.join(path, _IDEA_FOLDER)) 187 return False 188 189 190def get_script_from_input_path(input_path, ide_file_name): 191 """Get correct IntelliJ executable script path from input path. 192 193 1. If input_path is a file, check if it is an IDE executable script file. 194 2. It input_path is a directory, search if it contains IDE executable script 195 file(s). 196 197 Args: 198 input_path: input path to be checked if it's an IDE executable 199 script. 200 ide_file_name: the IDE executable script file name. 201 202 Returns: 203 IDE executable file(s) if exists otherwise None. 204 """ 205 if not input_path: 206 return None 207 ide_path = [] 208 if os.path.isfile(input_path): 209 ide_path = _get_scripts_from_file_path(input_path, ide_file_name) 210 if os.path.isdir(input_path): 211 ide_path = get_scripts_from_dir_path(input_path, ide_file_name) 212 if ide_path: 213 logging.debug('IDE installed path from user input: %s.', ide_path) 214 return ide_path 215 return None 216 217 218def get_intellij_version_path(version_path): 219 """Locates the IntelliJ IDEA launch script path by version. 220 221 Args: 222 version_path: IntelliJ CE or UE version launch script path. 223 224 Returns: 225 A list of the sh full paths, or None if no such IntelliJ version is 226 installed. 227 """ 228 ls_output = glob.glob(version_path, recursive=True) 229 if not ls_output: 230 return None 231 ls_output = sorted(ls_output, reverse=True) 232 logging.debug('Result for checking IntelliJ path %s after sorting:%s.', 233 version_path, ls_output) 234 return ls_output 235 236 237def ask_preference(all_versions, ide_name): 238 """Ask users which version they prefer. 239 240 Args: 241 all_versions: A list of all CE and UE version launch script paths. 242 ide_name: The IDE name is going to be launched. 243 244 Returns: 245 An users selected version. 246 """ 247 options = [] 248 for i, sfile in enumerate(all_versions, 1): 249 options.append('\t{}. {}'.format(i, sfile)) 250 query = ('You installed {} versions of {}:\n{}\nPlease select ' 251 'one.\t').format(len(all_versions), ide_name, '\n'.join(options)) 252 return _select_intellij_version(query, all_versions) 253 254 255def _select_intellij_version(query, all_versions): 256 """Select one from different IntelliJ versions users installed. 257 258 Args: 259 query: The query message. 260 all_versions: A list of all CE and UE version launch script paths. 261 """ 262 all_numbers = [] 263 for i in range(len(all_versions)): 264 all_numbers.append(str(i + 1)) 265 input_data = input(query) 266 while input_data not in all_numbers: 267 input_data = input('Please select a number:\t') 268 return all_versions[int(input_data) - 1] 269