Skip to content

Commit a81c0c4

Browse files
wehosclaude
andcommitted
fix(build): 插件里的 .vscode 目录打死了 macOS 后端构建
nightly 的 mac x64 / arm64 后端自 8-13 起连续红在重签名一步: dist/Xiao8/projectneko_server.app: bundle format unrecognized, invalid, or unsuitable In subcomponent: .../Contents/MacOS/plugin/plugins/lifekit/.vscode Nuitka --mode=app 把插件 payload 摆在 Contents/MacOS/ 下,codesign 的默认 规则把那里名字带点的目录一律当成嵌套 bundle。.vscode 里没有 Contents/, 读不出来,于是整个封印失败,报错只给出 subcomponent,不给别的线索。一个 插件里的一个编辑器目录就足以让整台 mac 构建停摆。 .vscode 是 #2811 随 lifekit 一起带进来的,plugin 打包的内建排除清单里有 .github 却没有 .vscode/.idea,于是原样落进暂存 payload。补上两项。 不做通用的「删掉所有点目录」:numpy / Pillow 这类 wheel 用 .dylibs 存放 随包动态库,一刀切会把运行时打断。 两条回归测试:一条用临时插件树验证 .vscode/.idea 不进 payload 且暂存树里 不留任何点目录;另一条直接扫 plugin/plugins/ 的真实文件,钉住允许出现的 点目录集合——排除清单只认识它列过的名字,换个新名字照样能再把 mac 构建 打死,所以护栏要落在真实树上。 验证:去掉修复两条测试都红;打上后 9 条全绿。对真实插件树跑 prepare, 修复前暂存产物里有 lifekit/.vscode,修复后点目录为零、lifekit 其余内容 照常落地。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent a459e2f commit a81c0c4

2 files changed

Lines changed: 86 additions & 0 deletions

File tree

plugin/neko_plugin_cli/core/build_rules.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,19 @@
77

88
# Built-in excludes are hard safety defaults. User rules extend them, but do
99
# not replace them, so common cache/build artifacts never leak into packages.
10+
#
11+
# On macOS an editor directory is not merely noise. The desktop build stages
12+
# this payload under projectneko_server.app/Contents/MacOS/, and codesign's
13+
# default rules treat every directory there whose name contains a dot as a
14+
# nested bundle. It then fails to read one — there is no Contents/ inside — and
15+
# aborts the whole seal with "bundle format unrecognized, invalid, or
16+
# unsuitable", naming only the subcomponent. One stray .vscode/ in one plugin
17+
# therefore breaks the entire mac backend build.
1018
_DEFAULT_EXCLUDE_DIR_NAMES = {
1119
"__pycache__",
1220
".github",
21+
".vscode",
22+
".idea",
1323
".pytest_cache",
1424
".mypy_cache",
1525
".venv",

tests/unit/test_prepare_nuitka_plugins.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pytest
1010

11+
from plugin.neko_plugin_cli.core.build_rules import _DEFAULT_EXCLUDE_DIR_NAMES
1112
from scripts.check_nuitka_dist import _check_plugin_stage, _check_plugin_tomls
1213
from scripts.prepare_nuitka_plugins import install_plugins, prepare_plugins
1314

@@ -208,3 +209,78 @@ def test_prepare_helper_is_directly_executable_without_pythonpath(tmp_path: Path
208209

209210
assert completed.returncode == 0, completed.stderr
210211
assert "stage plugins and generate launcher" in completed.stdout
212+
213+
214+
def test_prepare_drops_editor_directories_that_break_macos_codesign(tmp_path: Path) -> None:
215+
"""codesign reads any dotted directory under MacOS/ as a nested bundle.
216+
217+
A plugin that ships .vscode/ or .idea/ used to reach the staged payload
218+
verbatim, and `codesign --deep` then aborted the whole backend seal with
219+
"bundle format unrecognized, invalid, or unsuitable" — see the exclusion
220+
list in plugin/neko_plugin_cli/core/build_rules.py.
221+
"""
222+
223+
project_root = tmp_path / "repo"
224+
plugin_dir = project_root / "plugin" / "plugins" / "demo_plugin"
225+
_write(project_root / "launcher.py", "print('launcher')\n")
226+
_write(plugin_dir / "plugin.toml", '[plugin]\nid = "demo_plugin"\n')
227+
_write(plugin_dir / "__init__.py")
228+
_write(plugin_dir / "runtime.py")
229+
_write(plugin_dir / ".vscode" / "settings.json", "{}\n")
230+
_write(plugin_dir / ".vscode" / "tasks.json", "{}\n")
231+
_write(plugin_dir / ".idea" / "workspace.xml", "<project/>\n")
232+
233+
result = prepare_plugins(
234+
project_root=project_root,
235+
plugins_root=Path("plugin/plugins"),
236+
stage_dir=Path("build/nuitka-plugins"),
237+
source_launcher=Path("launcher.py"),
238+
generated_launcher=Path("build_nuitka_launcher.py"),
239+
)
240+
241+
stage_plugin = result.stage_dir / "demo_plugin"
242+
assert (stage_plugin / "runtime.py").is_file()
243+
assert not (stage_plugin / ".vscode").exists()
244+
assert not (stage_plugin / ".idea").exists()
245+
246+
# Nothing dotted may survive anywhere in the staged tree, whatever its depth.
247+
assert not [
248+
path
249+
for path in result.stage_dir.rglob("*")
250+
if path.is_dir() and "." in path.name
251+
]
252+
253+
254+
def test_no_bundled_plugin_ships_a_dotted_directory_besides_github() -> None:
255+
"""Guard the real tree, not just the staging helper.
256+
257+
build_rules only skips names it knows. A plugin adding some other dotted
258+
directory would sail past it and kill the mac build again, so pin the set
259+
of dotted directories that actually exist under plugin/plugins/.
260+
"""
261+
262+
repo_root = Path(__file__).resolve().parents[2]
263+
plugins_root = repo_root / "plugin" / "plugins"
264+
listed = subprocess.run(
265+
["git", "ls-files", "-z", "--", str(plugins_root)],
266+
cwd=repo_root,
267+
capture_output=True,
268+
text=True,
269+
check=True,
270+
)
271+
272+
offenders = sorted(
273+
{
274+
part
275+
for entry in listed.stdout.split("\0")
276+
if entry
277+
for part in Path(entry).parts[:-1]
278+
if "." in part
279+
}
280+
- set(_DEFAULT_EXCLUDE_DIR_NAMES)
281+
)
282+
283+
assert not offenders, (
284+
"dotted directories under plugin/plugins/ break `codesign --deep` on the "
285+
f"macOS backend bundle; add them to _DEFAULT_EXCLUDE_DIR_NAMES: {offenders}"
286+
)

0 commit comments

Comments
 (0)