|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -eo pipefail |
| 3 | + |
| 4 | +echo "---> Homebrew Buildpack" |
| 5 | + |
| 6 | +LAYERS_DIR="$1" |
| 7 | + |
| 8 | +# Separate layer for the CLI vs the installed packages |
| 9 | +BREW_CLI_LAYER="${LAYERS_DIR}/brew-cli" |
| 10 | +BREW_PKG_LAYER="${LAYERS_DIR}/brew-packages" |
| 11 | + |
| 12 | +# Homebrew CLI setup |
| 13 | +export HOMEBREW_REPOSITORY="${BREW_CLI_LAYER}" |
| 14 | +CLI_METADATA_FILE="${BREW_CLI_LAYER}.toml" |
| 15 | + |
| 16 | +# Determine the version to install |
| 17 | +if [[ -n "${BP_HOMEBREW_VERSION}" ]]; then |
| 18 | + BREW_VERSION="${BP_HOMEBREW_VERSION}" |
| 19 | +else |
| 20 | + echo "---> Determining latest stable Homebrew release..." |
| 21 | + # Follow GitHub's latest release redirect and grab the tag from the end of the URL |
| 22 | + LATEST_URL=$(curl -Ls -o /dev/null -w "%{url_effective}" https://github.com/Homebrew/brew/releases/latest) |
| 23 | + BREW_VERSION=$(echo "${LATEST_URL}" | grep -o '[^/]*$') |
| 24 | + |
| 25 | + # Safety fallback just in case GitHub rate-limits the request |
| 26 | + if [[ -z "${BREW_VERSION}" || "${BREW_VERSION}" == "latest" ]]; then |
| 27 | + echo "---> WARNING: Could not determine latest release. Falling back to master." |
| 28 | + BREW_VERSION="master" |
| 29 | + fi |
| 30 | +fi |
| 31 | + |
| 32 | +echo "---> Target Homebrew version: ${BREW_VERSION}" |
| 33 | + |
| 34 | +# Check if we need to clear the CLI cache (if the version changed) |
| 35 | +if [[ -f "${CLI_METADATA_FILE}" ]]; then |
| 36 | + CACHED_VERSION=$(grep "brew_version" "${CLI_METADATA_FILE}" | cut -d '"' -f 2 || true) |
| 37 | + if [[ "${CACHED_VERSION}" != "${BREW_VERSION}" ]]; then |
| 38 | + echo "---> Homebrew version changed from ${CACHED_VERSION:-unknown} to ${BREW_VERSION}. Invalidating CLI cache..." |
| 39 | + rm -rf "${BREW_CLI_LAYER}" |
| 40 | + fi |
| 41 | +fi |
| 42 | + |
| 43 | +# Download or use cached CLI |
| 44 | +if [[ ! -x "${HOMEBREW_REPOSITORY}/bin/brew" ]]; then |
| 45 | + echo "---> Downloading Homebrew CLI (version: ${BREW_VERSION}) to ${BREW_CLI_LAYER}..." |
| 46 | + mkdir -p "${HOMEBREW_REPOSITORY}" |
| 47 | + |
| 48 | + # The Homebrew source tarball is architecture-agnostic (Ruby/Bash scripts) |
| 49 | + curl -fsSL "https://github.com/Homebrew/brew/tarball/${BREW_VERSION}" | tar xz --strip 1 -C "${HOMEBREW_REPOSITORY}" |
| 50 | +else |
| 51 | + echo "---> Using cached Homebrew CLI (version: ${BREW_VERSION})" |
| 52 | +fi |
| 53 | + |
| 54 | +# Keep the CLI layer for builds, leave it out of the final image, and track the version |
| 55 | +cat >"${CLI_METADATA_FILE}" <<EOF |
| 56 | +[types] |
| 57 | +cache = true |
| 58 | +build = true |
| 59 | +launch = false |
| 60 | +
|
| 61 | +[metadata] |
| 62 | +brew_version = "${BREW_VERSION}" |
| 63 | +EOF |
| 64 | + |
| 65 | +# Packages installation env vars |
| 66 | +export HOMEBREW_PREFIX="${BREW_PKG_LAYER}" |
| 67 | +export HOMEBREW_CELLAR="${BREW_PKG_LAYER}/Cellar" |
| 68 | + |
| 69 | +# Calculate a checksum of the current Brewfile |
| 70 | +BREWFILE_SHA=$(sha256sum "Brewfile" | cut -d ' ' -f 1) |
| 71 | +PKG_METADATA_FILE="${BREW_PKG_LAYER}.toml" |
| 72 | + |
| 73 | +# Check if we need to clear the package cache (if the Brewfile changed) |
| 74 | +if [[ -f "${PKG_METADATA_FILE}" ]]; then |
| 75 | + CACHED_SHA=$(grep "brewfile_sha" "${PKG_METADATA_FILE}" | cut -d '"' -f 2 || true) |
| 76 | + if [[ "${CACHED_SHA}" != "${BREWFILE_SHA}" ]]; then |
| 77 | + echo "---> Brewfile changed. Invalidating package cache..." |
| 78 | + rm -rf "${BREW_PKG_LAYER}" |
| 79 | + fi |
| 80 | +fi |
| 81 | + |
| 82 | +# Ensure package layer directories exist |
| 83 | +mkdir -p "${HOMEBREW_PREFIX}/bin" "${HOMEBREW_CELLAR}" |
| 84 | + |
| 85 | +# Symlink the brew executable into our prefix so the bundle command works |
| 86 | +ln -sfn "${HOMEBREW_REPOSITORY}/bin/brew" "${HOMEBREW_PREFIX}/bin/brew" |
| 87 | + |
| 88 | +# Keep the package layer for both build and launch, and save the SHA |
| 89 | +cat >"${PKG_METADATA_FILE}" <<EOF |
| 90 | +[types] |
| 91 | +cache = true |
| 92 | +build = true |
| 93 | +launch = true |
| 94 | +
|
| 95 | +[metadata] |
| 96 | +brewfile_sha = "${BREWFILE_SHA}" |
| 97 | +EOF |
| 98 | + |
| 99 | +# Install packages |
| 100 | +export PATH="${HOMEBREW_PREFIX}/bin:${HOMEBREW_REPOSITORY}/bin:${PATH}" |
| 101 | +export HOMEBREW_NO_AUTO_UPDATE=1 |
| 102 | +export HOMEBREW_NO_INSTALL_CLEANUP=1 |
| 103 | + |
| 104 | +# Only run bundle if there's work to do (meaning the layer was just created or cleared) |
| 105 | +if [[ ! -d "${HOMEBREW_CELLAR}" ]] || [[ -z "$(ls -A "${HOMEBREW_CELLAR}" 2>/dev/null)" ]]; then |
| 106 | + echo "---> Running brew bundle into ${BREW_PKG_LAYER}..." |
| 107 | + brew bundle --file=Brewfile |
| 108 | +else |
| 109 | + echo "---> Using cached Homebrew packages" |
| 110 | +fi |
| 111 | + |
| 112 | +echo "---> Homebrew buildpack finished." |
0 commit comments