Skip to content

fix:macOS构建.便携Python缺失wheel原生库dylib,补全agent启动依赖#277

Merged
sunyink merged 5 commits into
sunyink:mainfrom
KalimiaChen:fix/macOS-dylib-missing
May 9, 2026
Merged

fix:macOS构建.便携Python缺失wheel原生库dylib,补全agent启动依赖#277
sunyink merged 5 commits into
sunyink:mainfrom
KalimiaChen:fix/macOS-dylib-missing

Conversation

@KalimiaChen
Copy link
Copy Markdown
Contributor

@KalimiaChen KalimiaChen commented May 8, 2026

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

由 Sourcery 提供的摘要

在 CI 安装过程中,新增对 macOS 便携式 Python 动态库(dylib)的验证和修复,以防止在加载本地扩展时发生运行时失败。

错误修复:

  • 确保 macOS CI 构建包含诸如 NumPy 和 Pillow 等包所需的本地 .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:

  • Ensure macOS CI builds include required native .dylib dependencies for packages like NumPy and Pillow so the agent no longer crashes during import.

Enhancements:

  • Introduce an automated scan of installed .so files to detect missing @loader_path-linked dylibs and restore them by extracting from the corresponding wheels in the build pipeline.

Summary by CodeRabbit

发布说明

  • Bug 修复

    • 改进 macOS 便携式 Python 安装流程:清理缓存、校验本地原生依赖并在发现缺失时自动修复(下载并部署缺失的二进制库),安装过程增加可见进度与日志输出,遇到无法识别的包则跳过,完成时给出修复完成提示。
  • Chores

    • 升级构建制品的上传/下载机制以改进工件处理与包含隐藏文件的支持。

Review Change Stack

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
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 8, 2026

审阅者指南(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
Loading

类图占位符(此 PR 中无相关类型变更)

classDiagram
    class NoTypeChanges {
        <<note>>
        This_PR_does_not_modify_or_add_application_classes_or_data_models
    }
Loading

文件级变更

Change Details Files
在 CI 工作流中为便携式 Python 运行时,在 pip install 之后新增 macOS dylib 验证与修复步骤。
  • 使用 otool 扫描 site-packages 中所有 .so 文件,查找基于 @loader_path 的 dylib 依赖并检测缺失目标。
  • 按所属的 Python 包对缺失的 dylib 进行分组,并通过 importlib.metadata 确定每个包已安装的版本。
  • 使用 pip download 为当前 macOS 平台/架构和 Python 版本下载每个受影响包的匹配 wheel。
  • 从 wheel 中提取所有 .dylib 文件到 site-packages 中对应包下的 .dylibs 子目录,设置可执行权限,并记录修复进度日志。
  • 当 site-packages 不存在或未检测到缺失的 dylib 时,能优雅地快速退出。
.github/workflows/install.yml
调整发布构件中的 macOS 发布启动脚本资源路径/文件。
  • 将 release/mac 目录下的 macOS 启动命令脚本文件更新或重命名为构建产物使用的新路径/名称。
release/mac/Mac启动方案1-用户兜底修复_mac.command

技巧与命令

与 Sourcery 交互

  • 触发新的审阅: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub issue: 通过回复审阅评论来请求 Sourcery 从该评论创建一个 issue。你也可以在审阅评论中回复 @sourcery-ai issue 来从中创建 issue。
  • 生成 pull request 标题: 在 pull request 标题中任意位置写上 @sourcery-ai,即可在任意时间生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中任意位置写上 @sourcery-ai summary,即可在任意时间在你想要的位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,这会很有用。
  • 关闭所有 Sourcery 审阅: 在 pull request 中评论 @sourcery-ai dismiss,即可关闭所有现有的 Sourcery 审阅。如果你想从头开始一次新的审阅,这尤其有用——别忘了再评论 @sourcery-ai review 来触发新一轮审阅!

自定义使用体验

访问你的 控制面板 以:

  • 启用或禁用诸如 Sourcery 生成的 pull request 摘要、审阅者指南等审阅功能。
  • 更改审阅语言。
  • 添加、删除或编辑自定义审阅指令。
  • 调整其他审阅设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adds 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 CI

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
Loading

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
    }
