Last active
February 27, 2024 02:11
-
-
Save seandstewart/5089e97c81c9e4367a0c7bd07ba8962b to your computer and use it in GitHub Desktop.
Setup your MacOS for modern Python development (Python3.8-9, Poetry, Pyenv, Pipx, plus many the necessary system packages and compiler links)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zsh | |
############################### | |
# -*- Runtime -*- # | |
############################### | |
main() { | |
prologue | |
local code | |
local restart | |
local invalid | |
code=0 | |
restart=1 | |
invalid=1 | |
while getopts sptcrah-: OPT; do | |
# based on https://stackoverflow.com/a/28466267 | |
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG | |
OPT="${OPTARG%%=*}" # extract long option name | |
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty) | |
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` | |
fi | |
if [ "$OPT" != "" ]; then | |
invalid=0 | |
fi | |
case $OPT in | |
s|system) setupBaseSystem ;; | |
p|python) setupBasePythonVersions ;; | |
t|tools) setupDevelopmentTools ;; | |
c|commandPrompt) setupBaseShell ;; | |
r|rcfile) setupBaseRCFile ;; | |
a|all) setupBaseSystem; setupBasePythonVersions; setupBaseShell; setupDevelopmentTools; setupBaseRCFile; break;; | |
h|help) showHelp; restart=0 ;; | |
??* ) err "Illegal option --$OPT"; code=1; showHelp ;; # bad long option | |
*) err "Illegal option..."; code=1; showHelp ;; | |
esac | |
done | |
if [ $invalid = 1 ]; then | |
err "Nothing to do, got no options." | |
showHelp | |
exit 1 | |
fi | |
if [ $restart = 1 ]; then | |
ok "All set! Happy hacking.( ^_^)o自自o(^_^ )" | |
infoMinor "Restarting shell to activate changes." | |
exec $SHELL | |
else | |
exit $code | |
fi | |
} | |
showHelp() { | |
echo "" | |
info "Description: Setup your machine for developing with Python." | |
info "Usage: bootstrap-macos [OPTIONS ...]" | |
bold "Options:" | |
italic " -h|--help: Show this message." | |
italic " -s|--system: Setup your base system requirements (C libraries, System packages)." | |
italic " -p|--python: Setup your base Python interpreter(s)." | |
italic " -t|--tools: Setup additional tools for Python development." | |
italic " -c|--commandPrompt: Setup your shell and command prompt with additional goodies." | |
italic " -r|--rcfile: Setup your ~/.zshrc file with basic components and configurations." | |
italic " -a|--all: Setup all of the above ^^.\n" | |
} | |
prologue() { | |
title "Hello $(whoami)!" | |
ok "Welcome to the MacOS Bootstrapper 🥾" | |
info "This script will run the necessary steps to get you started developing with Python." | |
infoMinor "If you've already run this script before, you can use it again to perform general updates." | |
} | |
############################### | |
# -*- Setup Routines -*- # | |
############################### | |
setupBaseShell() { | |
chownUserDirs | |
installOMZ | |
installStarship | |
} | |
setupBaseSystem() { | |
info "Setting up base system requirements." | |
chownUserDirs | |
xcode-select --install | |
installBrew | |
installSystemLibs | |
catch "Done setting up base system requirements." "Base system setup failed!" | |
} | |
setupBasePythonVersions() { | |
info "Setting up your base Python interpreter(s)..." | |
installLatestPyenv | |
local py39 | |
local py38 | |
py39="$(getLatestPython '3.9')" | |
py38="$(getLatestPython '3.8')" | |
info "Installing Python versions: $py39, $py38" | |
note "\t(This may take a while...)" | |
env_parallel installPython ::: $py39 $py38 | |
pyenv global $py39 | |
initPyenv | |
catch "Done setting up base python interpreter(s)." "Setting up interpreter(s) failed!" | |
} | |
setupDevelopmentTools() { | |
info "Setting up development tools." | |
installPipx | |
installPoetry | |
installCLITools | |
catch "Done setting up development tools." "Setting up development tools failed!" | |
} | |
setupBaseRCFile() { | |
infoMinor "Prepending a base RC file @ '$HOME/.zshrc'." | |
# Backup the current rc file for good measure. | |
cp "$HOME/.zshrc" "$HOME/.zshrc.backup" | |
# Export the user-configuration to its own backup. | |
awk 'x==1 {print} /END generated RC configuration/ {x=1}' < "$HOME/.zshrc.backup" > "$HOME/.zshrc.user" | |
# Prepend the boilerplate | |
cat > $HOME/.zshrc << EOF | |
$RC_FILE | |
EOF | |
# Add back any user config. | |
cat "$HOME/.zshrc.user" >> "$HOME/.zshrc" | |
okMinor "Done updating '$HOME/.zshrc'." | |
note "📝 Original RC file is saved to '$HOME/.zshrc.backup'." | |
note "📝 Detected user RC file is saved to '$HOME/.zshrc.user'." | |
} | |
############################### | |
# -*- Install Routines -*- # | |
############################### | |
installBrew() { | |
if ! command -v brew &> /dev/null | |
then | |
infoMinor "Installing brew..." | |
curl -fsSL "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" | bash | |
else | |
infoMinor "Updating brew..." | |
brew update | |
fi | |
catchMinor "Done installing homebrew." "Encountered an error installing brew." | |
} | |
installSystemLibs() { | |
infoMinor "Installing necessary system packages..." | |
brew install -q parallel pkg-config openssl libffi libxml2 libxmlsec1 libmagic kubectl | |
parallel --citation | |
source "$(which env_parallel.zsh)" | |
catchMinor "Done installing system packages." "Encountered an error installing system packages." | |
} | |
installLatestPyenv() { | |
if ! command -v pyenv &> /dev/null | |
then | |
infoMinor "Installing pyenv..." | |
curl "https://pyenv.run" | bash | |
export PATH="$HOME/.pyenv/bin:$PATH" | |
initPyenv | |
else | |
infoMinor "Updating pyenv..." | |
pyenv update | |
fi | |
catchMinor "Done installing latest pyenv." "Encountered an error the latest installing pyenv." | |
} | |
getLatestPython() { | |
pyenv install -l | grep -o " $1.\d*$" | grep -oE "[^ ]+$" | tail -1 | |
} | |
installPython() { | |
eval $PY_FLAGS | |
initPyenv | |
notify "Installing Python @ $1..." | |
pyenv install -s $1 | |
catchNotify "Done installing Python @ $1." "Couldn't install Python @ $1!" | |
} | |
installPipx() { | |
if ! command -v pipx &> /dev/null | |
then | |
infoMinor "Installing pipx..." | |
eval $PY_FLAGS | |
brew install pipx | |
catchMinor "Done installing pipx." "Encountered an error installing pipx" | |
fi | |
} | |
installPoetry() { | |
if ! command -v poetry &> /dev/null | |
then | |
infoMinor "Installing Poetry..." | |
eval $PY_FLAGS | |
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python3 - | |
poetry config virtualenvs.in-project true | |
catchMinor "Done installing poetry." "Encountered an error installing poetry." | |
else | |
infoMinor "Updating poetry..." | |
poetry self update | |
catchMinor "Done updating poetry." "Encountered an error updating poetry." | |
fi | |
mkdir -p "${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/poetry" | |
poetry completions zsh > "${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/poetry/_poetry" | |
} | |
installCLITools() { | |
local cmd | |
for tool in "${CLI_TOOLS[@]}"; do | |
cmd=$tool | |
if ! command -v $cmd &> /dev/null ; then | |
infoMinor "Installing $tool..." | |
pipx install $tool | |
catchMinor "Done installing $tool." "Encountered an error upgrading $tool." | |
else | |
infoMinor "Upgrading $tool..." | |
pipx upgrade $tool | |
catchMinor "Done upgrading $tool." "Encountered an error upgrading $tool." | |
fi | |
done | |
} | |
CLI_TOOLS=( | |
pre-commit | |
black | |
) | |
installStarship() { | |
infoMinor "Installing Starship (https://starship.rs/)..." | |
brew install starship | |
okMinor "Done installing Starship." | |
} | |
installOMZ() { | |
if ! command omz &> /dev/null ; then | |
infoMinor "Updating Oh My Zsh..." | |
omz update | |
catchMinor "Done updating OMZ." "Encountered an error updating Oh My Zsh." | |
else | |
infoMinor "Installing Oh My Zsh (https://ohmyz.sh/)..." | |
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" | |
catchMinor "Done installing OMZ." "Encountered an error installing Oh My Zsh." | |
fi | |
infoMinor "Installing some useful plugins..." | |
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions | |
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting | |
} | |
initPyenv() { | |
eval "$(pyenv init -)" | |
eval "$(pyenv init --path)" | |
} | |
chownUserDirs() { | |
if [ "$CHOWNED" = "0" ]; then | |
local owner | |
owner="$(whoami)" | |
infoMinor "Making '$owner' owner of directories for managing system dependencies." | |
note "📝 You may be asked for an administrator password to complete this operation." | |
sudo mkdir -p ${USER_DIRS[*]} &> /dev/null | |
sudo chown -R $owner ${USER_DIRS[*]} &> /dev/null | |
chmod u+w ${USER_DIRS[*]} &> /dev/null | |
catchMinor "Done chown-ing system directories." "Encountered an error changing ownership." | |
CHOWNED=1 | |
fi | |
} | |
CHOWNED=${CHOWNED:-0} | |
USER_DIRS=( | |
/usr/local/Caskroom | |
/usr/local/Cellar | |
/usr/local/Frameworks | |
/usr/local/Homebrew | |
/usr/local/bin | |
/usr/local/etc | |
/usr/local/include | |
/usr/local/lib | |
/usr/local/opt | |
/usr/local/sbin | |
/usr/local/share | |
/usr/local/var | |
) | |
############################### | |
# -*- Messaging -*- # | |
############################### | |
export BOLD="\033[1m" | |
export RESET_BOLD="\033[21m" | |
export ITALIC="\033[3m" | |
export RESET_ITALIC="\033[23m" | |
export DIM="\033[2m" | |
export BLINK="\033[5m" | |
export RESET_BLINK="\033[25m" | |
export RED="\033[1;31m" | |
export GREEN="\033[32m" | |
export YELLOW="\033[1;33m" | |
export MAGENTA="\033[1;35m" | |
export CYAN="\033[36m" | |
export RESET="\033[0m" | |
title() { | |
echo "$MAGENTA$1$RESET" | |
} | |
blink() { | |
echo "$BLINK$1$RESET" | |
} | |
bold() { | |
echo "$BOLD$1$RESET" | |
} | |
italic() { | |
echo "$ITALIC$1$RESET" | |
} | |
note() { | |
echo "$DIM$1$RESET" | |
} | |
info() { | |
echo "📝 $BOLD$CYAN$1$RESET" | |
} | |
infoMinor() { | |
echo "🔮 $CYAN$1$RESET" | |
} | |
ok() { | |
echo "✅ $BOLD$GREEN$1$RESET" | |
} | |
okMinor() { | |
echo "✨ $GREEN$1$RESET" | |
} | |
err() { | |
echo "🚨 $RED$1$RESET 🚨" | |
} | |
catchMinor() { | |
if [ "$?" = "0" ]; then | |
okMinor "${1:-'Success!'}" | |
else | |
err "${2:-'Got an error running the previous command!'}" | |
fi | |
} | |
catchNotify() { | |
if [ "$?" = "0" ]; then | |
notify "✨ ${1:-'Success!'}" | |
else | |
notify "🚨 ${2:-'Got an error running the previous command!'} 🚨" | |
fi | |
} | |
catch() { | |
if [ "$?" = "0" ]; then | |
ok "${1:-'Success!'}" | |
else | |
err "${2:-'Got an error running the previous command!'}" | |
fi | |
} | |
notify() { | |
osascript -e "display notification \"$1\" with title \"${2:-Bootstrapper 🥾}\"" | |
} | |
############################### | |
# -*- General Setup Cruft -*- # | |
############################### | |
# Flags we need to install Python... | |
read -r -d '' PY_FLAGS << 'EOM' | |
PATH="$HOME/.pyenv/bin:$HOME/.local/bin:$PATH"; | |
CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS="1"; | |
LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"; | |
CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"; | |
PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig; | |
EOM | |
# A useful starter rcfile... | |
read -r -d '' RC_FILE << 'EOM' | |
# BEGIN generated RC configuration. | |
# Added by DevTools MacOS Bootstrapper | |
# If you come from bash you might have to change your $PATH. | |
# export PATH=$HOME/bin:/usr/local/bin:$PATH | |
export PYENV_ROOT=$HOME/.pyenv | |
export PATH=$PYENV_ROOT/bin:$HOME/.local/bin:$PATH | |
export CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS="1" | |
export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" | |
export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include" | |
export PKG_CONFIG_PATH=/usr/local/opt/libffi/lib/pkgconfig | |
# Add profiling tool | |
zmodload zsh/zprof | |
plugins=( | |
git | |
dotenv | |
kubectl # if your shell is slow to startup, this may be the problem! | |
minikube | |
poetry | |
macos | |
zsh-syntax-highlighting | |
zsh-autosuggestions | |
) | |
export ZSH="$HOME/.oh-my-zsh" | |
source $ZSH/oh-my-zsh.sh | |
# Command completions | |
autoload -U bashcompinit | |
bashcompinit | |
eval "$(register-python-argcomplete pipx)" | |
eval "$(pyenv init --path)" | |
eval "$(pyenv init -)" | |
eval "$(starship init zsh)" | |
# END generated RC configuration. ↓ Add Your own configuration values here ↓ | |
EOM | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment