{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
module Distribution.MacOSX (
appBundleBuildHook,
appBundleInstallHook,
appBundleCopyHook,
makeAppBundle,
MacApp(..),
ChaseDeps(..),
Exclusions,
defaultExclusions
) where
import Control.Exception
import Prelude hiding ( catch )
import Control.Monad (forM_, when)
import Data.List ( isPrefixOf )
import System.Cmd (system)
import System.FilePath
import System.Directory (copyFile, createDirectoryIfMissing, getHomeDirectory)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Distribution.PackageDescription (PackageDescription(..))
import Distribution.Simple
import Distribution.Simple.InstallDirs (bindir, prefix, CopyDest(NoCopyDest))
import Distribution.Simple.LocalBuildInfo (absoluteInstallDirs, LocalBuildInfo(..))
import Distribution.Simple.Setup (BuildFlags, InstallFlags, CopyFlags, fromFlagOrDefault, installVerbosity, copyVerbosity)
import Distribution.Simple.Utils (installDirectoryContents, installExecutableFile)
#if MIN_VERSION_Cabal(1,18,0)
import Distribution.System (Platform (..), OS (OSX))
#else
import System.Info (os)
#endif
import Distribution.Verbosity (normal, Verbosity)
import Distribution.MacOSX.Internal
import Distribution.MacOSX.AppBuildInfo
import Distribution.MacOSX.Common
import Distribution.MacOSX.Dependencies
import Distribution.MacOSX.Templates
appBundleBuildHook ::
[MacApp]
-> Args
-> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
appBundleBuildHook apps _ _ pkg localb =
#if MIN_VERSION_Cabal(1,18,0)
if isMacOS localb
#else
if isMacOS
#endif
then do let buildDirLbi = buildDir localb
let macApps = getMacAppsForBuildableExecutors apps (executables pkg)
forM_ macApps (makeAppBundle . createAppBuildInfo buildDirLbi)
else putStrLn "Not OS X, so not building an application bundle."
appBundleInstallHook ::
[MacApp]
-> Args
-> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO ()
appBundleInstallHook apps _ iflags =
appBundleInstallOrCopyHook apps
(fromFlagOrDefault normal (installVerbosity iflags))
{-# DEPRECATED appBundleInstallHook "Use appBundleCopyHook instead" #-}
appBundleCopyHook ::
[MacApp]
-> Args
-> CopyFlags -> PackageDescription -> LocalBuildInfo -> IO ()
appBundleCopyHook apps _ cflags =
appBundleInstallOrCopyHook apps
(fromFlagOrDefault normal (copyVerbosity cflags))
appBundleInstallOrCopyHook ::
[MacApp]
-> Verbosity
-> PackageDescription -> LocalBuildInfo -> IO ()
#if MIN_VERSION_Cabal(1,18,0)
appBundleInstallOrCopyHook apps verbosity pkg localb = when (isMacOS localb) $ do
#else
appBundleInstallOrCopyHook apps verbosity pkg localb = when isMacOS $ do
#endif
libraryHaskell <- flip fmap getHomeDirectory $ (</> "Library/Haskell")
let standardPrefix = (libraryHaskell ++ "/") `isPrefixOf` prefix installDir
let applicationsDir = if standardPrefix
then libraryHaskell </> "Applications"
else prefix installDir </> "Applications"
createDirectoryIfMissing False applicationsDir
forM_ apps $ \app -> do
let appInfo = toAppBuildInfo localb app
appPathSrc = abAppPath appInfo
appPathTgt = applicationsDir </> takeFileName appPathSrc
exe ap = ap </> pathInApp app (appName app)
installDirectoryContents verbosity appPathSrc appPathTgt
installExecutableFile verbosity (exe appPathSrc) (exe appPathTgt)
let script = if standardPrefix
then bundleScriptLibraryHaskell localb app
else bundleScriptElsewhere localb app
scriptFileSrc = buildDir localb </> "_" ++ appName app <.> "sh"
scriptFileTgt = bindir installDir </> appName app
writeFile scriptFileSrc script
installExecutableFile verbosity scriptFileSrc scriptFileTgt
where
installDir = absoluteInstallDirs pkg localb NoCopyDest
bundleScriptLibraryHaskell :: LocalBuildInfo -> MacApp -> String
bundleScriptLibraryHaskell localb app = unlines
[ "#!/bin/bash"
, "$HOME/Library/Haskell/Applications"
</> takeFileName appPathSrc
</> "Contents/MacOS" </> appName app ++ " \"$@\""
]
where
appInfo = toAppBuildInfo localb app
appPathSrc = abAppPath appInfo
bundleScriptElsewhere :: LocalBuildInfo -> MacApp -> String
bundleScriptElsewhere localb app = unlines
[ "#!/bin/bash"
, "MAX_DEPTH=256"
, "COUNTER=0"
, "ZERO=$0"
, "STATUS=0"
, ""
, "# The counter is just a safeguard in case I'd done something silly"
, "while [ $STATUS -eq 0 -a $COUNTER -lt $MAX_DEPTH ]; do"
, " COUNTER=$(($COUNTER+1))"
, " NZERO=`readlink $ZERO`; STATUS=$?"
, " if [ $STATUS -eq 0 ]; then"
, " # go to my parent dir"
, " pushd $(dirname $ZERO) > /dev/null"
, " # now follow the symlink if at all"
, " pushd $(dirname $NZERO) > /dev/null"
, " ZERO=$PWD/$(basename $NZERO)"
, " popd > /dev/null"
, " popd > /dev/null"
, " fi"
, "done"
, "if [ $COUNTER -ge $MAX_DEPTH ]; then"
, " echo >&2 Urk! exceeded symlink depth of $MAX_DEPTH trying to dereference $0"
, " exit 1"
, "fi"
, "`dirname $ZERO`" </> "../Applications"
</> takeFileName appPathSrc
</> "Contents/MacOS" </> appName app ++ " \"$@\""
]
where
appInfo = toAppBuildInfo localb app
appPathSrc = abAppPath appInfo
#if MIN_VERSION_Cabal(1,18,0)
isMacOS :: LocalBuildInfo -> Bool
isMacOS localb = case hostPlatform localb of
Platform _ OSX -> True
_ -> False
#else
isMacOS :: Bool
isMacOS = os == "darwin"
#endif
makeAppBundle :: AppBuildInfo -> IO ()
makeAppBundle appInfo@(AppBuildInfo appPath _ app) =
do _ <- createAppDir appInfo
maybeCopyPlist appPath app
maybeCopyIcon appPath app
`catch` \(SomeException err) ->
putStrLn $ "Warning: could not set up icon for " ++ appName app ++ ": " ++ show err
includeResources appPath app
includeDependencies appPath app
osxIncantations appPath app
createAppDir :: AppBuildInfo -> IO FilePath
createAppDir (AppBuildInfo appPath exeSrc app) =
do putStrLn $ "Creating application bundle directory " ++ appPath
createDirectoryIfMissing False appPath
createDirectoryIfMissing True $ takeDirectory exeDest
createDirectoryIfMissing True $ appPath </> "Contents/Resources"
putStrLn $ "Copying executable " ++ appName app ++ " into place from " ++ exeSrc ++ " to " ++ exeDest
copyFile exeSrc exeDest
return appPath
where exeDest = appPath </> pathInApp app (appName app)
includeResources ::
FilePath
-> MacApp -> IO ()
includeResources appPath app = mapM_ includeResource $ resources app
where includeResource :: FilePath -> IO ()
includeResource p =
do let pDest = appPath </> pathInApp app p
putStrLn $ "Copying resource " ++ p ++ " to " ++ pDest
createDirectoryIfMissing True $ takeDirectory pDest
copyFile p $ pDest
return ()
maybeCopyPlist ::
FilePath
-> MacApp -> IO ()
maybeCopyPlist appPath app =
case appPlist app of
Just plPath -> do
putStrLn $ "Copying " ++ plPath ++ " to " ++ plDest
copyFile plPath plDest
Nothing -> case appIcon app of
Just icPath ->
do
let pl = T.replace "$program" (T.pack (appName app)) plistTemplate
pl' = T.replace "$iconPath" (T.pack (takeFileName icPath)) pl
T.writeFile plDest pl'
return ()
Nothing -> return ()
where plDest = appPath </> "Contents/Info.plist"
maybeCopyIcon ::
FilePath
-> MacApp -> IO ()
maybeCopyIcon appPath app =
case appIcon app of
Just icPath ->
do putStrLn $ "Copying " ++ icPath ++ " to app's icon"
copyFile icPath $
appPath </> "Contents/Resources" </> takeFileName icPath
Nothing -> return ()