From ee6faf360480833e81b96c3909ae68a5cb112c95 Mon Sep 17 00:00:00 2001 From: Bill Rich Date: Fri, 13 Mar 2026 23:55:12 +0000 Subject: [PATCH] feat(build): Add Docker-based Linux build using Wine and MSVC 2022 --- CMakePresets.json | 16 ++++ GeneralsMD/Code/Main/RTS.RC | 8 +- resources/dockerbuild-msvc/Dockerfile | 83 +++++++++++++++++ resources/dockerbuild-msvc/entrypoint.sh | 110 +++++++++++++++++++++++ scripts/docker-build-msvc.sh | 75 ++++++++++++++++ 5 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 resources/dockerbuild-msvc/Dockerfile create mode 100644 resources/dockerbuild-msvc/entrypoint.sh create mode 100755 scripts/docker-build-msvc.sh diff --git a/CMakePresets.json b/CMakePresets.json index 3b0a69e7261..81ad27cc9e9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -6,6 +6,22 @@ "patch": 0 }, "configurePresets": [ + { + "name": "msvc-wine", + "displayName": "Windows 32bit MSVC 2022 via Wine", + "generator": "Ninja", + "hidden": false, + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>DLL", + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_C_FLAGS": "/std:c17", + "CMAKE_CXX_FLAGS": "/std:c++20 /EHsc /Zc:__cplusplus", + "CMAKE_C_STANDARD": "17", + "RTS_FLAGS": "/W3" + } + }, { "name": "vc6", "displayName": "Windows 32bit VC6 Release", diff --git a/GeneralsMD/Code/Main/RTS.RC b/GeneralsMD/Code/Main/RTS.RC index faa4ee857fa..9513a8730da 100644 --- a/GeneralsMD/Code/Main/RTS.RC +++ b/GeneralsMD/Code/Main/RTS.RC @@ -7,7 +7,7 @@ // // Generated from the TEXTINCLUDE 2 resource. // -#include "afxres.h" +#include "winresrc.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS @@ -27,18 +27,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // TEXTINCLUDE // -1 TEXTINCLUDE DISCARDABLE +1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END -2 TEXTINCLUDE DISCARDABLE +2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END -3 TEXTINCLUDE DISCARDABLE +3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" diff --git a/resources/dockerbuild-msvc/Dockerfile b/resources/dockerbuild-msvc/Dockerfile new file mode 100644 index 00000000000..a8a51410d59 --- /dev/null +++ b/resources/dockerbuild-msvc/Dockerfile @@ -0,0 +1,83 @@ +FROM debian:12-slim + +# Build arguments +ARG CMAKE_VERSION="3.31.6" +ARG GIT_VERSION="2.49.0" +ARG UID=1000 +ARG GID=1000 + +# Install system dependencies (Wine, Python for msvc-wine, and build tools) +RUN apt-get update \ + && dpkg --add-architecture i386 \ + && apt-get install -y --no-install-recommends \ + wget gpg unzip git ca-certificates \ + python3 msitools \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Wine from WineHQ +RUN mkdir -pm755 /etc/apt/keyrings \ + && wget -O - https://dl.winehq.org/wine-builds/winehq.key | gpg --dearmor -o /etc/apt/keyrings/winehq-archive.key - \ + && wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/debian/dists/bookworm/winehq-bookworm.sources \ + && apt-get update \ + && apt-get install -y --no-install-recommends winehq-stable \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Setup build folder +RUN mkdir -p /build/tools /build/tmp /build/tmp/home \ + && chown -R ${UID}:${GID} /build + +USER ${UID}:${GID} +WORKDIR /build/tools + +# Install Windows CMake +RUN wget -q https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-windows-x86_64.zip \ + && unzip -q cmake-${CMAKE_VERSION}-windows-x86_64.zip -d /build/tools/ \ + && mv /build/tools/cmake-${CMAKE_VERSION}-windows-x86_64 /build/tools/cmake \ + && rm -f *.zip + +# Install Windows Git +RUN wget -q https://github.com/git-for-windows/git/releases/download/v${GIT_VERSION}.windows.1/MinGit-${GIT_VERSION}-64-bit.zip \ + && unzip -q MinGit-${GIT_VERSION}-64-bit.zip -d /build/tools/ \ + && mv /build/tools/cmd /build/tools/git \ + && rm -f *.zip + +# Install Windows Ninja +RUN wget -q https://github.com/ninja-build/ninja/releases/download/v1.13.1/ninja-win.zip \ + && unzip -q ninja-win.zip -d /build/tools/ \ + && rm -f *.zip + +# Install MSVC Build Tools via msvc-wine +RUN git clone --depth 1 https://github.com/mstorsjo/msvc-wine.git /build/tools/msvc-wine + +# Download MSVC 2022 and Windows SDK (x86 target for 32-bit game) +# --msvc-version 17 = VS 2022, host x64, target x86 +ENV WINEDEBUG=-all +ENV WINEARCH=win64 +ENV WINEPREFIX=/build/prefix64 +ENV HOME=/build/tmp/home + +RUN /build/tools/msvc-wine/vsdownload.py \ + --dest /build/tools/msvc \ + --accept-license \ + && /build/tools/msvc-wine/install.sh /build/tools/msvc + +# Setup environment for MSVC 2022 +# The install.sh creates wrapper scripts in msvc/bin/x86/ and msvc/bin/x64/ +# We use x86 for 32-bit compilation +ENV CC="Z:\\build\\tools\\msvc\\bin\\x86\\cl.exe" +ENV CXX="Z:\\build\\tools\\msvc\\bin\\x86\\cl.exe" +ENV PRESET=msvc-wine + +# Create empty TEMP folder for linking +ENV TMP="Z:\\build\\tmp" +ENV TEMP="Z:\\build\\tmp" +ENV TEMPDIR="Z:\\build\\tmp" + +WORKDIR /build/cnc + +USER root +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +CMD ["/entrypoint.sh"] diff --git a/resources/dockerbuild-msvc/entrypoint.sh b/resources/dockerbuild-msvc/entrypoint.sh new file mode 100644 index 00000000000..39e5c062f72 --- /dev/null +++ b/resources/dockerbuild-msvc/entrypoint.sh @@ -0,0 +1,110 @@ +#!/bin/bash +set -euo pipefail +cd /build/cnc + +# Use a fresh Wine prefix owned by the current user +export WINEPREFIX="/tmp/wineprefix" +mkdir -p "$WINEPREFIX" + +# Initialize Wine prefix +wineboot --init 2>/dev/null || true + +# MSVC paths - replicate what msvcenv.sh sets +BASE="Z:\\build\\tools\\msvc" +MSVCVER="14.50.35717" +SDKVER="10.0.26100.0" +ARCH="x86" + +MSVCDIR="${BASE}\\VC\\Tools\\MSVC\\${MSVCVER}" +SDKBASE="${BASE}\\Windows Kits\\10" +SDKINCLUDE="${SDKBASE}\\Include\\${SDKVER}" +SDKLIB="${SDKBASE}\\Lib\\${SDKVER}" + +# Real Windows executables (not the Unix wrapper scripts) +CL_WIN="${MSVCDIR}\\bin\\Hostx64\\${ARCH}\\cl.exe" +LINK_WIN="${MSVCDIR}\\bin\\Hostx64\\${ARCH}\\link.exe" + +# Environment for cl.exe to find headers and libraries +export INCLUDE="${MSVCDIR}\\atlmfc\\include;${MSVCDIR}\\include;${SDKINCLUDE}\\shared;${SDKINCLUDE}\\ucrt;${SDKINCLUDE}\\um;${SDKINCLUDE}\\winrt" +export LIB="${MSVCDIR}\\atlmfc\\lib\\${ARCH};${MSVCDIR}\\lib\\${ARCH};${SDKLIB}\\ucrt\\${ARCH};${SDKLIB}\\um\\${ARCH}" +export LIBPATH="${LIB}" + +# Wine needs the Hostx64/x64 dir in PATH for DLLs (vcruntime140.dll etc.) +export WINEPATH="${MSVCDIR}\\bin\\Hostx64\\${ARCH};${MSVCDIR}\\bin\\Hostx64\\x64;${SDKBASE}\\bin\\${SDKVER}\\x64" +export WINEDLLOVERRIDES="vcruntime140=n;vcruntime140_1=n" + +BUILD_DIR="/build/cnc/build/${PRESET}" +# +# Symlink "Windows Kits" to a path without spaces so Wine doesn't generate +# 8.3 short names (e.g. WIND~2DP) that can't be resolved on Linux filesystems. +WINKITS="/build/tools/msvc/Windows Kits" +WINKITS_LINK="/build/tools/msvc/WindowsKits" +if [ -d "$WINKITS" ] && [ ! -e "$WINKITS_LINK" ]; then + ln -s "$WINKITS" "$WINKITS_LINK" +fi + +RC_COMPILER="Z:/build/tools/msvc/WindowsKits/10/bin/${SDKVER}/x64/rc.exe" +RC_INCLUDE="Z:/build/tools/msvc/WindowsKits/10/Include/${SDKVER}" +RC_FLAGS="-I \"${RC_INCLUDE}/um\" -I \"${RC_INCLUDE}/shared\"" + +# Configure if needed +if [ "${FORCE_CMAKE:-}" = "true" ] || [ ! -f "${BUILD_DIR}/build.ninja" ]; then + rm -f "${BUILD_DIR}/CMakeCache.txt" + + wine /build/tools/cmake/bin/cmake.exe \ + --preset ${PRESET} \ + -DCMAKE_SYSTEM="Windows" \ + -DCMAKE_SYSTEM_NAME="Windows" \ + -DCMAKE_SIZEOF_VOID_P=4 \ + -DCMAKE_MAKE_PROGRAM="Z:/build/tools/ninja.exe" \ + -DCMAKE_C_COMPILER="${CL_WIN}" \ + -DCMAKE_CXX_COMPILER="${CL_WIN}" \ + -DCMAKE_LINKER="${LINK_WIN}" \ + -DCMAKE_C_COMPILER_ID=MSVC \ + -DCMAKE_CXX_COMPILER_ID=MSVC \ + -DCMAKE_C_COMPILER_VERSION=19.50.35726 \ + -DCMAKE_CXX_COMPILER_VERSION=19.50.35726 \ + -DMSVC_VERSION=1950 \ + -DMSVC=1 \ + -DCMAKE_C_STANDARD_COMPUTED_DEFAULT=17 \ + -DCMAKE_C_EXTENSIONS_COMPUTED_DEFAULT=OFF \ + -DCMAKE_CXX_STANDARD_COMPUTED_DEFAULT=20 \ + -DCMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT=OFF \ + -DCMAKE_SUPPRESS_REGENERATION=ON \ + -DCMAKE_C_COMPILER_WORKS=1 \ + -DCMAKE_CXX_COMPILER_WORKS=1 \ + -DGIT_EXECUTABLE="Z:/build/tools/git/git.exe" \ + -DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER \ + -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \ + -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \ + -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY \ + -DCMAKE_RC_COMPILER="${RC_COMPILER}" \ + -DCMAKE_RC_FLAGS="${RC_FLAGS}" \ + -B "${BUILD_DIR}" +fi + +# Fix PCH paths: CMake generates Unix paths for /FI, /Yc, /Fp flags. +# MSVC under Wine needs Z: drive prefix. +echo "Fixing PCH paths for Wine..." +sed -i \ + -e 's|/Yc/build/cnc/|/YcZ:/build/cnc/|g' \ + -e 's|/Yu/build/cnc/|/YuZ:/build/cnc/|g' \ + -e 's|/Fp/build/cnc/|/FpZ:/build/cnc/|g' \ + -e 's|/FI/build/cnc/|/FIZ:/build/cnc/|g' \ + -e 's| -c /build/cnc/| -c Z:/build/cnc/|g' \ + -e 's| -c \\build\\cnc\\| -c Z:\\build\\cnc\\|g' \ + -e 's| -I \\build\\cnc\\| -I Z:\\build\\cnc\\|g' \ + -e 's| -I /build/cnc/| -I Z:/build/cnc/|g' \ + -e 's|-LIBPATH:\\build\\cnc\\|-LIBPATH:Z:\\build\\cnc\\|g' \ + -e 's|-LIBPATH:/build/cnc/|-LIBPATH:Z:/build/cnc/|g' \ + "${BUILD_DIR}/build.ninja" + +# Remove the CMake regeneration rule so Ninja doesn't overwrite our fixes +sed -i '/^build build.ninja:/,/^$/d' "${BUILD_DIR}/build.ninja" + +FIXED=$(grep -c 'Z:/build/cnc/' "${BUILD_DIR}/build.ninja" || true) +echo "Fixed paths: ${FIXED} occurrences with Z: prefix" + +# Build - pass MSVC environment into the Windows cmd session +cd "${BUILD_DIR}" +wine cmd /c "set TMP=Z:\build\tmp& set TEMP=Z:\build\tmp& set INCLUDE=${INCLUDE}& set LIB=${LIB}& set LIBPATH=${LIBPATH}& set PATH=${WINEPATH};%PATH%& Z:\build\tools\ninja.exe ${MAKE_TARGET:-z_generals}" diff --git a/scripts/docker-build-msvc.sh b/scripts/docker-build-msvc.sh new file mode 100755 index 00000000000..5ab13cf3a34 --- /dev/null +++ b/scripts/docker-build-msvc.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# +# Build script for compiling Generals/Zero Hour on Linux using Docker + Wine + MSVC 2022 +# +# This script builds Windows executables using a Docker container with Wine and MSVC Build Tools. +# The resulting binaries are identical to native Windows VS2022 builds. +# +# Usage: +# ./scripts/docker-build-msvc.sh # Full build of Zero Hour (z_generals) +# ./scripts/docker-build-msvc.sh --clean # Clean build directory +# ./scripts/docker-build-msvc.sh --cmake # Force CMake reconfiguration +# ./scripts/docker-build-msvc.sh --interactive # Enter container shell +# + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +DOCKER_DIR="$PROJECT_DIR/resources/dockerbuild-msvc" +IMAGE_NAME="generals-msvc-build" +PRESET="msvc-wine" + +MAKE_TARGET="" +FORCE_CMAKE="false" +INTERACTIVE="false" + +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + MAKE_TARGET="$2" + shift 2 + ;; + --clean) + echo "Cleaning build directory..." + rm -rf "$PROJECT_DIR/build/$PRESET" + exit 0 + ;; + --cmake) + FORCE_CMAKE="true" + shift + ;; + --interactive) + INTERACTIVE="true" + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Build Docker image (this takes a while the first time - downloads MSVC) +echo "Building Docker image (first run downloads ~3GB of MSVC tools)..." +docker build \ + --build-arg UID="$(id -u)" \ + --build-arg GID="$(id -g)" \ + -t "$IMAGE_NAME" \ + "$DOCKER_DIR" + +DOCKER_ARGS=( + --rm + -v "$PROJECT_DIR:/build/cnc" + -e "PRESET=$PRESET" + -e "FORCE_CMAKE=$FORCE_CMAKE" + -e "MAKE_TARGET=${MAKE_TARGET}" +) + +if [ "$INTERACTIVE" = "true" ]; then + echo "Entering interactive shell..." + docker run -it "${DOCKER_ARGS[@]}" "$IMAGE_NAME" /bin/bash +else + echo "Building..." + docker run "${DOCKER_ARGS[@]}" "$IMAGE_NAME" +fi