diff --git a/hadrian/src/Packages.hs b/hadrian/src/Packages.hs
index 9ca18f14c3306957b4d8f6849a0a334a832ec7df..d1b49c35b1789decd06189596795d5cdfa33f999 100644
--- a/hadrian/src/Packages.hs
+++ b/hadrian/src/Packages.hs
@@ -5,7 +5,7 @@ module Packages (
     checkExact, countDeps,
     compareSizes, compiler, containers, deepseq, deriveConstants, directory,
     exceptions, filepath, genapply, genprimopcode, ghc, ghcBignum, ghcBoot, ghcBootTh,
-    ghcCompact, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline,
+    ghcCompact, ghcConfig, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline,
     hsc2hs, hp2ps, hpc, hpcBin, integerGmp, integerSimple, iserv, iservProxy,
     libffi, libiserv, mtl, parsec, pretty, primitive, process, remoteIserv, rts,
     runGhc, stm, templateHaskell, terminfo, text, time, timeout, touchy,
@@ -37,7 +37,7 @@ ghcPackages =
     [ array, base, binary, bytestring, cabalSyntax, cabal, checkPpr, checkExact, countDeps
     , compareSizes, compiler, containers, deepseq, deriveConstants, directory
     , exceptions, filepath, genapply, genprimopcode, ghc, ghcBignum, ghcBoot, ghcBootTh
-    , ghcCompact, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline, hsc2hs
+    , ghcCompact, ghcConfig, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline, hsc2hs
     , hp2ps, hpc, hpcBin, integerGmp, integerSimple, iserv, libffi, libiserv, mtl
     , parsec, pretty, process, rts, runGhc, stm, templateHaskell
     , terminfo, text, time, touchy, transformers, unlit, unix, win32, xhtml
@@ -53,7 +53,7 @@ isGhcPackage = (`elem` ghcPackages)
 array, base, binary, bytestring, cabalSyntax, cabal, checkPpr, checkExact, countDeps,
   compareSizes, compiler, containers, deepseq, deriveConstants, directory,
   exceptions, filepath, genapply, genprimopcode, ghc, ghcBignum, ghcBoot, ghcBootTh,
-  ghcCompact, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline, hsc2hs,
+  ghcCompact, ghcConfig, ghcHeap, ghci, ghciWrapper, ghcPkg, ghcPrim, haddock, haskeline, hsc2hs,
   hp2ps, hpc, hpcBin, integerGmp, integerSimple, iserv, iservProxy, remoteIserv, libffi, libiserv, mtl,
   parsec, pretty, primitive, process, rts, runGhc, stm, templateHaskell,
   terminfo, text, time, touchy, transformers, unlit, unix, win32, xhtml,
@@ -84,6 +84,7 @@ ghcBignum           = lib  "ghc-bignum"
 ghcBoot             = lib  "ghc-boot"
 ghcBootTh           = lib  "ghc-boot-th"
 ghcCompact          = lib  "ghc-compact"
+ghcConfig           = prg  "ghc-config"      `setPath` "testsuite/ghc-config"
 ghcHeap             = lib  "ghc-heap"
 ghci                = lib  "ghci"
 ghciWrapper         = prg  "ghci-wrapper"    `setPath` "driver/ghci"
diff --git a/hadrian/src/Rules/Test.hs b/hadrian/src/Rules/Test.hs
index f7d3a6c88305360b9e10fedac58be3d150c7638b..a1bc0612ef133ff9520ddb3a6a409eedd9383f75 100644
--- a/hadrian/src/Rules/Test.hs
+++ b/hadrian/src/Rules/Test.hs
@@ -21,12 +21,6 @@ import Utilities
 import Context.Type
 import qualified System.Directory as IO
 
-ghcConfigHsPath :: FilePath
-ghcConfigHsPath = "testsuite/mk/ghc-config.hs"
-
-ghcConfigProgPath :: FilePath
-ghcConfigProgPath = "test/bin/ghc-config" <.> exe
-
 checkPprProgPath, checkPprSourcePath :: FilePath
 checkPprProgPath = "test/bin/check-ppr" <.> exe
 checkPprSourcePath = "utils/check-ppr/Main.hs"
@@ -109,13 +103,6 @@ testRules = do
 
     testsuiteDeps
 
-    -- Using program shipped with testsuite to generate ghcconfig file.
-    root -/- ghcConfigProgPath %> \_ -> do
-        ghc0Path <- getCompilerPath "stage0"
-        -- Invoke via bash to work around #17362.
-        -- Reasons why this is required are not entirely clear.
-        cmd ["bash"] ["-c", ghc0Path ++ " " ++ ghcConfigHsPath ++ " -o " ++ (root -/- ghcConfigProgPath)]
-
     -- we need to create wrappers to test the stage1 compiler
     -- as the stage1 compiler needs the stage2 libraries
     -- to have any hope of passing tests.
@@ -179,11 +166,10 @@ testRules = do
         ghcPath <- getCompilerPath testGhc
         whenJust (stageOf testGhc) $ \stg ->
           need . (:[]) =<< programPath (Context stg ghc vanilla)
+        ghcConfigProgPath <- programPath =<< programContext Stage0 ghcConfig
         cwd <- liftIO $ IO.getCurrentDirectory
-        need [makeRelative cwd ghcPath]
-        need [root -/- ghcConfigProgPath]
-        cmd [FileStdout $ root -/- ghcConfigPath] (root -/- ghcConfigProgPath)
-            [ghcPath]
+        need [makeRelative cwd ghcPath, ghcConfigProgPath]
+        cmd [FileStdout $ root -/- ghcConfigPath] ghcConfigProgPath [ghcPath]
 
     root -/- timeoutPath %> \_ -> timeoutProgBuilder
 
diff --git a/hadrian/src/Settings/Default.hs b/hadrian/src/Settings/Default.hs
index 5e2c5f54f72d6ee2dff7ab6b81ae89fdcbf95c03..3b3498461c06dbc9a5b3cb18af478af34f318227 100644
--- a/hadrian/src/Settings/Default.hs
+++ b/hadrian/src/Settings/Default.hs
@@ -157,7 +157,7 @@ stage2Packages = stage1Packages
 
 -- | Packages that are built only for the testsuite.
 testsuitePackages :: Action [Package]
-testsuitePackages = return ([ timeout | windowsHost ] ++ [ checkPpr, checkExact, countDeps ])
+testsuitePackages = return ([ timeout | windowsHost ] ++ [ checkPpr, checkExact, countDeps, ghcConfig ])
 
 -- | Default build ways for library packages:
 -- * We always build 'vanilla' way.
diff --git a/testsuite/ghc-config/ghc-config.cabal b/testsuite/ghc-config/ghc-config.cabal
new file mode 100644
index 0000000000000000000000000000000000000000..017cbea8de394a8825719901fb59f33872e54c6a
--- /dev/null
+++ b/testsuite/ghc-config/ghc-config.cabal
@@ -0,0 +1,11 @@
+cabal-version:      2.4
+name:               ghc-config
+version:            0.1.0.0
+synopsis:           A utility used by GHC's testsuite driver to extract information from @ghc --info@.
+author:             The GHC Developers
+maintainer:         ghc-devs@haskell.org
+
+executable ghc-config
+    main-is:          ghc-config.hs
+    build-depends:    base, process
+    default-language: Haskell2010
diff --git a/testsuite/mk/ghc-config.hs b/testsuite/ghc-config/ghc-config.hs
similarity index 99%
rename from testsuite/mk/ghc-config.hs
rename to testsuite/ghc-config/ghc-config.hs
index f12b579e8dc84f4daee78f9fe64f8488c9264942..efb88f81f25f5f5b34aae17dc1a1615b56ca2ca2 100644
--- a/testsuite/mk/ghc-config.hs
+++ b/testsuite/ghc-config/ghc-config.hs
@@ -2,6 +2,7 @@ import System.Environment
 import System.Process
 import Data.Maybe
 
+main :: IO ()
 main = do
   [ghc] <- getArgs
 
diff --git a/testsuite/mk/boilerplate.mk b/testsuite/mk/boilerplate.mk
index df1b835b0cc3ce3423779c02e9c234861e65c1e8..8ce5dafb7bdfda0043cea37b737674eda199ca19 100644
--- a/testsuite/mk/boilerplate.mk
+++ b/testsuite/mk/boilerplate.mk
@@ -246,7 +246,7 @@ endif
 # the results, and emits a little .mk file with make bindings for the values.
 # This way we cache the results for different values of $(TEST_HC)
 
-$(TOP)/mk/ghc-config : $(TOP)/mk/ghc-config.hs
+$(TOP)/ghc-config/ghc-config : $(TOP)/ghc-config/ghc-config.hs
 	"$(TEST_HC)" --make -o $@ $<
 
 empty=
@@ -254,8 +254,8 @@ space=$(empty) $(empty)
 ifeq "$(ghc_config_mk)" ""
 ghc_config_mk = $(TOP)/mk/ghcconfig$(subst $(space),_,$(subst :,_,$(subst /,_,$(subst \,_,$(TEST_HC))))).mk
 
-$(ghc_config_mk) : $(TOP)/mk/ghc-config
-	$(TOP)/mk/ghc-config "$(TEST_HC)" >"$@"; if [ "$$?" != "0" ]; then $(RM) "$@"; exit 1; fi
+$(ghc_config_mk) : $(TOP)/ghc-config/ghc-config
+	$(TOP)/ghc-config/ghc-config "$(TEST_HC)" >"$@"; if [ "$$?" != "0" ]; then $(RM) "$@"; exit 1; fi
 # If the ghc-config fails, remove $@, and fail
 endif