#!/bin/bash set -e CC="${CC:-gcc}" CFLAGS="-std=c23 -Wall -Wextra" LIBS="-lm" MODE="debug" BUILDDIR=".build" BINDIR="bin" if command -v mold >/dev/null 2>&1; then LINKER_FLAGS="-fuse-ld=mold" else LINKER_FLAGS="" fi case "$(uname)" in Linux) LINKER_FLAGS="$LINKER_FLAGS -rdynamic" ;; Darwin) ;; MINGW*|MSYS*|CYGWIN*) LINKER_FLAGS="$LINKER_FLAGS -Wl,--export-all-symbols" ;; *) LINKER_FLAGS="$LINKER_FLAGS -rdynamic" ;; esac RED='\033[0;31m' GREEN='\033[0;32m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' if [[ "$(uname)" == "Linux" ]]; then CFLAGS="$CFLAGS -D_GNU_SOURCE" fi print_info() { echo -e "${BLUE}${BOLD}info:${NC} $1" } print_success() { echo -e "${GREEN}${BOLD}✓${NC} $1" } print_error() { echo -e "${RED}${BOLD}error:${NC} $1" >&2 } detect_simd() { local simd_flags="" case "$(uname -m)" in x86_64|amd64) if $CC -mavx2 -c -x c /dev/null -o /dev/null 2>/dev/null; then simd_flags="-mavx2 -msse2" elif $CC -msse2 -c -x c /dev/null -o /dev/null 2>/dev/null; then simd_flags="-msse2" fi ;; arm64|aarch64) if $CC -march=armv8-a+simd -c -x c /dev/null -o /dev/null 2>/dev/null; then simd_flags="-march=armv8-a+simd" fi ;; esac echo "$simd_flags" } print_usage() { echo -e "${BOLD}pxl8${NC} - framework build tool" echo echo -e "${BOLD}USAGE:${NC}" echo " ./pxl8.sh [options]" echo echo -e "${BOLD}COMMANDS:${NC}" echo " build Build pxl8" echo " run Build and run pxl8 [script.fnl|script.lua]" echo " clean Remove build artifacts" echo " update Download/update all dependencies" echo " vendor Fetch source for dependencies (ex. sdl3)" echo " help Show this help message" echo echo -e "${BOLD}OPTIONS:${NC}" echo " --all Remove all artifacts including dependencies when cleaning" echo " --release Build/run in release mode" } COMMAND="$1" shift || true for arg in "$@"; do case $arg in --release) MODE="release" ;; esac done SIMD_FLAGS=$(detect_simd) if [ "$MODE" = "release" ]; then CFLAGS="$CFLAGS -O3 -march=native -mtune=native -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables $SIMD_FLAGS" BUILDDIR="$BUILDDIR/release" BINDIR="$BINDIR/release" else CFLAGS="$CFLAGS -g -O1 -DDEBUG $SIMD_FLAGS" BUILDDIR="$BUILDDIR/debug" BINDIR="$BINDIR/debug" fi setup_sdl3() { if [[ -d "lib/SDL/build" ]]; then SDL3_CFLAGS="-Ilib/SDL/include" SDL3_LIBS="-Llib/SDL/build -lSDL3" SDL3_RPATH="-Wl,-rpath,$(pwd)/lib/SDL/build" print_info "Using vendored SDL3" else SDL3_CFLAGS=$(pkg-config --cflags sdl3 2>/dev/null || echo "") SDL3_LIBS=$(pkg-config --libs sdl3 2>/dev/null || echo "") SDL3_RPATH="" if [[ -z "$SDL3_LIBS" ]]; then case "$(uname)" in Darwin) SDL3_CFLAGS="-I/usr/local/include/SDL3" SDL3_LIBS="-L/usr/local/lib -lSDL3" ;; Linux) SDL3_CFLAGS="-I/usr/include/SDL3" SDL3_LIBS="-lSDL3" ;; MINGW*|MSYS*|CYGWIN*) SDL3_CFLAGS="-I/mingw64/include/SDL3" SDL3_LIBS="-lSDL3" ;; esac fi print_info "Using system SDL3" fi CFLAGS="$CFLAGS $SDL3_CFLAGS" LIBS="$LIBS $SDL3_LIBS $SDL3_RPATH" } update_linenoise() { print_info "Fetching linenoise" mkdir -p lib/linenoise if curl -s --max-time 5 -o lib/linenoise/linenoise.c https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.c 2>/dev/null && \ curl -s --max-time 5 -o lib/linenoise/linenoise.h https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.h 2>/dev/null; then print_success "Updated linenoise" else print_error "Failed to download linenoise" return 1 fi } update_fennel() { print_info "Fetching Fennel" mkdir -p lib/fennel local version="1.5.3" if curl -s --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then print_success "Updated Fennel (${version})" else print_error "Downloaded file is empty or missing" return 1 fi else print_error "Failed to download Fennel" return 1 fi } update_luajit() { print_info "Fetching LuaJIT" local version="2.1" local branch="v${version}" if [[ -d "lib/luajit/.git" ]]; then cd lib/luajit && git pull --quiet origin ${branch} cd - > /dev/null else rm -rf lib/luajit git clone --quiet --branch ${branch} https://github.com/LuaJIT/LuaJIT.git lib/luajit fi print_success "Updated LuaJIT (${version})" } build_luajit() { if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then print_info "Building LuaJIT" cd lib/luajit if [[ "$(uname)" == "Darwin" ]]; then export MACOSX_DEPLOYMENT_TARGET="10.11" fi make clean >/dev/null 2>&1 || true make -j$(nproc 2>/dev/null || echo 4) > /dev/null 2>&1 cd - > /dev/null print_success "Built LuaJIT" fi } update_microui() { print_info "Fetching microui" mkdir -p lib/microui/src if curl -s --max-time 5 -o lib/microui/src/microui.c https://raw.githubusercontent.com/rxi/microui/master/src/microui.c 2>/dev/null && \ curl -s --max-time 5 -o lib/microui/src/microui.h https://raw.githubusercontent.com/rxi/microui/master/src/microui.h 2>/dev/null; then print_success "Updated microui" else print_error "Failed to download microui" return 1 fi } update_miniz() { print_info "Fetching miniz" mkdir -p lib/miniz local version="3.0.2" if curl -sL --max-time 10 -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/${version}/miniz-${version}.zip" 2>/dev/null; then unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/ 2>/dev/null rm -f /tmp/miniz.zip if [[ -f "lib/miniz/miniz.c" ]] && [[ -f "lib/miniz/miniz.h" ]]; then print_success "Updated miniz (${version})" else print_error "Failed to extract miniz files" return 1 fi else print_error "Failed to download miniz" return 1 fi } vendor_sdl() { print_info "Fetching SDL3" if [[ -d "lib/SDL/.git" ]]; then cd lib/SDL && git pull --quiet origin main cd - > /dev/null else rm -rf lib/SDL git clone --quiet https://github.com/libsdl-org/SDL.git lib/SDL fi print_success "Updated SDL3" } build_sdl() { if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then print_info "Building SDL3" mkdir -p lib/SDL/build cd lib/SDL/build cmake .. -DCMAKE_BUILD_TYPE=Release > /dev/null 2>&1 cmake --build . --parallel $(nproc 2>/dev/null || echo 4) > /dev/null 2>&1 cd - > /dev/null print_success "Built SDL3" fi } case "$COMMAND" in build) mkdir -p "$BUILDDIR" mkdir -p "$BINDIR" if [[ ! -f "lib/microui/src/microui.c" ]] || \ [[ ! -d "lib/luajit" ]] || \ [[ ! -f "lib/linenoise/linenoise.c" ]] || \ [[ ! -f "lib/miniz/miniz.c" ]] || \ [[ ! -f "lib/fennel/fennel.lua" ]]; then print_info "Missing dependencies, fetching..." update_linenoise update_fennel update_luajit update_microui update_miniz fi if [[ ! -f "bin/.gitignore" ]]; then echo "*" > "bin/.gitignore" fi if [[ ! -f ".build/.gitignore" ]]; then echo "*" > ".build/.gitignore" fi if [[ ! -f "lib/.gitignore" ]]; then echo "*" > "lib/.gitignore" fi if [[ -d "lib/luajit" ]]; then build_luajit fi if [[ -d "lib/SDL" ]]; then build_sdl fi setup_sdl3 print_info "Building pxl8 ($MODE mode)" if [[ -n "$SIMD_FLAGS" ]]; then case "$(uname -m)" in x86_64|amd64) if [[ "$SIMD_FLAGS" == *"mavx2"* ]]; then print_info "SIMD: AVX2 + SSE2 enabled" elif [[ "$SIMD_FLAGS" == *"msse2"* ]]; then print_info "SIMD: SSE2 enabled" fi ;; arm64|aarch64) print_info "SIMD: ARM NEON enabled" ;; esac else print_info "SIMD: Scalar fallback" fi INCLUDES="-Isrc -Ilib -Ilib/microui/src -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz" COMPILE_FLAGS="$CFLAGS $INCLUDES" EXECUTABLE="$BINDIR/pxl8" LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c" SRC_SOURCE_FILES=" src/pxl8.c src/pxl8_ase.c src/pxl8_blit.c src/pxl8_cart.c src/pxl8_font.c src/pxl8_gfx.c src/pxl8_io.c src/pxl8_lua.c src/pxl8_tilemap.c src/pxl8_tilesheet.c src/pxl8_vfx.c " LUAJIT_LIB="lib/luajit/src/libluajit.a" OBJECT_DIR="$BUILDDIR/obj" mkdir -p "$OBJECT_DIR" OBJECTS="" NEED_LINK=false SOURCES_COMPILED="" for src_file in $LIB_SOURCE_FILES; do obj_name=$(basename "$src_file" .c).o obj_file="$OBJECT_DIR/$obj_name" OBJECTS="$OBJECTS $obj_file" if [[ "$src_file" -nt "$obj_file" ]]; then NEED_LINK=true print_info "Compiling: $src_file" if ! $CC -c $COMPILE_FLAGS "$src_file" -o "$obj_file"; then print_error "Compilation failed for $src_file" exit 1 fi SOURCES_COMPILED="yes" fi done for src_file in $SRC_SOURCE_FILES; do obj_name=$(basename "$src_file" .c).o obj_file="$OBJECT_DIR/$obj_name" OBJECTS="$OBJECTS $obj_file" if [[ "$src_file" -nt "$obj_file" ]] || \ [[ "src/pxl8_types.h" -nt "$obj_file" ]] || \ [[ "src/pxl8_macros.h" -nt "$obj_file" ]]; then NEED_LINK=true print_info "Compiling: $src_file" if ! $CC -c $COMPILE_FLAGS "$src_file" -o "$obj_file"; then print_error "Compilation failed for $src_file" exit 1 fi SOURCES_COMPILED="yes" fi done if [[ "$LUAJIT_LIB" -nt "$EXECUTABLE" ]] || [[ "$NEED_LINK" == true ]]; then print_info "Linking executable" if ! $CC $LINKER_FLAGS $OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then print_error "Linking failed" exit 1 fi elif [[ -z "$SOURCES_COMPILED" ]]; then print_info "All files are up to date, skipping build" fi print_success "Build complete" print_info "Binary: $EXECUTABLE" ;; run) "$0" build "$@" || exit 1 SCRIPT="" for arg in "$@"; do if [[ "$arg" != "--release" ]]; then SCRIPT="$arg" break fi done if [[ -z "$SCRIPT" ]]; then SCRIPT="src/fnl/demo.fnl" fi "$BINDIR/pxl8" "$SCRIPT" ;; clean) if [[ "$1" == "--all" ]] || [[ "$1" == "--deps" ]]; then print_info "Removing all build artifacts and dependencies" rm -rf "$BUILDDIR" "$BINDIR" lib else print_info "Removing build artifacts" rm -rf "$BUILDDIR" "$BINDIR" fi print_success "Cleaned" ;; update) update_linenoise update_fennel update_luajit update_microui update_miniz print_success "All dependencies updated" ;; vendor) vendor_sdl ;; help|--help|-h|"") print_usage ;; *) print_error "Unknown command: $COMMAND" print_usage exit 1 ;; esac