已合并
build: 回退打包期权限收紧,交回 cmake 默认值 #504
build: 回退打包期权限收紧,交回 cmake 默认值 #504
已合并
sinat_31531339创建于 26 天前
4 个文件变更+277-31
@@ -285,17 +285,13 @@ if(EXISTS "${BUNDLE_PKG}/aml")
285endif()285endif()
286 286 
287# 拷贝 built-in 目录287# 拷贝 built-in 目录
288-# 显式指定 440 文件权限 / 555 目录权限,与主线 CANN 包对 opp/built-in/op_impl 子树288+# 文件显式 440(owner/group 只读),与主线 CANN 包对 opp/built-in/op_impl 子树一致;
289-# 权限模型对齐。否则 cmake install(DIRECTORY) 默认会把文件重写成 644、目录 755。289+# 目录不再显式声明,交回 cmake install(DIRECTORY) 默认 755。
290-# 注:文件 440(owner/group r)+ 目录 555(world rx)这个不对称组合是有意为之 ——
291-# 主线 CANN 包对该子树就是此模型(other 可遍历目录但读不到文件内容),
292-# 切勿轻易改成对称形式(如全 550 / 全 444),会破坏与主线的兼容性。
293if(EXISTS "${BUNDLE_PKG}/built-in")290if(EXISTS "${BUNDLE_PKG}/built-in")
294 install(DIRECTORY ${BUNDLE_PKG}/built-in/op_impl/ai_core/tbe291 install(DIRECTORY ${BUNDLE_PKG}/built-in/op_impl/ai_core/tbe
295 DESTINATION opp/built-in/op_impl/ai_core292 DESTINATION opp/built-in/op_impl/ai_core
296 COMPONENT oam-tools293 COMPONENT oam-tools
297 FILE_PERMISSIONS OWNER_READ GROUP_READ294 FILE_PERMISSIONS OWNER_READ GROUP_READ
298- DIRECTORY_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
299 PATTERN "op_api" EXCLUDE295 PATTERN "op_api" EXCLUDE
300 )296 )
301 install(FILES297 install(FILES
@@ -275,7 +275,7 @@ add_dependencies(msprofbin msprofbin_proto)
275set(msprof_whl275set(msprof_whl
276 ${CMAKE_CURRENT_SOURCE_DIR}/msprof-0.0.1-py3-none-any.whl276 ${CMAKE_CURRENT_SOURCE_DIR}/msprof-0.0.1-py3-none-any.whl
277)277)
278-# whl 进包权限 555,等价旧 install(CODE) 的 chmod -R 555安装期终态,不影响重解包)。278+# whl 自身保留显式 555(文件权限,不影响目录可删除性,与历史终态一致
279install(PROGRAMS ${msprof_whl}279install(PROGRAMS ${msprof_whl}
280 DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool280 DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool
281 ${INSTALL_OPTIONAL}281 ${INSTALL_OPTIONAL}
@@ -305,13 +305,15 @@ if(EXISTS "${msprof_whl}")
305 # 绑定主构建目标,保证 install 依赖的解包产物在构建后就绪。305 # 绑定主构建目标,保证 install 依赖的解包产物在构建后就绪。
306 add_dependencies(msprofbin msprof_whl_extract)306 add_dependencies(msprofbin msprof_whl_extract)
307 307 
308- # 权限仅在安装期声明 555,等价旧 install(CODE)安装期 chmod 555308+ # 文件权限保留显式 555,与运行期 msprof_install.shchange_file_mode 555 一致,
309+ # 使 --noexec --extract 旁路(不跑安装脚本)下的文件权限不退回 644。
310+ # 目录权限不声明,交回 cmake 默认的 755:unlink 只看父目录写位,目录带 owner
311+ # 写位才能让 build/ 被 rm -rf 删除(文件无写位不阻塞删除)。
309 install(DIRECTORY "${_msprof_extracted_dir}/"312 install(DIRECTORY "${_msprof_extracted_dir}/"
J
Jjinyingqi26 天前

问题:这里删除 FILE_PERMISSIONS 后,预解包进包的 profiler_tool 文件会从原来的 555 退回 CMake 默认 644;但本 PR 要解决的是目录缺 owner 写位导致 build 无法删除,文件无写位并不会阻塞 rm -rf。

  • 严重程度:major
  • 影响:--noexec --extract 或仅解包检查包内容的场景下,profiler_tool 文件权限将与完整安装后 msprof_install.sh 收口到 555 的终态不一致,并可能丢失脚本/工具文件的可执行位;同时这属于超出目录可删除性问题范围的行为回退。
  • 修改建议:仅回退目录权限收紧:保留 install(DIRECTORY) 的 FILE_PERMISSIONS 555,删除 DIRECTORY_PERMISSIONS 或将目录权限改为包含 OWNER_WRITE 的 755;whl 自身如需保持历史终态,也应保留显式 555 文件权限。
likedislike
sinat_31531339
sinat_31531339
25 天前 评论:
sinat_31531339
sinat_31531339
25 天前 评论:
310 DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool313 DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool
311 ${INSTALL_OPTIONAL}314 ${INSTALL_OPTIONAL}
312 COMPONENT oam-tools315 COMPONENT oam-tools
313 FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE316 FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
314- DIRECTORY_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
315 PATTERN ".extracted" EXCLUDE317 PATTERN ".extracted" EXCLUDE
316 )318 )
317endif()319endif()
@@ -0,0 +1,244 @@
1+#!/usr/bin/env python3
zhangjie
zhangjiezhangjie26 天前

级别:提示 问题:版权声明年份为 2026,与当前实际不符。 影响:不影响功能,但版权信息不准确可能在合规审查时被标记。 修复建议:将 Copyright (c) 2026 改为实际年份。

likedislike
sinat_31531339
sinat_31531339
25 天前 评论:
2+# -*- coding: utf-8 -*-
3+# ----------------------------------------------------------------------------
4+# Copyright (c) 2026 Huawei Technologies Co., Ltd.
5+#
6+# Licensed under the Apache License, Version 2.0 (the "License");
7+# you may not use this file except in compliance with the License.
8+# You may obtain a copy of the License at
9+#
10+# http://www.apache.org/licenses/LICENSE-2.0
11+#
12+# Unless required by applicable law or agreed to in writing, software
13+# distributed under the License is distributed on an "AS IS" BASIS,
14+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+# See the License for the specific language governing permissions and
16+# limitations under the License.
17+# ----------------------------------------------------------------------------
18+ 
19+"""看护"编译产物目录在编译后可被删除"。
20+ 
21+背景:打包声明若把目录权限收紧到 555(r-xr-xr-x,无 owner 写位),这些目录会
22+连同 build/_CPack_Packages/ 下的 staging 子树一起落地。unlink 一个条目要的是
23+**父目录**的写位,父目录缺 u+w 就删不掉里面的内容——于是 `rm -rf build` 全程
24+Permission denied。这与 root 无关:目录属主本就是当前用户,属主自己也删不掉。
25+ 
26+后果是 CI 随机失败:流水线复用工作区,下一轮清理残留 build/ 时删不掉即报错,
27+失败与否取决于上一轮是否跑到了产出 555 目录的打包阶段,故表现为随机。
28+ 
29+因此打包期不再声明去掉 owner 写位的目录权限(当前做删除处理);权限收紧的
30+整改由后续需求统一处理,届时需连带解决产物目录的可删除性。
31+ 
32+本用例两层看护:
33+1. 静态扫描仓内 cmake 声明,禁止再引入"目录权限缺 owner 写位"的声明;
34+2. 验证机制本身——缺 u+w 的目录树删不掉,补上 u+w 后可删,对应 build.sh
35+ 里 safe_rm_dir 先 chmod -R u+w 再 rm 的兜底做法。
36+"""
37+ 
38+import os
39+import re
40+import shutil
41+import stat
42+from pathlib import Path
43+ 
44+import pytest
45+ 
46+ 
47+# 仓库根标记:这三者同时存在才认定为 oam-tools 仓库根。
48+REPO_ROOT_MARKERS = ("CMakeLists.txt", "build.sh", "cmake")
49+ 
50+ 
51+def find_repo_root():
52+ """从本文件向上搜索仓库根,找不到返回 None。
53+ 
54+ 不按固定层级数推算:用例文件可能被拷到不同深度执行(云端流水线的工作区
55+ 布局与本地不一致),硬编码 parents[N] 会算到不存在的路径上——此时扫不到
56+ 任何文件、违规列表恒为空,看护会静默通过。
57+ 
58+ 找不到时返回 None 而非抛异常:模块级抛异常会让 pytest 在**收集阶段**报
59+ ERROR 并中断整个会话(连带其他用例一起挂),影响面远大于本用例本身。
60+ 改由各用例调 require_repo_root() 显式 skip。
61+ """
62+ for candidate in Path(__file__).resolve().parents:
63+ if all((candidate / marker).exists() for marker in REPO_ROOT_MARKERS):
64+ return candidate
65+ return None
66+ 
67+ 
68+REPO_ROOT = find_repo_root()
69+ 
70+ 
71+def require_repo_root():
72+ """静态扫描类用例的前置:拿不到仓库根就 skip,并说清原因。"""
73+ if REPO_ROOT is None:
74+ pytest.skip(
75+ f"未能从 {Path(__file__).resolve()} 向上找到仓库根"
76+ f"(标记:{', '.join(REPO_ROOT_MARKERS)}),跳过仓内 cmake 静态扫描"
77+ )
78+ return REPO_ROOT
79+ 
80+ 
81+# 显式列出要递归扫描的子树(外加仓库根下的 CMakeLists.txt),避免全仓 rglob
82+# 扫进 build/ 等构建产物与 third_party/、submodule/、bundle/ 等非本仓管控目录。
83+SCAN_SUBTREES = ("cmake", "src", "test", "scripts")
84+ 
85+# 目录权限声明里必须出现的 owner 写位关键字。
86+OWNER_WRITE_KEYWORD = "OWNER_WRITE"
87+ 
88+# 只匹配**递归** chmod 的数字模式(如 chmod -R 555 / chmod -R 0555)。
89+# 不查非递归 chmod:单个文件设成只读(chmod 440 some_file)是合法用法,
90+# 且文件无写位不影响删除——unlink 只看父目录写位。批量收紧目录权限惯用 -R,
91+# 本用例要防的正是这类(历史上的 chmod -R 555 "${_msprof_dir}")。
92+CHMOD_MODE_PATTERN = re.compile(r"chmod\s+-R\s+([0-7]{3,4})\b")
J
Jjinyingqi25 天前

问题:test_no_cmake_chmod_strips_owner_write 只匹配 chmod -R <mode>,没有覆盖 PR 描述中声称要拦截的 find -type d ... -exec chmod <mode> 写法。

  • 严重程度:minor
  • 影响:后续如果在 CMake 中通过 find -type d -exec chmod 555 重新收紧目录权限,该静态看护会漏报,打包 staging 目录可能再次缺 owner 写位,导致 build 目录清理失败风险回归。
  • 修改建议:补充对 find ... -type d ... -exec chmod <mode> 的扫描,并复用 owner 写位判断;同时保留 find -type f 场景放行。
likedislike
93+ 
94+ 
95+def iter_cmake_files():
96+ """遍历仓内自有的 CMakeLists.txt 与 *.cmake(不含构建产物/三方件目录)。"""
97+ repo_root = require_repo_root()
98+ yield repo_root / "CMakeLists.txt"
99+ for subtree in SCAN_SUBTREES:
100+ root = repo_root / subtree
101+ if not root.is_dir():
102+ continue
103+ yield from sorted(root.rglob("CMakeLists.txt"))
104+ yield from sorted(root.rglob("*.cmake"))
105+ 
106+ 
107+# install(DIRECTORY) 的合法关键字(cmake 官方签名,含 MESSAGE_NEVER)。
108+# 用作 DIRECTORY_PERMISSIONS 权限串的终止边界,避免跨声明匹配。
109+INSTALL_DIRECTORY_KEYWORDS = (
110+ "TYPE", "DESTINATION", "FILE_PERMISSIONS", "DIRECTORY_PERMISSIONS",
111+ "USE_SOURCE_PERMISSIONS", "OPTIONAL", "MESSAGE_NEVER", "CONFIGURATIONS",
112+ "COMPONENT", "EXCLUDE_FROM_ALL", "FILES_MATCHING", "PATTERN", "REGEX",
113+ "EXCLUDE", "PERMISSIONS",
114+)
115+ 
116+ 
117+def iter_directory_permission_decls():
118+ """抽取所有 DIRECTORY_PERMISSIONS 声明,产出 (文件, 权限关键字串)。"""
119+ # 权限关键字连续出现,直到遇到下一个 install(DIRECTORY) 关键字或右括号为止。
120+ pattern = re.compile(
121+ r"DIRECTORY_PERMISSIONS\s+(.*?)(?=\b(?:"
122+ + "|".join(INSTALL_DIRECTORY_KEYWORDS)
123+ + r")\b|\))",
124+ re.S,
125+ )
126+ for path in iter_cmake_files():
127+ content = path.read_text(encoding="utf-8")
128+ for match in pattern.finditer(content):
129+ yield path, match.group(1)
130+ 
131+ 
132+def test_scan_actually_finds_cmake_files():
133+ # 自检:下面两条静态看护靠"扫出违规才报错",若路径算错导致一个文件都扫不到,
134+ # 违规列表恒为空、用例会静默通过——看护形同虚设。故先确认扫描确实有产出,
135+ # 且顶层 CMakeLists.txt(本次改动所在文件)在扫描范围内。
136+ repo_root = require_repo_root()
137+ files = list(iter_cmake_files())
138+ assert files, f"未扫到任何 cmake 文件,仓库根可能算错:{repo_root}"
139+ top_level = repo_root / "CMakeLists.txt"
140+ assert top_level.is_file(), f"仓库根缺少 CMakeLists.txt:{top_level}"
141+ assert top_level in files, "顶层 CMakeLists.txt 未被纳入扫描范围"
142+ # 不断言"仓内必须存在 DIRECTORY_PERMISSIONS 声明":把仓内声明清理干净是本
143+ # 看护乐见的结果,不该因此报错。这里只校验扫描链路本身通着——代码行能读出来,
144+ # 且顶层 CMakeLists.txt 的内容确实被读到(而非读成空)。
145+ code_lines = list(iter_cmake_code_lines())
146+ assert code_lines, "未读出任何 cmake 代码行,扫描链路可能失效"
147+ assert any(rel == Path("CMakeLists.txt") for rel, _ in code_lines), \
148+ "顶层 CMakeLists.txt 的内容未被读到"
149+ 
zhangjie
zhangjiezhangjie26 天前

级别:提示 问题:test_scan_actually_finds_cmake_files 断言仓内至少存在一处 DIRECTORY_PERMISSIONS 声明(注释提到依赖 cmake/package.cmake 有一处)。若后续该文件也被清理,此自检断言会失败。 影响:该自检与实际看护目标耦合,未来正常的 DIRECTORY_PERMISSIONS 清理工作会导致自检用例报错。 修复建议:将自检断言改为「扫描逻辑正常运行」的弱校验(如验证 iter_cmake_files 产出非空且顶层 CMakeLists.txt 在内),不应强依赖仓内必须存在 DIRECTORY_PERMISSIONS 声明。

likedislike
sinat_31531339
sinat_31531339
25 天前 评论:
150+ 
151+def test_no_directory_permission_decl_drops_owner_write():
152+ # 关键看护:任何 DIRECTORY_PERMISSIONS 声明都必须含 OWNER_WRITE,
153+ # 否则落地的目录缺 u+w,其内容无法被 unlink,build/ 删不掉。
154+ repo_root = require_repo_root()
155+ offenders = []
156+ for path, perms in iter_directory_permission_decls():
157+ if OWNER_WRITE_KEYWORD not in perms:
J
Jjinyingqi25 天前

问题:DIRECTORY_PERMISSIONS 看护直接在原始匹配文本中判断是否包含 OWNER_WRITE,未剥离 CMake 行尾注释;例如 DIRECTORY_PERMISSIONS OWNER_READ OWNER_EXECUTE # missing OWNER_WRITE 会因为注释里的 OWNER_WRITE 被误判为合规。

  • 严重程度:minor
  • 影响:后续若重新引入缺 owner 写位的目录权限声明,静态用例可能漏报,导致打包 staging 目录再次不可删除,CI 清理失败风险回归。
  • 修改建议:在匹配前先按 CMake 语法剥离注释,或将权限串解析成实际 token 集合后再判断 OWNER_WRITE,避免注释文本参与权限判断。
likedislike
158+ rel = path.relative_to(repo_root)
159+ offenders.append(f"{rel}: DIRECTORY_PERMISSIONS {' '.join(perms.split())}")
160+ assert not offenders, (
161+ "以下 DIRECTORY_PERMISSIONS 声明缺少 OWNER_WRITE,会造出无法删除的目录:\n"
162+ + "\n".join(offenders)
163+ )
164+ 
165+ 
166+def iter_cmake_code_lines():
167+ """产出 (相对路径, 代码行),跳过整行注释。"""
168+ repo_root = require_repo_root()
169+ for path in iter_cmake_files():
170+ rel = path.relative_to(repo_root)
171+ lines = path.read_text(encoding="utf-8").splitlines()
172+ for line in (ln for ln in lines if not ln.lstrip().startswith("#")):
173+ yield rel, line
174+ 
175+ 
176+def iter_chmod_modes_without_owner_write(line):
177+ """产出该行里摘掉 owner 写位的递归 chmod 数字模式。owner 位含写位即 2/3/6/7。"""
178+ for mode in CHMOD_MODE_PATTERN.findall(line):
179+ if not int(mode[-3]) & 0o2:
180+ yield mode
181+ 
182+ 
183+def test_no_cmake_chmod_strips_owner_write():
184+ # 打包期不得用递归 chmod 把目录写位摘掉(如 chmod -R 555/550/444/440),
185+ # 否则等价于上一条的后果。非递归 chmod(多为单个文件)不在检查范围。
186+ offenders = []
187+ for rel, line in iter_cmake_code_lines():
188+ for mode in iter_chmod_modes_without_owner_write(line):
189+ offenders.append(f"{rel}: chmod {mode} ({line.strip()})")
190+ assert not offenders, (
191+ "以下 chmod 摘掉了 owner 写位,会造出无法删除的目录:\n" + "\n".join(offenders)
192+ )
193+ 
194+ 
195+@pytest.mark.skipif(
196+ os.geteuid() == 0,
197+ reason="root 有 CAP_DAC_OVERRIDE,绕过权限位检查,555 目录下 rmtree 照样成功,"
198+ "无法复现普通用户的 PermissionError(云端 UT 以 root 运行)",
199+)
200+def test_dir_without_owner_write_blocks_removal(tmp_path):
201+ # 机制验证:父目录缺 u+w 时,其中的条目无法 unlink,rm -rf 失败。
202+ # 这正是 555 目录导致 build/ 删不掉、CI 随机失败的根因。
203+ #
204+ # 注意:本条只在**普通用户**下成立。root 不受权限位约束,故上面 skipif;
205+ # 真正的看护由上面两条静态扫描承担,它们与运行身份无关。
206+ staging = tmp_path / "staging"
207+ inner = staging / "tbe"
208+ inner.mkdir(parents=True)
209+ (inner / "op.py").write_text("# payload", encoding="utf-8")
210+ 
211+ # 收紧为 555(r-xr-xr-x):可读可遍历,但无写位。
212+ inner.chmod(0o555)
213+ try:
214+ assert not inner.stat().st_mode & stat.S_IWUSR, "用例前提失败:目录仍带 owner 写位"
215+ with pytest.raises(PermissionError):
216+ shutil.rmtree(staging)
zhangjie
zhangjiezhangjie26 天前

级别:提示 问题:test_restoring_owner_write_makes_dir_removable 直接调用 /bin/chmod,测试注释声称对应 build.sh 中 safe_rm_dir 的做法,但 build.sh 未在本 PR 变更范围内,无法确认 safe_rm_dir 是否真实存在且逻辑一致。 影响:若 build.sh 中 safe_rm_dir 的实际实现与此处验证的机制不一致(如使用 Python os.chmod 而非 /bin/chmod),测试通过不代表生产代码正确。 修复建议:补充对 build.sh 中 safe_rm_dir 的存在性及实现逻辑的静态断言,或在测试注释中明确此处仅验证机制可行性而非具体实现。

likedislike
sinat_31531339
sinat_31531339
25 天前 评论:
217+ assert inner.exists(), "缺 u+w 的目录树本应删除失败"
218+ finally:
219+ # 恢复写位,避免 tmp_path 清理阶段再次失败。目录可能已被删掉
220+ # (如断言未按预期成立),故先判存在再 chmod——否则这里抛
221+ # FileNotFoundError 会盖住上面断言的真实失败原因。
222+ if inner.is_dir():
223+ inner.chmod(0o755)
224+ 
225+ 
226+def test_restoring_owner_write_makes_dir_removable(tmp_path):
227+ # 兜底做法验证:给目录补回 u+w 后即可删除。
228+ #
229+ # 本条只验证"补 u+w 能让目录树可删"这一机制可行性,不验证 build.sh 里
230+ # safe_rm_dir 的具体实现——后者由 test_build_script.py 的
231+ # test_safe_rm_dir_defined 静态断言看护(含 `chmod -R u+w` 与 `rm -r`)。
232+ # 这里用 os.chmod 递归改权限而非调外部 chmod:chmod 在不同发行版下路径不同
233+ # (/bin 或 /usr/bin),写死绝对路径会在部分环境抛 FileNotFoundError。
234+ staging = tmp_path / "staging"
235+ inner = staging / "profiler_tool"
236+ inner.mkdir(parents=True)
237+ (inner / "msprof.py").write_text("# payload", encoding="utf-8")
238+ inner.chmod(0o555)
239+ 
240+ for path in (staging, *staging.rglob("*")):
241+ if path.is_dir():
242+ path.chmod(path.stat().st_mode | stat.S_IWUSR)
243+ shutil.rmtree(staging)
244+ assert not staging.exists(), "补上 u+w 后目录树应能删除"
@@ -19,13 +19,14 @@
19"""msprofbin/CMakeLists.txt 中 msprof whl 预解包进包方式的看护。19"""msprofbin/CMakeLists.txt 中 msprof whl 预解包进包方式的看护。
20 20 
21背景:whl 预解包由旧的 install(CODE ...)(安装期 pip + chmod 555)改为构建期21背景:whl 预解包由旧的 install(CODE ...)(安装期 pip + chmod 555)改为构建期
22-add_custom_command 解包 + install(DIRECTORY ...) 声明进包。install(DIRECTORY) 默认22+add_custom_command 解包 + install(DIRECTORY ...) 声明进包。
23-644/755,会覆盖构建期 chmod 555 的结果——若不显式声明权限,--noexec --extract
24-(仅解压、不跑 msprof_install.sh)路径下解出的文件是 644/755,与 --full 安装
25-(msprof_install.sh 结尾统一 chmod 555)不一致,且偏离旧 install(CODE) 的 555 行为。
26 23 
27-本用例参照 test_build_script.py 从源文件抽取声明做断言,锁定这些正确性约束,24+打包期不再显式声明权限,交回 cmake install() 的默认值(文件 644 / 目录 755);
28-防止后续无意改回默认权限或漏掉进包声明造成回归。25+profiler_tool 子树的权限终态由运行期 msprof_install.sh 末尾的 change_dir_mode /
26+change_file_mode 555 收口,打包期不重复声明。
27+ 
28+本用例参照 test_build_script.py 从源文件抽取声明做断言,锁定解包方式与进包声明,
29+防止后续无意改回安装期 install(CODE) 或漏掉进包声明造成回归。
29"""30"""
30 31 
31import re32import re
@@ -73,20 +74,25 @@ def test_extracted_dir_installed_via_install_directory():
73 assert "COMPONENT oam-tools" in block74 assert "COMPONENT oam-tools" in block
74 75 
75 76 
76-def test_install_directory_declares_555_permissions():77+def test_install_directory_declares_file_555_but_no_dir_perms():
77- # 关键回归看护:必须显式声明 555(r-xr-xr-x)文件/目录权限,78+ # 文件权限显式 555:与运行期 msprof_install.sh 的 change_file_mode 555 一致
78- # 否则 install(DIRECTORY) 默认 644/755,导致 --extract --full 权限不一致79+ # 使 --noexec --extract 旁路(不跑安装脚本)下的文件权限不退回 644
80+ # 目录权限不声明:交回 cmake 默认的 755。unlink 只看父目录写位,目录必须带
81+ # owner 写位,否则 build/_CPack_Packages/ 下的产物无法 rm -rf(CI 随机失败)。
79 block = get_install_directory_block()82 block = get_install_directory_block()
80 file_perm = re.search(r"FILE_PERMISSIONS\s+(.*?)(?:DIRECTORY_PERMISSIONS|PATTERN|\))", block, re.S)83 file_perm = re.search(r"FILE_PERMISSIONS\s+(.*?)(?:DIRECTORY_PERMISSIONS|PATTERN|\))", block, re.S)
84+ assert file_perm is not None, "install(DIRECTORY) 应显式声明 FILE_PERMISSIONS 555"
85+ perms = file_perm.group(1)
86+ # 555 = READ + EXECUTE for OWNER/GROUP/WORLD,且不含任何 WRITE。
87+ assert "OWNER_READ" in perms and "OWNER_EXECUTE" in perms
88+ assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms
89+ assert "WORLD_READ" in perms and "WORLD_EXECUTE" in perms
90+ assert "WRITE" not in perms, "文件 555 权限不应含任何 WRITE 位"
91+ # 目录权限一律不得摘掉 owner 写位:要么不声明,要么声明里必须含 OWNER_WRITE。
81 dir_perm = re.search(r"DIRECTORY_PERMISSIONS\s+(.*?)(?:PATTERN|\))", block, re.S)92 dir_perm = re.search(r"DIRECTORY_PERMISSIONS\s+(.*?)(?:PATTERN|\))", block, re.S)
82- assert file_perm is not None, "install(DIRECTORY) 缺少 FILE_PERMISSIONS 声明"93+ if dir_perm is not None:
83- assert dir_perm is not None, "install(DIRECTORY) 缺少 DIRECTORY_PERMISSIONS 声明"94+ assert "OWNER_WRITE" in dir_perm.group(1), \
84- for perms in (file_perm.group(1), dir_perm.group(1)):95+ "若声明 DIRECTORY_PERMISSIONS,必须含 OWNER_WRITE,否则产物目录无法删除"
85- # 555 = READ + EXECUTE for OWNER/GROUP/WORLD,且不含任何 WRITE。
86- assert "OWNER_READ" in perms and "OWNER_EXECUTE" in perms
87- assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms
88- assert "WORLD_READ" in perms and "WORLD_EXECUTE" in perms
89- assert "WRITE" not in perms, "555 权限不应含任何 WRITE 位"
90 96 
91 97 
92def test_sentinel_file_excluded_from_package():98def test_sentinel_file_excluded_from_package():
@@ -145,13 +151,11 @@ def get_install_programs_whl_block():
145 151 
146 152 
147def test_install_programs_whl_declares_555_permissions():153def test_install_programs_whl_declares_555_permissions():
148- # whl 随包分发、install.sh 判断否跑 msprof_install其包内权限属"两种154+ # whl 自身保留显式 555:它是文件无写位不影响目录可删除性,
149- # 安装文件树一致"目标范围。旧 install(CODE) 的 chmod -R 555 连带把 whl 555,155+ # 故与历史终态保持一致旧 install(CODE) 的 chmod -R 555 连带刷 whl)。
150- # 故 install(PROGRAMS) 须显式声明 555(安装期终态动作),否则 --noexec --extract
151- # 路径下 whl 停在默认 755,与旧行为不一致。
152 block = get_install_programs_whl_block()156 block = get_install_programs_whl_block()
153 perm = re.search(r"PERMISSIONS\s+(.*?)\)", block, re.S)157 perm = re.search(r"PERMISSIONS\s+(.*?)\)", block, re.S)
154- assert perm is not None, "install(PROGRAMS) 缺少 PERMISSIONS 声明"158+ assert perm is not None, "install(PROGRAMS) 应显式声明 PERMISSIONS 555"
155 perms = perm.group(1)159 perms = perm.group(1)
156 assert "OWNER_READ" in perms and "OWNER_EXECUTE" in perms160 assert "OWNER_READ" in perms and "OWNER_EXECUTE" in perms
157 assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms161 assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms