# syntax=docker/dockerfile:1
# Free Pascal Compiler (FPC) — amd64 image with cross-compilers for:
#
#   Linux:   aarch64  arm(hf)  i386  powerpc64  mipsel
#   FreeBSD: x86_64
#   NetBSD:  x86_64
#   OpenBSD: x86_64
#   DOS:     go32v2 (i386)
#
# Usage:
#   fpc hello.pas                            # native x86_64-linux
#   fpc -Paarch64    -Tlinux   hello.pas
#   fpc -Parm        -Tlinux   hello.pas     # armhf / ARMv7
#   fpc -Pi386       -Tlinux   hello.pas
#   fpc -Ppowerpc64  -Tlinux   hello.pas
#   fpc -Pmipsel     -Tlinux   hello.pas
#   fpc -Px86_64     -Tfreebsd hello.pas
#   fpc -Px86_64     -Tnetbsd  hello.pas
#   fpc -Px86_64     -Topenbsd hello.pas
#   fpc -Pi386       -Tgo32v2  hello.pas

ARG DEBIAN_VERSION=bookworm
ARG FPC_VERSION=3.2.2
ARG FPC_BASE_URL=https://downloads.freepascal.org/fpc/dist
ARG FREEBSD_VERSION=15.0-RELEASE
ARG NETBSD_VERSION=10.1
ARG OPENBSD_VERSION=7.8

# ── BSD sysroots ──────────────────────────────────────────────────────────────
# These three stages run in parallel. Only lib/ and usr/lib/ are extracted;
# FPC does not use C headers so there is no need for usr/include/.

FROM debian:${DEBIAN_VERSION} AS sysroot-freebsd
ARG FREEBSD_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates wget xz-utils \
    && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /sysroot \
    && wget -nv \
        "https://download.freebsd.org/releases/amd64/${FREEBSD_VERSION}/base.txz" \
        -O /tmp/base.txz \
    && tar -xJf /tmp/base.txz -C /sysroot ./lib ./usr/lib \
    && rm /tmp/base.txz

FROM debian:${DEBIAN_VERSION} AS sysroot-netbsd
ARG NETBSD_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates wget xz-utils \
    && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /sysroot \
    && wget -nv \
        "https://cdn.netbsd.org/pub/NetBSD/NetBSD-${NETBSD_VERSION}/amd64/binary/sets/base.tar.xz" \
        -O /tmp/base.tar.xz \
    && tar -xJf /tmp/base.tar.xz -C /sysroot ./lib ./usr/lib \
    && rm /tmp/base.tar.xz

