elm/Setup.hs

166 lines
5.9 KiB
Haskell
Raw Normal View History

2012-04-19 06:32:10 +00:00
import Distribution.Simple
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Setup
import Distribution.PackageDescription
import System.Cmd
import System.Directory
import System.FilePath
import System.IO
Fix cabal install missing docs.json (followup) See: https://github.com/ngunn/Elm/commit/14e32add30c690b1309252d530afbea29cc448ec for discussion on first attempt Changed Librarys.hs and LoadLibraries.hs from TemplateHaskell to usafeIO. Avoids needing to touch LoadLibraries after we change docs.json (as the file is loaded at runtime). Also removes need for using CPP Drawback of using unsafeIO is fairly well known/understood + this is a classic use-case. Added error handling to LoadLibraries.hs Setup.hs now uses "runProcess" instead of "system" to invoke elm compiler. This allows us to set the environment variable "Elm_datadir" which is picked up by LoadLibraries and makes sure we get the just-built version in dist/data. Also explicitly sets the RTS file to make sure we're not using one (as we're currently building it!) Also change the folder the compiled JavaScript ends up in to dist/js. Writing to the source tree, then deleting isn't very nice + this should make it easier to find elm compilation errors. cabal clean handles the file deletion, too. I think this fixes all the defects of the first version: 1. CPP is not needed 2. touch is not needed 3. building elm-runtime.js uses dist/data/docs.json 4. running elm after install uses the installed elm-runtime.js and docs.json 5. LoadLibraries.hs is simpler. Doesn't use CPP or TemplateHaskell. Has error handling and a decent error message 6. cabal clean && cabal install builds everything with the right data in the right order (tested on ghc 7.4.1)
2013-03-22 15:52:40 +00:00
import System.Process
import Control.Monad
import qualified Data.Binary as Binary
import qualified Data.ByteString.Lazy as BS
-- Part 1
-- ------
-- Add a build callout
-- We need to build elm-doc and run it because that generates the file "docs.json"
-- needs by Libraries.hs which is part of the elm library and executable
-- Unfort. there seems to be no way to tell cabal that:
-- (a) elm-doc generates docs.json, and
-- (b) elm (library) depends on docs.json
-- Therefore, we either use make instead (or a script), or hack around in cabal
-- Part 2
-- ------
-- Add a post-build callout.
-- We need to build the runtime.js after we've built elm (because we use elm to generate some of the JavaScript),
-- but before cabal does the install file copy step
-- Assumptions
-- Elm.cabal expects the generated files to end up in dist/data
-- git won't look in dist + cabal will clean it
rtsDir :: LocalBuildInfo -> FilePath
rtsDir lbi = "data"
tempDir :: LocalBuildInfo -> FilePath
tempDir lbi = "temp"
-- The runtime is called:
rts :: LocalBuildInfo -> FilePath
2013-06-24 04:14:08 +00:00
rts lbi = rtsDir lbi </> "elm-runtime.js"
-- The interfaces for the Standard Libraries live in:
interfaces :: LocalBuildInfo -> FilePath
interfaces lbi = rtsDir lbi </> "interfaces.data"
-- buildDir with LocalBuildInfo points to "dist/build" (usually)
2013-06-24 04:14:08 +00:00
elm lbi = buildDir lbi </> "elm" </> "elm"
-- Care! This appears to be based on an unstable API
-- See: http://www.haskell.org/cabal/release/cabal-latest/doc/API/Cabal/Distribution-Simple.html#2
main :: IO ()
main = defaultMainWithHooks simpleUserHooks { postBuild = myPostBuild }
-- Build
Fix cabal install missing docs.json (followup) See: https://github.com/ngunn/Elm/commit/14e32add30c690b1309252d530afbea29cc448ec for discussion on first attempt Changed Librarys.hs and LoadLibraries.hs from TemplateHaskell to usafeIO. Avoids needing to touch LoadLibraries after we change docs.json (as the file is loaded at runtime). Also removes need for using CPP Drawback of using unsafeIO is fairly well known/understood + this is a classic use-case. Added error handling to LoadLibraries.hs Setup.hs now uses "runProcess" instead of "system" to invoke elm compiler. This allows us to set the environment variable "Elm_datadir" which is picked up by LoadLibraries and makes sure we get the just-built version in dist/data. Also explicitly sets the RTS file to make sure we're not using one (as we're currently building it!) Also change the folder the compiled JavaScript ends up in to dist/js. Writing to the source tree, then deleting isn't very nice + this should make it easier to find elm compilation errors. cabal clean handles the file deletion, too. I think this fixes all the defects of the first version: 1. CPP is not needed 2. touch is not needed 3. building elm-runtime.js uses dist/data/docs.json 4. running elm after install uses the installed elm-runtime.js and docs.json 5. LoadLibraries.hs is simpler. Doesn't use CPP or TemplateHaskell. Has error handling and a decent error message 6. cabal clean && cabal install builds everything with the right data in the right order (tested on ghc 7.4.1)
2013-03-22 15:52:40 +00:00
-- note(1): We use to include docs.json directly into LoadLibraries at compile time
-- If docs.json is used in other (template) haskell files, they should be copied
-- and compiled in a separate directory (eg, dist/copiedSrc).
-- This is to make sure they are re-compiled on docs.json changes.
-- Copying is a better solution than 'touch'ing the source files
-- (touch is non-portable and confusing wrt RCS).
-- In the PackageDescription, the list of stuff to build is held in library
-- (in a Maybe) and the executables list. We want a PackageDescription that
-- only mentions the executable 'name'
filterExe :: String -> PackageDescription -> PackageDescription
filterExe name pd = pd {
library = Nothing,
executables = filter (\x -> (exeName x == name)) (executables pd)
}
-- It's not enough to fix the PackageDescription, we also have to fix the
-- LocalBuildInfo. This includes the component build order (data ComponentName)
-- which is horribly internal.
filterLBI :: String -> LocalBuildInfo -> LocalBuildInfo
filterLBI name lbi = lbi {
libraryConfig = Nothing,
compBuildOrder = [CExeName name],
executableConfigs = filter (\a -> (fst a == name)) (executableConfigs lbi)
}
-- Post Build
myPostBuild :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
myPostBuild as bfs pd lbi = do
putStrLn "Custom build step: compiling standard libraries"
(elmos, elmis) <- compileLibraries lbi
putStrLn "Custom build step: build interfaces.data"
buildInterfaces lbi elmis
putStrLn "Custom build step: build elm-runtime.js"
buildRuntime lbi elmos
removeDirectoryRecursive (tempDir lbi)
postBuild simpleUserHooks as bfs pd lbi
compileLibraries lbi = do
let temp = tempDir lbi -- temp
rts = rtsDir lbi -- data
createDirectoryIfMissing True temp
createDirectoryIfMissing True rts
out_c <- canonicalizePath temp -- temp (root folder)
elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
rtd_c <- canonicalizePath rts -- data
let make file = do
-- replace 'system' call with 'runProcess' which handles args better
-- and allows env variable "Elm_datadir" which is used by LoadLibraries
-- to find docs.json
let args = [ "--only-js", "--make", "--no-prelude"
, "--cache-dir="++out_c, "--build-dir="++out_c, file ]
arg = Just [("Elm_datadir", rtd_c)]
handle <- runProcess elm_c args Nothing arg Nothing Nothing Nothing
exitCode <- waitForProcess handle
return ( out_c </> replaceExtension file "elmo"
, out_c </> replaceExtension file "elmi")
setCurrentDirectory "libraries"
print =<< getCurrentDirectory
files <- getFiles ".elm" "."
files <- unzip `fmap` mapM make files
setCurrentDirectory ".."
return files
buildInterfaces :: LocalBuildInfo -> [FilePath] -> IO ()
buildInterfaces lbi elmis = do
createDirectoryIfMissing True (rtsDir lbi)
let ifaces = interfaces lbi
ifaceHandle <- openBinaryFile ifaces WriteMode
BS.hPut ifaceHandle (Binary.encode (length elmis))
let append file = do
handle <- openBinaryFile file ReadMode
bits <- hGetContents handle
length bits `seq` hPutStr ifaceHandle bits
hClose handle
mapM_ append elmis
hClose ifaceHandle
buildRuntime :: LocalBuildInfo -> [FilePath] -> IO ()
buildRuntime lbi elmos = do
createDirectoryIfMissing True (rtsDir lbi)
2013-08-31 20:14:06 +00:00
writeFile (rts lbi) "var Elm = {}; Elm.Native = {}; Elm.Native.Graphics = {};\n\
\var ElmRuntime = {}; ElmRuntime.Render = {};\n"
mapM_ (appendJS lbi) =<< getFiles ".js" "libraries"
mapM_ (appendJS lbi) elmos
mapM_ (appendJS lbi) =<< getFiles ".js" "runtime"
getFiles ext dir = do
contents <- map (dir </>) `fmap` getDirectoryContents dir
let files = filter (\f -> takeExtension f == ext) contents
dirs = filter (not . hasExtension) contents
filess <- mapM (getFiles ext) dirs
return (files ++ concat filess)
appendJS lbi file = do
putStrLn (dropExtension file)
str <- readFile file
length str `seq` return ()
appendFile (rts lbi) str