diff --git a/build.sh b/build.sh index a2825ed..629d2c0 100755 --- a/build.sh +++ b/build.sh @@ -68,6 +68,7 @@ echo echo "Running smoketests" uv venv --allow-existing --quiet "$VENV_DIR" +uv pip install --python "$VENV_DIR/bin/python" --quiet "cffi>=1.17.1" >/dev/null uv pip install --python "$VENV_DIR/bin/python" --reinstall --no-deps --quiet dist/*.whl >/dev/null for toml in */pyproject.toml; do diff --git a/raylib/.gitignore b/raylib/.gitignore index 6ee9dcf..984172e 100644 --- a/raylib/.gitignore +++ b/raylib/.gitignore @@ -1,4 +1,3 @@ raylib-src/ raylib/install/ -raylib/*.modified raylib/_raylib_cffi* diff --git a/raylib/build.sh b/raylib/build.sh index d3c0c67..47bfea1 100755 --- a/raylib/build.sh +++ b/raylib/build.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -e +set -euo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" cd "$DIR" @@ -7,113 +7,85 @@ cd "$DIR" INSTALL_DIR="$DIR/raylib/install" NJOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2)" -CC="ccache ${CC:-cc}" +if command -v ccache &>/dev/null; then + CC="ccache ${CC:-cc}" +else + CC="${CC:-cc}" +fi + +is_linux_aarch64() { + [[ "$(uname)" == "Linux" && ( "$(uname -m)" == "aarch64" || "$(uname -m)" == "arm64" ) ]] +} -# Detect platform: PLATFORM_COMMA for comma devices, PLATFORM_DESKTOP otherwise -RAYLIB_PLATFORM="${RAYLIB_PLATFORM:-PLATFORM_DESKTOP}" -if [ -f /TICI ]; then - RAYLIB_PLATFORM="PLATFORM_COMMA" +if [ -n "${RAYLIB_PLATFORM:-}" ]; then + echo "RAYLIB_PLATFORM is no longer supported; use RAYLIB_BACKEND=desktop or RAYLIB_BACKEND=comma" >&2 + exit 1 fi -export RAYLIB_PLATFORM - -# Install build dependencies -if [[ "$(uname)" == "Linux" ]]; then - if [ "$RAYLIB_PLATFORM" = "PLATFORM_COMMA" ]; then - # comma device: needs DRM/EGL/GLES headers (usually already present on AGNOS) - # apt may fail on devices due to read-only rootfs or package conflicts — that's OK - if command -v apt-get &>/dev/null; then - if [ "$(id -u)" -eq 0 ]; then - apt-get update && apt-get install -y libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev || true - else - sudo apt-get update && sudo apt-get install -y libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev || true - fi - fi - elif [ "$RAYLIB_PLATFORM" = "PLATFORM_OFFSCREEN" ]; then - # offscreen (CI): needs EGL/GL dev packages (no X11) - if command -v apt-get &>/dev/null; then - if [ "$(id -u)" -eq 0 ]; then - apt-get update && apt-get install -y libegl-dev libgl-dev - else - sudo apt-get update && sudo apt-get install -y libegl-dev libgl-dev - fi - fi - else - # desktop: needs X11/GL dev packages - if command -v dnf &>/dev/null; then - dnf install -y libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel mesa-libGL-devel - elif command -v apt-get &>/dev/null; then - if [ "$(id -u)" -eq 0 ]; then - apt-get update && apt-get install -y libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libgl-dev - else - sudo apt-get update && sudo apt-get install -y libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libgl-dev - fi - fi + +RAYLIB_BACKEND="${RAYLIB_BACKEND:-}" +if [ -z "$RAYLIB_BACKEND" ]; then + RAYLIB_BACKEND="desktop" + if [ -f /AGNOS ] || [ -f /TICI ]; then + RAYLIB_BACKEND="comma" fi fi +case "$RAYLIB_BACKEND" in + desktop|comma) ;; + *) + echo "Unsupported RAYLIB_BACKEND=$RAYLIB_BACKEND; expected desktop or comma" >&2 + exit 1 + ;; +esac + # Clone and build raylib C library -RAYLIB_COMMIT="d9d7cc1353ec0f73c97e84ddf0973983d1ee25e2" +RAYLIB_COMMIT="dff603f4f122163900469e73d113deacd9ec9817" if [ ! -d "raylib-src/.git" ]; then rm -rf raylib-src - git clone --depth 1 -b platform-offscreen --no-tags https://github.com/commaai/raylib.git raylib-src + git clone --depth 1 -b master --no-tags https://github.com/commaai/raylib.git raylib-src fi cd raylib-src +git remote set-url origin https://github.com/commaai/raylib.git git fetch --depth 1 origin "$RAYLIB_COMMIT" git reset --hard "$RAYLIB_COMMIT" -cd src -make clean -make -j"$NJOBS" PLATFORM="$RAYLIB_PLATFORM" CC="${CC:-gcc}" - cd "$DIR" # Install lib + headers rm -rf "$INSTALL_DIR" mkdir -p "$INSTALL_DIR"/{lib,include} -cp raylib-src/src/libraylib.a "$INSTALL_DIR/lib/" cp raylib-src/src/raylib.h raylib-src/src/raymath.h raylib-src/src/rlgl.h "$INSTALL_DIR/include/" -# On x86_64 Linux, also build the offscreen variant for CI headless rendering -if [[ "$(uname)" == "Linux" && "$(uname -m)" == "x86_64" && "$RAYLIB_PLATFORM" != "PLATFORM_OFFSCREEN" ]]; then - echo "Building offscreen variant..." - - # Install EGL/GL dev packages needed for offscreen build + bundling - if command -v dnf &>/dev/null; then - dnf install -y mesa-libEGL-devel mesa-libGL-devel libglvnd-opengl libglvnd-core-devel 2>/dev/null || true - elif command -v apt-get &>/dev/null; then - if [ "$(id -u)" -eq 0 ]; then - apt-get update && apt-get install -y libegl-dev libgl-dev - else - sudo apt-get update && sudo apt-get install -y libegl-dev libgl-dev - fi - fi +build_raylib() { + local platform="$1" + local output="$2" - cd raylib-src/src + cd "$DIR/raylib-src/src" make clean - make -j"$NJOBS" PLATFORM=PLATFORM_OFFSCREEN CC="${CC:-gcc}" - cp libraylib.a "$INSTALL_DIR/lib/libraylib_offscreen.a" + make -j"$NJOBS" PLATFORM="$platform" CC="${CC:-gcc}" + cp libraylib.a "$INSTALL_DIR/lib/$output" cd "$DIR" +} + +if is_linux_aarch64; then + echo "Building desktop backend..." + build_raylib PLATFORM_DESKTOP libraylib_desktop.a - # Bundle GLVND dispatchers so offscreen rendering works without extra system packages - MESA_DIR="$INSTALL_DIR/lib/mesa" - mkdir -p "$MESA_DIR" - ldconfig 2>/dev/null || true - for lib in libEGL.so.1 libOpenGL.so.0 libGLdispatch.so.0; do - src="$(ldconfig -p 2>/dev/null | grep "$lib" | grep -E 'x86.64|libc6,' | awk '{print $NF}' | head -1)" - if [ -n "$src" ] && [ -f "$src" ]; then - cp -L "$src" "$MESA_DIR/" - # Create unversioned symlink for the linker - base="${lib%%.so.*}" - ln -sf "$lib" "$MESA_DIR/${base}.so" - fi - done + echo "Building comma backend..." + build_raylib PLATFORM_COMMA libraylib_comma.a +else + if [ "$RAYLIB_BACKEND" = "comma" ]; then + build_raylib PLATFORM_COMMA libraylib_comma.a + else + build_raylib PLATFORM_DESKTOP libraylib_desktop.a + fi fi # Download raygui header -RAYGUI_COMMIT="76b36b597edb70ffaf96f046076adc20d67e7827" +RAYGUI_COMMIT="1e03efca48c50c5ea4b4a053d5bf04bad58d3e43" curl -fsSLo "$INSTALL_DIR/include/raygui.h" \ "https://raw.githubusercontent.com/raysan5/raygui/$RAYGUI_COMMIT/src/raygui.h" diff --git a/raylib/build_cffi.py b/raylib/build_cffi.py new file mode 100644 index 0000000..e0350f1 --- /dev/null +++ b/raylib/build_cffi.py @@ -0,0 +1,109 @@ +# Based on commaai/raylib-python-cffi (commit a32e910) +# Modified to use local install paths and compile standalone. + +import os +import platform +import re +import subprocess +import sys + +from cffi import FFI + +ROOT = os.path.dirname(os.path.abspath(__file__)) +PACKAGE_DIR = os.path.join(ROOT, "raylib") +RAYLIB_INCLUDE_PATH = os.path.join(PACKAGE_DIR, "install", "include") +RAYLIB_LIB_PATH = os.path.join(PACKAGE_DIR, "install", "lib") + +sys.path.insert(0, PACKAGE_DIR) +from _backend import BACKEND_ARCHIVES, BACKEND_CFFI_MODULES, BACKEND_LINK_ARGS, detect_backend # noqa: E402 +from validate_bindings import validate_static_bindings # noqa: E402 + +RAYLIB_BACKEND = detect_backend() +RAYLIB_CFFI_MODULE = os.getenv("RAYLIB_CFFI_MODULE", f"raylib.{BACKEND_CFFI_MODULES[RAYLIB_BACKEND]}") + +ffibuilder = FFI() + + +def _raylib_archive(): + archive = os.path.join(RAYLIB_LIB_PATH, BACKEND_ARCHIVES[RAYLIB_BACKEND]) + if not os.path.isfile(archive): + raise FileNotFoundError(f"{archive} not found. Please run build.sh first.") + return archive + + +def pre_process_header(filename, remove_function_bodies=False): + print("Pre-processing " + filename) + with open(filename, "r") as f: + filetext = "".join([line for line in f if '#include' not in line]) + command = ['gcc', '-CC', '-P', '-undef', '-nostdinc', '-DRL_MATRIX_TYPE', + '-DRL_QUATERNION_TYPE', '-DRL_VECTOR4_TYPE', '-DRL_VECTOR3_TYPE', '-DRL_VECTOR2_TYPE', + '-DRLAPI=', '-DPHYSACDEF=', '-DRAYGUIDEF=', '-DRMAPI=', + '-dDI', '-E', '-'] + filetext = subprocess.run(command, text=True, input=filetext, stdout=subprocess.PIPE, check=True).stdout + filetext = filetext.replace("va_list", "void *") + if remove_function_bodies: + filetext = re.sub('\n{\n(.|\n)*?\n}\n', ';', filetext) + return "\n".join([line for line in filetext.splitlines() if not line.startswith("#")]) + + +def build_ffi(): + raylib_archive = _raylib_archive() + + raylib_h = os.path.join(RAYLIB_INCLUDE_PATH, "raylib.h") + rlgl_h = os.path.join(RAYLIB_INCLUDE_PATH, "rlgl.h") + raymath_h = os.path.join(RAYLIB_INCLUDE_PATH, "raymath.h") + raygui_h = os.path.join(RAYLIB_INCLUDE_PATH, "raygui.h") + + for header in (raylib_h, rlgl_h, raymath_h, raygui_h): + if not os.path.isfile(header): + raise FileNotFoundError(f"{header} not found. Please run build.sh first.") + + ffi_includes = """ + #include "raylib.h" + #include "rlgl.h" + #include "raymath.h" + #define RAYGUI_IMPLEMENTATION + #define RAYGUI_SUPPORT_RICONS + #include "raygui.h" + """ + + ffibuilder.cdef(pre_process_header(raylib_h)) + ffibuilder.cdef(pre_process_header(rlgl_h)) + ffibuilder.cdef(pre_process_header(raymath_h, True)) + ffibuilder.cdef(pre_process_header(raygui_h)) + + validate_static_bindings(PACKAGE_DIR, RAYLIB_INCLUDE_PATH) + + if platform.system() == "Darwin": + print("BUILDING FOR MAC") + extra_link_args = [ + raylib_archive, + '-framework', 'OpenGL', + '-framework', 'Cocoa', + '-framework', 'IOKit', + '-framework', 'CoreFoundation', + '-framework', 'CoreVideo', + ] + extra_compile_args = ["-Wno-error=incompatible-function-pointer-types"] + else: + print("BUILDING FOR LINUX") + extra_link_args = [ + raylib_archive, + '-lm', '-lpthread', '-lrt', '-ldl', '-latomic', + *BACKEND_LINK_ARGS[RAYLIB_BACKEND], + ] + extra_compile_args = ["-Wno-incompatible-pointer-types"] + + print("extra_link_args: " + str(extra_link_args)) + ffibuilder.set_source(RAYLIB_CFFI_MODULE, + ffi_includes, + py_limited_api=True, + include_dirs=[RAYLIB_INCLUDE_PATH], + extra_link_args=extra_link_args, + extra_compile_args=extra_compile_args, + libraries=[]) + + +if __name__ == "__main__": + build_ffi() + ffibuilder.compile(verbose=True, tmpdir=ROOT) diff --git a/raylib/pyproject.toml b/raylib/pyproject.toml index 9bcfb1a..2a7f4b9 100644 --- a/raylib/pyproject.toml +++ b/raylib/pyproject.toml @@ -1,16 +1,20 @@ [build-system] -requires = ["setuptools>=64", "wheel", "cffi>=1.17.1"] +requires = ["setuptools>=70.1", "cffi>=1.17.1"] build-backend = "setuptools.build_meta" [project] name = "raylib" -version = "5.5.0.8" +version = "6.0.0.0" description = "raylib + pyray Python bindings (commaai fork)" requires-python = ">=3.8" dependencies = ["cffi>=1.17.1"] +[tool.setuptools] +include-package-data = false + [tool.setuptools.packages.find] -include = ["raylib*", "pyray*"] +include = ["raylib", "pyray"] +namespaces = false [tool.setuptools.package-data] -raylib = ["install/**/*", "_raylib_cffi*.so"] +raylib = ["_raylib_cffi*.so", "install/include/*.h", "install/lib/*.a"] diff --git a/raylib/raylib/__init__.py b/raylib/raylib/__init__.py index cbcc4cc..6ea6e43 100644 --- a/raylib/raylib/__init__.py +++ b/raylib/raylib/__init__.py @@ -1,71 +1,45 @@ +import importlib import os -import platform as _platform + +from ._backend import BACKEND_ARCHIVES, BACKEND_CFFI_MODULES, detect_backend, is_dual_backend_host +from .version import __version__ DIR = os.path.join(os.path.dirname(__file__), "install") -LIB_DIR = os.path.join(DIR, "lib") INCLUDE_DIR = os.path.join(DIR, "include") +LIB_DIR = os.path.join(DIR, "lib") +_BACKEND = detect_backend() -def _detect_platform(): - """Auto-detect the raylib platform. In CI on Linux x86_64, use offscreen EGL rendering.""" - explicit = os.environ.get("RAYLIB_PLATFORM", "") - if explicit: - return explicit - if os.environ.get("CI") and _platform.system() == "Linux" and _platform.machine() == "x86_64": - return "PLATFORM_OFFSCREEN" - return "" - - -def smoketest(): - assert os.path.isfile(os.path.join(LIB_DIR, "libraylib.a")), "libraylib.a not found" - assert os.path.isfile(os.path.join(INCLUDE_DIR, "raylib.h")), "raylib.h not found" - - -# Build CFFI extension on first import if not already compiled, -# or rebuild if the target platform changed since last build. -def _ensure_cffi_built(): - import glob - import subprocess - import sys - pkg_dir = os.path.dirname(__file__) - platform_marker = os.path.join(pkg_dir, ".raylib_platform") - requested = _detect_platform() - - # Export so build.py picks it up - if requested: - os.environ["RAYLIB_PLATFORM"] = requested - # Mesa llvmpipe for software rendering in headless CI - if requested == "PLATFORM_OFFSCREEN": - os.environ.setdefault("LIBGL_ALWAYS_SOFTWARE", "1") - - cffi_files = glob.glob(os.path.join(pkg_dir, "_raylib_cffi*")) - - # Rebuild if platform changed - if cffi_files and requested: - built_for = open(platform_marker).read().strip() if os.path.isfile(platform_marker) else "" - if built_for != requested: - for f in cffi_files: - os.remove(f) - cffi_files = [] - if not cffi_files: - build_script = os.path.join(pkg_dir, "build.py") - if os.path.isfile(build_script) and os.path.isfile(os.path.join(LIB_DIR, "libraylib.a")): - try: - subprocess.check_call([sys.executable, build_script], cwd=os.path.dirname(pkg_dir)) - with open(platform_marker, "w") as f: - f.write(requested) - except subprocess.CalledProcessError: - pass +def _expected_archives(): + if is_dual_backend_host(): + return BACKEND_ARCHIVES.values() + return (BACKEND_ARCHIVES[_BACKEND],) -_ensure_cffi_built() -# CFFI bindings (available when graphics libraries are present) -try: - from ._raylib_cffi import ffi, lib as rl - from raylib._raylib_cffi.lib import * # noqa: F403 - from raylib.colors import * # noqa: F403 - from raylib.defines import * # noqa: F403 - from .version import __version__ -except (ImportError, OSError): - pass +def smoketest(): + assert ffi is not None + assert rl is not None + for header in ("raylib.h", "raymath.h", "rlgl.h", "raygui.h"): + assert os.path.isfile(os.path.join(INCLUDE_DIR, header)), f"{header} not found" + for archive in _expected_archives(): + assert os.path.isfile(os.path.join(LIB_DIR, archive)), f"{archive} not found" + + +def _load_cffi(): + backend_module = BACKEND_CFFI_MODULES[_BACKEND] + try: + return importlib.import_module(f".{backend_module}", __name__) + except (ImportError, OSError) as e: + raise ImportError(f"failed to load raylib {_BACKEND} backend extension {backend_module}") from e + + +_cffi = _load_cffi() +ffi, rl = _cffi.ffi, _cffi.lib +# Module name is dynamic per backend, so we can't use `from ._raylib_cffi_X.lib import *`. +for _name in dir(rl): + if not _name.startswith("_"): + globals()[_name] = getattr(rl, _name) +del _name +from raylib.colors import * # noqa: F403, E402 +from raylib.defines import * # noqa: F403, E402 diff --git a/raylib/raylib/_backend.py b/raylib/raylib/_backend.py new file mode 100644 index 0000000..b1e52c1 --- /dev/null +++ b/raylib/raylib/_backend.py @@ -0,0 +1,45 @@ +import os +import platform + +DESKTOP = "desktop" +COMMA = "comma" +BACKENDS = (DESKTOP, COMMA) +DEFAULT_BACKEND = DESKTOP + +BACKEND_CFFI_MODULES = { + DESKTOP: "_raylib_cffi_desktop", + COMMA: "_raylib_cffi_comma", +} + +BACKEND_ARCHIVES = { + DESKTOP: "libraylib_desktop.a", + COMMA: "libraylib_comma.a", +} + +BACKEND_LINK_ARGS = { + DESKTOP: ("-lGL", "-lX11"), + COMMA: ("-lGLESv2", "-lEGL", "-lgbm", "-ldrm"), +} + +COMMA_DEVICE_MARKERS = ("/AGNOS", "/TICI") + + +def is_dual_backend_host(): + return platform.system() == "Linux" and platform.machine() in ("aarch64", "arm64") + + +def detect_backend(environ=None, exists=os.path.exists): + environ = os.environ if environ is None else environ + if environ.get("RAYLIB_PLATFORM"): + raise ValueError("RAYLIB_PLATFORM is no longer supported; use RAYLIB_BACKEND=comma|desktop") + + explicit = environ.get("RAYLIB_BACKEND", "").strip().lower() + if explicit: + if explicit not in BACKENDS: + raise ValueError("RAYLIB_BACKEND must be 'comma' or 'desktop'") + return explicit + + if any(exists(marker) for marker in COMMA_DEVICE_MARKERS): + return COMMA + + return DEFAULT_BACKEND diff --git a/raylib/raylib/build.py b/raylib/raylib/build.py deleted file mode 100644 index 74590f3..0000000 --- a/raylib/raylib/build.py +++ /dev/null @@ -1,137 +0,0 @@ -# Based on commaai/raylib-python-cffi (commit ab0191f) -# Modified to use local install paths and compile standalone (no cffi_modules) - -import re -import os -import platform -import subprocess -import time - -from cffi import FFI - -HERE = os.path.dirname(os.path.abspath(__file__)) -RAYLIB_INCLUDE_PATH = os.path.join(HERE, "install", "include") -RAYLIB_LIB_PATH = os.path.join(HERE, "install", "lib") -RAYLIB_PLATFORM = os.getenv("RAYLIB_PLATFORM", "") - -ffibuilder = FFI() - - -def check_raylib_installed(): - return os.path.isfile(os.path.join(RAYLIB_LIB_PATH, 'libraylib.a')) - - -def pre_process_header(filename, remove_function_bodies=False): - print("Pre-processing " + filename) - file = open(filename, "r") - filetext = "".join([line for line in file if '#include' not in line]) - command = ['gcc', '-CC', '-P', '-undef', '-nostdinc', '-DRL_MATRIX_TYPE', - '-DRL_QUATERNION_TYPE', '-DRL_VECTOR4_TYPE', '-DRL_VECTOR3_TYPE', '-DRL_VECTOR2_TYPE', - '-DRLAPI=', '-DPHYSACDEF=', '-DRAYGUIDEF=', '-DRMAPI=', - '-dDI', '-E', '-'] - filetext = subprocess.run(command, text=True, input=filetext, stdout=subprocess.PIPE).stdout - filetext = filetext.replace("va_list", "void *") - if remove_function_bodies: - filetext = re.sub('\n{\n(.|\n)*?\n}\n', ';', filetext) - filetext = "\n".join([line for line in filetext.splitlines() if not line.startswith("#")]) - modified_path = os.path.join(HERE, os.path.basename(filename) + ".modified") - with open(modified_path, "w") as f: - f.write(filetext) - return filetext - - -def check_header_exists(file): - if not os.path.isfile(file): - print(f"\nWARNING: {file} not found. Build will not contain these extra functions.\n") - time.sleep(1) - return False - return True - - -def build_ffi(): - """Set up the FFI builder. Must be called after libraylib.a and headers exist.""" - if not check_raylib_installed(): - raise Exception("ERROR: raylib not found. Please run build.sh first.") - - raylib_h = os.path.join(RAYLIB_INCLUDE_PATH, "raylib.h") - rlgl_h = os.path.join(RAYLIB_INCLUDE_PATH, "rlgl.h") - raymath_h = os.path.join(RAYLIB_INCLUDE_PATH, "raymath.h") - - for h in (raylib_h, rlgl_h, raymath_h): - if not os.path.isfile(h): - raise Exception(f"ERROR: {h} not found. Please run build.sh first.") - - ffi_includes = """ - #include "raylib.h" - #include "rlgl.h" - #include "raymath.h" - """ - - raygui_h = os.path.join(RAYLIB_INCLUDE_PATH, "raygui.h") - if check_header_exists(raygui_h): - ffi_includes += """ - #define RAYGUI_IMPLEMENTATION - #define RAYGUI_SUPPORT_RICONS - #include "raygui.h" - """ - - ffibuilder.cdef(pre_process_header(raylib_h)) - ffibuilder.cdef(pre_process_header(rlgl_h)) - ffibuilder.cdef(pre_process_header(raymath_h, True)) - - if os.path.isfile(raygui_h): - ffibuilder.cdef(pre_process_header(raygui_h)) - - if platform.system() == "Darwin": - print("BUILDING FOR MAC") - extra_link_args = [ - os.path.join(RAYLIB_LIB_PATH, 'libraylib.a'), - '-framework', 'OpenGL', - '-framework', 'Cocoa', - '-framework', 'IOKit', - '-framework', 'CoreFoundation', - '-framework', 'CoreVideo', - ] - libraries = [] - extra_compile_args = ["-Wno-error=incompatible-function-pointer-types"] - else: - print("BUILDING FOR LINUX") - extra_link_args = [ - f'-L{RAYLIB_LIB_PATH}', '-lraylib', - '-lm', '-lpthread', '-lGL', - '-lrt', '-ldl', '-lpthread', '-latomic', - ] - if RAYLIB_PLATFORM == "PLATFORM_COMMA": - extra_link_args.remove('-lGL') - extra_link_args += ['-lGLESv2', '-lEGL', '-lgbm', '-ldrm'] - elif RAYLIB_PLATFORM == "PLATFORM_OFFSCREEN": - # Use offscreen variant if available, otherwise fall back to default - offscreen_lib = os.path.join(RAYLIB_LIB_PATH, 'libraylib_offscreen.a') - if os.path.isfile(offscreen_lib): - extra_link_args[extra_link_args.index('-lraylib')] = '-lraylib_offscreen' - extra_link_args.remove('-lGL') - # Use bundled GLVND dispatchers if available, with RPATH for runtime - mesa_dir = os.path.join(RAYLIB_LIB_PATH, 'mesa') - if os.path.isdir(mesa_dir): - extra_link_args += [f'-L{mesa_dir}', f'-Wl,-rpath,$ORIGIN/install/lib/mesa'] - extra_link_args += ['-lOpenGL', '-lEGL'] - else: - extra_link_args += ['-lX11'] - extra_compile_args = ["-Wno-incompatible-pointer-types"] - libraries = [] - - print("extra_link_args: " + str(extra_link_args)) - ffibuilder.set_source("raylib._raylib_cffi", - ffi_includes, - py_limited_api=True, - include_dirs=[RAYLIB_INCLUDE_PATH], - extra_link_args=extra_link_args, - extra_compile_args=extra_compile_args, - libraries=libraries) - - -if __name__ == "__main__": - build_ffi() - # compile with output going to the package root (parent of raylib/) - pkg_root = os.path.dirname(HERE) - ffibuilder.compile(verbose=True, tmpdir=pkg_root) diff --git a/raylib/raylib/defines.py b/raylib/raylib/defines.py index a7e9902..b83bd58 100644 --- a/raylib/raylib/defines.py +++ b/raylib/raylib/defines.py @@ -1,9 +1,9 @@ import raylib -RAYLIB_VERSION_MAJOR: int = 5 -RAYLIB_VERSION_MINOR: int = 5 +RAYLIB_VERSION_MAJOR: int = 6 +RAYLIB_VERSION_MINOR: int = 0 RAYLIB_VERSION_PATCH: int = 0 -RAYLIB_VERSION: str = "5.5" +RAYLIB_VERSION: str = "6.0" PI: float = 3.141592653589793 DEG2RAD = PI / 180.0 RAD2DEG = 180.0 / PI @@ -15,7 +15,7 @@ SHADER_LOC_MAP_DIFFUSE = raylib.SHADER_LOC_MAP_ALBEDO SHADER_LOC_MAP_SPECULAR = raylib.SHADER_LOC_MAP_METALNESS EPSILON: float = 1e-06 -RLGL_VERSION: str = "5.0" +RLGL_VERSION: str = "6.0" RL_DEFAULT_BATCH_BUFFER_ELEMENTS: int = 8192 RL_DEFAULT_BATCH_BUFFERS: int = 1 RL_DEFAULT_BATCH_DRAWCALLS: int = 256 diff --git a/raylib/raylib/enums.py b/raylib/raylib/enums.py index b397095..8802191 100644 --- a/raylib/raylib/enums.py +++ b/raylib/raylib/enums.py @@ -232,7 +232,8 @@ class ShaderLocationIndex(IntEnum): SHADER_LOC_MAP_BRDF = 25 SHADER_LOC_VERTEX_BONEIDS = 26 SHADER_LOC_VERTEX_BONEWEIGHTS = 27 - SHADER_LOC_BONE_MATRICES = 28 + SHADER_LOC_MATRIX_BONETRANSFORMS = 28 + SHADER_LOC_VERTEX_INSTANCETRANSFORM = 29 class ShaderUniformDataType(IntEnum): SHADER_UNIFORM_FLOAT = 0 @@ -243,7 +244,11 @@ class ShaderUniformDataType(IntEnum): SHADER_UNIFORM_IVEC2 = 5 SHADER_UNIFORM_IVEC3 = 6 SHADER_UNIFORM_IVEC4 = 7 - SHADER_UNIFORM_SAMPLER2D = 8 + SHADER_UNIFORM_UINT = 8 + SHADER_UNIFORM_UIVEC2 = 9 + SHADER_UNIFORM_UIVEC3 = 10 + SHADER_UNIFORM_UIVEC4 = 11 + SHADER_UNIFORM_SAMPLER2D = 12 class ShaderAttributeDataType(IntEnum): SHADER_ATTRIB_FLOAT = 0 @@ -348,6 +353,12 @@ class GuiState(IntEnum): STATE_PRESSED = 2 STATE_DISABLED = 3 +class GuiPropertyElement(IntEnum): + BORDER = 0 + BASE = 1 + TEXT = 2 + OTHER = 3 + class GuiTextAlignment(IntEnum): TEXT_ALIGN_LEFT = 0 TEXT_ALIGN_CENTER = 1 @@ -716,4 +727,3 @@ class GuiIconName(IntEnum): ICON_253 = 253 ICON_254 = 254 ICON_255 = 255 - diff --git a/raylib/raylib/version.py b/raylib/raylib/version.py index fe59f63..2c91105 100644 --- a/raylib/raylib/version.py +++ b/raylib/raylib/version.py @@ -1 +1 @@ -__version__ = "5.5.0.8" \ No newline at end of file +__version__ = "6.0.0.0" diff --git a/raylib/setup.py b/raylib/setup.py index eb46389..c132a99 100644 --- a/raylib/setup.py +++ b/raylib/setup.py @@ -1,61 +1,59 @@ import glob import os import platform +import shutil import subprocess import sys from setuptools import setup from setuptools.command.build_py import build_py +from setuptools.command.bdist_wheel import bdist_wheel -try: - from wheel.bdist_wheel import bdist_wheel -except ImportError: - bdist_wheel = None +PKG_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(PKG_DIR, "raylib")) +from _backend import BACKENDS, BACKEND_CFFI_MODULES, detect_backend, is_dual_backend_host # noqa: E402 class BuildRaylib(build_py): - """Run build.sh to compile the C library and CFFI extension before collecting package data.""" + """Run build.sh to compile raylib artifacts 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) - - # Build CFFI extension so it's included in the wheel - cffi_so = glob.glob(os.path.join(pkg_dir, "raylib", "_raylib_cffi*")) - if not cffi_so: - build_cffi = os.path.join(pkg_dir, "raylib", "build.py") - if os.path.isfile(build_cffi): - subprocess.check_call([sys.executable, build_cffi], cwd=pkg_dir) - - super().run() + @staticmethod + def _build_cffi(backend): + env = os.environ.copy() + env["RAYLIB_BACKEND"] = backend + env["RAYLIB_CFFI_MODULE"] = f"raylib.{BACKEND_CFFI_MODULES[backend]}" + subprocess.check_call([sys.executable, "build_cffi.py"], cwd=PKG_DIR, env=env) + def run(self): + subprocess.check_call(["bash", "build.sh"], cwd=PKG_DIR) -cmdclass = {"build_py": BuildRaylib} - -if bdist_wheel is not None: + # Always regenerate CFFI extensions: the cached raylib source pin may have changed. + for old_cffi in glob.glob(os.path.join(PKG_DIR, "raylib", "_raylib_cffi*")): + os.remove(old_cffi) + backends = BACKENDS if is_dual_backend_host() else (detect_backend(),) + for backend in backends: + self._build_cffi(backend) - class PlatformWheel(bdist_wheel): - """Produce a platform-specific wheel (contains native .a library).""" + # Wipe build_lib package dirs so deleted/renamed files (e.g. old _raylib_cffi* + # backends, removed modules) don't get packaged from the cached build/ tree. + for package in ("raylib", "pyray"): + shutil.rmtree(os.path.join(self.build_lib, package), ignore_errors=True) - def finalize_options(self): - super().finalize_options() - self.root_is_pure = False + super().run() - 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}" +class PlatformWheel(bdist_wheel): + """Produce a platform-specific wheel with native raylib artifacts.""" - return "py3", "none", plat + def finalize_options(self): + super().finalize_options() + self.root_is_pure = False - cmdclass["bdist_wheel"] = PlatformWheel + def get_tag(self): + _, _, plat_tag = super().get_tag() + if platform.system() == "Linux": + plat_tag = f"linux_{platform.machine()}" + return "py3", "none", plat_tag -setup(cmdclass=cmdclass) +setup(cmdclass={"build_py": BuildRaylib, "bdist_wheel": PlatformWheel}) diff --git a/raylib/validate_bindings.py b/raylib/validate_bindings.py new file mode 100644 index 0000000..d062713 --- /dev/null +++ b/raylib/validate_bindings.py @@ -0,0 +1,109 @@ +import ast +import os +import re + + +def _read_python_assignments(path): + assignments = {} + with open(path, "r") as f: + tree = ast.parse(f.read()) + for node in tree.body: + if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): + try: + assignments[node.targets[0].id] = ast.literal_eval(node.value) + except ValueError: + pass + elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.value is not None: + try: + assignments[node.target.id] = ast.literal_eval(node.value) + except ValueError: + pass + return assignments + + +def _read_python_enums(path): + enums = {} + with open(path, "r") as f: + tree = ast.parse(f.read()) + for node in tree.body: + if not isinstance(node, ast.ClassDef): + continue + values = {} + for stmt in node.body: + if isinstance(stmt, ast.Assign) and len(stmt.targets) == 1 and isinstance(stmt.targets[0], ast.Name): + values[stmt.targets[0].id] = ast.literal_eval(stmt.value) + enums[node.name] = values + return enums + + +def _strip_c_comments(text): + text = re.sub(r'/\*.*?\*/', '', text, flags=re.S) + return re.sub(r'//.*', '', text) + + +def _eval_c_enum_expr(expr, symbols): + if not re.fullmatch(r'[A-Za-z0-9_ xXa-fA-F|&~!<>()+\-*/]+', expr): + raise ValueError(expr) + return int(eval(expr.replace("!", " not "), {"__builtins__": {}}, symbols)) + + +def _read_c_enums(header_texts): + symbols = {} + enums = {} + for text in header_texts: + for match in re.finditer(r'typedef\s+enum(?:\s+\w+)?\s*\{(.*?)\}\s*(\w+)\s*;', text, re.S): + body, enum_name = match.groups() + value = -1 + values = {} + for raw_item in body.split(','): + item = raw_item.strip() + if not item: + continue + if '=' in item: + name, expr = [part.strip() for part in item.split('=', 1)] + value = _eval_c_enum_expr(expr, symbols) + else: + name = item + value += 1 + if re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]*', name): + values[name] = value + symbols[name] = value + enums[enum_name] = values + return enums + + +def validate_static_bindings(package_dir, include_dir): + headers = {} + for name in ("raylib.h", "rlgl.h", "raygui.h"): + with open(os.path.join(include_dir, name), "r") as f: + headers[name] = _strip_c_comments(f.read()) + + defines = _read_python_assignments(os.path.join(package_dir, "defines.py")) + for header_name, names in ( + ("raylib.h", ("RAYLIB_VERSION_MAJOR", "RAYLIB_VERSION_MINOR", "RAYLIB_VERSION_PATCH", "RAYLIB_VERSION")), + ("rlgl.h", ("RLGL_VERSION",)), + ): + text = headers[header_name] + for name in names: + match = re.search(rf"#define\s+{name}\s+(.+)", text) + if not match: + continue + expected = match.group(1).strip().strip('"') + actual = str(defines[name]) + if actual != expected: + raise ValueError(f"{name} in defines.py is {actual}, expected {expected}") + + py_enums = _read_python_enums(os.path.join(package_dir, "enums.py")) + c_enums = _read_c_enums((headers["raylib.h"], headers["raygui.h"])) + missing_classes = sorted(set(c_enums) - set(py_enums) - {"bool"}) + extra_classes = sorted(set(py_enums) - set(c_enums)) + if missing_classes or extra_classes: + raise ValueError(f"enums.py drift: missing={missing_classes}, extra={extra_classes}") + for enum_name in sorted(set(c_enums) & set(py_enums)): + if py_enums[enum_name] != c_enums[enum_name]: + raise ValueError(f"enums.py drift in {enum_name}") + + +if __name__ == "__main__": + root = os.path.dirname(os.path.abspath(__file__)) + validate_static_bindings(os.path.join(root, "raylib"), os.path.join(root, "raylib", "install", "include")) diff --git a/setup.sh b/setup.sh index ab72c06..bcd6c87 100755 --- a/setup.sh +++ b/setup.sh @@ -17,10 +17,16 @@ run_as_root() { if [ "$(uname)" = "Darwin" ]; then brew install nasm pkg-config ccache autoconf automake libtool elif command -v dnf &>/dev/null; then - dnf install -y nasm cmake gcc-c++ pkgconfig git perl-IPC-Cmd ccache autoconf automake libtool + dnf install -y \ + nasm cmake gcc-c++ pkgconfig git perl-IPC-Cmd ccache autoconf automake libtool \ + libX11-devel libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel \ + mesa-libGL-devel libdrm-devel mesa-libgbm-devel mesa-libEGL-devel mesa-libGLES-devel elif command -v apt-get &>/dev/null; then run_as_root apt-get update - run_as_root apt-get install -y nasm cmake g++ pkg-config curl ccache autoconf automake libtool + run_as_root apt-get install -y \ + nasm cmake g++ pkg-config curl ccache autoconf automake libtool \ + libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libgl-dev \ + libdrm-dev libgbm-dev libgles2-mesa-dev libegl1-mesa-dev fi if ! command -v uv &>/dev/null; then