175f268622
See: 14e32add30
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)
149 lines
5.8 KiB
Haskell
149 lines
5.8 KiB
Haskell
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
|
|
import System.Process
|
|
|
|
import Control.Monad
|
|
|
|
-- 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
|
|
rtsDir lbi = (buildDir lbi) </> ".." </> "data" -- git won't look in dist + cabal will clean it
|
|
jsDir lbi = (buildDir lbi) </> ".." </> "js"
|
|
|
|
-- The runtime is called:
|
|
rts lbi = (rtsDir lbi) </> "elm-runtime.js"
|
|
|
|
-- The json file is called:
|
|
|
|
-- The elm-docs executable is called:
|
|
elmDoc = "elm-doc"
|
|
elm_doc lbi = (buildDir lbi) </> elmDoc </> elmDoc
|
|
|
|
types lbi = (rtsDir lbi) </> "docs.json"
|
|
|
|
-- buildDir with LocalBuildInfo points to "dist/build" (usually)
|
|
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 { buildHook = myBuild, postBuild = myPostBuild }
|
|
|
|
|
|
-- Build
|
|
|
|
myBuild :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
|
|
myBuild pd lbi uh bf = do
|
|
putStrLn $ "Custom build step started: compile " ++ elmDoc
|
|
withExe pd (\x -> putStrLn (exeName x))
|
|
buildHook simpleUserHooks (filterExe elmDoc pd) (filterLBI elmDoc lbi) uh bf
|
|
putStrLn "Custom build step started: build docs.json"
|
|
buildTypes lbi -- see note(1) below
|
|
putStrLn "Custom build step started: compile everything"
|
|
buildHook simpleUserHooks pd lbi uh bf
|
|
|
|
-- 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 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 name lbi = lbi {
|
|
libraryConfig = Nothing,
|
|
compBuildOrder = [CExeName name],
|
|
executableConfigs = filter (\a -> (fst a == name)) (executableConfigs lbi)
|
|
}
|
|
|
|
buildTypes lbi = do
|
|
createDirectoryIfMissing False (rtsDir lbi) -- dist should already exist
|
|
files <- getFiles ".elm" "libraries"
|
|
system (elm_doc lbi ++ " " ++ unwords files ++ " > " ++ (types lbi))
|
|
putStrLn $ "Custom build step completed: " ++ elmDoc
|
|
|
|
|
|
-- Post Build
|
|
|
|
myPostBuild :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
|
|
myPostBuild as bfs pd lbi = do
|
|
putStrLn "Custom post build step started: build elm-runtime.js"
|
|
buildRuntime lbi
|
|
postBuild simpleUserHooks as bfs pd lbi
|
|
|
|
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
|
|
|
|
appendElm lbi file = do
|
|
jsFile <- runElm lbi file
|
|
appendJS lbi jsFile
|
|
|
|
-- replace 'system' call with 'runProcess' which handles args better and allows env variable
|
|
-- "Elm_datadir" which is used by LoadLibraries to find docs.json
|
|
runElm :: LocalBuildInfo -> String -> IO FilePath
|
|
runElm lbi file = do
|
|
rts_c <- canonicalizePath (rts lbi) -- dist/data/elm-runtime.js
|
|
let js = jsDir lbi -- dist/js
|
|
let j = dropFileName (js </> file) -- dist/js/libraries/
|
|
createDirectoryIfMissing True j -- must do before any canonicalization
|
|
out_c <- canonicalizePath js -- dist/js (root folder)
|
|
elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
|
|
rtd_c <- canonicalizePath (rtsDir lbi) -- dist/data (for docs.json)
|
|
handle <- runProcess elm_c ["--only-js", "--runtime="++rts_c, "--output-directory="++out_c, file]
|
|
Nothing (Just [("Elm_datadir", rtd_c)]) Nothing Nothing Nothing
|
|
waitForProcess handle
|
|
return $ j </> replaceExtension (takeFileName file) ".js"
|
|
|
|
|
|
buildRuntime lbi = do
|
|
createDirectoryIfMissing False (rtsDir lbi) -- dist should already exist
|
|
writeFile (rts lbi) "Elm = {}; Elm.Native = {}; Elm.Native.Graphics = {};\n\
|
|
\Elm.Graphics = {}; ElmRuntime = {}; ElmRuntime.Render = {}\n"
|
|
mapM_ (appendJS lbi) =<< getFiles ".js" "libraries"
|
|
mapM_ (appendElm lbi) =<< getFiles ".elm" "libraries"
|
|
mapM_ (appendJS lbi) =<< getFiles ".js" "runtime"
|
|
putStrLn "\n+------------------------------------------+\
|
|
\\n| Success building runtime and libraries! |\
|
|
\\n+------------------------------------------+\n"
|