fix:macOS构建.便携Python缺失wheel原生库dylib,补全agent启动依赖#277
Conversation
CI 构建 macOS 包时, python-build-standalone 便携 Python 的 pip install 未能提取 wheel 内的 .dylibs 目录, 导致 numpy/Pillow 等包加载失败, agent 进程在 import 阶段崩溃, MFAAvalonia 报 Failed to LinkStart. 修复: - install.yml: pip install 后扫描所有 .so 的 @loader_path 依赖, 缺失的 dylib 从对应 wheel 中自动提取补回 - Mac启动方案1: 新增 Python 原生库检查步骤, 用户端兜底修复 Co-Authored-By: Claude Code
审阅者指南(Reviewer's Guide)在 CI 工作流中新增了一个 macOS 专用的安装后验证步骤,用于检测来自便携式 Python wheel 安装中缺失的本地 dylib,并通过从对应 wheel 中提取 dylib 进行修复,同时更新了 macOS 启动脚本的资源路径。 CI 中用于 macOS dylib 验证与修复的时序图sequenceDiagram
actor Dev as Developer
participant GH as GitHubActions
participant CI as macOS_runner
participant PY as Portable_Python
participant VR as VerifyRepair_dylibs_script
participant PIP as PyPI_index
Dev->>GH: Push code / open PR
GH->>CI: Start macOS build job
CI->>PY: Run pip install dependencies
CI->>PY: Clean pip cache and __pycache__
CI->>VR: Run Python one-off script
VR->>PY: Scan site_packages for *.so
loop For each .so
VR->>CI: Call otool -L so_file
CI-->>VR: otool output with @loader_path deps
VR->>VR: Resolve @loader_path to expected .dylib paths
alt dylib missing
VR->>VR: Record package and missing dylib name
end
end
alt No packages with missing dylibs
VR-->>CI: Print All native dylibs present
else Some packages have missing dylibs
VR-->>CI: Print packages with missing dylibs
VR->>VR: Detect platform and python_version
loop For each affected package
VR->>PY: Read installed package version via importlib.metadata
VR->>PIP: pip download pkg==version (platform-specific wheel)
PIP-->>VR: Wheel file (.whl)
VR->>VR: Open wheel as zip
VR->>VR: Find .dylib entries
alt dylibs found
VR->>PY: Create .dylibs folder under package
VR->>PY: Extract/copy .dylib files and chmod 755
else No dylibs in wheel
VR-->>CI: Log info: No dylibs in wheel
end
end
VR-->>CI: Print Dylib repair complete
end
CI-->>GH: Build artifacts with repaired portable Python
类图占位符(此 PR 中无相关类型变更)classDiagram
class NoTypeChanges {
<<note>>
This_PR_does_not_modify_or_add_application_classes_or_data_models
}
文件级变更
技巧与命令与 Sourcery 交互
自定义使用体验访问你的 控制面板 以:
获取帮助Original review guide in EnglishReviewer's GuideAdds a macOS-specific post-install verification step in the CI workflow to detect missing native dylibs from portable Python wheel installs and repair them by extracting the dylibs from the corresponding wheels, plus updates to the macOS launch script asset path. Sequence diagram for macOS dylib verification and repair in CIsequenceDiagram
actor Dev as Developer
participant GH as GitHubActions
participant CI as macOS_runner
participant PY as Portable_Python
participant VR as VerifyRepair_dylibs_script
participant PIP as PyPI_index
Dev->>GH: Push code / open PR
GH->>CI: Start macOS build job
CI->>PY: Run pip install dependencies
CI->>PY: Clean pip cache and __pycache__
CI->>VR: Run Python one-off script
VR->>PY: Scan site_packages for *.so
loop For each .so
VR->>CI: Call otool -L so_file
CI-->>VR: otool output with @loader_path deps
VR->>VR: Resolve @loader_path to expected .dylib paths
alt dylib missing
VR->>VR: Record package and missing dylib name
end
end
alt No packages with missing dylibs
VR-->>CI: Print All native dylibs present
else Some packages have missing dylibs
VR-->>CI: Print packages with missing dylibs
VR->>VR: Detect platform and python_version
loop For each affected package
VR->>PY: Read installed package version via importlib.metadata
VR->>PIP: pip download pkg==version (platform-specific wheel)
PIP-->>VR: Wheel file (.whl)
VR->>VR: Open wheel as zip
VR->>VR: Find .dylib entries
alt dylibs found
VR->>PY: Create .dylibs folder under package
VR->>PY: Extract/copy .dylib files and chmod 755
else No dylibs in wheel
VR-->>CI: Log info: No dylibs in wheel
end
end
VR-->>CI: Print Dylib repair complete
end
CI-->>GH: Build artifacts with repaired portable Python
Class diagram placeholder (no relevant type changes in this PR)classDiagram
class NoTypeChanges {
<<note>>
This_PR_does_not_modify_or_add_application_classes_or_data_models
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthrough在 macOS 的“设置便携式 Python (macOS)”步骤中,先删除 变更macOS 本地库修复工作流
Actions 版本升级
序列图sequenceDiagram
participant Workflow
participant Otool
participant Pip
participant Wheel
participant SitePackages
Workflow->>SitePackages: 列出 site-packages 下的 .so 文件
Workflow->>Otool: 对每个 .so 运行 otool -L
Otool-->>Workflow: 返回依赖列表(含缺失项)
Workflow->>Workflow: 按包分组并解析 dist 名称/版本与平台 tag
Workflow->>Pip: python -m pip download wheel(--only-binary --no-deps)
Pip-->>Wheel: 下载并提供 wheel 文件
Workflow->>Wheel: 解压并提取 .dylib 文件
Workflow->>SitePackages: 复制 .dylib 到 site-packages/<pkg>/.dylibs/ 并设为 755
Workflow-->>SitePackages: 完成并记录修复统计
预期代码审查工作量🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - 我发现了 2 个问题,并给出了一些整体性的反馈:
- 内联修复脚本对
python3.10和单一的 macOS platform tag 进行了硬编码;建议从当前运行的解释器和packaging.tags动态推导 site-packages 路径以及 wheel 的 platform tags,这样在 Python 版本或部署目标变更时依然能保持正确。 - 当前所有
pip download的输出都被屏蔽,异常信息也没有清晰暴露;在 wheel 无法下载或解包时记录一条简明的错误日志,会大大提升诊断 dylib 修复失败原因的效率。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- 内联修复脚本对 `python3.10` 和单一的 macOS platform tag 进行了硬编码;建议从当前运行的解释器和 `packaging.tags` 动态推导 site-packages 路径以及 wheel 的 platform tags,这样在 Python 版本或部署目标变更时依然能保持正确。
- 当前所有 `pip download` 的输出都被屏蔽,异常信息也没有清晰暴露;在 wheel 无法下载或解包时记录一条简明的错误日志,会大大提升诊断 dylib 修复失败原因的效率。
## Individual Comments
### Comment 1
<location path=".github/workflows/install.yml" line_range="541-546" />
<code_context>
+import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata
+from pathlib import Path
+
+site = Path('install/python/lib/python3.10/site-packages')
+if not site.exists():
+ sys.exit(0)
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 在 site-packages 路径中硬编码 `python3.10`,在嵌入式 Python 版本变化时可能会出问题。
当捆绑的 Python 升级后,这个路径会变成陈旧路径,修复步骤实际上会变成空操作。建议从运行时动态推导 site-packages 路径(例如通过 `sys.version_info` 或 `sysconfig.get_paths()['purelib']`),以确保它始终与嵌入的解释器匹配。
```suggestion
import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata, sysconfig
from pathlib import Path
site = Path(sysconfig.get_paths()["purelib"])
if not site.exists():
sys.exit(0)
```
</issue_to_address>
### Comment 2
<location path=".github/workflows/install.yml" line_range="561-562" />
<code_context>
+ ref = m.group(1)
+ resolved = (so.parent / ref.replace('@loader_path/', '')).resolve()
+ if not resolved.exists():
+ pkg = so.relative_to(site).parts[0]
+ broken.setdefault(pkg, set()).add(os.path.basename(ref))
+
+if not broken:
</code_context>
<issue_to_address>
**suggestion:** 从路径的第一个组件直接推导包名,可能与 `importlib.metadata` 使用的 distribution name 不一致。
有些包的顶级目录名与它们的 distribution 名称并不匹配(例如 `Pillow`→`PIL`,`google_cloud_storage` vs `google`),因此这里的 `pkg` 可能不是 `pip`/`importlib.metadata.version` 可用的有效名称,从而导致相关 wheel 的下载/修复被跳过。与其假设第一个路径组件就是 distribution 名称,不如为每个 `.so` 解析其归属的 distribution(例如通过 `importlib.metadata.packages_distributions()` 或维护一张映射表)。
建议的实现:
```
resolved = (so.parent / ref.replace('@loader_path/', '')).resolve()
if not resolved.exists():
try:
rel = so.relative_to(site)
except ValueError:
# if the .so is not under site, we can't reliably determine the owning dist
continue
top_level = rel.parts[0]
# map top-level package directory to an owning distribution name
dist_name = package_for_path.get(top_level, top_level)
broken.setdefault(dist_name, set()).add(os.path.basename(ref))
```
要完整落实该建议,你还需要:
1. 确保在该 workflow 中的 Python 片段里导入了 `importlib.metadata`,例如在其它 import 附近:
- `import importlib.metadata as importlib_metadata`
2. 在遍历 `.so` 文件的这个循环之前(并且在同一个 Python 片段中),预先构建一次 `package_for_path` 映射:
- `packages_dists = importlib_metadata.packages_distributions()`
- `package_for_path = {pkg: dists[0] for pkg, dists in packages_dists.items() if dists}`
3. 如果该 Python 片段会被多次调用或在不同的环境中执行,建议用 `try/except` 包裹 `packages_distributions` 调用,以兼容不提供该 API 的旧 Python 版本;或者在这种情况下回退到之前使用 `so.relative_to(site).parts[0]` 的行为。
</issue_to_address>Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Original comment in English
Hey - I've found 2 issues, and left some high level feedback:
- The inline repair script hardcodes
python3.10and a single macOS platform tag; consider deriving the site-packages path and wheel platform tags from the running interpreter andpackaging.tagsso it remains correct if Python or deployment targets change. - Currently all
pip downloadoutput is suppressed and exceptions are not surfaced clearly; logging a concise error when a wheel cannot be downloaded or unpacked would make diagnosing dylib repair failures much easier.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The inline repair script hardcodes `python3.10` and a single macOS platform tag; consider deriving the site-packages path and wheel platform tags from the running interpreter and `packaging.tags` so it remains correct if Python or deployment targets change.
- Currently all `pip download` output is suppressed and exceptions are not surfaced clearly; logging a concise error when a wheel cannot be downloaded or unpacked would make diagnosing dylib repair failures much easier.
## Individual Comments
### Comment 1
<location path=".github/workflows/install.yml" line_range="541-546" />
<code_context>
+import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata
+from pathlib import Path
+
+site = Path('install/python/lib/python3.10/site-packages')
+if not site.exists():
+ sys.exit(0)
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Hardcoding `python3.10` in the site-packages path may break when the embedded Python version changes.
When the bundled Python is upgraded, this path will become stale and the repair step will effectively no-op. Consider deriving the site-packages path from the runtime (e.g., via `sys.version_info` or `sysconfig.get_paths()['purelib']`) so it always matches the embedded interpreter.
```suggestion
import os, sys, subprocess, tempfile, zipfile, shutil, re, importlib.metadata, sysconfig
from pathlib import Path
site = Path(sysconfig.get_paths()["purelib"])
if not site.exists():
sys.exit(0)
```
</issue_to_address>
### Comment 2
<location path=".github/workflows/install.yml" line_range="561-562" />
<code_context>
+ ref = m.group(1)
+ resolved = (so.parent / ref.replace('@loader_path/', '')).resolve()
+ if not resolved.exists():
+ pkg = so.relative_to(site).parts[0]
+ broken.setdefault(pkg, set()).add(os.path.basename(ref))
+
+if not broken:
</code_context>
<issue_to_address>
**suggestion:** Deriving the package name from the first path component can misalign with the distribution name used by `importlib.metadata`.
Some packages’ top-level directories don’t match their distribution names (e.g. `Pillow`→`PIL`, `google_cloud_storage` vs `google`), so `pkg` here may not be a valid name for `pip`/`importlib.metadata.version`, which could cause downloads/repairs to be skipped. Instead of assuming the first path component is the distribution name, resolve the owning distribution for each `.so` (e.g. via `importlib.metadata.packages_distributions()` or a maintained mapping).
Suggested implementation:
```
resolved = (so.parent / ref.replace('@loader_path/', '')).resolve()
if not resolved.exists():
try:
rel = so.relative_to(site)
except ValueError:
# if the .so is not under site, we can't reliably determine the owning dist
continue
top_level = rel.parts[0]
# map top-level package directory to an owning distribution name
dist_name = package_for_path.get(top_level, top_level)
broken.setdefault(dist_name, set()).add(os.path.basename(ref))
```
To fully implement the suggestion, you should also:
1. Ensure `importlib.metadata` is imported in the Python snippet in this workflow, e.g. near the other imports:
- `import importlib.metadata as importlib_metadata`
2. Before this loop over `.so` files (and in the same Python snippet), build the `package_for_path` mapping once:
- `packages_dists = importlib_metadata.packages_distributions()`
- `package_for_path = {pkg: dists[0] for pkg, dists in packages_dists.items() if dists}`
3. If the Python snippet is invoked multiple times or in different environments, consider guarding the `packages_distributions` call with a `try/except` to handle older Python versions that might not provide it, or fall back to the previous `so.relative_to(site).parts[0]` behavior in that case.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
.github/workflows/install.yml (2)
544-544: 💤 Low value建议:硬编码
python3.10站点路径与 Windows 矩阵中py_ver_short=3.11的设计不一致,未来 macOS 也升级 Python 时易遗漏。虽然当前两条 macOS 矩阵都用 cpython-3.10.13 standalone,路径暂时正确,但站点目录名与 standalone 包版本强耦合却写死在 Python 代码里,且与 Windows 侧已经把
py_ver_short抽进 matrix 的做法不一致。建议从sys直接派生路径,避免日后只升standalone_url而漏改这里:-site = Path('install/python/lib/python3.10/site-packages') +site = Path(f'install/python/lib/python{sys.version_info.major}.{sys.version_info.minor}/site-packages')由于这里
sys.executable就是便携 Python 自身,sys.version_info是可信的。🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/install.yml at line 544, The hard-coded site path "install/python/lib/python3.10/site-packages" should be derived from the portable Python runtime instead of a literal "3.10"; update the code that sets the site variable (currently site = Path('install/python/lib/python3.10/site-packages')) to compute the correct site-packages directory using sys.executable or sys.version_info (e.g., determine the interpreter's lib path and version at runtime) so the path automatically matches the actual bundled Python version and stays consistent with the Windows matrix's py_ver_short approach.
582-587: ⚡ Quick win建议:不要把 pip download 的 stdout/stderr 全部丢到 DEVNULL。
第 587 行
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL会把 pip 在解析平台 tag、找不到 wheel、网络失败时的关键诊断信息一并吞掉,最终只能看到CalledProcessError抛栈,无法判断是 distribution 名错、平台 tag 不匹配还是源访问问题。CI 修复脚本失败时的可观测性几乎为零。建议改为
subprocess.run(..., capture_output=True, text=True),失败时把result.stderr完整打印出来再退出,或干脆让其继承 stdout/stderr 直出到 Action 日志(因为这一步本身就只在broken非空时触发,输出量不大)。♻️ 建议改写
- subprocess.check_call([ - sys.executable, '-m', 'pip', 'download', - f'{pkg}=={ver}', '--no-deps', '--dest', tmp, - '--platform', plat, '--python-version', py_ver, - '--only-binary=:all:', '--no-cache-dir', - ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + result = subprocess.run([ + sys.executable, '-m', 'pip', 'download', + f'{pkg}=={ver}', '--no-deps', '--dest', tmp, + '--platform', plat, '--python-version', py_ver, + '--only-binary=:all:', '--no-cache-dir', + ], capture_output=True, text=True) + if result.returncode != 0: + print(f' ⚠️ pip download failed for {pkg}=={ver}:') + print(result.stderr) + continue🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/install.yml around lines 582 - 587, The current subprocess.check_call invocation that sets stdout=subprocess.DEVNULL and stderr=subprocess.DEVNULL swallows pip diagnostic output; replace this call with subprocess.run(..., capture_output=True, text=True) and check the return (or use check=True) so that on failure you print result.stderr (and result.stdout if helpful) before exiting, or alternatively remove the stdout/stderr redirection so the subprocess inherits the workflow logs; target the subprocess.check_call([...], stdout=..., stderr=...) call in the diff and implement the run + error-logging approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/install.yml:
- Around line 540-605: 内嵌的 Python 脚本在 "$PY_EXEC" -c " ... " 的 YAML
块标量中未按块的缩进基线(约 10 个空格)对齐,导致 YAML 在解析时提前终止并报错;把整个 Python 多行字符串(从 import os, sys,
... 到最后的 print('✅ Dylib repair complete'))整体右移,使每行的前导空格与包含它的行(即 "$PY_EXEC" -c
")的缩进对齐,或将该段替换为以 heredoc(例如 <<'PYEOF' ... PYEOF)并通过 python -/"$PY_EXEC"
调用以避免引号和缩进问题;确保修改涵盖所有在该区域的行(包括 try/except/for/with/print 等语句)以恢复合法的 YAML
块标量解析并保持 Python 语义不变。
- Around line 569-571: The hardcoded macOS wheel tag logic using
platform.machine() and the plat variable can miss valid wheel tags; replace it
with runtime-derived mac platform tags (e.g., call
packaging.tags.mac_platforms() or run pip debug --verbose) and iterate over that
ordered list when invoking pip download until one succeeds, using the same loop
to try multiple candidate tags per architecture rather than a single exact tag;
also stop silencing errors with subprocess.DEVNULL so download failures surface
(adjust the code around platform.machine(), the plat assignment, and the pip
download invocation to implement the iterative tag-try and error-reporting
behavior).
- Around line 561-587: The code currently sets pkg =
so.relative_to(site).parts[0] (an import/top-level package name like "PIL") and
then uses importlib.metadata.version(pkg) and pip download pkg, which fails
because the PyPI distribution name is different; change the flow to map the
top-level import name to its distribution name using
importlib.metadata.packages_distributions() (available in Python 3.10+), look up
distributions = packages_distributions().get(pkg, []) and if empty skip with a
clear message, otherwise choose the appropriate distribution name (e.g., first
entry) and pass that distribution name to
importlib.metadata.version(distribution_name) and to pip download; also narrow
the exception handling around importlib.metadata.version to catch
PackageNotFoundError (or handle empty mapping) instead of swallowing all
exceptions so only missing-distribution cases are skipped.
---
Nitpick comments:
In @.github/workflows/install.yml:
- Line 544: The hard-coded site path
"install/python/lib/python3.10/site-packages" should be derived from the
portable Python runtime instead of a literal "3.10"; update the code that sets
the site variable (currently site =
Path('install/python/lib/python3.10/site-packages')) to compute the correct
site-packages directory using sys.executable or sys.version_info (e.g.,
determine the interpreter's lib path and version at runtime) so the path
automatically matches the actual bundled Python version and stays consistent
with the Windows matrix's py_ver_short approach.
- Around line 582-587: The current subprocess.check_call invocation that sets
stdout=subprocess.DEVNULL and stderr=subprocess.DEVNULL swallows pip diagnostic
output; replace this call with subprocess.run(..., capture_output=True,
text=True) and check the return (or use check=True) so that on failure you print
result.stderr (and result.stdout if helpful) before exiting, or alternatively
remove the stdout/stderr redirection so the subprocess inherits the workflow
logs; target the subprocess.check_call([...], stdout=..., stderr=...) call in
the diff and implement the run + error-logging approach.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 89c9ebba-3668-4c21-89b2-f0b8a2e79419
⛔ Files ignored due to path filters (1)
release/mac/Mac启动方案1-内置环境修复赋权_mac.commandis excluded by none and included by none
📒 Files selected for processing (1)
.github/workflows/install.yml
🤖 DeepSeek 自动评审报告模型: 概览本次 PR 在 macOS CI 构建流程和用户端启动脚本中增加了便携 Python 原生动态库( 阻塞性问题(必须修改)
建议改进(非阻塞)
疑问 / 需要作者确认
本评论由 GitHub Actions + DeepSeek 自动生成;最终判断以人工审查为准。 |
- YAML缩进全改为空格,修复block scalar解析断裂 - 目录名->分发名映射(PIL->Pillow),三级大小写回退 - platform tag改用sysconfig动态推导,替换-和. - pip --python-version改为3.10格式 - 启动脚本:无缺失时输出空,shell用-n判空 - pip download错误不再吞,失败时打印stderr - 注释说明全量提取dylib的原因(ABI一致性) Co-Authored-By: Claude Code
Co-Authored-By: Claude Code
CI 构建 macOS 包时, python-build-standalone 便携 Python 的 pip install 未能提取 wheel 内的 .dylibs 目录, 导致 numpy/Pillow 等包加载失败,
agent 进程在 import 阶段崩溃, MFAAvalonia 报 Failed to LinkStart.
修复:
Co-Authored-By: Claude Code
由 Sourcery 提供的摘要
在 CI 安装过程中,新增对 macOS 便携式 Python 动态库(dylib)的验证和修复,以防止在加载本地扩展时发生运行时失败。
错误修复:
.dylib依赖项,从而避免代理在导入时崩溃。增强功能:
.so文件的自动扫描,以检测缺失的、通过@loader_path链接的 dylib,并通过从构建流水线中的对应 wheel 包中提取来恢复这些依赖。Original summary in English
Summary by Sourcery
Add macOS portable Python dylib verification and repair during CI installation to prevent runtime failures when loading native extensions.
Bug Fixes:
Enhancements:
Summary by CodeRabbit
发布说明
Bug 修复
Chores