# DarkRoom — reproducible Windows cross-build environment
#
# Everything docs/dev/windows.md §2 names: Rust with the GNU Windows target, the
# MinGW-w64 cross compiler it links with, NSIS to build the installer, and Wine
# to smoke-test the result. Both CI and local builds use this image, so "works
# on my machine" and "works in CI" are the same machine — the same argument
# docker/android makes, and the same shape.
#
# Build:  docker build -t darkroom-windows:latest docker/windows
# Use:    ./docker/windows/build.sh cargo build --release --target x86_64-pc-windows-gnu -p darkroom-desktop

# trixie rather than the Android image's bookworm, for Wine: rustc's std
# imports bcryptprimitives.dll for its random source, and bookworm's Wine 8.0
# does not have it, so the smoke test dies at load with c0000135 before a
# single instruction of the application runs. Wine 10 does. trixie also ships
# Node 20 itself, so the NodeSource step the Android image needs is not here.
FROM docker.io/library/debian:trixie-slim

# ---------------------------------------------------------------------------
# Versions — pinned deliberately, like the Android image.
# ---------------------------------------------------------------------------
ARG RUST_VERSION=1.92.0

ENV DEBIAN_FRONTEND=noninteractive \
    CARGO_HOME=/opt/cargo \
    RUSTUP_HOME=/opt/rustup \
    PATH=/opt/cargo/bin:$PATH

# ---------------------------------------------------------------------------
# System packages
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y --no-install-recommends \
        ca-certificates curl git git-lfs \
        # A *host* C compiler as well as the cross one: build scripts and
        # proc-macros are compiled for Linux and linked with `cc`, whatever
        # the target. Without it the very first build script fails with
        # "linker `cc` not found" before any Windows code is reached.
        gcc libc6-dev \
        # The cross compiler, binutils and the MinGW runtime headers/libs. This
        # is the one C toolchain the target needs: bundled SQLite, ring's asm
        # and anything else the cc crate builds for the target go through it.
        gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 \
        # The installer compiler. A native Linux binary; NSIS has always built
        # its installers on POSIX hosts.
        nsis \
        # Runs the .exe and the installer for the smoke tests (windows.md §6).
        # Not needed to build anything. Both packages: `wine64` is the
        # loader under /usr/lib/wine, `wine` is the wrapper on PATH.
        wine wine64 \
        # `file` reports PE32+; `xz-utils` because the mingw packages are
        # compressed with it.
        file xz-utils \
        # Gitea runs JavaScript actions (checkout, cache) from inside the job
        # container, and current actions want Node 20 or newer.
        nodejs \
    && rm -rf /var/lib/apt/lists/* \
    && node --version

# ---------------------------------------------------------------------------
# Rust + the Windows target
#
# The component list must be a superset of rust-toolchain.toml's, for the
# reason the Android Dockerfile gives: rustup reconciles that file on the
# first cargo invocation and downloads anything missing inside the job.
# ---------------------------------------------------------------------------
RUN curl -fsSL https://sh.rustup.rs | sh -s -- \
        -y --no-modify-path --profile minimal --default-toolchain ${RUST_VERSION} \
    && rustup target add x86_64-pc-windows-gnu \
    && rustup component add rustfmt clippy rust-analyzer \
    && chmod -R a+rwX ${CARGO_HOME} ${RUSTUP_HOME}

# ---------------------------------------------------------------------------
# Linker configuration
#
# Debian ships the cross compiler in two thread models and the bare name is an
# alternatives symlink. `-posix` is stated: it is the one whose libstdc++ and
# libwinpthread the Rust target's own MinGW pieces were built against, and
# picking the other produces link errors that read as if std were missing.
#
# The runtime is linked statically (docs/dev/windows.md §2) so the installer
# carries one file. `-static-libgcc` is all it takes: rustc's windows-gnu
# target links its own copy of winpthread in self-contained mode, so nothing
# imports libwinpthread-1.dll — the smoke test's objdump step is what checks
# that. The `--whole-archive -lwinpthread` incantation the spec first named is
# wrong here: it forces in unused winpthread objects whose kernel32 and
# msvcrt references come after those libraries on the link line, and the
# link fails on a hundred undefined `__imp_` symbols.
# ---------------------------------------------------------------------------
ENV CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc-posix \
    CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUSTFLAGS="-C link-args=-static-libgcc -C link-args=-static-libstdc++" \
    CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc-posix \
    CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++-posix \
    AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc-ar-posix \
    WINDRES=x86_64-w64-mingw32-windres

# Wine writes its prefix under $HOME and refuses a directory it does not own.
# The caller passes --user, so nothing baked into the image can be owned by
# that user; build.sh bind-mounts a host directory here instead, which also
# keeps the prefix (and its slow first `wineboot`) across runs.
ENV HOME=/tmp/home \
    WINEDEBUG=-all

VOLUME ["/opt/cargo/registry"]

WORKDIR /work

CMD ["/bin/bash"]
