reorganized build directory
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Repository and packaging output paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# 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:
|
||||
return REPO_ROOT.joinpath(*parts)
|
||||
|
||||
|
||||
def sdk_dir(version: str) -> Path:
|
||||
return REPO_ROOT / "SDK" / version
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Read project metadata from pyproject.toml for packaging scripts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
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):
|
||||
import tomllib
|
||||
|
||||
return tomllib.loads(text)
|
||||
try:
|
||||
import tomli
|
||||
except ImportError:
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "tomli", "-q"])
|
||||
import tomli
|
||||
return tomli.loads(text)
|
||||
|
||||
|
||||
def load_pyproject(repo_root: Path) -> dict:
|
||||
path = repo_root / "pyproject.toml"
|
||||
return _load_toml(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def project_metadata(repo_root: Path) -> dict:
|
||||
return load_pyproject(repo_root)["project"]
|
||||
|
||||
|
||||
def install_requires(repo_root: Path) -> list[str]:
|
||||
return list(project_metadata(repo_root)["dependencies"])
|
||||
|
||||
|
||||
def extras_require(repo_root: Path) -> dict[str, list[str]]:
|
||||
optional = project_metadata(repo_root).get("optional-dependencies", {})
|
||||
return {name: list(requirements) for name, requirements in optional.items()}
|
||||
|
||||
|
||||
def _resolve_version_attr(repo_root: Path, attr: str) -> str:
|
||||
"""Resolve a setuptools dynamic version attr (e.g. orisys.__version__)."""
|
||||
module_name, _, attr_name = attr.partition(".")
|
||||
if not attr_name:
|
||||
raise RuntimeError(f"Invalid dynamic version attr: {attr!r}")
|
||||
|
||||
# Avoid importing package modules here: __init__ may import heavy runtime deps
|
||||
# (e.g., cv2/numpy) that are not required just to read __version__.
|
||||
rel_module = Path(*module_name.split("."))
|
||||
module_file = repo_root / "src" / rel_module.with_suffix(".py")
|
||||
if not module_file.is_file():
|
||||
module_file = repo_root / "src" / rel_module / "__init__.py"
|
||||
if not module_file.is_file():
|
||||
raise RuntimeError(f"Cannot resolve module file for dynamic version attr: {attr!r}")
|
||||
|
||||
tree = ast.parse(module_file.read_text(encoding="utf-8"), filename=str(module_file))
|
||||
target_name = attr_name.split(".")[0]
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if isinstance(target, ast.Name) and target.id == target_name:
|
||||
value = ast.literal_eval(node.value)
|
||||
return str(value)
|
||||
|
||||
raise RuntimeError(f"Attribute {target_name!r} not found in {module_file}")
|
||||
|
||||
|
||||
def get_version(repo_root: Path) -> str:
|
||||
"""Return project version from pyproject.toml (static or dynamic)."""
|
||||
project = project_metadata(repo_root)
|
||||
if "version" in project:
|
||||
return str(project["version"])
|
||||
|
||||
dynamic = load_pyproject(repo_root).get("tool", {}).get("setuptools", {}).get("dynamic", {})
|
||||
version_cfg = dynamic.get("version")
|
||||
if isinstance(version_cfg, dict) and "attr" in version_cfg:
|
||||
return _resolve_version_attr(repo_root, version_cfg["attr"])
|
||||
|
||||
raise RuntimeError("Project version is not defined in pyproject.toml")
|
||||
|
||||
|
||||
def bundle_items(repo_root: Path) -> list[dict]:
|
||||
"""Return bundle rules from [[tool.orisys.bundle.items]] in pyproject.toml."""
|
||||
items = load_pyproject(repo_root).get("tool", {}).get("orisys", {}).get("bundle", {}).get("items")
|
||||
if not items:
|
||||
raise RuntimeError("Missing [[tool.orisys.bundle.items]] in pyproject.toml")
|
||||
return list(items)
|
||||
|
||||
|
||||
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
|
||||
|
||||
grouped: dict[str, list[str]] = {}
|
||||
for item in bundle_items(repo_root):
|
||||
if not item.get("dev_install", False):
|
||||
continue
|
||||
|
||||
src_dir = repo_root / item["source"]
|
||||
base_dest = f"orisys/{item['target']}".replace("\\", "/")
|
||||
|
||||
for src_file in resolve_bundle_source_files(repo_root, item):
|
||||
rel_src = src_file.relative_to(repo_root).as_posix()
|
||||
if item.get("preserve_tree", False):
|
||||
rel_parent = src_file.relative_to(src_dir).parent.as_posix()
|
||||
dest = base_dest if rel_parent == "." else f"{base_dest}/{rel_parent}"
|
||||
else:
|
||||
dest = base_dest
|
||||
grouped.setdefault(dest, []).append(rel_src)
|
||||
|
||||
return [(dest, sorted(files)) for dest, files in sorted(grouped.items())]
|
||||
@@ -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}")
|
||||
@@ -0,0 +1,58 @@
|
||||
"""Isolated venv smoke tests for packaged orisys wheels."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import venv
|
||||
from pathlib import Path
|
||||
|
||||
# Public SDK surface that must import from a released wheel.
|
||||
_SMOKE_IMPORT_SCRIPT = """
|
||||
import orisys
|
||||
from orisys import Sensor, list_config, export_config, util, finger
|
||||
from orisys.finger import UVMeshViewer, build_uv_surface_mesh, FingerRuntime
|
||||
from orisys.finger.mesh import UVSurfaceMesh
|
||||
from orisys.finger.viewer import init_uv_mesh_viewer
|
||||
from orisys.hhd import nHHD
|
||||
from orisys.tracker import Tracker
|
||||
|
||||
assert orisys.__version__
|
||||
assert Sensor is not None
|
||||
assert callable(list_config)
|
||||
assert callable(export_config)
|
||||
assert util is not None
|
||||
assert finger is not None
|
||||
assert UVMeshViewer is not None
|
||||
assert build_uv_surface_mesh is not None
|
||||
assert FingerRuntime is not None
|
||||
assert UVSurfaceMesh is not None
|
||||
assert init_uv_mesh_viewer is not None
|
||||
assert nHHD is not None
|
||||
assert Tracker is not None
|
||||
print(f"orisys {orisys.__version__} public API imports OK")
|
||||
"""
|
||||
|
||||
|
||||
def _venv_python(venv_dir: Path) -> Path:
|
||||
if sys.platform == "win32":
|
||||
return venv_dir / "Scripts" / "python.exe"
|
||||
return venv_dir / "bin" / "python"
|
||||
|
||||
|
||||
def verify_built_wheel(wheel_path: Path) -> None:
|
||||
"""Install the wheel in a temporary venv and smoke-test public imports."""
|
||||
wheel_path = wheel_path.resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="orisys-wheel-verify-") as tmp:
|
||||
venv_dir = Path(tmp) / "venv"
|
||||
venv.create(venv_dir, with_pip=True)
|
||||
python = _venv_python(venv_dir)
|
||||
|
||||
subprocess.check_call(
|
||||
[python, "-m", "pip", "install", "-q", "--upgrade", "pip"],
|
||||
)
|
||||
subprocess.check_call(
|
||||
[python, "-m", "pip", "install", "-q", str(wheel_path)],
|
||||
)
|
||||
subprocess.check_call([python, "-c", _SMOKE_IMPORT_SCRIPT])
|
||||
Reference in New Issue
Block a user