Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,89 @@ jobs:
while read -r path; do
[[ -n "$path" ]] || continue
cask="$(basename "$path" .rb)"

# Respect `depends_on arch:`. The matrix runs both Intel and Apple
# Silicon, and a cask that legitimately declares one of them is not
# a failure on the other — brew would refuse the install and the
# step would report a defect that does not exist.
# Homebrew normalises the token: `depends_on arch: :arm64` comes
# back as "arm", not "arm64", and :x86_64 as "intel". Comparing
# against uname -m directly never matches.
case "$(uname -m)" in
arm64) here=arm ;;
*) here=intel ;;
esac
# One line on purpose: a multi-line python heredoc inside a YAML
# block scalar has to be indented to stay in the block, and python
# then rejects the uniform leading whitespace.
want="$(brew info --json=v2 --cask "drycodeworks/tap/$cask" 2>/dev/null \
| python3 -c "import json,sys; d=json.load(sys.stdin)['casks'][0]; a=(d.get('depends_on') or {}).get('arch') or []; print(','.join(x.get('type','') if isinstance(x,dict) else str(x) for x in a))" 2>/dev/null || true)"
if [[ -n "$want" && ",$want," != *",$here,"* ]]; then
echo "==> $cask requires arch [$want]; this runner is $here. Skipping."
continue
fi

echo "==> Installing drycodeworks/tap/$cask"
# Snapshot first so we can exec ONLY what this cask added. A runner
# image ships its own apps, and some of them never exit on --help:
# "Google Chrome for Testing" hangs, which made a first version of
# this check fail every cask PR and blame the cask.
before="$(find /Applications -maxdepth 1 -name '*.app' | sort)"
brew install --cask "drycodeworks/tap/$cask"
after="$(find /Applications -maxdepth 1 -name '*.app' | sort)"
installed="$(comm -13 <(echo "$before") <(echo "$after"))"

# Installing is not evidence the thing runs. DRYCodeWorks/tacet#25
# shipped a cask that installed perfectly, passed every check
# above, and then hung in _dyld_start on first exec — casks
# quarantine by default, and nothing ever launches these bundles
# through LaunchServices, so the consent gate had nobody to answer
# it. Green CI, broken for every user.
#
# So exec what we installed. --help is the cheapest proof a bundle
# reaches its own main(): a quarantine wedge blocks in the dynamic
# loader and never gets there. Bounded, because that failure mode
# is a hang rather than a crash — unbounded, it would burn the job
# timeout instead of naming the problem.
if [[ -z "$installed" ]]; then
echo "==> $cask added no .app to /Applications; nothing to exec."
fi
while read -r app; do
[[ -n "$app" && -d "$app" ]] || continue
exe_name="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' \
"$app/Contents/Info.plist" 2>/dev/null)" || continue
exe="$app/Contents/MacOS/$exe_name"
[[ -x "$exe" ]] || continue
echo "==> Executing $exe --help"
# Background-and-poll rather than timeout(1): a stock macOS
# runner has neither timeout nor gtimeout, so depending on
# coreutils would leave this silently unbounded on exactly the
# platform it protects. Same reasoning as run_bounded() in
# tacet's install-server.sh.
rc=0
"$exe" --help >/dev/null 2>&1 &
pid=$!
waited=0
while kill -0 "$pid" 2>/dev/null; do
if (( waited >= 30 )); then
kill -9 "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
rc=124
break
fi
sleep 1
waited=$((waited + 1))
done
[[ $rc -eq 124 ]] || { wait "$pid"; rc=$?; }
[[ $rc -eq 0 ]] && continue
if [[ $rc -eq 124 ]]; then
echo "::error::$cask: $app hangs on exec (rc=124) — likely a quarantine wedge, see DRYCodeWorks/tacet#25"
else
echo "::error::$cask: $app exited $rc on --help"
fi
exit 1
done <<< "$installed"

brew uninstall --cask "drycodeworks/tap/$cask"
done <<< "$changed"

Expand Down
9 changes: 8 additions & 1 deletion Casks/tacet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
strategy :github_latest
end

# The published bundle is `Mach-O thin (arm64)`. Without this, brew installs
# it happily on an Intel Mac and the binary cannot execute — the cask's own
# CI exec check caught exactly that on the macos-15-intel runner. Rosetta
# does not help: it translates x86_64 to arm64, not the other way.
depends_on arch: :arm64
depends_on macos: :ventura

app "Tacet.app"
Expand All @@ -29,7 +34,9 @@
# the fact does not recover it, and neither does replacing the file — only a
# different path does. So this has to run before anything execs the binary.
#
# See DRYCodeWorks/tacet#25 for the isolation.
# See DRYCodeWorks/tacet#25 for the isolation. CI enforces that this keeps
# working: the cask job execs the installed bundle with --help and fails the
# build on a hang, which is the signature of this bug coming back.
postflight do
system_command "/usr/bin/xattr",
args: ["-dr", "com.apple.quarantine", "#{appdir}/Tacet.app"]
Expand Down
Loading