122 lines
4.3 KiB
Bash
Executable file
122 lines
4.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# check-macos-apps.sh - Host gate for the macOS port's apps (make macos),
|
|
# which nothing here can run: every app in build/macos/bin is one macOS can
|
|
# open. Its Info.plist names, as an application for the macOS
|
|
# make/support/macos/cc.sh builds for, a program that is in Contents/MacOS
|
|
# and an icon that is in Contents/Resources. The program is universal: an
|
|
# Apple silicon (arm64) and an Intel (x86_64) program, each built for that
|
|
# macOS and linking nothing but the system's own libraries, the Apple silicon
|
|
# one signed (zig signs it ad hoc as it links; macOS will not run an
|
|
# unsigned arm64 program at all). Read with LLVM's Mach-O tools.
|
|
#
|
|
# Usage: scripts/check-macos-apps.sh
|
|
# Exit: 0 PASS, 1 FAIL, 2 SKIP (the port not built, or no LLVM tools)
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
label=check-macos-apps
|
|
|
|
joeyNeed MACOS_LIPO toolchain:macos
|
|
objdump="$(dirname "$MACOS_LIPO")/llvm-objdump"
|
|
joeyNeedFile "$objdump" toolchain:macos
|
|
minimum=$("$repo/make/support/macos/cc.sh" --minimum)
|
|
bin="$repo/build/macos/bin"
|
|
if ! compgen -G "$bin/*.app" > /dev/null; then
|
|
echo "$label: SKIP - no app in $bin (make macos)"
|
|
exit 2
|
|
fi
|
|
|
|
archs="arm64 x86_64"
|
|
signed="arm64"
|
|
|
|
# joinBy SEPARATOR WORD... -- the words with SEPARATOR between them.
|
|
joinBy() {
|
|
local separator="$1"
|
|
local out="$2"
|
|
shift 2
|
|
for word in "$@"; do
|
|
out+="$separator$word"
|
|
done
|
|
printf '%s\n' "$out"
|
|
}
|
|
|
|
|
|
# plistString PLIST KEY -- the string KEY holds in an Info.plist (the
|
|
# build's own XML, each value on the line after its key).
|
|
plistString() {
|
|
awk -v key="<key>$2</key>" '
|
|
found {
|
|
if (match($0, /<string>[^<]*<\/string>/)) {
|
|
print substr($0, RSTART + length("<string>"), RLENGTH - length("<string></string>"))
|
|
}
|
|
exit
|
|
}
|
|
index($0, key) {
|
|
found = 1
|
|
}' "$1"
|
|
}
|
|
|
|
|
|
# sliceProblems PROGRAM ARCH -- what is wrong with one arch of PROGRAM.
|
|
sliceProblems() {
|
|
local headers
|
|
local library
|
|
headers=" $("$objdump" --macho --private-headers --arch="$2" "$1" | tr -s ' \t\n' ' ') "
|
|
if [[ $headers != *" LC_BUILD_VERSION "* || $headers != *" minos $minimum "* ]]; then
|
|
problems+=("$2 not built for macOS $minimum")
|
|
fi
|
|
if [[ " $signed " == *" $2 "* && $headers != *" LC_CODE_SIGNATURE "* ]]; then
|
|
problems+=("$2 unsigned")
|
|
fi
|
|
while read -r library _; do
|
|
[[ $library == /System/Library/* || $library == /usr/lib/* ]] || problems+=("$2 links $library")
|
|
done < <("$objdump" --macho --dylibs-used --arch="$2" "$1" | tail -n +2)
|
|
}
|
|
|
|
|
|
# appProblems APP -- what is wrong with APP, in problems.
|
|
appProblems() {
|
|
local contents="$1/Contents"
|
|
local plist="$contents/Info.plist"
|
|
local program
|
|
local held
|
|
local arch
|
|
problems=()
|
|
[ -f "$plist" ] || { problems=("no Info.plist"); return; }
|
|
if [ "$(plistString "$plist" CFBundlePackageType)" != APPL ] || [ "$(plistString "$plist" LSMinimumSystemVersion)" != "$minimum" ]; then
|
|
problems+=("Info.plist is not an application for macOS $minimum")
|
|
fi
|
|
[ -f "$contents/Resources/$(plistString "$plist" CFBundleIconFile).icns" ] || problems+=("no icon")
|
|
program="$contents/MacOS/$(plistString "$plist" CFBundleExecutable)"
|
|
if [ ! -f "$program" ] || [ ! -x "$program" ]; then
|
|
problems+=("no program")
|
|
return
|
|
fi
|
|
# shellcheck disable=SC2046
|
|
held=$(printf '%s\n' $("$MACOS_LIPO" -archs "$program") | sort | paste -sd' ')
|
|
[ "$held" = "$archs" ] || problems+=("holds $held, not $archs")
|
|
for arch in $held; do
|
|
[[ " $archs " == *" $arch "* ]] && sliceProblems "$program" "$arch"
|
|
done
|
|
}
|
|
|
|
|
|
fails=()
|
|
count=0
|
|
for app in "$bin"/*.app; do
|
|
count=$((count + 1))
|
|
appProblems "$app"
|
|
if [ ${#problems[@]} -gt 0 ]; then
|
|
fails+=("$(basename "$app"): $(joinBy ', ' "${problems[@]}")")
|
|
fi
|
|
done
|
|
if [ ${#fails[@]} -gt 0 ]; then
|
|
echo "$label: FAIL - $(joinBy '; ' "${fails[@]}")"
|
|
exit 1
|
|
fi
|
|
echo "$label: PASS ($count app$([ "$count" = 1 ] || echo s): universal ${archs// /+}, arm64 signed, macOS $minimum on, system libraries only)"
|