FROM ubuntu:22.04

LABEL org.opencontainers.image.description="Dusk Engine — Sega Saturn build environment (sh2eb-elf cross-compiler + Yaul SDK)"

ENV DEBIAN_FRONTEND=noninteractive
ENV YAUL_INSTALL_ROOT=/opt/yaul

# All variables required by Yaul's env.mk
ENV YAUL_ARCH_SH_PREFIX=sh2eb-elf
ENV YAUL_PROG_SH_PREFIX=sh2eb-elf
ENV YAUL_ARCH_M68K_PREFIX=m68keb-elf
ENV YAUL_BUILD_ROOT=/tmp/yaul-build
ENV YAUL_BUILD=build
ENV YAUL_OPTION_MALLOC_IMPL=tlsf

ENV PATH="${YAUL_INSTALL_ROOT}/bin:${PATH}"

# Toolchain source versions
ARG BINUTILS_VER=2.40
ARG GCC_VER=12.3.0

# ---------------------------------------------------------------------------
# 1. Host build tools
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    wget \
    curl \
    xz-utils \
    python3 \
    python3-pip \
    python3-polib \
    python3-pil \
    python3-dotenv \
    texinfo \
    bison \
    flex \
    libgmp-dev \
    libmpfr-dev \
    libmpc-dev \
    && rm -rf /var/lib/apt/lists/*

RUN mkdir -p "${YAUL_INSTALL_ROOT}"

# ---------------------------------------------------------------------------
# 2. Download cross-compiler sources
# ---------------------------------------------------------------------------
RUN cd /tmp && \
    wget -q "https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VER}.tar.xz" && \
    wget -q "https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VER}/gcc-${GCC_VER}.tar.xz" && \
    tar xf "binutils-${BINUTILS_VER}.tar.xz" && \
    tar xf "gcc-${GCC_VER}.tar.xz" && \
    rm "binutils-${BINUTILS_VER}.tar.xz" "gcc-${GCC_VER}.tar.xz"

# Download GCC prerequisites (gmp, mpfr, mpc if not packaged)
RUN cd /tmp/gcc-${GCC_VER} && contrib/download_prerequisites

# ---------------------------------------------------------------------------
# 3. sh2eb-elf binutils (SH-2 big-endian)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-binutils && cd /tmp/build-sh-binutils && \
    /tmp/binutils-${BINUTILS_VER}/configure \
        --target=sh2eb-elf \
        --prefix="${YAUL_INSTALL_ROOT}" \
        --disable-nls \
        --disable-multilib \
        --disable-werror \
    && make -j"$(nproc)" && make install && \
    rm -rf /tmp/build-sh-binutils

# ---------------------------------------------------------------------------
# 4. sh2eb-elf GCC stage 1 (compiler only, no C library yet)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-sh-gcc1 && cd /tmp/build-sh-gcc1 && \
    /tmp/gcc-${GCC_VER}/configure \
        --target=sh2eb-elf \
        --prefix="${YAUL_INSTALL_ROOT}" \
        --enable-languages=c,c++ \
        --without-headers \
        --with-newlib \
        --disable-nls \
        --disable-shared \
        --disable-multilib \
        --disable-decimal-float \
        --disable-threads \
        --disable-libatomic \
        --disable-libgomp \
        --disable-libquadmath \
        --disable-libssp \
        --disable-libvtv \
        --disable-libstdcxx \
    && make -j"$(nproc)" all-gcc all-target-libgcc \
    && make install-gcc install-target-libgcc && \
    rm -rf /tmp/build-sh-gcc1

# Newlib does not recognise the sh2eb CPU name, and Yaul ships its own C
# runtime in libyaul/libc/ anyway.  Stage 1 (compiler + libgcc) is all
# we need; Yaul's specs file overrides *startfile:/*endfile:/*lib: to empty
# so nothing from a host C library is linked in.

# ---------------------------------------------------------------------------
# 7. m68k-elf binutils (Saturn 68EC000 sound CPU)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-binutils && cd /tmp/build-m68k-binutils && \
    /tmp/binutils-${BINUTILS_VER}/configure \
        --target=m68k-elf \
        --prefix="${YAUL_INSTALL_ROOT}" \
        --disable-nls \
        --disable-multilib \
        --disable-werror \
    && make -j"$(nproc)" && make install && \
    rm -rf /tmp/build-m68k-binutils

# ---------------------------------------------------------------------------
# 8. m68k-elf GCC (compiler only; Yaul provides its own sound startup)
# ---------------------------------------------------------------------------
RUN mkdir -p /tmp/build-m68k-gcc && cd /tmp/build-m68k-gcc && \
    /tmp/gcc-${GCC_VER}/configure \
        --target=m68k-elf \
        --prefix="${YAUL_INSTALL_ROOT}" \
        --enable-languages=c \
        --without-headers \
        --with-newlib \
        --disable-nls \
        --disable-shared \
        --disable-multilib \
        --disable-libssp \
    && make -j"$(nproc)" all-gcc && make install-gcc && \
    rm -rf /tmp/build-m68k-gcc

# Clean up source tarballs/trees
RUN rm -rf /tmp/binutils-${BINUTILS_VER} /tmp/gcc-${GCC_VER}

# ---------------------------------------------------------------------------
# 9. Create m68keb-elf symlinks
#    Yaul expects YAUL_ARCH_M68K_PREFIX=m68keb-elf but we built m68k-elf.
#    m68k is always big-endian, so m68k-elf == m68keb-elf semantically.
# ---------------------------------------------------------------------------
RUN for tool in "${YAUL_INSTALL_ROOT}/bin/m68k-elf-"*; do \
        base="$(basename "$tool")"; \
        newname="${YAUL_INSTALL_ROOT}/bin/m68keb-elf-${base#m68k-elf-}"; \
        ln -sf "$tool" "$newname"; \
    done

# ---------------------------------------------------------------------------
# 10. Clone and install libyaul
# ---------------------------------------------------------------------------
RUN git clone --depth 1 --recurse-submodules \
        https://github.com/yaul-org/libyaul.git /tmp/yaul && \
    mkdir -p /tmp/yaul-build && \
    cd /tmp/yaul && \
    YAUL_INSTALL_ROOT="${YAUL_INSTALL_ROOT}" \
    YAUL_ARCH_SH_PREFIX=sh2eb-elf \
    YAUL_PROG_SH_PREFIX=sh2eb-elf \
    YAUL_ARCH_M68K_PREFIX=m68keb-elf \
    YAUL_BUILD_ROOT=/tmp/yaul-build \
    YAUL_BUILD=build \
    YAUL_OPTION_MALLOC_IMPL=tlsf \
    make install && \
    rm -rf /tmp/yaul /tmp/yaul-build

# ---------------------------------------------------------------------------
# 11. Provide a freestanding stdint.h in the Yaul sysroot.
#     GCC 12 --without-headers installs a stdint.h wrapper that emits
#     `#include_next <stdint.h>`, but Yaul's sysroot has no system stdint.h
#     to satisfy that lookup.  Yaul's own errno.h (and anything it pulls in)
#     will fail to compile in any external cmake project without this stub.
# ---------------------------------------------------------------------------
RUN test -f "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h" || \
    cp /opt/yaul/lib/gcc/sh2eb-elf/12.3.0/include/stdint-gcc.h \
       "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h" 2>/dev/null || \
    printf '%s\n' \
    '#pragma once' \
    '#ifndef __STDINT_H' \
    '#define __STDINT_H' \
    'typedef signed char          int8_t;' \
    'typedef signed short         int16_t;' \
    'typedef signed int           int32_t;' \
    'typedef signed long long     int64_t;' \
    'typedef unsigned char        uint8_t;' \
    'typedef unsigned short       uint16_t;' \
    'typedef unsigned int         uint32_t;' \
    'typedef unsigned long long   uint64_t;' \
    'typedef int8_t               int_least8_t;' \
    'typedef int16_t              int_least16_t;' \
    'typedef int32_t              int_least32_t;' \
    'typedef int64_t              int_least64_t;' \
    'typedef uint8_t              uint_least8_t;' \
    'typedef uint16_t             uint_least16_t;' \
    'typedef uint32_t             uint_least32_t;' \
    'typedef uint64_t             uint_least64_t;' \
    'typedef int32_t              int_fast8_t;' \
    'typedef int32_t              int_fast16_t;' \
    'typedef int32_t              int_fast32_t;' \
    'typedef int64_t              int_fast64_t;' \
    'typedef uint32_t             uint_fast8_t;' \
    'typedef uint32_t             uint_fast16_t;' \
    'typedef uint32_t             uint_fast32_t;' \
    'typedef uint64_t             uint_fast64_t;' \
    'typedef int32_t              intptr_t;' \
    'typedef uint32_t             uintptr_t;' \
    'typedef int64_t              intmax_t;' \
    'typedef uint64_t             uintmax_t;' \
    '#define INT8_MIN    (-128)' \
    '#define INT16_MIN   (-32768)' \
    '#define INT32_MIN   (-2147483647-1)' \
    '#define INT64_MIN   (-9223372036854775807LL-1)' \
    '#define INT8_MAX    (127)' \
    '#define INT16_MAX   (32767)' \
    '#define INT32_MAX   (2147483647)' \
    '#define INT64_MAX   (9223372036854775807LL)' \
    '#define UINT8_MAX   (255U)' \
    '#define UINT16_MAX  (65535U)' \
    '#define UINT32_MAX  (4294967295U)' \
    '#define UINT64_MAX  (18446744073709551615ULL)' \
    '#define INTPTR_MIN  INT32_MIN' \
    '#define INTPTR_MAX  INT32_MAX' \
    '#define UINTPTR_MAX UINT32_MAX' \
    '#define INTMAX_MIN  INT64_MIN' \
    '#define INTMAX_MAX  INT64_MAX' \
    '#define UINTMAX_MAX UINT64_MAX' \
    '#define SIZE_MAX    UINT32_MAX' \
    '#define INT8_C(c)   (c)' \
    '#define INT16_C(c)  (c)' \
    '#define INT32_C(c)  (c)' \
    '#define INT64_C(c)  (c ## LL)' \
    '#define UINT8_C(c)  (c ## U)' \
    '#define UINT16_C(c) (c ## U)' \
    '#define UINT32_C(c) (c ## U)' \
    '#define UINT64_C(c) (c ## ULL)' \
    '#define INTMAX_C(c) (c ## LL)' \
    '#define UINTMAX_C(c)(c ## ULL)' \
    '#endif' \
    > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/stdint.h"

# ---------------------------------------------------------------------------
# 11b. Stubs for non-standard / bare-metal-missing headers.
#      Yaul's sysroot only ships a minimal libc; third-party libraries such as
#      libzip need these POSIX/GNU headers to compile.
# ---------------------------------------------------------------------------
RUN printf '#pragma once\n#include <stdlib.h>\n' \
        > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/malloc.h" && \
    printf '#pragma once\n#include <string.h>\n' \
        > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/memory.h" && \
    printf '%s\n' \
        '#pragma once' \
        '#ifndef _TIME_H' \
        '#define _TIME_H' \
        'typedef long time_t;' \
        'typedef long clock_t;' \
        '#define CLOCKS_PER_SEC 1000L' \
        'struct tm {' \
        '  int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year;' \
        '  int tm_wday, tm_yday, tm_isdst;' \
        '};' \
        'static inline time_t time(time_t *t) { if(t) *t=0; return 0; }' \
        'static inline time_t mktime(struct tm *t) { (void)t; return 0; }' \
        'static inline struct tm *localtime(const time_t *t) { (void)t; return 0; }' \
        'static inline struct tm *gmtime(const time_t *t) { (void)t; return 0; }' \
        '#endif' \
        > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/time.h" && \
    printf '%s\n' \
        '#pragma once' \
        '#ifndef _SYS_STAT_H' \
        '#define _SYS_STAT_H' \
        '#include <sys/types.h>' \
        '#include <time.h>' \
        'typedef unsigned int dev_t;' \
        'typedef unsigned int ino_t;' \
        'typedef unsigned short nlink_t;' \
        'typedef long blksize_t;' \
        'typedef long blkcnt_t;' \
        'struct stat {' \
        '  dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink;' \
        '  uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size;' \
        '  blksize_t st_blksize; blkcnt_t st_blocks;' \
        '  time_t st_atime, st_mtime, st_ctime;' \
        '};' \
        'static inline int stat(const char *p, struct stat *b){(void)p;(void)b;return -1;}' \
        'static inline int fstat(int f, struct stat *b){(void)f;(void)b;return -1;}' \
        '#define S_ISREG(m)  (((m)&0170000)==0100000)' \
        '#define S_ISDIR(m)  (((m)&0170000)==0040000)' \
        '#define S_IRUSR 0400' '#define S_IWUSR 0200' '#define S_IXUSR 0100' \
        '#endif' \
        > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/sys/stat.h"

# ---------------------------------------------------------------------------
# 12. sat-xc.cmake — shared CMake toolchain for zlib and libzip.
#     CMAKE_SYSROOT tells cmake to pass --sysroot to the compiler so that
#     GCC's #include_next wrappers resolve against the Yaul sysroot headers.
# ---------------------------------------------------------------------------
RUN printf '%s\n' \
    'set(CMAKE_SYSTEM_NAME Generic)' \
    'set(CMAKE_SYSTEM_PROCESSOR sh2)' \
    "set(CMAKE_C_COMPILER  \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc\")" \
    "set(CMAKE_AR          \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar\")" \
    "set(CMAKE_RANLIB      \"${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ranlib\")" \
    "set(CMAKE_SYSROOT     \"${YAUL_INSTALL_ROOT}/sh2eb-elf\")" \
    'set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)' \
    'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' \
    'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' \
    'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' \
    > /tmp/sat-xc.cmake

# ---------------------------------------------------------------------------
# 13. Cross-compile zlib for sh2eb-elf (inflate/deflate core only).
#     We compile the nine core source files directly rather than via cmake
#     to avoid the gz* files whose POSIX gzip-streaming API is both unused
#     (libzip only needs inflate/deflate) and cannot compile on bare metal
#     (gzguts.h -> errno.h -> stdint.h chain, and zutil.h includes gzguts.h
#     unless NO_GZIP is defined).
# ---------------------------------------------------------------------------
RUN wget -q https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz -O /tmp/zlib.tar.gz && \
    tar xf /tmp/zlib.tar.gz -C /tmp && \
    cd /tmp/zlib-1.3.1 && \
    for f in adler32.c compress.c crc32.c deflate.c infback.c \
              inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c; do \
        "${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-gcc" \
            -m2 -mb -fno-builtin -O2 -DNO_GZIP -DSTDC -I. -c "$f" -o "${f%.c}.o"; \
    done && \
    "${YAUL_INSTALL_ROOT}/bin/sh2eb-elf-ar" rcs \
        "${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/libz.a" \
        adler32.o compress.o crc32.o deflate.o infback.o inffast.o \
        inflate.o inftrees.o trees.o uncompr.o zutil.o && \
    install -m644 zlib.h zconf.h "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/" && \
    rm -rf /tmp/zlib-1.3.1 /tmp/zlib.tar.gz

# ---------------------------------------------------------------------------
# 14. Cross-compile libzip for sh2eb-elf (reuses /tmp/sat-xc.cmake above)
# ---------------------------------------------------------------------------
RUN wget -q https://github.com/nih-at/libzip/releases/download/v1.10.1/libzip-1.10.1.tar.gz -O /tmp/libzip.tar.gz && \
    tar xf /tmp/libzip.tar.gz -C /tmp && \
    cmake -S /tmp/libzip-1.10.1 -B /tmp/libzip-build \
        -DCMAKE_TOOLCHAIN_FILE=/tmp/sat-xc.cmake \
        -DCMAKE_INSTALL_PREFIX="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
        -DCMAKE_FIND_ROOT_PATH="${YAUL_INSTALL_ROOT}/sh2eb-elf" \
        -DCMAKE_C_FLAGS="-m2 -mb -fno-builtin -O2" \
        -DZLIB_LIBRARY="${YAUL_INSTALL_ROOT}/sh2eb-elf/lib/libz.a" \
        -DZLIB_INCLUDE_DIR="${YAUL_INSTALL_ROOT}/sh2eb-elf/include" \
        -DENABLE_BZIP2=OFF \
        -DENABLE_LZMA=OFF \
        -DENABLE_ZSTD=OFF \
        -DENABLE_OPENSSL=OFF \
        -DENABLE_GNUTLS=OFF \
        -DBUILD_SHARED_LIBS=OFF \
        -DBUILD_EXAMPLES=OFF \
        -DBUILD_DOCUMENTATION=OFF \
        -DBUILD_REGRESS=OFF \
        -DBUILD_TOOLS=OFF \
        -DHAVE_CLONEFILE=0 \
        -DHAVE_ARC4RANDOM=0 \
        -DHAVE_GETPROGNAME=0 \
    && cmake --build /tmp/libzip-build -- -j"$(nproc)" \
    && cmake --install /tmp/libzip-build \
    && rm -rf /tmp/libzip-1.10.1 /tmp/libzip-build /tmp/sat-xc.cmake \
    ; rm -f /tmp/libzip.tar.gz /tmp/zlib.tar.gz 2>/dev/null ; true

# ---------------------------------------------------------------------------
# 15. Disc-image tools (separate layer so it does not invalidate the
#     expensive cross-compiler cache layers above on changes).
#     xorriso provides xorrisofs, needed by Yaul's make-iso script.
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y xorriso && rm -rf /var/lib/apt/lists/*

WORKDIR /workdir
VOLUME ["/workdir"]
