diff --git a/.gitlab/ci.sh b/.gitlab/ci.sh
index bfb13fcf5cec10392b68cd55e86637acadb346f0..82df36d3983cfd049eafe8e7fda0e4ba9ea1f3a7 100755
--- a/.gitlab/ci.sh
+++ b/.gitlab/ci.sh
@@ -58,6 +58,9 @@ Hadrian build system
 Environment variables affecting both build systems:
 
   CROSS_TARGET      Triple of cross-compilation target.
+  CROSS_STAGE       The stage of the cross-compiler to build either
+                      * 2: Build a normal cross-compiler bindist
+                      * 3: Build a target executable bindist (with the stage2 cross-compiler)
   VERBOSE           Set to non-empty for verbose build output
   RUNTEST_ARGS      Arguments passed to runtest.py
   MSYSTEM           (Windows-only) Which platform to build from (CLANG64).
@@ -487,6 +490,12 @@ function build_hadrian() {
     export XZ_OPT="${XZ_OPT:-} -T$cores"
   fi
 
+  case "${CROSS_STAGE:2}" in
+    2) BINDIST_TARGET="binary-dist";;
+    3) BINDIST_TARGET="binary-dist-stage3";;
+    *) fail "Unknown CROSS_STAGE, must be 2 or 3";;
+  esac
+
   if [[ -n "${REINSTALL_GHC:-}" ]]; then
     run_hadrian build-cabal -V
   else
@@ -496,7 +505,7 @@ function build_hadrian() {
           mv _build/reloc-bindist/ghc*.tar.xz "$BIN_DIST_NAME.tar.xz"
           ;;
         *)
-          run_hadrian test:all_deps binary-dist -V
+          run_hadrian test:all_deps $BINDIST_TARGET
           mv _build/bindist/ghc*.tar.xz "$BIN_DIST_NAME.tar.xz"
           ;;
     esac
diff --git a/.gitlab/generate-ci/gen_ci.hs b/.gitlab/generate-ci/gen_ci.hs
index 30230a9dfec5ec096fb24623540310ceb445cc10..8064e9e4525acf3883195fbaca498ed196a1665c 100644
--- a/.gitlab/generate-ci/gen_ci.hs
+++ b/.gitlab/generate-ci/gen_ci.hs
@@ -145,6 +145,7 @@ data BuildConfig
                 , withNuma       :: Bool
                 , withZstd       :: Bool
                 , crossTarget    :: Maybe String
+                , crossStage     :: Maybe Int
                 , crossEmulator  :: CrossEmulator
                 , configureWrapper :: Maybe String
                 , fullyStatic    :: Bool
@@ -159,7 +160,7 @@ configureArgsStr :: BuildConfig -> String
 configureArgsStr bc = unwords $
      ["--enable-unregisterised"| unregisterised bc ]
   ++ ["--disable-tables-next-to-code" | not (tablesNextToCode bc) ]
-  ++ ["--with-intree-gmp" | Just _ <- pure (crossTarget bc) ]
+  ++ ["--with-intree-gmp" | Just _ <- [crossTarget bc] ]
   ++ ["--with-system-libffi" | crossTarget bc == Just "wasm32-wasi" ]
   ++ ["--enable-ipe-data-compression" | withZstd bc ]
   ++ ["--enable-strict-ghc-toolchain-check"]
@@ -203,6 +204,7 @@ vanilla = BuildConfig
   , withNuma = False
   , withZstd = False
   , crossTarget = Nothing
+  , crossStage  = Nothing
   , crossEmulator = NoEmulator
   , configureWrapper = Nothing
   , fullyStatic = False
@@ -249,6 +251,7 @@ crossConfig :: String       -- ^ target triple
             -> BuildConfig
 crossConfig triple emulator configure_wrapper =
     vanilla { crossTarget = Just triple
+            , crossStage  = Just 2
             , crossEmulator = emulator
             , configureWrapper = configure_wrapper
             }
@@ -746,6 +749,7 @@ job arch opsys buildConfig = NamedJob { name = jobName, jobInfo = Job {..} }
       , "CONFIGURE_ARGS" =: configureArgsStr buildConfig
       , maybe mempty ("CONFIGURE_WRAPPER" =:) (configureWrapper buildConfig)
       , maybe mempty ("CROSS_TARGET" =:) (crossTarget buildConfig)
+      , maybe mempty (("CROSS_STAGE" =:) . show) (crossStage buildConfig)
       , case crossEmulator buildConfig of
           NoEmulator       -> case crossTarget buildConfig of
             Nothing -> mempty