Loading

File-Level Changes

Change Details Files
Add macOS dylib verification and repair step after pip install in the CI workflow for the portable Python runtime.
  • Scan all .so files in site-packages with otool to find @loader_path-based dylib dependencies and detect missing targets.
  • Group missing dylibs by owning Python package and determine each package’s installed version via importlib.metadata.
  • Download the matching wheel for each affected package for the current macOS platform/architecture and Python version using pip download.
  • Extract all .dylib files from the wheel into a .dylibs subdirectory inside the package in site-packages, set executable permissions, and log repair progress.
  • Short-circuit gracefully when site-packages does not exist or when no missing dylibs are detected.
.github/workflows/install.yml
Adjust macOS release launch script asset path/file in the release artifacts.
  • Update or rename the macOS launch command script file under the release/mac directory to the new path/name used by the build artifacts.
release/mac/Mac启动方案1-用户兜底修复_mac.command

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4017067-8420-416f-9b41-795af4e0e01e

📥 Commits

Reviewing files that changed from the base of the PR and between eb2dcdf and 3f1694b.

📒 Files selected for processing (1)
  • .github/workflows/install.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/install.yml

📝 Walkthrough

Walkthrough

在 macOS 的“设置便携式 Python (macOS)”步骤中,先删除 __pycache__,然后扫描 site‑packages 中的 .so 文件以检测缺失的 @loader_path dylib,若发现缺失则解析包/平台信息、下载匹配 wheel、提取 .dylib 并将其复制到对应包的 .dylibs/ 目录;同时将 artifact upload/download actions 升级到 v5 并为安装作业启用隐藏文件包含。

变更

macOS 本地库修复工作流

层级 / 文件 摘要
环境清理
.github/workflows/install.yml
删除 install/python 下的 __pycache__ 目录,为后续本地 dylib 验证准备干净环境。
Dylib 验证与修复
.github/workflows/install.yml
新增脚本:遍历 site-packages.so 文件,使用 otool -L 检测缺失 @loader_path dylib;按包分组受影响文件,解析分发名/版本与平台 tag,pip download 对应 wheel(仅二进制、无依赖),提取 .dylib 并复制到 site-packages/<pkg>/.dylibs/(权限 755),记录过程与完成信息。

Actions 版本升级

层级 / 文件 摘要
Artifact Action 升级
.github/workflows/install.yml
actions/upload-artifactactions/download-artifactv4 升级到 v5;在安装作业的上传步骤添加 include-hidden-files: true

序列图

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: 完成并记录修复统计
Loading

预期代码审查工作量

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题用中文描述了macOS构建过程中便携Python缺失wheel原生库dylib的问题和解决方案,与changeset中修改.github/workflows/install.yml添加dylib扫描与修复逻辑的主要变更高度相关。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread .github/workflows/install.yml Outdated
Comment thread .github/workflows/install.yml Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2d27adf and 27f8cef.

⛔ Files ignored due to path filters (1)
  • release/mac/Mac启动方案1-内置环境修复赋权_mac.command is excluded by none and included by none
📒 Files selected for processing (1)
  • .github/workflows/install.yml

Comment thread .github/workflows/install.yml Outdated
Comment thread .github/workflows/install.yml Outdated
Comment thread .github/workflows/install.yml Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

🤖 DeepSeek 自动评审报告

模型deepseek-v4-pro 触发workflow_dispatch Diff 截断:否 输出截断:否 改动文件:2 个(其中 0 个二进制已跳过)

概览

本次 PR 在 macOS CI 构建流程和用户端启动脚本中增加了便携 Python 原生动态库(.dylib)的缺失检测与修复逻辑,修复了 numpy、Pillow 等包因 @loader_path 依赖静默丢失而导致的 import 崩溃。整体方向正确,但引入的 shell 检测/修复部分存在严重逻辑缺陷,可能导致用户端启动脚本误执行无效的 pip install 命令。同时 CI 脚本中 pip download 的参数格式错误会导致部分 wheel 下载失败,从而无法完成修复。


