on master help sign in
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)

commit175f268622313efe86e712036849ce39566db6e4 authorngunn <ngunn@computationalthinking.org.uk> dateFri 22 Mar 2013, 15:52 (14 years ago) parent29153a8
4 files changed+51−39
Elm.cabal−2 Setup.hs+28−12 compiler/Model/Libraries.hs+2−4 compiler/Model/LoadLibraries.hs+21−21
Elm.cabal−2
@@ -33,8 +33,6 @@ Library
3333 exposed-modules: Language.Elm,
3434 Language.Elm.Quasi
3535 Hs-Source-Dirs: compiler, compiler/Gen, compiler/Model, compiler/Transform
36− extensions: CPP
37− cpp-options: -DELM_COMPILEDATADIR="dist/data"
3836 other-modules: Ast,
3937 Context,
4038 CompileToJS,
Setup.hs+28−12
@@ -7,6 +7,7 @@ import System.Cmd
77import System.Directory
88import System.FilePath
99import System.IO
10+import System.Process
1011
1112import Control.Monad
1213
@@ -29,6 +30,7 @@ import Control.Monad
2930-- Assumptions
3031-- Elm.cabal expects the generated files to end up in dist/data
3132rtsDir lbi = (buildDir lbi) </> ".." </> "data" -- git won't look in dist + cabal will clean it
33+jsDir lbi = (buildDir lbi) </> ".." </> "js"
3234
3335-- The runtime is called:
3436rts lbi = (rtsDir lbi) </> "elm-runtime.js"
@@ -44,13 +46,6 @@ types lbi = (rtsDir lbi) </> "docs.json"
4446-- buildDir with LocalBuildInfo points to "dist/build" (usually)
4547elm lbi = (buildDir lbi) </> "elm" </> "elm"
4648
47−-- "touch" is available as a system command.
48−-- There is no portable file touch (unix-compat might work but leads to cabal library dependency
49−-- issues: see: http://hackage.haskell.org/trac/summer-of-code/ticket/1602 )
50−-- Files that need recompilation after docs.json is updated
51−-- (Would be nice if this list was in the cabal file, but can't see any way to do that)
52−docTouches = ["compiler" </> "Model" </> "LoadLibraries.hs"]
53−
5449-- Care! This appears to be based on an unstable API
5550-- See: http://www.haskell.org/cabal/release/cabal-latest/doc/API/Cabal/Distribution-Simple.html#2
5651
@@ -67,11 +62,17 @@ myBuild pd lbi uh bf = do
6762 withExe pd (\x -> putStrLn (exeName x))
6863 buildHook simpleUserHooks (filterExe elmDoc pd) (filterLBI elmDoc lbi) uh bf
6964 putStrLn "Custom build step started: build docs.json"
70− buildTypes lbi
71− forM_ docTouches (\s -> system ("touch " ++ s)) -- NB non-portable
65+ buildTypes lbi -- see note(1) below
7266 putStrLn "Custom build step started: compile everything"
7367 buildHook simpleUserHooks pd lbi uh bf
7468
69+-- note(1): We use to include docs.json directly into LoadLibraries at compile time
70+-- If docs.json is used in other (template) haskell files, they should be copied
71+-- and compiled in a separate directory (eg, dist/copiedSrc).
72+-- This is to make sure they are re-compiled on docs.json changes.
73+-- Copying is a better solution than 'touch'ing the source files
74+-- (touch is non-portable and confusing wrt RCS).
75+
7576-- In the PackageDescription, the list of stuff to build is held in library (in a Maybe)
7677-- and the executables list. We want a PackageDescription that only mentions the executable 'name'
7778filterExe name pd = pd {
@@ -116,10 +117,25 @@ appendJS lbi file = do
116117 appendFile (rts lbi) str
117118
118119appendElm lbi file = do
119− system (elm lbi ++ " --only-js " ++ file)
120− let jsFile = replaceExtension file ".js"
120+ jsFile <- runElm lbi file
121121 appendJS lbi jsFile
122− removeFile jsFile
122+
123+-- replace 'system' call with 'runProcess' which handles args better and allows env variable
124+-- "Elm_datadir" which is used by LoadLibraries to find docs.json
125+runElm :: LocalBuildInfo -> String -> IO FilePath
126+runElm lbi file = do
127+ rts_c <- canonicalizePath (rts lbi) -- dist/data/elm-runtime.js
128+ let js = jsDir lbi -- dist/js
129+ let j = dropFileName (js </> file) -- dist/js/libraries/
130+ createDirectoryIfMissing True j -- must do before any canonicalization
131+ out_c <- canonicalizePath js -- dist/js (root folder)
132+ elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
133+ rtd_c <- canonicalizePath (rtsDir lbi) -- dist/data (for docs.json)
134+ handle <- runProcess elm_c ["--only-js", "--runtime="++rts_c, "--output-directory="++out_c, file]
135+ Nothing (Just [("Elm_datadir", rtd_c)]) Nothing Nothing Nothing
136+ waitForProcess handle
137+ return $ j </> replaceExtension (takeFileName file) ".js"
138+
123139
124140buildRuntime lbi = do
125141 createDirectoryIfMissing False (rtsDir lbi) -- dist should already exist
compiler/Model/Libraries.hs+2−4
@@ -1,10 +1,8 @@
1−{-# LANGUAGE TemplateHaskell #-}
2−
31module Libraries (libraries) where
42
53import qualified Data.Map as Map
64import Text.JSON
7−import LoadLibraries
5+import LoadLibraries (docs)
86
97libraries :: Map.Map String (Map.Map String String)
108libraries = case getLibs of
@@ -13,7 +11,7 @@ libraries = case getLibs of
1311
1412getLibs :: Result (Map.Map String (Map.Map String String))
1513getLibs = do
16− obj <- decodeStrict "{\"modules\":[]}" -- $(docs) :: Result (JSObject JSValue)
14+ obj <- decodeStrict docs :: Result (JSObject JSValue)
1715 modules <- valFromObj "modules" obj :: Result [JSObject JSValue]
1816 Map.fromList `fmap` mapM getValues modules
1917
compiler/Model/LoadLibraries.hs+21−21
@@ -1,36 +1,36 @@
1−{-# LANGUAGE CPP #-}
2−
31module LoadLibraries (docs) where
42
5−import Language.Haskell.TH.Syntax
3+import Control.DeepSeq (force)
4+import qualified Control.Exception as E
65import Paths_Elm
76import System.Directory
87import System.FilePath
8+import System.IO.Unsafe (unsafePerformIO)
9+
10+-- See stackoverflow discussion for trade-off between using unsafeIO or TemplateHaskell:
11+-- http://stackoverflow.com/questions/12716215/load-pure-global-variable-from-file
912
10−docs :: Q Exp
11−docs = liftString =<< qRunIO (readDocs dataDir)
13+-- Given the awkwardness of including a compile-time generated file
14+-- vs loading static data, then the unsafeIO seems better.
1215
13−-- When compiling elm (library and executable), docs.json isn't (yet) in the standard location
14−-- Therefore, Elm.cabal overrides the location.
15−-- Since this is only used by the TemplateHaskell, which reads the file at compile time,
16−-- this should be ok.
16+{-# NOINLINE docs #-}
17+docs :: String
18+docs = force . unsafePerformIO . safeReadDocs . getDataFileName $ "docs.json"
1719
18−dataDir :: IO FilePath
19−#if defined ELM_COMPILEDATADIR
20−dataDir = canonicalizePath (ELM_COMPILEDATADIR </> "docs.json")
21−#else
22−dataDir = getDataFileName "docs.json"
23−#endif
20+safeReadDocs :: IO FilePath -> IO String
21+safeReadDocs path = E.catch (readDocs path) (emitError path)
2422
2523readDocs :: IO FilePath -> IO String
2624readDocs path = do
2725 name <- path
2826 exists <- doesFileExist name
2927 if exists then readFile name
30− else putStrLn warning >> return "{\"modules\":[]}"
28+ else ioError . userError $ "File Not Found"
29+
3130
32−warning = "Warning! Types for standard library not loaded properly!\n\
33− Run the following commands after compilation:\n\
34− \n\
35− touch compiler/Model/LoadLibraries.hs\n\
36− cabal install\n\n"
31+emitError :: IO FilePath -> IOError -> IO String
32+emitError path err = do
33+ name <- path
34+ putStrLn $ "Error! Types for standard library not loaded properly!\n File should be here:" ++ name ++ "\n The file is created and copied by command: cabal install"
35+ putStrLn (show err)
36+ return "{\"modules\":[]}"