diff --git a/build.sh b/build.sh index d5c6362..b341b57 100755 --- a/build.sh +++ b/build.sh @@ -41,22 +41,23 @@ EXCLUDE_CORES="" OPTION_SPECIFIED=0 UPDATE=0 CLEAN=0 +CORE_CLEAN_FLAG=0 # If argument '-p' or '--purge' provided first, set PURGE=1 if [ "$#" -gt 0 ]; then case "$1" in -p|--purge) - PURGE=1 - shift - ;; + PURGE=1 + shift + ;; -f|--force) - FORCE=1 - shift - ;; + FORCE=1 + shift + ;; -l|--latest) - LATEST=1 - shift - ;; + LATEST=1 + shift + ;; esac fi @@ -93,8 +94,10 @@ while [ "$#" -gt 0 ]; do break ;; -u | --update) - UPDATE=1 + [ "$OPTION_SPECIFIED" -ne 0 ] && USAGE + OPTION_SPECIFIED=1 shift + UPDATE=1 ;; -f | --force) FORCE=1 @@ -130,9 +133,60 @@ RETRO_DIR="$BASE_DIR/core" BUILD_DIR="$BASE_DIR/build" PATCH_DIR="$BASE_DIR/patch" +# Create a per-run temp directory for ALL scratch files (always cleaned up) +TMPDIR_BASE=${TMPDIR:-/tmp} +RUN_TMPDIR="$(mktemp -d "$TMPDIR_BASE/murcb.XXXXXX")" || { + printf "Error: mktemp failed\n" >&2 + exit 1 +} + +PV_PID="" + +CLEANUP() { + # Stop any progress loop + if [ -n "$PV_PID" ]; then + kill "$PV_PID" 2>/dev/null || true + PV_PID="" + fi + + # Remove all scratch files + if [ -n "$RUN_TMPDIR" ] && [ -d "$RUN_TMPDIR" ]; then + rm -rf -- "$RUN_TMPDIR" 2>/dev/null || true + fi + + # Best-effort return to base + cd "$BASE_DIR" 2>/dev/null || true +} + +# Always cleanup, including Ctrl+C +trap 'CLEANUP; exit 1' INT TERM HUP +trap 'CLEANUP' EXIT + # POSIX safe CPU count NPROC=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1) +# Default behavior: nproc-1 (min 1) +# Override: export MAKE_CORES= (must be positive int) +if [ -z "${MAKE_CORES+x}" ] || [ -z "$MAKE_CORES" ]; then + if [ "$NPROC" -gt 1 ]; then + MAKE_CORES=$((NPROC - 1)) + else + MAKE_CORES=1 + fi +fi + +# Validate it's a positive integer +case "$MAKE_CORES" in + *[!0-9]*|"") + printf "Error: MAKE_CORES must be a positive integer (got '%s')\n" "$MAKE_CORES" >&2 + exit 1 + ;; +esac +if [ "$MAKE_CORES" -lt 1 ]; then + printf "Error: MAKE_CORES must be >= 1 (got '%s')\n" "$MAKE_CORES" >&2 + exit 1 +fi + # Safe directory removal helper SAFE_RM_DIR() { _TARGET="$1" @@ -198,40 +252,38 @@ UPDATE_ZIP() { # Detect proper aarch64 objcopy command. if command -v aarch64-linux-gnu-objcopy >/dev/null 2>&1; then - OBJCOPY=aarch64-linux-gnu-objcopy + OBJCOPY=aarch64-linux-gnu-objcopy elif command -v aarch64-linux-objcopy >/dev/null 2>&1; then - OBJCOPY=aarch64-linux-objcopy + OBJCOPY=aarch64-linux-objcopy else - printf "Error: Neither aarch64-linux-gnu-objcopy nor aarch64-linux-objcopy found\n" >&2 - exit 1 + printf "Error: Neither aarch64-linux-gnu-objcopy nor aarch64-linux-objcopy found\n" >&2 + exit 1 fi # Detect proper aarch64 strip command. if command -v aarch64-linux-gnu-strip >/dev/null 2>&1; then - STRIP=aarch64-linux-gnu-strip + STRIP=aarch64-linux-gnu-strip elif command -v aarch64-linux-strip >/dev/null 2>&1; then - STRIP=aarch64-linux-strip + STRIP=aarch64-linux-strip elif command -v strip >/dev/null 2>&1; then - STRIP=strip + STRIP=strip else - printf "Error: No suitable strip command found\n" >&2 - exit 1 + printf "Error: No suitable strip command found\n" >&2 + exit 1 fi # Check for other required commands -for CMD in file git jq make patch pv readelf zip; do - if ! command -v "$CMD" >/dev/null 2>&1; then - printf "Error: Missing required command '%s'\n" "$CMD" >&2 - exit 1 - fi +for CMD in file git jq make patch pv readelf zip unzip cksum sort sed awk grep head cut tr find; do + if ! command -v "$CMD" >/dev/null 2>&1; then + printf "Error: Missing required command '%s'\n" "$CMD" >&2 + exit 1 + fi done # Create required directories mkdir -p "$RETRO_DIR" mkdir -p "$BUILD_DIR" -trap 'printf "\nAn error occurred. Returning to base directory.\n"; cd "$BASE_DIR"; exit 1' INT TERM - RETURN_TO_BASE() { cd "$BASE_DIR" || { printf "Failed to return to base directory\n" >&2 @@ -240,12 +292,33 @@ RETURN_TO_BASE() { } RUN_COMMANDS() { - printf "\nRunning '%s' commands\n" "$1" - CMD=$(printf '%s\n' "$2" | jq -r '.[]') - printf "Running:\n%s\n" "$CMD" - sh <"$_CMD_FILE" || return 1 + + if ! printf '%s\n' "$_JSON" | jq -r '.[]' >"$_CMD_FILE" 2>/dev/null; then + printf "Failed to parse '%s' commands JSON\n" "$_PHASE" >&2 + return 1 + fi + + # Echo what will run + while IFS= read -r _CMD; do + [ -n "$_CMD" ] || continue + printf "Running:\n%s\n" "$_CMD" + done <"$_CMD_FILE" + + # Run the whole phase in a subshell: + # - commands share state with each other (cd works inside the phase) + # - nothing leaks into the main build (so -C paths don't become dir/dir) + ( + # fail fast within the phase + set -e + . "$_CMD_FILE" + ) } APPLY_PATCHES() { @@ -281,7 +354,7 @@ fi # Load the cache file CACHE_FILE="$BASE_DIR/data/cache.json" if [ ! -f "$CACHE_FILE" ]; then - echo "{}" > "$CACHE_FILE" + echo "{}" > "$CACHE_FILE" fi for NAME in $CORES; do @@ -295,16 +368,36 @@ for NAME in $CORES; do fi # Required keys - DIR=$(echo "$MODULE" | jq -r '.directory') + DIR=$(echo "$MODULE" | jq -r '.directory') OUTPUT_LIST=$(echo "$MODULE" | jq -r '.output | if type=="string" then . else join(" ") end') SOURCE=$(echo "$MODULE" | jq -r '.source') SYMBOLS=$(echo "$MODULE" | jq -r '.symbols') # Make keys MAKE_FILE=$(echo "$MODULE" | jq -r '.make.file') - MAKE_ARGS=$(echo "$MODULE" | jq -r '.make.args') + + # Accept .make.args as either: + # - array: ["A=B","C=D"] + # - string: "A=B C=D" + # - null/missing: empty + MAKE_ARGS_TYPE=$(echo "$MODULE" | jq -r '(.make.args // empty) | type' 2>/dev/null || echo "") + MAKE_ARGS_STR="" + MAKE_ARGS_FILE="" + + if [ "$MAKE_ARGS_TYPE" = "array" ]; then + # IMPORTANT: temp file goes in RUN_TMPDIR, never in /cores + MAKE_ARGS_FILE="$RUN_TMPDIR/.make_args.${NAME}.$$" + : >"$MAKE_ARGS_FILE" + echo "$MODULE" | jq -r '.make.args[]' >"$MAKE_ARGS_FILE" 2>/dev/null || : + MAKE_ARGS_STR=$(tr '\n' ' ' <"$MAKE_ARGS_FILE" | sed 's/[[:space:]][[:space:]]*/ /g; s/[[:space:]]*$//') + elif [ "$MAKE_ARGS_TYPE" = "string" ]; then + MAKE_ARGS_STR=$(echo "$MODULE" | jq -r '.make.args' 2>/dev/null) + else + MAKE_ARGS_STR="" + fi + MAKE_TARGET=$(echo "$MODULE" | jq -r '.make.target') - MAKE_ARCH='-mtune=cortex-a53 -mcpu=cortex-a53+crc+crypto' + MAKE_ARCH='-march=armv8-a+crc+crypto -mtune=cortex-a53' # Verify required keys if [ -z "$DIR" ] || [ -z "$OUTPUT_LIST" ] || [ -z "$SOURCE" ] || [ -z "$MAKE_FILE" ] || [ -z "$SYMBOLS" ]; then @@ -312,9 +405,9 @@ for NAME in $CORES; do continue fi - BRANCH=$(echo "$MODULE" | jq -r '.branch // ""') - PRE_MAKE=$(echo "$MODULE" | jq -r '.commands["pre-make"] // []') - POST_MAKE=$(echo "$MODULE" | jq -r '.commands["post-make"] // []') + BRANCH=$(echo "$MODULE" | jq -r '.branch // ""') + PRE_MAKE=$(echo "$MODULE" | jq -r '.commands["pre-make"] // []') + POST_MAKE=$(echo "$MODULE" | jq -r '.commands["post-make"] // []') CORE_PURGE_FLAG=$(echo "$MODULE" | jq -r '.purge // 0') case "$CORE_PURGE_FLAG" in 1) CORE_PURGE_FLAG=1 ;; @@ -372,20 +465,19 @@ for NAME in $CORES; do SAFE_RM_DIR "$BUILD_DIR/$CACHED_DIR" fi - # If PURGE is set, delete the repo folder *now* (even if we skip later) - # This implements: "purge is to delete the repo folder" + # If PURGE is set, delete the repo folder now if [ "$PURGE" -eq 1 ] || [ "$CORE_PURGE_FLAG" -eq 1 ]; then printf "Purging core repo directory: %s\n" "$CORE_DIR" rm -rf "$CORE_DIR" fi - # Skip when up to date (purge does not block skipping; we won't re-clone) + # Skip when up to date if [ "$FORCE" -eq 0 ] && \ [ "$CACHED_HASH" = "$REMOTE_HASH" ] && [ -f "$RETRO_DIR/$ZIP_NAME" ]; then - printf "Core '%s' is up to date (hash: %s). Skipping build.\n" "$NAME" "$REMOTE_HASH" + printf "Core '%s' is up to date (hash: %s). Skipping build.\n" "$NAME" "$REMOTE_HASH" jq --arg name "$NAME" --arg hash "$REMOTE_HASH" --arg dir "$DIR" \ '(.[$name] = {"hash":$hash,"dir":$dir})' "$CACHE_FILE" >"$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" - continue + continue fi BEEN_CLONED=0 @@ -393,11 +485,11 @@ for NAME in $CORES; do printf "Core '%s' not found\n\n" "$DIR" # Clone if [ "$LATEST" -eq 1 ]; then - GC_CMD="git clone --progress --quiet --recurse-submodules -j$NPROC $SOURCE $CORE_DIR" + GC_CMD="git clone --progress --quiet --recurse-submodules -j$MAKE_CORES $SOURCE $CORE_DIR" elif [ -n "$BRANCH" ] && echo "$BRANCH" | grep -qE '^[0-9a-f]{7,40}$'; then - GC_CMD="git clone --progress --quiet --recurse-submodules -j$NPROC $SOURCE $CORE_DIR" + GC_CMD="git clone --progress --quiet --recurse-submodules -j$MAKE_CORES $SOURCE $CORE_DIR" else - GC_CMD="git clone --progress --quiet --recurse-submodules -j$NPROC" + GC_CMD="git clone --progress --quiet --recurse-submodules -j$MAKE_CORES" [ -n "$BRANCH" ] && GC_CMD="$GC_CMD -b $BRANCH" GC_CMD="$GC_CMD $SOURCE $CORE_DIR" fi @@ -417,7 +509,7 @@ for NAME in $CORES; do continue } - cd - > /dev/null + cd - >/dev/null 2>&1 printf "\n" BEEN_CLONED=1 fi @@ -432,7 +524,7 @@ for NAME in $CORES; do continue } - if [ $BEEN_CLONED -eq 0 ]; then + if [ "$BEEN_CLONED" -eq 0 ]; then if [ "$LATEST" -eq 1 ]; then printf "Updating '%s' to remote HEAD (latest)\n" "$NAME" git fetch --quiet origin || { printf " fetch failed for '%s'\n" "$NAME" >&2; RETURN_TO_BASE; continue; } @@ -477,7 +569,7 @@ for NAME in $CORES; do printf "Make Structure:" printf "\n\tFILE:\t%s" "$MAKE_FILE" printf "\n\tARCH:\t%s" "ARM64_A53" - printf "\n\tARGS:\t%s" "$MAKE_ARGS" + printf "\n\tARGS:\t%s" "$MAKE_ARGS_STR" printf "\n\tTARGET: %s\n" "$MAKE_TARGET" printf "\nBuilding '%s' ...\n" "$NAME" @@ -486,23 +578,89 @@ for NAME in $CORES; do printf '.' sleep 1 done) | pv -q -L 10 -N "Building $NAME" & - PV_PID=$! - trap 'kill $PV_PID 2>/dev/null' EXIT LOGFILE="$(dirname "$0")/build.log" START_TS=$(date +%s) - # Run make; capture everything into build.log - kill $PV_PID 2>/dev/null - if CFLAGS="$MAKE_ARCH" CXXFLAGS="$MAKE_ARCH" make -j"$NPROC" -f "$MAKE_FILE" $MAKE_ARGS $MAKE_TARGET >>"$LOGFILE" 2>&1; then + # Build make argv safely (no eval) + set -- make -j"$MAKE_CORES" -f "$MAKE_FILE" + + TRANSFORM_MAKE_ARG() { + _a=$1 + + case $_a in + OVERRIDE_CC=*) + _v=${_a#OVERRIDE_CC=} + case " $_v " in + *" -march="*|*" -mcpu="*|*" -mtune="*) printf '%s\n' "OVERRIDE_CC=$_v" ;; + *) printf '%s\n' "OVERRIDE_CC=$_v $MAKE_ARCH" ;; + esac + return 0 + ;; + OVERRIDE_CXX=*) + _v=${_a#OVERRIDE_CXX=} + case " $_v " in + *" -march="*|*" -mcpu="*|*" -mtune="*) printf '%s\n' "OVERRIDE_CXX=$_v" ;; + *) printf '%s\n' "OVERRIDE_CXX=$_v $MAKE_ARCH" ;; + esac + return 0 + ;; + OVERRIDE_LD=*) + _v=${_a#OVERRIDE_LD=} + case " $_v " in + *" -march="*|*" -mcpu="*|*" -mtune="*) printf '%s\n' "OVERRIDE_LD=$_v" ;; + *) printf '%s\n' "OVERRIDE_LD=$_v $MAKE_ARCH" ;; + esac + return 0 + ;; + esac + + printf '%s\n' "$_a" + } + + # Append args from JSON + if [ "$MAKE_ARGS_TYPE" = "array" ] && [ -n "$MAKE_ARGS_FILE" ] && [ -s "$MAKE_ARGS_FILE" ]; then + while IFS= read -r _a; do + [ -n "$_a" ] || continue + _t=$(TRANSFORM_MAKE_ARG "$_a") + [ -n "$_t" ] && set -- "$@" "$_t" + done <"$MAKE_ARGS_FILE" + elif [ -n "$MAKE_ARGS_STR" ]; then + # shellcheck disable=SC2086 + for _a in $MAKE_ARGS_STR; do + _t=$(TRANSFORM_MAKE_ARG "$_a") + [ -n "$_t" ] && set -- "$@" "$_t" + done + fi + + # Add explicit make target if present + if [ -n "$MAKE_TARGET" ] && [ "$MAKE_TARGET" != "null" ]; then + set -- "$@" "$MAKE_TARGET" + fi + + # Debug: log actual argv we will run + printf "EXEC:" >>"$LOGFILE" + for __x in "$@"; do + printf " [%s]" "$__x" >>"$LOGFILE" + done + printf "\n" >>"$LOGFILE" + + # Run make; DO NOT export CFLAGS/CXXFLAGS globally (breaks host tools) + if "$@" >>"$LOGFILE" 2>&1; then + kill "$PV_PID" 2>/dev/null || true + PV_PID="" + printf "\nBuild succeeded: %s\n" "$NAME" jq --arg name "$NAME" --arg hash "$REMOTE_HASH" --arg dir "$DIR" \ '(.[$name] = {"hash":$hash,"dir":$dir})' "$CACHE_FILE" >"$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" else - printf "\nBuild FAILED: %s - see %s\n" "$NAME" "$LOGFILE" >&2 - RETURN_TO_BASE - continue + kill "$PV_PID" 2>/dev/null || true + PV_PID="" + + printf "\nBuild FAILED: %s - see %s\n" "$NAME" "$LOGFILE" >&2 + RETURN_TO_BASE + continue fi END_TS=$(date +%s) @@ -565,7 +723,6 @@ for NAME in $CORES; do if [ -n "$SOFILE" ]; then ZIP_NAME="$(basename "$SOFILE").zip" else - # fallback to original behavior set -- $OUTPUTS if [ "$#" -eq 1 ]; then ZIP_NAME="$(basename "$1").zip" @@ -612,15 +769,23 @@ for NAME in $CORES; do sort -k3 .index-extended -o .index-extended sort .index -o .index - # After build: if PURGE (global or per-core) was requested, delete the repo folder; otherwise try a clean + # After packaging: purge repo if requested, otherwise try cleaning build artifacts if [ "$PURGE" -eq 1 ] || [ "$CORE_PURGE_FLAG" -eq 1 ]; then printf "\nPurging core repo directory: %s\n" "$CORE_DIR" - rm -rf "$CORE_DIR" + rm -rf -- "$CORE_DIR" else - printf "Cleaning build environment for '%s'\n" "$NAME" - make -C "$CORE_DIR" clean >/dev/null 2>&1 || { - printf "Clean failed or not required\n" >&2 - } + printf "\nCleaning build environment for '%s'\n" "$NAME" + ( + cd "$CORE_DIR" 2>/dev/null || exit 0 + + # Prefer cleaning using the same makefile we built with + make -f "$MAKE_FILE" clean -j"$MAKE_CORES" >/dev/null 2>&1 && exit 0 + + # Fallback: common clean target + make clean -j"$MAKE_CORES" >/dev/null 2>&1 && exit 0 + + exit 0 + ) || : fi RETURN_TO_BASE diff --git a/data/core.json b/data/core.json index 2cfaf14..79cbc1e 100755 --- a/data/core.json +++ b/data/core.json @@ -582,6 +582,27 @@ "mv build/flycast_libretro.so ." ] } + }, + "ffmpeg": { + "source": "https://github.com/libretro/RetroArch.git", + "branch": "a8d0ad0", + "directory": "libretro-ffmpeg", + "output": "ffmpeg_libretro.so", + "make": { + "file": "Makefile", + "args": "-C cores/libretro-ffmpeg HAVE_SSA=0", + "target": "" + }, + "symbols": 0, + "commands": { + "pre-make": [ + "./configure", + "cd cores/libretro-ffmpeg" + ], + "post-make": [ + "mv cores/libretro-ffmpeg/ffmpeg_libretro.so ." + ] + } }, "morpheuscast_xtreme": { "source": "https://github.com/KMFDManic/morpheuscast_xtreme", @@ -1022,6 +1043,38 @@ }, "symbols": 0 }, + "mame": { + "source": "https://github.com/libretro/mame", + "branch": "b207f9c", + "directory": "mame", + "output": "mame_libretro.so", + "make": { + "file": "Makefile.libretro", + "args": [ + "REGENIE=1", + "VERBOSE=1", + "OSD=retro", + "PYTHON_EXECUTABLE=python3", + "CONFIG=libretro", + "LIBRETRO_CPU=arm64", + "CROSS_BUILD=1", + "TARGETOS=linux", + "PLATFORM=arm64", + "platform=arm64", + "FORCE_DRC_C_BACKEND=1", + "OVERRIDE_CC=aarch64-linux-gnu-gcc", + "OVERRIDE_CXX=aarch64-linux-gnu-g++", + "OVERRIDE_LD=aarch64-linux-gnu-g++", + "STRIP=aarch64-linux-gnu-strip", + "UNAME=Linux raspberry 6.12.47+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.12.47-1+rpt1~bookworm (2025-09-16) aarch64 GNU/Linux", + "UNAME_M=aarch64", + "UNAME_P=aarch64", + "TARGET=mame" + ], + "target": "" + }, + "symbols": 0 + }, "mame2000": { "source": "https://github.com/libretro/mame2000-libretro", "branch": "2ec60f6", @@ -1321,6 +1374,19 @@ ] } }, + "mupen64plus": { + "source": "https://github.com/libretro/mupen64plus-libretro-nx.git", + "branch": "680e033", + "purge": 1, + "directory": "mupen64plus-libretro-nx", + "output": "mupen64plus_libretro.so", + "make": { + "file": "Makefile", + "args": "WITH_DYNAREC=aarch64", + "target": "" + }, + "symbols": 0 + }, "nekop2": { "source": "https://github.com/libretro/libretro-meowPC98", "branch": "dc905d4", @@ -1441,6 +1507,19 @@ }, "symbols": 0 }, + "parallel_n64": { + "source": "https://github.com/libretro/parallel-n64.git", + "branch": "1da824e", + "purge": 1, + "directory": "parallel-n64", + "output": "parallel_n64_libretro.so", + "make": { + "file": "Makefile", + "args": "WITH_DYNAREC=aarch64", + "target": "" + }, + "symbols": 0 + }, "pcsx_rearmed": { "source": "https://github.com/libretro/pcsx_rearmed", "branch": "228c14e", diff --git a/system/base/Media Player/assign/Media Player/ffmpeg.ini b/system/base/Media Player/assign/Media Player/ffmpeg.ini new file mode 100755 index 0000000..ffdac3e --- /dev/null +++ b/system/base/Media Player/assign/Media Player/ffmpeg.ini @@ -0,0 +1,11 @@ +[ffmpeg] +name=FFMpeg +core=ffmpeg_libretro.so + +[launch] +prep= +exec=/opt/muos/script/launch/lr-general.sh +done= + +[friendly] +ffmpeg