-- This file is part of Hoppy.
--
-- Copyright 2015-2021 Bryan Gardiner <bog@khumba.net>
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

{-# LANGUAGE CPP #-}

-- | Implementations of Cabal setup programs for use in packages of generated
-- bindings.
--
-- Much like the default Setup.hs that Cabal recommends for packages,
--
-- @
-- import "Distribution.Simple"
-- main = 'Distribution.Simple.defaultMain'
-- @
--
-- this module provides simplified configuration of packages for generated
-- bindings.  Suppose you have a project named foobar that is composed of Cabal
-- packages named \"foobar-generator\" for the code generator, \"foobar-cpp\"
-- for the C++ gateway, and \"foobar\" for the Haskell gateway.  The C++ gateway
-- package can use the following code:
--
-- @
-- import "Foreign.Hoppy.Setup" ('ProjectConfig' (..), 'cppMain')
--
-- main =
--   cppMain $
--   ProjectConfig
--   { generatorExecutableName = \"foobar-generator\"
--   , cppPackageName = \"foobar-cpp\"
--   , cppSourcesDir = \"cpp\"
--   , hsSourcesDir = \"src\"
--   }
-- @
--
-- The Haskell gateway uses the same code, except calling 'hsMain' instead of
-- 'cppMain'.  This causes C++ sources to be generated in @foobar-cpp\/cpp@ and
-- (assuming the Haskell gateway is at @foobar\/@) the Haskell sources to be
-- generated in @foobar\/src@.
--
-- The gateway packages need to set @build-type: Custom@ in their @.cabal@ files
-- to use these setup files.
module Foreign.Hoppy.Setup (
  ProjectConfig (..),
  cppMain,
  cppUserHooks,
  hsMain,
  hsUserHooks,
  ) where

import Control.Monad (unless, when)
import Data.List (isInfixOf)
import Distribution.InstalledPackageInfo (libraryDirs)
#if MIN_VERSION_Cabal(2,0,0)
import Distribution.Package (mkPackageName)
#else
import Distribution.Package (PackageName (PackageName))
#endif
import Distribution.PackageDescription (
  HookedBuildInfo,
  PackageDescription,
  emptyBuildInfo,
  extraLibDirs,
  )
import Distribution.Simple (defaultMainWithHooks, simpleUserHooks)
import Distribution.Simple.LocalBuildInfo (
  LocalBuildInfo,
  absoluteInstallDirs,
  buildDir,
  installedPkgs,
  libdir,
  withPrograms,
  )
import Distribution.Simple.PackageIndex (lookupPackageName)
import Distribution.Simple.Program (
  runDbProgram,
  runProgram,
  )
import Distribution.Simple.Program.Find (
  ProgramSearchPathEntry (ProgramSearchPathDefault),
  findProgramOnSearchPath,
  )
import Distribution.Simple.Program.Types (
  ConfiguredProgram,
  Program,
  ProgramLocation (FoundOnSystem),
  simpleConfiguredProgram,
  simpleProgram,
  )
import Distribution.Simple.Setup (
  CopyDest (CopyTo, NoCopyDest),
  RegisterFlags,
  buildVerbosity,
  cleanVerbosity,
  configVerbosity,
  copyDest,
  copyVerbosity,
  flagToMaybe,
  fromFlagOrDefault,
  installDistPref,
  installVerbosity,
  regInPlace,
  regVerbosity,
  )
import Distribution.Simple.UserHooks (
  UserHooks (
    buildHook,
    hookedPrograms,
    cleanHook,
    copyHook,
    instHook,
    regHook,
    postConf,
    preBuild,
    preCopy,
    preInst,
    preReg,
    preRepl,
    preTest
    ),
  )
#if MIN_VERSION_Cabal(2,0,0)
import Distribution.Simple.Utils (die')
#else
import Distribution.Simple.Utils (die)
#endif
import Distribution.Simple.Utils (installOrdinaryFile)
import Distribution.Verbosity (Verbosity, normal)
import Foreign.Hoppy.Generator.Main (run)
import Foreign.Hoppy.Generator.Spec (Interface)
import System.Directory (createDirectoryIfMissing, doesFileExist)
import System.FilePath ((</>), takeDirectory)

-- | Configuration parameters for a project using Hoppy.
data ProjectConfig = ProjectConfig
  { ProjectConfig -> Either String Interface
interfaceResult :: Either String Interface
    -- ^ The interface to run the generator with.  This result is returned from
    -- @Foreign.Hoppy.Generator.Spec.interface@ and some may have failed; the
    -- string is a message indicating the problem.
  , ProjectConfig -> String
cppPackageName :: String
    -- ^ The name of the C++ gateway package.
  , ProjectConfig -> String
cppSourcesDir :: FilePath
    -- ^ The directory into which to generate C++ sources, under the C++ gateway
    -- package root.
  , ProjectConfig -> String
hsSourcesDir :: FilePath
    -- ^ The directory into which to generate Haskell sources, under the Haskell
    -- gateway package root.
  }

-- | The name of the file we'll use to hold the enum evaluation cache.
enumEvalCacheFileName :: FilePath
enumEvalCacheFileName :: String
enumEvalCacheFileName = String
"hoppy-enum-eval-cache"

-- | Extracts the 'Interface' from a 'ProjectConfig', checking its
-- 'interfaceResult' and aborting the program if the result is unsuccessful.
getInterface :: ProjectConfig -> Verbosity -> IO Interface
getInterface :: ProjectConfig -> Verbosity -> IO Interface
getInterface ProjectConfig
project Verbosity
verbosity = case ProjectConfig -> Either String Interface
interfaceResult ProjectConfig
project of
  Left String
errorMsg ->
#if MIN_VERSION_Cabal(2,0,0)
    Verbosity -> String -> IO Interface
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity (String -> IO Interface) -> String -> IO Interface
forall a b. (a -> b) -> a -> b
$
#else
    die $
#endif
    String
"Error initializing interface: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
errorMsg
  Right Interface
iface -> Interface -> IO Interface
forall (m :: * -> *) a. Monad m => a -> m a
return Interface
iface

-- | A @main@ implementation to be used in the @Setup.hs@ of a C++ gateway
-- package.
--
-- @cppMain project = 'defaultMainWithHooks' $ 'cppUserHooks' project@
cppMain :: ProjectConfig -> IO ()
cppMain :: ProjectConfig -> IO ()
cppMain ProjectConfig
project = UserHooks -> IO ()
defaultMainWithHooks (UserHooks -> IO ()) -> UserHooks -> IO ()
forall a b. (a -> b) -> a -> b
$ ProjectConfig -> UserHooks
cppUserHooks ProjectConfig
project

-- | Cabal user hooks for a C++ gateway package.  When overriding fields in the
-- result, be sure to call the previous hook.
--
-- The following hooks are defined:
--
-- - 'postConf': Runs the generator program to generate C++ sources.  Checks if
-- a @configure@ script exists in the C++ gateway root, and calls it if so
-- (without arguments).
--
-- - 'buildHook': Runs @make@ with no arguments from the C++ gateway root.
--
-- - 'copyHook' and 'instHook': Runs @make install libdir=$libdir@ where
-- @$libdir@ is the directory into which to install the built shared library.
--
-- - 'cleanHook': Removes files created by the generator, then calls @make
-- clean@.
cppUserHooks :: ProjectConfig -> UserHooks
cppUserHooks :: ProjectConfig -> UserHooks
cppUserHooks ProjectConfig
project =
  UserHooks
simpleUserHooks
  { hookedPrograms :: [Program]
hookedPrograms = [Program
makeProgram]

  , postConf :: Args
-> ConfigFlags -> PackageDescription -> LocalBuildInfo -> IO ()
postConf = \Args
args ConfigFlags
flags PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo -> do
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ ConfigFlags -> Flag Verbosity
configVerbosity ConfigFlags
flags
      ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
cppConfigure ProjectConfig
project Verbosity
verbosity LocalBuildInfo
localBuildInfo
      UserHooks
-> Args
-> ConfigFlags
-> PackageDescription
-> LocalBuildInfo
-> IO ()
postConf UserHooks
simpleUserHooks Args
args ConfigFlags
flags PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo

  , buildHook :: PackageDescription
-> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
buildHook = \PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks BuildFlags
flags -> do
      UserHooks
-> PackageDescription
-> LocalBuildInfo
-> UserHooks
-> BuildFlags
-> IO ()
buildHook UserHooks
simpleUserHooks PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks BuildFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ BuildFlags -> Flag Verbosity
buildVerbosity BuildFlags
flags
      Verbosity -> LocalBuildInfo -> IO ()
cppBuild Verbosity
verbosity LocalBuildInfo
localBuildInfo

  , copyHook :: PackageDescription
-> LocalBuildInfo -> UserHooks -> CopyFlags -> IO ()
copyHook = \PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks CopyFlags
flags -> do
      UserHooks
-> PackageDescription
-> LocalBuildInfo
-> UserHooks
-> CopyFlags
-> IO ()
copyHook UserHooks
simpleUserHooks PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks CopyFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ CopyFlags -> Flag Verbosity
copyVerbosity CopyFlags
flags
          dest :: CopyDest
dest = CopyDest -> Flag CopyDest -> CopyDest
forall a. a -> Flag a -> a
fromFlagOrDefault CopyDest
NoCopyDest (Flag CopyDest -> CopyDest) -> Flag CopyDest -> CopyDest
forall a b. (a -> b) -> a -> b
$ CopyFlags -> Flag CopyDest
copyDest CopyFlags
flags
      Verbosity
-> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
cppInstall Verbosity
verbosity PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo CopyDest
dest

  , instHook :: PackageDescription
-> LocalBuildInfo -> UserHooks -> InstallFlags -> IO ()
instHook = \PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks InstallFlags
flags -> do
      UserHooks
-> PackageDescription
-> LocalBuildInfo
-> UserHooks
-> InstallFlags
-> IO ()
instHook UserHooks
simpleUserHooks PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks InstallFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ InstallFlags -> Flag Verbosity
installVerbosity InstallFlags
flags
          dest :: CopyDest
dest = CopyDest -> (String -> CopyDest) -> Maybe String -> CopyDest
forall b a. b -> (a -> b) -> Maybe a -> b
maybe CopyDest
NoCopyDest String -> CopyDest
CopyTo (Maybe String -> CopyDest) -> Maybe String -> CopyDest
forall a b. (a -> b) -> a -> b
$
                 Flag String -> Maybe String
forall a. Flag a -> Maybe a
flagToMaybe (Flag String -> Maybe String) -> Flag String -> Maybe String
forall a b. (a -> b) -> a -> b
$ InstallFlags -> Flag String
installDistPref InstallFlags
flags
      Verbosity
-> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
cppInstall Verbosity
verbosity PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo CopyDest
dest

  , regHook :: PackageDescription
-> LocalBuildInfo -> UserHooks -> RegisterFlags -> IO ()
regHook = \PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks RegisterFlags
flags -> do
      UserHooks
-> PackageDescription
-> LocalBuildInfo
-> UserHooks
-> RegisterFlags
-> IO ()
regHook UserHooks
simpleUserHooks PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo UserHooks
hooks RegisterFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ RegisterFlags -> Flag Verbosity
regVerbosity RegisterFlags
flags
      Verbosity -> LocalBuildInfo -> RegisterFlags -> IO ()
cppRegister Verbosity
verbosity LocalBuildInfo
localBuildInfo RegisterFlags
flags

  , cleanHook :: PackageDescription -> () -> UserHooks -> CleanFlags -> IO ()
cleanHook = \PackageDescription
pkgDesc ()
z UserHooks
hooks CleanFlags
flags -> do
      UserHooks
-> PackageDescription -> () -> UserHooks -> CleanFlags -> IO ()
cleanHook UserHooks
simpleUserHooks PackageDescription
pkgDesc ()
z UserHooks
hooks CleanFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ CleanFlags -> Flag Verbosity
cleanVerbosity CleanFlags
flags
      ProjectConfig -> Verbosity -> IO ()
cppClean ProjectConfig
project Verbosity
verbosity
  }

makeProgram :: Program
makeProgram :: Program
makeProgram = String -> Program
simpleProgram String
"make"

cppConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
cppConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
cppConfigure ProjectConfig
project Verbosity
verbosity LocalBuildInfo
localBuildInfo = do
  -- Invoke the generator to create C++ code.
  let sourcesDir :: String
sourcesDir = ProjectConfig -> String
cppSourcesDir ProjectConfig
project
  Interface
iface <- ProjectConfig -> Verbosity -> IO Interface
getInterface ProjectConfig
project Verbosity
verbosity
  Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
sourcesDir
  Bool -> String -> IO ()
createDirectoryIfMissing Bool
True (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ LocalBuildInfo -> String
buildDir LocalBuildInfo
localBuildInfo
  [Action]
_ <- [Interface] -> Args -> IO [Action]
run [Interface
iface]
           [ String
"--enum-eval-cache-mode", String
"refresh"
           , String
"--enum-eval-cache-path", LocalBuildInfo -> String
buildDir LocalBuildInfo
localBuildInfo String -> String -> String
</> String
enumEvalCacheFileName
           , String
"--gen-cpp", String
sourcesDir
           ]

  -- When there is a configure script, then run it.
  Maybe ConfiguredProgram
maybeConfigureProgram <- IO (Maybe ConfiguredProgram)
findConfigure
  case Maybe ConfiguredProgram
maybeConfigureProgram of
    Just ConfiguredProgram
configureProgram -> Verbosity -> ConfiguredProgram -> Args -> IO ()
runProgram Verbosity
verbosity ConfiguredProgram
configureProgram []
    Maybe ConfiguredProgram
Nothing -> () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

  where findConfigure :: IO (Maybe ConfiguredProgram)
        findConfigure :: IO (Maybe ConfiguredProgram)
findConfigure = do
          Bool
hasConfigure <- String -> IO Bool
doesFileExist String
"configure"
          Maybe ConfiguredProgram -> IO (Maybe ConfiguredProgram)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ConfiguredProgram -> IO (Maybe ConfiguredProgram))
-> Maybe ConfiguredProgram -> IO (Maybe ConfiguredProgram)
forall a b. (a -> b) -> a -> b
$ if Bool
hasConfigure
                   then ConfiguredProgram -> Maybe ConfiguredProgram
forall a. a -> Maybe a
Just (ConfiguredProgram -> Maybe ConfiguredProgram)
-> ConfiguredProgram -> Maybe ConfiguredProgram
forall a b. (a -> b) -> a -> b
$ String -> ProgramLocation -> ConfiguredProgram
simpleConfiguredProgram String
"configure" (ProgramLocation -> ConfiguredProgram)
-> ProgramLocation -> ConfiguredProgram
forall a b. (a -> b) -> a -> b
$ String -> ProgramLocation
FoundOnSystem String
"./configure"
                   else Maybe ConfiguredProgram
forall a. Maybe a
Nothing

cppBuild :: Verbosity -> LocalBuildInfo -> IO ()
cppBuild :: Verbosity -> LocalBuildInfo -> IO ()
cppBuild Verbosity
verbosity LocalBuildInfo
localBuildInfo = do
  Bool
hasMakefile <- String -> IO Bool
doesFileExist String
"Makefile"
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
hasMakefile (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
#if MIN_VERSION_Cabal(2,0,0)
    Verbosity -> String -> IO ()
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity
#else
    die
#endif
    String
"No Makefile found."
  let programDb :: ProgramDb
programDb = LocalBuildInfo -> ProgramDb
withPrograms LocalBuildInfo
localBuildInfo
  Verbosity -> Program -> ProgramDb -> Args -> IO ()
runDbProgram Verbosity
verbosity Program
makeProgram ProgramDb
programDb []

cppInstall :: Verbosity -> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
cppInstall :: Verbosity
-> PackageDescription -> LocalBuildInfo -> CopyDest -> IO ()
cppInstall Verbosity
verbosity PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo CopyDest
dest = do
  Bool
hasMakefile <- String -> IO Bool
doesFileExist String
"Makefile"
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
hasMakefile (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
#if MIN_VERSION_Cabal(2,0,0)
    Verbosity -> String -> IO ()
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity
#else
    die
#endif
    String
"No Makefile found."
  let programDb :: ProgramDb
programDb = LocalBuildInfo -> ProgramDb
withPrograms LocalBuildInfo
localBuildInfo
      libDir :: String
libDir = InstallDirs String -> String
forall dir. InstallDirs dir -> dir
libdir (InstallDirs String -> String) -> InstallDirs String -> String
forall a b. (a -> b) -> a -> b
$ PackageDescription
-> LocalBuildInfo -> CopyDest -> InstallDirs String
absoluteInstallDirs PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo CopyDest
dest
  Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
libDir
  Verbosity -> Program -> ProgramDb -> Args -> IO ()
runDbProgram Verbosity
verbosity Program
makeProgram ProgramDb
programDb [String
"install", String
"libdir=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
libDir]

  -- We're doing an old-style install, so copy the enum eval cache file from the
  -- build directory to the library directory where the Haskell side of the
  -- bindings will find it.
  let enumEvalCacheFilePath :: String
enumEvalCacheFilePath = LocalBuildInfo -> String
buildDir LocalBuildInfo
localBuildInfo String -> String -> String
</> String
enumEvalCacheFileName
  Bool
enumEvalCacheExists <- String -> IO Bool
doesFileExist String
enumEvalCacheFilePath
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
enumEvalCacheExists (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
    Verbosity -> String -> String -> IO ()
installOrdinaryFile Verbosity
verbosity
                        String
enumEvalCacheFilePath
                        (String
libDir String -> String -> String
</> String
enumEvalCacheFileName)

cppRegister :: Verbosity -> LocalBuildInfo -> RegisterFlags -> IO ()
cppRegister :: Verbosity -> LocalBuildInfo -> RegisterFlags -> IO ()
cppRegister Verbosity
verbosity LocalBuildInfo
localBuildInfo RegisterFlags
flags = do
  Bool
hasMakefile <- String -> IO Bool
doesFileExist String
"Makefile"
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
hasMakefile (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
#if MIN_VERSION_Cabal(2,0,0)
    Verbosity -> String -> IO ()
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity
#else
    die
#endif
    String
"No Makefile found."
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Flag Bool -> Bool
forall a. a -> Flag a -> a
fromFlagOrDefault Bool
False (RegisterFlags -> Flag Bool
regInPlace RegisterFlags
flags)) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
    let programDb :: ProgramDb
programDb = LocalBuildInfo -> ProgramDb
withPrograms LocalBuildInfo
localBuildInfo
        libDir :: String
libDir = LocalBuildInfo -> String
buildDir LocalBuildInfo
localBuildInfo
    Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
libDir
    Verbosity -> Program -> ProgramDb -> Args -> IO ()
runDbProgram Verbosity
verbosity Program
makeProgram ProgramDb
programDb [String
"install", String
"libdir=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
libDir]

cppClean :: ProjectConfig -> Verbosity -> IO ()
cppClean :: ProjectConfig -> Verbosity -> IO ()
cppClean ProjectConfig
project Verbosity
verbosity = do
  Interface
iface <- ProjectConfig -> Verbosity -> IO Interface
getInterface ProjectConfig
project Verbosity
verbosity
  [Action]
_ <- [Interface] -> Args -> IO [Action]
run [Interface
iface] [String
"--clean-cpp", ProjectConfig -> String
cppSourcesDir ProjectConfig
project]

  Bool
hasMakefile <- String -> IO Bool
doesFileExist String
"Makefile"
  Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
hasMakefile (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$
#if MIN_VERSION_Cabal(2,0,0)
    Verbosity -> String -> IO ()
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity
#else
    die
#endif
    String
"No Makefile found."
  ConfiguredProgram
make <- Verbosity -> String -> IO ConfiguredProgram
findSystemProgram Verbosity
verbosity String
"make"
  Verbosity -> ConfiguredProgram -> Args -> IO ()
runProgram Verbosity
verbosity ConfiguredProgram
make [String
"clean"]

findSystemProgram :: Verbosity -> FilePath -> IO ConfiguredProgram
findSystemProgram :: Verbosity -> String -> IO ConfiguredProgram
findSystemProgram Verbosity
verbosity String
basename = do
  Maybe String
maybePath <-
#if MIN_VERSION_Cabal(1,24,0)
    (Maybe (String, Args) -> Maybe String)
-> IO (Maybe (String, Args)) -> IO (Maybe String)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (((String, Args) -> String) -> Maybe (String, Args) -> Maybe String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (String, Args) -> String
forall a b. (a, b) -> a
fst) (IO (Maybe (String, Args)) -> IO (Maybe String))
-> IO (Maybe (String, Args)) -> IO (Maybe String)
forall a b. (a -> b) -> a -> b
$  -- We don't care about failed search paths.
#endif
    Verbosity
-> ProgramSearchPath -> String -> IO (Maybe (String, Args))
findProgramOnSearchPath Verbosity
verbosity [ProgramSearchPathEntry
ProgramSearchPathDefault] String
basename
  case Maybe String
maybePath of
    Just String
path -> ConfiguredProgram -> IO ConfiguredProgram
forall (m :: * -> *) a. Monad m => a -> m a
return (ConfiguredProgram -> IO ConfiguredProgram)
-> ConfiguredProgram -> IO ConfiguredProgram
forall a b. (a -> b) -> a -> b
$ String -> ProgramLocation -> ConfiguredProgram
simpleConfiguredProgram String
basename (ProgramLocation -> ConfiguredProgram)
-> ProgramLocation -> ConfiguredProgram
forall a b. (a -> b) -> a -> b
$ String -> ProgramLocation
FoundOnSystem String
path
    Maybe String
Nothing ->
#if MIN_VERSION_Cabal(2,0,0)
      Verbosity -> String -> IO ConfiguredProgram
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity (String -> IO ConfiguredProgram) -> String -> IO ConfiguredProgram
forall a b. (a -> b) -> a -> b
$
#else
      die $
#endif
      String
"Couldn't find program " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
basename String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."

-- | A @main@ implementation to be used in the @Setup.hs@ of a Haskell gateway
-- package.
--
-- @hsMain project = 'defaultMainWithHooks' $ 'hsUserHooks' project@
hsMain :: ProjectConfig -> IO ()
hsMain :: ProjectConfig -> IO ()
hsMain ProjectConfig
project = UserHooks -> IO ()
defaultMainWithHooks (UserHooks -> IO ()) -> UserHooks -> IO ()
forall a b. (a -> b) -> a -> b
$ ProjectConfig -> UserHooks
hsUserHooks ProjectConfig
project

-- | Cabal user hooks for a Haskell gateway package.  When overriding fields in
-- the result, be sure to call the previous hook.
--
-- The following hooks are defined:
--
-- - 'postConf': Finds the shared library directory for the installed C++
-- gateway package, and writes this path to a @dist\/build\/hoppy-cpp-libdir@
-- file.  Runs the generator program to generate Haskell sources.
--
-- - 'preBuild', 'preTest', 'preCopy', 'preInst', 'preReg': Reads the C++
-- library directory from @dist\/build\/hoppy-cpp-libdir@ and adds it to the
-- library search path ('extraLibDirs').
--
-- - 'cleanHook': Removes files created by the generator.
hsUserHooks :: ProjectConfig -> UserHooks
hsUserHooks :: ProjectConfig -> UserHooks
hsUserHooks ProjectConfig
project =
  UserHooks
simpleUserHooks
  { postConf :: Args
-> ConfigFlags -> PackageDescription -> LocalBuildInfo -> IO ()
postConf = \Args
args ConfigFlags
flags PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo -> do
      UserHooks
-> Args
-> ConfigFlags
-> PackageDescription
-> LocalBuildInfo
-> IO ()
postConf UserHooks
simpleUserHooks Args
args ConfigFlags
flags PackageDescription
pkgDesc LocalBuildInfo
localBuildInfo
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ ConfigFlags -> Flag Verbosity
configVerbosity ConfigFlags
flags
      ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
hsConfigure ProjectConfig
project Verbosity
verbosity LocalBuildInfo
localBuildInfo

  , preBuild :: Args -> BuildFlags -> IO HookedBuildInfo
preBuild = \Args
_ BuildFlags
_ -> IO HookedBuildInfo
addLibDir
  , preTest :: Args -> TestFlags -> IO HookedBuildInfo
preTest = \Args
_ TestFlags
_ -> IO HookedBuildInfo
addLibDir
  , preCopy :: Args -> CopyFlags -> IO HookedBuildInfo
preCopy = \Args
_ CopyFlags
_ -> IO HookedBuildInfo
addLibDir  -- Not sure if necessary, but doesn't hurt.
  , preInst :: Args -> InstallFlags -> IO HookedBuildInfo
preInst = \Args
_ InstallFlags
_ -> IO HookedBuildInfo
addLibDir  -- Not sure if necessary, but doesn't hurt.
  , preReg :: Args -> RegisterFlags -> IO HookedBuildInfo
preReg = \Args
_ RegisterFlags
_ -> IO HookedBuildInfo
addLibDir  -- Necessary.
  , preRepl :: Args -> ReplFlags -> IO HookedBuildInfo
preRepl = \Args
_ ReplFlags
_ -> IO HookedBuildInfo
addLibDir -- Necessary.

  , cleanHook :: PackageDescription -> () -> UserHooks -> CleanFlags -> IO ()
cleanHook = \PackageDescription
pkgDesc ()
z UserHooks
hooks CleanFlags
flags -> do
      UserHooks
-> PackageDescription -> () -> UserHooks -> CleanFlags -> IO ()
cleanHook UserHooks
simpleUserHooks PackageDescription
pkgDesc ()
z UserHooks
hooks CleanFlags
flags
      let verbosity :: Verbosity
verbosity = Verbosity -> Flag Verbosity -> Verbosity
forall a. a -> Flag a -> a
fromFlagOrDefault Verbosity
normal (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ CleanFlags -> Flag Verbosity
cleanVerbosity CleanFlags
flags
      ProjectConfig -> Verbosity -> IO ()
hsClean ProjectConfig
project Verbosity
verbosity
  }

hsCppLibDirFile :: FilePath
hsCppLibDirFile :: String
hsCppLibDirFile = String
"dist/build/hoppy-cpp-libdir"

hsConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
hsConfigure :: ProjectConfig -> Verbosity -> LocalBuildInfo -> IO ()
hsConfigure ProjectConfig
project Verbosity
verbosity LocalBuildInfo
localBuildInfo = do
  Interface
iface <- ProjectConfig -> Verbosity -> IO Interface
getInterface ProjectConfig
project Verbosity
verbosity
  String
libDir <- IO String
lookupCppLibDir
  String -> IO ()
storeCppLibDir String
libDir
  Interface -> String -> IO ()
generateSources Interface
iface String
libDir

  where lookupCppLibDir :: IO String
lookupCppLibDir = do
          -- Look for an installed -cpp package.
          let packageName :: String
packageName = ProjectConfig -> String
cppPackageName ProjectConfig
project
          InstalledPackageInfo
cppPkg <- case PackageIndex InstalledPackageInfo
-> PackageName -> [(Version, [InstalledPackageInfo])]
forall a. PackageIndex a -> PackageName -> [(Version, [a])]
lookupPackageName (LocalBuildInfo -> PackageIndex InstalledPackageInfo
installedPkgs LocalBuildInfo
localBuildInfo) (PackageName -> [(Version, [InstalledPackageInfo])])
-> PackageName -> [(Version, [InstalledPackageInfo])]
forall a b. (a -> b) -> a -> b
$
#if MIN_VERSION_Cabal(2,0,0)
                         String -> PackageName
mkPackageName String
packageName
#else
                         PackageName packageName
#endif
                         of
            [(Version
_, [InstalledPackageInfo
pkg])] -> InstalledPackageInfo -> IO InstalledPackageInfo
forall (m :: * -> *) a. Monad m => a -> m a
return InstalledPackageInfo
pkg
            [(Version, [InstalledPackageInfo])]
results ->
#if MIN_VERSION_Cabal(2,0,0)
              Verbosity -> String -> IO InstalledPackageInfo
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity (String -> IO InstalledPackageInfo)
-> String -> IO InstalledPackageInfo
forall a b. (a -> b) -> a -> b
$
#else
              die $
#endif
              String
"Failed to find a unique " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
packageName String -> String -> String
forall a. [a] -> [a] -> [a]
++
              String
" installation.  Found " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [(Version, [InstalledPackageInfo])] -> String
forall a. Show a => a -> String
show [(Version, [InstalledPackageInfo])]
results String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."

          -- Look up the libdir of the package we found.  The filter here is for NixOS,
          -- where libraryDirs includes the library directories of dependencies as well.
          case (String -> Bool) -> Args -> Args
forall a. (a -> Bool) -> [a] -> [a]
filter (\String
x -> String
packageName String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
x) (Args -> Args) -> Args -> Args
forall a b. (a -> b) -> a -> b
$ InstalledPackageInfo -> Args
libraryDirs InstalledPackageInfo
cppPkg of
            [String
libDir] -> String -> IO String
forall (m :: * -> *) a. Monad m => a -> m a
return String
libDir
            Args
libDirs ->
#if MIN_VERSION_Cabal(2,0,0)
              Verbosity -> String -> IO String
forall a. Verbosity -> String -> IO a
die' Verbosity
verbosity (String -> IO String) -> String -> IO String
forall a b. (a -> b) -> a -> b
$
#else
              die $
#endif
              String
"Expected a single library directory for " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
packageName String -> String -> String
forall a. [a] -> [a] -> [a]
++
              String
", got " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Args -> String
forall a. Show a => a -> String
show Args
libDirs String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."

        storeCppLibDir :: String -> IO ()
storeCppLibDir String
libDir = do
          Bool -> String -> IO ()
createDirectoryIfMissing Bool
True (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String -> String
takeDirectory String
hsCppLibDirFile
          String -> String -> IO ()
writeFile String
hsCppLibDirFile String
libDir

        generateSources :: Interface -> String -> IO ()
generateSources Interface
iface String
libDir = do
          let sourcesDir :: String
sourcesDir = ProjectConfig -> String
hsSourcesDir ProjectConfig
project
          Bool -> String -> IO ()
createDirectoryIfMissing Bool
True String
sourcesDir
          [Action]
_ <- [Interface] -> Args -> IO [Action]
run [Interface
iface]
                   [ String
"--enum-eval-cache-mode", String
"must-exist"
                   , String
"--enum-eval-cache-path", String
libDir String -> String -> String
</> String
enumEvalCacheFileName
                   , String
"--gen-hs", String
sourcesDir
                   ]
          () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

addLibDir :: IO HookedBuildInfo
addLibDir :: IO HookedBuildInfo
addLibDir = do
  String
libDir <- String -> IO String
readFile String
hsCppLibDirFile
  HookedBuildInfo -> IO HookedBuildInfo
forall (m :: * -> *) a. Monad m => a -> m a
return (BuildInfo -> Maybe BuildInfo
forall a. a -> Maybe a
Just BuildInfo
emptyBuildInfo {extraLibDirs :: Args
extraLibDirs = [String
libDir]}, [])

hsClean :: ProjectConfig -> Verbosity -> IO ()
hsClean :: ProjectConfig -> Verbosity -> IO ()
hsClean ProjectConfig
project Verbosity
verbosity = do
  Interface
iface <- ProjectConfig -> Verbosity -> IO Interface
getInterface ProjectConfig
project Verbosity
verbosity
  [Action]
_ <- [Interface] -> Args -> IO [Action]
run [Interface
iface] [String
"--clean-hs", ProjectConfig -> String
hsSourcesDir ProjectConfig
project]
  () -> IO ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()