module MacOSX.Build ( useMacOSXBuildHook, addExecutableAppPackaging, ArtifactPackagingPhase(..), AppPackageDescription(..) ) where import MacOSX.Build.FileSystemUtils import Distribution.Simple import Distribution.Setup (BuildFlags(..)) import Distribution.PackageDescription ( PackageDescription(..), BuildInfo(..), withLib, setupMessage, Executable(..), withExe, Library(..), libModules, hcOptions ) import Distribution.Simple.LocalBuildInfo ( LocalBuildInfo(..), autogenModulesDir, mkLibDir, mkIncludeDir ) import Distribution.Simple.Utils( rawSystemExit, rawSystemPath, rawSystemVerbose, maybeExit, xargs, die, dirOf, moduleToFilePath, smartCopySources, findFile, copyFileVerbose, mkLibName, mkProfLibName, dotToSep ) import Distribution.Package ( PackageIdentifier(..), showPackageId ) import Distribution.Program ( rawSystemProgram, ranlibProgram, Program(..), ProgramConfiguration(..), ProgramLocation(..), lookupProgram, arProgram ) import Distribution.Compiler ( Compiler(..), CompilerFlavor(..), extensionsToGHCFlag ) import Distribution.Version ( Version(..) ) import qualified Distribution.Simple.GHCPackageConfig as GHC ( localPackageConfig, canReadLocalPackageConfig ) import Language.Haskell.Extension (Extension(..)) import Control.Monad ( unless, when ) import Data.List ( isSuffixOf, nub ) import System.Directory ( removeFile, renameFile, getDirectoryContents, doesFileExist, createDirectoryIfMissing, copyFile ) import System.Exit (ExitCode(..)) import System.FilePath import System.Exit import System.Cmd (rawSystem) import System.Posix.Files ( getFileStatus, modificationTime, setFileTimes ) import Control.Exception (try) mac_os_sdk = "/Developer/SDKs/MacOSX10.4u.sdk/" mac_os_version = "10.4" compile_C_source build_info source odir verbose = do createDirectoryIfMissing True odir let subdirectory = takeDirectory source if(length subdirectory /= 0) then createDirectoryIfMissing True $ odir subdirectory else return () let output_path = odir (replaceExtension source ".o") source_mod_time <- (getFileStatus source) >>= (return . modificationTime) should_build <- do possible_mod_time <- try $ (getFileStatus output_path) >>= (return . modificationTime) case possible_mod_time of Left _ -> return True Right mod_time -> if mod_time < source_mod_time then return True else return False if should_build then do let cArgs = ["-isysroot" ++ mac_os_sdk] ++ ["-mmacosx-version-min=" ++ mac_os_version] ++ ["-I./"] ++ ["-F" ++ framework | framework <- frameworks build_info] ++ ["-I" ++ dir | dir <- includeDirs build_info] ++ ["-I" ++ "/usr/local/lib/ghc-6.6.1/include/"] ++ [opt | opt <- ccOptions build_info] ++ ["-o", output_path, "-c"] ++ (if verbose > 4 then ["-v"] else []) putStrLn $ "Compiling: " ++ source let cxx = "/usr/bin/g++-4.0" cc = "/usr/bin/gcc-4.0" let compiler = case takeExtension source of ".cpp" -> cxx ".mm" -> cxx ".c" -> cc ".m" -> cc "" -> cc rawSystemExit verbose compiler (cArgs ++ [source]) setFileTimes output_path source_mod_time source_mod_time else putStrLn $ "Skipping compile: " ++ source -- ----------------------------------------------------------------------------- -- Building -- |Building for GHC. If .ghc-packages exists and is readable, add -- it to the command-line. defaultBuild :: PackageDescription -> LocalBuildInfo -> Int -> IO () defaultBuild pkg_descr lbi verbose = do let pref = buildDir lbi let ghcPath = compilerPath (compiler lbi) ifVanillaLib forceVanilla = when (forceVanilla || withVanillaLib lbi) ifProfLib = when (withProfLib lbi) ifGHCiLib = when (withGHCiLib lbi) -- GHC versions prior to 6.4 didn't have the user package database, -- so we fake it. TODO: This can go away in due course. pkg_conf <- if versionBranch (compilerVersion (compiler lbi)) >= [6,4] then return [] else do pkgConf <- GHC.localPackageConfig pkgConfReadable <- GHC.canReadLocalPackageConfig if pkgConfReadable then return ["-package-conf", pkgConf] else return [] -- Build lib withLib pkg_descr () $ \lib -> do when (verbose > 3) (putStrLn "Building library...") let libBi = libBuildInfo lib libTargetDir = pref forceVanillaLib = TemplateHaskell `elem` extensions libBi -- TH always needs vanilla libs, even when building for profiling createDirectoryIfMissing True libTargetDir -- put hi-boot files into place for mutually recurive modules smartCopySources verbose (hsSourceDirs libBi) libTargetDir (libModules pkg_descr) ["hi-boot"] False False let ghc_vers = compilerVersion (compiler lbi) packageId | versionBranch ghc_vers >= [6,4] = showPackageId (package pkg_descr) | otherwise = pkgName (package pkg_descr) -- Only use the version number with ghc-6.4 and later ghcArgs = pkg_conf ++ ["-package-name", packageId ] ++ (if splitObjs lbi then ["-split-objs"] else []) ++ constructGHCCmdLine lbi libBi libTargetDir verbose ++ (libModules pkg_descr) ghcArgsProf = ghcArgs ++ ["-prof", "-hisuf", "p_hi", "-osuf", "p_o" ] ++ ghcProfOptions libBi unless (null (libModules pkg_descr)) $ do ifVanillaLib forceVanillaLib (rawSystemExit verbose ghcPath ghcArgs) ifProfLib (rawSystemExit verbose ghcPath ghcArgsProf) -- build any C sources unless (null (cSources libBi)) $ do when (verbose > 3) (putStrLn "Building C Sources...") -- FIX: similar 'versionBranch' logic duplicated below. refactor for code sharing sequence_ [compile_C_source libBi c pref verbose | c <- cSources libBi] -- link: when (verbose > 3) (putStrLn "cabal-linking...") let cObjs = [ path <.> ".o" | (path,_) <- (map splitExtensions (cSources libBi)) ] libName = mkLibName pref (showPackageId (package pkg_descr)) profLibName = mkProfLibName pref (showPackageId (package pkg_descr)) ghciLibName = mkGHCiLibName pref (showPackageId (package pkg_descr)) stubObjs <- sequence [moduleToFilePath [libTargetDir] (x ++"_stub") [".o"] | x <- libModules pkg_descr ] >>= return . concat stubProfObjs <- sequence [moduleToFilePath [libTargetDir] (x ++"_stub") [".p_o"] | x <- libModules pkg_descr ] >>= return . concat hObjs <- getHaskellObjects pkg_descr libBi lbi pref ".o" hProfObjs <- if (withProfLib lbi) then getHaskellObjects pkg_descr libBi lbi pref (".p_o") else return [] try (removeFile libName) -- first remove library if it exists try (removeFile profLibName) -- first remove library if it exists try (removeFile ghciLibName) -- first remove library if it exists unless (null hObjs && null cObjs && null stubObjs) $ do let arArgs = ["q"++ (if verbose > 4 then "v" else "")] ++ [libName] arObjArgs = hObjs ++ map (pref ) cObjs ++ stubObjs arProfArgs = ["q"++ (if verbose > 4 then "v" else "")] ++ [profLibName] arProfObjArgs = hProfObjs ++ map (pref ) cObjs ++ stubProfObjs ldArgs = ["-r"] ++ ["-x"] -- FIXME: only some systems's ld support the "-x" flag ++ ["-o", ghciLibName <.> "tmp"] ldObjArgs = hObjs ++ map (pref ) cObjs ++ stubObjs ld = "ld" rawSystemLd = rawSystemPath --TODO: discover this at configure time on unix maxCommandLineSize = 30 * 1024 runLd ld args = do exists <- doesFileExist ghciLibName status <- rawSystemLd verbose ld (args ++ if exists then [ghciLibName] else []) when (status == ExitSuccess) (renameFile (ghciLibName <.> "tmp") ghciLibName) return status ifVanillaLib False $ maybeExit $ xargs maxCommandLineSize (rawSystemPath verbose) "ar" arArgs arObjArgs ifProfLib $ maybeExit $ xargs maxCommandLineSize (rawSystemPath verbose) "ar" arProfArgs arProfObjArgs ifGHCiLib $ maybeExit $ xargs maxCommandLineSize runLd ld ldArgs ldObjArgs -- build any executables withExe pkg_descr $ \ (Executable exeName' modPath exeBi) -> do when (verbose > 3) (putStrLn $ "Building executable: " ++ exeName' ++ "...") -- exeNameReal, the name that GHC really uses (with .exe on Windows) let exeNameReal = exeName' let targetDir = pref exeName' let exeDir = joinPath [targetDir, (exeName' ++ "-tmp")] createDirectoryIfMissing True targetDir createDirectoryIfMissing True exeDir -- put hi-boot files into place for mutually recursive modules -- FIX: what about exeName.hi-boot? smartCopySources verbose (hsSourceDirs exeBi) exeDir (otherModules exeBi) ["hi-boot"] False False -- build executables unless (null (cSources exeBi)) $ do when (verbose > 3) (putStrLn "Building C Sources.") sequence_ [compile_C_source exeBi c exeDir verbose | c <- cSources exeBi] srcMainFile <- findFile (hsSourceDirs exeBi) modPath let cObjs = [ path <.> ".o" | (path, _) <- (map splitExtensions (cSources exeBi)) ] let binArgs linkExe profExe = pkg_conf ++ ["-I"++pref] ++ (if linkExe then ["-o", targetDir exeNameReal] else ["-c"]) ++ constructGHCCmdLine lbi exeBi exeDir verbose ++ [exeDir x | x <- cObjs] ++ [srcMainFile] ++ ldOptions exeBi ++ ["-framework" ++ framework | framework <- frameworks exeBi] ++ ["-l"++lib | lib <- extraLibs exeBi] ++ ["/usr/lib/libstdc++-static.a", "-optl-static-libgcc"] ++ ["-L"++libDir | libDir <- extraLibDirs exeBi] ++ if profExe then ["-prof", "-hisuf", "p_hi", "-osuf", "p_o" ] ++ ghcProfOptions exeBi else [] try (removeFile $ targetDir exeNameReal) -- first remove library if it exists -- For building exe's for profiling that use TH we actually -- have to build twice, once without profiling and the again -- with profiling. This is because the code that TH needs to -- run at compile time needs to be the vanilla ABI so it can -- be loaded up and run by the compiler. when (withProfExe lbi && TemplateHaskell `elem` extensions exeBi) (rawSystemExit verbose ghcPath (binArgs False False)) rawSystemExit verbose ghcPath (binArgs True (withProfExe lbi)) -- when using -split-objs, we need to search for object files in the -- Module_split directory for each module. getHaskellObjects :: PackageDescription -> BuildInfo -> LocalBuildInfo -> FilePath -> String -> IO [FilePath] getHaskellObjects pkg_descr libBi lbi pref wanted_obj_ext | splitObjs lbi = do let dirs = [ pref (dotToSep x ++ "_split") | x <- libModules pkg_descr ] objss <- mapM getDirectoryContents dirs let objs = [ dir obj | (objs,dir) <- zip objss dirs, obj <- objs, let (_,obj_ext) = splitExtensions obj, wanted_obj_ext == obj_ext ] return objs | otherwise = return [ pref (dotToSep x) <.> wanted_obj_ext | x <- libModules pkg_descr ] constructGHCCmdLine :: LocalBuildInfo -> BuildInfo -> FilePath -> Int -- verbosity level -> [String] constructGHCCmdLine lbi bi odir verbose = ["--make"] ++ (if verbose > 4 then ["-v"] else []) -- Unsupported extensions have already been checked by configure ++ (if compilerVersion (compiler lbi) > Version [6,4] [] then ["-hide-all-packages"] else []) ++ ["-i"] ++ ["-i" ++ autogenModulesDir lbi] ++ ["-i" ++ l | l <- nub (hsSourceDirs bi)] ++ ["-I" ++ dir | dir <- includeDirs bi] ++ ["-optc" ++ opt | opt <- ccOptions bi] ++ ["-optc" ++ opt | opt <- ["-isysroot", mac_os_sdk]] ++ ["-optc-mmacosx-version-min=" ++ mac_os_version] ++ ["-optl" ++ opt | opt <- ["-isysroot", mac_os_sdk, "-Wl,-syslibroot," ++ mac_os_sdk]] ++ [ "-#include \"" ++ inc ++ "\"" | inc <- includes bi ++ installIncludes bi ] ++ [ "-odir", odir, "-hidir", odir ] ++ ["-static"] ++ (concat [ ["-package", showPackageId pkg] | pkg <- packageDeps lbi ]) ++ hcOptions GHC (options bi) ++ snd (extensionsToGHCFlag (extensions bi)) mkGHCiLibName :: FilePath -- ^file Prefix -> String -- ^library name. -> String mkGHCiLibName pref lib = pref ("HS" ++ lib ++ ".o") fixedDefaultBuild :: PackageDescription -> LocalBuildInfo -> Maybe UserHooks -> BuildFlags -> IO () fixedDefaultBuild package_description local_build_info _ flags = defaultBuild package_description local_build_info (buildVerbose flags) useMacOSXBuildHook hooks = hooks { buildHook = fixedDefaultBuild } data ArtifactPackagingPhase = ResourceCopy String String | ExecutableCopy String String data AppPackageDescription = AppPackageDescription { executable :: String, infoPlist :: String, packaging_phases :: [ArtifactPackagingPhase] } appPackageExe :: AppPackageDescription -> Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ExitCode appPackageExe app_desc _ build_flags package_desc local_build_info = do let executable_name = executable app_desc putStrLn $ "Building application package for: " ++ executable_name let destination_dir = (buildDir local_build_info) executable_name let source_executable_path = destination_dir executable_name let app_package_path = destination_dir executable_name <.> ".app" remove app_package_path createDirectoryIfMissing True app_package_path mapM_ (createDirectoryIfMissing True) $ map (combine app_package_path) [ "Contents", "Contents/MacOS", "Contents/Resources", "Contents/Frameworks" ] let dest_executable_dir = app_package_path "Contents/MacOS" let resource_dest_base_dir = app_package_path "Contents/Resources" let dest_executable_path = dest_executable_dir executable_name copyFile source_executable_path dest_executable_path let source_info_plist = infoPlist app_desc let dest_info_plist = app_package_path "Contents" source_info_plist copyFile source_info_plist dest_info_plist let handle_phase (ResourceCopy from to) = do let dest_path = resource_dest_base_dir to copy from dest_path handle_phase (ExecutableCopy from to) = do let dest_path = dest_executable_dir to copy from dest_path (flip mapM_) (packaging_phases app_desc) handle_phase -- In order to distribute a fairly stand-alone application binary the GMP -- framework needs to be installed in the application bundle and the application -- itself modified to reference the framework from this location. -- Copy the GMP.framework from the system's install to within the application rawSystem "/bin/rm" $ ["-rf", app_package_path "Contents/Frameworks/GMP.framework"] copy "/Library/Frameworks/GMP.framework" $ app_package_path "Contents/Frameworks/GMP.framework" -- Change the install path the application uses. rawSystem "/usr/bin/install_name_tool" [ "-change", "GMP.framework/Versions/A/GMP", "@executable_path/../Frameworks/GMP.framework/Versions/A/GMP", dest_executable_path ] return ExitSuccess addExecutableAppPackaging app_package_description hooks = let current_post_build = postBuild hooks new_post_build args build_flags package build_info = do status <- current_post_build args build_flags package build_info if(status == ExitSuccess) then appPackageExe app_package_description args build_flags package build_info else return status in hooks { postBuild = new_post_build }