已关闭
[Bug]: setup.py 转换 shell 脚本换行后留下 write-only 权限,普通用户构建 sdist/wheel 失败 #205
肾炝喜鲤创建于  7月7日关闭于  13 天前
肾炝喜鲤
肾炝喜鲤
7月7日 创建

问题描述

当前 setup.py 在构建包前会调用 handle_shell_scripts() 转换 ascend_deployer 目录下 shell 脚本换行符。但 crlf_to_lf() 中执行:

os.chmod(file_path, stat.S_IWRITE)

后没有恢复原始读/执行权限,导致 install.sh、start_download.sh、large_scale_install.sh 等脚本被改成 write-only。普通用户执行 python setup.py sdist 或 python setup.py bdist_wheel 时,setuptools 后续复制这些脚本会因无法读取源文件而失败。

复现步骤

  1. 使用普通用户从干净仓库执行:

    python3 setup.py bdist_wheel
    
  2. 或执行:

    python3 setup.py sdist
    

实际结果

两种构建方式都会失败,错误示例:

copying ascend_deployer/install.sh -> build/lib/ascend_deployer
error: could not open 'ascend_deployer/install.sh': Permission denied

失败后检查 shell 脚本权限:

--w------- ascend_deployer/install.sh
--w------- ascend_deployer/start_download.sh
--w------- ascend_deployer/large_scale_install.sh

最小权限复现:

before 0o644
after_setup_chmod 0o550
after_crlf_to_lf_chmod 0o200
read_after_write_only=FAIL PermissionError

预期结果

建议 crlf_to_lf() 在转换换行符时保留原始权限,或在写回后恢复脚本的读/执行权限。例如:

  1. 读取并保存 file_path 原权限。
  2. 临时增加 owner write 权限完成写入。
  3. 写入完成后恢复原权限,或恢复到预期的 shell 脚本权限。
  4. 将 setup.py sdist 和 setup.py bdist_wheel 纳入打包回归验证。

影响

  • 普通用户或 CI 从源码构建发布包会失败。
  • 发布流程如果依赖源码构建 wheel/sdist,可能无法产出在线安装包。
  • 即使 root 用户构建绕过读取权限,也可能留下异常脚本权限,影响后续包内容和可执行性。

本地验证环境

代码分支:Ascend/ascend-deployer dev,HEAD 326c7da1
Python:3.11
构建命令:python3 setup.py bdist_wheel / python3 setup.py sdist
likedislike
xiangjie10成员
7月7日 评论:

👋 您好,感谢向 ascend-deployer 提交 Issue!
🎉 我们已收到您的反馈,感谢你对开源社区的支持!

📅 处理时效 维护团队将在工作日 24 小时内查看并回复您的问题。
🔍 自助排查(推荐优先查看) 在等待回复期间,您可以先查阅仓库README以及历史 Issue 中相似问题的解决方案,多数问题可快速解决。
💡 为了更快定位问题,请您确保 Issue 包含:

  • 清晰的问题描述
  • 可复现的操作步骤
  • 相关日志、截图或环境信息
    我们会尽快跟进,感谢您的理解与配合!
likedislike
xiangjie10成员
7月7日 评论:

/label add triaged

likedislike
ascend-robotascend-robot成员
7月7日 添加了label:triaged
肾炝喜鲤肾炝喜鲤
7月7日 关联了pull request:修复打包时 shell 脚本权限被改为只写
肾炝喜鲤肾炝喜鲤
7月7日 关联了pull request:确保配套版本配置随下载器打包
Wu,Qiang-RoyWu,Qiang-Roy成员
7月7日 issue状态由 TODO 改变为 Analysing
ascend-robotascend-robot成员
7月7日 关联了看板:MindStudio ISSUE管理
xuchuan成员
7月11日 评论:

/label add resolved

likedislike
ascend-robotascend-robot成员
7月11日 添加了label:resolved
ascend-robot
ascend-robot成员
7月18日 评论:

您好,当前Issue标记为resolved且有一段时间未进一步更新,因此我们将其标记为'stale'(闲置)状态。若您认为这是误操作,可通过添加任意评论来去除'stale'标签。标记为stale的Issue在4天内无更新活动将自动关闭。

likedislike
ascend-robotascend-robot成员
7月18日 添加了label:stale
肾炝喜鲤
肾炝喜鲤
7月19日 评论:

