gitlab-ci: Add jobs to make Windows x86_64 and i386 packages

Run CPack in a separate job for nightly binaries, and not at all for
release binaries.  Unlike macOS disk images (.dmg), we cannot sign the
binaries inside Windows installers (.msi) after-the-fact.  Instead,
produce enough artifacts from the build job to sign and package release
binaries manually.

Port build settings from `Utilities/Release/win/x86/Dockerfile` and its
helper scripts.
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7917803..44ea456 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,6 +16,7 @@
     - build
     - test
     - test-ext
+    - package
     - upload
 
 ################################################################################
@@ -36,6 +37,7 @@
 
 # Job prefixes:
 #   - `b:` build
+#   - `k:` package
 #   - `l:` lint
 #   - `p:` prep
 #   - `t:` test
@@ -983,3 +985,69 @@
         - t:windows-vs2022-x64-ninja
     variables:
         CMAKE_CI_JOB_NIGHTLY: "true"
+
+b:windows-x86_64-package:
+    extends:
+        - .windows_x86_64_package
+        - .cmake_build_windows
+        - .cmake_build_package_artifacts
+        - .windows_tags_nonconcurrent_vs2022
+        - .run_only_for_package
+    dependencies:
+        - p:doc-package
+    needs:
+        - p:doc-package
+
+k:windows-x86_64-package:
+    extends:
+        - .windows_x86_64_package
+        - .cmake_package_windows
+        - .cmake_release_artifacts
+        - .windows_tags_nonconcurrent_vs2022
+        - .run_only_for_package
+    dependencies:
+        - b:windows-x86_64-package
+    needs:
+        - b:windows-x86_64-package
+
+u:windows-x86_64-package:
+    extends:
+        - .rsync_upload_package
+        - .run_only_for_package
+    dependencies:
+        - k:windows-x86_64-package
+    needs:
+        - k:windows-x86_64-package
+
+b:windows-i386-package:
+    extends:
+        - .windows_i386_package
+        - .cmake_build_windows
+        - .cmake_build_package_artifacts
+        - .windows_tags_nonconcurrent_vs2022
+        - .run_only_for_package
+    dependencies:
+        - p:doc-package
+    needs:
+        - p:doc-package
+
+k:windows-i386-package:
+    extends:
+        - .windows_i386_package
+        - .cmake_package_windows
+        - .cmake_release_artifacts
+        - .windows_tags_nonconcurrent_vs2022
+        - .run_only_for_package
+    dependencies:
+        - b:windows-i386-package
+    needs:
+        - b:windows-i386-package
+
+u:windows-i386-package:
+    extends:
+        - .rsync_upload_package
+        - .run_only_for_package
+    dependencies:
+        - k:windows-i386-package
+    needs:
+        - k:windows-i386-package
diff --git a/.gitlab/artifacts.yml b/.gitlab/artifacts.yml
index c3f4418..57ae573 100644
--- a/.gitlab/artifacts.yml
+++ b/.gitlab/artifacts.yml
@@ -63,6 +63,34 @@
             - build/DartConfiguation.tcl
             - build/CTestCustom.cmake
 
+.cmake_build_package_artifacts:
+    artifacts:
+        expire_in: 1d
+        paths:
+            # Allow CPack to find CMAKE_ROOT.
+            - build/CMakeFiles/CMakeSourceDir.txt
+
+            # Install rules.
+            - build/**/cmake_install.cmake
+
+            # We need the main binaries.
+            - build/bin/
+
+            # Pass through the documentation.
+            - build/install-doc/
+
+            # CPack configuration.
+            - build/CPackConfig.cmake
+            - build/CMakeCPackOptions.cmake
+            - build/Source/QtDialog/QtDialogCPack.cmake
+
+            # CPack/IFW packaging files.
+            - build/CMake*.qs
+
+            # CPack/WIX packaging files.
+            - build/Utilities/Release/WiX/custom_action_dll*.wxs
+            - build/Utilities/Release/WiX/CustomAction/CMakeWiXCustomActions.*
+
 .cmake_release_artifacts:
     artifacts:
         expire_in: 5d
@@ -73,6 +101,8 @@
             - build/cmake-*-linux-x86_64.*
             - build/cmake-*-linux-aarch64.*
             - build/cmake-*-macos*-universal.*
+            - build/cmake-*-windows-x86_64.*
+            - build/cmake-*-windows-i386.*
             # Any source packages made.
             - build/cmake-*.tar.gz
             - build/cmake-*.zip