diff --git a/.gitlab/jobs.yaml b/.gitlab/jobs.yaml
index 43950ecc9a9b334997f54df0b16f56a659fa24c5..def2f0aff19895739461f839e67bb908638148c3 100644
--- a/.gitlab/jobs.yaml
+++ b/.gitlab/jobs.yaml
@@ -951,6 +951,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -1015,6 +1016,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-int_native-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -1079,6 +1081,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-unreg-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--enable-unregisterised --with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -1768,6 +1771,7 @@
       "BUILD_FLAVOUR": "validate",
       "CONFIGURE_ARGS": "--with-intree-gmp --enable-strict-ghc-toolchain-check",
       "CROSS_EMULATOR": "qemu-aarch64 -L /usr/aarch64-linux-gnu",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "aarch64-linux-gnu",
       "RUNTEST_ARGS": "",
       "TEST_ENV": "x86_64-linux-deb11-cross_aarch64-linux-gnu-validate",
@@ -1833,6 +1837,7 @@
       "CONFIGURE_ARGS": "--with-intree-gmp --enable-strict-ghc-toolchain-check",
       "CONFIGURE_WRAPPER": "emconfigure",
       "CROSS_EMULATOR": "js-emulator",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "javascript-unknown-ghcjs",
       "RUNTEST_ARGS": "",
       "TEST_ENV": "x86_64-linux-deb11-int_native-cross_javascript-unknown-ghcjs-validate",
@@ -4526,6 +4531,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -4590,6 +4596,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-int_native-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -4654,6 +4661,7 @@
       "BIN_DIST_NAME": "ghc-x86_64-linux-alpine3_17-wasm-unreg-cross_wasm32-wasi-release+fully_static",
       "BUILD_FLAVOUR": "release+fully_static",
       "CONFIGURE_ARGS": "--enable-unregisterised --with-intree-gmp --with-system-libffi --enable-strict-ghc-toolchain-check",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "wasm32-wasi",
       "HADRIAN_ARGS": "--docs=none",
       "RUNTEST_ARGS": "",
@@ -5211,6 +5219,7 @@
       "BUILD_FLAVOUR": "validate",
       "CONFIGURE_ARGS": "--with-intree-gmp --enable-strict-ghc-toolchain-check",
       "CROSS_EMULATOR": "qemu-aarch64 -L /usr/aarch64-linux-gnu",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "aarch64-linux-gnu",
       "RUNTEST_ARGS": "",
       "TEST_ENV": "x86_64-linux-deb11-cross_aarch64-linux-gnu-validate"
@@ -5275,6 +5284,7 @@
       "CONFIGURE_ARGS": "--with-intree-gmp --enable-strict-ghc-toolchain-check",
       "CONFIGURE_WRAPPER": "emconfigure",
       "CROSS_EMULATOR": "js-emulator",
+      "CROSS_STAGE": "2",
       "CROSS_TARGET": "javascript-unknown-ghcjs",
       "RUNTEST_ARGS": "",
       "TEST_ENV": "x86_64-linux-deb11-int_native-cross_javascript-unknown-ghcjs-validate"
diff --git a/hadrian/src/Rules/BinaryDist.hs b/hadrian/src/Rules/BinaryDist.hs
index e888f9ca409f91311d51c5907b9c1179bb00e381..efb8e2482f2be86297c49a5560b08e1b0a738554 100644
--- a/hadrian/src/Rules/BinaryDist.hs
+++ b/hadrian/src/Rules/BinaryDist.hs
@@ -330,6 +330,7 @@ bindistRules = do
       if cross
         then need ["binary-dist-dir-cross"]
         else buildBinDistDir root normalBindist
+
     phony "binary-dist-dir-cross" $ buildBinDistDir root crossBindist
     phony "binary-dist-dir-stage3" $ buildBinDistDir root targetBindist
 
@@ -361,6 +362,10 @@ bindistRules = do
       phony (name <> "-dist-bzip2") $ mk_bindist Bzip2
       phony (name <> "-dist-xz") $ mk_bindist Xz
 
+    -- TODO: Generate these targets as well
+    phony ("binary-dist-cross") $ buildBinDistX "binary-dist-dir-cross" "bindist" Xz
+    phony ("binary-dist-stage3") $ buildBinDistX "binary-dist-dir-stage3" "bindist" Xz
+
     -- Prepare binary distribution configure script
     -- (generated under <ghc root>/distrib/configure by 'autoreconf')
     root -/- "bindist" -/- "ghc-*" -/- "configure" %> \configurePath -> do