阻塞性问题(必须修改)

  • 文件release/mac/Mac启动方案1-内置环境修复赋权_mac.command 中新增的 Python 内联脚本与后续 shell 判断逻辑

  • 问题:当所有 dylib 均存在时,Python 脚本输出 ✅ All native dylibs present,但后续 shell 判断 [ "$MISSING_COUNT" != "0" ] 仍为真(字符串不为零),进而进入“联网修复”分支,并将整句 ✅ All native dylibs present 当作包名传入 pip install --force-reinstall,造成无意义的失败操作。

  • 原因:内联 Python 在“无缺失”时不应向 stdout 输出非零标记;shell 对返回值语义理解与脚本实际输出不匹配。

  • 建议:修改 Python 脚本,使无缺失时只输出 0(且不输出其他文本);或将检测输出与修复标记分离(例如仅输出包列表,无缺失时输出空行),并修改 shell 判断为 [ -z "$MISSING_COUNT" ]。同时,MISSING_COUNT 变量名具有误导性,建议改为 MISSING_PACKAGES

  • 文件.github/workflows/install.yml 中内联 Python 脚本的 pip download 参数

  • 问题--python-version 参数值为 310(如 sys.version_info 拼接得到),而 pip 期望的格式为 3.103.10.5。这将导致 pip download 因参数格式错误而失败,缺失 dylib 无从修复。

  • 原因:字符串拼接方式未遵循 pip 命令行参数规范。

  • 建议:修改 py_ver = f'{sys.version_info.major}.{sys.version_info.minor}'(即 3.10),或显式引用 sys.version 的前段。例如:py_ver = f'{sys.version_info.major}.{sys.version_info.minor}'


建议改进(非阻塞)

  • 文件.github/workflows/install.ymlMac启动方案1 中的内联 Python

  • 问题:硬编码路径 install/python/lib/python3.10/site-packages,若将来 Python 版本升级(如 3.11)或目录结构调整,该路径失效。

  • 原因:未使用 sys.pathsite.getsitepackages() 动态获取 site-packages 目录,依赖假定版本。

  • 建议:改为 site = Path(sysconfig.get_paths()["purelib"])site = Path(next(p for p in sys.path if p.endswith('site-packages'))),使脚本可兼容未来 Python 版本。

  • 文件.github/workflows/install.yml

  • 问题subprocess.check_call(... stdout=DEVNULL, stderr=DEVNULL) 完全静默,若 pip download 异常,无法在 CI 日志中看到任何错误信息,难以排查。

  • 原因:错误输出被丢弃。

  • 建议:至少保留 stderr 输出,或使用 subprocess.run 并捕获输出,失败时打印。例如:subprocess.check_call(..., stdout=DEVNULL) 保留 stderr 默认输出。

  • 文件Mac启动方案1-内置环境修复赋权_mac.command

  • 问题:在 while read pkg_ver 循环中直接 pip install,未处理单次安装失败的情况。

  • 原因:可能某个包安装失败但脚本继续,最终输出“修复完成”,掩盖问题。

  • 建议:检查 pip install 返回值,或在循环后汇总失败列表。

  • 文件:两处新增脚本

  • 问题:代码风格上,内联 Python 缩进混杂(YAML 多行字符串中),可读性较低。

  • 原因:长段脚本直接内联在 YAML 中。

  • 建议:若复杂度过高,考虑将脚本抽成独立 .py 文件,通过 $PY_EXEC 直接调用,提升可维护性。


疑问 / 需要作者确认

  • install.yml 中修复步骤仅从 wheel 中提取全部 .dylib 文件,并未使用 broken 字典中具体的缺失名称列表(仅记录了缺失名称但未用于筛选提取的 dylib)。这是有意为之(考虑部分 dylib 可能缺失时同时补全所有 dylib 以保证一致性),还是未完成的具体补全逻辑?若有意为之,建议添加注释说明意图。

本评论由 GitHub Actions + DeepSeek 自动生成;最终判断以人工审查为准。

KalimiaChen and others added 4 commits May 8, 2026 22:01
- 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
@sunyink sunyink merged commit 82160f6 into sunyink:main May 9, 2026
8 checks passed
sunyink added a commit that referenced this pull request May 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants