| @@ -285,17 +285,13 @@ if(EXISTS "${BUNDLE_PKG}/aml") | |||
| 285 | endif() | 285 | endif() |
| 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),会破坏与主线的兼容性。 | ||
| 293 | if(EXISTS "${BUNDLE_PKG}/built-in") | 290 | if(EXISTS "${BUNDLE_PKG}/built-in") |
| 294 | install(DIRECTORY ${BUNDLE_PKG}/built-in/op_impl/ai_core/tbe | 291 | install(DIRECTORY ${BUNDLE_PKG}/built-in/op_impl/ai_core/tbe |
| 295 | DESTINATION opp/built-in/op_impl/ai_core | 292 | DESTINATION opp/built-in/op_impl/ai_core |
| 296 | COMPONENT oam-tools | 293 | COMPONENT oam-tools |
| 297 | FILE_PERMISSIONS OWNER_READ GROUP_READ | 294 | 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" EXCLUDE | 295 | PATTERN "op_api" EXCLUDE |
| 300 | ) | 296 | ) |
| 301 | install(FILES | 297 | install(FILES |
| @@ -275,7 +275,7 @@ add_dependencies(msprofbin msprofbin_proto) | |||
| 275 | set(msprof_whl | 275 | set(msprof_whl |
| 276 | ${CMAKE_CURRENT_SOURCE_DIR}/msprof-0.0.1-py3-none-any.whl | 276 | ${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(文件权限,不影响目录可删除性),与历史终态一致。 |
| 279 | install(PROGRAMS ${msprof_whl} | 279 | install(PROGRAMS ${msprof_whl} |
| 280 | DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool | 280 | 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 555。 | 308 | + # 文件权限保留显式 555,与运行期 msprof_install.sh 的 change_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 | |||
| 310 | DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool | 313 | DESTINATION ${INSTALL_LIBRARY_DIR}/tools/profiler/profiler_tool |
| 311 | ${INSTALL_OPTIONAL} | 314 | ${INSTALL_OPTIONAL} |
| 312 | COMPONENT oam-tools | 315 | COMPONENT oam-tools |
| 313 | FILE_PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE | 316 | 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" EXCLUDE | 317 | PATTERN ".extracted" EXCLUDE |
| 316 | ) | 318 | ) |
| 317 | endif() | 319 | endif() |
| @@ -0,0 +1,244 @@ | |||
| 1 | +#!/usr/bin/env python3 | ||
级别:提示
问题:版权声明年份为 2026,与当前实际不符。
影响:不影响功能,但版权信息不准确可能在合规审查时被标记。
修复建议:将 ![]() ![]() | |||
| 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 问题:test_no_cmake_chmod_strips_owner_write 只匹配
![]() ![]() | |||
| 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 | + | ||
级别:提示 问题:test_scan_actually_finds_cmake_files 断言仓内至少存在一处 DIRECTORY_PERMISSIONS 声明(注释提到依赖 cmake/package.cmake 有一处)。若后续该文件也被清理,此自检断言会失败。 影响:该自检与实际看护目标耦合,未来正常的 DIRECTORY_PERMISSIONS 清理工作会导致自检用例报错。 修复建议:将自检断言改为「扫描逻辑正常运行」的弱校验(如验证 iter_cmake_files 产出非空且顶层 CMakeLists.txt 在内),不应强依赖仓内必须存在 DIRECTORY_PERMISSIONS 声明。 ![]() ![]() | |||
| 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 问题:DIRECTORY_PERMISSIONS 看护直接在原始匹配文本中判断是否包含 OWNER_WRITE,未剥离 CMake 行尾注释;例如
![]() ![]() | |||
| 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 | + | ||
| 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) | ||
级别:提示 问题: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 的存在性及实现逻辑的静态断言,或在测试注释中明确此处仅验证机制可行性而非具体实现。 ![]() ![]() | |||
| 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 | ||
| 31 | import re | 32 | import re |
| @@ -73,20 +74,25 @@ def test_extracted_dir_installed_via_install_directory(): | |||
| 73 | assert "COMPONENT oam-tools" in block | 74 | 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 | ||
| 92 | def test_sentinel_file_excluded_from_package(): | 98 | def test_sentinel_file_excluded_from_package(): |
| @@ -145,13 +151,11 @@ def get_install_programs_whl_block(): | |||
| 145 | 151 | ||
| 146 | 152 | ||
| 147 | def test_install_programs_whl_declares_555_permissions(): | 153 | def 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 perms | 160 | assert "OWNER_READ" in perms and "OWNER_EXECUTE" in perms |
| 157 | assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms | 161 | assert "GROUP_READ" in perms and "GROUP_EXECUTE" in perms |


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