Compare commits
6 Commits
mdbook/v0.
...
xvfb/v1.20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf61c10458 | ||
|
|
975313a86f | ||
|
|
19a9ed13c0 | ||
|
|
f9dd627e57 | ||
|
|
058ed3c076 | ||
|
|
9f9bc38a3e |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -30,3 +30,9 @@ git-lfs/git_lfs/bin/
|
|||||||
|
|
||||||
# downloaded mdbook
|
# downloaded mdbook
|
||||||
mdbook/mdbook/bin/
|
mdbook/mdbook/bin/
|
||||||
|
|
||||||
|
# vendored at build time
|
||||||
|
acados/acados/acados_template/
|
||||||
|
acados/casadi/
|
||||||
|
acados/casadi-venv/
|
||||||
|
acados/casadi-wheel/
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ DATADIR = _cfg["tool"]["shim"]["datadir"]
|
|||||||
VERSION = _cfg["project"]["version"]
|
VERSION = _cfg["project"]["version"]
|
||||||
MODULE = _cfg["project"]["name"].replace("-", "_")
|
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 = {
|
PLATFORM_MAP = {
|
||||||
("Linux", "x86_64"): "linux_x86_64",
|
("Linux", "x86_64"): "linux_x86_64",
|
||||||
("Linux", "aarch64"): "linux_aarch64",
|
("Linux", "aarch64"): "linux_aarch64",
|
||||||
@@ -43,59 +54,52 @@ class InstallPrebuilt(build_py):
|
|||||||
if plat is None:
|
if plat is None:
|
||||||
raise RuntimeError(f"unsupported platform: {key}")
|
raise RuntimeError(f"unsupported platform: {key}")
|
||||||
|
|
||||||
whl_name = f"{MODULE}-{VERSION}-py3-none-{plat}.whl"
|
whl_names = [
|
||||||
url = f"{REPO_URL}/releases/download/{TAG}/{whl_name}"
|
f"{MODULE}-{VERSION}-py3-none-{plat}.whl",
|
||||||
|
f"{MODULE}-{VERSION}-py3-none-any.whl",
|
||||||
|
]
|
||||||
|
|
||||||
print(f"Downloading {url} ...")
|
raw = None
|
||||||
for attempt in range(3):
|
for whl_name in whl_names:
|
||||||
try:
|
url = f"{REPO_URL}/releases/download/{TAG}/{whl_name}"
|
||||||
raw = urlopen(url, timeout=60).read()
|
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
|
break
|
||||||
except (URLError, OSError) as e:
|
|
||||||
if attempt == 2:
|
|
||||||
raise
|
|
||||||
wait = 2 ** attempt
|
|
||||||
print(f"Download failed ({e}), retrying in {wait}s ...")
|
|
||||||
time.sleep(wait)
|
|
||||||
|
|
||||||
print(f"Extracting {DATADIR} ...")
|
print("Extracting wheel ...")
|
||||||
with zipfile.ZipFile(BytesIO(raw)) as zf:
|
with zipfile.ZipFile(BytesIO(raw)) as zf:
|
||||||
prefix = f"{MODULE}/{DATADIR}/"
|
purelib_data = f"{MODULE}-{VERSION}.data/purelib/"
|
||||||
alt_prefix = f"{MODULE}-{VERSION}.data/purelib/{MODULE}/{DATADIR}/"
|
|
||||||
for info in zf.infolist():
|
for info in zf.infolist():
|
||||||
for p in (prefix, alt_prefix):
|
if info.is_dir():
|
||||||
if info.filename.startswith(p):
|
|
||||||
rel = info.filename[len(p):]
|
|
||||||
if not rel:
|
|
||||||
continue
|
|
||||||
dest = os.path.join(data_dir, rel)
|
|
||||||
if info.is_dir():
|
|
||||||
os.makedirs(dest, exist_ok=True)
|
|
||||||
else:
|
|
||||||
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)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Also extract compiled extension modules (.so)
|
|
||||||
ext_prefix = f"{MODULE}/"
|
|
||||||
ext_alt_prefix = f"{MODULE}-{VERSION}.data/purelib/{MODULE}/"
|
|
||||||
for info in zf.infolist():
|
|
||||||
if not info.filename.endswith('.so'):
|
|
||||||
continue
|
continue
|
||||||
for p in (ext_prefix, ext_alt_prefix):
|
name = info.filename
|
||||||
if info.filename.startswith(p):
|
if name.startswith(purelib_data):
|
||||||
rel = info.filename[len(p):]
|
rel = name[len(purelib_data):]
|
||||||
if rel and '/' not in rel:
|
else:
|
||||||
dest = os.path.join(module_dir, rel)
|
rel = name
|
||||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
if "/" not in rel:
|
||||||
with open(dest, "wb") as f:
|
continue
|
||||||
f.write(zf.read(info))
|
top = rel.split("/", 1)[0]
|
||||||
if info.external_attr >> 16 & 0o111:
|
if top not in TOP_PACKAGES:
|
||||||
os.chmod(dest, 0o755)
|
continue
|
||||||
break
|
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()
|
super().run()
|
||||||
|
|
||||||
|
|||||||
48
acados/acados/__init__.py
Normal file
48
acados/acados/__init__.py
Normal file
@@ -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)
|
||||||
139
acados/build.sh
Executable file
139
acados/build.sh
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
# v0.2.2
|
||||||
|
VERSION="8af9b0ad180940ef611884574a0b27a43504311d"
|
||||||
|
INSTALL_DIR="$DIR/acados/install"
|
||||||
|
TEMPLATE_DIR="$DIR/acados/acados_template"
|
||||||
|
CASADI_DIR="$DIR/casadi"
|
||||||
|
CASADI_VERSION="3.6.7"
|
||||||
|
|
||||||
|
NJOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)"
|
||||||
|
|
||||||
|
# pick BLAS target per host arch
|
||||||
|
ARCH="$(uname -m)"
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
# this BLASFEO version doesn't have an Apple Silicon target; Cortex-A57
|
||||||
|
# baseline ARMv8 SIMD compiles and runs fine on M1+.
|
||||||
|
BLAS_TARGET="ARMV8A_ARM_CORTEX_A57"
|
||||||
|
elif [[ "$ARCH" == "aarch64" ]]; then
|
||||||
|
# Cortex-A57 = TICI baseline; safe for any modern aarch64
|
||||||
|
BLAS_TARGET="ARMV8A_ARM_CORTEX_A57"
|
||||||
|
else
|
||||||
|
BLAS_TARGET="X64_AUTOMATIC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ACADOS_FLAGS=(
|
||||||
|
-DACADOS_WITH_QPOASES=ON
|
||||||
|
-UBLASFEO_TARGET
|
||||||
|
-DBLASFEO_TARGET="$BLAS_TARGET"
|
||||||
|
-DACADOS_INSTALL_DIR="$INSTALL_DIR"
|
||||||
|
# acados (and several of its submodules) still pin cmake_minimum_required <3.5;
|
||||||
|
# CMake 4 removed that compatibility, so re-enable it here.
|
||||||
|
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||||
|
# qpOASES + blasfeo both call malloc()/posix_memalign() without including
|
||||||
|
# <stdlib.h>. C99/C2x and modern gcc/clang reject implicit declarations as
|
||||||
|
# errors. qpOASES also has a real Constraints*/Constraints** pointer bug
|
||||||
|
# that gcc 14+ now flags as an error too. Downgrade both so the upstream
|
||||||
|
# (pinned) sources keep compiling.
|
||||||
|
"-DCMAKE_C_FLAGS=-Wno-implicit-function-declaration -Wno-incompatible-pointer-types"
|
||||||
|
)
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
ACADOS_FLAGS+=(
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES=arm64
|
||||||
|
-DCMAKE_MACOSX_RPATH=1
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# clone/update source
|
||||||
|
if [ ! -d "acados-src/.git" ]; then
|
||||||
|
rm -rf acados-src
|
||||||
|
git clone https://github.com/acados/acados.git acados-src
|
||||||
|
fi
|
||||||
|
git -C acados-src fetch --all --tags
|
||||||
|
git -C acados-src checkout --force "$VERSION"
|
||||||
|
git -C acados-src submodule update --init --recursive --depth=1
|
||||||
|
|
||||||
|
# build acados
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake "${ACADOS_FLAGS[@]}" "$DIR/acados-src"
|
||||||
|
make -j"$NJOBS" install
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
# we don't ship sample json templates
|
||||||
|
rm -f "$INSTALL_DIR"/lib/*.json
|
||||||
|
|
||||||
|
# python interface package (acados_template)
|
||||||
|
rm -rf "$TEMPLATE_DIR"
|
||||||
|
cp -r acados-src/interfaces/acados_template/acados_template "$TEMPLATE_DIR"
|
||||||
|
|
||||||
|
# strip future_fstrings (avoids needing the compatibility package on py>=3.6).
|
||||||
|
# Cython chokes on the unknown encoding in .pyx/.pxd too, not just .py.
|
||||||
|
find "$TEMPLATE_DIR" -type f \( -name '*.py' -o -name '*.pyx' -o -name '*.pxd' \) \
|
||||||
|
-exec sed -i.bak '/future.fstrings/d' {} +
|
||||||
|
find "$TEMPLATE_DIR" -name '*.bak' -delete
|
||||||
|
|
||||||
|
# acados_template's gnsf/check_reformulation.py uses an absolute `from
|
||||||
|
# acados_template.utils import ...` that only worked when acados_template was
|
||||||
|
# itself a top-level package on the path. We ship it as `acados.acados_template`,
|
||||||
|
# so rewrite to the relative form the rest of gnsf already uses.
|
||||||
|
if [ -f "$TEMPLATE_DIR/gnsf/check_reformulation.py" ]; then
|
||||||
|
sed -i.bak 's/^from acados_template\.utils /from ..utils /' "$TEMPLATE_DIR/gnsf/check_reformulation.py"
|
||||||
|
rm -f "$TEMPLATE_DIR/gnsf/check_reformulation.py.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build tera renderer (needs cargo)
|
||||||
|
if ! command -v cargo >/dev/null 2>&1; then
|
||||||
|
echo "installing rust toolchain (needed for tera_renderer)..."
|
||||||
|
curl -LsSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR/bin"
|
||||||
|
cd "$DIR/acados-src/interfaces/acados_template/tera_renderer/"
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
cargo build --release --target aarch64-apple-darwin
|
||||||
|
cp target/aarch64-apple-darwin/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
|
||||||
|
else
|
||||||
|
cargo build --release
|
||||||
|
cp target/release/t_renderer "$INSTALL_DIR/bin/t_renderer"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
# vendor a slim casadi: install the upstream wheel into a throwaway cp312 venv
|
||||||
|
# (uv resolves the right platform wheel automatically), then move the casadi/
|
||||||
|
# tree out and slim it.
|
||||||
|
echo "vendoring casadi $CASADI_VERSION ..."
|
||||||
|
rm -rf "$CASADI_DIR" casadi-venv
|
||||||
|
uv venv --python 3.12 --quiet casadi-venv
|
||||||
|
uv pip install --python casadi-venv/bin/python --no-deps --quiet "casadi==$CASADI_VERSION"
|
||||||
|
mv casadi-venv/lib/python3.12/site-packages/casadi "$CASADI_DIR"
|
||||||
|
rm -rf casadi-venv
|
||||||
|
|
||||||
|
# drop everything except the bits openpilot actually needs:
|
||||||
|
# - __init__.py, casadi.py, tools/ (Python wrapper)
|
||||||
|
# - _casadi.so (CPython extension; same name on linux+darwin)
|
||||||
|
# - libcasadi.{so,dylib}* (the C++ runtime that _casadi.so links to)
|
||||||
|
# openpilot only uses symbolic SX/MX/Function/jacobian etc., which live in
|
||||||
|
# libcasadi + _casadi. Solver plugins (conic_*, nlpsol_*, integrator_*, ...)
|
||||||
|
# and their third-party backends (ipopt, bonmin, hpipm, fatrop, ...) are
|
||||||
|
# loaded lazily via dlopen and never reached.
|
||||||
|
cd "$CASADI_DIR"
|
||||||
|
shopt -s extglob
|
||||||
|
# libc++.*.dylib only exists on darwin and is needed by _casadi.so via @rpath
|
||||||
|
rm -rf !(__init__.py|casadi.py|_casadi.so|tools|libcasadi.*|libc++.*)
|
||||||
|
shopt -u extglob
|
||||||
|
|
||||||
|
cd "$DIR"
|
||||||
|
rm -rf casadi-wheel
|
||||||
|
|
||||||
|
echo "Installed acados to $INSTALL_DIR"
|
||||||
|
du -sh "$INSTALL_DIR"
|
||||||
|
echo "Vendored casadi (slim) at $CASADI_DIR"
|
||||||
|
du -sh "$CASADI_DIR"
|
||||||
23
acados/pyproject.toml
Normal file
23
acados/pyproject.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "acados"
|
||||||
|
version = "0.2.2"
|
||||||
|
description = "acados solvers + Python interface, with a slimmed casadi vendored alongside"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
dependencies = [
|
||||||
|
# the vendored casadi.py shim imports numpy at module load time
|
||||||
|
"numpy",
|
||||||
|
]
|
||||||
|
|
||||||
|
# `find` runs at setup() init time, before build.sh creates these dirs, so
|
||||||
|
# enumerate packages explicitly. acados.acados_template + casadi.tools are
|
||||||
|
# subpackages and ship via package-data.
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["acados", "casadi"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
acados = ["install/**/*", "acados_template/**/*"]
|
||||||
|
casadi = ["**/*"]
|
||||||
68
acados/setup.py
Normal file
68
acados/setup.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from setuptools.command.build_py import build_py
|
||||||
|
|
||||||
|
# `casadi/` is generated by build.sh (vendored from the upstream wheel), but
|
||||||
|
# setuptools requires every declared package to have a directory + __init__.py
|
||||||
|
# *before* any command runs. Drop a stub if it isn't there yet — build.sh
|
||||||
|
# replaces it with the real slim casadi during build_py.
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_casadi_init = os.path.join(_HERE, "casadi", "__init__.py")
|
||||||
|
if not os.path.exists(_casadi_init):
|
||||||
|
os.makedirs(os.path.dirname(_casadi_init), exist_ok=True)
|
||||||
|
open(_casadi_init, "w").close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from wheel.bdist_wheel import bdist_wheel
|
||||||
|
except ImportError:
|
||||||
|
bdist_wheel = None
|
||||||
|
|
||||||
|
|
||||||
|
class BuildAcados(build_py):
|
||||||
|
"""Run build.sh to compile acados before collecting package data."""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
build_script = os.path.join(pkg_dir, "build.sh")
|
||||||
|
subprocess.check_call(["bash", build_script], cwd=pkg_dir)
|
||||||
|
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
|
||||||
|
cmdclass = {"build_py": BuildAcados}
|
||||||
|
|
||||||
|
if bdist_wheel is not None:
|
||||||
|
|
||||||
|
class PlatformWheel(bdist_wheel):
|
||||||
|
"""Produce a platform-specific, Python-version-agnostic wheel."""
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
super().finalize_options()
|
||||||
|
self.root_is_pure = False
|
||||||
|
|
||||||
|
def get_tag(self):
|
||||||
|
system = platform.system()
|
||||||
|
machine = platform.machine()
|
||||||
|
|
||||||
|
if system == "Linux":
|
||||||
|
plat = f"linux_{machine}"
|
||||||
|
elif system == "Darwin":
|
||||||
|
plat = "macosx_11_0_arm64"
|
||||||
|
else:
|
||||||
|
plat = f"{system.lower()}_{machine}"
|
||||||
|
|
||||||
|
return "py3", "none", plat
|
||||||
|
|
||||||
|
cmdclass["bdist_wheel"] = PlatformWheel
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
from setuptools import setup as _setup
|
||||||
|
|
||||||
|
_setup(cmdclass=cmdclass)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setup()
|
||||||
22
catch2/build.sh
Executable file
22
catch2/build.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
VERSION="v2.13.10"
|
||||||
|
INSTALL_DIR="$DIR/catch2/install"
|
||||||
|
|
||||||
|
if [ ! -d "catch2-src/.git" ]; then
|
||||||
|
rm -rf catch2-src
|
||||||
|
git clone --depth 1 https://github.com/catchorg/Catch2.git catch2-src
|
||||||
|
fi
|
||||||
|
git -C catch2-src fetch --depth 1 origin "$VERSION"
|
||||||
|
git -C catch2-src checkout --force FETCH_HEAD
|
||||||
|
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
mkdir -p "$INSTALL_DIR/include"
|
||||||
|
cp -r catch2-src/single_include/catch2 "$INSTALL_DIR/include/"
|
||||||
|
|
||||||
|
echo "Installed catch2 to $INSTALL_DIR"
|
||||||
|
du -sh "$INSTALL_DIR"
|
||||||
9
catch2/catch2/__init__.py
Normal file
9
catch2/catch2/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
DIR = os.path.join(os.path.dirname(__file__), "install")
|
||||||
|
INCLUDE_DIR = os.path.join(DIR, "include")
|
||||||
|
LIB_DIR = DIR # header-only; no libraries
|
||||||
|
|
||||||
|
|
||||||
|
def smoketest():
|
||||||
|
assert os.path.isfile(os.path.join(INCLUDE_DIR, "catch2", "catch.hpp")), "catch2/catch.hpp not found"
|
||||||
16
catch2/pyproject.toml
Normal file
16
catch2/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "catch2"
|
||||||
|
version = "2.13.10"
|
||||||
|
description = "Catch2 C++ test framework headers"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["catch2*"]
|
||||||
|
exclude = ["catch2-src*"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
catch2 = ["install/**/*"]
|
||||||
28
catch2/setup.py
Normal file
28
catch2/setup.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from setuptools.command.build_py import build_py
|
||||||
|
|
||||||
|
|
||||||
|
class BuildCatch2(build_py):
|
||||||
|
"""Run build.sh to download Catch2 headers before collecting package data."""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
build_script = os.path.join(pkg_dir, "build.sh")
|
||||||
|
subprocess.check_call(["bash", build_script], cwd=pkg_dir)
|
||||||
|
|
||||||
|
super().run()
|
||||||
|
|
||||||
|
|
||||||
|
cmdclass = {"build_py": BuildCatch2}
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
from setuptools import setup as _setup
|
||||||
|
|
||||||
|
_setup(cmdclass=cmdclass)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
setup()
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
|
||||||
cd "$DIR"
|
|
||||||
|
|
||||||
VERSION="0.5.2"
|
|
||||||
INSTALL_DIR="$DIR/mdbook/bin"
|
|
||||||
VERSION_FILE="$INSTALL_DIR/.version"
|
|
||||||
|
|
||||||
# Skip if already at correct version
|
|
||||||
if [ -f "$VERSION_FILE" ] && [ "$(cat "$VERSION_FILE")" = "$VERSION" ]; then
|
|
||||||
echo "mdbook $VERSION already present, skipping."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
OS="$(uname -s)"
|
|
||||||
ARCH="$(uname -m)"
|
|
||||||
|
|
||||||
case "${OS}-${ARCH}" in
|
|
||||||
Linux-x86_64) TARGET="x86_64-unknown-linux-musl" ;;
|
|
||||||
Linux-aarch64) TARGET="aarch64-unknown-linux-musl" ;;
|
|
||||||
Darwin-arm64) TARGET="aarch64-apple-darwin" ;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported platform: ${OS}-${ARCH}" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
FILENAME="mdbook-v${VERSION}-${TARGET}.tar.gz"
|
|
||||||
URL="https://github.com/rust-lang/mdBook/releases/download/v${VERSION}/${FILENAME}"
|
|
||||||
|
|
||||||
echo "Downloading $FILENAME ..."
|
|
||||||
curl -fSL -o "$FILENAME" "$URL"
|
|
||||||
|
|
||||||
echo "Extracting ..."
|
|
||||||
mkdir -p "$INSTALL_DIR"
|
|
||||||
tar -xzf "$FILENAME" -C "$INSTALL_DIR" mdbook
|
|
||||||
|
|
||||||
chmod +x "$INSTALL_DIR/mdbook"
|
|
||||||
|
|
||||||
rm -f "$FILENAME"
|
|
||||||
echo "$VERSION" > "$VERSION_FILE"
|
|
||||||
|
|
||||||
echo "Installed mdbook to $INSTALL_DIR"
|
|
||||||
du -sh "$INSTALL_DIR"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
BIN_DIR = os.path.join(os.path.dirname(__file__), "bin")
|
|
||||||
|
|
||||||
|
|
||||||
def _run():
|
|
||||||
binary = os.path.join(BIN_DIR, "mdbook")
|
|
||||||
os.execvp(binary, [binary] + sys.argv[1:])
|
|
||||||
|
|
||||||
|
|
||||||
def smoketest():
|
|
||||||
import subprocess
|
|
||||||
binary = os.path.join(BIN_DIR, "mdbook")
|
|
||||||
subprocess.run([binary, "--version"], check=True)
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
[tool.uv.workspace]
|
[tool.uv.workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"acados",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"capnproto",
|
"capnproto",
|
||||||
|
"catch2",
|
||||||
"cppcheck",
|
"cppcheck",
|
||||||
"eigen",
|
"eigen",
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
@@ -13,11 +15,11 @@ members = [
|
|||||||
"libusb",
|
"libusb",
|
||||||
"libjpeg",
|
"libjpeg",
|
||||||
"libyuv",
|
"libyuv",
|
||||||
"mdbook",
|
|
||||||
"nanosvg",
|
"nanosvg",
|
||||||
"ncurses",
|
"ncurses",
|
||||||
"qt5",
|
"qt5",
|
||||||
"raylib",
|
"raylib",
|
||||||
|
"xvfb",
|
||||||
"zeromq",
|
"zeromq",
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|||||||
53
release.sh
53
release.sh
@@ -65,18 +65,37 @@ for toml in sorted(pathlib.Path(".").glob("*/pyproject.toml")):
|
|||||||
for py_file in src_mod.glob("*.py"):
|
for py_file in src_mod.glob("*.py"):
|
||||||
shutil.copy2(py_file, mod_dir / py_file.name)
|
shutil.copy2(py_file, mod_dir / py_file.name)
|
||||||
|
|
||||||
# copy extra packages (e.g. pyray for raylib)
|
# copy extra packages (e.g. pyray for raylib, slim casadi for acados)
|
||||||
pkgs_val = data.get("tool", {}).get("setuptools", {}).get("packages", {})
|
# binaries (.so/.dylib) are fetched from the wheel at install time, so the
|
||||||
include_patterns = pkgs_val.get("find", {}).get("include", []) if isinstance(pkgs_val, dict) else []
|
# shim repo only carries Python sources to keep it small
|
||||||
|
def _ignore_binaries(_dir, names):
|
||||||
|
return [n for n in names if n.endswith((".so", ".dylib", ".a")) or ".so." in n or n == "__pycache__"]
|
||||||
|
|
||||||
|
# `packages` is either a flat list (["acados", "casadi"]) or a dict with
|
||||||
|
# `find.include` patterns (["acados*", "casadi*"]) — handle both
|
||||||
|
pkgs_val = data.get("tool", {}).get("setuptools", {}).get("packages", [])
|
||||||
|
if isinstance(pkgs_val, list):
|
||||||
|
include_patterns = list(pkgs_val)
|
||||||
|
elif isinstance(pkgs_val, dict):
|
||||||
|
include_patterns = pkgs_val.get("find", {}).get("include", [])
|
||||||
|
else:
|
||||||
|
include_patterns = []
|
||||||
extra_packages = []
|
extra_packages = []
|
||||||
for pattern in include_patterns:
|
for pattern in include_patterns:
|
||||||
p = pattern.rstrip("*")
|
p = pattern.rstrip("*")
|
||||||
if p and p != module and p != f"{module}/":
|
if p and p != module and p != f"{module}/":
|
||||||
src_extra = pathlib.Path(pkg) / p
|
src_extra = pathlib.Path(pkg) / p
|
||||||
|
dst_extra = pkg_dir / p
|
||||||
if src_extra.is_dir():
|
if src_extra.is_dir():
|
||||||
dst_extra = pkg_dir / p
|
shutil.copytree(src_extra, dst_extra, dirs_exist_ok=True, ignore=_ignore_binaries)
|
||||||
shutil.copytree(src_extra, dst_extra, dirs_exist_ok=True)
|
else:
|
||||||
extra_packages.append(pattern)
|
# source not in the workspace (typically built lazily by build.sh and
|
||||||
|
# not cached into the publish job). Drop a placeholder __init__.py so
|
||||||
|
# setuptools.packages.find picks it up; the shim's setup.py overwrites
|
||||||
|
# it from the wheel at install time.
|
||||||
|
dst_extra.mkdir(parents=True, exist_ok=True)
|
||||||
|
(dst_extra / "__init__.py").write_text("")
|
||||||
|
extra_packages.append(pattern)
|
||||||
|
|
||||||
shutil.copy2(shim_setup, pkg_dir / "setup.py")
|
shutil.copy2(shim_setup, pkg_dir / "setup.py")
|
||||||
|
|
||||||
@@ -102,14 +121,28 @@ for toml in sorted(pathlib.Path(".").glob("*/pyproject.toml")):
|
|||||||
for name, target in scripts.items():
|
for name, target in scripts.items():
|
||||||
lines.append(f'"{name}" = {json.dumps(target)}')
|
lines.append(f'"{name}" = {json.dumps(target)}')
|
||||||
|
|
||||||
find_include = [f"{module}*"] + extra_packages
|
# propagate the workspace's package-data so the shim wheel knows to ship
|
||||||
|
# everything the upstream wheel ships (e.g. acados_template/, casadi/*.so).
|
||||||
|
workspace_pkgdata = data.get("tool", {}).get("setuptools", {}).get("package-data", {})
|
||||||
|
|
||||||
|
# extra_packages may be plain names (["casadi"]) or globs (["casadi*"]).
|
||||||
|
# Normalise to plain package names for both `packages` and `package-data`.
|
||||||
|
extra_pkg_names = [p.rstrip("*").rstrip("/") for p in extra_packages]
|
||||||
|
|
||||||
lines += [
|
lines += [
|
||||||
"",
|
"",
|
||||||
"[tool.setuptools.packages.find]",
|
"[tool.setuptools]",
|
||||||
f"include = {json.dumps(find_include)}",
|
f"packages = {json.dumps([module] + extra_pkg_names)}",
|
||||||
"",
|
"",
|
||||||
"[tool.setuptools.package-data]",
|
"[tool.setuptools.package-data]",
|
||||||
f'{module} = ["{datadir}/**/*", "*.so"]',
|
]
|
||||||
|
module_data = sorted(set(workspace_pkgdata.get(module, [f"{datadir}/**/*"]) + ["*.so"]))
|
||||||
|
lines.append(f"{module} = {json.dumps(module_data)}")
|
||||||
|
for name in extra_pkg_names:
|
||||||
|
extra_data = workspace_pkgdata.get(name, ["**/*"])
|
||||||
|
lines.append(f"{name} = {json.dumps(list(extra_data))}")
|
||||||
|
|
||||||
|
lines += [
|
||||||
"",
|
"",
|
||||||
"[tool.shim]",
|
"[tool.shim]",
|
||||||
f'repo_url = "{repo_url}"',
|
f'repo_url = "{repo_url}"',
|
||||||
|
|||||||
133
xvfb/build.sh
Executable file
133
xvfb/build.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
|
||||||
|
cd "$DIR"
|
||||||
|
|
||||||
|
INSTALL_DIR="$DIR/xvfb/install"
|
||||||
|
|
||||||
|
# macOS: Xvfb is Linux-only. Ship an empty install dir so the wheel still
|
||||||
|
# builds; smoketest() is a no-op on Darwin.
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
mkdir -p "$INSTALL_DIR"/{bin,lib,share/X11/xkb}
|
||||||
|
echo "xvfb: macOS not supported, shipping empty install dir"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Linux: bundle the Xvfb binary, xkbcomp, xkb keymap data, and the closure
|
||||||
|
# of shared libraries it needs (minus libc/libpthread/etc which the host
|
||||||
|
# always provides). For CI/manylinux we pull from AlmaLinux 8 so the wheel
|
||||||
|
# works on any glibc >= 2.28 distro; on a Debian/Ubuntu dev host we accept
|
||||||
|
# whatever the system has (the local wheel just won't be as portable).
|
||||||
|
if command -v dnf >/dev/null 2>&1; then
|
||||||
|
dnf install -y -q xorg-x11-server-Xvfb xorg-x11-xkb-utils xkeyboard-config >/dev/null
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
|
if [[ "$(id -u)" -eq 0 ]]; then SUDO=""
|
||||||
|
elif command -v sudo >/dev/null 2>&1; then SUDO=sudo
|
||||||
|
else echo "xvfb: need sudo or root to apt-get install" >&2; exit 1; fi
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
$SUDO apt-get update -qq
|
||||||
|
$SUDO apt-get install -y -qq --no-install-recommends \
|
||||||
|
xvfb x11-xkb-utils xkb-data patchelf
|
||||||
|
else
|
||||||
|
echo "xvfb: need dnf or apt-get to fetch upstream Xvfb" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v patchelf >/dev/null 2>&1; then
|
||||||
|
echo "xvfb: patchelf is required but not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$INSTALL_DIR"
|
||||||
|
mkdir -p "$INSTALL_DIR"/{bin,lib,share/X11/xkb}
|
||||||
|
|
||||||
|
# Xvfb may live in /usr/bin (RPM/Debian) — just locate it.
|
||||||
|
XVFB_SRC="$(command -v Xvfb || true)"
|
||||||
|
XKBCOMP_SRC="$(command -v xkbcomp || true)"
|
||||||
|
if [[ -z "$XVFB_SRC" || -z "$XKBCOMP_SRC" ]]; then
|
||||||
|
echo "xvfb: Xvfb or xkbcomp not found after install" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp "$XVFB_SRC" "$INSTALL_DIR/bin/Xvfb"
|
||||||
|
cp "$XKBCOMP_SRC" "$INSTALL_DIR/bin/xkbcomp"
|
||||||
|
chmod u+w "$INSTALL_DIR/bin/Xvfb" "$INSTALL_DIR/bin/xkbcomp"
|
||||||
|
cp -a /usr/share/X11/xkb/. "$INSTALL_DIR/share/X11/xkb/"
|
||||||
|
|
||||||
|
# Two binary patches to make Xvfb relocatable:
|
||||||
|
#
|
||||||
|
# 1. "/usr/bin" -> "" (null bytes)
|
||||||
|
# Xvfb's compile-time XkbBinDirectory points at /usr/bin and is used to
|
||||||
|
# build an absolute path to xkbcomp. Blanking it makes the spawned
|
||||||
|
# command just "xkbcomp", which popen() then resolves via PATH. Our
|
||||||
|
# Python wrapper prepends the bundled bin/ to PATH.
|
||||||
|
#
|
||||||
|
# 2. "-R%s" -> "-I%s" (in the xkbcomp argv format string)
|
||||||
|
# Xvfb passes its XkbBaseDirectory to xkbcomp via -R, expecting xkbcomp
|
||||||
|
# to chdir there *and* add "." to the include path. xkbcomp 1.4.x only
|
||||||
|
# chdirs — the include-path side was added later. Switching to -I makes
|
||||||
|
# 1.4.2 actually search the path we hand it via -xkbdir.
|
||||||
|
python3 - "$INSTALL_DIR/bin/Xvfb" <<'PY'
|
||||||
|
import sys
|
||||||
|
path = sys.argv[1]
|
||||||
|
with open(path, "r+b") as f:
|
||||||
|
data = bytearray(f.read())
|
||||||
|
|
||||||
|
def replace_unique(needle: bytes, replacement: bytes):
|
||||||
|
assert len(needle) == len(replacement)
|
||||||
|
idx = data.find(needle)
|
||||||
|
if idx < 0:
|
||||||
|
sys.exit(f"could not find {needle!r} in Xvfb binary")
|
||||||
|
if data.find(needle, idx + 1) >= 0:
|
||||||
|
sys.exit(f"multiple {needle!r} matches; refusing to patch")
|
||||||
|
data[idx:idx + len(needle)] = replacement
|
||||||
|
|
||||||
|
replace_unique(b"/usr/bin\x00", b"\x00" * 9)
|
||||||
|
replace_unique(b'"-R%s"\x00', b'"-I%s"\x00')
|
||||||
|
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
PY
|
||||||
|
|
||||||
|
# bundle the shared library closure. Recursively walk ldd output to catch
|
||||||
|
# libs-of-libs (e.g. libXfont2 -> libfontenc -> libbz2). Skip core glibc
|
||||||
|
# pieces; everything else gets copied alongside the binary.
|
||||||
|
declare -A SEEN
|
||||||
|
collect_libs() {
|
||||||
|
local target="$1"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
local lib
|
||||||
|
lib=$(echo "$line" | awk '{print $3}')
|
||||||
|
[[ -z "$lib" || "$lib" == "not" ]] && continue
|
||||||
|
[[ ! -e "$lib" ]] && continue
|
||||||
|
local base
|
||||||
|
base=$(basename "$lib")
|
||||||
|
case "$base" in
|
||||||
|
libc.so.*|libpthread.so.*|libm.so.*|librt.so.*|libdl.so.*|libgcc_s.so.*|libresolv.so.*|libutil.so.*|ld-linux-*.so.*|linux-vdso.so.*|linux-gate.so.*)
|
||||||
|
continue ;;
|
||||||
|
esac
|
||||||
|
[[ -n "${SEEN[$base]:-}" ]] && continue
|
||||||
|
SEEN[$base]=1
|
||||||
|
cp -L "$lib" "$INSTALL_DIR/lib/$base"
|
||||||
|
chmod u+w "$INSTALL_DIR/lib/$base"
|
||||||
|
collect_libs "$INSTALL_DIR/lib/$base"
|
||||||
|
done < <(ldd "$target" 2>/dev/null || true)
|
||||||
|
}
|
||||||
|
collect_libs "$INSTALL_DIR/bin/Xvfb"
|
||||||
|
collect_libs "$INSTALL_DIR/bin/xkbcomp"
|
||||||
|
|
||||||
|
# point the binaries (and the bundled libs) at our private lib dir so they
|
||||||
|
# don't accidentally resolve against an incompatible host copy.
|
||||||
|
patchelf --set-rpath '$ORIGIN/../lib' "$INSTALL_DIR/bin/Xvfb"
|
||||||
|
patchelf --set-rpath '$ORIGIN/../lib' "$INSTALL_DIR/bin/xkbcomp"
|
||||||
|
for so in "$INSTALL_DIR"/lib/*.so*; do
|
||||||
|
patchelf --set-rpath '$ORIGIN' "$so" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
strip --strip-unneeded "$INSTALL_DIR/bin/Xvfb" "$INSTALL_DIR/bin/xkbcomp" 2>/dev/null || true
|
||||||
|
find "$INSTALL_DIR/lib" -name '*.so*' -exec strip --strip-unneeded {} + 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Installed xvfb to $INSTALL_DIR"
|
||||||
|
du -sh "$INSTALL_DIR"
|
||||||
@@ -3,16 +3,16 @@ requires = ["setuptools>=64", "wheel"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mdbook"
|
name = "xvfb"
|
||||||
version = "0.5.2"
|
version = "1.20.11.post1"
|
||||||
description = "mdBook command-line tool for creating books from Markdown"
|
description = "Xvfb (X virtual framebuffer) headless X server"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
mdbook = "mdbook:_run"
|
Xvfb = "xvfb:_run_xvfb"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
include = ["mdbook*"]
|
include = ["xvfb*"]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
mdbook = ["bin/**/*"]
|
xvfb = ["install/**/*"]
|
||||||
@@ -10,8 +10,8 @@ except ImportError:
|
|||||||
bdist_wheel = None
|
bdist_wheel = None
|
||||||
|
|
||||||
|
|
||||||
class BuildMdbook(build_py):
|
class BuildXvfb(build_py):
|
||||||
"""Run build.sh to download mdbook before collecting package data."""
|
"""Run build.sh to fetch and bundle Xvfb before collecting package data."""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
pkg_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
@@ -21,7 +21,7 @@ class BuildMdbook(build_py):
|
|||||||
super().run()
|
super().run()
|
||||||
|
|
||||||
|
|
||||||
cmdclass = {"build_py": BuildMdbook}
|
cmdclass = {"build_py": BuildXvfb}
|
||||||
|
|
||||||
if bdist_wheel is not None:
|
if bdist_wheel is not None:
|
||||||
|
|
||||||
57
xvfb/xvfb/__init__.py
Normal file
57
xvfb/xvfb/__init__.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DIR = os.path.join(os.path.dirname(__file__), "install")
|
||||||
|
BIN_DIR = os.path.join(DIR, "bin")
|
||||||
|
LIB_DIR = os.path.join(DIR, "lib")
|
||||||
|
XKB_DIR = os.path.join(DIR, "share", "X11", "xkb")
|
||||||
|
|
||||||
|
XVFB_BIN = os.path.join(BIN_DIR, "Xvfb")
|
||||||
|
XKBCOMP_BIN = os.path.join(BIN_DIR, "xkbcomp")
|
||||||
|
|
||||||
|
|
||||||
|
# Common host paths where Mesa DRI drivers (e.g. swrast_dri.so) live. Xvfb
|
||||||
|
# was built on AlmaLinux 8 with /usr/lib64/dri baked in; on other distros
|
||||||
|
# (Debian/Ubuntu in particular) the drivers are elsewhere, and without them
|
||||||
|
# Xvfb fails to bring up a GL provider and silently disables the GLX
|
||||||
|
# extension. Probing the standard locations lets the host's drivers be
|
||||||
|
# found regardless of distro.
|
||||||
|
_DRI_PATHS = (
|
||||||
|
"/usr/lib64/dri",
|
||||||
|
"/usr/lib/x86_64-linux-gnu/dri",
|
||||||
|
"/usr/lib/aarch64-linux-gnu/dri",
|
||||||
|
"/usr/lib/dri",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_xvfb():
|
||||||
|
# The bundled Xvfb has its compile-time XkbBinDirectory blanked out so it
|
||||||
|
# invokes xkbcomp via PATH lookup; prepend our bin dir so the bundled
|
||||||
|
# xkbcomp wins. -xkbdir points the server at the bundled keymap data.
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PATH"] = BIN_DIR + os.pathsep + env.get("PATH", "")
|
||||||
|
if "LIBGL_DRIVERS_PATH" not in env:
|
||||||
|
found = [p for p in _DRI_PATHS if os.path.isdir(p)]
|
||||||
|
if found:
|
||||||
|
env["LIBGL_DRIVERS_PATH"] = os.pathsep.join(found)
|
||||||
|
args = sys.argv[1:]
|
||||||
|
if not any(a == "-xkbdir" for a in args):
|
||||||
|
args = ["-xkbdir", XKB_DIR] + args
|
||||||
|
os.execvpe(XVFB_BIN, [XVFB_BIN] + args, env)
|
||||||
|
|
||||||
|
|
||||||
|
def smoketest():
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
return
|
||||||
|
assert os.path.isfile(XVFB_BIN), f"Xvfb not found at {XVFB_BIN}"
|
||||||
|
assert os.path.isfile(XKBCOMP_BIN), f"xkbcomp not found at {XKBCOMP_BIN}"
|
||||||
|
assert os.path.isdir(XKB_DIR), f"xkb data not found at {XKB_DIR}"
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
# Xvfb prints usage to stderr and exits non-zero on `-help`; the banner
|
||||||
|
# mentions Xvfb-specific flags like -screen and -fbdir. If those appear,
|
||||||
|
# the binary loaded its bundled libs and ran far enough to print help.
|
||||||
|
result = subprocess.run([XVFB_BIN, "-help"], capture_output=True, text=True)
|
||||||
|
output = result.stderr + result.stdout
|
||||||
|
assert "-screen scrn WxHxD" in output, \
|
||||||
|
f"Xvfb -help did not produce expected output: {output}"
|
||||||
Reference in New Issue
Block a user