packages feed

hs-bindgen-1.0.0.0: src-internal/HsBindgen/Clang/Macos.hs

module HsBindgen.Clang.Macos (
    -- * Types
    SdkPath
    -- * Trace messages
  , MacosMsg(..)
    -- * API
  , checkMacosEnv
  ) where

import Control.Exception (Exception (displayException))
import Control.Monad ((<=<))
import System.Environment qualified as Env
import System.Info qualified
import System.Process qualified as Proc
import Text.SimplePrettyPrint ((<+>))
import Text.SimplePrettyPrint qualified as PP

import HsBindgen.Imports
import HsBindgen.Util.Process
import HsBindgen.Util.Tracer

{-------------------------------------------------------------------------------
  Types
-------------------------------------------------------------------------------}

-- | SDK path
type SdkPath = FilePath

{-------------------------------------------------------------------------------
  Trace messages
-------------------------------------------------------------------------------}

data MacosMsg =
    MacosEnvAlreadySet SdkPath
  | MacosEnvNotSet
  | MacosXcrunRun
  | MacosXcrunUnexpected String
  | MacosXcrunIOError IOError
  | MacosEnvSet SdkPath
  deriving stock (Show)

instance PrettyForTrace MacosMsg where
  prettyForTrace = \case
    MacosEnvAlreadySet path ->
      PP.string envName <+> "already set:" <+> PP.string (show path)
    MacosEnvNotSet ->
      PP.string envName <+> "not set"
    MacosXcrunRun ->
      PP.hang ("Running xcrun to determine" <+> PP.string envName) 2 $
        "Command:" <+> cmdSpecPrettyForTrace xcrunCmd
    MacosXcrunUnexpected s ->
      PP.hangs ("Unable to determine" <+> PP.string envName) 2 [
          "Command:" <+> cmdSpecPrettyForTrace xcrunCmd
        , "Unexpected output:" <+> PP.string (show s)
        ]
    MacosXcrunIOError e ->
      PP.hangs ("Unable to determine" <+> PP.string envName) 2 [
          "Command:" <+> cmdSpecPrettyForTrace xcrunCmd
        , "IO error:" <+> PP.string (displayException e)
        ]
    MacosEnvSet path ->
      "Setting"
        <+> PP.string (envName ++ ":")
        <+> PP.string (show path)

instance IsTrace Level MacosMsg where
  getDefaultLogLevel = \case
    MacosEnvAlreadySet{}   -> Debug
    MacosEnvNotSet         -> Debug
    MacosXcrunRun          -> Info
    MacosXcrunUnexpected{} -> Warning
    MacosXcrunIOError{}    -> Warning
    MacosEnvSet{}          -> Notice

  getSource  = const HsBindgen
  getTraceId = const "macos"

{-------------------------------------------------------------------------------
  API
-------------------------------------------------------------------------------}

-- | Check @SDKROOT@ environment variable on macOS
--
-- If @SDKROOT@ is not set, attempt to determine it with @xcrun@ and set it when
-- successful.
checkMacosEnv :: Tracer MacosMsg -> IO ()
checkMacosEnv tracer = when (System.Info.os == "darwin") $
    getEnv tracer >>= \case
      Just{}  -> return ()
      Nothing -> getXcrunSdkPath tracer >>= \case
        Nothing   -> return ()
        Just path -> setEnv tracer path

{-------------------------------------------------------------------------------
  Auxiliary functions
-------------------------------------------------------------------------------}

envName :: String
envName = "SDKROOT"

getEnv :: Tracer MacosMsg -> IO (Maybe SdkPath)
getEnv tracer = do
    mx <- Env.lookupEnv envName
    traceWith tracer $ withCallStack $ maybe MacosEnvNotSet MacosEnvAlreadySet mx
    return mx

setEnv :: Tracer MacosMsg -> SdkPath -> IO ()
setEnv tracer path = do
    traceWith tracer (withCallStack $ MacosEnvSet path)
    Env.setEnv envName path

--------------------------------------------------------------------------------

xcrunCmd :: Proc.CmdSpec
xcrunCmd = Proc.RawCommand "xcrun" ["--sdk", "macosx", "--show-sdk-path"]

getXcrunSdkPath :: Tracer MacosMsg -> IO (Maybe SdkPath)
getXcrunSdkPath tracer = do
    traceWith tracer (withCallStack MacosXcrunRun)
    checkOutput
      tracer
      MacosXcrunUnexpected
      MacosXcrunIOError
      (parseNonEmpty <=< parseSingleLine)
      (cmdSpecReadAction xcrunCmd)