diff --git a/Brewfile b/Brewfile index 02ca0c720..45f090352 100644 --- a/Brewfile +++ b/Brewfile @@ -28,11 +28,10 @@ brew "gstreamer" # Strawberry requires KDAB's KDSingleApplication (CMake package name: KDSingleApplication-qt6). # Homebrew core doesn't consistently provide it, so this repo includes a local formula. -# Homebrew requires formulae to be installed from a tap; `brew bundle` will tap *this repo* -# using the current working directory (run `brew bundle` from the repo root). -# If you previously tapped `strawberry/local` before `Formula/` existed, refresh it with: -# brew untap strawberry/local && brew tap strawberry/local "file://$PWD" -tap "strawberry/local", "file://#{Dir.pwd}" +# Homebrew requires formulae to be installed from a tap; we tap *this repo* via file://. +# Use the Brewfile's directory (repo root) rather than the current working directory, +# so `brew bundle --file /path/to/Brewfile` works no matter where you run it from. +tap "strawberry/local", "file://#{File.expand_path(__dir__)}" brew "strawberry/local/kdsingleapplication-qt6" brew "strawberry/local/qtsparkle-qt6" # optional: QtSparkle integration brew "strawberry/local/sparkle-framework" # optional: Sparkle integration (framework) diff --git a/Formula/macdeploycheck.rb b/Formula/macdeploycheck.rb index 6f5438293..b12ef959f 100644 --- a/Formula/macdeploycheck.rb +++ b/Formula/macdeploycheck.rb @@ -1,7 +1,6 @@ class Macdeploycheck < Formula desc "Sanity checks a macOS .app bundle for accidental Homebrew runtime dependencies" homepage "https://github.com/strawberrymusicplayer/strawberry" - url "file://#{__FILE__}" version "0.1.0" sha256 :no_check license "MIT" @@ -9,80 +8,10 @@ class Macdeploycheck < Formula depends_on :macos def install - (bin/"macdeploycheck").write <<~'EOS' - #!/usr/bin/env bash - set -euo pipefail - - app="${1:-}" - if [[ -z "$app" ]]; then - echo "Usage: macdeploycheck " >&2 - exit 2 - fi - if [[ ! -d "$app" ]]; then - echo "Error: app bundle not found: $app" >&2 - exit 2 - fi - if [[ ! -d "$app/Contents" ]]; then - echo "Error: not a macOS app bundle (missing Contents/): $app" >&2 - exit 2 - fi - - fail=0 - tmp="$(mktemp -t macdeploycheck.XXXXXX)" - trap 'rm -f "$tmp"' EXIT - - # Collect Mach-O files (executables + dylibs) inside the bundle. - while IFS= read -r -d '' f; do - if file "$f" | grep -q "Mach-O"; then - echo "$f" >>"$tmp" - fi - done < <(find "$app/Contents" -type f \( -perm -111 -o -name "*.dylib" -o -name "*.so" \) -print0 2>/dev/null) - - if [[ ! -s "$tmp" ]]; then - echo "Warning: no Mach-O files found under $app/Contents" >&2 - exit 0 - fi - - echo "macdeploycheck: scanning for external (Homebrew) runtime deps..." - while IFS= read -r f; do - # otool -L prints: - # : - # (compatibility version ..., current version ...) - deps="$(otool -L "$f" 2>/dev/null | tail -n +2 | awk '{print $1}' || true)" - while IFS= read -r dep; do - [[ -z "$dep" ]] && continue - - # Ignore system and rpath/loader/executable paths. - case "$dep" in - /System/*|/usr/lib/*|@rpath/*|@loader_path/*|@executable_path/*) continue ;; - esac - - # These are the common accidental runtime deps that will break distribution. - if [[ "$dep" == /opt/homebrew/* || "$dep" == /usr/local/* || "$dep" == /opt/local/* ]]; then - echo "ERROR: $f links to external path: $dep" >&2 - fail=1 - fi - done <<<"$deps" - done <"$tmp" - - if [[ "$fail" -ne 0 ]]; then - cat >&2 <<'EOM' - -One or more binaries in your .app link to a Homebrew (or MacPorts) path. -That usually means the bundle is not self-contained and will fail on other machines, -or will fail notarization/codesigning validation. - -Fix: re-run your deploy step (e.g. macdeployqt) so frameworks/dylibs are bundled and -their install names are rewritten to @rpath/@loader_path. -EOM - exit 1 - fi - - echo "OK: no external Homebrew/MacPorts runtime deps detected." - exit 0 - EOS - - chmod 0755, bin/"macdeploycheck" + # Install the script from the tapped repo working tree. + # (__dir__ is .../Formula, so ../.. is the tap root) + script = File.expand_path("../../dist/macos/macdeploycheck.sh", __dir__) + bin.install script => "macdeploycheck" end test do diff --git a/build_tools/macos/install_brew_deps.sh b/build_tools/macos/install_brew_deps.sh index 673f67bce..811d02240 100755 --- a/build_tools/macos/install_brew_deps.sh +++ b/build_tools/macos/install_brew_deps.sh @@ -96,6 +96,8 @@ run_with_heartbeat "Refreshing strawberry/local tap clone" bash -lc ' for f in kdsingleapplication-qt6 qtsparkle-qt6 sparkle-framework libgpod macdeploycheck; do if ! brew info "strawberry/local/${f}" >/dev/null 2>&1; then echo "Error: Missing formula strawberry/local/${f} in the tapped repo." >&2 + echo "Details (brew info):" >&2 + brew info "strawberry/local/${f}" >&2 || true echo "If you recently added/changed formulae, ensure they are committed, then refresh the tap:" >&2 echo " git -C \"$(brew --repo strawberry/local)\" pull --ff-only" >&2 exit 1 diff --git a/dist/macos/macdeploycheck.sh b/dist/macos/macdeploycheck.sh new file mode 100644 index 000000000..feab5b869 --- /dev/null +++ b/dist/macos/macdeploycheck.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +# macdeploycheck: sanity check a deployed macOS .app bundle for accidental runtime deps +# on Homebrew/MacPorts paths (which break distribution / App Store / notarization). +# +# Usage: +# macdeploycheck /path/to/App.app + +app="${1:-}" +if [[ -z "$app" ]]; then + echo "Usage: macdeploycheck " >&2 + exit 2 +fi +if [[ ! -d "$app" ]]; then + echo "Error: app bundle not found: $app" >&2 + exit 2 +fi +if [[ ! -d "$app/Contents" ]]; then + echo "Error: not a macOS app bundle (missing Contents/): $app" >&2 + exit 2 +fi + +fail=0 +tmp="$(mktemp -t macdeploycheck.XXXXXX)" +trap 'rm -f "$tmp"' EXIT + +# Collect Mach-O files (executables + dylibs) inside the bundle. +while IFS= read -r -d '' f; do + if file "$f" | grep -q "Mach-O"; then + echo "$f" >>"$tmp" + fi +done < <(find "$app/Contents" -type f \( -perm -111 -o -name "*.dylib" -o -name "*.so" \) -print0 2>/dev/null) + +if [[ ! -s "$tmp" ]]; then + echo "Warning: no Mach-O files found under $app/Contents" >&2 + exit 0 +fi + +echo "macdeploycheck: scanning for external (Homebrew/MacPorts) runtime deps..." +while IFS= read -r f; do + deps="$(otool -L "$f" 2>/dev/null | tail -n +2 | awk '{print $1}' || true)" + while IFS= read -r dep; do + [[ -z "$dep" ]] && continue + + # Ignore system and rpath/loader/executable paths. + case "$dep" in + /System/*|/usr/lib/*|@rpath/*|@loader_path/*|@executable_path/*) continue ;; + esac + + # Common accidental runtime deps that will break distribution. + if [[ "$dep" == /opt/homebrew/* || "$dep" == /usr/local/* || "$dep" == /opt/local/* ]]; then + echo "ERROR: $f links to external path: $dep" >&2 + fail=1 + fi + done <<<"$deps" +done <"$tmp" + +if [[ "$fail" -ne 0 ]]; then + cat >&2 <<'EOM' + +One or more binaries in your .app link to a Homebrew (or MacPorts) path. +That usually means the bundle is not self-contained and will fail on other machines. + +Fix: re-run your deploy step (e.g. macdeployqt) so frameworks/dylibs are bundled and +their install names are rewritten to @rpath/@loader_path. +EOM + exit 1 +fi + +echo "OK: no external Homebrew/MacPorts runtime deps detected." +exit 0 +