Dev #19
+5
-1
@@ -5,8 +5,12 @@ __pycache__/
|
||||
|
||||
# Packaging / build outputs
|
||||
/build/nuitka/
|
||||
/build/lib/orisys/
|
||||
/build/build/
|
||||
/build/bdist.*/
|
||||
/build/dist/
|
||||
/build/lib/
|
||||
/build/pyinstaller_work/
|
||||
/build/*.spec
|
||||
/dist/
|
||||
/dist_obfuscated/
|
||||
/dist_pyd/
|
||||
|
||||
@@ -4,6 +4,8 @@ Orisys SDK is a Python toolkit for optical tactile sensing. It provides image ca
|
||||
stitching, dense optical flow, Helmholtz-Hodge decomposition, contact/depth estimation,
|
||||
and several runnable examples for camera and video-based workflows.
|
||||
|
||||
PyPI: [https://pypi.org/project/orisys/](https://pypi.org/project/orisys/)
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `src/orisys/`: installable SDK package
|
||||
@@ -93,9 +95,8 @@ cfg = orisys.export_config("ddjx01", "./config/exported_config.json")
|
||||
|
||||
- Example entry points: `examples/README.md`
|
||||
- User documentation: `docs/get_started.md`
|
||||
- Packaging helper: `build/build_nuitka.py` (compiled SDK wheel)
|
||||
- Manylinux / PyPI binary wheel (Docker Desktop): `docs/publishing.md`
|
||||
- GUI standalone folder: `build/build_gui_win.bat` → `dist/OrisysDashboard/`
|
||||
- Build launcher: `python build/run.py` (see `build/README.md`)
|
||||
- Manylinux / PyPI binary wheel: `docs/publishing.md`
|
||||
- Specialized utilities: `tools/`
|
||||
|
||||
## Publishing (Linux manylinux)
|
||||
@@ -103,7 +104,7 @@ cfg = orisys.export_config("ddjx01", "./config/exported_config.json")
|
||||
To build PyPI-compliant CPython 3.10/3.11/3.12 Linux wheels from Windows Docker Desktop:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1
|
||||
python build/run.py manylinux
|
||||
```
|
||||
|
||||
Upload candidates land in `dist/repaired/`. Full TestPyPI → PyPI steps:
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['D:\\repo\\orisys-sdk\\examples\\finger_pipeline_3dview.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('D:\\repo\\orisys-sdk\\config', 'config'), ('D:\\repo\\orisys-sdk\\assets', 'assets')],
|
||||
hiddenimports=['PyQt5', 'pyqtgraph', 'open3d', 'cv2', 'orisys', 'gui', 'pipeline', 'viz', 'gui.app_paths', 'plotly', '_plotly_utils', 'open3d.cpu.pybind'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=['cupy', 'cupyx', 'pyopencl', 'tkinter', 'pip', 'wheel', 'matplotlib'],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='OrisysDashboard',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='OrisysDashboard',
|
||||
)
|
||||
@@ -0,0 +1,65 @@
|
||||
# Build tooling
|
||||
|
||||
Use one launcher from the repo root:
|
||||
|
||||
```bash
|
||||
python build/run.py <command>
|
||||
```
|
||||
|
||||
```text
|
||||
build/
|
||||
run.py << single entrypoint
|
||||
README.md
|
||||
common/ shared helpers (paths, pyproject, verify)
|
||||
wheel/ SDK .whl (Nuitka + manylinux Docker)
|
||||
gui/
|
||||
common/ shared PyInstaller driver
|
||||
ory_finger/ ORY-FINGER dashboard
|
||||
ory_mini/ ORY-MINI internal GUI (scaffold)
|
||||
ory_box/ ORY-BOX internal GUI (scaffold)
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### SDK wheel
|
||||
|
||||
```bash
|
||||
python build/run.py wheel-win
|
||||
python build/run.py wheel-manylinux
|
||||
|
||||
# Single Version
|
||||
python build/run.py wheel
|
||||
# Inside Docker
|
||||
python build/run.py wheel --target pypi-linux --verify
|
||||
```
|
||||
|
||||
Direct module paths (same behavior):
|
||||
|
||||
```bash
|
||||
python build/wheel/build.py
|
||||
build\wheel\win.bat
|
||||
.\build\wheel\docker\run.ps1
|
||||
```
|
||||
|
||||
### GUI
|
||||
|
||||
```bash
|
||||
python build/run.py gui finger
|
||||
python build/run.py gui mini # scaffold / NotImplemented
|
||||
python build/run.py gui box # scaffold / NotImplemented
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
python build/run.py verify path\to\orisys.whl
|
||||
python build/run.py verify --gui-dist --product ory_finger
|
||||
```
|
||||
|
||||
## Outputs
|
||||
|
||||
- Wheels: `SDK/<version>/`, `dist/repaired/`
|
||||
- Finger GUI: `dist/OrisysDashboard/`
|
||||
- Future mini/box: `dist/OryMini/`, `dist/OryBox/`
|
||||
|
||||
Generated work files under `build/nuitka/` and `build/pyinstaller_work/` are gitignored.
|
||||
@@ -1,2 +0,0 @@
|
||||
powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1
|
||||
powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1 -PythonVersions 3.10,3.12
|
||||
@@ -1,194 +0,0 @@
|
||||
"""Build a CPU-only standalone Windows folder for Orisys 3D dashboard using PyInstaller.
|
||||
|
||||
Usage:
|
||||
conda activate py310 # or a clean virtualenv with only required packages
|
||||
pip install pyinstaller
|
||||
python build/build_gui_win_pyinstaller.py
|
||||
|
||||
Output:
|
||||
dist/OrisysDashboard/OrisysDashboard.exe (+ bundled Python, DLLs, config/, assets/)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ---------- Configuration ----------
|
||||
ENTRY_SCRIPT = Path("examples/products/ory_finger/finger_pipeline_3dview.py").resolve()
|
||||
EXE_NAME = "OrisysDashboard.exe"
|
||||
DIST_NAME = "OrisysDashboard" # final folder name
|
||||
repo_root = Path(__file__).parent.parent
|
||||
|
||||
# Packages you want to force-include (PyInstaller often auto-detects them, but explicit avoids misses)
|
||||
HIDDEN_IMPORTS = [
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
"open3d",
|
||||
"cv2",
|
||||
"orisys",
|
||||
"gui",
|
||||
"pipeline",
|
||||
"viz",
|
||||
"gui.app_paths",
|
||||
"plotly",
|
||||
"_plotly_utils",
|
||||
# sometimes needed for open3d:
|
||||
"open3d.cpu.pybind", # adjust to actual module if needed
|
||||
]
|
||||
|
||||
# Modules to completely exclude (they won't be imported anyway if absent in venv)
|
||||
EXCLUDE_MODULES = [
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"tkinter",
|
||||
# "unittest",
|
||||
# "email",
|
||||
# "http",
|
||||
# "xmlrpc",
|
||||
# "pkg_resources",
|
||||
# "setuptools",
|
||||
"pip",
|
||||
"wheel",
|
||||
"matplotlib", # unless you use it
|
||||
# "scipy", # unless you use it
|
||||
]
|
||||
|
||||
# Data directories: (source relative to repo root, destination in bundle)
|
||||
DATA_DIRS = [
|
||||
("config", "config"),
|
||||
("assets", "assets"),
|
||||
]
|
||||
|
||||
# Optional: UPX directory (download from https://upx.github.io)
|
||||
UPX_DIR = r"C:\tools\upx" # set to None if not available
|
||||
# ---------- End Configuration ----------
|
||||
|
||||
|
||||
def clean_dist(dist_path: Path) -> None:
|
||||
if dist_path.exists():
|
||||
shutil.rmtree(dist_path)
|
||||
dist_path.mkdir(parents=True)
|
||||
|
||||
|
||||
def build_with_pyinstaller():
|
||||
# Ensure we are in a clean, minimal venv
|
||||
print("Using Python:", sys.executable)
|
||||
|
||||
# Output folder for PyInstaller work files
|
||||
work_dir = Path("build/pyinstaller_work")
|
||||
dist_dir = Path("dist") / DIST_NAME
|
||||
clean_dist(work_dir)
|
||||
clean_dist(dist_dir)
|
||||
|
||||
# Build PyInstaller command
|
||||
cmd = [
|
||||
sys.executable, "-m", "PyInstaller",
|
||||
"--onedir", # produce a folder, not a single exe
|
||||
"--name", DIST_NAME,
|
||||
f"--distpath={dist_dir.parent}",
|
||||
f"--workpath={work_dir}",
|
||||
f"--specpath=build",
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
"--windowed", # or "--console" if you need terminal
|
||||
]
|
||||
|
||||
# Add data files
|
||||
for src_rel, dest_rel in DATA_DIRS:
|
||||
src = Path(src_rel).resolve()
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory missing: {src}")
|
||||
cmd.append(f"--add-data={src};{dest_rel}")
|
||||
|
||||
# Logo file if exists
|
||||
logo = Path("logo.png")
|
||||
if logo.exists():
|
||||
cmd.append(f"--add-data={logo.resolve()};.")
|
||||
|
||||
# Hidden imports
|
||||
for mod in HIDDEN_IMPORTS:
|
||||
cmd.append(f"--hidden-import={mod}")
|
||||
|
||||
# Exclude modules
|
||||
for mod in EXCLUDE_MODULES:
|
||||
cmd.append(f"--exclude-module={mod}")
|
||||
|
||||
# UPX if available
|
||||
if UPX_DIR and Path(UPX_DIR).is_dir():
|
||||
cmd.append(f"--upx-dir={UPX_DIR}")
|
||||
|
||||
# Entry point
|
||||
cmd.append(str(ENTRY_SCRIPT))
|
||||
|
||||
# Environment (same as Nuitka script)
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(Path("src").resolve()) + os.pathsep + str(Path("examples").resolve()) + os.pathsep + env.get("PYTHONPATH", "")
|
||||
|
||||
print("\n=== Running PyInstaller ===")
|
||||
print("Command:", " ".join(cmd))
|
||||
subprocess.check_call(cmd, env=env)
|
||||
|
||||
# After build, copy data directories explicitly
|
||||
# PyInstaller output folder
|
||||
dist_dir = Path("dist") / DIST_NAME
|
||||
final_exe = dist_dir / EXE_NAME
|
||||
if not final_exe.is_file():
|
||||
raise RuntimeError(f"Executable not found at {final_exe}")
|
||||
print("\n=== Copying data directories ===")
|
||||
for src_rel, dest_rel in DATA_DIRS:
|
||||
src = repo_root / src_rel # use an absolute reference to repo root
|
||||
dest = dist_dir / dest_rel
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory not found: {src}")
|
||||
if dest.exists():
|
||||
shutil.rmtree(dest)
|
||||
shutil.copytree(src, dest, dirs_exist_ok=True) # Python 3.8+ use dirs_exist_ok=True
|
||||
print(f" {src} -> {dest}")
|
||||
|
||||
# Copy logo
|
||||
logo_src = repo_root / "logo.png"
|
||||
if logo_src.is_file():
|
||||
shutil.copy2(logo_src, dist_dir / "logo.png")
|
||||
print(f" Logo copied")
|
||||
|
||||
# Write README (same as Nuitka script)
|
||||
readme = dist_dir / "README.txt"
|
||||
readme.write_text(
|
||||
"\n".join([
|
||||
"Orisys Finger Dashboard (standalone)",
|
||||
"",
|
||||
"Run:",
|
||||
f" {EXE_NAME}",
|
||||
f" {EXE_NAME} --video path\\to\\video.mp4",
|
||||
f" {EXE_NAME} --config config\\dd02-ov.json",
|
||||
"",
|
||||
"Bundled folders:",
|
||||
" config/ sensor JSON configs",
|
||||
" assets/ finger mesh and visualization data",
|
||||
"",
|
||||
"CPU-only build (no CUDA/CuPy).",
|
||||
]),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
print(f"\n[SUCCESS] Standalone GUI folder: {dist_dir}")
|
||||
print(f"Executable: {final_exe}")
|
||||
|
||||
# Optional smoke test (same as Nuitka script)
|
||||
try:
|
||||
from verify import verify_gui_dist
|
||||
print("\n=== Smoke test ===")
|
||||
verify_gui_dist(dist_dir)
|
||||
except Exception as exc:
|
||||
print(f"\n[WARNING] Smoke test failed: {exc}")
|
||||
|
||||
return dist_dir
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build_with_pyinstaller()
|
||||
@@ -0,0 +1 @@
|
||||
"""Shared build utilities used by wheel and GUI packaging targets."""
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Ensure ``build/`` is importable as the root for ``common``, ``wheel``, and ``gui``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def ensure_build_root_on_path() -> Path:
|
||||
root = str(_BUILD_ROOT)
|
||||
if root not in sys.path:
|
||||
sys.path.insert(0, root)
|
||||
return _BUILD_ROOT
|
||||
@@ -1,15 +1,17 @@
|
||||
"""Repository and Nuitka build output paths."""
|
||||
"""Repository and packaging output paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
# build/common/paths.py → parents: common, build, repo
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
NUITKA_COMPILE_DIR = REPO_ROOT / "build" / "nuitka" / "compile"
|
||||
NUITKA_WHEEL_DIR = REPO_ROOT / "build" / "nuitka" / "wheel"
|
||||
NUITKA_GUI_DIR = REPO_ROOT / "build" / "nuitka" / "gui"
|
||||
GUI_DIST_DIR = REPO_ROOT / "dist" / "OrisysDashboard"
|
||||
PYINSTALLER_WORK_DIR = REPO_ROOT / "build" / "pyinstaller_work"
|
||||
|
||||
|
||||
def repo_path(*parts: str) -> Path:
|
||||
@@ -23,3 +25,7 @@ def sdk_dir(version: str) -> Path:
|
||||
def repaired_wheel_dir() -> Path:
|
||||
"""Directory for auditwheel-repaired wheels under the Nuitka wheel build tree."""
|
||||
return NUITKA_WHEEL_DIR / "repaired"
|
||||
|
||||
|
||||
def gui_dist_dir(dist_name: str) -> Path:
|
||||
return REPO_ROOT / "dist" / dist_name
|
||||
@@ -7,6 +7,10 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
|
||||
def _load_toml(text: str) -> dict:
|
||||
if sys.version_info >= (3, 11):
|
||||
@@ -90,7 +94,7 @@ def bundle_items(repo_root: Path) -> list[dict]:
|
||||
|
||||
def bundle_data_files(repo_root: Path) -> list[tuple[str, list[str]]]:
|
||||
"""Map bundle items marked dev_install=true to setuptools data_files entries."""
|
||||
from wheel_layout import resolve_bundle_source_files
|
||||
from wheel.layout import resolve_bundle_source_files
|
||||
|
||||
grouped: dict[str, list[str]] = {}
|
||||
for item in bundle_items(repo_root):
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Post-build smoke tests for wheels and product GUI folders."""
|
||||
|
||||
from common.verify.gui import GUI_PRODUCTS, verify_gui_dist
|
||||
from common.verify.wheel import verify_built_wheel
|
||||
|
||||
__all__ = ["GUI_PRODUCTS", "verify_built_wheel", "verify_gui_dist"]
|
||||
@@ -0,0 +1,34 @@
|
||||
"""CLI for packaged artifact verification."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from common.verify import GUI_PRODUCTS, verify_built_wheel, verify_gui_dist
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Verify packaged Orisys artifacts.")
|
||||
parser.add_argument("--gui-dist", action="store_true", help="Verify a GUI dist folder")
|
||||
parser.add_argument(
|
||||
"--product",
|
||||
choices=sorted(GUI_PRODUCTS),
|
||||
default="ory_finger",
|
||||
help="GUI product profile (default: ory_finger)",
|
||||
)
|
||||
parser.add_argument("--dist-dir", type=Path, default=None, help="Override GUI dist folder")
|
||||
parser.add_argument("wheel", nargs="?", type=Path, help="Wheel path for import smoke test")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.gui_dist:
|
||||
verify_gui_dist(args.dist_dir, product=args.product)
|
||||
return 0
|
||||
if args.wheel is None:
|
||||
parser.error("Provide a wheel path or pass --gui-dist")
|
||||
verify_built_wheel(args.wheel)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Smoke tests for standalone GUI product folders."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import GUI_DIST_DIR, gui_dist_dir
|
||||
|
||||
# Product profiles for GUI packaging verification.
|
||||
GUI_PRODUCTS: dict[str, dict] = {
|
||||
"ory_finger": {
|
||||
"dist_name": "OrisysDashboard",
|
||||
"exe_name": "OrisysDashboard.exe",
|
||||
"required_relpaths": [
|
||||
"config/dd02-ov.json",
|
||||
"assets/finger/30fps-0703/mesh.npz",
|
||||
"assets/finger/30fps-0703/world_to_cam.npy",
|
||||
],
|
||||
},
|
||||
"ory_mini": {
|
||||
"dist_name": "OryMini",
|
||||
"exe_name": "OryMini.exe",
|
||||
"required_relpaths": [
|
||||
"config/mini.json",
|
||||
],
|
||||
},
|
||||
"ory_box": {
|
||||
"dist_name": "OryBox",
|
||||
"exe_name": "OryBox.exe",
|
||||
"required_relpaths": [
|
||||
"config/ddjx01.json",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def verify_gui_dist(
|
||||
dist_dir: Path | None = None,
|
||||
*,
|
||||
product: str = "ory_finger",
|
||||
) -> None:
|
||||
"""Smoke-test a standalone GUI folder for the given product."""
|
||||
if product not in GUI_PRODUCTS:
|
||||
raise ValueError(f"Unknown GUI product {product!r}; choose from {sorted(GUI_PRODUCTS)}")
|
||||
|
||||
profile = GUI_PRODUCTS[product]
|
||||
if dist_dir is None:
|
||||
dist_dir = gui_dist_dir(profile["dist_name"]) if product != "ory_finger" else GUI_DIST_DIR
|
||||
dist_dir = dist_dir.resolve()
|
||||
|
||||
exe = dist_dir / profile["exe_name"]
|
||||
if not exe.is_file():
|
||||
raise FileNotFoundError(f"GUI executable not found: {exe}")
|
||||
|
||||
required = [dist_dir / rel for rel in profile["required_relpaths"]]
|
||||
missing = [str(path.relative_to(dist_dir)) for path in required if not path.is_file()]
|
||||
if missing:
|
||||
raise FileNotFoundError(f"Missing bundled files in {dist_dir}: {', '.join(missing)}")
|
||||
|
||||
subprocess.check_call([str(exe), "--help"], cwd=dist_dir)
|
||||
print(f"GUI dist smoke test OK ({product}): {exe}")
|
||||
@@ -1,16 +1,13 @@
|
||||
"""Post-build smoke tests for packaged wheels and standalone GUI builds."""
|
||||
"""Isolated venv smoke tests for packaged orisys wheels."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import venv
|
||||
from pathlib import Path
|
||||
|
||||
from paths import GUI_DIST_DIR
|
||||
|
||||
# Public SDK surface that must import from a released wheel.
|
||||
_SMOKE_IMPORT_SCRIPT = """
|
||||
import orisys
|
||||
@@ -59,42 +56,3 @@ def verify_built_wheel(wheel_path: Path) -> None:
|
||||
[python, "-m", "pip", "install", "-q", str(wheel_path)],
|
||||
)
|
||||
subprocess.check_call([python, "-c", _SMOKE_IMPORT_SCRIPT])
|
||||
|
||||
|
||||
def verify_gui_dist(dist_dir: Path | None = None) -> None:
|
||||
"""Smoke-test a standalone GUI folder produced by build_gui_win.py."""
|
||||
dist_dir = (dist_dir or GUI_DIST_DIR).resolve()
|
||||
exe = dist_dir / "OrisysDashboard.exe"
|
||||
if not exe.is_file():
|
||||
raise FileNotFoundError(f"GUI executable not found: {exe}")
|
||||
|
||||
required = [
|
||||
dist_dir / "config" / "dd02-ov.json",
|
||||
dist_dir / "assets" / "finger" / "visualization" / "world_to_cam.npy",
|
||||
dist_dir / "assets" / "finger" / "30fps-0703" / "mesh.npz",
|
||||
]
|
||||
missing = [str(path.relative_to(dist_dir)) for path in required if not path.is_file()]
|
||||
if missing:
|
||||
raise FileNotFoundError(f"Missing bundled files in {dist_dir}: {', '.join(missing)}")
|
||||
|
||||
subprocess.check_call([str(exe), "--help"], cwd=dist_dir)
|
||||
print(f"GUI dist smoke test OK: {exe}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Verify packaged Orisys artifacts.")
|
||||
parser.add_argument("--gui-dist", action="store_true", help="Verify dist/OrisysDashboard/")
|
||||
parser.add_argument("--dist-dir", type=Path, default=None, help="Override GUI dist folder")
|
||||
parser.add_argument("wheel", nargs="?", type=Path, help="Wheel path for import smoke test")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.gui_dist:
|
||||
verify_gui_dist(args.dist_dir)
|
||||
return
|
||||
if args.wheel is None:
|
||||
parser.error("Provide a wheel path or pass --gui-dist")
|
||||
verify_built_wheel(args.wheel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
"""Standalone GUI packaging targets (PyInstaller)."""
|
||||
@@ -0,0 +1 @@
|
||||
"""Shared helpers for product GUI builds."""
|
||||
@@ -0,0 +1,141 @@
|
||||
"""Shared PyInstaller driver for product GUI standalone folders."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import PYINSTALLER_WORK_DIR, REPO_ROOT, gui_dist_dir
|
||||
|
||||
|
||||
@dataclass
|
||||
class GuiBuildConfig:
|
||||
product: str
|
||||
entry_script: Path
|
||||
exe_name: str
|
||||
dist_name: str
|
||||
hidden_imports: list[str] = field(default_factory=list)
|
||||
exclude_modules: list[str] = field(default_factory=list)
|
||||
data_dirs: list[tuple[str, str]] = field(default_factory=list)
|
||||
readme_lines: list[str] = field(default_factory=list)
|
||||
windowed: bool = True
|
||||
upx_dir: str | None = r"C:\tools\upx"
|
||||
logo_name: str = "logo.png"
|
||||
work_dir: Path = PYINSTALLER_WORK_DIR
|
||||
spec_dir: Path = REPO_ROOT / "build" / "pyinstaller_work"
|
||||
|
||||
|
||||
def clean_dir(path: Path) -> None:
|
||||
if path.exists():
|
||||
shutil.rmtree(path)
|
||||
path.mkdir(parents=True)
|
||||
|
||||
|
||||
def build_with_pyinstaller(
|
||||
config: GuiBuildConfig,
|
||||
*,
|
||||
verify_fn: Callable[[Path], None] | None = None,
|
||||
) -> Path:
|
||||
"""Run PyInstaller for one GUI product and copy bundled data folders."""
|
||||
print("Using Python:", sys.executable)
|
||||
print(f"Building GUI product: {config.product}")
|
||||
|
||||
dist_dir = gui_dist_dir(config.dist_name)
|
||||
clean_dir(config.work_dir)
|
||||
clean_dir(dist_dir)
|
||||
config.spec_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
entry = config.entry_script
|
||||
if not entry.is_file():
|
||||
raise FileNotFoundError(f"GUI entry script not found: {entry}")
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"PyInstaller",
|
||||
"--onedir",
|
||||
"--name",
|
||||
config.dist_name,
|
||||
f"--distpath={dist_dir.parent}",
|
||||
f"--workpath={config.work_dir}",
|
||||
f"--specpath={config.spec_dir}",
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
"--windowed" if config.windowed else "--console",
|
||||
]
|
||||
|
||||
for src_rel, dest_rel in config.data_dirs:
|
||||
src = (REPO_ROOT / src_rel).resolve()
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory missing: {src}")
|
||||
cmd.append(f"--add-data={src};{dest_rel}")
|
||||
|
||||
logo = REPO_ROOT / config.logo_name
|
||||
if logo.is_file():
|
||||
cmd.append(f"--add-data={logo.resolve()};.")
|
||||
|
||||
for mod in config.hidden_imports:
|
||||
cmd.append(f"--hidden-import={mod}")
|
||||
for mod in config.exclude_modules:
|
||||
cmd.append(f"--exclude-module={mod}")
|
||||
|
||||
if config.upx_dir and Path(config.upx_dir).is_dir():
|
||||
cmd.append(f"--upx-dir={config.upx_dir}")
|
||||
|
||||
cmd.append(str(entry.resolve()))
|
||||
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = (
|
||||
str((REPO_ROOT / "src").resolve())
|
||||
+ os.pathsep
|
||||
+ str((REPO_ROOT / "examples").resolve())
|
||||
+ os.pathsep
|
||||
+ env.get("PYTHONPATH", "")
|
||||
)
|
||||
|
||||
print("\n=== Running PyInstaller ===")
|
||||
print("Command:", " ".join(cmd))
|
||||
subprocess.check_call(cmd, env=env, cwd=REPO_ROOT)
|
||||
|
||||
final_exe = dist_dir / config.exe_name
|
||||
if not final_exe.is_file():
|
||||
raise RuntimeError(f"Executable not found at {final_exe}")
|
||||
|
||||
print("\n=== Copying data directories ===")
|
||||
for src_rel, dest_rel in config.data_dirs:
|
||||
src = REPO_ROOT / src_rel
|
||||
dest = dist_dir / dest_rel
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory not found: {src}")
|
||||
if dest.exists():
|
||||
shutil.rmtree(dest)
|
||||
shutil.copytree(src, dest, dirs_exist_ok=True)
|
||||
print(f" {src} -> {dest}")
|
||||
|
||||
if logo.is_file():
|
||||
shutil.copy2(logo, dist_dir / config.logo_name)
|
||||
print(" Logo copied")
|
||||
|
||||
if config.readme_lines:
|
||||
(dist_dir / "README.txt").write_text("\n".join(config.readme_lines) + "\n", encoding="utf-8")
|
||||
|
||||
print(f"\n[SUCCESS] Standalone GUI folder: {dist_dir}")
|
||||
print(f"Executable: {final_exe}")
|
||||
|
||||
if verify_fn is not None:
|
||||
try:
|
||||
print("\n=== Smoke test ===")
|
||||
verify_fn(dist_dir)
|
||||
except Exception as exc:
|
||||
print(f"\n[WARNING] Smoke test failed: {exc}")
|
||||
|
||||
return dist_dir
|
||||
@@ -0,0 +1 @@
|
||||
"""ORY-BOX internal calibration/testing GUI packaging (scaffold)."""
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Scaffold for ORY-BOX internal GUI packaging.
|
||||
|
||||
Not implemented yet. When ready, wire a dedicated calibration/testing entry
|
||||
script and call ``gui.common.pyinstaller_driver.build_with_pyinstaller``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from gui.ory_box.config import DIST_NAME, ENTRY_SCRIPT, make_config
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = make_config()
|
||||
raise NotImplementedError(
|
||||
"ORY-BOX GUI packaging is a scaffold only. "
|
||||
f"Planned dist={DIST_NAME!r}, entry={ENTRY_SCRIPT}. "
|
||||
"Implement the internal calibration/testing UI entry, then call "
|
||||
"gui.common.pyinstaller_driver.build_with_pyinstaller(config)."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Product configuration scaffold for the ORY-BOX internal GUI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import REPO_ROOT
|
||||
from gui.common.pyinstaller_driver import GuiBuildConfig
|
||||
|
||||
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_box" / "basic_pipeline.py"
|
||||
EXE_NAME = "OryBox.exe"
|
||||
DIST_NAME = "OryBox"
|
||||
|
||||
HIDDEN_IMPORTS = [
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
"cv2",
|
||||
"orisys",
|
||||
"gui",
|
||||
"pipeline",
|
||||
"viz",
|
||||
]
|
||||
|
||||
EXCLUDE_MODULES = [
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"open3d",
|
||||
"tkinter",
|
||||
"pip",
|
||||
"wheel",
|
||||
]
|
||||
|
||||
DATA_DIRS = [
|
||||
("config", "config"),
|
||||
]
|
||||
|
||||
README_LINES = [
|
||||
"ORY-BOX internal calibration/testing GUI (scaffold)",
|
||||
"",
|
||||
"This product packaging target is reserved for a future internal tool.",
|
||||
"Entry point placeholder:",
|
||||
f" {ENTRY_SCRIPT.as_posix()}",
|
||||
]
|
||||
|
||||
|
||||
def make_config() -> GuiBuildConfig:
|
||||
return GuiBuildConfig(
|
||||
product="ory_box",
|
||||
entry_script=ENTRY_SCRIPT,
|
||||
exe_name=EXE_NAME,
|
||||
dist_name=DIST_NAME,
|
||||
hidden_imports=list(HIDDEN_IMPORTS),
|
||||
exclude_modules=list(EXCLUDE_MODULES),
|
||||
data_dirs=list(DATA_DIRS),
|
||||
readme_lines=list(README_LINES),
|
||||
windowed=False,
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
"""ORY-FINGER standalone GUI packaging."""
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Build the ORY-FINGER standalone Windows GUI folder with PyInstaller.
|
||||
|
||||
Usage:
|
||||
python build/run.py gui finger
|
||||
python build/gui/ory_finger/build.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.verify.gui import verify_gui_dist
|
||||
from gui.common.pyinstaller_driver import build_with_pyinstaller
|
||||
from gui.ory_finger.config import make_config
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = make_config()
|
||||
build_with_pyinstaller(
|
||||
config,
|
||||
verify_fn=lambda dist_dir: verify_gui_dist(dist_dir, product="ory_finger"),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,75 @@
|
||||
"""Product configuration for the ORY-FINGER Windows dashboard."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import REPO_ROOT
|
||||
from gui.common.pyinstaller_driver import GuiBuildConfig
|
||||
|
||||
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_finger" / "finger_pipeline_3dview.py"
|
||||
EXE_NAME = "OrisysDashboard.exe"
|
||||
DIST_NAME = "OrisysDashboard"
|
||||
|
||||
HIDDEN_IMPORTS = [
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
"open3d",
|
||||
"cv2",
|
||||
"orisys",
|
||||
"gui",
|
||||
"pipeline",
|
||||
"viz",
|
||||
"gui.app_paths",
|
||||
"plotly",
|
||||
"_plotly_utils",
|
||||
"open3d.cpu.pybind",
|
||||
]
|
||||
|
||||
EXCLUDE_MODULES = [
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"tkinter",
|
||||
"pip",
|
||||
"wheel",
|
||||
"matplotlib",
|
||||
]
|
||||
|
||||
DATA_DIRS = [
|
||||
("config", "config"),
|
||||
("assets", "assets"),
|
||||
]
|
||||
|
||||
README_LINES = [
|
||||
"Orisys Finger Dashboard (standalone)",
|
||||
"",
|
||||
"Run:",
|
||||
f" {EXE_NAME}",
|
||||
f" {EXE_NAME} --video path\\to\\video.mp4",
|
||||
f" {EXE_NAME} --config config\\dd02-ov.json",
|
||||
"",
|
||||
"Bundled folders:",
|
||||
" config/ sensor JSON configs",
|
||||
" assets/ finger mesh and visualization data",
|
||||
"",
|
||||
"CPU-only build (no CUDA/CuPy).",
|
||||
]
|
||||
|
||||
|
||||
def make_config() -> GuiBuildConfig:
|
||||
return GuiBuildConfig(
|
||||
product="ory_finger",
|
||||
entry_script=ENTRY_SCRIPT,
|
||||
exe_name=EXE_NAME,
|
||||
dist_name=DIST_NAME,
|
||||
hidden_imports=list(HIDDEN_IMPORTS),
|
||||
exclude_modules=list(EXCLUDE_MODULES),
|
||||
data_dirs=list(DATA_DIRS),
|
||||
readme_lines=list(README_LINES),
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
"""ORY-MINI internal calibration/testing GUI packaging (scaffold)."""
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Scaffold for ORY-MINI internal GUI packaging.
|
||||
|
||||
Not implemented yet. When ready, wire a dedicated calibration/testing entry
|
||||
script and call ``gui.common.pyinstaller_driver.build_with_pyinstaller``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from gui.ory_mini.config import DIST_NAME, ENTRY_SCRIPT, make_config
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = make_config()
|
||||
raise NotImplementedError(
|
||||
"ORY-MINI GUI packaging is a scaffold only. "
|
||||
f"Planned dist={DIST_NAME!r}, entry={ENTRY_SCRIPT}. "
|
||||
"Implement the internal calibration/testing UI entry, then call "
|
||||
"gui.common.pyinstaller_driver.build_with_pyinstaller(config)."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,63 @@
|
||||
"""Product configuration scaffold for the ORY-MINI internal GUI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import REPO_ROOT
|
||||
from gui.common.pyinstaller_driver import GuiBuildConfig
|
||||
|
||||
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_mini" / "basic_pipeline.py"
|
||||
EXE_NAME = "OryMini.exe"
|
||||
DIST_NAME = "OryMini"
|
||||
|
||||
HIDDEN_IMPORTS = [
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
"cv2",
|
||||
"orisys",
|
||||
"gui",
|
||||
"pipeline",
|
||||
"viz",
|
||||
]
|
||||
|
||||
EXCLUDE_MODULES = [
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"open3d",
|
||||
"tkinter",
|
||||
"pip",
|
||||
"wheel",
|
||||
]
|
||||
|
||||
DATA_DIRS = [
|
||||
("config", "config"),
|
||||
]
|
||||
|
||||
README_LINES = [
|
||||
"ORY-MINI internal calibration/testing GUI (scaffold)",
|
||||
"",
|
||||
"This product packaging target is reserved for a future internal tool.",
|
||||
"Entry point placeholder:",
|
||||
f" {ENTRY_SCRIPT.as_posix()}",
|
||||
]
|
||||
|
||||
|
||||
def make_config() -> GuiBuildConfig:
|
||||
return GuiBuildConfig(
|
||||
product="ory_mini",
|
||||
entry_script=ENTRY_SCRIPT,
|
||||
exe_name=EXE_NAME,
|
||||
dist_name=DIST_NAME,
|
||||
hidden_imports=list(HIDDEN_IMPORTS),
|
||||
exclude_modules=list(EXCLUDE_MODULES),
|
||||
data_dirs=list(DATA_DIRS),
|
||||
readme_lines=list(README_LINES),
|
||||
windowed=False,
|
||||
)
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
"""Unified Orisys packaging entrypoint.
|
||||
|
||||
Usage (from repo root):
|
||||
|
||||
python build/run.py wheel [--target internal|pypi-linux] [--verify]
|
||||
python build/run.py wheel-win
|
||||
python build/run.py wheel-manylinux [PowerShell args...]
|
||||
python build/run.py gui finger|mini|box
|
||||
python build/run.py verify [wheel.whl]
|
||||
python build/run.py verify --gui-dist [--product ory_finger]
|
||||
|
||||
See build/README.md for details.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_BUILD_ROOT = Path(__file__).resolve().parent
|
||||
_REPO_ROOT = _BUILD_ROOT.parent
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
|
||||
def _run_python(script: Path, argv: list[str]) -> int:
|
||||
cmd = [sys.executable, str(script), *argv]
|
||||
return subprocess.call(cmd, cwd=_REPO_ROOT)
|
||||
|
||||
|
||||
def _cmd_wheel(argv: list[str]) -> int:
|
||||
return _run_python(_BUILD_ROOT / "wheel" / "build.py", argv)
|
||||
|
||||
|
||||
def _cmd_wheel_win(argv: list[str]) -> int:
|
||||
bat = _BUILD_ROOT / "wheel" / "win.bat"
|
||||
return subprocess.call(["cmd", "/c", str(bat), *argv], cwd=_REPO_ROOT)
|
||||
|
||||
|
||||
def _cmd_manylinux(argv: list[str]) -> int:
|
||||
ps1 = _BUILD_ROOT / "wheel" / "docker" / "run.ps1"
|
||||
cmd = [
|
||||
"powershell",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-File",
|
||||
str(ps1),
|
||||
*argv,
|
||||
]
|
||||
return subprocess.call(cmd, cwd=_REPO_ROOT)
|
||||
|
||||
|
||||
def _cmd_gui(argv: list[str]) -> int:
|
||||
parser = argparse.ArgumentParser(prog="build/run.py gui")
|
||||
parser.add_argument(
|
||||
"product",
|
||||
choices=("finger", "mini", "box", "ory_finger", "ory_mini", "ory_box"),
|
||||
help="GUI product to package",
|
||||
)
|
||||
args, rest = parser.parse_known_args(argv)
|
||||
product = {
|
||||
"finger": "ory_finger",
|
||||
"mini": "ory_mini",
|
||||
"box": "ory_box",
|
||||
}.get(args.product, args.product)
|
||||
return _run_python(_BUILD_ROOT / "gui" / product / "build.py", rest)
|
||||
|
||||
|
||||
def _cmd_verify(argv: list[str]) -> int:
|
||||
from common.verify.cli import main as verify_main
|
||||
|
||||
return verify_main(argv)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
argv = list(sys.argv[1:] if argv is None else argv)
|
||||
if not argv or argv[0] in ("-h", "--help"):
|
||||
print(__doc__)
|
||||
print("Commands: wheel | wheel-win | manylinux | gui | verify")
|
||||
print("Examples:")
|
||||
print(" python build/run.py wheel --help")
|
||||
print(" python build/run.py gui finger")
|
||||
print(" python build/run.py manylinux -PythonVersions 3.10")
|
||||
return 0
|
||||
|
||||
command, rest = argv[0], argv[1:]
|
||||
dispatch = {
|
||||
"wheel": _cmd_wheel,
|
||||
"wheel-win": _cmd_wheel_win,
|
||||
"wheel-manylinux": _cmd_manylinux,
|
||||
"gui": _cmd_gui,
|
||||
"verify": _cmd_verify,
|
||||
}
|
||||
if command not in dispatch:
|
||||
print(f"Unknown command: {command!r}", file=sys.stderr)
|
||||
print("Choose from: wheel, wheel-win, manylinux, gui, verify", file=sys.stderr)
|
||||
return 2
|
||||
return dispatch[command](rest)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1 @@
|
||||
"""Nuitka-compiled SDK wheel packaging."""
|
||||
@@ -11,8 +11,9 @@ Targets:
|
||||
pypi-linux – PyPI binary wheel; requires Linux + CPython 3.10/3.11/3.12 + compliant tag.
|
||||
|
||||
Run from repo root:
|
||||
python build/build_nuitka.py
|
||||
python build/build_nuitka.py --target pypi-linux
|
||||
python build/run.py wheel
|
||||
python build/run.py wheel --target pypi-linux
|
||||
python build/wheel/build.py --target pypi-linux
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -24,18 +25,22 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from nuitka_compile import compile_orisys, copy_compiled_orisys, install_nuitka
|
||||
from paths import NUITKA_COMPILE_DIR, NUITKA_WHEEL_DIR, REPO_ROOT, repaired_wheel_dir, repo_path
|
||||
from pyproject_utils import bundle_items, extras_require, get_version, install_requires
|
||||
from verify import verify_built_wheel
|
||||
from wheel_build import (
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import NUITKA_COMPILE_DIR, NUITKA_WHEEL_DIR, REPO_ROOT, repaired_wheel_dir, repo_path
|
||||
from common.pyproject_utils import bundle_items, extras_require, get_version, install_requires
|
||||
from common.verify.wheel import verify_built_wheel
|
||||
from wheel.compile import compile_orisys, copy_compiled_orisys, install_nuitka
|
||||
from wheel.layout import copy_bundle_items, copy_orisys_blend, package_data_from_bundle
|
||||
from wheel.package import (
|
||||
is_compliant_manylinux_wheel,
|
||||
publish_wheel_to_sdk,
|
||||
repair_linux_wheel,
|
||||
run_bdist_wheel,
|
||||
write_wheel_setup,
|
||||
)
|
||||
from wheel_layout import copy_bundle_items, copy_orisys_blend, package_data_from_bundle
|
||||
|
||||
VALID_TARGETS = ("internal", "pypi-linux")
|
||||
# ABI tags that manylinux images ship under /opt/python/cpXYY-cpXYY.
|
||||
@@ -8,7 +8,11 @@ import sys
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from pathlib import Path
|
||||
|
||||
from paths import repo_path
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import repo_path
|
||||
|
||||
NUITKA_INCLUDE_PACKAGES = (
|
||||
"orisys",
|
||||
@@ -23,4 +23,4 @@ RUN for PYBIN in \
|
||||
done
|
||||
|
||||
WORKDIR /src
|
||||
ENTRYPOINT ["/bin/bash", "/src/build/manylinux_entrypoint.sh"]
|
||||
ENTRYPOINT ["/bin/bash", "/src/build/wheel/docker/entrypoint.sh"]
|
||||
@@ -42,7 +42,7 @@ for tag in "${TAG_ARRAY[@]}"; do
|
||||
|
||||
"${PYBIN}/python" -m pip install -q --upgrade pip setuptools wheel auditwheel
|
||||
|
||||
if "${PYBIN}/python" build/build_nuitka.py --target "${TARGET}"; then
|
||||
if "${PYBIN}/python" build/wheel/build.py --target "${TARGET}"; then
|
||||
BUILT+=("${tag}")
|
||||
else
|
||||
echo "[ERROR] Build failed for ${tag}" >&2
|
||||
@@ -1,17 +1,11 @@
|
||||
# Build PyPI-compliant manylinux wheels for CPython 3.10/3.11/3.12 using Docker Desktop.
|
||||
#
|
||||
# Usage (from repo root, PowerShell):
|
||||
# .\build\build_manylinux_docker.ps1
|
||||
# .\build\build_manylinux_docker.ps1 -PythonVersions 3.10,3.11,3.12
|
||||
# .\build\build_manylinux_docker.ps1 -PythonVersions 3.11
|
||||
# .\build\build_manylinux_docker.ps1 -SkipImageBuild
|
||||
# .\build\wheel\docker\run.ps1
|
||||
# .\build\build_manylinux_docker.ps1 # compatibility wrapper
|
||||
#
|
||||
# From cmd.exe:
|
||||
# powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Docker Desktop running in Linux container mode
|
||||
# - Enough disk for the manylinux image (~2GB+)
|
||||
|
||||
param(
|
||||
[ValidateSet("pypi-linux", "internal")]
|
||||
@@ -39,7 +33,7 @@ function Convert-ToManylinuxTag {
|
||||
}
|
||||
}
|
||||
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..\..")).Path
|
||||
Set-Location $RepoRoot
|
||||
|
||||
$tags = @()
|
||||
@@ -67,15 +61,17 @@ if ($dockerInfo -ne "linux") {
|
||||
throw "Docker server OS is '$dockerInfo'. Switch Docker Desktop to Linux containers."
|
||||
}
|
||||
|
||||
$entrypoint = Join-Path $RepoRoot "build\manylinux_entrypoint.sh"
|
||||
$entrypoint = Join-Path $RepoRoot "build\wheel\docker\entrypoint.sh"
|
||||
# Normalize CRLF -> LF so bash in the container can execute the script.
|
||||
$raw = [System.IO.File]::ReadAllText($entrypoint)
|
||||
$normalized = $raw -replace "`r`n", "`n" -replace "`r", "`n"
|
||||
[System.IO.File]::WriteAllText($entrypoint, $normalized)
|
||||
|
||||
$dockerfile = "build/wheel/docker/Dockerfile.manylinux"
|
||||
|
||||
if (-not $SkipImageBuild) {
|
||||
Write-Host "`n=== Building Docker image ==="
|
||||
docker build -f build/Dockerfile.manylinux -t $ImageName .
|
||||
docker build -f $dockerfile -t $ImageName .
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "docker build failed"
|
||||
}
|
||||
@@ -8,7 +8,11 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from paths import sdk_dir
|
||||
_BUILD_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_BUILD_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_BUILD_ROOT))
|
||||
|
||||
from common.paths import sdk_dir
|
||||
|
||||
# PyPI rejects bare tags like "manylinux_x86_64"; require a glibc ABI version.
|
||||
_MANYLINUX_TAG_RE = re.compile(
|
||||
@@ -3,7 +3,7 @@ REM Build Nuitka wheels for Python 3.10, 3.11, and 3.12.
|
||||
REM Expects conda envs named py310, py311, py312 (or set CONDA_ENVS_ROOT).
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "%~dp0\.."
|
||||
cd /d "%~dp0\..\.."
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Could not change to repository root.
|
||||
exit /b 1
|
||||
@@ -32,7 +32,7 @@ for %%E in (py310 py311 py312) do (
|
||||
|
||||
set "OK=0"
|
||||
if !HAS_CONDA! equ 1 (
|
||||
call conda run --no-capture-output -n %%E python build\build_nuitka.py
|
||||
call conda run --no-capture-output -n %%E python build\wheel\build.py
|
||||
if not errorlevel 1 set "OK=1"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ for %%E in (py310 py311 py312) do (
|
||||
if not exist "!PYEXE!" (
|
||||
echo ERROR: Python not found: !PYEXE!
|
||||
) else (
|
||||
"!PYEXE!" build\build_nuitka.py
|
||||
"!PYEXE!" build\wheel\build.py
|
||||
if not errorlevel 1 set "OK=1"
|
||||
)
|
||||
)
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
"dis_preset": "medium",
|
||||
"dis_mode": 3,
|
||||
"algorithm_list": ["raft", "dis"],
|
||||
"hhd_downsample": 3
|
||||
"hhd_downsample": 1
|
||||
},
|
||||
"contact": {
|
||||
"min_contour_area": 3000,
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"dis_preset": "medium",
|
||||
"dis_mode": 3,
|
||||
"algorithm_list": ["raft", "dis"],
|
||||
"hhd_downsample": 3,
|
||||
"hhd_downsample": 1,
|
||||
"backend": "cpu"
|
||||
},
|
||||
"contact": {
|
||||
|
||||
+23
-19
@@ -40,35 +40,35 @@ docker run --rm hello-world
|
||||
|
||||
## Build manylinux wheels (3.10 / 3.11 / 3.12)
|
||||
|
||||
From the repository root. Prefer PowerShell, or from **cmd.exe**:
|
||||
From the repository root:
|
||||
|
||||
```bat
|
||||
powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1
|
||||
```bash
|
||||
python build/run.py manylinux
|
||||
```
|
||||
|
||||
PowerShell:
|
||||
Or call the Docker helper directly:
|
||||
|
||||
```powershell
|
||||
.\build\build_manylinux_docker.ps1
|
||||
.\build\wheel\docker\run.ps1
|
||||
```
|
||||
|
||||
Default builds **all three** ABIs. To build a subset:
|
||||
|
||||
```powershell
|
||||
.\build\build_manylinux_docker.ps1 -PythonVersions 3.10,3.12
|
||||
.\build\build_manylinux_docker.ps1 -PythonVersions 3.11
|
||||
python build/run.py manylinux -PythonVersions 3.10,3.12
|
||||
python build/run.py manylinux -PythonVersions 3.11
|
||||
```
|
||||
|
||||
What this does:
|
||||
|
||||
1. Builds `orisys-manylinux` from [`build/Dockerfile.manylinux`](../build/Dockerfile.manylinux)
|
||||
1. Builds `orisys-manylinux` from [`build/wheel/docker/Dockerfile.manylinux`](../build/wheel/docker/Dockerfile.manylinux)
|
||||
(`quay.io/pypa/manylinux_2_28_x86_64` with Nuitka/auditwheel in cp310/cp311/cp312).
|
||||
2. Runs [`build/manylinux_entrypoint.sh`](../build/manylinux_entrypoint.sh), which loops:
|
||||
2. Runs [`build/wheel/docker/entrypoint.sh`](../build/wheel/docker/entrypoint.sh), which loops:
|
||||
|
||||
```bash
|
||||
/opt/python/cp310-cp310/bin/python build/build_nuitka.py --target pypi-linux
|
||||
/opt/python/cp311-cp311/bin/python build/build_nuitka.py --target pypi-linux
|
||||
/opt/python/cp312-cp312/bin/python build/build_nuitka.py --target pypi-linux
|
||||
/opt/python/cp310-cp310/bin/python build/wheel/build.py --target pypi-linux
|
||||
/opt/python/cp311-cp311/bin/python build/wheel/build.py --target pypi-linux
|
||||
/opt/python/cp312-cp312/bin/python build/wheel/build.py --target pypi-linux
|
||||
```
|
||||
|
||||
3. For each ABI: Nuitka compile → wheel → `auditwheel repair` → isolated smoke test.
|
||||
@@ -82,13 +82,13 @@ Expect roughly **3×** the wall time of a single-ABI build.
|
||||
Rebuild without refreshing the image:
|
||||
|
||||
```powershell
|
||||
.\build\build_manylinux_docker.ps1 -SkipImageBuild
|
||||
python build/run.py manylinux -SkipImageBuild
|
||||
```
|
||||
|
||||
Equivalent manual `docker` invocation:
|
||||
|
||||
```powershell
|
||||
docker build -f build/Dockerfile.manylinux -t orisys-manylinux .
|
||||
docker build -f build/wheel/docker/Dockerfile.manylinux -t orisys-manylinux .
|
||||
docker run --rm `
|
||||
-e ORISYS_BUILD_TARGET=pypi-linux `
|
||||
-e ORISYS_PYTHON_TAGS=cp310-cp310,cp311-cp311,cp312-cp312 `
|
||||
@@ -106,7 +106,7 @@ docker run --rm `
|
||||
Optional re-verify on a Linux machine:
|
||||
|
||||
```bash
|
||||
python build/verify.py dist/repaired/orisys-*-cp310-cp310-manylinux_*.whl
|
||||
python build/run.py verify dist/repaired/orisys-*-cp310-cp310-manylinux_*.whl
|
||||
```
|
||||
|
||||
## Promote: TestPyPI then PyPI
|
||||
@@ -157,11 +157,15 @@ are not stored on developer machines.
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `python build/build_nuitka.py` | Internal SDK wheel on the current host (Windows `.pyd` or Linux `.so`) |
|
||||
| `python build/build_nuitka.py --target pypi-linux` | Linux-only; requires manylinux + CPython 3.10–3.12 |
|
||||
| `.\build\build_nuitka_win.bat` | Windows wheels for py310/py311/py312 conda envs |
|
||||
| `.\build\build_manylinux_docker.ps1` | Linux manylinux wheels for 3.10/3.11/3.12 via Docker |
|
||||
| `python build/run.py wheel` | Preferred wheel builder launcher |
|
||||
| `python build/wheel/build.py` | Direct wheel builder (current host) |
|
||||
| `python build/run.py wheel --target pypi-linux` | Linux-only; requires manylinux + CPython 3.10–3.12 |
|
||||
| `python build/run.py wheel-win` | Windows wheels for py310/py311/py312 conda envs |
|
||||
| `python build/run.py manylinux` | manylinux Docker runner |
|
||||
| `python build/run.py gui finger` | ORY-FINGER standalone GUI |
|
||||
| `python -m build` | Pure-Python / sdist path from `pyproject.toml` (no Nuitka) |
|
||||
|
||||
See also [`build/README.md`](../build/README.md) for the target-oriented layout.
|
||||
|
||||
Windows binary wheels and Linux manylinux wheels must be built on their respective
|
||||
platforms. Never rename a platform tag to bypass PyPI validation.
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ version = { attr = "orisys.__version__" }
|
||||
|
||||
# Single source of truth for bundled non-code files.
|
||||
# - dev_install = true -> pip install / editable install (via setup.py)
|
||||
# - all items -> Nuitka wheel (build/wheel_layout.py)
|
||||
# - all items -> Nuitka wheel (build/wheel/layout.py)
|
||||
[tool.orisys.bundle]
|
||||
|
||||
# Use patterns (globs) and/or files (explicit names under source/).
|
||||
|
||||
@@ -8,6 +8,6 @@ from setuptools import setup
|
||||
REPO_ROOT = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(REPO_ROOT / "build"))
|
||||
|
||||
from pyproject_utils import bundle_data_files
|
||||
from common.pyproject_utils import bundle_data_files
|
||||
|
||||
setup(data_files=bundle_data_files(REPO_ROOT))
|
||||
|
||||
@@ -12,7 +12,7 @@ def packaging_utils():
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
from pyproject_utils import extras_require, install_requires, project_metadata
|
||||
from common.pyproject_utils import extras_require, install_requires, project_metadata
|
||||
|
||||
return install_requires, extras_require, project_metadata
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_pyproject_dynamic_version(packaging_utils):
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
from pyproject_utils import get_version
|
||||
from common.pyproject_utils import get_version
|
||||
|
||||
assert get_version(REPO_ROOT) == "0.4.2"
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_pyproject_bundle_manifest(packaging_utils):
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
from pyproject_utils import bundle_data_files, bundle_items
|
||||
from common.pyproject_utils import bundle_data_files, bundle_items
|
||||
|
||||
items = bundle_items(REPO_ROOT)
|
||||
names = {item["name"] for item in items}
|
||||
@@ -69,7 +69,7 @@ def test_bundle_explicit_files():
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
from wheel_layout import data_file_entries_for_item, resolve_bundle_source_files
|
||||
from wheel.layout import data_file_entries_for_item, resolve_bundle_source_files
|
||||
|
||||
item = {
|
||||
"name": "configs",
|
||||
|
||||
+12
-12
@@ -6,37 +6,37 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def wheel_build():
|
||||
def wheel_package():
|
||||
import sys
|
||||
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
import wheel_build
|
||||
from wheel import package as wheel_package
|
||||
|
||||
return wheel_build
|
||||
return wheel_package
|
||||
|
||||
|
||||
def test_compliant_manylinux_tags(wheel_build):
|
||||
assert wheel_build.is_compliant_manylinux_wheel(
|
||||
def test_compliant_manylinux_tags(wheel_package):
|
||||
assert wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-manylinux_2_17_x86_64.whl")
|
||||
)
|
||||
assert wheel_build.is_compliant_manylinux_wheel(
|
||||
assert wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-manylinux2014_x86_64.whl")
|
||||
)
|
||||
assert wheel_build.is_compliant_manylinux_wheel(
|
||||
assert wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-manylinux_2_28_x86_64.whl")
|
||||
)
|
||||
|
||||
|
||||
def test_rejects_illegal_manylinux_tags(wheel_build):
|
||||
assert not wheel_build.is_compliant_manylinux_wheel(
|
||||
def test_rejects_illegal_manylinux_tags(wheel_package):
|
||||
assert not wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-manylinux_x86_64.whl")
|
||||
)
|
||||
assert not wheel_build.is_compliant_manylinux_wheel(
|
||||
assert not wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-linux_x86_64.whl")
|
||||
)
|
||||
assert not wheel_build.is_compliant_manylinux_wheel(
|
||||
assert not wheel_package.is_compliant_manylinux_wheel(
|
||||
Path("orisys-0.4.2-cp310-cp310-win_amd64.whl")
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ def test_pyproject_no_private_classifier():
|
||||
build_dir = REPO_ROOT / "build"
|
||||
if str(build_dir) not in sys.path:
|
||||
sys.path.insert(0, str(build_dir))
|
||||
from pyproject_utils import project_metadata
|
||||
from common.pyproject_utils import project_metadata
|
||||
|
||||
classifiers = project_metadata(REPO_ROOT).get("classifiers", [])
|
||||
assert "Private :: Do Not Upload" not in classifiers
|
||||
|
||||
Reference in New Issue
Block a user