进展更新:对应权限修复见 PR !2219,已处理换行转换后的脚本权限保留并补充回归测试,当前 CLA、CI 和文档检查均已通过;后续继续跟进人工检视。

likedislike
肾炝喜鲤
肾炝喜鲤
7月19日 评论:

/label remove stale

likedislike
ascend-robotascend-robot成员
7月19日 删除了label:stale
ascend-robot
ascend-robot成员
7月26日 评论:

您好,当前Issue标记为resolved且有一段时间未进一步更新,因此我们将其标记为'stale'(闲置)状态。若您认为这是误操作,可通过添加任意评论来去除'stale'标签。标记为stale的Issue在4天内无更新活动将自动关闭。

likedislike
ascend-robotascend-robot成员
7月26日 添加了label:stale
肾炝喜鲤
肾炝喜鲤
7月29日 评论:

按父任务 #126 的检视要求,补充本 Issue 的设计文档与自验证报告。以下材料只覆盖本 Issue,不代表父任务整体已经验收。

设计文档

#126-205 脚本权限保持设计文档

目录

1. 评审目标

确认 setup.py 在转换 shell 脚本换行符时只临时增加写权限,并在正常或异常路径
恢复原始权限,使 sdist/wheel 构建不再得到 write-only 源文件。

项目 内容
父任务 #126
改进 Issue #205
对应 PR !2219
PR head 93bff630324de42ad22bb08330ff2091c8b8458d
目标分支 dev@3fef2e620246ca72879920bf8ff26e52c2f0cb91

2. 问题与根因

原 crlf_to_lf() 调用:

os.chmod(file_path, stat.S_IWRITE)

该调用覆盖整个权限位,而不是只增加 owner write,导致脚本变为 0200。
setuptools 随后无法读取脚本,普通用户构建 sdist/wheel 失败。

3. PR 设计

读取 original_mode
  -> original_mode | owner-write
  -> 写入 LF 内容
  -> finally 恢复 original_mode

实现使用 stat.S_IMODE() 保存权限,避免把文件类型位混入 chmod。测试在临时项目
中创建带 CRLF 且权限为 0550 的三份脚本,执行真实 setup.py --name,同时断言:

  • CRLF 被转换为 LF;
  • 命令成功;
  • 三份脚本权限逐项恢复为原值。

4. 正确性约束

  • 只临时增加 owner write;
  • 不删除原有 read/execute 位;
  • 写入成功或异常退出都恢复权限;
  • 不把所有文件写死为同一个固定 mode;
  • 测试不依赖私有目录或真实安装环境。

5. 验收标准

  • Python 3.8/3.10 目标测试通过;
  • setup.py sdist bdist_wheel 成功;
  • 源码和 sdist 脚本权限保持可读、可执行;
  • wheel 产物不出现 0200;
  • CLA、CI、安全与格式门禁通过。

6. 当前状态

2026-07-29 API 回读确认 !2219 open、mergeable,head 93bff630 的 CLA、文档和
主 CI 通过;#205 仍 open。代码候选已验证,但人工评审、合入和父任务验收仍待外部
完成。

7. 目标、非目标与约束

目标是在换行转换前后保持每个脚本的原始权限不变量。非目标是统一所有脚本为
0755、改变归档工具自身的权限规范化或重构 setuptools 流程。约束是成功和异常
路径都恢复权限,并兼容项目声明的 Python 范围。

8. 需求与评审意见追踪

来源 要求 决定 证据 状态
#205 构建不产生 write-only 脚本 保存、按位增加、finally 恢复 修复前后构建 候选已验证
兼容性复核 测试不依赖新语法 保持 Python 3.6 可解析 AST 语法检查 运行待补
父任务检视 设计与自验证 独立子包 本包 written artifacts complete

9. 总体架构与职责边界

flowchart LR
    F["shell 脚本 / 原 mode"] -->|读取并保存| M["original_mode"]
    M -->|临时增加 owner write| W["CRLF → LF 写入"]
    W -->|finally 恢复| R["源码原 mode"]
    R -->|交给| B["setuptools 构建"]

crlf_to_lf() 只拥有转换期间的临时权限;源码策略和归档权限由各自层负责。

10. 代表性主流程

sequenceDiagram
    participant Setup as setup.py
    participant FS as 文件系统
    participant Build as setuptools
    Setup->>FS: 读取 original_mode
    Setup->>FS: 临时增加 owner write
    Setup->>FS: 写入 LF 内容
    Setup->>FS: finally 恢复 original_mode
    Setup->>Build: 开始 sdist / wheel
    Build-->>Setup: 返回构建结果

