From c4663ef7690489f5124f82bb4dbf7789367e96c2 Mon Sep 17 00:00:00 2001 From: ykcheungab Date: Sat, 10 Oct 2026 16:17:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9E=84=E5=BB=BA=EF=BC=9A=E4=BB=8E=E6=89=93?= =?UTF-8?q?=E5=8C=85=E6=95=B0=E6=8D=AE=E4=B8=AD=E5=89=94=E9=99=A4=20.gitig?= =?UTF-8?q?nore=20=E7=AD=89=E7=89=88=E6=9C=AC=E6=8E=A7=E5=88=B6=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E5=B9=B6=E5=8A=A0=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit collect_data_files("orisys") 会把 SDK 包内受 git 跟踪的 src/orisys/.gitignore 一并拷进 _internal\orisys; 现在 spec 统一过滤版本控制与缓存文件名,build.ps1 第 6 步对我们自己发布的三个包硬失败、其它位置仅告警。 验证:清理重建 exit 0,dist 内 .git* / __pycache__ / 缓存文件 0 命中,Open3D 检查通过,冒烟测试 打印 "the application started" 与 "the packaged 3D scene loaded",输出 488.7 MB。 --- build/README.md | 12 +++++--- build/studio/build.ps1 | 35 ++++++++++++++++++++-- build/studio/orisys-studio.spec | 53 ++++++++++++++++++++++++++++++--- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/build/README.md b/build/README.md index 83ce094..f1cbb59 100644 --- a/build/README.md +++ b/build/README.md @@ -97,6 +97,7 @@ script checks the things that only show up when the EXE runs: | `collect_data_files('orisys_app_shared', includes=['theme/*.json'])` is non-empty | the theme colours are package data; without them the app dies in `build_qss()` before logging anything | | every `config\sensors\**\*.oryscene` is present, and the finger manifest/`sensor.json`/unroll map survive the config strip | the 3D pane refuses the scene when the device description cannot be derived | | no `open3d*` under `dist` | the offline tool leaking into the shipped bundle | +| no source-control or cache files in the bundle (`_without_residue` in the spec, step 6 of the script) | a `.gitignore`, `__pycache__` or `.scratch` from the source trees shipping inside `dist` — the SDK keeps a `.gitignore` *inside* the `orisys` package, so `collect_data_files('orisys')` copies it | | **smoke test**: start the produced EXE, wait for its own log to say `Studio shell started` (and `3D scene loaded` when a finger scene shipped), then stop it | a bundle that cannot start at all — missing runtime DLLs, an excluded module, missing data. The log is kept under `build\_smoke\` | The smoke test seeds a `preference.json` beside the EXE to select the finger sensor with the @@ -116,13 +117,15 @@ never sources. ```text _internal\orisys_app_shared\theme\colors_dark.json <- package data, bundled (qss.py reads it) _internal\orisys_app_shared\theme\colors_light.json -_internal\orisys\.gitignore <- stray non-.py file of the SDK package _internal\branding\... <- logos ``` (`collect_data_files("orisys")` exists for the case where the SDK is installed from a wheel that -bundles `sensors/` assets; with a local editable checkout the only non-`.py` file is that -`.gitignore`. It is harmless — the app prefers `config\sensors` — but it does look odd.) +bundles `sensors/` assets. With a local editable checkout its only non-`.py` file *was* the SDK's +own `src\orisys\.gitignore` — a repository file with no business inside an importable package — so +it landed here as `_internal\orisys\.gitignore`. The spec now filters source-control and cache +files out of every `datas` entry, and step 6 of the script fails the build if any survive in a +package we ship.) To list what is really in the archive: @@ -185,7 +188,8 @@ Expect ~450 MB total (Qt + numpy/scipy/numba + OpenCV + PyOpenGL). appear next to the EXE. 4. Camera preview + box/mini/finger Run (video) with image modes and force plots. 5. Open the 3D pane on a finger source: the surface draws, the legend shows the field. -6. Confirm no `open3d*` files under `dist\OrisysStudio`. +6. Confirm no `open3d*` files under `dist\OrisysStudio`, and no `.gitignore`/`__pycache__`/ + `.scratch` anywhere inside it (step 6 of the build fails on residue in our own packages). ## Editable run (unchanged) diff --git a/build/studio/build.ps1 b/build/studio/build.ps1 index f64b245..d944316 100644 --- a/build/studio/build.ps1 +++ b/build/studio/build.ps1 @@ -11,7 +11,7 @@ 1. make sure that provisioning happened (and run `setup.ps1` if it plainly has not), 2. verify the environment can import what the frozen app imports, 3. build, - 4. verify the artefact — config, excludes, and a real start-up of the produced EXE. + 4. verify the artefact — config, excludes, residue, and a real start-up of the produced EXE. Step 2 and step 4 exist because the interesting failures are silent ones: a stale venv still builds (the spec's pathex finds the modules) yet loses the theme data; an excluded @@ -177,7 +177,36 @@ if ($o3dHits) { Write-Host "Open3D check: none found under dist." } -# ----------------------------------------------------------------- 6. smoke test +# -------------------------------------------- 6. no source-control or cache residue +# PyInstaller copies whatever data a package declares, and `collect_data_files("orisys")` +# copies every non-Python file the SDK package holds — including the tracked +# `src/orisys/.gitignore`, which is how `_internal\orisys\.gitignore` got shipped. A file +# like that is not a crash, so nothing else in this script would notice it; this check is +# the only thing that keeps it from coming back. It is a hard failure inside the bundles we +# own, and a warning elsewhere, where the file may belong to a third-party wheel. +$residueNames = '^(\.git.*|\.gitkeep|__pycache__|\.pytest_cache|\.mypy_cache|\.ruff_cache|\.scratch|\.tox|\.idea|\.vscode)$' +$ourPackages = @("_internal\orisys", "_internal\orisys_app_shared", "_internal\orisys_studio") +$residue = @(Get-ChildItem $OutDir -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { $_.Name -match $residueNames }) +$ownedResidue = @() +foreach ($item in $residue) { + $relative = $item.FullName.Substring($OutDir.Length + 1) + foreach ($package in $ourPackages) { + if ($relative -like "$package\*") { $ownedResidue += $item; break } + } +} +if ($ownedResidue.Count -gt 0) { + Write-Host "Residue inside the bundles we own:" + $ownedResidue | ForEach-Object { Write-Host (" " + $_.FullName.Substring($OutDir.Length + 1)) } + throw "the bundle ships $($ownedResidue.Count) source-control/cache file(s); drop them in build\studio\orisys-studio.spec and rebuild" +} elseif ($residue.Count -gt 0) { + Write-Warning "Residue outside our packages ($($residue.Count) item(s)), left alone:" + $residue | ForEach-Object { Write-Warning (" " + $_.FullName.Substring($OutDir.Length + 1)) } +} else { + Write-Host "Residue check: no .git* / __pycache__ / cache files under dist." +} + +# ----------------------------------------------------------------- 7. smoke test # Start the EXE the way a user would and require the application's own log to say it came up. # This is the step that would have caught every silent failure found in this build. $smokeFailed = $false @@ -276,7 +305,7 @@ if (-not $SkipSmokeTest -and (Test-Path $Exe)) { } } -# ------------------------------------------------------------------- 7. summary +# ------------------------------------------------------------------- 8. summary if (Test-Path $OutDir) { $bytes = (Get-ChildItem $OutDir -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum diff --git a/build/studio/orisys-studio.spec b/build/studio/orisys-studio.spec index b48d6e4..338dced 100644 --- a/build/studio/orisys-studio.spec +++ b/build/studio/orisys-studio.spec @@ -71,6 +71,46 @@ def _branding_datas() -> list[tuple[str, str]]: return datas +#: Source-control and tool-cache names that must never ship. ``collect_data_files`` copies +#: *every* non-Python file a package contains, and the SDK keeps a tracked +#: ``src/orisys/.gitignore`` — which is how ``_internal\orisys\.gitignore`` reached the +#: bundle. The list is deliberately narrow: a real asset (``theme/*.json``, cv2's own +#: ``config.py``) must survive, so only VCS/cache names are dropped. +_RESIDUE_NAMES = frozenset( + { + ".git", + ".gitignore", + ".gitattributes", + ".gitmodules", + ".gitkeep", + "__pycache__", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + ".scratch", + ".tox", + ".idea", + ".vscode", + } +) +_RESIDUE_SUFFIXES = (".pyc", ".pyo", ".orig", ".rej", "~") + + +def _without_residue(datas: list[tuple[str, str]]) -> list[tuple[str, str]]: + """Drop VCS/cache residue from collected package data, naming what was dropped.""" + kept: list[tuple[str, str]] = [] + dropped: list[str] = [] + for source, dest in datas: + parts = Path(source).parts + if any(part in _RESIDUE_NAMES for part in parts) or source.endswith(_RESIDUE_SUFFIXES): + dropped.append(source) + continue + kept.append((source, dest)) + for item in dropped: + print(f"[spec] dropped residue from package data: {item}") + return kept + + block_cipher = None # Config is copied beside the EXE by build.ps1 (writable / editable). @@ -92,15 +132,20 @@ hiddenimports = [ datas += collect_data_files("orisys_app_shared", includes=["theme/*.json"]) try: datas += collect_data_files("orisys") -except Exception: - pass +except Exception as exc: + # Tolerated so a spec run by hand still works, but never silently: a package whose data + # cannot be collected loses assets, and that is invisible in the built EXE. + print(f"[spec] WARNING: could not collect orisys package data: {exc}") try: cv2_datas, cv2_binaries, cv2_hidden = collect_all("cv2") datas += cv2_datas binaries += cv2_binaries hiddenimports += list(cv2_hidden) -except Exception: - pass +except Exception as exc: + print(f"[spec] WARNING: could not collect cv2: {exc}") + +# Applied last, so it covers every contributor above (including a third-party wheel). +datas = _without_residue(datas) icon_path = BRANDING_DIR / "logo.ico" icon = str(icon_path) if icon_path.is_file() else None