From 8fd972019085ac7210a55623d5b7964e852269fd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 1 May 2026 02:20:40 +0000 Subject: [PATCH] update acados shim --- acados/acados/__init__.py | 48 +++++++++++++++++ acados/casadi/__init__.py | 0 acados/pyproject.toml | 22 ++++++++ acados/setup.py | 107 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 acados/acados/__init__.py create mode 100644 acados/casadi/__init__.py create mode 100644 acados/pyproject.toml create mode 100644 acados/setup.py diff --git a/acados/acados/__init__.py b/acados/acados/__init__.py new file mode 100644 index 0000000..2c84aef --- /dev/null +++ b/acados/acados/__init__.py @@ -0,0 +1,48 @@ +import os + +DIR = os.path.join(os.path.dirname(__file__), "install") +INCLUDE_DIR = os.path.join(DIR, "include") +LIB_DIR = os.path.join(DIR, "lib") +BIN_DIR = os.path.join(DIR, "bin") +TERA_PATH = os.path.join(BIN_DIR, "t_renderer") + +TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "acados_template") + + +def smoketest(): + import sys + + lib_ext = ".dylib" if sys.platform == "darwin" else ".so" + for lib in ("libacados", "libblasfeo", "libhpipm", "libqpOASES_e"): + path = os.path.join(LIB_DIR, lib + lib_ext) + assert os.path.isfile(path), f"missing lib: {path}" + + assert os.path.isfile(os.path.join(INCLUDE_DIR, "acados_c", "ocp_nlp_interface.h")) + assert os.path.isfile(os.path.join(INCLUDE_DIR, "blasfeo", "include", "blasfeo.h")) + assert os.path.isfile(os.path.join(INCLUDE_DIR, "hpipm", "include", "hpipm_common.h")) + + assert os.path.isfile(TERA_PATH) and os.access(TERA_PATH, os.X_OK), f"t_renderer missing/not executable: {TERA_PATH}" + + assert os.path.isfile(os.path.join(TEMPLATE_DIR, "__init__.py")) + assert os.path.isfile(os.path.join(TEMPLATE_DIR, "acados_layout.json")) + assert os.path.isdir(os.path.join(TEMPLATE_DIR, "c_templates_tera")) + + # the vendored slim casadi shipped in this wheel is built against cpython 3.12 + # and pulls in numpy; only exercise it when both are available (real consumers + # like openpilot install via the shim, which declares numpy as a dep). + try: + import numpy # noqa: F401 + except ImportError: + return + if sys.version_info[:2] != (3, 12): + return + + from casadi import SX, MX, DM, Function, CasadiMeta, vertcat, jacobian, sin, cos, n_nodes # noqa: F401 + + x = SX.sym("x") + y = SX.sym("y") + expr = vertcat(sin(x), cos(y)) + J = jacobian(expr, vertcat(x, y)) + assert J.shape == (2, 2) + assert n_nodes(J) > 0 + assert isinstance(CasadiMeta.version(), str) diff --git a/acados/casadi/__init__.py b/acados/casadi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/acados/pyproject.toml b/acados/pyproject.toml new file mode 100644 index 0000000..3c845b2 --- /dev/null +++ b/acados/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=64", "wheel", 'tomli; python_version < "3.11"'] +build-backend = "setuptools.build_meta" + +[project] +name = "acados" +version = "0.2.2" +description = "acados solvers + Python interface, with a slimmed casadi vendored alongside (pre-built)" +requires-python = ">=3.8" +dependencies = ["numpy"] + +[tool.setuptools] +packages = ["acados", "casadi"] + +[tool.setuptools.package-data] +acados = ["*.so", "acados_template/**/*", "install/**/*"] +casadi = ["**/*"] + +[tool.shim] +repo_url = "https://github.com/commaai/dependencies" +tag = "acados/v0.2.2" +datadir = "install" diff --git a/acados/setup.py b/acados/setup.py new file mode 100644 index 0000000..8b58b1c --- /dev/null +++ b/acados/setup.py @@ -0,0 +1,107 @@ +"""Shim setup.py: downloads pre-built wheels from GitHub Releases at install time.""" +import os +import platform +import time +import zipfile +from io import BytesIO +from urllib.error import URLError +from urllib.request import urlopen + +try: + import tomllib +except ImportError: + import tomli as tomllib + +from setuptools import setup +from setuptools.command.build_py import build_py + +_HERE = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(_HERE, "pyproject.toml"), "rb") as _f: + _cfg = tomllib.load(_f) + +REPO_URL = _cfg["tool"]["shim"]["repo_url"] +TAG = _cfg["tool"]["shim"]["tag"] +DATADIR = _cfg["tool"]["shim"]["datadir"] +VERSION = _cfg["project"]["version"] +MODULE = _cfg["project"]["name"].replace("-", "_") + +# all top-level packages bundled in this wheel (e.g. acados + casadi). +# `packages` may be a flat list or a dict with find.include patterns. +_pkgs = _cfg.get("tool", {}).get("setuptools", {}).get("packages", [f"{MODULE}*"]) +if isinstance(_pkgs, list): + _INCLUDE = _pkgs +elif isinstance(_pkgs, dict): + _INCLUDE = _pkgs.get("find", {}).get("include", [f"{MODULE}*"]) +else: + _INCLUDE = [f"{MODULE}*"] +TOP_PACKAGES = sorted({p.rstrip("*").rstrip("/") for p in _INCLUDE if p.rstrip("*").rstrip("/")}) + +PLATFORM_MAP = { + ("Linux", "x86_64"): "linux_x86_64", + ("Linux", "aarch64"): "linux_aarch64", + ("Darwin", "arm64"): "macosx_11_0_arm64", +} + + +class InstallPrebuilt(build_py): + def run(self): + module_dir = os.path.join(_HERE, MODULE) + data_dir = os.path.join(module_dir, DATADIR) + + if not os.path.exists(os.path.join(data_dir, "bin")): + key = (platform.system(), platform.machine()) + plat = PLATFORM_MAP.get(key) + if plat is None: + raise RuntimeError(f"unsupported platform: {key}") + + whl_names = [ + f"{MODULE}-{VERSION}-py3-none-{plat}.whl", + f"{MODULE}-{VERSION}-py3-none-any.whl", + ] + + raw = None + for whl_name in whl_names: + url = f"{REPO_URL}/releases/download/{TAG}/{whl_name}" + print(f"Downloading {url} ...") + for attempt in range(3): + try: + raw = urlopen(url, timeout=60).read() + break + except (URLError, OSError) as e: + if attempt == 2: + if whl_name == whl_names[-1]: + raise + break + wait = 2 ** attempt + print(f"Download failed ({e}), retrying in {wait}s ...") + time.sleep(wait) + if raw is not None: + break + + print("Extracting wheel ...") + with zipfile.ZipFile(BytesIO(raw)) as zf: + purelib_data = f"{MODULE}-{VERSION}.data/purelib/" + for info in zf.infolist(): + if info.is_dir(): + continue + name = info.filename + if name.startswith(purelib_data): + rel = name[len(purelib_data):] + else: + rel = name + if "/" not in rel: + continue + top = rel.split("/", 1)[0] + if top not in TOP_PACKAGES: + continue + dest = os.path.join(_HERE, rel) + os.makedirs(os.path.dirname(dest), exist_ok=True) + with open(dest, "wb") as f: + f.write(zf.read(info)) + if info.external_attr >> 16 & 0o111: + os.chmod(dest, 0o755) + + super().run() + + +setup(cmdclass={"build_py": InstallPrebuilt})