Skip to content

Commit 1d68e8d

Browse files
chenchaoyiclaude
andauthored
fix(release): tarball + frozen-binary import errors blocking v1.0.x (#41)
Second hot-fix to the v1.0.0 pipeline. v1.0.1 unblocked Swift; the arch-tarball step then hit two more issues: 1. scripts/package_release.sh called tar with GNU-only flags (--owner=0 --group=0 --numeric-owner) that macOS bsdtar rejects. The fallback branch I added for --no-mac-metadata also kept those flags, so the second tar invocation failed too. End-state on macos-14: workflow died at "==> tarballing" with no asset uploaded. 2. PyInstaller-frozen binary crashed on first run with `ImportError: attempted relative import with no known parent package`. PyInstaller compiled src/diting/cli.py as a top-level script, stripping the diting package context that the module's `from .x import y` imports rely on. Only surfaces when the frozen binary is actually executed — the tarball would have uploaded successfully and install.sh would have downloaded a broken binary. Both fixes verified end-to-end by running `uv run --group release bash scripts/package_release.sh 1.0.2-rc` locally, extracting the resulting tarball, and confirming `diting --help` exits 0 with usage text. Fixes: - scripts/package_release.sh: drop GNU-only tar flags; plain `tar -czf` runs everywhere. SHA256 in SHASUMS256.txt is the real integrity guarantee. - scripts/frozen_entry.py (new): PyInstaller entry stub that does `from diting.cli import main; main()` so the package context is preserved. - scripts/build_frozen.py: target the stub via --paths src/, not src/diting/cli.py. - .gitignore: ignore the PyInstaller-generated diting.spec and the Claude Code scheduled-task lockfile. Bump to v1.0.2; v1.0.0 and v1.0.1 never produced consumable assets, so end users should install v1.0.2 directly. install.sh resolves "latest" by default, so curl ... | bash auto-picks v1.0.2 with no flag change. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f6b4bf0 commit 1d68e8d

8 files changed

Lines changed: 113 additions & 24 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ build/
88
dist/
99
*.egg-info/
1010
*.egg
11+
# PyInstaller writes <name>.spec at the repo root on the first run
12+
# of `scripts/build_frozen.py`. The build script is the source of
13+
# truth for the freeze config; the generated .spec is regenerated
14+
# every build and never checked in.
15+
diting.spec
1116

1217
# Virtual environments (uv puts venvs at .venv by default)
1318
.venv/
@@ -63,3 +68,6 @@ snapshot-output/
6368
# Personal/meeting notes — never commit. Keep these out of the public repo.
6469
会议纪要_*.md
6570
/会议纪要*.md
71+
72+
# Claude Code scheduled-task local state.
73+
.claude/scheduled_tasks.lock

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,40 @@ the project follows [Semantic Versioning](https://semver.org/) where
99
practical. The leading `v0.x` line is allowed to break minor
1010
behaviours between releases.
1111

12+
## [1.0.2] — 2026-05-13
13+
14+
Second hot-fix to the v1.0.0 release pipeline. v1.0.1 unblocked the
15+
Swift helper build but the per-arch tarball step then failed on
16+
two further issues that surfaced once the pipeline got further:
17+
18+
1. `scripts/package_release.sh` invoked `tar` with GNU-only flags
19+
(`--owner=0 --group=0 --numeric-owner`) which macOS bsdtar
20+
rejects outright. The fallback path I added for
21+
`--no-mac-metadata` still kept those flags, so the tarball
22+
command failed on the hosted runners.
23+
2. The PyInstaller-frozen binary crashed on first run with
24+
`ImportError: attempted relative import with no known parent
25+
package`. PyInstaller compiled `cli.py` as a top-level script,
26+
stripping the `diting` package context that the module's
27+
`from .x import y` imports rely on.
28+
29+
End users with v1.0.0 or v1.0.1 should install v1.0.2 — those tags
30+
never produced consumable assets. `install.sh` resolves "latest"
31+
by default, so the curl one-liner picks v1.0.2 automatically.
32+
33+
### Fixed
34+
- **Tarball builds on macOS bsdtar.** Drop the GNU-only
35+
`--owner=0 --group=0 --numeric-owner` flags from
36+
`scripts/package_release.sh`; plain `tar -czf` runs everywhere.
37+
Tarball reproducibility (deterministic uid/gid headers) was a
38+
nice-to-have, not load-bearing — SHA256 in `SHASUMS256.txt`
39+
remains the integrity guarantee.
40+
- **Frozen binary preserves package context.** New
41+
`scripts/frozen_entry.py` stub imports `diting.cli:main`, and
42+
PyInstaller now compiles the stub (with `--paths src`) instead
43+
of `cli.py` directly. Relative imports inside the diting
44+
package resolve correctly at runtime.
45+
1246
## [1.0.1] — 2026-05-13
1347

1448
Hot-fix for the v1.0.0 release pipeline. The Swift helper source

docs/zh/CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,36 @@
88
[Semantic Versioning](https://semver.org/)`v0.x` 阶段允许破坏性的次要
99
行为变更。
1010

11+
## [1.0.2] — 2026-05-13
12+
13+
第二个针对 v1.0.0 发布流水线的热修复。v1.0.1 解开了 Swift helper 构建
14+
那一卡,分架构 tarball 那一步又卡在另外两个问题上:
15+
16+
1. `scripts/package_release.sh``tar` 时带了 GNU-only 标志
17+
`--owner=0 --group=0 --numeric-owner`),macOS bsdtar 直接拒绝。
18+
我为 `--no-mac-metadata` 写的回退分支也带着这些 GNU 标志,所以
19+
tarball 那一步在托管 runner 上就死了。
20+
2. PyInstaller 冻结后的 binary 首次运行就崩在
21+
`ImportError: attempted relative import with no known parent
22+
package`。PyInstaller 把 `cli.py` 当顶层脚本编译,丢掉了模块自身
23+
`from .x import y` 依赖的 `diting` 包上下文。
24+
25+
装了 v1.0.0 / v1.0.1 的最终用户应该装 v1.0.2 —— 那两个 tag 都没产出
26+
可消费的发布产物。`install.sh` 默认解析最新 tag,所以 curl 一行会自
27+
动取 v1.0.2。
28+
29+
### 修复
30+
- **Tarball 在 macOS bsdtar 上能正常打**。从
31+
`scripts/package_release.sh` 里去掉 GNU-only 的
32+
`--owner=0 --group=0 --numeric-owner`,纯 `tar -czf` 哪里都能跑。
33+
原本想要的 tarball 可复现(uid/gid 头一致)只是 nice-to-have,
34+
不是关键 —— SHASUMS256.txt 的 SHA256 才是真正的完整性保证。
35+
- **冻结 binary 保留包上下文**。新增
36+
`scripts/frozen_entry.py` stub,里面只 import `diting.cli:main`
37+
并调用;PyInstaller 现在编译这个 stub(外加 `--paths src`),
38+
而不是直接编译 `cli.py`。diting 包内部的相对 import 运行时能正
39+
常 resolve。
40+
1141
## [1.0.1] — 2026-05-13
1242

1343
v1.0.0 发布流水线的热修复。Swift helper 源码里一个函数调用参数列表的

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "diting"
3-
version = "1.0.1"
3+
version = "1.0.2"
44
description = "macOS terminal listening post for Wi-Fi, BLE, link health, and the RF environment — your Mac hears more than it tells you"
55
readme = "README.md"
66
license = "MIT"

scripts/build_frozen.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,19 @@ def main() -> None:
104104
# data files. They live under src/diting/data/ and are read
105105
# via importlib.resources at runtime.
106106
"--add-data", f"{REPO_ROOT / 'src' / 'diting' / 'data'}:diting/data",
107+
# PyInstaller needs to know where to find the `diting`
108+
# package since the entry stub imports it. Pointing
109+
# --paths at src/ lets the analyzer walk the package
110+
# tree.
111+
"--paths", str(REPO_ROOT / "src"),
107112
# Strip debug symbols from binaries. Knocks ~6 MB off a
108113
# release build at no runtime cost.
109114
"--strip",
110-
# The entry script — the CLI dispatcher that pyproject.toml's
111-
# [project.scripts] maps `diting` to.
112-
str(REPO_ROOT / "src" / "diting" / "cli.py"),
115+
# Entry stub that imports `diting.cli:main` rather than
116+
# PyInstaller-compiling `cli.py` directly. Compiling cli.py
117+
# as a top-level script breaks `from .x import y` relative
118+
# imports inside the package.
119+
str(REPO_ROOT / "scripts" / "frozen_entry.py"),
113120
]
114121

115122
# Invoke PyInstaller; let its output stream straight to our

scripts/frozen_entry.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""PyInstaller entry-point stub.
2+
3+
PyInstaller compiles the script you point it at AS a top-level
4+
module, which breaks ``from .x import y`` style relative imports
5+
inside the diting package. A stub that imports ``diting.cli.main``
6+
preserves the package context — the same way pyproject.toml's
7+
``[project.scripts]`` entry calls into ``main`` via ``diting.cli:main``.
8+
9+
Only used by ``scripts/build_frozen.py``. Never invoked directly.
10+
"""
11+
from __future__ import annotations
12+
13+
from diting.cli import main
14+
15+
16+
if __name__ == "__main__":
17+
main()

scripts/package_release.sh

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,26 +103,19 @@ cp -R helper/diting-tianer.app "$STAGE_DIR/share/diting-tianer.app"
103103

104104
# ---- produce tarball ----
105105

106-
# Reproducibility-ish: pin the mtime so a rebuild of the same
107-
# version doesn't produce a byte-different tarball. --owner / --group
108-
# / --numeric-owner make the archive look the same regardless of
109-
# whose machine produced it (helps SHASUMS verification land
110-
# consistently across CI matrix jobs).
106+
# Plain `tar -czf`. We do NOT pass `--owner=0 --group=0
107+
# --numeric-owner` here even though it would produce a more
108+
# reproducible archive — those are GNU-tar-only flags and macOS
109+
# ships bsdtar, which rejects them outright (bsdtar accepts a
110+
# different `--uid`/`--gid` syntax that doesn't ride alongside).
111+
# Per-arch tarballs are built fresh on clean hosted runners, so the
112+
# uid/gid baked into the archive header is whatever the runner uses
113+
# (uid 502 in practice). SHA256 verification of the resulting
114+
# tarball is what the install.sh path actually relies on for
115+
# integrity; a deterministic header would be nice-to-have, not
116+
# load-bearing.
111117
echo "==> tarballing into ${TARBALL}"
112-
tar \
113-
--no-mac-metadata 2>/dev/null \
114-
--owner=0 --group=0 --numeric-owner \
115-
-czf "$TARBALL" \
116-
-C dist \
117-
"${STAGE_NAME}" || tar \
118-
--owner=0 --group=0 --numeric-owner \
119-
-czf "$TARBALL" \
120-
-C dist \
121-
"${STAGE_NAME}"
122-
# (the `|| tar ...` fallback drops `--no-mac-metadata` on platforms
123-
# where bsdtar doesn't recognise that flag — older macOS releases
124-
# accept it, brand-new GNU tar does not. The tarball still produces
125-
# correct contents either way.)
118+
tar -czf "$TARBALL" -C dist "${STAGE_NAME}"
126119

127120
# SHA256 sidecar so CI can stitch SHASUMS256.txt together across
128121
# matrix jobs without re-hashing.

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)