on ce614bb help sign in
elk/Setup.hs
180 lines 6.5 kB history raw
1import Distribution.Simple
2import Distribution.Simple.LocalBuildInfo
3import Distribution.Simple.Setup
4import Distribution.PackageDescription
6import System.Cmd
7import System.Directory
8import System.FilePath
9import System.IO
10import System.Process
12import Control.Monad
13import qualified Data.Binary as Binary
14import qualified Data.ByteString.Lazy as BS
15import qualified Data.List as List
17-- Part 1
18-- ------
19-- Add a build callout
20-- We need to build elm-doc and run it because that generates the file "docs.json"
21-- needs by Libraries.hs which is part of the elm library and executable
22-- Unfort. there seems to be no way to tell cabal that:
23-- (a) elm-doc generates docs.json, and
24-- (b) elm (library) depends on docs.json
25-- Therefore, we either use make instead (or a script), or hack around in cabal
27-- Part 2
28-- ------
29-- Add a post-build callout.
30-- We need to build the runtime.js after we've built elm (because we use elm to generate some of the JavaScript),
31-- but before cabal does the install file copy step
33-- Assumptions
34-- Elm.cabal expects the generated files to end up in dist/data
35-- git won't look in dist + cabal will clean it
36rtsDir :: LocalBuildInfo -> FilePath
37rtsDir lbi = "data"
39tempDir :: LocalBuildInfo -> FilePath
40tempDir lbi = "temp"
42-- The runtime is called:
43rts :: LocalBuildInfo -> FilePath
44rts lbi = rtsDir lbi </> "elm-runtime.js"
46-- The runtime is called:
47docs :: LocalBuildInfo -> FilePath
48docs lbi = rtsDir lbi </> "docs.json"
50-- The interfaces for the Standard Libraries live in:
51interfaces :: LocalBuildInfo -> FilePath
52interfaces lbi = rtsDir lbi </> "interfaces.data"
54elm :: LocalBuildInfo -> FilePath
55elm lbi = buildDir lbi </> "elm" </> "elm"
57document :: LocalBuildInfo -> FilePath
58document lbi = buildDir lbi </> "elm-doc" </> "elm-doc"
60-- Care! This appears to be based on an unstable API
61-- See: http://www.haskell.org/cabal/release/cabal-latest/doc/API/Cabal/Distribution-Simple.html#2
64main :: IO ()
65main = defaultMainWithHooks simpleUserHooks { postBuild = myPostBuild }
68-- Build
70-- note(1): We use to include docs.json directly into LoadLibraries at compile time
71-- If docs.json is used in other (template) haskell files, they should be copied
72-- and compiled in a separate directory (eg, dist/copiedSrc).
73-- This is to make sure they are re-compiled on docs.json changes.
74-- Copying is a better solution than 'touch'ing the source files
75-- (touch is non-portable and confusing wrt RCS).
77-- In the PackageDescription, the list of stuff to build is held in library
78-- (in a Maybe) and the executables list. We want a PackageDescription that
79-- only mentions the executable 'name'
80filterExe :: String -> PackageDescription -> PackageDescription
81filterExe name pd = pd {
82 library = Nothing,
83 executables = filter (\x -> (exeName x == name)) (executables pd)
84 }
87-- Post Build
89myPostBuild :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
90myPostBuild as bfs pd lbi = do
91 putStrLn "Custom build step: compiling standard libraries"
92 (elmos, elmis) <- compileLibraries lbi
93 putStrLn "Custom build step: build interfaces.data"
94 buildInterfaces lbi elmis
95 putStrLn "Custom build step: build elm-runtime.js"
96 buildRuntime lbi elmos
97 putStrLn "Custom build step: build docs.json"
98 buildDocs lbi
99 removeDirectoryRecursive (tempDir lbi)
100 removeDirectoryRecursive ("libraries" </> "docs")
101 postBuild simpleUserHooks as bfs pd lbi
104compileLibraries lbi = do
105 let temp = tempDir lbi -- temp
106 rts = rtsDir lbi -- data
107 createDirectoryIfMissing True temp
108 createDirectoryIfMissing True rts
109 out_c <- canonicalizePath temp -- temp (root folder)
110 elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
111 doc_c <- canonicalizePath (document lbi) -- dist/build/elm-doc/elm-doc
112 rtd_c <- canonicalizePath rts -- data
114 let make file = do
115 -- replace 'system' call with 'runProcess' which handles args better
116 -- and allows env variable "Elm_datadir" which is used by LoadLibraries
117 -- to find docs.json
118 let args = [ "--only-js", "--make", "--no-prelude"
119 , "--cache-dir="++out_c, "--build-dir="++out_c, file ]
120 arg = Just [("Elm_datadir", rtd_c)]
121 handle <- runProcess elm_c args Nothing arg Nothing Nothing Nothing
122 exitCode <- waitForProcess handle
123 return ( out_c </> replaceExtension file "elmo"
124 , out_c </> replaceExtension file "elmi")
126 setCurrentDirectory "libraries"
127 paths <- getFiles ".elm" "."
128 files <- unzip `fmap` mapM make paths
129 mapM_ (\path -> rawSystem doc_c [path]) paths
130 setCurrentDirectory ".."
131 return files
133buildInterfaces :: LocalBuildInfo -> [FilePath] -> IO ()
134buildInterfaces lbi elmis = do
135 createDirectoryIfMissing True (rtsDir lbi)
136 let ifaces = interfaces lbi
137 ifaceHandle <- openBinaryFile ifaces WriteMode
138 BS.hPut ifaceHandle (Binary.encode (length elmis))
139 let append file = do
140 handle <- openBinaryFile file ReadMode
141 bits <- hGetContents handle
142 length bits `seq` hPutStr ifaceHandle bits
143 hClose handle
144 mapM_ append elmis
145 hClose ifaceHandle
147buildRuntime :: LocalBuildInfo -> [FilePath] -> IO ()
148buildRuntime lbi elmos = do
149 createDirectoryIfMissing True (rtsDir lbi)
150 let rts' = rts lbi
151 writeFile rts' "'use strict';\n\
152 \var Elm = {}; Elm.Native = {}; Elm.Native.Graphics = {};\n\
153 \var ElmRuntime = {}; ElmRuntime.Render = {};\n"
154 mapM_ (appendTo rts') =<< getFiles ".js" "libraries"
155 mapM_ (appendTo rts') elmos
156 mapM_ (appendTo rts') =<< getFiles ".js" "runtime"
158buildDocs :: LocalBuildInfo -> IO ()
159buildDocs lbi = do
160 createDirectoryIfMissing True (rtsDir lbi)
161 let docs' = docs lbi
162 writeFile docs' "[\n"
163 json <- getFiles ".json" ("libraries" </> "docs")
164 let appends = map (appendTo docs') json
165 addCommas = List.intersperse (appendFile docs' ",\n")
166 sequence_ (addCommas appends)
167 appendFile docs' "\n]"
169getFiles ext dir = do
170 contents <- map (dir </>) `fmap` getDirectoryContents dir
171 let files = filter (\f -> takeExtension f == ext) contents
172 dirs = filter (not . hasExtension) contents
173 filess <- mapM (getFiles ext) dirs
174 return (files ++ concat filess)
176appendTo :: FilePath -> FilePath -> IO ()
177appendTo destination source = do
178 str <- readFile source
179 length str `seq` return ()
180 appendFile destination str