Repository navigation
xcode 引入scipy,真机调试scipy依赖的文件找不到 #2
Copy link
Copy link
Open
Description
您好,我在xcode OC语言,集成python环境,然后调用我自己的脚本,我的脚本依赖了scipy使用 from scipy.signal import butter, sosfiltfilt, find_peaks, savgol_filter
from scipy.signal.windows import gaussian 这几个依赖函数,然后报如下错误
Traceback (most recent call last):
File "", line 1, in
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/complete_preprocessing_pipeline.py", line 73, in
from scipy.signal import butter, sosfiltfilt, find_peaks, savgol_filter
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/signal/init.py", line 293, in
from . import _sigtools, windows
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/signal/windows/init.py", line 42, in
from ._windows import *
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/signal/windows/_windows.py", line 7, in
from scipy import linalg, special, fft as sp_fft
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/init.py", line 134, in getattr
return _importlib.import_module(f'scipy.{name}')
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-stdlib/importlib/init.py", line 88, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/linalg/init.py", line 221, in
from ._decomp_update import *
File "scipy/linalg/_decomp_update.pyx", line 1, in init scipy.linalg._decomp_update
ImportError: dlopen(/private/var/containers/Bundle/Application/A3F77FC6-80B2-48D2-8EC7-739316232F85/hLifeKup.app/python-ios-lib_SciPy.bundle/scipy/linalg/cython_blas.cpython-314-iphoneos.so, 0x0002): symbol not found in flat namespace 'dcabs1'
❌w模块导入失败,
Numpy 版本: 2.3.5
Scipy 版本: 1.15.0
我是按照项目的使用说明集成的,我该怎么解决这个问题?
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
baixiaochunbudanchun commentedon May 28, 2026
scipy.linalg/ scipy.signal的 .so扩展(cython_blas, cython_lapack)在 iOS 真机运行时报 symbol not found in flat namespace 'dcabs1',原因是 BLAS 符号未静态链接进 .so,iOS 的 Accelerate BLAS 符号不公开无法在 flat namespace 解析。作者可以在构建时把 OpenBLAS 或静态 BLAS 的 dcabs1, _lsame_等符号链接进这些 .so,或改用完整静态合并的 scipybundle。
yu314-coder commentedon May 31, 2026
你好,这个问题已经定位并修复,修复已经推送到
main。原因
scipy.linalg.cython_blas引用了三个 参考 BLAS 辅助函数:dcabs1、scabs1、lsame。苹果的 Accelerate 框架提供了 BLAS/LAPACK 主体内核(dgemm、zgesv等),但没有导出dcabs1/scabs1;而cython_blas是在 flat namespace(平面命名空间) 里查找它们的,所以真机上dyld一旦找不到dcabs1就崩溃:(
lsame其实在 Accelerate 里,是 two-level 绑定,没问题;真正缺的只有dcabs1/scabs1。)最简单的解决办法(推荐)
在你自己的 App target 里加入一个很小的 C 文件,把缺失的符号放进主程序,flat-namespace 查找就能解析到——不用改 scipy,也不用额外的 dylib:
把
blas_compat_stubs.c加进 Xcode 的 Build Phases → Compile Sources,重新编译,import scipy.signal/scipy.linalg就能正常加载了。仓库里已经带上的修复
为了让打包好的 scipy「开箱即用」,
main上现在也加了:fortran/blas_compat_stubs.c— 这三个辅助函数的源码;Frameworks/scipy_aux/libscipy_blas_stubs.dylib— 预编译好的(arm64-ios,ad-hoc 签名,@rpath);cython_blas*.so加了LC_LOAD_DYLIB,运行时解析这个 dylib;scripts/appstore/build-blas-stubs.sh— 每次重建 scipy 后可重新生成;docs/scipy_ios_dcabs1_fix.md有详细说明,test_scipy_blas_fix.py可在真机上验证。如果你是直接嵌入这个包,记得把
libscipy_blas_stubs.dylib拷进 App 的Frameworks/并保证有@rpath能找到它;或者直接用上面「最简单办法」,连 dylib 都不用。我在真机(iPad / iOS 26)上验证过:复数
linalg.solve、eigvals,以及butter→sosfiltfilt→savgol_filter→find_peaks信号管线都通过了。麻烦你试一下,确认是否解决,谢谢!English: Accelerate doesn't export the reference-BLAS helpers
dcabs1/scabs1, andcython_blaslooks them up in the flat namespace →dyldaborts on device. Easiest fix: add the tinyblas_compat_stubs.cabove to your app target (Build Phases → Compile Sources) so the symbols live in your main binary — no scipy patching, no extra dylib.mainnow also bundleslibscipy_blas_stubs.dylib+ anLC_LOAD_DYLIBoncython_blas*.soso the packaged scipy works out of the box (seedocs/scipy_ios_dcabs1_fix.md; verify on-device withtest_scipy_blas_fix.py). Please give it a try and let me know if it resolves the crash.baixiaochunbudanchun commentedon Jun 1, 2026
作者你好,非常感谢你给的回复及解决方案,我通过导入libfortran_io_stubs.dylib,libsf_error_state.dylib,libscipy_blas_stubs.dylib三个文件,在真机上能够将脚本模块导入成功,并获取对应的函数。
现在我又遇到提交商店代码审核的问题,按照你的readme文档操作,执行了wrap-loose-dylibs脚本,但是仍然会报Upload-fail-with-errors.txt文档中的错误,
Run-Script-build-phase.txt 是我按照5. Boot Python and add the bundles to sys.path进行的项目配置
Run-Wrap-loose-dylibs.txt是我按照App Store distribution 指导进行的配置,
wrap-loose-dylibs-result.txt 是编译之后脚本执行的结果,
我的项目只引用了numpy和scipy 两个库,请你在帮我看看什么原因导致商店审核不通过
baixiaochunbudanchun commentedon Jun 1, 2026
yu314-coder commentedon Jun 1, 2026
很高兴
dcabs1的问题已经解决,scipy 在真机上能正常导入了 👍关于上传 App Store 失败 —— 先说明一下:你附的四个
.txt(Upload-fail-with-errors.txt等)通过邮件回复发上来时变成了失效链接。GitHub 的邮件回复网关只保留图片附件,会把.txt之类的文件丢掉,所以它们点开都是 404(对我、对你都一样)。以后如果要我看日志,麻烦直接把文件拖进网页评论框(不要用邮件回复),链接才有效。不过你的两张截图已经足够定位问题了:
① General 标签 —— 三个桩 dylib 都设成了 Embed & Sign:
libfortran_io_stubs.dylib、libscipy_blas_stubs.dylib、libsf_error_state.dylib也就是以松散 dylib(loose
.dylib)的形式打进 App 包里。② Build Phases 的执行顺序:
这个顺序就是根因。
Run Wrap-loose-dylibs排在Embed Frameworks前面,所以脚本去扫描Frameworks/时,那三个桩 dylib 还没被拷进去(Embed Frameworks在后面才拷)。脚本把 python-ios-lib 包自带的二进制都正确打包成 framework 了,唯独漏掉了你手动加的这三个 —— 它们最终以松散、未打包的形式留在了.app/Frameworks/里。Apple 的校验器就会拒绝这些松散.dylib(ITMS-90xxx里 "Invalid Bundle / 不支持的动态库" 那一类)。解决办法(拖一下顺序就行):
在 CustomCalculator target → Build Phases,把
Run Wrap-loose-dylibs拖到最底部,放到Embed Frameworks的下面,让它在 dylib 被嵌入之后再执行打包。然后重新 Archive → 上传 即可。可选精简:把那段很小的
blas_compat_stubs.c加进 App target(Build Phases → Compile Sources),就能把libscipy_blas_stubs.dylib整个去掉(少一个松散 dylib);但另外两个libfortran_io_stubs/libsf_error_state仍然要靠 wrap 脚本打包成 framework —— 所以调整 Build Phase 顺序才是这次的关键。调整后如果还报错,请把
Upload-fail-with-errors.txt的内容直接贴在评论里(或拖进评论框),我再帮你看是具体哪一类。English summary: Your four
.txtlogs arrived as dead links — GitHub's email-reply gateway only keeps image attachments, so the.txtfiles were never hosted (they 404 for everyone). Please drag them into a web comment if you want me to read them next time. Your two screenshots are enough, though: the three stub dylibs (libfortran_io_stubs,libscipy_blas_stubs,libsf_error_state) are Embed & Sign (loose dylibs), and in Build Phases yourRun Wrap-loose-dylibsphase runs beforeEmbed Frameworks— so those three dylibs aren't inFrameworks/yet when the wrap script scans, and they ship loose → Apple rejects them (ITMS-90xxxInvalid Bundle / unsupported dylib).Fix: in the CustomCalculator target → Build Phases, drag
Run Wrap-loose-dylibsto the bottom, belowEmbed Frameworks, then re-Archive and upload. (Optionally addblas_compat_stubs.cto the app target to droplibscipy_blas_stubs.dylib, but the phase reorder is the real fix.) If it still fails, paste the contents ofUpload-fail-with-errors.txtdirectly and I'll pinpoint the exact category.baixiaochunbudanchun commentedon Jun 2, 2026
非常感谢你的回复和指导,由于中国访问github不是很稳定,我分几步提交我遇到的问题。第一、我听取你的建议,使用blas_compat_stubs.c 加进 App target(Build Phases → Compile Sources)这种方式,但是报了如下如错误,
Copy Bundle Resources (19)
Embed Frameworks (4 items)
Sign Python Binary Modules
Run Wrap-loose-dylibs
编译时报了如下图错误
我通过修改 wrap-loose-dylibs 脚本,解决上面问题,进行打包提交,
#!/bin/bash
wrap-loose-dylibs.sh — App Store fix for CodeBench / hLifeKup
===============================================================
1. 将 Frameworks/ 中的松散 .dylib 包装为 .framework
2. 重写所有二进制中对这些 dylib 的引用(包括主可执行文件、已有 framework、.so 等)
3. 清理 App Store 会拒绝的静态库、位码、私有 API 等
4. 重新签名所有修改过的二进制
Build Settings 要求:ENABLE_USER_SCRIPT_SANDBOXING = NO
===============================================================
set +e
echo "=== wrap-loose-dylibs.sh starting ==="
echo " BUILT_PRODUCTS_DIR=${BUILT_PRODUCTS_DIR:-(unset)}"
echo " WRAPPER_NAME=${WRAPPER_NAME:-(unset)}"
echo " EXECUTABLE_NAME=${EXECUTABLE_NAME:-(unset)}"
echo " EXPANDED_CODE_SIGN_IDENTITY=${EXPANDED_CODE_SIGN_IDENTITY:-(unset)}"
echo " ENABLE_USER_SCRIPT_SANDBOXING=${ENABLE_USER_SCRIPT_SANDBOXING:-(unset)}"
确定 .app 路径(优先使用 CODESIGNING_FOLDER_PATH)
if [ -n "${CODESIGNING_FOLDER_PATH}" ] && [ -d "${CODESIGNING_FOLDER_PATH}" ]; then
APP="${CODESIGNING_FOLDER_PATH}"
else
APP="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}"
fi
FW="$APP/Frameworks"
IDENT="${EXPANDED_CODE_SIGN_IDENTITY:--}"
if [ ! -d "$APP" ]; then
for _alt in
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
"${INSTALL_DIR}/${WRAPPER_NAME}"
"${INSTALL_ROOT}${INSTALL_PATH}/${WRAPPER_NAME}"
"${DSTROOT}${INSTALL_PATH}/${WRAPPER_NAME}"; do
if [ -d "$_alt" ]; then
APP="$_alt"
FW="$APP/Frameworks"
echo "wrap-loose-dylibs: using fallback APP=$APP"
break
fi
done
fi
if [ ! -d "$APP" ]; then
echo "wrap-loose-dylibs: ERROR APP dir missing — refusing to silently skip"
echo " BUILT_PRODUCTS_DIR=${BUILT_PRODUCTS_DIR}"
echo " TARGET_BUILD_DIR=${TARGET_BUILD_DIR}"
echo " INSTALL_DIR=${INSTALL_DIR}"
echo " DSTROOT=${DSTROOT}"
exit 1
fi
if [ ! -d "$FW" ]; then
echo "wrap-loose-dylibs: no Frameworks/ dir at $FW — Install Python phase didn't run yet"
exit 1
fi
========== 提前定义主可执行文件路径 ==========
MAIN_BIN="$APP/$EXECUTABLE_NAME"
============================================
============================================================
Step 0: 硬删除 App Store 明确拒绝的文件
============================================================
echo "wrap-loose-dylibs: Step 0 — hard cleanup of App Store-rejected files"
_HARD_CLEANUP_PATHS=(
"$FW/libkernels_optimized_ios.a"
"$FW/libexecutorch_ios.a"
"$FW/libbackend_coreml_ios.a"
"$FW/libbackend_xnnpack_ios.a"
"$FW/libthreadpool_ios.a"
"$APP/app_packages/site-packages/numpy/_core/lib/libnpymath.a"
"$APP/app_packages/site-packages/numpy/random/lib/libnpyrandom.a"
"$APP/app_packages/site-packages/scipy/.dylibs/libgcc_s.1.1.dylib"
"$APP/app_packages/site-packages/scipy/.dylibs/libgfortran.5.dylib"
"$APP/app_packages/site-packages/scipy/.dylibs/libquadmath.0.dylib"
"$FW/libgcc_s_1_1.framework"
"$FW/libgfortran_5.framework"
"$FW/libquadmath_0.framework"
"$APP/app_packages/site-packages/manimpango/_register_font.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/cmanimpango.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/enums.cpython-314-iphoneos.so"
"$FW/site-packages.manimpango._register_font.framework"
"$FW/site-packages.manimpango.cmanimpango.framework"
"$FW/site-packages.manimpango.enums.framework"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.so"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.fwork"
"$FW/site-packages.psutil._psutil_osx.framework"
)
for _f in "${_HARD_CLEANUP_PATHS[@]}"; do
if [ -e "$_f" ]; then
rm -rf "$_f" && echo " removed $_f"
fi
done
echo " APP=$APP ($(du -sh "$APP" 2>/dev/null | awk '{print $1}'))"
echo " FW=$FW ($(find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | wc -l | tr -d ' ') loose, $(find "$FW" -name "*.framework" -type d 2>/dev/null | wc -l | tr -d ' ') frameworks)"
============================================================
Step 0b: 统一 MinimumOSVersion
============================================================
TARGET_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
echo "wrap-loose-dylibs: Step 0b — harmonizing MinimumOSVersion to $TARGET_MIN_OS"
_PLIST_FIXED=0
while IFS= read -r -d '' _plist; do
/usr/libexec/PlistBuddy -c "Set :MinimumOSVersion $TARGET_MIN_OS" "$_plist" 2>/dev/null
|| /usr/libexec/PlistBuddy -c "Add :MinimumOSVersion string $TARGET_MIN_OS" "$_plist" 2>/dev/null
_PLIST_FIXED=$((_PLIST_FIXED + 1))
done < <(find "$FW" -name "Info.plist" -path "*.framework/Info.plist" -print0 2>/dev/null)
echo " fixed MinimumOSVersion in $_PLIST_FIXED framework Info.plists"
============================================================
Step 0b2: 去除 fat framework 中的 armv7 切片
============================================================
echo "wrap-loose-dylibs: Step 0b2 — stripping armv7 from fat framework binaries"
_ARMV7_STRIPPED=0
while IFS= read -r -d '' _fw; do
_plist="$_fw/Info.plist"
[ -f "$_plist" ] || continue
_exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$_plist" 2>/dev/null)
[ -z "$_exe" ] && continue
_bin="$_fw/$_exe"
[ -f "$_bin" ] || continue
file "$_bin" 2>/dev/null | grep -q "universal binary" || continue
lipo -archs "$_bin" 2>/dev/null | grep -q "armv7" || continue
if lipo -remove armv7 "$_bin" -output "$_bin.thinned" 2>/dev/null &&
mv -f "$_bin.thinned" "$_bin"; then
_ARMV7_STRIPPED=$((_ARMV7_STRIPPED + 1))
echo " stripped armv7 from $(basename "$_fw")"
else
rm -f "$_bin.thinned"
fi
done < <(find "$FW" -name "*.framework" -type d -print0 2>/dev/null)
echo " $_ARMV7_STRIPPED framework(s) thinned to arm64-only"
============================================================
Step 0b3: 修复 .fwork 文件为绝对路径
============================================================
echo "wrap-loose-dylibs: Step 0b3 — rewriting .fwork files to absolute paths"
_FWORK_FIXED=0
_FWORK_SKIPPED=0
while IFS= read -r -d '' _fwork; do
_content=$(cat "$_fwork" 2>/dev/null | tr -d '\n')
[ -z "$_content" ] && continue
case "$_content" in
@executable_path/|@rpath/|@loader_path/|/)
_FWORK_SKIPPED=$((_FWORK_SKIPPED + 1))
continue ;;
esac
printf '%s' "@executable_path/$_content" > "$_fwork"
_FWORK_FIXED=$((_FWORK_FIXED + 1))
done < <(find "$APP" -name "*.fwork" -type f -print0 2>/dev/null)
echo " rewrote $_FWORK_FIXED .fwork files (skipped $_FWORK_SKIPPED already-absolute)"
============================================================
Step 0c: 修补 scipy.linalg.cython_lapack(私有 API 规避)
============================================================
CYTHON_LAPACK_BIN="$FW/site-packages.scipy.linalg.cython_lapack.framework/site-packages.scipy.linalg.cython_lapack"
CFBundleDevelopmentRegion en CFBundleExecutable libscipy_lapack_stubs CFBundleIdentifier ai.codebench.dylib.libscipy-lapack-stubs CFBundleInfoDictionaryVersion 6.0 CFBundleName libscipy_lapack_stubs CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 STUB_PLIST_EOF printf ' MinimumOSVersion %s\n' \ "${IPHONEOS_DEPLOYMENT_TARGET:-17.0}" >> "$_STUB_PLIST" cat >> "$_STUB_PLIST" <<'STUB_PLIST_EOF2' STUB_PLIST_EOF2 SCRIPT_DIR="${PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}/scripts" if [ -f "$SCRIPT_DIR/patch-cython-lapack.py" ]; then python3 "$SCRIPT_DIR/patch-cython-lapack.py" \ "$CYTHON_LAPACK_BIN" \ "@rpath/libscipy_lapack_stubs.framework/libscipy_lapack_stubs" \ 2>&1 | sed 's/^/ /' else echo " ⚠ patch-cython-lapack.py missing — cython_lapack still has _xerbla_array__" fi codesign --force --sign "$IDENT" --timestamp=none "$STUBS_DYLIB" 2>/dev/null || true codesign --force --sign "$IDENT" --timestamp=none \ "$FW/libscipy_lapack_stubs.framework" 2>/dev/null || true else echo " ⚠ failed to build libscipy_lapack_stubs.dylib — cython_lapack will trip ITMS-90338" fi fiif [ -f "$CYTHON_LAPACK_BIN" ]; then
echo "wrap-loose-dylibs: Step 0c — patching scipy cython_lapack for App Store"
SDK_PATH=$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null)
STUBS_C="${TEMP_DIR:-/tmp}/scipy_lapack_stub.c"
STUBS_DYLIB="$FW/libscipy_lapack_stubs.framework/libscipy_lapack_stubs"
mkdir -p "$(dirname "$STUBS_DYLIB")"
cat > "$STUBS_C" <<'STUB_EOF'
#include <stddef.h>
void xerbla_arr_io_(const char *srname, const int *info,
const int *lerr, size_t srname_len) {
(void)srname; (void)info; (void)lerr; (void)srname_len;
}
STUB_EOF
_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
if xcrun -sdk iphoneos clang -arch arm64
"-mios-version-min=$_MIN_OS"
-isysroot "$SDK_PATH"
-dynamiclib
-install_name "@rpath/libscipy_lapack_stubs.framework/libscipy_lapack_stubs"
-o "$STUBS_DYLIB"
"$STUBS_C" 2>&1 | sed 's/^/ /'; then
echo " built libscipy_lapack_stubs.dylib"
_STUB_PLIST="$FW/libscipy_lapack_stubs.framework/Info.plist"
cat > "$_STUB_PLIST" <<'STUB_PLIST_EOF'
============================================================
Step 0d: 创建 libscipy_blas_stubs.framework
============================================================
BLAS_STUBS_DYLIB="$FW/libscipy_blas_stubs.framework/libscipy_blas_stubs"
CFBundleDevelopmentRegion en CFBundleExecutable libscipy_blas_stubs CFBundleIdentifier ai.codebench.dylib.libscipy-blas-stubs CFBundleInfoDictionaryVersion 6.0 CFBundleName libscipy_blas_stubs CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 STUB_PLIST_EOF printf ' MinimumOSVersion %s\n' \ "${IPHONEOS_DEPLOYMENT_TARGET:-17.0}" >> "$_STUB_PLIST" cat >> "$_STUB_PLIST" <<'STUB_PLIST_EOF2' STUB_PLIST_EOF2 codesign --force --sign "$IDENT" --timestamp=none "$BLAS_STUBS_DYLIB" 2>/dev/null || true codesign --force --sign "$IDENT" --timestamp=none \ "$FW/libscipy_blas_stubs.framework" 2>/dev/null || true else echo " ⚠ failed to build libscipy_blas_stubs.dylib — BLAS-dependent modules may fail" fi fiif [ ! -f "$BLAS_STUBS_DYLIB" ]; then
echo "wrap-loose-dylibs: Step 0d — creating libscipy_blas_stubs stub"
SDK_PATH=$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null)
_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
mkdir -p "$(dirname "$BLAS_STUBS_DYLIB")"
if xcrun -sdk iphoneos clang -arch arm64
"-mios-version-min=$_MIN_OS"
-isysroot "$SDK_PATH"
-dynamiclib
-undefined dynamic_lookup
-install_name "@rpath/libscipy_blas_stubs.framework/libscipy_blas_stubs"
-o "$BLAS_STUBS_DYLIB"
-x c /dev/null 2>&1 | sed 's/^/ /'; then
echo " built libscipy_blas_stubs.dylib"
_STUB_PLIST="$FW/libscipy_blas_stubs.framework/Info.plist"
cat > "$_STUB_PLIST" <<'STUB_PLIST_EOF'
============================================================
辅助函数
============================================================
to_fw_name() {
local base; base=$(basename "$1" .dylib)
echo "$base" | tr '.' '_'
}
write_plist() {
CFBundleDevelopmentRegion en CFBundleExecutable ${name} CFBundleIdentifier ai.codebench.dylib.${id_safe} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${name} CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 MinimumOSVersion ${IPHONEOS_DEPLOYMENT_TARGET:-17.0} PLIST }local name="$1" out="$2"
local id_safe; id_safe=$(echo "$name" | tr '_' '-')
cat > "$out" <<PLIST
sign_bin() {
codesign --force --sign "$IDENT" --timestamp=none
--preserve-metadata=identifier,entitlements,flags "$1" 2>/dev/null
|| codesign --force --sign "$IDENT" --timestamp=none "$1" 2>/dev/null
|| true
}
============================================================
Step 1: 收集所有松散的 .dylib 并建立重命名映射
============================================================
MAP_FILE="${TEMP_DIR:-/tmp}/wrap-loose-dylibs-$$.map"
: > "$MAP_FILE"
while IFS= read -r -d '' dylib; do
base=$(basename "$dylib")
name=$(to_fw_name "$dylib")
fw_dir="$FW/${name}.framework"
new_load="@rpath/${name}.framework/${name}"
echo "$base|$new_load|$fw_dir|$fw_dir/$name" >> "$MAP_FILE"
done < <(find "$FW" -maxdepth 1 -name "*.dylib" -not -type l -print0)
LOOSE_COUNT=$(wc -l < "$MAP_FILE" | tr -d ' ')
if [ "$LOOSE_COUNT" -eq 0 ]; then
echo "wrap-loose-dylibs: 0 loose dylibs — nothing to do"
rm -f "$MAP_FILE"
exit 0
fi
echo "wrap-loose-dylibs: $LOOSE_COUNT loose dylibs to wrap"
============================================================
Step 2: 将每个松散 .dylib 包装为 .framework
============================================================
while IFS='|' read -r base new_load fw_dir new_bin; do
[ -z "$base" ] && continue
src="$FW/$base"
[ -f "$src" ] || continue
mkdir -p "$fw_dir"
mv -f "$src" "$new_bin"
install_name_tool -id "$new_load" "$new_bin" 2>/dev/null || true
name=$(basename "$fw_dir" .framework)
write_plist "$name" "$fw_dir/Info.plist"
done < "$MAP_FILE"
清理遗留的符号链接
for sym in "$FW"/*.dylib; do
[ -L "$sym" ] || continue
base=$(basename "$sym")
name=$(to_fw_name "$sym")
fw_dir="$FW/${name}.framework"
if [ -d "$fw_dir" ]; then
rm -f "$sym"
fi
done
============================================================
Step 3: 重写所有二进制中对这些 dylib 的引用
============================================================
rewrite_refs_in() {
local target="$1"
[ -f "$target" ] || return
while IFS='|' read -r base new_load fw_dir new_bin; do
[ -z "$base" ] && continue
old_ref="@rpath/$base"
if otool -L "$target" 2>/dev/null | grep -q "^ $old_ref "; then gbf修改后可以编译成功
}
echo "wrap-loose-dylibs: rewriting references in all binaries"
{
# 主可执行文件
[ -f "$MAIN_BIN" ] && printf '%s\0' "$MAIN_BIN"
} | while IFS= read -r -d '' bin; do
rewrite_refs_in "$bin"
done
确保主可执行文件有 @executable_path/Frameworks 这个 rpath(如果没有则添加)
if [ -f "$MAIN_BIN" ]; then
if ! otool -l "$MAIN_BIN" 2>/dev/null | grep -A2 LC_RPATH | grep -q "Frameworks"; then
install_name_tool -add_rpath "@executable_path/Frameworks" "$MAIN_BIN" 2>/dev/null || true
fi
fi
============================================================
Step 3a: 删除静态 .a 归档
============================================================
echo "wrap-loose-dylibs: scrubbing static .a archives"
A_REMOVED=0
while IFS= read -r -d '' a; do
rm -f "$a" && A_REMOVED=$((A_REMOVED + 1))
done < <(find "$FW" "$APP" -name "*.a" -type f -print0 2>/dev/null)
echo " removed $A_REMOVED static archives"
============================================================
Step 3b: 将非框架目录(latex/katex)移出 Frameworks/
============================================================
for d in latex katex; do$d/ → $ {dest_root#$APP/}/"
if [ -d "$FW/$d" ] && [ ! -d "$FW/${d}.framework" ]; then
dest_root="$APP"
[ -d "$APP/Resources" ] && dest_root="$APP/Resources"
echo " relocating
rm -rf "$dest_root/$d"
mv "$FW/$d" "$dest_root/$d"
fi
done
============================================================
Step 3c: 处理 app_packages 深处的松散 dylib
============================================================
APP_PKGS=""
for cand in "$APP/app_packages" "$APP/Resources/app_packages" "$APP/python/app_packages"; do
[ -d "$cand" ] && APP_PKGS="$cand" && break
done
if [ -n "$APP_PKGS" ]; then
echo "wrap-loose-dylibs: scrubbing app_packages/ at $APP_PKGS"
iOS placeholder for torch_shm_manager
The real torch_shm_manager is a Mach-O executable used by
torch.multiprocessing to spawn shared-memory daemons via fork().
iOS forbids fork() in app bundles AND App Store rejects standalone
Mach-O executables in app_packages/, so we replace the binary with
this text placeholder. PyTorch's manager_path() finds the file,
os.path.exists() returns True, _C._initExtension() succeeds, and
torch loads with most features intact (tensors, autograd, nn,
optim, jit, save/load, ExecuTorch, CoreML — all work).
torch.multiprocessing's fork-based APIs will fail at call time,
which is the actual behavior on iOS regardless.
EOF_SHM_PLACEHOLDER
chmod 644 "$TORCH_SHM"
echo " torch_shm_manager: Mach-O → text placeholder (App Store-safe)"
fi
fi
fi
============================================================
Step 3c2: 去除位码 (bitcode)
============================================================
echo "wrap-loose-dylibs: stripping bitcode from binaries"
BC_STRIPPED=0
strip_bitcode() {
local bin="$1"
[ -f "$bin" ] || return
file "$bin" 2>/dev/null | grep -q "Mach-O" || return
if xcrun bitcode_strip -r "$bin" -o "$bin" 2>/dev/null; then
BC_STRIPPED=$((BC_STRIPPED + 1))
fi
}
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
[ -n "$exe" ] && strip_bitcode "$fw/$exe"
done < <(find "$FW" -name ".framework" -type d -print0)
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
[ -n "$exe" ] && strip_bitcode "$fw/$exe"
done < <(find "$APP" -path ".xcframework/.framework" -type d -print0 2>/dev/null)
if [ -n "$APP_PKGS" ]; then
while IFS= read -r -d '' bin; do
strip_bitcode "$bin"
done < <(find "$APP_PKGS" ( -name ".so" -o -name "*.dylib" ) -type f -not -type l -print0 2>/dev/null)
fi
[ -f "$MAIN_BIN" ] && strip_bitcode "$MAIN_BIN"
echo " stripped bitcode from $BC_STRIPPED binaries"
============================================================
Step 3d: 将 MH_BUNDLE 转换为 MH_DYLIB
============================================================
SCRIPT_DIR="${PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}/scripts"
FIX_PY="$SCRIPT_DIR/fix-macho-type.py"
if [ -f "$FIX_PY" ]; then
echo "wrap-loose-dylibs: flipping MH_BUNDLE → MH_DYLIB"
python3 "$FIX_PY" "$APP" 2>&1 | sed 's/^/ /'
else
echo " ⚠ fix-macho-type.py not found at $FIX_PY — MH_BUNDLE will trip validator"
fi
============================================================
Step 3e: 修复 libscipy_blas_stubs.dylib 引用
============================================================
echo "wrap-loose-dylibs: fixing references to libscipy_blas_stubs.dylib"
OLD_BLAS_REF="@rpath/libscipy_blas_stubs.dylib"
NEW_BLAS_REF="@rpath/libscipy_blas_stubs.framework/libscipy_blas_stubs"
if [ -f "$MAIN_BIN" ]; then
if otool -L "$MAIN_BIN" 2>/dev/null | grep -q "$OLD_BLAS_REF"; then
install_name_tool -change "$OLD_BLAS_REF" "$NEW_BLAS_REF" "$MAIN_BIN" 2>/dev/null && echo " updated main executable"
fi
fi
while IFS= read -r -d '' bin; do
if otool -L "$bin" 2>/dev/null | grep -q "$OLD_BLAS_REF"; then
install_name_tool -change "$OLD_BLAS_REF" "$NEW_BLAS_REF" "$bin" 2>/dev/null && echo " updated $bin"
fi
done < <(
find "$FW" -name "*.framework" -type d -print0 |
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
bin="$fw/$exe"
[ -f "$bin" ] && printf '%s\0' "$bin"
done
)
============================================================
Step 4: 重新签名所有被修改过的二进制和框架
============================================================
while IFS='|' read -r base new_load fw_dir new_bin; do
[ -z "$base" ] && continue
sign_bin "$new_bin"
codesign --force --sign "$IDENT" --timestamp=none "$fw_dir" 2>/dev/null || true
done < "$MAP_FILE"
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
bin="$fw/$exe"
[ -f "$bin" ] && sign_bin "$bin"
codesign --force --sign "$IDENT" --timestamp=none "$fw" 2>/dev/null || true
done < <(find "$FW" -name "*.framework" -type d -print0)
if [ -n "$APP_PKGS" ]; then
find "$APP_PKGS" -name "*.so" -type f -not -type l -print0 2>/dev/null |
while IFS= read -r -d '' so; do
sign_bin "$so"
done
fi
[ -f "$MAIN_BIN" ] && sign_bin "$MAIN_BIN"
rm -f "$MAP_FILE"
LEFT=$(find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | wc -l | tr -d ' ')
WRAPPED=$(find "$FW" -name ".framework" -type d | wc -l | tr -d ' ')
DEEP_LEFT=0
[ -n "$APP_PKGS" ] && DEEP_LEFT=$(find "$APP_PKGS" -name ".dylib" -type f -not -type l 2>/dev/null | wc -l | tr -d ' ')
STATIC_LEFT=$(find "$FW" "$APP" -name ".a" -type f 2>/dev/null | wc -l | tr -d ' ')
echo "wrap-loose-dylibs: done — $WRAPPED frameworks, $LEFT loose top-level, $DEEP_LEFT loose in app_packages, $STATIC_LEFT static archives"
if [ "$LEFT" -gt 0 ] || [ "$DEEP_LEFT" -gt 0 ] || [ "$STATIC_LEFT" -gt 0 ]; then
echo " ⚠ remaining items the validator may reject:"
find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | sed 's|^| |'
[ -n "$APP_PKGS" ] && find "$APP_PKGS" -name ".dylib" -type f -not -type l 2>/dev/null | sed 's|^| |'
find "$FW" "$APP" -name "*.a" -type f 2>/dev/null | sed 's|^| |'
fi
============================================================
最终 App Store 就绪检查
============================================================
echo "=== wrap-loose-dylibs: final App Store readiness check ==="
_REJECTED=0
_check_absent() {
local label="$1"; shift
local found=()
for _path in "$@"; do
[ -e "$_path" ] && found+=("$_path")
done
if [ ${#found[@]} -gt 0 ]; then
echo " ❌ $label still present:"
for _f in "${found[@]}"; do echo " $_f"; done
_REJECTED=$((_REJECTED + ${#found[@]}))
else
echo " ✓ $label clean"
fi
}
_static_archives=()
while IFS= read -r -d '' _a; do _static_archives+=("$_a"); done < <(find "$APP" -name ".a" -type f -print0 2>/dev/null)
_check_absent "static .a archives" "${_static_archives[@]}"
_loose_dylibs=()
while IFS= read -r -d '' _d; do _loose_dylibs+=("$_d"); done < <(find "$FW" -maxdepth 1 -name ".dylib" -type f -print0 2>/dev/null)
_check_absent "loose .dylib in Frameworks/" "${_loose_dylibs[@]}"
if [ -n "$APP_PKGS" ]; then
_deep_dylibs=()
while IFS= read -r -d '' _d; do _deep_dylibs+=("$_d"); done < <(find "$APP_PKGS" -name ".dylib" -type f -not -type l -print0 2>/dev/null)
_check_absent "loose .dylib in app_packages/" "${_deep_dylibs[@]}"
fi
_TORCH_SHM_PATH="$APP/app_packages/site-packages/torch/bin/torch_shm_manager"
if [ -f "$_TORCH_SHM_PATH" ]; then
if file "$_TORCH_SHM_PATH" 2>/dev/null | grep -q "Mach-O"; then
echo " ❌ torch_shm_manager is still a Mach-O — App Store will reject"
_REJECTED=$((_REJECTED + 1))
else
echo " ✓ torch_shm_manager is text placeholder (App Store-safe)"
fi
else
echo " ⚠ torch_shm_manager missing — torch.multiprocessing won't initialize"
fi
_check_absent "manimpango native .so (CTFontCopyDefaultCascadeList)"
"$APP/app_packages/site-packages/manimpango/_register_font.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/cmanimpango.cpython-314-iphoneos.so"
_check_absent "psutil._psutil_osx (IOPSCopyPowerSources)"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.so"
[ -d "$FW/latex" ] && [ ! -d "$FW/latex.framework" ] &&
echo " ❌ Frameworks/latex/ data dir still present" && _REJECTED=$((_REJECTED + 1))
[ -d "$FW/katex" ] && [ ! -d "$FW/katex.framework" ] &&
echo " ❌ Frameworks/katex/ data dir still present" && _REJECTED=$((_REJECTED + 1))
if [ "$_REJECTED" -gt 0 ]; then
echo ""
echo "❌ App Store readiness FAILED: $_REJECTED rejected file(s) still in bundle."
echo " App Store Connect will reject this upload. Fix above issues before archiving."
exit 1
fi
echo "✓ App Store readiness OK"
exit 0
baixiaochunbudanchun commentedon Jun 2, 2026
第二进行打包提交商店审核,
下面是提交包的项目文件结构,
Invalid bundle structure. The “CustomCalculator.app/python-ios-lib_SciPy.bundle/scipy/linalg/_fblas.cpython-314-iphoneos.so” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \U201cCustomCalculator.app/python-ios-lib_SciPy.bundle/scipy/linalg/_fblas.cpython-314-iphoneos.so\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.
Invalid bundle structure. The “CustomCalculator.app/python-ios-lib_NumPy.bundle/numpy/random/_mt19937.cpython-314-iphoneos.so” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \U201cCustomCalculator.app/python-ios-lib_NumPy.bundle/numpy/random/_mt19937.cpython-314-iphoneos.so\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.
Upload Symbols Failed
The archive did not include a dSYM for the Python.framework with the UUIDs [89F1E796-CAC3-3DCF-AE52-9F1D7BB1AEA5]. Ensure that the archive's dSYM folder includes a DWARF file for Python.framework with the expected UUIDs.
baixiaochunbudanchun commentedon Jun 2, 2026
第三步我继续需修改wrap-loose-dylibs 脚本
#!/bin/bash
wrap-loose-dylibs.sh — App Store fix for CodeBench / hLifeKup
===============================================================
1. 将 Frameworks/ 中的松散 .dylib 包装为 .framework
2. 将 .bundle 和 lib-dynload 中的 .so 包装为 .framework
3. 重写所有二进制中对这些 dylib/so 的引用(包括主可执行文件、已有 framework 等)
4. 清理 App Store 会拒绝的静态库、位码、私有 API 等
5. 重新签名所有修改过的二进制
Build Settings 要求:ENABLE_USER_SCRIPT_SANDBOXING = NO
===============================================================
set +e
echo "=== wrap-loose-dylibs.sh starting ==="
echo " BUILT_PRODUCTS_DIR=${BUILT_PRODUCTS_DIR:-(unset)}"
echo " WRAPPER_NAME=${WRAPPER_NAME:-(unset)}"
echo " EXECUTABLE_NAME=${EXECUTABLE_NAME:-(unset)}"
echo " EXPANDED_CODE_SIGN_IDENTITY=${EXPANDED_CODE_SIGN_IDENTITY:-(unset)}"
echo " ENABLE_USER_SCRIPT_SANDBOXING=${ENABLE_USER_SCRIPT_SANDBOXING:-(unset)}"
确定 .app 路径(优先使用 CODESIGNING_FOLDER_PATH)
if [ -n "${CODESIGNING_FOLDER_PATH}" ] && [ -d "${CODESIGNING_FOLDER_PATH}" ]; then
APP="${CODESIGNING_FOLDER_PATH}"
else
APP="${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}"
fi
FW="$APP/Frameworks"
IDENT="${EXPANDED_CODE_SIGN_IDENTITY:--}"
if [ ! -d "$APP" ]; then
for _alt in
"${TARGET_BUILD_DIR}/${WRAPPER_NAME}"
"${INSTALL_DIR}/${WRAPPER_NAME}"
"${INSTALL_ROOT}${INSTALL_PATH}/${WRAPPER_NAME}"
"${DSTROOT}${INSTALL_PATH}/${WRAPPER_NAME}"; do
if [ -d "$_alt" ]; then
APP="$_alt"
FW="$APP/Frameworks"
echo "wrap-loose-dylibs: using fallback APP=$APP"
break
fi
done
fi
if [ ! -d "$APP" ]; then
echo "wrap-loose-dylibs: ERROR APP dir missing — refusing to silently skip"
echo " BUILT_PRODUCTS_DIR=${BUILT_PRODUCTS_DIR}"
echo " TARGET_BUILD_DIR=${TARGET_BUILD_DIR}"
echo " INSTALL_DIR=${INSTALL_DIR}"
echo " DSTROOT=${DSTROOT}"
exit 1
fi
if [ ! -d "$FW" ]; then
echo "wrap-loose-dylibs: no Frameworks/ dir at $FW — Install Python phase didn't run yet"
exit 1
fi
========== 提前定义主可执行文件路径 ==========
MAIN_BIN="$APP/$EXECUTABLE_NAME"
============================================
============================================================
Step 0: 硬删除 App Store 明确拒绝的文件
============================================================
echo "wrap-loose-dylibs: Step 0 — hard cleanup of App Store-rejected files"
_HARD_CLEANUP_PATHS=(
"$FW/libkernels_optimized_ios.a"
"$FW/libexecutorch_ios.a"
"$FW/libbackend_coreml_ios.a"
"$FW/libbackend_xnnpack_ios.a"
"$FW/libthreadpool_ios.a"
"$APP/app_packages/site-packages/numpy/_core/lib/libnpymath.a"
"$APP/app_packages/site-packages/numpy/random/lib/libnpyrandom.a"
"$APP/app_packages/site-packages/scipy/.dylibs/libgcc_s.1.1.dylib"
"$APP/app_packages/site-packages/scipy/.dylibs/libgfortran.5.dylib"
"$APP/app_packages/site-packages/scipy/.dylibs/libquadmath.0.dylib"
"$FW/libgcc_s_1_1.framework"
"$FW/libgfortran_5.framework"
"$FW/libquadmath_0.framework"
"$APP/app_packages/site-packages/manimpango/_register_font.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/cmanimpango.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/enums.cpython-314-iphoneos.so"
"$FW/site-packages.manimpango._register_font.framework"
"$FW/site-packages.manimpango.cmanimpango.framework"
"$FW/site-packages.manimpango.enums.framework"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.so"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.fwork"
"$FW/site-packages.psutil._psutil_osx.framework"
)
for _f in "${_HARD_CLEANUP_PATHS[@]}"; do
if [ -e "$_f" ]; then
rm -rf "$_f" && echo " removed $_f"
fi
done
echo " APP=$APP ($(du -sh "$APP" 2>/dev/null | awk '{print $1}'))"
echo " FW=$FW ($(find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | wc -l | tr -d ' ') loose, $(find "$FW" -name "*.framework" -type d 2>/dev/null | wc -l | tr -d ' ') frameworks)"
============================================================
Step 0b: 统一 MinimumOSVersion
============================================================
TARGET_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
echo "wrap-loose-dylibs: Step 0b — harmonizing MinimumOSVersion to $TARGET_MIN_OS"
_PLIST_FIXED=0
while IFS= read -r -d '' _plist; do
/usr/libexec/PlistBuddy -c "Set :MinimumOSVersion $TARGET_MIN_OS" "$_plist" 2>/dev/null
|| /usr/libexec/PlistBuddy -c "Add :MinimumOSVersion string $TARGET_MIN_OS" "$_plist" 2>/dev/null
_PLIST_FIXED=$((_PLIST_FIXED + 1))
done < <(find "$FW" -name "Info.plist" -path "*.framework/Info.plist" -print0 2>/dev/null)
echo " fixed MinimumOSVersion in $_PLIST_FIXED framework Info.plists"
============================================================
Step 0b2: 去除 fat framework 中的 armv7 切片
============================================================
echo "wrap-loose-dylibs: Step 0b2 — stripping armv7 from fat framework binaries"
_ARMV7_STRIPPED=0
while IFS= read -r -d '' _fw; do
_plist="$_fw/Info.plist"
[ -f "$_plist" ] || continue
_exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$_plist" 2>/dev/null)
[ -z "$_exe" ] && continue
_bin="$_fw/$_exe"
[ -f "$_bin" ] || continue
file "$_bin" 2>/dev/null | grep -q "universal binary" || continue
lipo -archs "$_bin" 2>/dev/null | grep -q "armv7" || continue
if lipo -remove armv7 "$_bin" -output "$_bin.thinned" 2>/dev/null &&
mv -f "$_bin.thinned" "$_bin"; then
_ARMV7_STRIPPED=$((_ARMV7_STRIPPED + 1))
echo " stripped armv7 from $(basename "$_fw")"
else
rm -f "$_bin.thinned"
fi
done < <(find "$FW" -name "*.framework" -type d -print0 2>/dev/null)
echo " $_ARMV7_STRIPPED framework(s) thinned to arm64-only"
============================================================
Step 0b3: 修复 .fwork 文件为绝对路径
============================================================
echo "wrap-loose-dylibs: Step 0b3 — rewriting .fwork files to absolute paths"
_FWORK_FIXED=0
_FWORK_SKIPPED=0
while IFS= read -r -d '' _fwork; do
_content=$(cat "$_fwork" 2>/dev/null | tr -d '\n')
[ -z "$_content" ] && continue
case "$_content" in
@executable_path/|@rpath/|@loader_path/|/)
_FWORK_SKIPPED=$((_FWORK_SKIPPED + 1))
continue ;;
esac
printf '%s' "@executable_path/$_content" > "$_fwork"
_FWORK_FIXED=$((_FWORK_FIXED + 1))
done < <(find "$APP" -name "*.fwork" -type f -print0 2>/dev/null)
echo " rewrote $_FWORK_FIXED .fwork files (skipped $_FWORK_SKIPPED already-absolute)"
============================================================
Step 0c: 修补 scipy.linalg.cython_lapack(私有 API 规避)
============================================================
CYTHON_LAPACK_BIN="$FW/site-packages.scipy.linalg.cython_lapack.framework/site-packages.scipy.linalg.cython_lapack"
CFBundleDevelopmentRegion en CFBundleExecutable libscipy_lapack_stubs CFBundleIdentifier ai.codebench.dylib.libscipy-lapack-stubs CFBundleInfoDictionaryVersion 6.0 CFBundleName libscipy_lapack_stubs CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 STUB_PLIST_EOF printf ' MinimumOSVersion %s\n' \ "${IPHONEOS_DEPLOYMENT_TARGET:-17.0}" >> "$_STUB_PLIST" cat >> "$_STUB_PLIST" <<'STUB_PLIST_EOF2' STUB_PLIST_EOF2 SCRIPT_DIR="${PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}/scripts" if [ -f "$SCRIPT_DIR/patch-cython-lapack.py" ]; then python3 "$SCRIPT_DIR/patch-cython-lapack.py" \ "$CYTHON_LAPACK_BIN" \ "@rpath/libscipy_lapack_stubs.framework/libscipy_lapack_stubs" \ 2>&1 | sed 's/^/ /' else echo " ⚠ patch-cython-lapack.py missing — cython_lapack still has _xerbla_array__" fi codesign --force --sign "$IDENT" --timestamp=none "$STUBS_DYLIB" 2>/dev/null || true codesign --force --sign "$IDENT" --timestamp=none \ "$FW/libscipy_lapack_stubs.framework" 2>/dev/null || true else echo " ⚠ failed to build libscipy_lapack_stubs.dylib — cython_lapack will trip ITMS-90338" fi fiif [ -f "$CYTHON_LAPACK_BIN" ]; then
echo "wrap-loose-dylibs: Step 0c — patching scipy cython_lapack for App Store"
SDK_PATH=$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null)
STUBS_C="${TEMP_DIR:-/tmp}/scipy_lapack_stub.c"
STUBS_DYLIB="$FW/libscipy_lapack_stubs.framework/libscipy_lapack_stubs"
mkdir -p "$(dirname "$STUBS_DYLIB")"
cat > "$STUBS_C" <<'STUB_EOF'
#include <stddef.h>
void xerbla_arr_io_(const char *srname, const int *info,
const int *lerr, size_t srname_len) {
(void)srname; (void)info; (void)lerr; (void)srname_len;
}
STUB_EOF
_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
if xcrun -sdk iphoneos clang -arch arm64
"-mios-version-min=$_MIN_OS"
-isysroot "$SDK_PATH"
-dynamiclib
-install_name "@rpath/libscipy_lapack_stubs.framework/libscipy_lapack_stubs"
-o "$STUBS_DYLIB"
"$STUBS_C" 2>&1 | sed 's/^/ /'; then
echo " built libscipy_lapack_stubs.dylib"
_STUB_PLIST="$FW/libscipy_lapack_stubs.framework/Info.plist"
cat > "$_STUB_PLIST" <<'STUB_PLIST_EOF'
============================================================
Step 0d: 创建 libscipy_blas_stubs.framework
============================================================
BLAS_STUBS_DYLIB="$FW/libscipy_blas_stubs.framework/libscipy_blas_stubs"
CFBundleDevelopmentRegion en CFBundleExecutable libscipy_blas_stubs CFBundleIdentifier ai.codebench.dylib.libscipy-blas-stubs CFBundleInfoDictionaryVersion 6.0 CFBundleName libscipy_blas_stubs CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 STUB_PLIST_EOF printf ' MinimumOSVersion %s\n' \ "${IPHONEOS_DEPLOYMENT_TARGET:-17.0}" >> "$_STUB_PLIST" cat >> "$_STUB_PLIST" <<'STUB_PLIST_EOF2' STUB_PLIST_EOF2 codesign --force --sign "$IDENT" --timestamp=none "$BLAS_STUBS_DYLIB" 2>/dev/null || true codesign --force --sign "$IDENT" --timestamp=none \ "$FW/libscipy_blas_stubs.framework" 2>/dev/null || true else echo " ⚠ failed to build libscipy_blas_stubs.dylib — BLAS-dependent modules may fail" fi fiif [ ! -f "$BLAS_STUBS_DYLIB" ]; then
echo "wrap-loose-dylibs: Step 0d — creating libscipy_blas_stubs stub"
SDK_PATH=$(xcrun --sdk iphoneos --show-sdk-path 2>/dev/null)
_MIN_OS="${IPHONEOS_DEPLOYMENT_TARGET:-17.0}"
mkdir -p "$(dirname "$BLAS_STUBS_DYLIB")"
if xcrun -sdk iphoneos clang -arch arm64
"-mios-version-min=$_MIN_OS"
-isysroot "$SDK_PATH"
-dynamiclib
-undefined dynamic_lookup
-install_name "@rpath/libscipy_blas_stubs.framework/libscipy_blas_stubs"
-o "$BLAS_STUBS_DYLIB"
-x c /dev/null 2>&1 | sed 's/^/ /'; then
echo " built libscipy_blas_stubs.dylib"
_STUB_PLIST="$FW/libscipy_blas_stubs.framework/Info.plist"
cat > "$_STUB_PLIST" <<'STUB_PLIST_EOF'
============================================================
辅助函数
============================================================
to_fw_name() {
local file_path="$1"
local base
# 如果路径中有 .bundle,则提取 .bundle 名称
if [[ "$file_path" == ".bundle" ]]; then
# 提取 .bundle 前缀
base=$(basename "$(dirname "$(dirname "$file_path")")" .bundle)
else
base=$(basename "$file_path" .so)
base=${base%.dylib}
fi
# 将名称中的点号替换为下划线
echo "$base" | tr '.' '_'
}
write_plist() {
CFBundleDevelopmentRegion en CFBundleExecutable ${name} CFBundleIdentifier ai.codebench.dylib.${id_safe} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${name} CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion 1 MinimumOSVersion ${IPHONEOS_DEPLOYMENT_TARGET:-17.0} PLIST }local name="$1" out="$2"
local id_safe; id_safe=$(echo "$name" | tr '_' '-')
cat > "$out" <<PLIST
sign_bin() {
codesign --force --sign "$IDENT" --timestamp=none
--preserve-metadata=identifier,entitlements,flags "$1" 2>/dev/null
|| codesign --force --sign "$IDENT" --timestamp=none "$1" 2>/dev/null
|| true
}
============================================================
Step 1: 收集所有松散的 .dylib 并建立重命名映射
============================================================
MAP_FILE="${TEMP_DIR:-/tmp}/wrap-loose-dylibs-$$.map"
: > "$MAP_FILE"
while IFS= read -r -d '' dylib; do
base=$(basename "$dylib")
name=$(to_fw_name "$dylib")
fw_dir="$FW/${name}.framework"
new_load="@rpath/${name}.framework/${name}"
echo "$base|$new_load|$fw_dir|$fw_dir/$name" >> "$MAP_FILE"
done < <(find "$FW" -maxdepth 1 -name "*.dylib" -not -type l -print0)
LOOSE_COUNT=$(wc -l < "$MAP_FILE" | tr -d ' ')
if [ "$LOOSE_COUNT" -eq 0 ]; then
echo "wrap-loose-dylibs: 0 loose dylibs — nothing to do"
rm -f "$MAP_FILE"
# 继续执行后续步骤,可能还有 .so 需要处理
else
echo "wrap-loose-dylibs: $LOOSE_COUNT loose dylibs to wrap"
fi
============================================================
Step 3: 重写所有二进制中对这些 dylib 的引用 gbf修改后可以编译成功
============================================================
rewrite_refs_in() {
local target="$1"
[ -f "$target" ] || return
while IFS='|' read -r base new_load fw_dir new_bin; do
[ -z "$base" ] && continue
old_ref="@rpath/$base"
if otool -L "$target" 2>/dev/null | grep -q "$old_ref"; then
install_name_tool -change "$old_ref" "$new_load" "$target" 2>/dev/null || true
fi
done < "$MAP_FILE"
}
echo "wrap-loose-dylibs: rewriting references in all binaries"
{
# 主可执行文件
[ -f "$MAIN_BIN" ] && printf '%s\0' "$MAIN_BIN"
} | while IFS= read -r -d '' bin; do
rewrite_refs_in "$bin"
done
确保主可执行文件有 @executable_path/Frameworks 这个 rpath(如果没有则添加)
if [ -f "$MAIN_BIN" ]; then
if ! otool -l "$MAIN_BIN" 2>/dev/null | grep -A2 LC_RPATH | grep -q "Frameworks"; then
install_name_tool -add_rpath "@executable_path/Frameworks" "$MAIN_BIN" 2>/dev/null || true
fi
fi
============================================================
Step 3a: 删除静态 .a 归档
============================================================
echo "wrap-loose-dylibs: scrubbing static .a archives"
A_REMOVED=0
while IFS= read -r -d '' a; do
rm -f "$a" && A_REMOVED=$((A_REMOVED + 1))
done < <(find "$FW" "$APP" -name "*.a" -type f -print0 2>/dev/null)
echo " removed $A_REMOVED static archives"
============================================================
Step 3b: 将非框架目录(latex/katex)移出 Frameworks/
============================================================
for d in latex katex; do$d/ → $ {dest_root#$APP/}/"
if [ -d "$FW/$d" ] && [ ! -d "$FW/${d}.framework" ]; then
dest_root="$APP"
[ -d "$APP/Resources" ] && dest_root="$APP/Resources"
echo " relocating
rm -rf "$dest_root/$d"
mv "$FW/$d" "$dest_root/$d"
fi
done
============================================================
Step 3c: 处理 app_packages 深处的松散 dylib
============================================================
APP_PKGS=""
for cand in "$APP/app_packages" "$APP/Resources/app_packages" "$APP/python/app_packages"; do
[ -d "$cand" ] && APP_PKGS="$cand" && break
done
if [ -n "$APP_PKGS" ]; then
echo "wrap-loose-dylibs: scrubbing app_packages/ at $APP_PKGS"
iOS placeholder for torch_shm_manager
The real torch_shm_manager is a Mach-O executable used by
torch.multiprocessing to spawn shared-memory daemons via fork().
iOS forbids fork() in app bundles AND App Store rejects standalone
Mach-O executables in app_packages/, so we replace the binary with
this text placeholder. PyTorch's manager_path() finds the file,
os.path.exists() returns True, _C._initExtension() succeeds, and
torch loads with most features intact (tensors, autograd, nn,
optim, jit, save/load, ExecuTorch, CoreML — all work).
torch.multiprocessing's fork-based APIs will fail at call time,
which is the actual behavior on iOS regardless.
EOF_SHM_PLACEHOLDER
chmod 644 "$TORCH_SHM"
echo " torch_shm_manager: Mach-O → text placeholder (App Store-safe)"
fi
fi
fi
============================================================
Step 3c2: 去除位码 (bitcode)
============================================================
echo "wrap-loose-dylibs: stripping bitcode from binaries"
BC_STRIPPED=0
strip_bitcode() {
local bin="$1"
[ -f "$bin" ] || return
file "$bin" 2>/dev/null | grep -q "Mach-O" || return
if xcrun bitcode_strip -r "$bin" -o "$bin" 2>/dev/null; then
BC_STRIPPED=$((BC_STRIPPED + 1))
fi
}
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
[ -n "$exe" ] && strip_bitcode "$fw/$exe"
done < <(find "$FW" -name ".framework" -type d -print0)
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
[ -n "$exe" ] && strip_bitcode "$fw/$exe"
done < <(find "$APP" -path ".xcframework/.framework" -type d -print0 2>/dev/null)
if [ -n "$APP_PKGS" ]; then
while IFS= read -r -d '' bin; do
strip_bitcode "$bin"
done < <(find "$APP_PKGS" ( -name ".so" -o -name "*.dylib" ) -type f -not -type l -print0 2>/dev/null)
fi
[ -f "$MAIN_BIN" ] && strip_bitcode "$MAIN_BIN"
echo " stripped bitcode from $BC_STRIPPED binaries"
============================================================
Step 3d: 将 MH_BUNDLE 转换为 MH_DYLIB
============================================================
SCRIPT_DIR="${PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}/scripts"
FIX_PY="$SCRIPT_DIR/fix-macho-type.py"
if [ -f "$FIX_PY" ]; then
echo "wrap-loose-dylibs: flipping MH_BUNDLE → MH_DYLIB"
python3 "$FIX_PY" "$APP" 2>&1 | sed 's/^/ /'
else
echo " ⚠ fix-macho-type.py not found at $FIX_PY — MH_BUNDLE will trip validator"
fi
============================================================
Step 3e: 修复 libscipy_blas_stubs.dylib 引用
============================================================
echo "wrap-loose-dylibs: fixing references to libscipy_blas_stubs.dylib"
OLD_BLAS_REF="@rpath/libscipy_blas_stubs.dylib"
NEW_BLAS_REF="@rpath/libscipy_blas_stubs.framework/libscipy_blas_stubs"
if [ -f "$MAIN_BIN" ]; then
if otool -L "$MAIN_BIN" 2>/dev/null | grep -q "$OLD_BLAS_REF"; then
install_name_tool -change "$OLD_BLAS_REF" "$NEW_BLAS_REF" "$MAIN_BIN" 2>/dev/null && echo " updated main executable"
fi
fi
while IFS= read -r -d '' bin; do
if otool -L "$bin" 2>/dev/null | grep -q "$OLD_BLAS_REF"; then
install_name_tool -change "$OLD_BLAS_REF" "$NEW_BLAS_REF" "$bin" 2>/dev/null && echo " updated $bin"
fi
done < <(
find "$FW" -name "*.framework" -type d -print0 |
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
bin="$fw/$exe"
[ -f "$bin" ] && printf '%s\0' "$bin"
done
)
============================================================
新增: 处理 .bundle 和 lib-dynload 中的 .so 文件
============================================================
wrap_so_files() {
echo "wrap-loose-dylibs: wrapping .so files to .framework..."
}
调用 .so 文件处理函数
wrap_so_files
============================================================
Step 4: 重新签名所有被修改过的二进制和框架
============================================================
while IFS='|' read -r base new_load fw_dir new_bin; do
[ -z "$base" ] && continue
sign_bin "$new_bin"
codesign --force --sign "$IDENT" --timestamp=none "$fw_dir" 2>/dev/null || true
done < "$MAP_FILE"
while IFS= read -r -d '' fw; do
plist="$fw/Info.plist"
[ -f "$plist" ] || continue
exe=$(/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)
bin="$fw/$exe"
[ -f "$bin" ] && sign_bin "$bin"
codesign --force --sign "$IDENT" --timestamp=none "$fw" 2>/dev/null || true
done < <(find "$FW" -name "*.framework" -type d -print0)
if [ -n "$APP_PKGS" ]; then
find "$APP_PKGS" -name "*.so" -type f -not -type l -print0 2>/dev/null |
while IFS= read -r -d '' so; do
sign_bin "$so"
done
fi
[ -f "$MAIN_BIN" ] && sign_bin "$MAIN_BIN"
rm -f "$MAP_FILE"
LEFT=$(find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | wc -l | tr -d ' ')
WRAPPED=$(find "$FW" -name ".framework" -type d | wc -l | tr -d ' ')
DEEP_LEFT=0
[ -n "$APP_PKGS" ] && DEEP_LEFT=$(find "$APP_PKGS" -name ".dylib" -type f -not -type l 2>/dev/null | wc -l | tr -d ' ')
STATIC_LEFT=$(find "$FW" "$APP" -name ".a" -type f 2>/dev/null | wc -l | tr -d ' ')
echo "wrap-loose-dylibs: done — $WRAPPED frameworks, $LEFT loose top-level, $DEEP_LEFT loose in app_packages, $STATIC_LEFT static archives"
if [ "$LEFT" -gt 0 ] || [ "$DEEP_LEFT" -gt 0 ] || [ "$STATIC_LEFT" -gt 0 ]; then
echo " ⚠ remaining items the validator may reject:"
find "$FW" -maxdepth 1 ( -name ".dylib" -o -name ".so" ) -not -type l 2>/dev/null | sed 's|^| |'
[ -n "$APP_PKGS" ] && find "$APP_PKGS" -name ".dylib" -type f -not -type l 2>/dev/null | sed 's|^| |'
find "$FW" "$APP" -name "*.a" -type f 2>/dev/null | sed 's|^| |'
fi
============================================================
最终 App Store 就绪检查
============================================================
echo "=== wrap-loose-dylibs: final App Store readiness check ==="
_REJECTED=0
_check_absent() {
local label="$1"; shift
local found=()
for _path in "$@"; do
[ -e "$_path" ] && found+=("$_path")
done
if [ ${#found[@]} -gt 0 ]; then
echo " ❌ $label still present:"
for _f in "${found[@]}"; do echo " $_f"; done
_REJECTED=$((_REJECTED + ${#found[@]}))
else
echo " ✓ $label clean"
fi
}
_static_archives=()
while IFS= read -r -d '' _a; do _static_archives+=("$_a"); done < <(find "$APP" -name ".a" -type f -print0 2>/dev/null)
_check_absent "static .a archives" "${_static_archives[@]}"
_loose_dylibs=()
while IFS= read -r -d '' _d; do _loose_dylibs+=("$_d"); done < <(find "$FW" -maxdepth 1 -name ".dylib" -type f -print0 2>/dev/null)
_check_absent "loose .dylib in Frameworks/" "${_loose_dylibs[@]}"
if [ -n "$APP_PKGS" ]; then
_deep_dylibs=()
while IFS= read -r -d '' _d; do _deep_dylibs+=("$_d"); done < <(find "$APP_PKGS" -name ".dylib" -type f -not -type l -print0 2>/dev/null)
_check_absent "loose .dylib in app_packages/" "${_deep_dylibs[@]}"
fi
_TORCH_SHM_PATH="$APP/app_packages/site-packages/torch/bin/torch_shm_manager"
if [ -f "$_TORCH_SHM_PATH" ]; then
if file "$_TORCH_SHM_PATH" 2>/dev/null | grep -q "Mach-O"; then
echo " ❌ torch_shm_manager is still a Mach-O — App Store will reject"
_REJECTED=$((_REJECTED + 1))
else
echo " ✓ torch_shm_manager is text placeholder (App Store-safe)"
fi
else
echo " ⚠ torch_shm_manager missing — torch.multiprocessing won't initialize"
fi
_check_absent "manimpango native .so (CTFontCopyDefaultCascadeList)"
"$APP/app_packages/site-packages/manimpango/_register_font.cpython-314-iphoneos.so"
"$APP/app_packages/site-packages/manimpango/cmanimpango.cpython-314-iphoneos.so"
_check_absent "psutil._psutil_osx (IOPSCopyPowerSources)"
"$APP/app_packages/site-packages/psutil/_psutil_osx.abi3.so"
[ -d "$FW/latex" ] && [ ! -d "$FW/latex.framework" ] &&
echo " ❌ Frameworks/latex/ data dir still present" && _REJECTED=$((_REJECTED + 1))
[ -d "$FW/katex" ] && [ ! -d "$FW/katex.framework" ] &&
echo " ❌ Frameworks/katex/ data dir still present" && _REJECTED=$((_REJECTED + 1))
if [ "$_REJECTED" -gt 0 ]; then
echo ""
echo "❌ App Store readiness FAILED: $_REJECTED rejected file(s) still in bundle."
echo " App Store Connect will reject this upload. Fix above issues before archiving."
exit 1
fi
echo "✓ App Store readiness OK"
exit 0
打包上传,新的app freamework产生了很多小文件如下图,但是包中的.so文件没有删除
还是报 code = 90171;错误,在这个新的脚本中提交一次审核报了 code = 90210;
Invalid bundle structure. The “CustomCalculator.app/python-ios-lib_SciPy.bundle/scipy/linalg/_fblas.cpython-314-iphoneos.so” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \U201cCustomCalculator.app/python-ios-lib_SciPy.bundle/scipy/linalg/_fblas.cpython-314-iphoneos.so\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.
Missing load commands. The executable at 'CustomCalculator.app/Frameworks/numpy.framework' does not have the necessary load commands. Try rebuilding the app with the latest Xcode version. If you are using third party development tools, contact the provider.
The server’s response was: ‘{
code = 90210;
description = "Missing load commands. The executable at 'CustomCalculator.app/Frameworks/numpy.framework' does not have the necessary load commands. Try rebuilding the app with the latest Xcode version. If you are using third party development tools, contact the provider.";
}’.
yu314-coder commentedon Jun 2, 2026
Thanks for the very detailed write-up and screenshots 🙏 Four points:
1) scipy
_dcabs1_— resolved. Good.2)
blas_compat_stubs.cbuild failure ("Could not build module 'Foundation'/'UIKit'", "Module 'ObjectiveC.NSObject' requires feature 'objc'"): that's the.cfile inheriting your app's Objective-C prefix header — a pure-C translation unit can't build the ObjC system modules. Fix: rename it toblas_compat_stubs.m(Objective-C is a superset of C, so the stubs still compile), or exclude it from the prefix header. I've updated the README example to.m.3)
code = 90171(loose.so"binary file is not permitted"). Your diagnosis is exactly right — App Store requires every Mach-O to live inside a.frameworkunderFrameworks/. The issue is the SwiftPM resource-bundle layout: the.soinpython-stdlib/lib-dynload/and insidepython-ios-lib_*.bundle/are loose, and the script I'd pointed you at (wrap-loose-dylibs.sh) only covers theapp_packages/layout — that was a documentation mistake on my side.The repo already ships the script built for exactly this layout:
scripts/appstore/wrap-binaries-as-frameworks.shscripts/appstore/python_ios_lib_import_hook.pyIt wraps every
.soinlib-dynload/and the bundles intoFrameworks/*.framework, rewrites the cross-LC_LOAD_DYLIBreferences, re-signs, and a runtime import hook (MetaPathFinder) re-routesimport numpy._core…to the framework — i.e. the same thing your manually-extended script does. I had wrongly marked it "deprecated"; I've corrected the README and added an App Store Connect submission section with the full SwiftPM steps (build phase + import-hook bootstrap):https://github.com/yu314-coder/python-ios-lib#app-store-connect-submission
4) "Upload Symbols Failed: no dSYM for Python.framework" is a non-blocking warning, not a rejection — the prebuilt
Python.frameworkships without a dSYM. Untick "Upload your app's symbols" in the Distribute flow, or ignore it.Thanks again for the thorough repro — it caught a genuinely misleading bit in the docs.
baixiaochunbudanchun commentedon Jun 3, 2026
作者你好,再次感谢你的回复及技术指导,我根据你的README.md文档,使用Swift Package Manager这种方式,对项目进行修改,使用blas_compat_stubs.m方式替换libscipy_blas_stubs.dylib文件,同时我还执行了wrap-binaries-as-frameworks.sh脚本,然后项目在debug环境报如下错误。
Archive 打好的包,提交商店审核,依然被拒,产生下面三种类型的Code,
Invalid bundle structure. The “CustomCalculator.app/Frameworks/scipy-optimize-minpack.framework/scipy-optimize-minpack” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \U201cCustomCalculator.app/Frameworks/scipy-optimize-minpack.framework/scipy-optimize-minpack\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.
The binary is invalid. The executable 'CustomCalculator.app/Frameworks/scipy-interpolate-rgi-cython.framework/scipy-interpolate-rgi-cython' has type 'BUNDLE' that is not valid. Only 'EXECUTE' is permitted.
The server’s response was: ‘{
code = 90124;
description = "The binary is invalid. The executable 'CustomCalculator.app/Frameworks/scipy-interpolate-rgi-cython.framework/scipy-interpolate-rgi-cython' has type 'BUNDLE' that is not valid. Only 'EXECUTE' is permitted.";
}’.
Missing load commands. The executable at 'CustomCalculator.app/Frameworks/scipy-sparse-csgraph-traversal.framework' does not have the necessary load commands. Try rebuilding the app with the latest Xcode version. If you are using third party development tools, contact the provider.
The server’s response was: ‘{
code = 90210;
description = "Missing load commands. The executable at 'CustomCalculator.app/Frameworks/scipy-sparse-csgraph-traversal.framework' does not have the necessary load commands. Try rebuilding the app with the latest Xcode version. If you are using third party development tools, contact the provider.";
}’.
同时我还尝试使用python_ios_lib_import_hook.py脚本,但是失败,下图是我的使用方式
我还尝试先后同时运行wrap-loose-dylibs.sh 和wrap-binaries-as-frameworks.sh 脚本,打包上传应用上商店,还是被拒
还请你在项目中写一个完整的可以审核通过的 xcode OC demo让我学习,我的项目目前只导入numpy和scipy,
再次感谢你的回复与技术指导
baixiaochunbudanchun commentedon Jun 3, 2026
Run Script Wrap-binaries-as-frameworks 下面内容是我执行wrap-binaries-as-frameworks.sh的方式
#!/bin/bash
set -e
WRAP="${SRCROOT}/scripts/wrap-binaries-as-frameworks.sh"
if [ ! -f "$WRAP" ]; then
echo "error: wrap-binaries-as-frameworks.sh not found at $WRAP"
exit 1
fi
if [ -f "$WRAP" ]; then
bash "$WRAP"
fi
3. 修复所有生成的 .framework 的 CFBundleIdentifier
APP_BUNDLE_ID="${PRODUCT_BUNDLE_IDENTIFIER}" # 从 Xcode 环境自动获取
FRAMEWORKS_DIR="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"
find "$FRAMEWORKS_DIR" -name ".framework" -type d | while read fw; do
plist="$fw/Info.plist"
if [ -f "$plist" ]; then
old_id=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$plist" 2>/dev/null)
# 只修改由 wrap 脚本生成的标识符(包含 ai.codebench.pythonext)
if [[ "$old_id" == ai.codebench.pythonext. ]]; then
# 新标识符:主BundleID.pythonext.原框架名
fw_name=$(basename "$fw" .framework)
new_id="${APP_BUNDLE_ID}.pythonext.${fw_name}"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $new_id" "$plist"
echo "Fixed CFBundleIdentifier for $fw: $old_id -> $new_id"
# 重新签名(因为修改了 plist)
codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY:--}" "$fw"
fi
fi
done
baixiaochunbudanchun commentedon Jun 3, 2026
Invalid bundle structure. The “CustomCalculator.app/Frameworks/scipy-sparse-csgraph-flow.framework/scipy-sparse-csgraph-flow” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \U201cCustomCalculator.app/Frameworks/scipy-sparse-csgraph-flow.framework/scipy-sparse-csgraph-flow\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.针对这个错,添加上我应用包里面的文件结构如下
fix(appstore): wrap-binaries-as-frameworks.sh now flips MH_BUNDLE→MH_…
yu314-coder commentedon Jun 3, 2026
Thanks for the detailed repro and screenshots again — they pinpoint it exactly. All three rejection codes are the same root cause, and it was a real gap in the script I pointed you to. Apologies — that one's on me.
Root cause:
MH_BUNDLEvsMH_DYLIB. Python.soextension modules are Mach-O typeMH_BUNDLE(filetype0x8, dlopen-only). Apple's validator rejects any.frameworkwhose executable isMH_BUNDLE:MH_DYLIBMH_BUNDLEhas noLC_ID_DYLIBwrap-binaries-as-frameworks.shwrapped your.sointo.frameworks but didn't flip the Mach-O type — itsinstall_name_tool -idsilently no-ops on anMH_BUNDLE, so they shipped asBUNDLE. The repo already had the fixer (scripts/appstore/fix-macho-type.py), but the wrap script wasn't calling it. Fixed now (commit321213f5) — the wrap script runs the flip automatically before re-signing.To unblock you today, either is enough:
Option A — pull the updated script (
scripts/appstore/wrap-binaries-as-frameworks.sh, now self-contained; keepfix-macho-type.pybeside it), then re-archive.Option B — add one Run Script phase right after your wrap phase (and before Xcode's code-sign):
Verify before archiving — this should print
DYLIB, notBUNDLE:A few more notes:
CFBundleIdentifierstep: run wrap → fix-macho-type → your plist fix + codesign. The type-flip mutates the binary, so it must precede the final signature.wrap-loose-dylibs.shandwrap-binaries-as-frameworks.sh. For a SwiftPM app use onlywrap-binaries-as-frameworks.sh.Py_Initialize()and before the firstimport numpy, withPYTHON_IOS_LIB_HOOK_DIRpointing at your bundledpython-stdlib. If it still fails, paste the exact traceback and I'll look.If an archive still gets a code after the type-flip, send the
otool -hvof the offending framework binary and I'll pinpoint it.baixiaochunbudanchun commentedon Jun 4, 2026
你好,非常感谢你的耐心解答和指导,经过反复的尝试我已经将应用打包到商店并成功提交审核,这个过程还是有许多细节问题,后面我会总结一下反馈给你,但是我也收到一个新的商店反馈问题。由于时间我还没有看你的readme文档是否有该解决方案,图片中拒绝APP上架的原因
fix(appstore): add PrivacyInfo.xcprivacy for OpenSSL frameworks (ITMS…
yu314-coder commentedon Jun 4, 2026
🎉 Congrats — getting into review means the
MH_BUNDLE → MH_DYLIBflip cleared all three codes (90124/90210/90171). And yes, please send that write-up of the detail issues — I'll fold them into the README so the next person hits fewer walls.ITMS-91061 is a separate, newer requirement, not a regression. Your
_ssland_hashlibmodules statically link OpenSSL/BoringSSL, which is on Apple's commonly-used third-party SDK list. Since spring 2024, any binary bundling such an SDK must ship aPrivacyInfo.xcprivacy— so the wrappedstdlib_ssl.framework/stdlib_hashlib.frameworkeach need one.Fix — a minimal manifest (these do crypto only: no tracking, no data collection):
It must sit at each framework root inside the codesign seal (add it before signing):
stdlib_ssl.framework/PrivacyInfo.xcprivacy,stdlib_hashlib.framework/PrivacyInfo.xcprivacy.I've automated this in the repo (commit
0ed7c8ed):scripts/appstore/PrivacyInfo.xcprivacy+wrap-binaries-as-frameworks.shnow copies it into every OpenSSL-linked wrapped framework right before re-signing. So pull the update and re-archive — no manual step.Two notes:
PrivacyInfo.xcprivacy(in the app target) declaring your app's data use + any required-reason APIs your own code uses (e.g.UserDefaults→CA92.1). That's separate from these SDK manifests.Verify before upload:
unzip -l YourApp.ipa | grep xcprivacyshould list the manifests inside the frameworks.baixiaochunbudanchun commentedon Jun 5, 2026
此次回复主要感谢作者的耐心回复和精准的技术指导,解决我遇到的问题。下面我将遇到的问题、解决方法和注意事项详细列举
此次集成主要使用SPM(Swift Package Manager实现framework自动管理和分发)引入库,在三个阶段出现的问题如下(我在最后附加上我的项目配置和最终执行的文件):
第一阶段项目运行wrap-binaries-as-frameworks.sh进行编译报的错误
1.没有导出 dcabs1 / scabs1;而 cython_blas 是在 flat namespace(平面命名空间) 里查找它们的,所以真机上 dyld 一旦找不到 dcabs1 就崩溃
2.应用编译不通过
dyld[33098]: Library not loaded: @rpath/libfortran_io_stubs.dylib Referenced from: <3FB752A6-86D3-388F-AA6B-54F8425DE8DE> /private/var/containers/Bundle/Application/A05662E9-EEA6-4944-B901-3789FBBC7178/xxxx.app/xxxx
Library not loaded: @rpath/libfortran_io_stubs.dylib Referenced from: <3FB752A6-86D3-388F-AA6B-54F8425DE8DE> /private/var/containers/Bundle/Application/A05662E9-EEA6-4944-B901-3789FBBC7178/xxxx.app/xxxx
rewrite_cross_refs 函数在处理 @rpath/xxx.dylib 形式的依赖时,匹配逻辑不够强,导致引用未被更新,在我的wrap-binaries-as-frameworks.sh有修复
3.应用编译通过,python_ios_lib_import_hook.py 调用失败
Application/40902EC6-3799-4F4A-A46B-EF8E1C5AB87B/CustomCalculator.app/python-stdlib
Traceback (most recent call last): File "", line 5, in File "/private/var/containers/Bundle/Application/40902EC6-3799-4F4A-A46B-EF8E1C5AB87B/CustomCalculator.app/python-stdlib/python_ios_lib_import_hook.py", line 27 import sys, os IndentationError: unexpected indent
第二阶段应用提交审核
1应用包提交商店前出现错误(code值包括:90530、90360、90171、90124、90210)
Invalid MinimumOSVersion. Apps that only support 64-bit devices must specify a deployment target of 8.0 or later. MinimumOSVersion in 'xxxx.app/Frameworks/scipy.sparse.linalg._propack._spropack.framework' is ''.
The server’s response was: ‘{
code = 90530;
description = "Invalid MinimumOSVersion. Apps that only support 64-bit devices must specify a deployment target of 8.0 or later. MinimumOSVersion in 'xxxx.app/Frameworks/scipy.sparse.linalg._propack._spropack.framework' is ''.";
}’.
Missing Info.plist value. A value for the key 'MinimumOSVersion' in bundle xxxx.app/Frameworks/_interpqueues.framework is required.
The server’s response was: ‘{
code = 90360;
description = "Missing Info.plist value. A value for the key 'MinimumOSVersion' in bundle hLifeKup.app/Frameworks/_interpqueues.framework is required.";
}’.
Invalid bundle structure. The “hLifeKup.app/Frameworks/scipy.optimize._highspy._core.framework/scipy.optimize._highspy._core” binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle
The server’s response was: ‘{
code = 90171;
description = "Invalid bundle structure. The \xxxx.app/Frameworks/scipy.optimize._highspy._core.framework/scipy.optimize._highspy._core\U201d binary file is not permitted. Your app cannot contain standalone executables or libraries, other than a valid CFBundleExecutable of supported bundles. For details, visit: https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle";
}’.
2应用包提交到商店,在TestFlight 版本构建失败,包上传成功到构建失败时间间隔在8分钟左右会发失败邮件提醒
ITMS-90338: Non-public API usage - The app references non-public symbols in Frameworks/scipy_linalg_cython_lapack.framework/scipy_linalg_cython_lapack: xerbla_array_. If method names in your source code match the private Apple APIs listed above, altering your method names will help prevent this app from being flagged in future submissions. In addition, note that one or more of the above APIs may be located in a static library that was included with your app. If so, they must be removed. For further information, visit the Technical Support Information at http://developer.apple.com/support/technical/
ITMS-90208: Invalid Bundle - The bundle hLifeKup.app/Frameworks/loose_libscipy_blas_stubs.framework does not support the minimum OS Version specified in the Info.plist.
ITMS-90338和ITMS-90208 READMe文档有提到解决方法,
1)在wrap-loose-dylibs.sh 和patch-cython-lapack.py 解决ITMS-90338 方案,后面我在我使用的wrap-binaries-as-frameworks.sh 也会加上解决ITMS-90338的方法。
2)ITMS-90208 (细节问题)主要是设置应用适用最低版本问题,在项目的TARFETS->General->Minimun Deployments iOS ^17.0 (大于17以上版本号即可)
第三阶段审核中拒绝应用上架
ITMS-91061: Missing privacy manifest - Your app includes “Frameworks/stdlib_hashlib.framework/stdlib_hashlib”, which includes BoringSSL / openssl_grpc, an SDK that was identified in the documentation as a commonly used third-party SDK. If a new app includes a commonly used third-party SDK, or an app update adds a new commonly used third-party SDK, the SDK must include a privacy manifest file. Please contact the provider of the SDK that includes this file to get an updated SDK version with a privacy manifest. For more details about this policy, including a list of SDKs that are required to include signatures and manifests, visit: https://developer.apple.com/support/third-party-SDK-requirements
解决方发:
隐私清单(ITMS-91061)。 Python 的 _ssl / _hashlib 静态链接了 OpenSSL/BoringSSL,这是一个“常用的第三方 SDK” — 因此包装后的 stdlib_ssl.framework / stdlib_hashlib.framework 必须各自携带一个 PrivacyInfo.xcprivacy,否则 App Store Connect 会以 "Missing privacy manifest"(ITMS-91061)拒绝。包装脚本会将位于 scripts/appstore/PrivacyInfo.xcprivacy 的最小清单(无追踪、无数据收集)复制到每个链接了 OpenSSL 的 framework 中,然后重新签名 — 请将该文件放在脚本旁边。如果后续上传返回 ITMS-91053("missing required-reason API"),请将指定的原因代码添加到清单的 NSPrivacyAccessedAPITypes 数组中。
在wrap-binaries-as-frameworks.sh 的这个# =====================================================
4. Pass 3: re-sign every framework now that install_name_tool changes
位置上面添加下面代码
============================================================
3c. Privacy manifests (ITMS-91061). Any binary bundling a "commonly used
third-party SDK" must ship a PrivacyInfo.xcprivacy or App Store Connect
rejects. OpenSSL/BoringSSL is on that list and is statically linked into
Python's _ssl / _hashlib (→ stdlib_ssl/stdlib_hashlib.framework). Drop a
minimal manifest (no tracking, no data collection) into every wrapped
framework whose binary carries OpenSSL markers — BEFORE the re-sign below,
so codesign seals it into the bundle.
============================================================
PRIVACY_SRC=""
for cand in "$SCRIPT_DIR/PrivacyInfo.xcprivacy"
"${SRCROOT}/scripts/appstore/PrivacyInfo.xcprivacy"
"${SRCROOT}/scripts/PrivacyInfo.xcprivacy"; do
[ -f "$cand" ] && { PRIVACY_SRC="$cand"; break; }
done
if [ -n "$PRIVACY_SRC" ]; then
while IFS= read -r -d '' fw; do
fname=$(basename "$fw" .framework)
bin="$fw/$fname"
[ -f "$bin" ] || continue
if strings "$bin" 2>/dev/null | grep -qiE 'BoringSSL|openssl_grpc|OpenSSL [0-9]+.'
|| echo "$fname" | grep -qiE '(ssl|hashlib|crypto)'; then
cp "$PRIVACY_SRC" "$fw/PrivacyInfo.xcprivacy"
echo "wrap-binaries: privacy manifest → $fname.framework (OpenSSL — ITMS-91061)"
fi
done < <(find "$FRAMEWORKS" -name "*.framework" -type d -print0)
else
echo "warning: PrivacyInfo.xcprivacy not found beside this script or under $SRCROOT/scripts[/appstore] —"
echo " OpenSSL-linked frameworks (stdlib_ssl/stdlib_hashlib) will be rejected with ITMS-91061."
fi
=============我的项目代码及文件结构仅供参考==============
第一在OC项目中,下面是我初始化Python 的环境以及引入python_ios_lib_import_hook.py(注意脚本开始的脚本介绍要删除掉,否则会被程序执行然后报错)脚本代码
NSLog(@"🚀 [PyInit] 开始配置 Python 环境...");
NSBundle *mainBundle = [NSBundle mainBundle];
NSURL *bundleURL = [mainBundle bundleURL];
NSURL *stdlib = [bundleURL URLByAppendingPathComponent:@"python-stdlib"];
NSURL dynload = [stdlib URLByAppendingPathComponent:@"lib-dynload"];
// 自动收集所有 python-ios-lib_.bundle
NSMutableArray *libBundles = [NSMutableArray array];
NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:bundleURL.path error:nil];
for (NSString *name in contents) {
if ([name hasPrefix:@"python-ios-lib_"] && [name hasSuffix:@".bundle"]) {
[libBundles addObject:[[bundleURL URLByAppendingPathComponent:name] path]];
}
}
NSMutableString *pythonPath = [NSMutableString stringWithFormat:@"%@:%@", stdlib.path, dynload.path];
for (NSString *bundlePath in libBundles) {
[pythonPath appendFormat:@":%@", bundlePath];
}
setenv("PYTHONHOME", stdlib.path.UTF8String, 1);
setenv("PYTHONPATH", pythonPath.UTF8String, 1);
setenv("PYTHONNOUSERSITE", "1", 1);
setenv("PYTHONDONTWRITEBYTECODE", "1", 1);
setenv("PYTHONMALLOC", "malloc", 1);
setenv("TMPDIR", NSTemporaryDirectory().UTF8String, 1);
// 根据真机/模拟器选择正确的 sysconfigdata
// 真机 arm64:
setenv("_PYTHON_SYSCONFIGDATA_NAME", "_sysconfigdata__ios_arm64-iphoneos", 1);
// 若为模拟器则改为 "_sysconfigdata__ios_arm64-iphonesimulator"
setenv("PYTHON_IOS_LIB_HOOK_DIR",stdlib.path.UTF8String, 1);
setenv("PYTHONPATH", [pythonPath UTF8String], 1); // 可选,但建议
//3. 初始化解释器 ---
if (!Py_IsInitialized()) {
Py_Initialize();
NSLog(@"✅ [PyInit] Python 解释器已初始化");
const char *pythonScript = "print('Python environment is ready!')";
PyRun_SimpleString(pythonScript);
// 新添加的
NSLog(@"安装运行时 import hook的路径 stdlib path: %@", stdlib.path);
setenv("PYTHON_IOS_LIB_HOOK_DIR", stdlib.path.UTF8String, 1);
// 安装 import hook 下面格式不要动,否则影响钩子初始化
PyRun_SimpleString(
"import sys, os\n"
"hook_dir = os.environ.get('PYTHON_IOS_LIB_HOOK_DIR')\n"
"if hook_dir and os.path.isdir(hook_dir):\n"
" sys.path.insert(0, hook_dir)\n"
" import python_ios_lib_import_hook\n"
" print('🚗DEBUG: PYTHONHOME =', os.environ.get('PYTHONHOME'))\n"
" home = os.environ.get('PYTHONHOME')\n"
" bundle = os.path.dirname(home) if home else None\n"
" print('🚗DEBUG: bundle =', bundle)\n"
" manifest = os.path.join(bundle, 'python-ios-lib_extension_manifest.txt') if bundle else None\n"
" print('🚗DEBUG: manifest path =', manifest)\n"
" print('🚗DEBUG: manifest exists =', os.path.exists(manifest) if manifest else False)\n"
" python_ios_lib_import_hook.install()\n"
" print('🚗DEBUG: _INSTALLED after install =', python_ios_lib_import_hook._INSTALLED)\n"
"else:\n"
" print('Hook dir invalid')\n"
);
}
}
项目和app包文件结构如下图:
### Sign Python Binary Modules如下
#!/bin/bash
set -e
BEEWARE="${SRCROOT}/Support/Python.xcframework"
PYIOSLIB_FW="${SRCROOT}/Support/Frameworks" # adjust if needed
APP="${BUILT_PRODUCTS_DIR}/${CONTENTS_FOLDER_PATH}"
DST="$APP/python-stdlib"
FRAMEWORKS="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}"
IDENT="${EXPANDED_CODE_SIGN_IDENTITY:--}"
case "$PLATFORM_NAME" in
iphoneos) SLICE="ios-arm64" ;;
iphonesimulator) SLICE="ios-arm64_x86_64-simulator" ;;
esac
ARCH="${CURRENT_ARCH:-arm64}"; [ "$ARCH" = "undefined_arch" ] && ARCH="arm64"
1. Copy stdlib + per-arch lib-dynload + sysconfigdata.
mkdir -p "$DST"
rsync -a --delete --exclude 'pycache' --exclude 'lib-dynload'
"$BEEWARE/lib/python3.14/" "$DST/"
rsync -a --delete
"$BEEWARE/$SLICE/lib-$ARCH/python3.14/lib-dynload/" "$DST/lib-dynload/"
find "$BEEWARE/$SLICE/lib-$ARCH/python3.14" -maxdepth 1
-name "sysconfigdata_*.py" -exec cp -f {} "$DST/" ;
2. Copy standalone dylibs from python-ios-lib/Frameworks/ into app
Frameworks/ (libfortran_io_stubs.dylib for scipy.special, ffmpeg dylibs
for PyAV, etc.).
mkdir -p "$FRAMEWORKS"
if [ -d "$PYIOSLIB_FW" ]; then
while IFS= read -r -d '' src; do
base=$(basename "$src")
cp -f "$src" "$FRAMEWORKS/$base"
codesign --force --sign "$IDENT" --timestamp=none "$FRAMEWORKS/$base" 2>/dev/null || true
done < <(find "$PYIOSLIB_FW" -maxdepth 2 -name "*.dylib" -print0)
fi
2.1 复制 import hook 到 python-stdlib/
HOOK_SRC="${SRCROOT}/Support/Frameworks/python_ios_lib_import_hook.py"
if [ -f "$HOOK_SRC" ]; then
cp -f "$HOOK_SRC" "$DST/"
echo "Copied python_ios_lib_import_hook.py to $DST"
else
echo "warning: python_ios_lib_import_hook.py not found at $HOOK_SRC"
fi
3. Re-sign every .so/.dylib in stdlib + python-ios-lib_*.bundle.
sign_lib() {
codesign --force --sign "$IDENT" --timestamp=none
--preserve-metadata=identifier,entitlements,flags "$1" 2>/dev/null
|| codesign --force --sign "$IDENT" --timestamp=none "$1"
}
find "$DST/lib-dynload" -name ".so" -print0 | while IFS= read -r -d '' so; do sign_lib "$so"; done
for b in "$APP"/python-ios-lib_.bundle; do
[ -d "$b" ] || continue
while IFS= read -r -d '' lib; do sign_lib "$lib"; done
< <(find "$b" ( -name ".so" -o -name ".dylib" ) -print0)
done
4. Rewrite PyAV's hardcoded /tmp/ffmpeg-ios/ install_names → @rpath.
AV="$APP/python-ios-lib_FFmpegPyAV.bundle"
fix_names() {
for old in $(otool -L "$1" 2>/dev/null | awk '//tmp/ffmpeg-ios/{print $1}'); do
install_name_tool -change "$old" "@rpath/$(basename "$old")" "$1" 2>/dev/null || true
done
for old in $(otool -D "$1" 2>/dev/null | awk 'NR==2 && //tmp/ffmpeg-ios/{print $1}'); do
install_name_tool -id "@rpath/$(basename "$old")" "$1" 2>/dev/null || true
done
}
for lib in "$FRAMEWORKS"/libav*.dylib "$FRAMEWORKS"/libsw*.dylib; do
[ -f "$lib" ] && { fix_names "$lib"; sign_lib "$lib"; }
done
if [ -d "$AV/av" ]; then
while IFS= read -r -d '' so; do fix_names "$so"; sign_lib "$so"; done
< <(find "$AV/av" -name "*.so" -print0)
fi
Run Script Wrap-binaries-as-frameworks 如下:
set -e
WRAP="${SRCROOT}/scripts/wrap-binaries-as-frameworks.sh"
if [ ! -f "$WRAP" ]; then
echo "error: wrap-binaries-as-frameworks.sh not found at $WRAP"
exit 1
fi
bash "$WRAP"
Run Script Wrap-binaries-as-frameworks 如下:
set -e
WRAP="${SRCROOT}/scripts/wrap-binaries-as-frameworks.sh"
if [ ! -f "$WRAP" ]; then
echo "error: wrap-binaries-as-frameworks.sh not found at $WRAP"
exit 1
fi
bash "$WRAP"
fix(appstore): harden @rpath cross-ref rewrite + document field gotch…
yu314-coder commentedon Jun 5, 2026
Huge thanks for this — it's one of the best closeout write-ups I've received, and exactly what I hoped for when I asked. Congrats on getting through review. 🎉
I've folded the two net-new items from your report into the repo (commit
0217addb):1.
Library not loaded: @rpath/libfortran_io_stubs.dylib(your Stage 1.2). You're right thatrewrite_cross_refscould miss a bare@rpath/<basename>.dylibdep. I've added an on-disk fallback: when a dependency matches noRPATH_MAPentry, the script now probesFrameworks/directly for the wrappedloose_<stem>/<stem>framework and rewrites the reference to it, instead of leaving@rpath/<lib>.dylibdangling → the runtime crash. It's purely additive — it only fires when the reference is otherwise unmatched and the wrapped framework actually exists on disk, so it can't regress existing matches.2.
python_ios_lib_import_hook.py→IndentationError: unexpected indentat ~line 27 (your Stage 1.3). Nailed it — line 27 is the indented usage example inside the file's leading docstring. If the"""delimiters get dropped during a copy, that example parses as top-level code → the exact error you saw. Documented now: copy the file verbatim (or just delete the leading docstring — it's only documentation), and install via the\n-joined form rather than an indented"""…"""heredoc:Both are now in the Common gotchas table, with your report credited.
On the rest of your write-up — all already handled, so a fresh pull covers them:
0ed7c8ed): it dropsscripts/appstore/PrivacyInfo.xcprivacyinto every OpenSSL-linked framework before the re-sign pass.write_plist(it stampsMinimumOSVersion= yourIPHONEOS_DEPLOYMENT_TARGET) + theMH_BUNDLE → MH_DYLIBflip +wrap-loose-dylibs.sh/patch-cython-lapack.py. Setting the deployment target to ≥ iOS 17 as you did is the right call.dcabs1/scabs1flat-namespace crash — that's thelibscipy_blas_stubs.dylibpath (build-blas-stubs.sh), also in the gotchas table.Thanks again for documenting all three stages so thoroughly — it'll save the next person a lot of walls. 🙏