module Distribution.Simple.NHC (
configure,
getInstalledPackages,
buildLib,
buildExe,
installLib,
installExe,
) where
import Distribution.Package
( PackageName, PackageIdentifier(..), InstalledPackageId(..)
, packageName )
import Distribution.InstalledPackageInfo
( InstalledPackageInfo
, InstalledPackageInfo_( InstalledPackageInfo, installedPackageId
, sourcePackageId )
, emptyInstalledPackageInfo, parseInstalledPackageInfo )
import Distribution.PackageDescription
( PackageDescription(..), BuildInfo(..), Library(..), Executable(..)
, hcOptions, usedExtensions )
import Distribution.ModuleName (ModuleName)
import qualified Distribution.ModuleName as ModuleName
import Distribution.Simple.LocalBuildInfo
( LocalBuildInfo(..), ComponentLocalBuildInfo(..) )
import Distribution.Simple.BuildPaths
( mkLibName, objExtension, exeExtension )
import Distribution.Simple.Compiler
( CompilerFlavor(..), CompilerId(..), Compiler(..)
, Flag, languageToFlags, extensionsToFlags
, PackageDB(..), PackageDBStack )
import qualified Distribution.Simple.PackageIndex as PackageIndex
import Distribution.Simple.PackageIndex (PackageIndex)
import Language.Haskell.Extension
( Language(Haskell98), Extension(..), KnownExtension(..) )
import Distribution.Simple.Program
( ProgramConfiguration, userMaybeSpecifyPath, programPath
, requireProgram, requireProgramVersion, lookupProgram
, nhcProgram, hmakeProgram, ldProgram, arProgram
, rawSystemProgramConf )
import Distribution.Simple.Utils
( die, info, findFileWithExtension, findModuleFiles
, installOrdinaryFile, installExecutableFile, installOrdinaryFiles
, createDirectoryIfMissingVerbose, withUTF8FileContents )
import Distribution.Version
( Version(..), orLaterVersion )
import Distribution.Verbosity
import Distribution.Text
( display, simpleParse )
import Distribution.ParseUtils
( ParseResult(..) )
import System.FilePath
( (</>), (<.>), normalise, takeDirectory, dropExtension )
import System.Directory
( doesFileExist, doesDirectoryExist, getDirectoryContents
, removeFile, getHomeDirectory )
import Data.Char ( toLower )
import Data.List ( nub )
import Data.Maybe ( catMaybes )
import Data.Monoid ( Monoid(..) )
import Control.Monad ( when, unless )
import Distribution.Compat.Exception
import Distribution.System ( Platform )
configure :: Verbosity -> Maybe FilePath -> Maybe FilePath
-> ProgramConfiguration -> IO (Compiler, Maybe Platform, ProgramConfiguration)
configure verbosity hcPath _hcPkgPath conf = do
(_nhcProg, nhcVersion, conf') <-
requireProgramVersion verbosity nhcProgram
(orLaterVersion (Version [1,20] []))
(userMaybeSpecifyPath "nhc98" hcPath conf)
(_hmakeProg, _hmakeVersion, conf'') <-
requireProgramVersion verbosity hmakeProgram
(orLaterVersion (Version [3,13] [])) conf'
(_ldProg, conf''') <- requireProgram verbosity ldProgram conf''
(_arProg, conf'''') <- requireProgram verbosity arProgram conf'''
let comp = Compiler {
compilerId = CompilerId NHC nhcVersion,
compilerLanguages = nhcLanguages,
compilerExtensions = nhcLanguageExtensions
}
compPlatform = Nothing
return (comp, compPlatform, conf'''')
nhcLanguages :: [(Language, Flag)]
nhcLanguages = [(Haskell98, "-98")]
nhcLanguageExtensions :: [(Extension, Flag)]
nhcLanguageExtensions =
[(EnableExtension MonomorphismRestriction, "")
,(DisableExtension MonomorphismRestriction, "")
,(EnableExtension ForeignFunctionInterface, "")
,(DisableExtension ForeignFunctionInterface, "")
,(EnableExtension ExistentialQuantification, "")
,(DisableExtension ExistentialQuantification, "")
,(EnableExtension EmptyDataDecls, "")
,(DisableExtension EmptyDataDecls, "")
,(EnableExtension NamedFieldPuns, "-puns")
,(DisableExtension NamedFieldPuns, "-nopuns")
,(EnableExtension CPP, "-cpp")
,(DisableExtension CPP, "")
]
getInstalledPackages :: Verbosity -> PackageDBStack -> ProgramConfiguration
-> IO PackageIndex
getInstalledPackages verbosity packagedbs conf = do
homedir <- getHomeDirectory
(nhcProg, _) <- requireProgram verbosity nhcProgram conf
let bindir = takeDirectory (programPath nhcProg)
incdir = takeDirectory bindir </> "include" </> "nhc98"
dbdirs = nub (concatMap (packageDbPaths homedir incdir) packagedbs)
indexes <- mapM getIndividualDBPackages dbdirs
return $! mconcat indexes
where
getIndividualDBPackages :: FilePath -> IO PackageIndex
getIndividualDBPackages dbdir = do
pkgdirs <- getPackageDbDirs dbdir
pkgs <- sequence [ getInstalledPackage pkgname pkgdir
| (pkgname, pkgdir) <- pkgdirs ]
let pkgs' = map setInstalledPackageId (catMaybes pkgs)
return (PackageIndex.fromList pkgs')
packageDbPaths :: FilePath -> FilePath -> PackageDB -> [FilePath]
packageDbPaths _home incdir db = case db of
GlobalPackageDB -> [ incdir </> "packages" ]
UserPackageDB -> []
SpecificPackageDB path -> [ path ]
getPackageDbDirs :: FilePath -> IO [(PackageName, FilePath)]
getPackageDbDirs dbdir = do
dbexists <- doesDirectoryExist dbdir
if not dbexists
then return []
else do
entries <- getDirectoryContents dbdir
pkgdirs <- sequence
[ do pkgdirExists <- doesDirectoryExist pkgdir
return (pkgname, pkgdir, pkgdirExists)
| (entry, Just pkgname) <- [ (entry, simpleParse entry)
| entry <- entries ]
, let pkgdir = dbdir </> entry ]
return [ (pkgname, pkgdir) | (pkgname, pkgdir, True) <- pkgdirs ]
getInstalledPackage :: PackageName -> FilePath -> IO (Maybe InstalledPackageInfo)
getInstalledPackage pkgname pkgdir = do
let pkgconfFile = pkgdir </> "package.conf"
pkgconfExists <- doesFileExist pkgconfFile
let cabalFile = pkgdir <.> "cabal"
cabalExists <- doesFileExist cabalFile
case () of
_ | pkgconfExists -> getFullInstalledPackageInfo pkgname pkgconfFile
| cabalExists -> getPhonyInstalledPackageInfo pkgname cabalFile
| otherwise -> return Nothing
getFullInstalledPackageInfo :: PackageName -> FilePath
-> IO (Maybe InstalledPackageInfo)
getFullInstalledPackageInfo pkgname pkgconfFile =
withUTF8FileContents pkgconfFile $ \contents ->
case parseInstalledPackageInfo contents of
ParseOk _ pkginfo | packageName pkginfo == pkgname
-> return (Just pkginfo)
_ -> return Nothing
getPhonyInstalledPackageInfo :: PackageName -> FilePath
-> IO (Maybe InstalledPackageInfo)
getPhonyInstalledPackageInfo pkgname pathsModule = do
content <- readFile pathsModule
case extractVersion content of
Nothing -> return Nothing
Just version -> return (Just pkginfo)
where
pkgid = PackageIdentifier pkgname version
pkginfo = emptyInstalledPackageInfo { sourcePackageId = pkgid }
where
extractVersion :: String -> Maybe Version
extractVersion content =
case catMaybes (map extractVersionLine (lines content)) of
[version] -> Just version
_ -> Nothing
extractVersionLine :: String -> Maybe Version
extractVersionLine line =
case words line of
[versionTag, ":", versionStr]
| map toLower versionTag == "version" -> simpleParse versionStr
[versionTag, versionStr]
| map toLower versionTag == "version:" -> simpleParse versionStr
_ -> Nothing
setInstalledPackageId :: InstalledPackageInfo -> InstalledPackageInfo
setInstalledPackageId pkginfo@InstalledPackageInfo {
installedPackageId = InstalledPackageId "",
sourcePackageId = pkgid
}
= pkginfo {
installedPackageId = InstalledPackageId (display pkgid)
}
setInstalledPackageId pkginfo = pkginfo
buildLib :: Verbosity -> PackageDescription -> LocalBuildInfo
-> Library -> ComponentLocalBuildInfo -> IO ()
buildLib verbosity pkg_descr lbi lib clbi = do
libName <- case componentLibraries clbi of
[libName] -> return libName
[] -> die "No library name found when building library"
_ -> die "Multiple library names found when building library"
let conf = withPrograms lbi
Just nhcProg = lookupProgram nhcProgram conf
let bi = libBuildInfo lib
modules = exposedModules lib ++ otherModules bi
languageFlags = languageToFlags (compiler lbi) (defaultLanguage bi)
++ extensionsToFlags (compiler lbi) (usedExtensions bi)
inFiles <- getModulePaths lbi bi modules
let targetDir = buildDir lbi
srcDirs = nub (map takeDirectory inFiles)
destDirs = map (targetDir </>) srcDirs
mapM_ (createDirectoryIfMissingVerbose verbosity True) destDirs
rawSystemProgramConf verbosity hmakeProgram conf $
["-hc=" ++ programPath nhcProg]
++ nhcVerbosityOptions verbosity
++ ["-d", targetDir, "-hidir", targetDir]
++ maybe [] (hcOptions NHC . libBuildInfo)
(library pkg_descr)
++ languageFlags
++ concat [ ["-package", display (packageName pkgid) ]
| (_, pkgid) <- componentPackageDeps clbi ]
++ inFiles
info verbosity "Linking..."
let
libFilePath = targetDir </> mkLibName libName
hObjs = [ targetDir </> ModuleName.toFilePath m <.> objExtension
| m <- modules ]
unless (null hObjs ) $ do
removeFile libFilePath `catchIO` \_ -> return ()
let arVerbosity | verbosity >= deafening = "v"
| verbosity >= normal = ""
| otherwise = "c"
rawSystemProgramConf verbosity arProgram (withPrograms lbi) $
["q"++ arVerbosity, libFilePath]
++ hObjs
buildExe :: Verbosity -> PackageDescription -> LocalBuildInfo
-> Executable -> ComponentLocalBuildInfo -> IO ()
buildExe verbosity pkg_descr lbi exe clbi = do
let conf = withPrograms lbi
Just nhcProg = lookupProgram nhcProgram conf
when (dropExtension (modulePath exe) /= exeName exe) $
die $ "hmake does not support exe names that do not match the name of "
++ "the 'main-is' file. You will have to rename your executable to "
++ show (dropExtension (modulePath exe))
let bi = buildInfo exe
modules = otherModules bi
languageFlags = languageToFlags (compiler lbi) (defaultLanguage bi)
++ extensionsToFlags (compiler lbi) (usedExtensions bi)
inFiles <- getModulePaths lbi bi modules
let targetDir = buildDir lbi </> exeName exe
exeDir = targetDir </> (exeName exe ++ "-tmp")
srcDirs = nub (map takeDirectory (modulePath exe : inFiles))
destDirs = map (exeDir </>) srcDirs
mapM_ (createDirectoryIfMissingVerbose verbosity True) destDirs
rawSystemProgramConf verbosity hmakeProgram conf $
["-hc=" ++ programPath nhcProg]
++ nhcVerbosityOptions verbosity
++ ["-d", targetDir, "-hidir", targetDir]
++ maybe [] (hcOptions NHC . libBuildInfo)
(library pkg_descr)
++ languageFlags
++ concat [ ["-package", display (packageName pkgid) ]
| (_, pkgid) <- componentPackageDeps clbi ]
++ inFiles
++ [exeName exe]
nhcVerbosityOptions :: Verbosity -> [String]
nhcVerbosityOptions verbosity
| verbosity >= deafening = ["-v"]
| verbosity >= normal = []
| otherwise = ["-q"]
getModulePaths :: LocalBuildInfo -> BuildInfo -> [ModuleName] -> IO [FilePath]
getModulePaths lbi bi modules = sequence
[ findFileWithExtension ["hs", "lhs"] (buildDir lbi : hsSourceDirs bi)
(ModuleName.toFilePath module_) >>= maybe (notFound module_) (return . normalise)
| module_ <- modules ]
where notFound module_ = die $ "can't find source for module " ++ display module_
installExe :: Verbosity
-> FilePath
-> FilePath
-> (FilePath, FilePath)
-> Executable
-> IO ()
installExe verbosity pref buildPref (progprefix,progsuffix) exe
= do createDirectoryIfMissingVerbose verbosity True pref
let exeBaseName = exeName exe
exeFileName = exeBaseName <.> exeExtension
fixedExeFileName = (progprefix ++ exeBaseName ++ progsuffix) <.> exeExtension
installExecutableFile verbosity
(buildPref </> exeBaseName </> exeFileName)
(pref </> fixedExeFileName)
installLib :: Verbosity
-> FilePath
-> FilePath
-> PackageIdentifier
-> Library
-> ComponentLocalBuildInfo
-> IO ()
installLib verbosity pref buildPref _pkgid lib clbi
= do let bi = libBuildInfo lib
modules = exposedModules lib ++ otherModules bi
findModuleFiles [buildPref] ["hi"] modules
>>= installOrdinaryFiles verbosity pref
let libNames = map mkLibName (componentLibraries clbi)
installLib' libName = installOrdinaryFile verbosity
(buildPref </> libName)
(pref </> libName)
mapM_ installLib' libNames