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 \ xorriso \ && 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 `, 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 \n' \ > "${YAUL_INSTALL_ROOT}/sh2eb-elf/include/malloc.h" && \ printf '#pragma once\n#include \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 ' \ '#include ' \ '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 WORKDIR /workdir VOLUME ["/workdir"]