diff --git a/.gitlab/ci/configure_windows_i386_package.cmake b/.gitlab/ci/configure_windows_i386_package.cmake
new file mode 100644
index 0000000..279f5cf
--- /dev/null
+++ b/.gitlab/ci/configure_windows_i386_package.cmake
@@ -0,0 +1,9 @@
+# CPack package file name component for this platform.
+set(CPACK_SYSTEM_NAME "windows-i386" CACHE STRING "")
+
+# Use APIs from at most Windows 7
+set(CMAKE_C_FLAGS "-D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "")
+set(CMAKE_CXX_FLAGS "-GR -EHsc -D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "")
+set(CMAKE_EXE_LINKER_FLAGS "-machine:x86 -subsystem:console,6.01" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_package_common.cmake")
diff --git a/.gitlab/ci/configure_windows_package_common.cmake b/.gitlab/ci/configure_windows_package_common.cmake
new file mode 100644
index 0000000..cea0ba0
--- /dev/null
+++ b/.gitlab/ci/configure_windows_package_common.cmake
@@ -0,0 +1,35 @@
+set(CMake_DOC_ARTIFACT_PREFIX "$ENV{CI_PROJECT_DIR}/build/install-doc" CACHE PATH "")
+
+# Set up install destinations as expected by the packaging scripts.
+set(CMAKE_DOC_DIR "doc/cmake" CACHE STRING "")
+
+# Link C/C++ runtime library statically.
+set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "")
+
+# Enable cmake-gui with static qt plugins
+set(BUILD_QtDialog "TRUE" CACHE BOOL "")
+set(CMake_GUI_DISTRIBUTE_WITH_Qt_LGPL "3" CACHE STRING "")
+set(qt "$ENV{CI_PROJECT_DIR}/.gitlab/qt")
+set(CMake_QT_STATIC_QWindowsIntegrationPlugin_LIBRARIES
+  ${qt}/plugins/platforms/qwindows.lib
+  ${qt}/plugins/styles/qwindowsvistastyle.lib
+  ${qt}/lib/Qt5EventDispatcherSupport.lib
+  ${qt}/lib/Qt5FontDatabaseSupport.lib
+  ${qt}/lib/Qt5ThemeSupport.lib
+  ${qt}/lib/qtfreetype.lib
+  ${qt}/lib/qtlibpng.lib
+  imm32.lib
+  wtsapi32.lib
+  CACHE STRING "")
+set(CMAKE_PREFIX_PATH "${qt}" CACHE STRING "")
+
+# Disable ccmake.
+set(BUILD_CursesDialog "OFF" CACHE BOOL "")
+
+set(CMAKE_SKIP_BOOTSTRAP_TEST "TRUE" CACHE STRING "")
+set(CMake_TEST_Java OFF CACHE BOOL "")
+set(CMake_TEST_Qt5 OFF CACHE BOOL "")
+set(CMake_TEST_Qt6 OFF CACHE BOOL "")
+set(Python_FIND_REGISTRY NEVER CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_common.cmake")
diff --git a/.gitlab/ci/configure_windows_x86_64_package.cmake b/.gitlab/ci/configure_windows_x86_64_package.cmake
new file mode 100644
index 0000000..b7bba85
--- /dev/null
+++ b/.gitlab/ci/configure_windows_x86_64_package.cmake
@@ -0,0 +1,9 @@
+# CPack package file name component for this platform.
+set(CPACK_SYSTEM_NAME "windows-x86_64" CACHE STRING "")
+
+# Use APIs from at most Windows 7
+set(CMAKE_C_FLAGS "-D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "")
+set(CMAKE_CXX_FLAGS "-GR -EHsc -D_WIN32_WINNT=0x601 -DNTDDI_VERSION=0x06010000" CACHE STRING "")
+set(CMAKE_EXE_LINKER_FLAGS "-machine:x64 -subsystem:console,6.01" CACHE STRING "")
+
+include("${CMAKE_CURRENT_LIST_DIR}/configure_windows_package_common.cmake")
diff --git a/.gitlab/ci/download_qt.cmake b/.gitlab/ci/download_qt.cmake
index 5940a28..90c2187 100644
--- a/.gitlab/ci/download_qt.cmake
+++ b/.gitlab/ci/download_qt.cmake
@@ -11,7 +11,19 @@
 
 # Files needed to download.
 set(qt_files)
-if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows")
+if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows.*package")
+  if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows_x86_64_package")
+    list(APPEND qt_files "qt-5.12.1-win-x86_64-msvc_v142-1.zip")
+    set(qt_subdir "qt-5.12.1-win-x86_64-msvc_v142-1")
+  elseif ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows_i386_package")
+    list(APPEND qt_files "qt-5.12.1-win-i386-msvc_v142-1.zip")
+    set(qt_subdir "qt-5.12.1-win-i386-msvc_v142-1")
+  else ()
+    message(FATAL_ERROR "Unknown arch to use for Qt")
+  endif()
+  set(qt_url_root "https://cmake.org/files/dependencies")
+  set(qt_url_path "")
+elseif ("$ENV{CMAKE_CONFIGURATION}" MATCHES "windows")
   # Determine the ABI to fetch for Qt.
   if ("$ENV{CMAKE_CONFIGURATION}" MATCHES "vs2015")
     set(qt_platform "windows_x86")
diff --git a/.gitlab/ci/download_qt_hashes.cmake b/.gitlab/ci/download_qt_hashes.cmake
index afbc081..d19d284 100644
--- a/.gitlab/ci/download_qt_hashes.cmake
+++ b/.gitlab/ci/download_qt_hashes.cmake
@@ -12,3 +12,6 @@
 
 set("qt-5.9.9-macosx10.10-x86_64-arm64.tar.xz_hash" d4449771afa0bc6a809c14f1e6d939e7732494cf059503ae451e2bfe8fc60cc1)
 set("qt-5.15.2-macosx10.13-x86_64-arm64.tar.xz_hash" 7b9463a01c8beeee5bf8d01c70deff2d08561cd20aaf6f7a2f41cf8b68ce8a6b)
+
+set("qt-5.12.1-win-i386-msvc_v142-1.zip_hash" aa78711fdaa5d9b146bf7ddcf15983f9fbb3f995462f2d043f8cca74b40ddd11)
+set("qt-5.12.1-win-x86_64-msvc_v142-1.zip_hash" c2fc068b9dac40bb420e28e1ee15ce4f2ccfc866d767f3b99b6bb435b7c4f44b)
diff --git a/.gitlab/os-windows.yml b/.gitlab/os-windows.yml
index 4b4656a..e5febbe 100644
--- a/.gitlab/os-windows.yml
+++ b/.gitlab/os-windows.yml
@@ -37,6 +37,12 @@
         VCVARSPLATFORM: "x64"
         VCVARSVERSION: "14.32.31326"
 
+.windows_vcvarsall_vs2022_x86:
+    variables:
+        VCVARSALL: "${VS170COMNTOOLS}\\..\\..\\VC\\Auxiliary\\Build\\vcvarsall.bat"
+        VCVARSPLATFORM: "x86"
+        VCVARSVERSION: "14.32.31326"
+
 .windows_vs2022_x64_ninja:
     extends:
         - .windows_build_ninja
@@ -45,6 +51,30 @@
     variables:
         CMAKE_CONFIGURATION: windows_vs2022_x64_ninja
 
+.windows_package:
+    extends:
+        - .windows_build_ninja
+
+    variables:
+        CMAKE_CI_BUILD_TYPE: Release
+        CMAKE_CI_NO_INSTALL: 1
+
+.windows_x86_64_package:
+    extends:
+        - .windows_package
+        - .windows_vcvarsall_vs2022_x64
+
+    variables:
+        CMAKE_CONFIGURATION: windows_x86_64_package
+
+.windows_i386_package:
+    extends:
+        - .windows_package
+        - .windows_vcvarsall_vs2022_x86
+
+    variables:
+        CMAKE_CONFIGURATION: windows_i386_package
+
 ### External testing
 
 .windows_vs2022_x64:
@@ -236,6 +266,18 @@
 
     interruptible: true
 
+.cmake_package_windows:
+    stage: package
+
+    script:
+        - *before_script_windows
+        - Invoke-Expression -Command .gitlab/ci/vcvarsall.ps1
+        - cd build
+        - cpack -G ZIP
+        - cpack -G WIX
+
+    interruptible: true
+
 .cmake_test_windows:
     stage: test
 
diff --git a/.gitlab/rules.yml b/.gitlab/rules.yml
index 8efa304..8fc40a7f 100644
--- a/.gitlab/rules.yml
+++ b/.gitlab/rules.yml
@@ -70,7 +70,7 @@
           when: on_success
         - if: '$CMAKE_CI_PACKAGE != null && $CI_JOB_STAGE == "prep"'
           when: manual
-        - if: '$CMAKE_CI_PACKAGE != null && $CI_JOB_STAGE != "upload"'
+        - if: '$CMAKE_CI_PACKAGE != null && $CI_JOB_STAGE != "package" && $CI_JOB_STAGE != "upload"'
           when: on_success
         - when: never
 
diff --git a/CMakeCPackOptions.cmake.in b/CMakeCPackOptions.cmake.in
index 81dfeee..f354362 100644
--- a/CMakeCPackOptions.cmake.in
+++ b/CMakeCPackOptions.cmake.in
@@ -248,6 +248,13 @@
 
   set(CPACK_WIX_LIGHT_EXTRA_FLAGS "-dcl:high")
 
+  if(NOT "$ENV{CMAKE_CI_PACKAGE}" STREQUAL "")
+    # Suppress validation.  It does not work without
+    # an interactive session or an admin account.
+    # https://github.com/wixtoolset/issues/issues/3968
+    list(APPEND CPACK_WIX_LIGHT_EXTRA_FLAGS "-sval")
+  endif()
+
   set(CPACK_WIX_UI_BANNER
     "@CMake_SOURCE_DIR@/Utilities/Release/WiX/ui_banner.jpg"
   )