# 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 \ # The bundled ONNX Runtime builds arrive as wheels, which are zips # (tools/fetch-bundled-runtimes.sh). unzip \ # 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"]