FROM debian:${DEBIAN_VERSION} AS sysroot-openbsd
ARG OPENBSD_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates wget \
    && rm -rf /var/lib/apt/lists/*
# OpenBSD encodes the version in the filename without the dot (7.8 → 78).
RUN set -eu; \
    SHORT=$(echo "${OPENBSD_VERSION}" | tr -d '.'); \
    mkdir -p /sysroot \
    && wget -nv \
        "https://cdn.openbsd.org/pub/OpenBSD/${OPENBSD_VERSION}/amd64/base${SHORT}.tgz" \
        -O /tmp/base.tgz \
    && tar -xzf /tmp/base.tgz -C /sysroot ./usr/lib \
    && rm /tmp/base.tgz

# ── build stage ───────────────────────────────────────────────────────────────
FROM debian:${DEBIAN_VERSION} AS build

ARG FPC_VERSION
ARG FPC_BASE_URL

RUN apt-get update && apt-get install -y --no-install-recommends \
        binutils \
        binutils-aarch64-linux-gnu \
        binutils-arm-linux-gnueabihf \
        binutils-djgpp \
        binutils-i686-linux-gnu \
        binutils-mipsel-linux-gnu \
        binutils-powerpc64le-linux-gnu \
        ca-certificates \
        fpc \
        gcc \
        libc6-dev \
        lld \
        make \
        wget \
    && rm -rf /var/lib/apt/lists/*

# BSD cross-linker wrappers.
# ld:      calls ld.lld; uses --sysroot if /opt/{os}-sysroot exists so the
#          same script works in the build stage (no sysroot) and final image.
# ar/ranlib/strip: ELF archive format is OS-agnostic; native tools suffice.
RUN set -e; \
    for os in freebsd netbsd openbsd; do \
        printf '#!/bin/sh\nSR="/opt/%s-sysroot"\n[ -d "$SR" ] && set -- --sysroot="$SR" "$@"\nexec ld.lld "$@"\n' \
            "${os}" > "/usr/local/bin/x86_64-${os}-ld" \
        && chmod +x "/usr/local/bin/x86_64-${os}-ld"; \
        for tool in ar as ranlib strip; do \
            printf '#!/bin/sh\nexec %s "$@"\n' "${tool}" \
                > "/usr/local/bin/x86_64-${os}-${tool}" \
            && chmod +x "/usr/local/bin/x86_64-${os}-${tool}"; \
        done; \
    done

# FPC source.
RUN wget -nv \
        "${FPC_BASE_URL}/${FPC_VERSION}/source/fpc-${FPC_VERSION}.source.tar.gz" \
        -O /tmp/fpc-src.tar.gz \
    && tar -xzf /tmp/fpc-src.tar.gz -C /tmp \
    && rm /tmp/fpc-src.tar.gz

WORKDIR /tmp/fpc-${FPC_VERSION}

# ── host compiler (x86_64-linux) ──────────────────────────────────────────────
# Bootstrap using Debian's packaged fpc (same version, 3.2.2 on bookworm).
# OVERRIDEVERSIONCHECK=1 bypasses the Makefile's REQUIREDVERSION enforcement so
# we can use any working compiler as the starting point.
# env -u FPC_VERSION: Docker exports ARG FPC_VERSION into the shell environment;
# the FPC Makefile's `ifndef FPC_VERSION` block would skip auto-detection and
# mis-set internal version variables, so we unset it before invoking make.
RUN env -u FPC_VERSION make all \
        PP=ppcx64 OPT="-O2" NOGDB=1 OVERRIDEVERSIONCHECK=1 \
        INSTALL_PREFIX=/usr/local \
    && env -u FPC_VERSION make install \
        PP=ppcx64 OVERRIDEVERSIONCHECK=1 \
        INSTALL_PREFIX=/usr/local

# All crossall steps use the freshly built compiler from /usr/local/bin.
# /usr/local/bin is already on PATH in Debian; make this explicit for clarity.
ENV PATH="/usr/local/bin:${PATH}"

# ── cross: Linux targets ───────────────────────────────────────────────────────
RUN make crossall PP=ppcx64 CPU_TARGET=aarch64 OS_TARGET=linux \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=aarch64-linux-gnu- \
    && make crossinstall CPU_TARGET=aarch64 OS_TARGET=linux \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=aarch64-linux-gnu-

# CROSSOPT bakes ARMv7 hard-float ABI into the RTL, matching Debian armhf.
RUN make crossall PP=ppcx64 CPU_TARGET=arm OS_TARGET=linux \
        OPT="-O2" CROSSOPT="-CpARMV7A -CfVFPV3_D16" NOGDB=1 \
        BINUTILSPREFIX=arm-linux-gnueabihf- \
    && make crossinstall CPU_TARGET=arm OS_TARGET=linux \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=arm-linux-gnueabihf-

RUN make crossall PP=ppcx64 CPU_TARGET=i386 OS_TARGET=linux \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=i686-linux-gnu- \
    && make crossinstall CPU_TARGET=i386 OS_TARGET=linux \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=i686-linux-gnu-

# go32v2 reuses ppc386 built above; DJGPP binutils provide the DOS linker.
RUN make crossall PP=ppcx64 CPU_TARGET=i386 OS_TARGET=go32v2 \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=i586-pc-msdosdjgpp- \
    && make crossinstall CPU_TARGET=i386 OS_TARGET=go32v2 \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=i586-pc-msdosdjgpp-

RUN make crossall PP=ppcx64 CPU_TARGET=powerpc64 OS_TARGET=linux \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=powerpc64le-linux-gnu- \
    && make crossinstall CPU_TARGET=powerpc64 OS_TARGET=linux \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=powerpc64le-linux-gnu-

RUN make crossall PP=ppcx64 CPU_TARGET=mipsel OS_TARGET=linux \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=mipsel-linux-gnu- \
    && make crossinstall CPU_TARGET=mipsel OS_TARGET=linux \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=mipsel-linux-gnu-

# ── cross: BSD targets ─────────────────────────────────────────────────────────
# BSD targets reuse ppcx64 (same CPU); only the OS-specific RTL units differ.
RUN make crossall PP=ppcx64 CPU_TARGET=x86_64 OS_TARGET=freebsd \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=x86_64-freebsd- \
    && make crossinstall CPU_TARGET=x86_64 OS_TARGET=freebsd \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=x86_64-freebsd-

RUN make crossall PP=ppcx64 CPU_TARGET=x86_64 OS_TARGET=netbsd \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=x86_64-netbsd- \
    && make crossinstall CPU_TARGET=x86_64 OS_TARGET=netbsd \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=x86_64-netbsd-

RUN make crossall PP=ppcx64 CPU_TARGET=x86_64 OS_TARGET=openbsd \
        OPT="-O2" NOGDB=1 BINUTILSPREFIX=x86_64-openbsd- \
    && make crossinstall CPU_TARGET=x86_64 OS_TARGET=openbsd \
        INSTALL_PREFIX=/usr/local BINUTILSPREFIX=x86_64-openbsd-

# ── cross: fv (Free Vision) ────────────────────────────────────────────────────
# fv is a pure-Pascal TUI library; build it for every cross-target so that
# programs depending on it (e.g. rcs-shell) can be cross-compiled.
# Failures are tolerated — fv may not support every architecture.
RUN set -e; \
    for t in \
        "aarch64  linux   aarch64-linux-gnu-" \
        "arm      linux   arm-linux-gnueabihf-" \
        "i386     linux   i686-linux-gnu-" \
        "powerpc64 linux  powerpc64le-linux-gnu-" \
        "mipsel   linux   mipsel-linux-gnu-" \
        "x86_64   freebsd x86_64-freebsd-" \
        "x86_64   netbsd  x86_64-netbsd-" \
        "x86_64   openbsd x86_64-openbsd-" \
        "i386     go32v2  i586-pc-msdosdjgpp-"; do \
        cpu=$(echo "$t" | awk '{print $1}'); \
        os=$(echo "$t"  | awk '{print $2}'); \
        pfx=$(echo "$t" | awk '{print $3}'); \
        make -C packages/fv crossall \
            PP=ppcx64 CPU_TARGET="$cpu" OS_TARGET="$os" \
            OPT="-O2" NOGDB=1 BINUTILSPREFIX="$pfx" \
        && make -C packages/fv crossinstall \
            CPU_TARGET="$cpu" OS_TARGET="$os" \
            INSTALL_PREFIX=/usr/local BINUTILSPREFIX="$pfx" \
        || echo "WARNING: fv for $cpu-$os failed, skipping"; \
    done

# ── final image ───────────────────────────────────────────────────────────────
FROM debian:${DEBIAN_VERSION}-slim

ARG FPC_VERSION
ARG FREEBSD_VERSION
ARG NETBSD_VERSION
ARG OPENBSD_VERSION

LABEL org.opencontainers.image.title="Free Pascal Compiler (cross-compiler)" \
      org.opencontainers.image.version="${FPC_VERSION}" \
      org.opencontainers.image.description="FPC ${FPC_VERSION} — Linux (aarch64 arm i386 ppc64 mipsel) + FreeBSD/NetBSD/OpenBSD (x86_64) + DOS go32v2" \
      org.opencontainers.image.url="https://www.freepascal.org/" \
      org.opencontainers.image.source="https://codeberg.org/lyda/fpc-docker"

# Linux cross runtimes + lld for BSD linking + DJGPP for DOS go32v2.
RUN apt-get update && apt-get install -y --no-install-recommends \
        binutils \
        binutils-aarch64-linux-gnu \
        binutils-arm-linux-gnueabihf \
        binutils-djgpp \
        binutils-i686-linux-gnu \
        binutils-mipsel-linux-gnu \
        binutils-powerpc64le-linux-gnu \
        gcc \
        libc6-dev \
        libc6-dev-arm64-cross \
        libc6-dev-armhf-cross \
        libc6-dev-i386-cross \
        libc6-dev-mipsel-cross \
        libc6-dev-ppc64el-cross \
        lld \
        git make ca-certificates patch ssh-client jq curl netbase \
    && rm -rf /var/lib/apt/lists/*

# BSD sysroots (lib/ and usr/lib/ only — enough to link programs).
COPY --from=sysroot-freebsd /sysroot/ /opt/freebsd-sysroot/
COPY --from=sysroot-netbsd  /sysroot/ /opt/netbsd-sysroot/
COPY --from=sysroot-openbsd /sysroot/ /opt/openbsd-sysroot/

# FPC installation + BSD linker wrappers (both installed under /usr/local/).
COPY --from=build /usr/local/ /usr/local/

# FPC installs compiler binaries to lib/fpc/$VERSION/ but does not create
# bin/ symlinks; add them for all ppc* binaries (host + all cross-compilers).
RUN ln -s /usr/local/lib/fpc/${FPC_VERSION}/ppc* /usr/local/bin/

# FPC looks for x86_64-{os}-ld.bfd; our wrappers are named without .bfd.
RUN for os in freebsd netbsd openbsd; do \
        ln -s /usr/local/bin/x86_64-${os}-ld /usr/local/bin/x86_64-${os}-ld.bfd; \
    done

# Generate fpc.cfg, then append the BSD cross-compilation sections.
COPY fpc-cross.cfg /tmp/fpc-cross.cfg
RUN fpcmkcfg \
        -d "basepath=/usr/local/lib/fpc/${FPC_VERSION}" \
        -o /etc/fpc.cfg \
    && cat /tmp/fpc-cross.cfg >> /etc/fpc.cfg \
    && rm /tmp/fpc-cross.cfg \
    && fpc -iV

WORKDIR /src
