From 3337f00ef3f7b7799f0140edf523a27498e04f3d Mon Sep 17 00:00:00 2001 From: Brandon <33914549+bwrightkc@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:55:17 -0500 Subject: [PATCH] Fix/macos py2app libsharpyuv (#587) * Bundle libsharpyuv for macOS py2app build py2app doesn't rewrite @rpath load commands, so libwebp's dependency on libsharpyuv (split out since webp 1.3) goes missing from the app bundle and PIL fails to import at launch. Bundle and patch it explicitly, same as the freecell-solver lib. * Re-sign macOS bundle after post-py2app dylib patching install_name_tool invalidates py2app's ad-hoc signature, so a bundle patched after signing (e.g. the libsharpyuv fix, or the existing fc-solve rpath fix) gets silently SIGKILLed by macOS on launch. Verified locally: without this, the fixed app dies with no output at all: the patched dylib breaks the code signature seal. --- setup_osx.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/setup_osx.py b/setup_osx.py index 9ba0da897..650bccbeb 100644 --- a/setup_osx.py +++ b/setup_osx.py @@ -6,7 +6,7 @@ import os import shutil import sys -from subprocess import call +from subprocess import CalledProcessError, call, check_output from pysollib.settings import PACKAGE, VERSION @@ -36,6 +36,19 @@ SOLVER_LIB_PATH = None SOLVER = [] +# libwebp (a Pillow dependency) depends on libsharpyuv via a bare +# @rpath reference that py2app's dependency walker doesn't follow. +# Bundle it explicitly, same as the solver lib above. +SHARPYUV_LIB_PATH = None +try: + _webp_prefix = check_output( + ["brew", "--prefix", "webp"]).decode().strip() + _candidate = os.path.join(_webp_prefix, "lib", "libsharpyuv.0.dylib") + if os.path.exists(_candidate): + SHARPYUV_LIB_PATH = _candidate +except (OSError, CalledProcessError): + pass + GETINFO_STRING = "PySol Fan Club Edition \ %s %s, (C) 1998-2003 Markus F.X.J Oberhumer \ (C) 2006-2007 Skomoroh" % (PACKAGE, VERSION) @@ -55,7 +68,8 @@ DATA_FILES = ['docs', 'data', 'locale', 'scripts', 'COPYING', 'README.md', ] + SOLVER RESOURCES = [] -FRAMEWORKS = [SOLVER_LIB_PATH] if SOLVER_LIB_PATH else [] +FRAMEWORKS = ([SOLVER_LIB_PATH] if SOLVER_LIB_PATH else []) + \ + ([SHARPYUV_LIB_PATH] if SHARPYUV_LIB_PATH else []) # with argv_emulation=True, the app window is not shown when launched OPTIONS = dict(argv_emulation=False, emulate_shell_environment=True, @@ -84,3 +98,27 @@ @executable_path/../Frameworks/libfreecell-solver.0.dylib fc-solve", shell=True ) + os.chdir(top) + +# Point libwebp at the bundled libsharpyuv instead of its broken +# original rpath. +if SHARPYUV_LIB_PATH and "py2app" in sys.argv: + frameworks_dir = os.path.join( + top, 'dist', '%s.app' % PACKAGE, 'Contents', 'Frameworks') + for name in os.listdir(frameworks_dir): + if name.startswith('libwebp'): + call([ + 'install_name_tool', '-change', + '@rpath/libsharpyuv.0.dylib', + '@executable_path/../Frameworks/libsharpyuv.0.dylib', + os.path.join(frameworks_dir, name), + ]) + +# install_name_tool invalidates py2app's ad-hoc signature above, and +# macOS refuses to launch a bundle whose seal doesn't match its +# contents. Re-sign after any post-processing. +if (SOLVER or SHARPYUV_LIB_PATH) and "py2app" in sys.argv: + call([ + 'codesign', '--force', '--deep', '--sign', '-', + os.path.join(top, 'dist', '%s.app' % PACKAGE), + ])