hsenv-0.5: src/Actions.hs
module Actions ( cabalUpdate
, installCabalConfig
, installCabalWrapper
, installActivateScript
, installSimpleWrappers
, installProgSymlinks
, symlinkToSkeleton
, copyBaseSystem
, initGhcDb
, installGhc
, createDirStructure
) where
import Control.Monad
import System.Directory (setCurrentDirectory, getCurrentDirectory, createDirectory, removeDirectoryRecursive, getAppUserDataDirectory, doesFileExist, findExecutable)
import System.FilePath ((</>))
import System.Info (arch, os)
import System.Posix hiding (createDirectory, version)
import Distribution.Version (Version (..))
import Distribution.Package (PackageName(..))
import Safe (lastMay)
import Data.List (intercalate)
import Data.Maybe (fromMaybe, isJust)
import Network.Http.Client
import qualified Data.ByteString.Char8 as C8
import qualified System.IO.Streams as S
import HsenvMonad
import Types
import Paths
import PackageManagement
import Process
import Util.Template (substs)
import Util.IO (makeExecutable, createTemporaryDirectory)
import Skeletons
-- update cabal package info inside Virtual Haskell Environment
cabalUpdate :: Hsenv ()
cabalUpdate = do
noSharingFlag <- asks noSharing
if noSharingFlag then do
debug "Sharing user-wide ~/.cabal/packages disabled"
cabalUpdate'
else do
debug "Sharing user-wide ~/.cabal/packages enabled, checking if data is already downloaded"
cabalInstallDir <- liftIO $ getAppUserDataDirectory "cabal"
let hackageData = foldl (</>) cabalInstallDir [ "packages"
, "hackage.haskell.org"
, "00-index.tar"
]
dataExists <- liftIO $ doesFileExist hackageData
if dataExists then do
info "Skipping 'cabal update' step, Hackage download cache already downloaded"
info " to ~/.cabal/packages/. You can update it manually with 'cabal update'"
info " (from inside or outside the virtual environment)."
else do
debug "No user-wide Hackage cache data downloaded"
cabalUpdate'
where cabalUpdate' = do
cabalConfig <- cabalConfigLocation
info "Updating cabal package database inside Virtual Haskell Environment."
_ <- indentMessages $ insideProcess "cabal" ["--config-file=" ++ cabalConfig, "update"] Nothing
return ()
-- install cabal wrapper (in bin/ directory) inside virtual environment dir structure
installCabalWrapper :: Hsenv ()
installCabalWrapper = do
cabalConfig <- cabalConfigLocation
dirStructure <- hseDirStructure
hsEnvName' <- asks hsEnvName
let cabalWrapper = hsEnvBinDir dirStructure </> "cabal"
info $ concat [ "Installing cabal wrapper using "
, cabalConfig
, " at "
, cabalWrapper
]
let cabalWrapperContents = substs [ ("<CABAL_CONFIG>", cabalConfig)
, ("<HSENV_NAME>", fromMaybe "" hsEnvName')] cabalWrapperSkel
indentMessages $ do
trace "cabal wrapper contents:"
indentMessages $ mapM_ trace $ lines cabalWrapperContents
liftIO $ writeFile cabalWrapper cabalWrapperContents
liftIO $ makeExecutable cabalWrapper
installActivateScriptSupportFiles :: Hsenv ()
installActivateScriptSupportFiles = do
debug "installing supporting files"
dirStructure <- hseDirStructure
ghc <- asks ghcSource
indentMessages $ do
let pathVarPrependixLocation = hsEnvDir dirStructure </> "path_var_prependix"
pathVarElems =
case ghc of
System -> [hsEnvBinDir dirStructure, cabalBinDir dirStructure]
_ -> [ hsEnvBinDir dirStructure
, cabalBinDir dirStructure
, ghcBinDir dirStructure
]
pathVarPrependix = intercalate ":" pathVarElems
debug $ "installing path_var_prependix file to " ++ pathVarPrependixLocation
indentMessages $ trace $ "path_var_prependix contents: " ++ pathVarPrependix
liftIO $ writeFile pathVarPrependixLocation pathVarPrependix
ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
let ghcPackagePathVarLocation = hsEnvDir dirStructure </> "ghc_package_path_var"
ghcPackagePathVar = ghcPkgDbPath
debug $ "installing ghc_package_path_var file to " ++ ghcPackagePathVarLocation
indentMessages $ trace $ "path_var_prependix contents: " ++ ghcPackagePathVar
liftIO $ writeFile ghcPackagePathVarLocation ghcPackagePathVar
-- install activate script (in bin/ directory) inside virtual environment dir structure
installActivateScript :: Hsenv ()
installActivateScript = do
info "Installing activate script"
hsEnvName' <- asks hsEnvName
noModifyPS1 <- asks noPS1
dirStructure <- hseDirStructure
ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
let activateScript = hsEnvBinDir dirStructure </> "activate"
indentMessages $ debug $ "using location: " ++ activateScript
let activateScriptContents =
substs [ ("<HSENV_NAME>", fromMaybe "" hsEnvName')
, ("<HSENV_DIR>", hsEnvDir dirStructure)
, ("<HSENV>", hsEnv dirStructure)
, ("<GHC_PACKAGE_PATH>", ghcPkgDbPath)
, ("<HSENV_BIN_DIR>", hsEnvBinDir dirStructure)
, ("<CABAL_BIN_DIR>", cabalBinDir dirStructure)
, ("<GHC_BIN_DIR>", ghcBinDir dirStructure)
, ("<MODIFY_PS1>", if noModifyPS1 then "false" else "true")
] activateSkel
indentMessages $ do
trace "activate script contents:"
indentMessages $ mapM_ trace $ lines activateScriptContents
liftIO $ writeFile activateScript activateScriptContents
indentMessages installActivateScriptSupportFiles
-- install cabal's config file (in cabal/ directory) inside virtual environment dir structure
installCabalConfig :: Hsenv ()
installCabalConfig = do
cabalConfig <- cabalConfigLocation
dirStructure <- hseDirStructure
noSharingFlag <- asks noSharing
hackageCache <- indentMessages $
if noSharingFlag then do
info "Using private Hackage download cache directory"
return $ cabalDir dirStructure </> "packages"
else do
info "Using user-wide (~/.cabal/packages) Hackage download cache directory"
cabalInstallDir <- liftIO $ getAppUserDataDirectory "cabal"
return $ cabalInstallDir </> "packages"
info $ "Installing cabal config at " ++ cabalConfig
let cabalConfigContents = substs [ ("<GHC_PACKAGE_PATH>", ghcPackagePath dirStructure)
, ("<CABAL_DIR>", cabalDir dirStructure)
, ("<HACKAGE_CACHE>", hackageCache)
] cabalConfigSkel
indentMessages $ do
trace "cabal config contents:"
indentMessages $ mapM_ trace $ lines cabalConfigContents
liftIO $ writeFile cabalConfig cabalConfigContents
installSimpleWrappers :: Hsenv ()
installSimpleWrappers = mapM_ installSimpleWrapper simpleWrappers
installSimpleWrapper :: (String, String) -> Hsenv ()
installSimpleWrapper (targetFilename, skeleton) = do
ghcPkgDbPath <- indentMessages ghcPkgDbPathLocation
dirStructure <- hseDirStructure
let ghcWrapperContents =
substs [("<GHC_PACKAGE_PATH>", ghcPkgDbPath)] skeleton
ghcWrapper = hsEnvBinDir dirStructure </> targetFilename
liftIO $ writeFile ghcWrapper ghcWrapperContents
liftIO $ makeExecutable ghcWrapper
installProgSymlinks :: Hsenv ()
installProgSymlinks = mapM_ installSymlink extraProgs
extraProgs :: [String]
extraProgs = [ "alex"
, "ar"
, "c2hs"
, "cpphs"
, "ffihugs"
, "gcc"
, "greencard"
, "haddock"
, "happy"
, "hmake"
, "hpc"
, "hsc2hs"
, "hscolour"
, "hugs"
, "jhc"
, "ld"
, "lhc"
, "lhc-pkg"
, "nhc98"
, "pkg-config"
, "ranlib"
, "strip"
, "tar"
, "uhc"
]
installSymlink :: String -> Hsenv ()
installSymlink prog = do
dirStructure <- hseDirStructure
ghcSourceOpt <- asks ghcSource
mPrivateLoc <- case ghcSourceOpt of
System -> return Nothing
_ -> liftIO $ findExecutable $ ghcDir dirStructure </> "bin" </> prog
mSystemLoc <- liftIO $ findExecutable prog
let mProgLoc = mPrivateLoc `mplus` mSystemLoc
when (isJust mProgLoc) $ do
let Just progLoc = mProgLoc
liftIO $ createSymbolicLink progLoc $ hsEnvBinDir dirStructure </> prog
-- | Install a symbolic link to a skeleton script in hsenv's bin directory
symlinkToSkeleton :: String -- ^ Name of skeleton
-> String -- ^ Name of link
-> Hsenv ()
symlinkToSkeleton skel link = do
dirStructure <- hseDirStructure
let prependBinDir = (hsEnvBinDir dirStructure </>)
liftIO $ createSymbolicLink (prependBinDir skel) (prependBinDir link)
createDirStructure :: Hsenv ()
createDirStructure = do
dirStructure <- hseDirStructure
info "Creating Virtual Haskell directory structure"
indentMessages $ do
debug $ "hsenv directory: " ++ hsEnvDir dirStructure
liftIO $ createDirectory $ hsEnvDir dirStructure
debug $ "cabal directory: " ++ cabalDir dirStructure
liftIO $ createDirectory $ cabalDir dirStructure
debug $ "hsenv bin directory: " ++ hsEnvBinDir dirStructure
liftIO $ createDirectory $ hsEnvBinDir dirStructure
-- initialize private GHC package database inside virtual environment
initGhcDb :: Hsenv ()
initGhcDb = do
dirStructure <- hseDirStructure
info $ "Initializing GHC Package database at " ++ ghcPackagePath dirStructure
out <- indentMessages $ outsideGhcPkg ["--version"]
case lastMay $ words out of
Nothing -> throwError $ HsenvException $ "Couldn't extract ghc-pkg version number from: " ++ out
Just versionString -> do
indentMessages $ trace $ "Found version string: " ++ versionString
version <- parseVersion versionString
let ghc_6_12_1_version = Version [6,12,1] []
if version < ghc_6_12_1_version then do
indentMessages $ debug "Detected GHC older than 6.12, initializing GHC_PACKAGE_PATH to file with '[]'"
liftIO $ writeFile (ghcPackagePath dirStructure) "[]"
else do
_ <- indentMessages $ outsideGhcPkg ["init", ghcPackagePath dirStructure]
return ()
-- copy optional packages and don't fail completely if this copying fails
-- some packages mail fail to copy and it's not fatal (e.g. older GHCs don't have haskell2010)
transplantOptionalPackage :: String -> Hsenv ()
transplantOptionalPackage name = transplantPackage (PackageName name) `catchError` handler
where handler e = do
warning $ "Failed to copy optional package " ++ name ++ " from system's GHC: "
indentMessages $ warning $ getExceptionMessage e
-- copy base system
-- base - needed for ghci and everything else
-- Cabal - needed to install non-trivial cabal packages with cabal-install
-- haskell98 - some packages need it but they don't specify it (seems it's an implicit dependancy)
-- haskell2010 - maybe it's similar to haskell98?
-- ghc and ghc-binary - two packages that are provided with GHC and cannot be installed any other way
-- also include dependant packages of all the above
-- when using GHC from tarball, just reuse its package database
-- cannot do the same when using system's GHC, because there might be additional packages installed
-- then it wouldn't be possible to work on them insie virtual environment
copyBaseSystem :: Hsenv ()
copyBaseSystem = do
info "Copying necessary packages from original GHC package database"
indentMessages $ do
ghc <- asks ghcSource
case ghc of
System -> do
transplantPackage $ PackageName "base"
transplantPackage $ PackageName "Cabal"
mapM_ transplantOptionalPackage ["haskell98", "haskell2010", "ghc", "ghc-binary"]
_ -> debug "Using external GHC - nothing to copy, Virtual environment will reuse GHC package database"
installGhc :: Hsenv ()
installGhc = do
info "Installing GHC"
ghc <- asks ghcSource
case ghc of
System -> indentMessages $ debug "Using system version of GHC - nothing to install."
Tarball tarballPath -> indentMessages $ installExternalGhc tarballPath
Url url -> indentMessages $ installRemoteGhc url
Release tag -> indentMessages $ installReleasedGhc tag
installExternalGhc :: FilePath -> Hsenv ()
installExternalGhc tarballPath = do
info $ "Installing GHC from " ++ tarballPath
indentMessages $ do
dirStructure <- hseDirStructure
tmpGhcDir <- liftIO $ createTemporaryDirectory (hsEnv dirStructure) "ghc"
debug $ "Unpacking GHC tarball to " ++ tmpGhcDir
_ <- indentMessages $ outsideProcess' "tar" ["xf", tarballPath, "-C", tmpGhcDir, "--strip-components", "1"]
let configureScript = tmpGhcDir </> "configure"
debug $ "Configuring GHC with prefix " ++ ghcDir dirStructure
cwd <- liftIO getCurrentDirectory
liftIO $ setCurrentDirectory tmpGhcDir
make <- asks makeCmd
let configureAndInstall = do
_ <- indentMessages $ outsideProcess' configureScript ["--prefix=" ++ ghcDir dirStructure]
debug $ "Installing GHC with " ++ make ++ " install"
_ <- indentMessages $ outsideProcess' make ["install"]
return ()
configureAndInstall `finally` liftIO (setCurrentDirectory cwd)
liftIO $ removeDirectoryRecursive tmpGhcDir
return ()
-- Download a file over HTTP using streams, so it
-- has constant memory allocation.
downloadFile :: URL -> FilePath -> Hsenv ()
downloadFile url name = do
m_ex <- liftIO $ get url $ \response inStream ->
case getStatusCode response of
200 -> S.withFileAsOutput name (S.connect inStream) >> return Nothing
code -> return $ Just $ HsenvException $
"Failed to download "
++ name
++ ": http response returned "
++ show code
maybe (return ()) throwError m_ex
installRemoteGhc :: String -> Hsenv ()
installRemoteGhc url = do
dirStructure <- hseDirStructure
downloadDir <- liftIO $ createTemporaryDirectory (hsEnv dirStructure) "ghc-download"
let tarball = downloadDir </> "tarball"
debug $ "Downloading GHC from " ++ url
downloadFile (C8.pack url) tarball
installExternalGhc tarball
liftIO $ removeDirectoryRecursive downloadDir
return ()
installReleasedGhc :: String -> Hsenv ()
installReleasedGhc tag = do
let url = "http://www.haskell.org/ghc/dist/" ++ tag ++ "/ghc-" ++ tag ++ "-" ++ platform ++ ".tar.bz2"
installRemoteGhc url
platform :: String
platform = intercalate "-" [arch, if os == "darwin" then "apple" else "unknown", os]