11. 关键类或组件关系

本项是函数级修复,没有新增类或公共接口。setup.py::crlf_to_lf 操作源码文件,
测试在临时项目调用真实 setup 路径,setuptools 再读取产物。

12. 兼容、迁移与版本策略

无需配置迁移。回滚会恢复 0200 风险。源码与 sdist 应保持原 0550;wheel 工具
可能增加 owner write 形成 0750,但仍必须可读、可执行且不得为 write-only。

自验证报告

#126-205 自验证报告

目录

1. 验证基线

项目 内容
本地验证日期 2026-07-21
集成基线 dev@bcf91984
PR head 93bff630324de42ad22bb08330ff2091c8b8458d
Python 3.8.8、3.10.20
状态刷新 2026-07-29

2. 修复前复现

在未应用修复的基线执行:

python3.10 setup.py sdist bdist_wheel

观察:

构建退出码:1
错误:Permission denied: ascend_deployer/install.sh
三份目标脚本权限:0200

3. PR 目标验证

执行:

python3.8 -m pytest test/test_setup.py -q
python3.10 -m pytest test/test_setup.py -q
python3.10 setup.py sdist bdist_wheel

结果:

Python 3.8:1 passed
Python 3.10:1 passed
sdist:通过
wheel:通过

4. 产物权限

位置 权限
源码脚本 0550
sdist 脚本 0550
wheel 脚本 0750

wheel 工具为归档内脚本增加 owner write 位;三类文件均可读、可执行,不再出现
0200。

5. 兼容与门禁

将 !2219 临时叠加到验证基线后,Python 3.8/3.10 目标测试和真实构建结果一致。
变更文件的 whitespace、Ruff、format、Pylint、Bandit、codespell、typos 和
git diff --check 通过。Python 3.6 只做语法解析检查,未进行真机运行。

6. 远端状态

2026-07-29 GitCode API 回读:

Issue #205:open;标签含 resolved、stale
PR !2219:open、mergeable
head:93bff630
门禁:CLA、docs CI、主 CI passed

7. 结论与限制

提交方证据支持修复前复现、修复后测试、真实构建及产物权限闭环。PR 尚未合入,
Issue 仍 open,因此不表述为维护者已验收。

8. 验证范围

覆盖未修复基线、Python 3.8/3.10 目标测试、真实 sdist/wheel、源码和产物 mode、
静态门禁及远端 CI。未覆盖 Python 3.6 运行时、Windows 权限语义或 PR 合入。

9. 不可变环境基线

实跑固定为 2026-07-21:dev@bcf91984、!2219 93bff630,
Python 3.8.8/3.10.20、setuptools 82.0.1、wheel 0.47.0。远端状态于
2026-07-29 回读。

10. 需求到验证矩阵

要求 场景 结果 证明 未证明
复现根因 未修复构建 exit 1、0200 缺陷存在 所有平台
保持源码 mode 目标测试 0550 恢复不变量 并发构建
真实构建可用 sdist/wheel passed 工具链读取成功 合入后发布
产物可执行 归档检查 0550 / 0750 无 write-only 安装部署 E2E

11. 自验证结果总览

范围 结果 分类
未修复基线 构建失败 verified negative
Python 3.8/3.10 各 1 passed verified
sdist / wheel passed verified
PR 合入 / 验收 未发生 external-pending

12. 故障与负向链路

未修复基线直接展示权限覆盖后的失败。实现使用 finally 的设计覆盖写入异常;当前
报告保留的实跑主要证明正常转换和构建,若维护者要求应补显式注入写入异常的单测。

13. 兼容与迁移验证

Python 3.8/3.10 已运行;Python 3.6 仅语法解析。无需数据迁移;回滚会恢复原缺陷。
归档工具对 wheel mode 的调整不被误判为源码权限未恢复。

14. 限制与提交方结论

提交方结论是 !2219 在所列环境形成修复前复现、修复后测试、真实构建和权限检查
闭环。人工评审、合入和父任务验收仍待外部。

likedislike
ascend-robotascend-robot成员
7月29日 删除了label:resolvedstale
yangpeng197成员
7月29日 评论:

/label add feature

likedislike
ascend-robotascend-robot成员
7月29日 添加了label:feature
Xxuchuan成员
13 天前 issue状态由 Analysing 改变为 DONE
Xxuchuan成员
13 天前 关闭了 issue