def __prune_remote_branches( self, username): # remove deleted remote branches locally last_prune = self.fork_params.get('last_prune') if isinstance(last_prune, str) and datetime.now().strftime("%d") == last_prune: return self.fork_params.put('last_prune', datetime.now().strftime("%d")) r = check_output([ 'git', '-C', OPENPILOT_PATH, 'remote', 'prune', username, '--dry-run' ]) if r.output == '': # nothing to prune return branches_to_prune = [ b.strip() for b in r.output.split('\n') if 'would prune' in b ] branches_to_prune = [b[b.index(username):] for b in branches_to_prune] error('Deleted remote branches detected:', start='\n') for b in branches_to_prune: print(COLORS.CYAN + ' - {}'.format(b) + COLORS.ENDC) warning('\nWould you like to delete them locally?') if is_affirmative(): r = check_output( ['git', '-C', OPENPILOT_PATH, 'remote', 'prune', username]) if r.success: success('Pruned local branches successfully!') else: error('Please try again, something went wrong:') print(r.output)
def __init__(self, description=None, commands=None, flags=None): self.parser = ArgumentParser() self.description = description self.commands = commands self.has_flags = False self.flags = flags if flags is not None: self.has_flags = True for flag in flags: # for each flag, add it as argument with aliases. parser_args = {} # handle various conditions if not flag.required and flag.dtype not in ['bool']: parser_args['nargs'] = '?' if flag.dtype != 'bool': parser_args['action'] = 'store' elif flag.dtype == 'bool': parser_args['action'] = 'store_true' if flag.dtype == 'bool': # type bool is not required when store_true pass elif flag.dtype == 'str': parser_args['type'] = str elif flag.dtype == 'int': parser_args['type'] = int else: error('Unsupported dtype: {}'.format(flag.dtype)) return self.parser.add_argument(*flag.aliases, help=flag.description, **parser_args)
def get_flags(self, cmd_name): try: return self.commands[cmd_name].parser.parse_args(self.args) except Exception as e: error(e) self._help(cmd_name) exit(1)
def _list(self): if not self._init(): return flags = self.get_flags('list') specified_fork = flags.fork installed_forks = self.fork_params.get('installed_forks') if specified_fork is None: max_branches = 4 # max branches to display per fork when listing all forks success('Installed forks:') for idi, fork in enumerate(installed_forks): print('- {}{}{}'.format(COLORS.OKBLUE, fork, COLORS.ENDC), end='') current_fork = self.fork_params.get('current_fork') if current_fork == fork: print(' (current)') else: print() branches = installed_forks[fork]['installed_branches'] current_branch = self.fork_params.get('current_branch') if current_branch in branches: branches.remove(current_branch) branches.insert( 0, current_branch) # move cur_branch to beginning if len(branches) > 0: success(' Branches:') for idx, branch in enumerate(branches): if idx < max_branches: print(' - {}{}{}'.format(COLORS.RED, branch, COLORS.ENDC), end='') if branch == current_branch and fork == current_fork: print(' (current)') else: print() else: print( ' - {}...see more branches: {}emu fork list {}{}' .format(COLORS.RED, COLORS.CYAN, fork, COLORS.ENDC)) break print() else: specified_fork = specified_fork.lower() remote_info = self.__get_remote_info(specified_fork) if remote_info is not None: # there's an overriding default username available specified_fork = remote_info.username if specified_fork not in installed_forks: error( '{} not an installed fork! Try installing it with the {}switch{} command' .format(specified_fork, COLORS.CYAN, COLORS.RED)) return installed_branches = installed_forks[specified_fork][ 'installed_branches'] success('Installed branches for {}:'.format(specified_fork)) for branch in installed_branches: print(' - {}{}{}'.format(COLORS.RED, branch, COLORS.ENDC))
def _shutdown(self): flags, e = self.parse_flags(self.commands['shutdown'].parser) if e is not None: error(e) self._help('shutdown') return if flags.reboot: self.__reboot() return check_output( 'am start -n android/com.android.internal.app.ShutdownActivity') success('🌙 Goodnight!')
def _controlsd(self): flags, e = self.parse_flags(self.commands['controlsd'].parser) if e is not None: error(e) return out_file = self.default_path if flags.output is not None: out_file = flags.output # r = run('pkill -f controlsd') # terminates file for some reason # todo: remove me if not needed r = kill('selfdrive.controls.controlsd' ) # seems to work, some process names are weird if r is None: warning('controlsd is already dead! (continuing...)') run('python {}/selfdrive/controls/controlsd.py'.format(OPENPILOT_PATH), out_file=out_file)
def __init_submodules(): r = check_output(['git', '-C', OPENPILOT_PATH, 'submodule', 'status']) if len(r.output): info('Submodules detected, reinitializing!') r0 = check_output([ 'git', '-C', OPENPILOT_PATH, 'submodule', 'deinit', '--all', '-f' ]) r1 = check_output([ 'git', '-C', OPENPILOT_PATH, 'submodule', 'update', '--init', '--recursive' ]) if not r0.success or not r1.success: error('Error reinitializing submodules for this branch!') else: return True return False
def _reload(): info('This will kill the current openpilot tmux session, set up a new one, and relaunch openpilot.') info('Confirm you would like to continue') if not is_affirmative(): error('Aborting!') return r = check_output('tmux kill-session -t comma') if r.success: info('Killed the current openpilot session') else: warning('Error killing current openpilot session, continuing...') # Command below thanks to mlp r = check_output(['tmux', 'new', '-s', 'comma', '-d', "echo $$ > /dev/cpuset/app/tasks;" # add pid of current shell to app cpuset "echo $PPID > /dev/cpuset/app/tasks;" # (our parent, tmux, also gets all the cores) "/data/openpilot/launch_openpilot.sh"]) if r.success: success('Succesfully started a new tmux session for openpilot!') success('Type {}tmux a{} to attach to it'.format(COLORS.FAIL, COLORS.SUCCESS))
def _battery(): r = check_output('dumpsys batterymanager') if not r: error('Unable to get battery status!') return r = r.output.split('\n') r = [i.strip() for i in r if i != ''][1:] battery_idxs = {'level': 7, 'temperature': 10} success('Battery info:') for name in battery_idxs: idx = battery_idxs[name] info = r[idx] value = float(info.split(': ')[1]) if name == 'temperature': value /= 10 value = str(value) + '°C' else: value = str(value) + '%' value = COLORS.SUCCESS + str(value) name = COLORS.WARNING + name.title() print('- {}: {}{}'.format(name, value, COLORS.ENDC))
def __get_remote_branches(r): # get remote's branches to verify from output of command in parent function if not r.success: error(r.output) return None, None if REMOTE_BRANCHES_START in r.output: start_remote_branches = r.output.index(REMOTE_BRANCHES_START) remote_branches_txt = r.output[start_remote_branches + len(REMOTE_BRANCHES_START):].split( '\n') remote_branches = [] for b in remote_branches_txt: b = b.replace('tracked', '').strip() if 'stale' in b: # support stale/to-be-pruned branches b = b.split(' ')[0].split('/')[-1] if ' ' in b or b == '': # end of branches break remote_branches.append(b) elif REMOTE_BRANCH_START in r.output: # remote has single branch, shouldn't need to handle stale here start_remote_branch = r.output.index(REMOTE_BRANCH_START) remote_branches = r.output[start_remote_branch + len(REMOTE_BRANCH_START):].split('\n') remote_branches = [ b.replace('tracked', '').strip() for b in remote_branches if b.strip() != '' and 'tracked' in b ] else: error('Unable to parse remote branches!') return None, None if len(remote_branches) == 0: error('Error getting remote branches!') return None, None start_default_branch = r.output.index( DEFAULT_BRANCH_START) # get default branch to return default_branch = r.output[start_default_branch + len(DEFAULT_BRANCH_START):] end_default_branch = default_branch.index('\n') default_branch = default_branch[:end_default_branch] return remote_branches, default_branch
def _uninstall(): print('Are you sure you want to uninstall emu?') if input_with_options(['Y', 'n'], 'n')[0] == 0: run(['sh', UNINSTALL_PATH]) else: error('Not uninstalling!')
def _flash(): r = run('make -C {}/panda/board recover'.format(OPENPILOT_PATH)) if not r: error('Error running make command!')
def _init(self): if os.path.isdir('/data/community/forks'): shutil.rmtree('/data/community/forks') # remove to save space if self.fork_params.get('setup_complete'): if os.path.exists(OPENPILOT_PATH): r = check_output( ['git', '-C', OPENPILOT_PATH, 'remote', 'show']) if self.comma_origin_name in r.output.split( '\n' ): # sign that we're set up correctly todo: check all forks exist as remotes return True self.fork_params.put( 'setup_complete', False ) # renamed origin -> commaai does not exist, restart setup self.fork_params.reset() warning( 'There was an error with your clone of commaai/openpilot, restarting initialization!' ) info( 'To set up emu fork management we will clone commaai/openpilot into {}' .format(OPENPILOT_PATH)) info('Confirm you would like to continue') if not is_affirmative(): error('Stopping initialization!') return # backup openpilot here to free up /data/openpilot if os.path.exists(OPENPILOT_PATH): bak_dir = '{}.bak'.format(OPENPILOT_PATH) idx = 0 while os.path.exists(bak_dir): bak_dir = '{}{}'.format(bak_dir, idx) idx += 1 shutil.move(OPENPILOT_PATH, bak_dir) success('Backed up your current openpilot install to {}'.format( bak_dir)) info('Cloning commaai/openpilot into {}, please wait...'.format( OPENPILOT_PATH)) r = run([ 'git', 'clone', '-b', self.comma_default_branch, GIT_OPENPILOT_URL, OPENPILOT_PATH ]) # default to stock/release2 for setup if not r: error('Error while cloning, please try again') return # rename origin to commaai so it's easy to switch to stock without any extra logic for url checking, etc r = check_output([ 'git', '-C', OPENPILOT_PATH, 'remote', 'rename', 'origin', self.comma_origin_name ]) if not r.success: error(r.output) return # rename release2 to commaai_release2 to align with emu fork standards r = check_output([ 'git', '-C', OPENPILOT_PATH, 'branch', '-m', f'{self.comma_origin_name}_{self.comma_default_branch}' ]) if not r.success: error(r.output) return # set git config push.default to `upstream` to remove differently named remote branch warning when pushing check_output([ 'git', '-C', OPENPILOT_PATH, 'config', 'push.default', 'upstream' ]) # not game breaking if this fails # remember username and password of user for pushing check_output([ 'git', '-C', OPENPILOT_PATH, 'config', 'credential.helper', 'cache --timeout=1440' ]) # cache for a day success('Fork management set up successfully! You\'re on {}/{}'.format( self.comma_origin_name, self.comma_default_branch)) success( 'To get started, try running: {}emu fork switch (username) [-b BRANCH]{}' .format(COLORS.RED, COLORS.ENDC)) self.__add_fork(self.comma_origin_name, self.comma_default_branch) self.fork_params.put('setup_complete', True) self.fork_params.put('current_fork', self.comma_origin_name) self.fork_params.put('current_branch', self.comma_default_branch) return True
def _flash2(): if not run('pkill -f boardd'): error('Error killing boardd! Is it running? (continuing...)') importlib.import_module('panda', 'Panda').Panda().flash()
def start_function_from_str(self, cmd): cmd = '_' + cmd if not hasattr(self, cmd): error('Command has not been implemented yet, please try updating.') return getattr(self, cmd)() # call command's function
def _switch(self): if not self._init(): return flags = self.get_flags('switch') if flags.username is flags.branch is None: # since both are non-required we need custom logic to check user supplied sufficient args/flags error('You must supply either username or branch or both') self._help('switch') return username = flags.username branch = flags.branch repo_name = flags.repo force_switch = flags.force if username is None: # branch is specified, so use current checked out fork/username _current_fork = self.fork_params.get('current_fork') if _current_fork is not None: # ...if available info('Assuming current fork for username: {}'.format( COLORS.SUCCESS + _current_fork + COLORS.ENDC)) username = _current_fork else: error( 'Current fork is unknown, please switch to a fork first before switching between branches!' ) return username = username.lower() remote_info = self.__get_remote_info(username) if remote_info is not None: # user entered an alias (ex. stock, dragonpilot) username = remote_info.username installed_forks = self.fork_params.get('installed_forks') fork_in_params = True if username not in installed_forks: fork_in_params = False if remote_info is not None: remote_url = f'https://github.com/{username}/{remote_info.fork_name}' # dragonpilot doesn't have a GH redirect else: # for most forks, GH will redirect from /openpilot if user renames fork if repo_name is None: repo_name = DEFAULT_REPO_NAME # openpilot remote_url = f'https://github.com/{username}/{repo_name}' if not valid_fork_url(remote_url): error('Invalid username{}! {} does not exist'.format( '' if flags.repo is None else ' or repository name', remote_url)) return r = check_output([ 'git', '-C', OPENPILOT_PATH, 'remote', 'add', username, remote_url ]) if r.success and r.output == '': success('Remote added successfully!') elif r.success and REMOTE_ALREADY_EXISTS in r.output: # remote already added, update params info('Fork exists but wasn\'t in params, updating now...') self.__add_fork(username) else: error(r.output) return # fork has been added as a remote, switch to it if fork_in_params: info('Fetching {}\'s latest changes...'.format(COLORS.SUCCESS + username + COLORS.WARNING)) else: info('Fetching {}\'s fork, this may take a sec...'.format( COLORS.SUCCESS + username + COLORS.WARNING)) r = run(['git', '-C', OPENPILOT_PATH, 'fetch', username]) if not r: error('Error while fetching remote, please try again') return self.__add_fork(username) self.__prune_remote_branches(username) r = check_output( ['git', '-C', OPENPILOT_PATH, 'remote', 'show', username]) remote_branches, default_remote_branch = self.__get_remote_branches(r) if remote_branches is None: return if DEFAULT_BRANCH_START not in r.output: error('Error: Cannot find default branch from fork!') return if branch is None: # user hasn't specified a branch, use remote's default branch if remote_info is not None: # there's an overriding default branch specified remote_branch = remote_info.default_branch local_branch = '{}_{}'.format(remote_info.username, remote_branch) else: remote_branch = default_remote_branch # for command to checkout correct branch from remote, branch is previously None since user didn't specify local_branch = '{}_{}'.format(username, default_remote_branch) else: if branch not in remote_branches: close_branches = most_similar( branch, remote_branches ) # remote_branches is gauranteed to have at least 1 branch if close_branches[0][1] > 0.5: branch = close_branches[0][0] info( 'Unknown branch, checking out most similar: {}'.format( COLORS.SUCCESS + branch + COLORS.WARNING)) else: error('The branch you specified does not exist!') self.__show_similar_branches( branch, remote_branches) # if possible return remote_branch = branch # branch is now gauranteed to be in remote_branches local_branch = f'{username}_{branch}' # checkout remote branch and prepend username so we can have multiple forks with same branch names locally if remote_branch not in installed_forks[username][ 'installed_branches']: info('New branch! Tracking and checking out {} from {}'.format( local_branch, f'{username}/{remote_branch}')) command = [ 'git', '-C', OPENPILOT_PATH, 'checkout', '--track', '-b', local_branch, f'{username}/{remote_branch}' ] else: # already installed branch, checking out fork_branch from f'{username}/{branch}' command = ['git', '-C', OPENPILOT_PATH, 'checkout', local_branch] if force_switch: command.append('-f') r = run(command) if not r: error( 'Error while checking out branch, please try again or use flag --force' ) return self.__add_branch( username, remote_branch ) # we can deduce fork branch from username and original branch f({username}_{branch}) # reset to remote/branch just to ensure we checked out fully. if remote branch has been force pushed, this will also reset local to remote r = check_output([ 'git', '-C', OPENPILOT_PATH, 'reset', '--hard', f'{username}/{remote_branch}' ]) if not r.success: error(r.output) return reinit_subs = self.__init_submodules() self.fork_params.put('current_fork', username) self.fork_params.put('current_branch', remote_branch) info('\n✅ Successfully checked out {}/{} as {}'.format( COLORS.SUCCESS + username, remote_branch + COLORS.WARNING, COLORS.SUCCESS + local_branch)) if reinit_subs: success('✅ Successfully reinitialized submodules!')
def _update(): if not run(['sh', UPDATE_PATH]): error('Error updating!')
#!/usr/bin/python # -*- coding: utf-8 -*- import os import importlib from py_utils.emu_utils import error EMU_COMMANDS = [] basedir = os.path.dirname(__file__) for module_name in os.listdir(basedir): if module_name.endswith( '.py') or module_name == '__pycache__' or not os.path.isdir( os.path.join(basedir, module_name)): continue try: module = importlib.import_module('commands.{}'.format(module_name)) module = getattr(module, module_name.title()) EMU_COMMANDS.append(module()) except Exception as e: error('Error loading {} command, please try updating!'.format( module_name)) error(e)