182 lines
8.8 KiB
Markdown
182 lines
8.8 KiB
Markdown
# Build
|
|
|
|
Windows packaging for the studio desktop app: **one provisioning step per machine, then
|
|
repeatable builds.**
|
|
|
|
## Layout
|
|
|
|
```text
|
|
build/
|
|
studio/
|
|
setup.bat / setup.ps1 # provision the build venv (run once per machine)
|
|
constraints-build.txt # pinned build-time versions
|
|
build.bat / build.ps1 # build the EXE and verify it
|
|
entry.py # frozen entry (no source chdir)
|
|
orisys-studio.spec # PyInstaller onedir spec
|
|
_pyinstaller/ # PyInstaller workpath (gitignored)
|
|
_smoke/ # log of the last build's start-up smoke test
|
|
dist/
|
|
OrisysStudio/
|
|
OrisysStudio.exe
|
|
config/ # lean locales + sensors (no finger visualization/)
|
|
_internal/ # Python, PyQt5, opencv-headless, orisys, OpenGL, …
|
|
```
|
|
|
|
## Why there are two steps
|
|
|
|
A PyInstaller bundle depends on the *machine* as much as on the sources: which interpreter,
|
|
whether it is Anaconda (whose `libexpat`/`libffi`/OpenSSL DLLs live outside the venv and are
|
|
therefore **not** bundled), and where the `orisys` SDK checkout is. Those facts are decided
|
|
once, by `setup.ps1`, and recorded in `.venv-build\orisys-build-env.json`.
|
|
|
|
`build.ps1` then installs nothing and guesses nothing: it verifies the environment, builds,
|
|
and verifies the artefact. That split is what keeps a build reproducible instead of
|
|
"works on my machine".
|
|
|
|
## First time on a machine
|
|
|
|
```bat
|
|
cd build\studio
|
|
setup.bat
|
|
setup.bat -Python C:\Python310\python.exe
|
|
setup.bat -AllowConda
|
|
```
|
|
|
|
`setup.bat` finds an interpreter, creates or repairs `.venv-build`, installs the pinned
|
|
toolchain plus the app's extras (including the studio's `three-d` extra — without it the 3D
|
|
pane silently reports "PyOpenGL is not installed" on the customer's machine), writes the
|
|
stamp, and then verifies the result by importing what the frozen app imports.
|
|
|
|
Interpreter resolution, in order: `-Python <path>`; `py -3.10`; `python`; the interpreter
|
|
already behind `.venv-build`. The Microsoft Store `python.exe` alias is refused (it is not an
|
|
interpreter), and an Anaconda interpreter is refused unless `-AllowConda` is passed — in that
|
|
case its `Library\bin` is recorded and the spec adds the runtime DLLs, because PyInstaller
|
|
does not search there and the EXE would otherwise die at start-up with
|
|
`ImportError: DLL load failed while importing pyexpat`. Building with python.org CPython is
|
|
still the preferred answer.
|
|
|
|
The SDK is installed from `..\..\..\orisys-sdk` when that checkout exists (`-SdkPath` to point
|
|
elsewhere, `-FromPypi` to install the published wheel instead). Installing the published SDK
|
|
is how a build once shipped with no `orisys.finger` at all.
|
|
|
|
Re-running `setup.bat` is safe: with a valid stamp for this checkout it reports and exits;
|
|
without one it repairs the existing venv rather than deleting it. `-Force` recreates it.
|
|
|
|
## Build
|
|
|
|
```bat
|
|
build.bat
|
|
build.bat -Clean # after dependency/spec changes
|
|
build.bat -RebuildVenv # setup.bat -Force, then build
|
|
build.bat -SkipInstall # never provision: fail instead
|
|
build.bat -SkipSmokeTest # do not launch the produced EXE
|
|
build.bat -StrictSmokeTest # treat a 3D-context warning as a failure
|
|
```
|
|
|
|
Or from the repo root:
|
|
|
|
```powershell
|
|
powershell -ExecutionPolicy Bypass -File .\build\studio\build.ps1
|
|
```
|
|
|
|
`build.bat` always resolves `build.ps1` next to itself (cwd-independent) and forwards flags.
|
|
|
|
## What the build verifies, and why
|
|
|
|
Every silent failure this packaging has produced looked like a *successful* build, so the
|
|
script checks the things that only show up when the EXE runs:
|
|
|
|
| check | failure it prevents |
|
|
|---|---|
|
|
| the venv imports `orisys_app_shared`, `orisys_studio`, `orisys`, `OpenGL`, `cv2`, `PyInstaller` | a stale editable install still builds (the spec's `pathex` finds the modules) and produces an EXE that cannot start |
|
|
| `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 |
|
|
| **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
|
|
3D pane on, and restores (or removes) it afterwards. A *GL context* failure is reported as a
|
|
warning, because it is environment-dependent; `-StrictSmokeTest` turns it into a failure.
|
|
|
|
## Where the app's own code lives in the bundle
|
|
|
|
`_internal\` contains **native code and data files only**. Every pure-Python module —
|
|
`orisys_studio.*`, `orisys_app_shared.*`, `orisys.*` — is compiled into the `PYZ` archive that
|
|
sits *inside* `OrisysStudio.exe` (that is most of the EXE's ~13 MB). So there is deliberately
|
|
no `_internal\orisys_studio\` folder, and no `.py` files anywhere: PyInstaller ships bytecode,
|
|
never sources.
|
|
|
|
`_internal` therefore shows only these orisys-related paths:
|
|
|
|
```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.)
|
|
|
|
To list what is really in the archive:
|
|
|
|
```bat
|
|
.venv-build\Scripts\python.exe -m PyInstaller.utils.cliutils.archive_viewer ^
|
|
-l -r -b dist\OrisysStudio\OrisysStudio.exe
|
|
```
|
|
|
|
(The `PYZ-00.toc` under `build\_pyinstaller\orisys-studio\` is the same information at build
|
|
time, and `build\_smoke\*.log` is the runtime proof: "Studio shell started" and "3D scene
|
|
loaded" can only be printed if those modules imported.)
|
|
|
|
## Dependency notes
|
|
|
|
- App shared dep uses **`opencv-python-headless`** (PyQt owns the GUI). `setup.ps1` uninstalls
|
|
any `opencv-python` and pins `opencv-python-headless>=4.10,<5` (5.x has shipped wheels
|
|
without a `cv2` module) and verifies `import cv2`.
|
|
- **PyOpenGL** is the GL binding for the 3D pane and comes from the studio's `three-d` extra.
|
|
Add the `protected-assets` extra (`cryptography`) if sealed `.oryscene` packages are shipped.
|
|
- **Open3D stays excluded** (spec + build env): it is an offline geometry tool. The 3D pane is
|
|
Qt `QOpenGLWidget` + PyOpenGL.
|
|
- `constraints-build.txt` pins the toolchain. Move a pin only together with `setup.bat -Force`
|
|
and a passing `build.bat` — the smoke test is the acceptance evidence, not the file.
|
|
|
|
## GL note
|
|
|
|
The pane asks for the profile in `gl_profile` (`desktop33` by default, `gles30` otherwise). A
|
|
`desktop33` request needs a desktop GL 3.3 core context; on some sessions Qt falls back to its
|
|
ANGLE (GLES) backend, which cannot satisfy it, and the widget then reports no context on a
|
|
machine whose GPU is fine. `QT_OPENGL=desktop` is the workaround; the durable fix is to pin
|
|
the backend in `orisys_studio.main` before `QApplication` is constructed
|
|
(`Qt.AA_UseDesktopOpenGL` / `Qt.AA_UseOpenGLES`). `build.bat -StrictSmokeTest` makes this a
|
|
build failure.
|
|
|
|
## Size / startup
|
|
|
|
- **onedir** (not onefile): faster cold start; ship the whole `OrisysStudio` folder.
|
|
- Finger `assets/visualization/` is stripped from the shipped `config/` (~11 MB saved). Nothing
|
|
in the app reads it (`manifest.json`'s `viz_pose` is used only by the SDK examples); the 3D
|
|
pane needs `scene/*.oryscene` plus `assets/30fps-0703`, and the build checks both.
|
|
- Studio shell defers OpenCV / acquisition imports until Preview or Run.
|
|
|
|
Expect ~450 MB total (Qt + numpy/scipy/numba + OpenCV + PyOpenGL).
|
|
|
|
## Verification on a new machine
|
|
|
|
1. `setup.bat` (see its report: interpreter, conda or not, SDK source).
|
|
2. `build.bat` — its smoke test must print "the application started" and "the packaged 3D
|
|
scene loaded".
|
|
3. Launch `OrisysStudio.exe` and check theme/locale/prefs; `preference.json` and `outputs\`
|
|
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`.
|
|
|
|
## Editable run (unchanged)
|
|
|
|
```powershell
|
|
$env:PYTHONPATH = "D:\repo\orisys-app\shared\src;D:\repo\orisys-app\apps\studio\src"
|
|
python D:\repo\orisys-app\apps\studio\run.py
|
|
```
|