def tools(): if yesno('install_with', 'Install `with` utility?', want_full): withutil = InstallFromSource('https://github.com/mchav/with', '~/src/with.git') withutil.symlink('with', '~/bin/with') withutil.select_branch('master') run(withutil) if yesno('install_universal_ctags', 'Install Universal Ctags?', want_full): need_installpkg(apt=('autoconf', 'g++')) mkdir('~/bin') if haveexecutable('brew'): # install with homebrew execute(['brew', 'tap', 'universal-ctags/universal-ctags']) execute(['brew', 'install', '--HEAD', 'universal-ctags']) else: uc = InstallFromSource('https://github.com/universal-ctags/ctags', '~/src/universal-ctags.git') uc.select_branch('master') uc.compile_cmd([ ['./autogen.sh'], ['./configure'], ['make'], ]) uc.symlink('ctags', '~/bin/ctags') run(uc) elif allow_installing_stuff and yesno('install_ctags', 'Install `ctags`?', want_full): installpkg('ctags') if allow_installing_stuff and yesno('install_patch', 'Install patch?', want_full): installpkg('patch') if allow_installing_stuff and yesno('install_tidy', 'Install tidy cli tool?', want_full): installpkg('tidy') # on OSX we want to install gnu utils (brew install coreutils findutils) # and put /usr/local/opt/coreutils/libexec/gnubin in PATH if IS_OSX and haveexecutable('brew') and allow_installing_stuff: if yesno('brew_install_coreutils', 'Install gnu utils?', default=want_full): brew_list = set( execute(['brew', 'list'], stdout=True)[1].decode('utf-8').splitlines()) install = [ pkg for pkg in ('coreutils', 'findutils') if pkg not in brew_list ] if len(install): execute(['brew', 'install'] + install)
def need_installpkg(*, apt=None, brew=None, yum=None): if not allow_installing_stuff: what = apt or brew or yum raise Exception( "Can't install {} when only doing minimal config".format(what)) if haveexecutable('apt-get'): for name in apt or []: installpkg(name, brew=False, yum=False, port=False) if haveexecutable('brew'): for name in brew or []: installpkg(name, apt=False, yum=False, port=False) if haveexecutable('yum'): for name in yum or []: installpkg(name, apt=False, brew=False, port=False)
def getpippaths(): if IS_OSX: return {} # do we need to out a pip config such that py2/py3 binaries don't clobber each other? question = 'Force pip to install into separate py2/py3 bin dirs?' if not yesno('force_pip_bin_paths', question, None): return {} scripts = {} for version in (2, 3): # TODO: we probably should drop into vim somewhere and make sure g:my_pyX_paths is # defined in prefs.vim or else our stuff is gonna be broken # TODO: we also want these in our $PATH ... or not? pip = "pip%d" % version var = "g:my_py%d_paths" % version if not haveexecutable(pip): continue stdout = execute([pip, '--version'], stdout=True)[1].decode('utf-8').rstrip() assert re.search(r' \(python \d+\.\d+\)$', stdout) version = stdout.rsplit(' ', 1)[1][:-1] path = '%s/.local/python-%s-bin' % (HOME, version) scripts[pip] = path lineinfile('~/.vimrc', "let %s = ['%s']" % (var, path), where=WHERE_END) return scripts
def brew_install(): if haveexecutable('brew'): return if yesno('install_homebrew', 'Install Homebrew?', default=True): install_cmd = '/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"' execute(['bash', '-c', install_cmd], stdout="TTY")
def jerjerrod_install(): # NOTE: only install jerjerrod where we've installed powerline install_commands = [ ['pip3', 'install', '--user', '-e', '.'], ] if IS_OSX and haveexecutable('/usr/bin/pip3'): # XXX: this version of pip3 is too old to understand how to install in # ---editable mode with just a pyproject.toml, so we need to just # install it as-is install_commands.append( ['/usr/bin/pip3', 'install', '--user', '.'], ) # install from source! inst = InstallFromSource('https://github.com/phodge/jerjerrod.git', '~/src/jerjerrod.git') inst.select_branch('master') # TODO: if you uninstall jerjerrod, this won't actually reinstalll it :-( inst.compile_cmd(install_commands) run(inst) jerjerrod_addline('PROJECT', '~/src/*.git') jerjerrod_addline('PROJECT', '~/src/*.hg') jerjerrod_addline('PROJECT', HERE)
def search_tools(): if yesno('install_ack', 'Install ack?', False): installpkg('ack', apt='ack-grep') if want_silver_searcher(): installpkg('ag', yum='the_silver_searcher', apt='silversearcher-ag') if yesno('install_ripgrep', 'Install ripgrep?', True): yum = False if haveexecutable('yum') and install_fedora_copr(): yum = 'ripgrep' installpkg('ripgrep', yum=yum)
def install_fedora_copr(): if not allow_installing_stuff: return False if not haveexecutable('yum'): return False copr_url = 'https://copr.fedorainfracloud.org/coprs/carlwgeorge/ripgrep/repo/epel-7/carlwgeorge-ripgrep-epel-7.repo' if not yesno('allow_fedora_copr', 'Add fedora COPR repo on this host?', None): return False # enable the repo installpkg('yum-utils') execute(['sudo', 'yum-config-manager', '--add-repo=' + copr_url], stdout="TTY") return True
def fzf_install(): if not yesno('install_fzf', 'Install fzf?', want_full): return if haveexecutable('brew') and allow_installing_stuff: installpkg('fzf') brewpath = execute(['brew', '--prefix'], stdout=True)[1].decode('utf-8').strip() if brewpath == '/opt/homebrew': # brew puts the fzf files into versioned folders, so all we can do # is glob and sort (which isn't perfect because it would need to be # a semver-compatible sort) and pick the first one fzf_path = execute( [ 'bash', '-c', f'echo {brewpath}/Cellar/fzf/* | sort -r | head -n 1' ], stdout=True, )[1].decode('utf-8').strip() else: # this is how it was on my old mac fzf_path = brewpath + '/opt/fzf' else: # do it the long way import os.path fzf_repo = os.path.expanduser('~/src/fzf.git') fzf_install = InstallFromSource('https://github.com/junegunn/fzf.git', fzf_repo) fzf_install.select_tag('0.17.3') fzf_install.compile_cmd([ ['./install', '--bin'], ]) fzf_install.symlink('bin/fzf', '~/bin/fzf') run(fzf_install) execute(['./install', '--bin'], cwd=fzf_repo, stdout='TTY') fzf_path = fzf_repo lineinfile('~/.bashrc', 'source {}/shell/completion.bash'.format(fzf_path)) lineinfile('~/.bashrc', 'source {}/shell/key-bindings.bash'.format(fzf_path)) if wantzsh(): lineinfile('~/.zshrc', 'source {}/shell/completion.zsh'.format(fzf_path)) lineinfile('~/.zshrc', 'source {}/shell/key-bindings.zsh'.format(fzf_path))
def mypips(venv_pip=None, write_dev_reqs=False): # if we're on macos then we need to tell homely.pipinstall to use 'pip3' instead of 'pip' if IS_OSX: pips = ['pip3'] else: pips = None # of course we probably want virtualenv! if venv_pip is None: pipinstall('virtualenv', pips=pips) theworks = want_full or venv_pip # these packages will be installed using the virtualenv's pip, or pip2+pip3 depending on what's # present. They're needed for development. packages = [ 'jedi', 'yapf', 'isort', # needed for `git rebase -i` commit comparisons 'GitPython', ] if wantnvim() and not install_nvim_via_apt(): # if we want nvim then we probably need the pynvim package packages.append('pynvim') # a nice python repl if theworks or yesno('install_ptpython', 'PIP Install ptpython?', True): packages.append('ptpython') # another nice python repl if theworks or yesno('install_ipython', 'PIP Install iPython?', True): packages.append('ipython') # a few of my macros use `q` for logging if theworks or yesno('install_python_q', 'PIP Install `q`?', True): packages.append('q') if write_dev_reqs: assert venv_pip is None mkdir('~/.config') with writefile('~/.config/dev_requirements.txt') as f: f.write('flake8\n') for p in packages: f.write(p + '\n') if want_python2_anything: trypips = ['pip2', 'pip3'] else: trypips = ['pip3'] for package in packages: if venv_pip: venv_exec(venv_pip, ['pip', 'install', package]) else: mypipinstall(package, trypips=trypips) # if it's a virtualenv, always just install flake8. Otherwise, we need to ask the user if # they want to install both if venv_pip: venv_exec(venv_pip, ['pip', 'install', 'flake8']) else: # always install simplejson globally as we need it for other parts of our homely install mypipinstall('simplejson', trypips=trypips) have_pip3 = haveexecutable('pip3') if have_pip3 and yesno('install_flake8_python3', 'Install flake8 for python3?'): mypipinstall('flake8', ['pip3']) if want_python2_anything and yesno('install_flake8_python2', 'Install flake8 for python2?'): mypipinstall('flake8', ['pip2'])
def install_python2_pip(): import subprocess if not haveexecutable('pip2'): if yesno('global_pip2', 'Install pip2 systemwide?', None): cmd = 'curl https://bootstrap.pypa.io/get-pip.py | sudo python2' subprocess.check_call(cmd, shell=True)
def install_winwin_shortcuts(): if not IS_OSX: # FIXME: get this working under Ubuntu as well return q = 'Install macOS system terminal shortcuts (requires Alacritty)?' if not yesno('want_winwin_shortcuts', q): return # we need to install winwin package or the launcher won't be able to find # the libs execute(['pip3', 'install', '--user', '-e', '.'], cwd=HERE + '/winwin.git') # XXX: for some reason on later versions of macOS I had to also install # winwin into this python/pip as well as this was the only one available to # the automation tool if IS_OSX and haveexecutable('/usr/bin/pip3'): execute(['/usr/bin/pip3', 'install', '--user', '-e', '.'], cwd=HERE + '/winwin.git') import shutil from tempfile import TemporaryDirectory def _replace_wildcards_recursive(target, name, command): # skip symlinks if os.path.islink(target): return # perform wildcard replacement in ordinary files if os.path.isfile(target): if target.endswith('.template'): dest = target[:-9] with open(target, 'rb') as fp_read, open(dest, 'xb') as fp_dest: contents = fp_read.read() contents = contents.replace(b'[[[WORKFLOW_NAME]]]', name.encode('utf-8')) contents = contents.replace(b'[[[WORKFLOW_COMMAND]]]', command.encode('utf-8')) fp_dest.write(contents) os.unlink(target) print("Wrote wildcards to {}".format(dest)) return for child in os.listdir(target): if child.startswith('.'): continue _replace_wildcards_recursive(target + '/' + child, name, command) def _install_macos_workflow_service(name, command): with TemporaryDirectory() as tmpdir: tmp_workflow = '{}/{}.workflow'.format(tmpdir, name) shutil.copytree( '{}/macos_automation/Template.workflow'.format(HERE), tmp_workflow) _replace_wildcards_recursive(tmp_workflow, name, command) # remove existing service dest_workflow = '{}/Library/Services/{}.workflow'.format( HOME, name) if os.path.exists(dest_workflow): shutil.rmtree(dest_workflow) shutil.copytree(tmp_workflow, dest_workflow) todo_launcher_data = { 'key_equivalent': '@^$t', 'presentation_modes': { 'ContextMenu': '1', 'ServicesMenu': '1', 'TouchBar': '1' }, } todo_launcher_key = '(null) - TODO Launcher QA - runWorkflowAsService' _install_macos_workflow_service( 'TODO Launcher QA', # we use '/bin/bash -i ...' here because otherwise the Quick Action # will launch in non-interactive mode, causing it to skip ~/.bashrc and # so on, and then tmux won't load correctly because $PATH isn't set up, # and other Bad Things "/bin/bash -i -c '{}/bin/macos-launch-todos'".format(HERE), ) vanilla_launcher_key = '(null) - Terminal Launcher QA - runWorkflowAsService' vanilla_launcher_data = { 'key_equivalent': '@^t', 'presentation_modes': { 'ContextMenu': '1', 'ServicesMenu': '1', 'TouchBar': '1' }, } _install_macos_workflow_service( 'Terminal Launcher QA', '/Applications/Alacritty.app/Contents/MacOS/alacritty', ) _install_macos_workflow_service( 'Terminal Selector QA', '{}/bin/macos-launch-terminal-selector'.format(HERE), ) all_pbs = execute(['defaults', 'read', 'pbs'], stdout=True)[1] if b'NSServicesStatus' not in all_pbs: print("NSServicesStatus not found") return import plistlib from subprocess import PIPE, Popen raw = execute(['defaults', 'read', 'pbs', 'NSServicesStatus'], stdout=True)[1] p = Popen(['plutil', '-convert', 'xml1', '-', '-o', '-'], stdin=PIPE, stdout=PIPE) xml, stderr = p.communicate(raw, timeout=5.0) assert stderr is None assert p.returncode == 0 # now we can read the xml using plistlib data = plistlib.loads(xml) needs_writing = False for key, value in ( (todo_launcher_key, todo_launcher_data), (vanilla_launcher_key, vanilla_launcher_data), ): if data[key] != value: needs_writing = True data[key] = value if needs_writing: new_xml = plistlib.dumps(data) if not allowinteractive(): return print("You need to set keyboard shortcuts.") print("Go to:") print(" -> System Preferences") print(" -> Keyboard") print(" -> Shortcuts") print(" -> Services") print( " -> under 'General' set keyboard shortcuts for 'XXX QA' services" ) yesno(None, "Done?") if yesno(None, "Attempt automated install of keyboard shortcuts?", default=False): # FIXME: this never worked - the keyboard shortcuts don't seem to # activate even if the System Preferences UI does show them there execute([ 'defaults', 'write', 'pbs', 'NSServicesStatus', new_xml.decode('utf-8') ])
@section_ubuntu(enabled=allow_installing_stuff) def git_install(): if not yesno('upgrade_git', 'Install latest git from ppa:git-core/ppa?', default=want_full): return for cmd in [ ['sudo', 'apt-add-repository', '-y', 'ppa:git-core/ppa'], ['sudo', 'apt-get', 'update'], ['sudo', 'apt-get', 'install', 'git'], ]: execute(cmd, stdout="TTY") @section_macos(enabled=haveexecutable('brew')) def font_install(): fonts = [ 'homebrew/cask-fonts/font-inconsolata', # this download doesn't seem to work any more # 'homebrew/cask-fonts/font-anonymous-pro', ] if wantpowerline(): fonts.extend([ 'homebrew/cask-fonts/font-inconsolata-for-powerline', # this one seems to require `brew install svn` which I'm maybe not prepared to do # 'homebrew/cask-fonts/font-anonymice-powerline', ]) # install some nicer fonts execute(['brew', 'install'] + fonts)