Upgrade Local

I currently have a function called updateLocal that runs the outdated function for homebrew, pip and yarn/npm. This runs pretty well and does its job. Recently, homebrew has updated it’s bottle policy to enable Mojave bottles that are built against cpu hardware instructions that aren’t supported by my computer. We need a new strategy.

Homebrew has support for the last three operating systems going back to Sierra. Bottles made on the previous two OS’s work just fine on this computer and thusly will work for the next two years. In order to make this work we need to do some work as there doesn’t appear to be an option to specify which bottle to use by default. I propose a new function called upgradeLocal.

The function will run like so: get the list of outdated binaries, loop through each one and uninstall, use brew info --json to get the download location of other bottles, use jq to parse the json, brew install -f with the url to the bottle. It looks like this:

brew uninstall --ignore-dependencies libuv; brew install -f "$( brew info --json libuv | jq '.[0].bottle.stable.files.high_sierra.url' | sed s/\"//g )"; brew cleanup

It seems we need to use sed at the end to remove quotation marks in order for the command to function. We also need to use --ignore-dependencies as there is a good chance that homebrew will complain otherwise. Let’s write it.

while read line; do
brew uninstall --ignore-dependencies "$line"; 
brew install -f "$( brew info --json $line | jq '.[0].bottle.stable.files.high_sierra.url' | sed s/\"//g )"
done < <(brew outdated)

This looks reasonable but I don’t know what will happen when new dependencies need to be installed. We could suppress any dependencies being installed then list the dependencies and install the correct bottle. But what if the dependency requires more dependencies that require correct bottles? We need to check if a dependency exists first then run a function to get the correct bottle. Gosh, wouldn’t this be easier if we could simply specify bottle=high_sierra? Anyway we are just dealing with lists so this should be a simple case of recursion. This snippet should check for previously installed binaries:

if brew ls --versions myformula > /dev/null; then
  # The package is installed
else
  # The package is not installed
fi

Lastly! We also need a means to install new bottles so adapt the upgrade script to allow the user to specify the binary to install and then run through the same procedure.


It seems like a pain to run all these checks and recursive loops to check all dependencies. It also means that our backup procedure will break as dependencies are installed as separate binaries rather than listed as dependencies. In this case, I think we should simply write a function that runs the uninstall/install process and take our chances with dependencies breaking due to cpu issues.

brinstall() {
    brew uninstall --ignore-dependencies "$1"
    brew install -f "$( brew info --json $1 | jq '.[0].bottle.stable.file.high_sierra.url' | sed s/\"//g )"
}

broutdated() {
    while read line; do
        brinstall "$line"
    done < <(brew outdated)
}

That seems reasonable. Funnily enough, in the two days I’ve been pondering how to do this I haven’t come across any outdated binaries to test. Still we could test the install function first with a new install. I doubt the nature of the first bottle will materially effect that of the subsequent dependency bottles…

02/05/2019 Update

In the main, this method worked ok. Until it didn’t work. One can never be sure what will break and where the breakage occurs. A recent update to node seemingly broke yarn but node was installed with the High Sierra bottle. Hmm, a dependency issue. Dependencies did indeed turn out to be problematic but they weren’t the only issue. Some packages aren’t a single binary and the uninstall command also breaks. The solution was actually very simple but still annoying. Digging around the homebrew folders yielded the solution. In the file brew.sh is a variable that checks for the operating system version in the form “10.14.3”. I merely hardcoded that variable to the last version of High Sierra. And that did it. The last remaining issue is the obvious one: what happens when homebrew updates itself? Homebrew updates ALL THE TIME.

When attempting to install something, Homebrew updates before installing and stashes the changes made to the repository. This is solved by adding the variable set -gx HOMEBREW_NO_AUTO_UPDATE 1 to your shell config file. The next step is to reapply the stashed changes after an update.

function modBrew --description 'Reapply an homebrew edit after an update'
    cd /usr/local/Homebrew && git stash pop && cd -
end

For my own sanity I wrote this quick (fish) function that I run whenever there’s a message that my changes have been stashed which is exclusively when I run updateLocal.

Now all bottles are of the High Sierra flavour and run like a charm. Huzzah.