{-# LANGUAGE CPP #-} import Prelude hiding (read) import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString as BS import Data.Version import Data.List (foldl') import Data.Maybe (fromJust) import Codec.Compression.BZip import Codec.Archive.Tar import Haste.Environment import Haste.Version import Control.Shell import Control.Shell.Concurrent import Control.Shell.Download import Data.Char (isDigit) import Haste.Args import System.Console.GetOpt import GHC.Paths (libdir) import System.Info (os) import System.Directory (copyPermissions) #if __GLASGOW_HASKELL__ >= 710 ghcMajor = "7.10" libDir = "ghc-7.10" primVersion = "0.4.0.0" #else ghcMajor = "7.8" libDir = "ghc-7.8" primVersion = "0.3.0.0" #endif data HasteCabal = Download | Prebuilt FilePath | Source (Maybe FilePath) data Cfg = Cfg { getLibs :: Bool, getClosure :: Bool, useLocalLibs :: Bool, tracePrimops :: Bool, forceBoot :: Bool, initialPortableBoot :: Bool, getHasteCabal :: HasteCabal, verbose :: Bool } defCfg :: Cfg #ifdef PORTABLE defCfg = Cfg { getLibs = False, getClosure = False, useLocalLibs = False, tracePrimops = False, forceBoot = False, initialPortableBoot = False, getHasteCabal = Download, verbose = False } #else defCfg = Cfg { getLibs = True, getClosure = True, useLocalLibs = False, tracePrimops = False, forceBoot = False, initialPortableBoot = False, getHasteCabal = Download, verbose = False } #endif devBoot :: Cfg -> Cfg devBoot cfg = cfg { useLocalLibs = True, forceBoot = True, getClosure = False, getHasteCabal = Source Nothing } setInitialPortableBoot :: Cfg -> Cfg setInitialPortableBoot cfg = cfg { getLibs = True, useLocalLibs = True, forceBoot = True, getClosure = True, initialPortableBoot = True, getHasteCabal = Download } specs :: [OptDescr (Cfg -> Cfg)] specs = [ #ifndef PORTABLE Option "" ["dev"] (NoArg devBoot) $ "Boot Haste for development. Implies --force " ++ "--local --no-closure --build-haste-cabal" , Option "" ["force"] #else Option "" ["force"] #endif (NoArg $ \cfg -> cfg {forceBoot = True}) $ "Re-boot Haste even if already properly booted." , Option "" ["initial"] (NoArg setInitialPortableBoot) $ "Prepare boot files for binary distribution. Should only ever " ++ "be called by the release build scripts, never by users.\n" ++ "Implies --local --force." #ifndef PORTABLE , Option "" ["local"] (NoArg $ \cfg -> cfg {useLocalLibs = True}) $ "Use libraries from source repository rather than " ++ "downloading a matching set from the Internet. " ++ "This is nearly always necessary when installing " ++ "Haste from Git rather than from Hackage. " ++ "When using --local, your current working directory " ++ "must be the root of the Haste source tree." , Option "" ["no-closure"] (NoArg $ \cfg -> cfg {getClosure = False}) $ "Don't download Closure compiler. You won't be able " ++ "to use --opt-minify, unless you manually " ++ "give hastec the path to compiler.jar." , Option "" ["download-haste-cabal"] (NoArg $ \cfg -> cfg {getHasteCabal = Download}) $ "Download pre-built haste-cabal for your platform. " ++ "This is the default behaviour." , Option "" ["with-haste-cabal"] (ReqArg (\f cfg -> cfg {getHasteCabal = Prebuilt f}) "FILE") $ "Use FILE to provide haste-cabal." , Option "" ["build-haste-cabal"] (OptArg (\md cfg -> cfg {getHasteCabal = Source md}) "DIR") $ "Build haste-cabal from the source contained in DIR. " ++ "If DIR is not specified, `../cabal/' is assumed." , Option "" ["no-libs"] (NoArg $ \cfg -> cfg {getLibs = False}) $ "Don't install any libraries. This is probably not " ++ "what you want." , Option "" ["trace-primops"] (NoArg $ \cfg -> cfg {tracePrimops = True}) $ "Build standard libs for tracing of primitive " ++ "operations. Only use if you're debugging the code " ++ "generator." #endif , Option "v" ["verbose"] (NoArg $ \cfg -> cfg {verbose = True}) $ "Print absolutely everything." ] hdr :: String hdr = "Fetch, build and install all libraries necessary to use Haste.\n" data CabalOp = Configure | Build | Install | Clean | Do String main :: IO () main = shell_ $ do when ("--help" `elem` cmdline || "-?" `elem` cmdline) $ do echo $ printHelp hdr specs exit case getOpt Permute specs cmdline of (cfgs, [], []) -> do let cfg = foldl' (flip (.)) id cfgs defCfg when (hasteNeedsReboot || forceBoot cfg) $ do if useLocalLibs cfg then bootHaste cfg "." else withTempDirectory "haste" $ bootHaste cfg (cfgs, nopts, errs) -> do let errors = errs ++ map (\x -> "unrecognized option `" ++ x ++ "'") nopts fail $ unlines errors bootHaste :: Cfg -> FilePath -> Shell () bootHaste cfg tmpdir = withEnv "nodosfilewarning" (const "1") . inDirectory tmpdir $ do removeBootFile <- isFile bootFile when removeBootFile $ rm bootFile when (getLibs cfg) $ do -- Don't clear dir when it contains binaries; portable should only be built -- by scripts anyway, so this dir ought to be clean. when (not portableHaste) $ do mapM_ clearDir [pkgUserLibDir, jsmodUserDir, pkgUserDir, pkgSysLibDir, jsmodSysDir, pkgSysDir] mkdir True (hasteCabalRootDir portableHaste) case getHasteCabal cfg of Download -> installHasteCabal portableHaste tmpdir Prebuilt fp -> copyHasteCabal portableHaste fp Source mdir -> buildHasteCabal portableHaste (maybe "../cabal" id mdir) -- Spawn off closure download in the background. dir <- pwd -- use absolute path for closure to avoid dir changing race closure <- future $ when (getClosure cfg) (installClosure dir) when (not $ useLocalLibs cfg) $ do fetchLibs tmpdir when (not portableHaste || initialPortableBoot cfg) $ do mkdir True hasteSysDir copyGhcSettings hasteSysDir void $ run hastePkgBinary ["init", pkgSysDir] "" buildLibs cfg when (initialPortableBoot cfg) $ do mapM_ relocate ["array", "bytestring", "containers", "base", "deepseq", "haste-prim", "time", "haste-lib", "monads-tf", "old-locale", "transformers", "integer-gmp", "hashable", "text", "binary", "random", "QuickCheck", "text"] -- Wait for closure download to finish. await closure output bootFile (showBootVersion bootVersion) clearDir :: FilePath -> Shell () clearDir dir = do exists <- isDirectory dir when exists $ rmdir dir copyHasteCabal :: Bool -> FilePath -> Shell () copyHasteCabal portable file = do mkdir True cabalDir cp file cabalBinary output (hasteBinDir "haste-cabal") (hasteCabalLauncher portable) liftIO $ copyPermissions cabalBinary (hasteBinDir "haste-cabal") where cabalDir = hasteCabalRootDir portable "haste-cabal" cabalBinary = cabalDir "haste-cabal.bin" buildHasteCabal :: Bool -> FilePath -> Shell () buildHasteCabal portable dir = do inDirectory dir $ run_ "runghc" ["build-haste-cabal.hs"] "" copyHasteCabal portable (dir "haste-cabal" "haste-cabal.bin") installHasteCabal :: Bool -> FilePath -> Shell () installHasteCabal portable tmpdir = do echo "Downloading haste-cabal from GitHub" f <- (decompress . BSL.fromChunks . (:[])) `fmap` fetchBytes hasteCabalUrl if os == "linux" then do liftIO . unpack rootdir $ read f liftIO $ copyPermissions hasteBinary (rootdir "haste-cabal/haste-cabal.bin") output (hasteBinDir hasteCabalFile) (hasteCabalLauncher portable) else do liftIO $ BSL.writeFile (hasteBinDir hasteCabalFile) f liftIO $ copyPermissions hasteBinary (hasteBinDir hasteCabalFile) where rootdir = hasteCabalRootDir portable baseUrl = "http://valderman.github.io/haste-libs/" hasteCabalUrl | os == "linux" = baseUrl ++ "haste-cabal.linux.tar.bz2" | otherwise = baseUrl ++ "haste-cabal" <.> os <.> "bz2" hasteCabalFile = "haste-cabal" ++ if os == "mingw32" then ".exe" else "" hasteCabalRootDir :: Bool -> FilePath hasteCabalRootDir True = hasteBinDir ".." hasteCabalRootDir False = hasteSysDir -- We need to determine the haste-cabal libdir at runtime if we're -- portable hasteCabalLauncher :: Bool -> String hasteCabalLauncher True = unlines [ "#!/bin/bash" , "HASTEC=\"$(dirname $0)/hastec\"" , "DIR=\"$($HASTEC --print-libdir)/../haste-cabal\"" , "export LD_LIBRARY_PATH=$DIR" , "exec $DIR/haste-cabal.bin $@" ] hasteCabalLauncher False = unlines [ "#!/bin/bash" , "DIR=\"" ++ hasteCabalRootDir False "haste-cabal" ++ "\"" , "export LD_LIBRARY_PATH=$DIR" , "exec $DIR/haste-cabal.bin $@" ] -- | Fetch the Haste base libs. fetchLibs :: FilePath -> Shell () fetchLibs tmpdir = do echo "Downloading base libs from GitHub" file <- fetchBytes $ mkUrl hasteVersion liftIO . unpack tmpdir . read . decompress $ BSL.fromChunks [file] where mkUrl v = "http://valderman.github.io/haste-libs/haste-libs-" ++ showVersion v ++ ".tar.bz2" -- | Fetch and install the Closure compiler. installClosure :: FilePath -> Shell () installClosure dir = do echo "Downloading Google Closure compiler..." downloadClosure `orElse` do echo "Couldn't install Closure compiler; continuing without." where downloadClosure = do fetchBytes closureURI >>= (liftIO . BS.writeFile (dir closureCompiler)) closureURI = "http://valderman.github.io/haste-libs/compiler.jar" -- | Build haste's base libs. buildLibs :: Cfg -> Shell () buildLibs cfg = do -- Set up dirs and copy includes mkdir True $ pkgSysLibDir cpdir "include" hasteSysDir inDirectory ("utils" "unlit") $ do let out = if os == "mingw32" then "unlit.exe" else "unlit" static = if os == "darwin" then [] else ["-static"] dash_s = if os == "darwin" then [] else ["-s"] run_ "gcc" (["-o" ++ out, "-O2", "unlit.c"]++static) "" run_ "strip" (dash_s ++ [out]) "" cp out (hasteSysDir out) run_ hastePkgBinary ["update", "--global", "libraries" "rts.pkg"] "" inDirectory "libraries" $ do inDirectory libDir $ do -- Install ghc-prim inDirectory "ghc-prim" $ do hasteCabal Install ["--solver", "topdown"] -- To get the GHC.Prim module in spite of pretending to have -- build-type: Simple let osxprim = if os == "darwin" then "-osx" else "" run_ hastePkgBinary ["unregister", "--global","ghc-prim"] "" run_ hastePkgBinary ["update", "--global", "ghc-prim-"++primVersion++osxprim++".conf"] "" -- Install integer-gmp; double install shouldn't be needed anymore. inDirectory "integer-gmp" $ do hasteCabal Install ["--solver", "topdown"] -- Install base inDirectory "base" $ do hasteCabal Clean [] hasteCabal Install ["--solver", "topdown", "-finteger-gmp"] -- Install array inDirectory "array" $ hasteCabal Clean [] inDirectory "array" $ hasteCabal Install [] -- Install haste-prim inDirectory "haste-prim" $ hasteCabal Install [] -- Install time + hashable + haste-lib inDirectory "time" $ hasteCabal Install [] hasteCabal Install [ "hashable-1.2.4.0" , "-f-integer-gmp" , "-f-sse2" , "-f-sse41" , "./haste-lib" ] -- Install QuickCheck libdir <- pwd inTempDirectory $ do hasteCabal (Do "unpack") ["QuickCheck-2.6"] inDirectory "QuickCheck-2.6" $ do run_ "patch" [ "-p1" , "-i" , libdir "quickcheck-2.6-amp.patch" ] "" hasteCabal Install [ "-f-templatehaskell" , "--allow-newer=base" , "./QuickCheck-2.6" ] -- Export monads-tf; it seems to be hidden by default run_ hastePkgBinary ["expose", "monads-tf"] "" where ghcOpts = concat [ if tracePrimops cfg then ["--hastec-option=-debug"] else [], if verbose cfg then ["--verbose"] else []] configOpts = [ "--with-hastec=" ++ hasteBinary , "--with-haste-pkg=" ++ hastePkgBinary , "--libdir=" ++ if os == "darwin" then pkgSysLibDir else takeDirectory pkgSysLibDir , "--package-db=clear" , "--package-db=global" , "--hastec-option=-fforce-recomp" #if __GLASGOW_HASKELL__ < 709 , "--hastec-option=-DHASTE_HOST_WORD_SIZE_IN_BITS=" ++ show hostWordSize #endif ] hasteCabal Configure args = withEnv "HASTE_BOOTING" (const "1") $ run_ hasteCabalBinary as "" where as = "configure" : args ++ ghcOpts ++ configOpts hasteCabal Install args = withEnv "HASTE_BOOTING" (const "1") $ run_ hasteCabalBinary as "" where as = "install" : args ++ ghcOpts ++ configOpts hasteCabal Build args = withEnv "HASTE_BOOTING" (const "1") $ run_ hasteCabalBinary as "" where as = "build" : args ++ ghcOpts hasteCabal Clean args = withEnv "HASTE_BOOTING" (const "1") $ run_ hasteCabalBinary as "" where as = "clean" : args hasteCabal (Do what) args = withEnv "HASTE_BOOTING" (const "1") $ run_ hasteCabalBinary as "" where as = what : args vanillaCabal args = run_ "cabal" args "" -- | Copy GHC settings and utils into the given directory. copyGhcSettings :: FilePath -> Shell () copyGhcSettings dest = do cp (libdir "platformConstants") (dest "platformConstants") #ifdef mingw32_HOST_OS cp ("settings-ghc-" ++ ghcMajor ++ ".windows") (dest "settings") cp (libdir "touchy.exe") (dest "touchy.exe") #else cp (libdir "settings") (dest "settings") #endif relocate :: String -> Shell () relocate pkg = run_ hastePkgBinary ["relocate", pkg] ""