Files
dusk/docker/saturn/Dockerfile
T
2026-06-21 11:40:46 -05:00

369 lines
16 KiB
Docker

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"]