{-# LANGUAGE RecordWildCards, OverloadedRecordDot #-}
module Distribution.XCFramework.SetupHooks
( SetupHooks, xcframeworkHooks )
where
import System.IO.Temp
import System.Process
import System.FilePath
import Distribution.Simple.SetupHooks
import Distribution.Simple.LocalBuildInfo
( interpretSymbolicPathLBI, withPrograms )
import Distribution.Simple.Setup (setupVerbosity)
import Distribution.Pretty (prettyShow)
import Distribution.Simple.Flag (fromFlag)
import Distribution.Simple.Program
import System.Directory
import Control.Monad
import Data.Maybe
xcframeworkHooks :: FilePath
-> SetupHooks
xcframeworkHooks :: String -> SetupHooks
xcframeworkHooks String
out = SetupHooks
noSetupHooks
{ buildHooks = noBuildHooks
{ postBuildComponentHook = Just $ postBuild out
}
}
postBuild :: FilePath
-> PostBuildComponentInputs
-> IO ()
postBuild :: String -> PostBuildComponentInputs -> IO ()
postBuild String
outFile PostBuildComponentInputs{TargetInfo
LocalBuildInfo
BuildFlags
buildFlags :: BuildFlags
localBuildInfo :: LocalBuildInfo
targetInfo :: TargetInfo
buildFlags :: PostBuildComponentInputs -> BuildFlags
localBuildInfo :: PostBuildComponentInputs -> LocalBuildInfo
targetInfo :: PostBuildComponentInputs -> TargetInfo
..} = do
let
verbosity :: Verbosity
verbosity = Flag Verbosity -> Verbosity
forall a. WithCallStack (Flag a -> a)
fromFlag (Flag Verbosity -> Verbosity) -> Flag Verbosity -> Verbosity
forall a b. (a -> b) -> a -> b
$ CommonSetupFlags -> Flag Verbosity
setupVerbosity (CommonSetupFlags -> Flag Verbosity)
-> CommonSetupFlags -> Flag Verbosity
forall a b. (a -> b) -> a -> b
$ BuildFlags -> CommonSetupFlags
buildCommonFlags BuildFlags
buildFlags
i :: SymbolicPathX allowAbsolute Pkg to -> String
i = LocalBuildInfo -> SymbolicPathX allowAbsolute Pkg to -> String
forall (allowAbsolute :: AllowAbsolute) (to :: FileOrDir).
LocalBuildInfo -> SymbolicPathX allowAbsolute Pkg to -> String
interpretSymbolicPathLBI LocalBuildInfo
localBuildInfo
clbi :: ComponentLocalBuildInfo
clbi = TargetInfo -> ComponentLocalBuildInfo
targetCLBI TargetInfo
targetInfo
progDb :: ProgramDb
progDb = LocalBuildInfo -> ProgramDb
withPrograms LocalBuildInfo
localBuildInfo
do_it :: String -> IO ()
do_it String
libHSname = do
let buildDir :: String
buildDir = SymbolicPathX 'AllowAbsolute Pkg ('Dir Build) -> String
forall {allowAbsolute :: AllowAbsolute} {to :: FileOrDir}.
SymbolicPathX allowAbsolute Pkg to -> String
i (LocalBuildInfo
-> ComponentLocalBuildInfo
-> SymbolicPathX 'AllowAbsolute Pkg ('Dir Build)
componentBuildDir LocalBuildInfo
localBuildInfo ComponentLocalBuildInfo
clbi)
let libHS :: String
libHS = String
buildDir String -> String -> String
</> String
libHSname
(ghcPkgProg, _) <- Verbosity
-> Program -> ProgramDb -> IO (ConfiguredProgram, ProgramDb)
requireProgram Verbosity
verbosity Program
ghcPkgProgram ProgramDb
progDb
let ghcPkg = ConfiguredProgram -> String
programPath ConfiguredProgram
ghcPkgProg
includeDirsStr <- readProcess ghcPkg ["field", "rts", "include-dirs", "--simple-output"] ""
let includeDirs = String -> [String]
words String
includeDirsStr
tmpDir <- getCanonicalTemporaryDirectory
withTempDirectory tmpDir "xcframework" $ \String
finalHeadersDir -> do
(String -> IO [String]) -> [String] -> IO ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\String
idir -> String -> String -> IO [String]
copyHeaderFiles String
idir String
finalHeadersDir) [String]
includeDirs
hsLibraryHeaders <- String -> String -> IO [String]
copyHeaderFiles String
buildDir (String
finalHeadersDir)
writeFile (finalHeadersDir </> "module.modulemap") $ unlines $
[ "// This file is automatically generated by swift-ffi"
, "// Do not edit manually."
, ""
, "module Haskell {"
, " module Foreign {"
, ""
, " module Rts {"
, " header \"HsFFI.h\""
, " export *"
, " }"
, ""
, " module Exports {"
]
++
[ " header \"" ++ h ++ "\""
| h <- hsLibraryHeaders ]
++
[ " export *"
, " }"
, ""
, " }" ]
++
[ "}" ]
let cmd = [String] -> String
unwords ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$
[ String
"xcodebuild", String
"-create-xcframework"
, String
"-output", String
outFile
, String
"-library", String
libHS
, String
"-headers", String
finalHeadersDir
]
xcfExists <- doesDirectoryExist outFile
when (xcfExists && takeExtension outFile == ".xcframework") $ do
putStrLn $ "Removing existing XCFramework at " ++ outFile
removePathForcibly outFile
putStrLn "Creating XCFramework..."
putStrLn cmd
callCommand cmd
case TargetInfo -> ComponentLocalBuildInfo
targetCLBI TargetInfo
targetInfo of
l :: ComponentLocalBuildInfo
l@LibComponentLocalBuildInfo{}
->
String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$
String
"Ignoring xcframeworkHooks for library (but not foreign-lib) component, because libraries are currently unsupported "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ ComponentName -> String
forall a. Pretty a => a -> String
prettyShow (ComponentLocalBuildInfo -> ComponentName
componentLocalName ComponentLocalBuildInfo
l)
FLibComponentLocalBuildInfo{componentLocalName :: ComponentLocalBuildInfo -> ComponentName
componentLocalName=CFLibName UnqualComponentName
flibName}
-> String -> IO ()
do_it (String
"lib" String -> String -> String
forall a. [a] -> [a] -> [a]
++ UnqualComponentName -> String
forall a. Pretty a => a -> String
prettyShow UnqualComponentName
flibName String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
".dylib")
ComponentLocalBuildInfo
other ->
String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$
String
"Ignoring xcframeworkHooks for non-library component "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ ComponentName -> String
forall a. Pretty a => a -> String
prettyShow (ComponentLocalBuildInfo -> ComponentName
componentLocalName ComponentLocalBuildInfo
other)
getHeaderFiles :: FilePath -> IO [FilePath]
String
dir = do
contents <- String -> IO [String]
listDirectory String
dir
paths <- forM contents $ \String
name -> do
let path :: String
path = String
dir String -> String -> String
</> String
name
isDir <- String -> IO Bool
doesDirectoryExist String
path
isSymlink <- pathIsSymbolicLink path
if isDir && not isSymlink
then getHeaderFiles path
else return [path | takeExtension name == ".h" || (isSymlink && isDir)]
return (concat paths)
copyHeaderFiles :: FilePath -> FilePath -> IO [FilePath]
String
srcDir String
destDir = [Maybe String] -> [String]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe String] -> [String]) -> IO [Maybe String] -> IO [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> do
headerFiles <- String -> IO [String]
getHeaderFiles String
srcDir
forM headerFiles $ \String
srcPath -> do
srcIsSymlink <- String -> IO Bool
pathIsSymbolicLink String
srcPath
let relPath = String -> String -> String
makeRelative String
srcDir String
srcPath
destPath = String
destDir String -> String -> String
</> String
relPath
destDirPath = String -> String
takeDirectory String
destPath
if srcIsSymlink then do
tgt <- getSymbolicLinkTarget srcPath
createDirectoryLink tgt destPath
return Nothing
else do
createDirectoryIfMissing True destDirPath
copyFile srcPath destPath
return (Just relPath)