def docker_requirements_generate(requirements_in='requirements-in.txt'): if not os.path.exists(requirements_in): requirements_in = os.path.join(BASE_FOLDER + "/../../", requirements_in) assert os.path.exists(requirements_in) requirements = read_file(requirements_in) logger.info(f'Generate requirements for Docker image') new = {} logger.info(requirements) for name, version in requirements.items(): # alas-webapp is for windows only if name == 'alas-webapp': continue if name == 'opencv-python': name = 'opencv-python-headless' version = None # if name == 'numpy': # version = None new[name] = version write_file(os.path.join(BASE_FOLDER, f'./requirements.txt'), data=new)
def iter_process_by_name(self, name): """ Args: name (str): process name, such as 'alas.exe' Yields: str, str, str: executable_path, process_name, process_id """ try: from win32com.client import GetObject except ModuleNotFoundError: logger.info('pywin32 not installed, skip') return False try: wmi = GetObject('winmgmts:') processes = wmi.InstancesOf('Win32_Process') for p in processes: executable_path = p.Properties_["ExecutablePath"].Value process_name = p.Properties_("Name").Value process_id = p.Properties_["ProcessID"].Value if executable_path is not None and process_name == name and process_id != self.self_pid: executable_path = executable_path.replace(r'\\', '/').replace( '\\', '/') for folder in self.alas_folder: if folder in executable_path: yield executable_path, process_name, process_id except Exception as e: # Possible exception # pywintypes.com_error: (-2147217392, 'OLE error 0x80041010', None, None) logger.info(str(e)) return False
def pip_install(self): logger.hr('Update Dependencies', 0) if not self.InstallDependencies: logger.info('InstallDependencies is disabled, skip') return logger.hr('Check Python', 1) self.execute(f'"{self.python}" --version') arg = [] if self.PypiMirror: mirror = self.PypiMirror arg += ['-i', mirror] # Trust http mirror if 'http:' in mirror: arg += ['--trusted-host', urlparse(mirror).hostname] # Don't update pip, just leave it. # logger.hr('Update pip', 1) # self.execute(f'"{self.pip}" install --upgrade pip{arg}') arg += ['--disable-pip-version-check'] logger.hr('Update Dependencies', 1) arg = ' ' + ' '.join(arg) if arg else '' self.execute(f'{self.pip} install -r {self.requirements_file}{arg}')
def app_update(self): logger.hr(f'Update app.asar', 0) if not self.AutoUpdate: logger.info('AutoUpdate is disabled, skip') return False return self.app_asar_replace(os.getcwd())
def show_fix_tip(module): logger.info(f""" To fix this: 1. Open console.bat 2. Execute the following commands: pip uninstall -y {module} pip install --no-cache-dir {module} 3. Re-open Alas.exe """)
def show_config(self): logger.hr("Show deploy config", 1) for k, v in self.config.items(): if k in ("Password", "SSHUser"): continue if self.config_template[k] == v: continue logger.info(f"{k}: {v}") logger.info(f"Rest of the configs are the same as default")
def kill_by_name(self, name): """ Args: name (str): Process name """ logger.hr(f'Kill {name}', 1) for row in self.iter_process_by_name(name): logger.info(' '.join(map(str, row))) self.execute(f'taskkill /f /pid {row[2]}', allow_failure=True, output=False)
def adb_kill(self): # self._execute([self.adb_binary, 'devices']) # self._execute([self.adb_binary, 'kill-server']) # Just kill it, because some adb don't obey. logger.info('Kill all known ADB') for exe in [ # Most emulator use this 'adb.exe', # NoxPlayer 夜神模拟器 'nox_adb.exe', # MumuPlayer MuMu模拟器 'adb_server.exe', # Bluestacks 蓝叠模拟器 'HD-Adb.exe' ]: ret_code = self._execute(['taskkill', '/f', '/im', exe], output=False) if ret_code == 0: logger.info(f'Task {exe} killed') elif ret_code == 128: logger.info(f'Task {exe} not found') else: logger.info( f'Error occurred when killing task {exe}, return code {ret_code}' )
def execute(self, command, allow_failure=False, output=True): """ Args: command (str): allow_failure (bool): output(bool): Returns: bool: If success. Terminate installation if failed to execute and not allow_failure. """ command = command.replace(r"\\", "/").replace("\\", "/").replace('"', '"') if not output: command = command + ' >nul 2>nul' logger.info(command) error_code = os.system(command) if error_code: if allow_failure: logger.info(f"[ allowed failure ], error_code: {error_code}") return False else: logger.info(f"[ failure ], error_code: {error_code}") self.show_error(command, error_code) raise ExecutionError else: logger.info(f"[ success ]") return True
def git_install(self): logger.hr('Update Alas', 0) if not self.AutoUpdate: logger.info('AutoUpdate is disabled, skip') return self.git_repository_init( repo=self.Repository, source='origin', branch=self.Branch, proxy=self.GitProxy, keep_changes=self.KeepLocalChanges, )
def emulators(self): """ Returns: list: List of installed emulators on current computer. """ emulators = [] for emulator in self.SUPPORTED_EMULATORS: try: serial = emulator.serial emulators.append(emulator) except FileNotFoundError: continue if len(serial): logger.info( f'Emulator {emulator.name} found, instances: {serial}') return emulators
def devices(self): """ Returns: list[str]: Connected devices in adb """ result = self._execute([self.adb_binary, 'devices']).decode() devices = [] for line in result.replace('\r\r\n', '\n').replace('\r\n', '\n').split('\n'): if line.startswith('List') or '\t' not in line: continue serial, status = line.split('\t') if status == 'device': devices.append(serial) logger.info(f'Devices: {devices}') return devices
def adb_install(self): logger.hr('Start ADB service', 0) emulator = EmulatorConnect(adb=self.adb) if self.ReplaceAdb: logger.hr('Replace ADB', 1) emulator.adb_replace() elif self.AutoConnect: logger.hr('ADB Connect', 1) emulator.brute_force_connect() if self.InstallUiautomator2: logger.hr('Uiautomator2 Init', 1) try: import adbutils except ModuleNotFoundError as e: message = str(e) for module in ['apkutils2', 'progress']: # ModuleNotFoundError: No module named 'apkutils2' # ModuleNotFoundError: No module named 'progress.bar' if module in message: show_fix_tip(module) exit(1) # Remove global proxies, or uiautomator2 will go through it for k in list(os.environ.keys()): if k.lower().endswith('_proxy'): del os.environ[k] from uiautomator2.init import Initer for device in adbutils.adb.iter_device(): init = Initer(device, loglevel=logging.DEBUG) init.set_atx_agent_addr('127.0.0.1:7912') try: init.install() except AssertionError: logger.info( f'AssertionError when installing uiautomator2 on device {device.serial}' ) logger.info( 'If you are using BlueStacks or LD player or WSA, ' 'please enable ADB in the settings of your emulator') exit(1) init._device.shell(["rm", "/data/local/tmp/minicap"]) init._device.shell(["rm", "/data/local/tmp/minicap.so"])
def aidlux_requirements_generate(requirements_in='requirements-in.txt'): logger.info('aidlux_requirements_generate') requirements = read_file(requirements_in) for aidlux in iter_version(): logger.info(f'Generate requirements for AidLux {aidlux}') pre_installed = read_file( os.path.join(BASE_FOLDER, f'./{aidlux}/pre-installed.txt')) new = {} for name, version in requirements.items(): # alas-webapp is for windows only if name == 'alas-webapp': continue version = pre_installed.get(name, version) # Having conflicts with numpy, let pip to decide if name == 'numpy': version = None new[name] = version write_file(os.path.join(BASE_FOLDER, f'./{aidlux}/requirements.txt'), data=new)
def adb_replace(self, adb): """ Backup the adb in emulator folder to xxx.bak, replace it with your adb. Need to call `adb kill-server` before replacing. Args: adb (str): Absolute path to adb.exe """ for ori, bak in zip(self.adb_binary, self.adb_backup): logger.info(f'Replacing {ori}') if os.path.exists(ori): if filecmp.cmp(adb, ori, shallow=True): logger.info(f'{adb} is same as {ori}, skip') else: logger.info(f'{ori} -----> {bak}') shutil.move(ori, bak) logger.info(f'{adb} -----> {ori}') shutil.copy(adb, ori) else: logger.info(f'{ori} not exists, skip')
def adb_recover(self): """ Revert adb replacement """ for ori in self.adb_binary: logger.info(f'Recovering {ori}') bak = f'{ori}.bak' if os.path.exists(bak): logger.info(f'Delete {ori}') if os.path.exists(ori): os.remove(ori) logger.info(f'{bak} -----> {ori}') shutil.move(bak, ori) else: logger.info(f'Not exists {bak}, skip')
def show_error(self, command=None, error_code=None): logger.hr("Update failed", 0) self.show_config() logger.info("") logger.info(f"Last command: {command}\nerror_code: {error_code}") logger.info("Please check your deploy settings in config/deploy.yaml " "and re-open Alas.exe")
def _execute(self, cmd, timeout=10, output=True): """ Returns: Object: Stdout(str) of cmd if output, return code(int) of cmd if not output. """ if not output: cmd.extend(['>nul', '2>nul']) logger.info(' '.join(cmd)) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) try: stdout, stderr = process.communicate(timeout=timeout) ret_code = process.returncode except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() ret_code = 1 logger.info(f'TimeoutExpired, stdout={stdout}, stderr={stderr}') if output: return stdout else: return ret_code
def git_repository_init(self, repo, source='origin', branch='master', proxy='', keep_changes=False): logger.hr('Git Init', 1) self.execute(f'"{self.git}" init') logger.hr('Set Git Proxy', 1) if proxy: self.execute(f'"{self.git}" config --local http.proxy {proxy}') self.execute(f'"{self.git}" config --local https.proxy {proxy}') else: self.execute(f'"{self.git}" config --local --unset http.proxy', allow_failure=True) self.execute(f'"{self.git}" config --local --unset https.proxy', allow_failure=True) logger.hr('Set Git Repository', 1) if not self.execute(f'"{self.git}" remote set-url {source} {repo}', allow_failure=True): self.execute(f'"{self.git}" remote add {source} {repo}') logger.hr('Fetch Repository Branch', 1) self.execute(f'"{self.git}" fetch {source} {branch}') logger.hr('Pull Repository Branch', 1) # Remove git lock lock_file = './.git/index.lock' if os.path.exists(lock_file): logger.info(f'Lock file {lock_file} exists, removing') os.remove(lock_file) if keep_changes: if self.execute(f'"{self.git}" stash', allow_failure=True): self.execute(f'"{self.git}" pull --ff-only {source} {branch}') if self.execute(f'"{self.git}" stash pop', allow_failure=True): pass else: # No local changes to existing files, untracked files not included logger.info( 'Stash pop failed, there seems to be no local changes, skip instead' ) else: logger.info( 'Stash failed, this may be the first installation, drop changes instead' ) self.execute(f'"{self.git}" reset --hard {source}/{branch}') self.execute(f'"{self.git}" pull --ff-only {source} {branch}') else: self.execute(f'"{self.git}" reset --hard {source}/{branch}') self.execute(f'"{self.git}" pull --ff-only {source} {branch}') logger.hr('Show Version', 1) self.execute(f'"{self.git}" log --no-merges -1')
def app_asar_replace(folder, path='./toolkit/WebApp/resources/app.asar'): """ Args: folder (str): Path to AzurLaneAutoScript path (str): Path from AzurLaneAutoScript to app.asar Returns: bool: If updated. """ source = os.path.abspath(os.path.join(folder, path)) logger.info(f'Old file: {source}') try: import alas_webapp except ImportError: logger.info(f'Dependency alas_webapp not exists, skip updating') return False update = alas_webapp.app_file() logger.info(f'New version: {alas_webapp.__version__}') logger.info(f'New file: {update}') if os.path.exists(source): if filecmp.cmp(source, update, shallow=True): logger.info('app.asar is already up to date') return False else: logger.info(f'Copy {update} -----> {source}') os.remove(source) shutil.copy(update, source) return True else: logger.info(f'{source} not exists, skip updating') return False
import os from deploy.logger import logger BASE_FOLDER = os.path.dirname(os.path.abspath(__file__)) logger.info(BASE_FOLDER) def read_file(file): out = {} with open(file, 'r', encoding='utf-8') as f: for line in f.readlines(): res = [s.strip() for s in line.split('==')] if len(res) > 1: name, version = res else: name, version = res[0], None out[name] = version return out def write_file(file, data): lines = [] for name, version in data.items(): if version: lines.append(f'{name}=={version}') else: lines.append(str(name)) with open(file, 'w', encoding='utf-8', newline='') as f: f.write('\n'.join(lines))
asyncio.run(connect()) return self.devices() def adb_replace(self, adb=None): """ Different version of ADB will kill each other when starting. Chinese emulators (NoxPlayer, LDPlayer, MemuPlayer, MuMuPlayer) use their own adb, instead of the one in system PATH, so when they start they kill the adb.exe Alas is using. Replacing the ADB in emulator is the simplest way to solve this. Args: adb (str): Absolute path to adb.exe """ self.adb_kill() for emulator in self.emulators: emulator.adb_replace(adb if adb is not None else self.adb_binary) self.brute_force_connect() def adb_recover(self): """ Revert adb replacement """ self.adb_kill() for emulator in self.emulators: emulator.adb_recover() self.brute_force_connect() if __name__ == '__main__': emu = EmulatorConnect() logger.info(emu.brute_force_connect())