{-# LANGUAGE CPP #-}

module Distribution.Compat.Process
  ( -- * Redefined functions
    proc

    -- * Additions
  , enableProcessJobs
  ) where

import System.Process (CreateProcess)
import qualified System.Process as Process

#if defined(mingw32_HOST_OS) && MIN_VERSION_process(1,6,9)
import           System.IO.Unsafe (unsafePerformIO)
import           System.Win32.Info.Version (dwMajorVersion, dwMinorVersion, getVersionEx)
#endif

-------------------------------------------------------------------------------
-- enableProcessJobs
-------------------------------------------------------------------------------

#if defined(mingw32_HOST_OS) && MIN_VERSION_process(1,6,9)
-- This exception, needed to support Windows 7, could be removed when
-- the lowest GHC version cabal supports is a GHC that doesn’t support
-- Windows 7 any more.
{-# NOINLINE isWindows8OrLater #-}
isWindows8OrLater :: Bool
isWindows8OrLater = unsafePerformIO $ do
  v <- getVersionEx
  pure $ (dwMajorVersion v, dwMinorVersion v) >= (6, 2)
#endif

-- | Enable process jobs to ensure accurate determination of process completion
-- in the presence of @exec(3)@ on Windows.
--
-- Unfortunately the process job support is badly broken in @process@ releases
-- prior to 1.6.9, so we disable it in these versions, despite the fact that
-- this means we may see sporadic build failures without jobs.
--
-- On Windows 7 or before the jobs are disabled due to the fact that
-- processes on these systems can only have one job. This prevents
-- spawned process from assigning jobs to its own children. Suppose
-- process A spawns process B. The B process has a job assigned (call
-- it J1) and when it tries to spawn a new process C the C
-- automatically inherits the job. But at it also tries to assign a
-- new job J2 to C since it doesn’t have access J1. This fails on
-- Windows 7 or before.
enableProcessJobs :: CreateProcess -> CreateProcess
#if defined(mingw32_HOST_OS) && MIN_VERSION_process(1,6,9)
enableProcessJobs cp = cp {Process.use_process_jobs = isWindows8OrLater}
#else
enableProcessJobs :: CreateProcess -> CreateProcess
enableProcessJobs CreateProcess
cp = CreateProcess
cp
#endif

-------------------------------------------------------------------------------
-- process redefinitions
-------------------------------------------------------------------------------

-- | 'System.Process.proc' with process jobs enabled when appropriate,
-- and defaulting 'delegate_ctlc' to 'True'.
proc :: FilePath -> [String] -> CreateProcess
proc :: FilePath -> [FilePath] -> CreateProcess
proc FilePath
path [FilePath]
args = CreateProcess -> CreateProcess
enableProcessJobs (FilePath -> [FilePath] -> CreateProcess
Process.proc FilePath
path [FilePath]
args){Process.delegate_ctlc = True}