Compare commits

..

2 Commits

43 changed files with 833 additions and 356 deletions
+5 -1
View File
@@ -5,8 +5,12 @@ __pycache__/
# Packaging / build outputs # Packaging / build outputs
/build/nuitka/ /build/nuitka/
/build/lib/orisys/ /build/build/
/build/bdist.*/
/build/dist/
/build/lib/
/build/pyinstaller_work/ /build/pyinstaller_work/
/build/*.spec
/dist/ /dist/
/dist_obfuscated/ /dist_obfuscated/
/dist_pyd/ /dist_pyd/
+5 -4
View File
@@ -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, stitching, dense optical flow, Helmholtz-Hodge decomposition, contact/depth estimation,
and several runnable examples for camera and video-based workflows. and several runnable examples for camera and video-based workflows.
PyPI: [https://pypi.org/project/orisys/](https://pypi.org/project/orisys/)
## Repository Layout ## Repository Layout
- `src/orisys/`: installable SDK package - `src/orisys/`: installable SDK package
@@ -93,9 +95,8 @@ cfg = orisys.export_config("ddjx01", "./config/exported_config.json")
- Example entry points: `examples/README.md` - Example entry points: `examples/README.md`
- User documentation: `docs/get_started.md` - User documentation: `docs/get_started.md`
- Packaging helper: `build/build_nuitka.py` (compiled SDK wheel) - Build launcher: `python build/run.py` (see `build/README.md`)
- Manylinux / PyPI binary wheel (Docker Desktop): `docs/publishing.md` - Manylinux / PyPI binary wheel: `docs/publishing.md`
- GUI standalone folder: `build/build_gui_win.bat` → `dist/OrisysDashboard/`
- Specialized utilities: `tools/` - Specialized utilities: `tools/`
## Publishing (Linux manylinux) ## 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: To build PyPI-compliant CPython 3.10/3.11/3.12 Linux wheels from Windows Docker Desktop:
```powershell ```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: Upload candidates land in `dist/repaired/`. Full TestPyPI → PyPI steps:
-44
View File
@@ -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',
)
+65
View File
@@ -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.
-2
View File
@@ -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
-194
View File
@@ -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()
+1
View File
@@ -0,0 +1 @@
"""Shared build utilities used by wheel and GUI packaging targets."""
+15
View File
@@ -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
+8 -2
View File
@@ -1,15 +1,17 @@
"""Repository and Nuitka build output paths.""" """Repository and packaging output paths."""
from __future__ import annotations from __future__ import annotations
from pathlib import Path 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_COMPILE_DIR = REPO_ROOT / "build" / "nuitka" / "compile"
NUITKA_WHEEL_DIR = REPO_ROOT / "build" / "nuitka" / "wheel" NUITKA_WHEEL_DIR = REPO_ROOT / "build" / "nuitka" / "wheel"
NUITKA_GUI_DIR = REPO_ROOT / "build" / "nuitka" / "gui" NUITKA_GUI_DIR = REPO_ROOT / "build" / "nuitka" / "gui"
GUI_DIST_DIR = REPO_ROOT / "dist" / "OrisysDashboard" GUI_DIST_DIR = REPO_ROOT / "dist" / "OrisysDashboard"
PYINSTALLER_WORK_DIR = REPO_ROOT / "build" / "pyinstaller_work"
def repo_path(*parts: str) -> Path: def repo_path(*parts: str) -> Path:
@@ -23,3 +25,7 @@ def sdk_dir(version: str) -> Path:
def repaired_wheel_dir() -> Path: def repaired_wheel_dir() -> Path:
"""Directory for auditwheel-repaired wheels under the Nuitka wheel build tree.""" """Directory for auditwheel-repaired wheels under the Nuitka wheel build tree."""
return NUITKA_WHEEL_DIR / "repaired" 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 import sys
from pathlib import Path 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: def _load_toml(text: str) -> dict:
if sys.version_info >= (3, 11): 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]]]: def bundle_data_files(repo_root: Path) -> list[tuple[str, list[str]]]:
"""Map bundle items marked dev_install=true to setuptools data_files entries.""" """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]] = {} grouped: dict[str, list[str]] = {}
for item in bundle_items(repo_root): for item in bundle_items(repo_root):
+6
View File
@@ -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"]
+34
View File
@@ -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())
+67
View File
@@ -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 from __future__ import annotations
import argparse
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
import venv import venv
from pathlib import Path from pathlib import Path
from paths import GUI_DIST_DIR
# Public SDK surface that must import from a released wheel. # Public SDK surface that must import from a released wheel.
_SMOKE_IMPORT_SCRIPT = """ _SMOKE_IMPORT_SCRIPT = """
import orisys import orisys
@@ -59,42 +56,3 @@ def verify_built_wheel(wheel_path: Path) -> None:
[python, "-m", "pip", "install", "-q", str(wheel_path)], [python, "-m", "pip", "install", "-q", str(wheel_path)],
) )
subprocess.check_call([python, "-c", _SMOKE_IMPORT_SCRIPT]) 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()
+1
View File
@@ -0,0 +1 @@
"""Standalone GUI packaging targets (PyInstaller)."""
+1
View File
@@ -0,0 +1 @@
"""Shared helpers for product GUI builds."""
+141
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
"""ORY-BOX internal calibration/testing GUI packaging (scaffold)."""
+30
View File
@@ -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()
+63
View File
@@ -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,
)
+1
View File
@@ -0,0 +1 @@
"""ORY-FINGER standalone GUI packaging."""
+31
View File
@@ -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()
+75
View File
@@ -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),
)
+1
View File
@@ -0,0 +1 @@
"""ORY-MINI internal calibration/testing GUI packaging (scaffold)."""
+30
View File
@@ -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()
+63
View File
@@ -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
View File
@@ -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())
+1
View File
@@ -0,0 +1 @@
"""Nuitka-compiled SDK wheel packaging."""
+13 -8
View File
@@ -11,8 +11,9 @@ Targets:
pypi-linux – PyPI binary wheel; requires Linux + CPython 3.10/3.11/3.12 + compliant tag. pypi-linux – PyPI binary wheel; requires Linux + CPython 3.10/3.11/3.12 + compliant tag.
Run from repo root: Run from repo root:
python build/build_nuitka.py python build/run.py wheel
python build/build_nuitka.py --target pypi-linux python build/run.py wheel --target pypi-linux
python build/wheel/build.py --target pypi-linux
""" """
from __future__ import annotations from __future__ import annotations
@@ -24,18 +25,22 @@ import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from nuitka_compile import compile_orisys, copy_compiled_orisys, install_nuitka _BUILD_ROOT = Path(__file__).resolve().parents[1]
from paths import NUITKA_COMPILE_DIR, NUITKA_WHEEL_DIR, REPO_ROOT, repaired_wheel_dir, repo_path if str(_BUILD_ROOT) not in sys.path:
from pyproject_utils import bundle_items, extras_require, get_version, install_requires sys.path.insert(0, str(_BUILD_ROOT))
from verify import verify_built_wheel
from wheel_build import ( 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, is_compliant_manylinux_wheel,
publish_wheel_to_sdk, publish_wheel_to_sdk,
repair_linux_wheel, repair_linux_wheel,
run_bdist_wheel, run_bdist_wheel,
write_wheel_setup, write_wheel_setup,
) )
from wheel_layout import copy_bundle_items, copy_orisys_blend, package_data_from_bundle
VALID_TARGETS = ("internal", "pypi-linux") VALID_TARGETS = ("internal", "pypi-linux")
# ABI tags that manylinux images ship under /opt/python/cpXYY-cpXYY. # ABI tags that manylinux images ship under /opt/python/cpXYY-cpXYY.
@@ -8,7 +8,11 @@ import sys
from importlib.machinery import EXTENSION_SUFFIXES from importlib.machinery import EXTENSION_SUFFIXES
from pathlib import Path 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 = ( NUITKA_INCLUDE_PACKAGES = (
"orisys", "orisys",
@@ -23,4 +23,4 @@ RUN for PYBIN in \
done done
WORKDIR /src 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 "${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}") BUILT+=("${tag}")
else else
echo "[ERROR] Build failed for ${tag}" >&2 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. # Build PyPI-compliant manylinux wheels for CPython 3.10/3.11/3.12 using Docker Desktop.
# #
# Usage (from repo root, PowerShell): # Usage (from repo root, PowerShell):
# .\build\build_manylinux_docker.ps1 # .\build\wheel\docker\run.ps1
# .\build\build_manylinux_docker.ps1 -PythonVersions 3.10,3.11,3.12 # .\build\build_manylinux_docker.ps1 # compatibility wrapper
# .\build\build_manylinux_docker.ps1 -PythonVersions 3.11
# .\build\build_manylinux_docker.ps1 -SkipImageBuild
# #
# From cmd.exe: # From cmd.exe:
# powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1 # 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( param(
[ValidateSet("pypi-linux", "internal")] [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 Set-Location $RepoRoot
$tags = @() $tags = @()
@@ -67,15 +61,17 @@ if ($dockerInfo -ne "linux") {
throw "Docker server OS is '$dockerInfo'. Switch Docker Desktop to Linux containers." 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. # Normalize CRLF -> LF so bash in the container can execute the script.
$raw = [System.IO.File]::ReadAllText($entrypoint) $raw = [System.IO.File]::ReadAllText($entrypoint)
$normalized = $raw -replace "`r`n", "`n" -replace "`r", "`n" $normalized = $raw -replace "`r`n", "`n" -replace "`r", "`n"
[System.IO.File]::WriteAllText($entrypoint, $normalized) [System.IO.File]::WriteAllText($entrypoint, $normalized)
$dockerfile = "build/wheel/docker/Dockerfile.manylinux"
if (-not $SkipImageBuild) { if (-not $SkipImageBuild) {
Write-Host "`n=== Building Docker image ===" Write-Host "`n=== Building Docker image ==="
docker build -f build/Dockerfile.manylinux -t $ImageName . docker build -f $dockerfile -t $ImageName .
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
throw "docker build failed" throw "docker build failed"
} }
@@ -8,7 +8,11 @@ import subprocess
import sys import sys
from pathlib import Path 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. # PyPI rejects bare tags like "manylinux_x86_64"; require a glibc ABI version.
_MANYLINUX_TAG_RE = re.compile( _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). REM Expects conda envs named py310, py311, py312 (or set CONDA_ENVS_ROOT).
setlocal enabledelayedexpansion setlocal enabledelayedexpansion
cd /d "%~dp0\.." cd /d "%~dp0\..\.."
if errorlevel 1 ( if errorlevel 1 (
echo ERROR: Could not change to repository root. echo ERROR: Could not change to repository root.
exit /b 1 exit /b 1
@@ -32,7 +32,7 @@ for %%E in (py310 py311 py312) do (
set "OK=0" set "OK=0"
if !HAS_CONDA! equ 1 ( 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" if not errorlevel 1 set "OK=1"
) )
@@ -44,7 +44,7 @@ for %%E in (py310 py311 py312) do (
if not exist "!PYEXE!" ( if not exist "!PYEXE!" (
echo ERROR: Python not found: !PYEXE! echo ERROR: Python not found: !PYEXE!
) else ( ) else (
"!PYEXE!" build\build_nuitka.py "!PYEXE!" build\wheel\build.py
if not errorlevel 1 set "OK=1" if not errorlevel 1 set "OK=1"
) )
) )
+1 -1
View File
@@ -31,7 +31,7 @@
"dis_preset": "medium", "dis_preset": "medium",
"dis_mode": 3, "dis_mode": 3,
"algorithm_list": ["raft", "dis"], "algorithm_list": ["raft", "dis"],
"hhd_downsample": 3 "hhd_downsample": 1
}, },
"contact": { "contact": {
"min_contour_area": 3000, "min_contour_area": 3000,
+1 -1
View File
@@ -31,7 +31,7 @@
"dis_preset": "medium", "dis_preset": "medium",
"dis_mode": 3, "dis_mode": 3,
"algorithm_list": ["raft", "dis"], "algorithm_list": ["raft", "dis"],
"hhd_downsample": 3, "hhd_downsample": 1,
"backend": "cpu" "backend": "cpu"
}, },
"contact": { "contact": {
+23 -19
View File
@@ -40,35 +40,35 @@ docker run --rm hello-world
## Build manylinux wheels (3.10 / 3.11 / 3.12) ## Build manylinux wheels (3.10 / 3.11 / 3.12)
From the repository root. Prefer PowerShell, or from **cmd.exe**: From the repository root:
```bat ```bash
powershell -ExecutionPolicy Bypass -File .\build\build_manylinux_docker.ps1 python build/run.py manylinux
``` ```
PowerShell: Or call the Docker helper directly:
```powershell ```powershell
.\build\build_manylinux_docker.ps1 .\build\wheel\docker\run.ps1
``` ```
Default builds **all three** ABIs. To build a subset: Default builds **all three** ABIs. To build a subset:
```powershell ```powershell
.\build\build_manylinux_docker.ps1 -PythonVersions 3.10,3.12 python build/run.py manylinux -PythonVersions 3.10,3.12
.\build\build_manylinux_docker.ps1 -PythonVersions 3.11 python build/run.py manylinux -PythonVersions 3.11
``` ```
What this does: 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). (`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 ```bash
/opt/python/cp310-cp310/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/build_nuitka.py --target pypi-linux /opt/python/cp311-cp311/bin/python build/wheel/build.py --target pypi-linux
/opt/python/cp312-cp312/bin/python build/build_nuitka.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. 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: Rebuild without refreshing the image:
```powershell ```powershell
.\build\build_manylinux_docker.ps1 -SkipImageBuild python build/run.py manylinux -SkipImageBuild
``` ```
Equivalent manual `docker` invocation: Equivalent manual `docker` invocation:
```powershell ```powershell
docker build -f build/Dockerfile.manylinux -t orisys-manylinux . docker build -f build/wheel/docker/Dockerfile.manylinux -t orisys-manylinux .
docker run --rm ` docker run --rm `
-e ORISYS_BUILD_TARGET=pypi-linux ` -e ORISYS_BUILD_TARGET=pypi-linux `
-e ORISYS_PYTHON_TAGS=cp310-cp310,cp311-cp311,cp312-cp312 ` -e ORISYS_PYTHON_TAGS=cp310-cp310,cp311-cp311,cp312-cp312 `
@@ -106,7 +106,7 @@ docker run --rm `
Optional re-verify on a Linux machine: Optional re-verify on a Linux machine:
```bash ```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 ## Promote: TestPyPI then PyPI
@@ -157,11 +157,15 @@ are not stored on developer machines.
| Command | Purpose | | Command | Purpose |
|---------|---------| |---------|---------|
| `python build/build_nuitka.py` | Internal SDK wheel on the current host (Windows `.pyd` or Linux `.so`) | | `python build/run.py wheel` | Preferred wheel builder launcher |
| `python build/build_nuitka.py --target pypi-linux` | Linux-only; requires manylinux + CPython 3.10–3.12 | | `python build/wheel/build.py` | Direct wheel builder (current host) |
| `.\build\build_nuitka_win.bat` | Windows wheels for py310/py311/py312 conda envs | | `python build/run.py wheel --target pypi-linux` | Linux-only; requires manylinux + CPython 3.10–3.12 |
| `.\build\build_manylinux_docker.ps1` | Linux manylinux wheels for 3.10/3.11/3.12 via Docker | | `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) | | `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 Windows binary wheels and Linux manylinux wheels must be built on their respective
platforms. Never rename a platform tag to bypass PyPI validation. platforms. Never rename a platform tag to bypass PyPI validation.
+1 -1
View File
@@ -63,7 +63,7 @@ version = { attr = "orisys.__version__" }
# Single source of truth for bundled non-code files. # Single source of truth for bundled non-code files.
# - dev_install = true -> pip install / editable install (via setup.py) # - 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] [tool.orisys.bundle]
# Use patterns (globs) and/or files (explicit names under source/). # Use patterns (globs) and/or files (explicit names under source/).
+1 -1
View File
@@ -8,6 +8,6 @@ from setuptools import setup
REPO_ROOT = Path(__file__).resolve().parent REPO_ROOT = Path(__file__).resolve().parent
sys.path.insert(0, str(REPO_ROOT / "build")) 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)) setup(data_files=bundle_data_files(REPO_ROOT))
+4 -4
View File
@@ -12,7 +12,7 @@ def packaging_utils():
build_dir = REPO_ROOT / "build" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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 return install_requires, extras_require, project_metadata
@@ -33,7 +33,7 @@ def test_pyproject_dynamic_version(packaging_utils):
build_dir = REPO_ROOT / "build" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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" assert get_version(REPO_ROOT) == "0.4.2"
@@ -44,7 +44,7 @@ def test_pyproject_bundle_manifest(packaging_utils):
build_dir = REPO_ROOT / "build" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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) items = bundle_items(REPO_ROOT)
names = {item["name"] for item in items} names = {item["name"] for item in items}
@@ -69,7 +69,7 @@ def test_bundle_explicit_files():
build_dir = REPO_ROOT / "build" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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 = { item = {
"name": "configs", "name": "configs",
+12 -12
View File
@@ -6,37 +6,37 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
@pytest.fixture @pytest.fixture
def wheel_build(): def wheel_package():
import sys import sys
build_dir = REPO_ROOT / "build" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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): def test_compliant_manylinux_tags(wheel_package):
assert wheel_build.is_compliant_manylinux_wheel( assert wheel_package.is_compliant_manylinux_wheel(
Path("orisys-0.4.2-cp310-cp310-manylinux_2_17_x86_64.whl") 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") 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") Path("orisys-0.4.2-cp310-cp310-manylinux_2_28_x86_64.whl")
) )
def test_rejects_illegal_manylinux_tags(wheel_build): def test_rejects_illegal_manylinux_tags(wheel_package):
assert not wheel_build.is_compliant_manylinux_wheel( assert not wheel_package.is_compliant_manylinux_wheel(
Path("orisys-0.4.2-cp310-cp310-manylinux_x86_64.whl") 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") 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") 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" build_dir = REPO_ROOT / "build"
if str(build_dir) not in sys.path: if str(build_dir) not in sys.path:
sys.path.insert(0, str(build_dir)) 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", []) classifiers = project_metadata(REPO_ROOT).get("classifiers", [])
assert "Private :: Do Not Upload" not in classifiers assert "Private :: Do Not Upload" not in classifiers