module Distribution.MacOSX (
appBundleBuildHook, appBundleInstallHook,
makeAppBundle,
MacApp(..),
ChaseDeps(..),
Exclusions,
defaultExclusions
) where
import Control.Exception
import Prelude hiding ( catch )
import Control.Monad (forM_, when)
import Data.String.Utils (replace)
import Distribution.PackageDescription (PackageDescription(..),
Executable(..))
import Distribution.Simple
import Distribution.Simple.InstallDirs (bindir, prefix, CopyDest(NoCopyDest))
import Distribution.Simple.LocalBuildInfo (absoluteInstallDirs, LocalBuildInfo(..))
import Distribution.Simple.Setup (BuildFlags, InstallFlags,
fromFlagOrDefault, installVerbosity
)
import Distribution.Simple.Utils (installDirectoryContents, installExecutableFile)
import Distribution.Verbosity (normal)
import System.Cmd (system)
import System.FilePath
import System.Info (os)
import System.Directory (copyFile, createDirectoryIfMissing)
import System.Exit
import Distribution.MacOSX.AppBuildInfo
import Distribution.MacOSX.Common
import Distribution.MacOSX.Dependencies
appBundleBuildHook ::
[MacApp]
-> Args
-> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
appBundleBuildHook apps _ _ pkg localb =
if isMacOS
then forM_ apps' $ makeAppBundle . toAppBuildInfo localb
else putStrLn "Not OS X, so not building an application bundle."
where apps' = case apps of
[] -> map mkDefault $ executables pkg
xs -> xs
mkDefault x = MacApp (exeName x) Nothing Nothing [] [] DoNotChase
appBundleInstallHook ::
[MacApp]
-> Args
-> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO ()
appBundleInstallHook apps _ iflags pkg localb = when isMacOS $ do
let verbosity = fromFlagOrDefault normal (installVerbosity iflags)
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 = "`dirname $0`" </> "../Applications"
</> takeFileName appPathSrc
</> "Contents/MacOS" </> appName 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
applicationsDir = prefix installDir </> "Applications"
isMacOS :: Bool
isMacOS = os == "darwin"
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"
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 = replace "$program" (appName app) plistTemplate
pl' = replace "$iconPath" (takeFileName icPath) pl
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 ()
osxIncantations ::
FilePath
-> MacApp -> IO ()
osxIncantations appPath app =
do putStrLn "Running Rez, etc."
ExitSuccess <- system $ rez ++ " Carbon.r -o " ++
appPath </> pathInApp app (appName app)
writeFile (appPath </> "PkgInfo") "APPL????"
ExitSuccess <- system $ setFile ++ " -a C " ++ appPath </> "Contents"
return ()
rez :: FilePath
rez = "/Developer/Tools/Rez"
setFile :: FilePath
setFile = "/Developer/Tools/SetFile"
plistTemplate :: String
plistTemplate = "\
\<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
\<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">\n\
\<plist version=\"0.9\">\n\
\<dict>\n\
\<key>CFBundleInfoDictionaryVersion</key>\n\
\<string>6.0</string>\n\
\<key>CFBundleIdentifier</key>\n\
\<string>org.haskell.$program</string>\n\
\<key>CFBundleDevelopmentRegion</key>\n\
\<string>English</string>\n\
\<key>CFBundleExecutable</key>\n\
\<string>$program</string>\n\
\<key>CFBundleIconFile</key>\n\
\<string>$iconPath</string>\n\
\<key>CFBundleName</key>\n\
\<string>$program</string>\n\
\<key>CFBundlePackageType</key>\n\
\<string>APPL</string>\n\
\<key>CFBundleSignature</key>\n\
\<string>????</string>\n\
\<key>CFBundleVersion</key>\n\
\<string>1.0</string>\n\
\<key>CFBundleShortVersionString</key>\n\
\<string>1.0</string>\n\
\<key>CFBundleGetInfoString</key>\n\
\<string>$program, bundled by cabal-macosx</string>\n\
\<key>LSRequiresCarbon</key>\n\
\<true/>\n\
\<key>CSResourcesFileMapped</key>\n\
\<true/>\n\
\</dict>\n\
\</plist>"