# syntax=docker/dockerfile:1.7 # # Builds against Ubuntu 22.04 (glibc 2.35) so the binaries also run on # distributions older than the developer machine. Two things make that work: # # * GCC 14 from the toolchain PPA — jammy's own gcc-11 has no , which # eight translation units use. # * -static-libstdc++ -static-libgcc — the target then needs nothing newer # than the glibc of this base image. # # docker build -f docker/Dockerfile --target mock-client -t tw_mock_client . # docker build -f docker/Dockerfile --target test . # docker build -f docker/Dockerfile --target export --output type=local,dest=dist . ARG UBUNTU_VERSION=22.04 # ── toolchain stage ────────────────────────────────────────────────────────── FROM ubuntu:${UBUNTU_VERSION} AS toolchain ENV DEBIAN_FRONTEND=noninteractive # jammy's Vulkan headers are 1.3.204, which predates the VkBufferUsageFlags2 # constants the renderer uses, and its glslang predates the -gVS the shader # target passes. LunarG publishes current headers, loader and glslang for jammy, # and outranks the distro packages on version. ARG VULKAN_SDK_VERSION=1.4.313 # The apt lists are kept: conan installs the xorg and egl system packages itself # while resolving SDL. RUN apt-get update && apt-get install -y --no-install-recommends \ software-properties-common ca-certificates gnupg wget \ && add-apt-repository -y ppa:ubuntu-toolchain-r/test \ && wget -qO /etc/apt/trusted.gpg.d/lunarg.asc \ https://packages.lunarg.com/lunarg-signing-key-pub.asc \ && wget -qO /etc/apt/sources.list.d/lunarg-vulkan.list \ "https://packages.lunarg.com/vulkan/${VULKAN_SDK_VERSION}/lunarg-vulkan-${VULKAN_SDK_VERSION}-jammy.list" \ && apt-get update && apt-get install -y --no-install-recommends \ gcc-14 g++-14 \ git make ninja-build ccache pkg-config \ python3-pip \ vulkan-headers libvulkan-dev glslang-tools \ libdecor-0-dev # cmake comes from pip because jammy ships 3.22 and the project asks for 3.26. ARG CMAKE_VERSION=4.4.0 ARG CONAN_VERSION=2.31.1 RUN pip3 install --no-cache-dir "cmake==${CMAKE_VERSION}" "conan==${CONAN_VERSION}" # Some autotools dependencies (flex, by way of SDL) run sub-configures that look # for an unsuffixed gcc/cc and ignore $CC, so 14 has to answer to the plain names. RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100 \ --slave /usr/bin/g++ g++ /usr/bin/g++-14 \ --slave /usr/bin/gcov gcov /usr/bin/gcov-14 \ && update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-14 100 \ && update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-14 100 ENV CC=gcc ENV CXX=g++ COPY docker/conan/jammy /etc/conan/jammy # ── dependency stage ───────────────────────────────────────────────────────── # Kept apart from the build so that editing sources does not rebuild SDL and # protobuf. The conan cache lives in the image layer rather than a cache mount, # so the generators in /deps can never outlive the packages they point at. FROM toolchain AS deps COPY conanfile.txt /src/conanfile.txt RUN conan install /src/conanfile.txt \ --profile:all=/etc/conan/jammy \ --build=missing \ --output-folder=/deps # ── build stage ────────────────────────────────────────────────────────────── # The build tree is a cache mount, so an edit rebuilds only what it touched. # That is also why glm, entt and Jolt have to travel in the build context: their # FetchContent SOURCE_DIRs point into external/, while the stamps that record # them as populated live in the cached build tree. Dropping them from the # context would leave the stamps pointing at empty directories. FROM deps AS builder WORKDIR /src COPY . . # tw_server is absent on purpose: it needs libpqxx >= 7.7 for pqxx::params and # jammy carries 6.4. RUN --mount=type=cache,target=/build \ --mount=type=cache,target=/root/.ccache \ cmake -S /src -B /build -G Ninja \ -DCMAKE_TOOLCHAIN_FILE=/deps/conan_toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release \ -DUSE_MOLD_LINKER=OFF \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_EXE_LINKER_FLAGS="-static-libstdc++ -static-libgcc" \ && cmake --build /build --target \ tw_client \ tw_mock_client \ tw_chat_server_exe \ tw_chat_mock_client \ tw_message_protocol_tests \ tw_metrics_tests \ tw_network_tests \ tw_peer_to_peer_tests \ && mkdir -p /out/bin /out/shaders \ && cp /build/modules/client/tw_client \ /build/modules/mock_client/tw_mock_client \ /build/modules/chat_service/chat_server_exe/tw_chat_server_exe \ /build/modules/chat_service/chat_service/tests/chat_mock_client/tw_chat_mock_client \ /out/bin/ \ && cp /build/modules/client/shaders/*.spirv /out/shaders/ # ── unit test stage ────────────────────────────────────────────────────────── # Catch2 suites only. The process harnesses below need peers and a network, so # they run as compose services instead. FROM builder AS test RUN --mount=type=cache,target=/build \ ctest --test-dir /build --output-on-failure # ── client export stage ────────────────────────────────────────────────────── # Not runnable as a container; the client needs a GPU and a display. Extract it: # docker build -f docker/Dockerfile --target export --output type=local,dest=dist . FROM scratch AS export COPY --from=builder /out/bin/tw_client /tw_client COPY --from=builder /out/shaders/ /shaders/ # ── runtime stages ─────────────────────────────────────────────────────────── FROM gcr.io/distroless/base-debian12:nonroot AS mock-client COPY --from=builder /out/bin/tw_mock_client /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/tw_mock_client"] FROM gcr.io/distroless/base-debian12:nonroot AS chat-server COPY --from=builder /out/bin/tw_chat_server_exe /usr/local/bin/ EXPOSE 8101/udp ENTRYPOINT ["/usr/local/bin/tw_chat_server_exe"] FROM gcr.io/distroless/base-debian12:nonroot AS chat-mock-client COPY --from=builder /out/bin/tw_chat_mock_client /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/tw_chat_mock_client"]