Skip to content

Commit

Permalink
Add full automated setup support for M1 macs (#66)
Browse files Browse the repository at this point in the history
## Summary:
- Updates the `install-mac-homebrew` script to set up homebrew correctly for both x86_64 and arm64
- Ensures khan py2 is installed on x84_64 arch
- Runs `npm install` for `our-lovely-cli` (not specific to M1 macs, this just isn't covered in onboarding docs or as part of setup scripts)
- Check to verify `/opt/homebrew/bin` is before `/usr/local/bin` in `PATH` to ensure arm64 homebrew is used by default

I've run this on my machine and everything works, no longer requiring manual work before running the setup scripts.

One issue that might come up is related to dotfiles. This will fail on m1 at the step that verifies the correct order of homebrew binaries. A simple solution would be to move the dotfiles setup up to be the first step that's run.

Issue: XXX-XXXX

## Test plan:
I've tested each change on my machine, but not from a fresh install

Author: jwiesebron

Reviewers: yogieric, jwiesebron

Required Reviewers:

Approved By: yogieric

Checks:

Pull Request URL: #66
  • Loading branch information
jwbron authored Feb 3, 2023
1 parent ecb91e4 commit 70115bd
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 56 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/**/packer_cache/
/**/builds/
.vscode
.idea*
*.iml
177 changes: 134 additions & 43 deletions bin/install-mac-homebrew.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,139 @@
# This script will prompt for user's password if sudo access is needed
# TODO(ericbrown): Can we check, install & upgrade apps we know we need/want?

import os
import platform
import subprocess

HOMEBREW_INSTALLER = \
'https://raw.githubusercontent.com/Homebrew/install/master/install.sh'

print('Checking for mac homebrew')

install_brew = False
which = subprocess.run(['which', 'brew'], capture_output=True)
if which.returncode != 0:
print('Brew not found, Installing!')
install_brew = True
else:
result = subprocess.run(['brew', '--help'], capture_output=True)
if result.returncode != 0:
print('Brew broken, Re-installing')
install_brew = True

if install_brew:
# Download installer
installer = subprocess.run(['curl', '-fsSL', HOMEBREW_INSTALLER],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True)

# Validate that we have sudo access (as installer script checks)
print("This setup script needs your password to install things as root.")
subprocess.run(['sudo', 'sh', '-c', 'echo You have sudo'], check=True)

# Run downloaded installer
result = subprocess.run(['bash'], input=installer.stdout, check=True)

print('Updating (but not upgrading) Homebrew')
subprocess.run(['brew', 'update'], capture_output=True, check=True)

# Install homebrew-cask, so we can use it manage installing binary/GUI apps
# brew tap caskroom/cask

# Likely need an alternate versions of Casks in order to install chrome-canary
# Required to install chrome-canary
# (Moved to mac-install-apps.sh, but might be needed elsewhere unbeknownst!)
# subprocess.run(['brew', 'tap', 'brew/cask-versions'], check=True)

# This is where we store our own formula, including a python@2 backport
subprocess.run(['brew', 'tap', 'khan/repo'], check=True)

class HomebrewInstaller:
HOMEBREW_INSTALLER = (
"https://raw.githubusercontent.com/Homebrew/install/master/install.sh"
)
HOMEBREW_UNINSTALLER = (
"https://raw.githubusercontent.com/Homebrew/install/master/install.sh"
)
ARM64_BREW_DIR = "/opt/homebrew/bin"
X86_BREW_DIR = "/usr/local/bin"

def __init__(self):
self.__install_script = None
self.__uninstall_script = None

@property
def _install_script(self):
if not self.__install_script:
self.__install_script = self._pull_script(self.HOMEBREW_INSTALLER)
return self.__install_script

@property
def _uninstall_script(self):
if not self.__install_script:
self.__install_script = self._pull_script(self.HOMEBREW_INSTALLER)
return self.__install_script

@staticmethod
def _pull_script(script_url):
return subprocess.run(
["curl", "-fsSL", script_url],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
).stdout

@staticmethod
def _install_or_uninstall_homebrew(brew_script, force_x86=False):
# Validate that we have sudo access (as installer script checks)
print(
"This setup script needs your password to install things as root."
)
subprocess.run(["sudo", "sh", "-c", "echo You have sudo"], check=True)

# Run installer
installer_runner = (
["arch", "-x86_64", "/bin/bash"] if force_x86 else ["/bin/bash"]
)
subprocess.run(installer_runner, input=brew_script, check=True)

def install_homebrew(self, force_x86=False):
self._install_or_uninstall_homebrew(
brew_script=self._install_script, force_x86=force_x86
)

def uninstall_homebrew(self, force_x86=False):
if force_x86:
os.environ["PATH"] = self.X86_BREW_DIR + os.environ["PATH"]
self._install_or_uninstall_homebrew(brew_script=self._uninstall_script)
if force_x86:
os.environ["PATH"] = self.ARM64_BREW_DIR + os.environ["PATH"]

def _validate_and_install_homebrew(self, force_x86=False):
brew_runner = (
["arch", "-x86_64", "/usr/local/bin/brew"]
if force_x86
else ["brew"]
)
if force_x86:
brew_bin_exists = os.path.exists("/usr/local/bin/brew")
else:
brew_bin_exists = (
subprocess.run(
["which", "brew"], capture_output=True
).returncode
== 0
)
if not brew_bin_exists:
print("Brew not found, Installing!")
self.install_homebrew(force_x86=force_x86)
else:
result = subprocess.run(
brew_runner + ["--help"], capture_output=True
)
if result.returncode != 0:
print("Brew broken, Re-installing")
self.uninstall_homebrew(force_x86=force_x86)
self.install_homebrew(force_x86=force_x86)
update_msg = "Updating (but not upgrading) Homebrew"
if force_x86:
update_msg += " x86"
print(update_msg)
subprocess.run(
brew_runner + ["update"], capture_output=True, check=True
)

# Install homebrew-cask, so we can use it manage installing binary/GUI
# apps brew tap caskroom/cask

# Likely need an alternate versions of Casks in order to install
# chrome-canary
# Required to install chrome-canary
# (Moved to mac-install-apps.sh, but might be needed elsewhere
# unbeknownst!)
# subprocess.run(['brew', 'tap', 'brew/cask-versions'], check=True)

# This is where we store our own formula, including a python@2 backport
subprocess.run(brew_runner + ["tap", "khan/repo"], check=True)

def validate_and_install_homebrew(self):
self._validate_and_install_homebrew()

if platform.uname().machine == "arm64":
# Ensure arm64 brew bin is used by default over x86
path_msg = (
self.ARM64_BREW_DIR
+ "must come before "
+ self.X86_BREW_DIR
+ " in PATH"
)
env_path = os.environ["PATH"]
assert self.ARM64_BREW_DIR in env_path, path_msg
opt_homebrew_idx = env_path.index(self.ARM64_BREW_DIR)
usr_local_bin_idx = env_path.index(self.X86_BREW_DIR)
assert opt_homebrew_idx < usr_local_bin_idx, path_msg
# Install x86 brew for M1 architecture to be run with rosetta
self._validate_and_install_homebrew(force_x86=True)


if __name__ == "__main__":
print("Checking for mac homebrew")
HomebrewInstaller().validate_and_install_homebrew()
30 changes: 19 additions & 11 deletions bin/install-mac-python2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,46 @@
"""Install Khan's python2."""

import argparse
import platform
import re
import subprocess

parser = argparse.ArgumentParser()
parser.add_argument("--force", help="Force install of Khan's python2",
action="store_true")
parser.add_argument(
"--force", help="Force install of Khan's python2", action="store_true"
)
args = parser.parse_args()

which = subprocess.run(['which', 'python2'], capture_output=True, text=True)
is_installed = (which.returncode == 0
and which.stdout.strip() != "/usr/bin/python2")
which = subprocess.run(["which", "python2"], capture_output=True, text=True)
is_installed = (
which.returncode == 0 and which.stdout.strip() != "/usr/bin/python2"
)
if is_installed:
print("Already running a non-system python2.")

if args.force or not is_installed:
action = "reinstall" if is_installed else "install"
print("Installing python2 from khan/repo. This may take a few minutes.")
subprocess.run(['brew', action, 'khan/repo/python@2'], check=True)
if platform.uname().machine == "arm64":
brew_runner = ["arch", "-x86_64", "/usr/local/bin/brew"]
else:
brew_runner = ["brew"]
subprocess.run(brew_runner + [action, "khan/repo/python@2"], check=True)

# Get version of pip2
pip2_version = ""
pip2_version_str = subprocess.run(['pip2', '--version'],
capture_output=True, text=True)
pip2_version_str = subprocess.run(
["pip2", "--version"], capture_output=True, text=True
)
if pip2_version_str:
match = re.match(r'\w+ (\d+)', pip2_version_str.stdout)
match = re.match(r"\w+ (\d+)", pip2_version_str.stdout)
if match:
pip2_version = match.group(1)

if pip2_version and pip2_version > "19":
print("Reverting pip2 from version: " + pip2_version_str.stdout.strip())
subprocess.run(['pip2', 'install', 'pip<20', '-U'], check=True)
subprocess.run(["pip2", "install", "pip<20", "-U"], check=True)

# Simple diagnostics
subprocess.run(['pip2', '--version'])
subprocess.run(["pip2", "--version"])
print("which python2: " + which.stdout.strip())
2 changes: 1 addition & 1 deletion mac-setup-normal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ install_python2() {
fi

info "Installing python2 from khan/repo. This may take a few minutes."
brew install khan/repo/python@2
brew86 install khan/repo/python@2
}

install_node() {
Expand Down
8 changes: 7 additions & 1 deletion setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,15 +310,21 @@ install_hooks() {
fi
}

install_our_lovely_cli() {
cd "$DEVTOOLS_DIR/our-lovely-cli"
npm install
}

install_dotfiles

check_dependencies

update_userinfo

# the order for these is (mostly!) important, beware
clone_repos
setup_python
clone_repos
install_our_lovely_cli # pre-req: clone_repos
install_and_setup_gcloud # pre-req: setup_python
install_deps # pre-reqs: clone_repos, install_and_setup_gcloud
install_hooks # pre-req: clone_repos
Expand Down

0 comments on commit 70115bd

Please sign in to comment.