on master help sign in
Generate and use interfaces.data so standard library types are available
0a6bd8bEvan Czaplicki
Setup.hs+71−74
@@ -10,12 +10,13 @@ import System.IO
10import System.Process
10import System.Process
11
11
12import Control.Monad
12import Control.Monad
13+import qualified Data.Binary as Binary
13
14
14-- Part 1
15-- Part 1
15-- ------
16-- ------
16-- Add a build callout
17-- Add a build callout
17−-- We need to build elm-doc and run it because that generates the file "docs.json" needs by Libraries.hs
18+-- We need to build elm-doc and run it because that generates the file "docs.json"
18−-- which is part of the elm library and executable
19+-- needs by Libraries.hs which is part of the elm library and executable
19-- Unfort. there seems to be no way to tell cabal that:
20-- Unfort. there seems to be no way to tell cabal that:
20-- (a) elm-doc generates docs.json, and
21-- (a) elm-doc generates docs.json, and
21-- (b) elm (library) depends on docs.json
22-- (b) elm (library) depends on docs.json
@@ -29,19 +30,20 @@ import Control.Monad
29
30
30-- Assumptions
31-- Assumptions
31-- Elm.cabal expects the generated files to end up in dist/data
32-- Elm.cabal expects the generated files to end up in dist/data
32−rtsDir lbi = buildDir lbi </> ".." </> "data" -- git won't look in dist + cabal will clean it
33+-- git won't look in dist + cabal will clean it
34+rtsDir :: LocalBuildInfo -> FilePath
35+rtsDir lbi = buildDir lbi </> ".." </> "data"
36+
37+tempDir :: LocalBuildInfo -> FilePath
33−jsDir lbi = buildDir lbi </> ".." </> "js"
38+tempDir lbi = buildDir lbi </> ".." </> "temp"
34
39
35-- The runtime is called:
40-- The runtime is called:
41+rts :: LocalBuildInfo -> FilePath
36rts lbi = rtsDir lbi </> "elm-runtime.js"
42rts lbi = rtsDir lbi </> "elm-runtime.js"
37
43
38−-- The json file is called:
44+-- The interfaces for the Standard Libraries live in:
39−
45+interfaces :: LocalBuildInfo -> FilePath
40−-- The elm-docs executable is called:
46+interfaces lbi = rtsDir lbi </> "interfaces.data"
41−elmDoc = "elm-doc"
42−elm_doc lbi = buildDir lbi </> elmDoc </> elmDoc
43−
44−types lbi = rtsDir lbi </> "docs.json"
45
47
46-- buildDir with LocalBuildInfo points to "dist/build" (usually)
48-- buildDir with LocalBuildInfo points to "dist/build" (usually)
47elm lbi = buildDir lbi </> "elm" </> "elm"
49elm lbi = buildDir lbi </> "elm" </> "elm"
@@ -51,25 +53,11 @@ elm lbi = buildDir lbi </> "elm" </> "elm"
51
53
52
54
53main :: IO ()
55main :: IO ()
54−main = defaultMainWithHooks simpleUserHooks { {-- buildHook = myBuild, --} postBuild = myPostBuildWithTypes }
56+main = defaultMainWithHooks simpleUserHooks { postBuild = myPostBuild }
55
57
56
58
57-- Build
59-- Build
58
60
59−-- Not currently used. buildTypes is in 'myPostBuildWithTypes'
60−-- If using this again, change postBuild to 'myPostBuild'
61−-- Purpose is to make sure docs.json was built before elm exec was (as elm exec depended on it)
62−-- This is no longer true and the code below seems to affect cabal's build dependencies
63−myBuild :: PackageDescription -> LocalBuildInfo -> UserHooks -> BuildFlags -> IO ()
64−myBuild pd lbi uh bf = do
65− putStrLn $ "Custom build step started: compile " ++ elmDoc
66− withExe pd (\x -> putStrLn (exeName x))
67− buildHook simpleUserHooks (filterExe elmDoc pd) (filterLBI elmDoc lbi) uh bf
68− putStrLn "Custom build step started: build docs.json"
69− buildTypes lbi -- see note(1) below
70− putStrLn "Custom build step started: compile everything"
71− buildHook simpleUserHooks pd lbi uh bf
72−
73-- note(1): We use to include docs.json directly into LoadLibraries at compile time
61-- note(1): We use to include docs.json directly into LoadLibraries at compile time
74-- If docs.json is used in other (template) haskell files, they should be copied
62-- If docs.json is used in other (template) haskell files, they should be copied
75-- and compiled in a separate directory (eg, dist/copiedSrc).
63-- and compiled in a separate directory (eg, dist/copiedSrc).
@@ -77,42 +65,81 @@ myBuild pd lbi uh bf = do
77-- Copying is a better solution than 'touch'ing the source files
65-- Copying is a better solution than 'touch'ing the source files
78-- (touch is non-portable and confusing wrt RCS).
66-- (touch is non-portable and confusing wrt RCS).
79
67
80−-- In the PackageDescription, the list of stuff to build is held in library (in a Maybe)
68+-- In the PackageDescription, the list of stuff to build is held in library
81−-- and the executables list. We want a PackageDescription that only mentions the executable 'name'
69+-- (in a Maybe) and the executables list. We want a PackageDescription that
70+-- only mentions the executable 'name'
71+filterExe :: String -> PackageDescription -> PackageDescription
82filterExe name pd = pd {
72filterExe name pd = pd {
83 library = Nothing,
73 library = Nothing,
84 executables = filter (\x -> (exeName x == name)) (executables pd)
74 executables = filter (\x -> (exeName x == name)) (executables pd)
85 }
75 }
86
76
87−-- It's not enough to fix the PackageDescription, we also have to fix the LocalBuildInfo.
77+-- It's not enough to fix the PackageDescription, we also have to fix the
88−-- This includes the component build order (data ComponentName) which is horribly internal.
78+-- LocalBuildInfo. This includes the component build order (data ComponentName)
79+-- which is horribly internal.
80+filterLBI :: String -> LocalBuildInfo -> LocalBuildInfo
89filterLBI name lbi = lbi {
81filterLBI name lbi = lbi {
90 libraryConfig = Nothing,
82 libraryConfig = Nothing,
91 compBuildOrder = [CExeName name],
83 compBuildOrder = [CExeName name],
92 executableConfigs = filter (\a -> (fst a == name)) (executableConfigs lbi)
84 executableConfigs = filter (\a -> (fst a == name)) (executableConfigs lbi)
93 }
85 }
94
86
95−buildTypes lbi = do
96− createDirectoryIfMissing False (rtsDir lbi) -- dist should already exist
97− files <- getFiles ".elm" "libraries"
98− system (elm_doc lbi ++ " " ++ unwords files ++ " > " ++ (types lbi))
99− putStrLn $ "Custom build step completed: " ++ elmDoc
100−
101
87
102-- Post Build
88-- Post Build
103
89
104−myPostBuildWithTypes :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
105−myPostBuildWithTypes as bfs pd lbi = do
106− putStrLn "Custom build step started: build docs.json"
107− buildTypes lbi -- see note(1) below
108− myPostBuild as bfs pd lbi
109−
110myPostBuild :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
90myPostBuild :: Args -> BuildFlags -> PackageDescription -> LocalBuildInfo -> IO ()
111myPostBuild as bfs pd lbi = do
91myPostBuild as bfs pd lbi = do
112− putStrLn "Custom post build step started: build elm-runtime.js"
92+ putStrLn "Custom build step: compiling standard libraries"
93+ (elmos, elmis) <- compileLibraries lbi
94+ putStrLn "Custom build step: build interfaces.data"
95+ buildInterfaces lbi elmis
96+ putStrLn "Custom build step: build elm-runtime.js"
113− buildRuntime lbi
97+ buildRuntime lbi elmos
98+ removeDirectoryRecursive ("dist" </> "temp")
114 postBuild simpleUserHooks as bfs pd lbi
99 postBuild simpleUserHooks as bfs pd lbi
115
100
101+
102+compileLibraries lbi = do
103+ let temp = tempDir lbi -- dist/temp
104+ createDirectoryIfMissing True temp
105+ out_c <- canonicalizePath temp -- dist/temp (root folder)
106+ elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
107+ rtd_c <- canonicalizePath (rtsDir lbi) -- dist/data (for docs.json)
108+
109+ let make file = do
110+ -- replace 'system' call with 'runProcess' which handles args better
111+ -- and allows env variable "Elm_datadir" which is used by LoadLibraries
112+ -- to find docs.json
113+ let args = ["--only-js","--make","--no-prelude","--output-directory="++out_c,file]
114+ arg = Just [("Elm_datadir", rtd_c)]
115+ handle <- runProcess elm_c args Nothing arg Nothing Nothing Nothing
116+ exitCode <- waitForProcess handle
117+ return ( out_c </> replaceExtension file "elmo"
118+ , out_c </> replaceExtension file "elmi")
119+
120+ setCurrentDirectory "libraries"
121+ print =<< getCurrentDirectory
122+ files <- getFiles ".elm" "."
123+ files <- unzip `fmap` mapM make files
124+ setCurrentDirectory ".."
125+ return files
126+
127+buildInterfaces :: LocalBuildInfo -> [FilePath] -> IO ()
128+buildInterfaces lbi elmis = do
129+ createDirectoryIfMissing True (rtsDir lbi)
130+ let ifaces = interfaces lbi
131+ Binary.encodeFile ifaces (length elmis)
132+ mapM_ (\elmi -> readFile elmi >>= appendFile ifaces) elmis
133+
134+buildRuntime :: LocalBuildInfo -> [FilePath] -> IO ()
135+buildRuntime lbi elmos = do
136+ createDirectoryIfMissing True (rtsDir lbi)
137+ writeFile (rts lbi) "Elm = {}; Elm.Native = {}; Elm.Native.Graphics = {};\n\
138+ \Elm.Graphics = {}; ElmRuntime = {}; ElmRuntime.Render = {};\n"
139+ mapM_ (appendJS lbi) =<< getFiles ".js" "libraries"
140+ mapM_ (appendJS lbi) elmos
141+ mapM_ (appendJS lbi) =<< getFiles ".js" "runtime"
142+
116getFiles ext dir = do
143getFiles ext dir = do
117 contents <- map (dir </>) `fmap` getDirectoryContents dir
144 contents <- map (dir </>) `fmap` getDirectoryContents dir
118 let files = filter (\f -> takeExtension f == ext) contents
145 let files = filter (\f -> takeExtension f == ext) contents
@@ -125,33 +152,3 @@ appendJS lbi file = do
125 str <- readFile file
152 str <- readFile file
126 length str `seq` return ()
153 length str `seq` return ()
127 appendFile (rts lbi) str
154 appendFile (rts lbi) str
128−
129−appendElm lbi file = do
130− jsFile <- runElm lbi file
131− appendJS lbi jsFile
132−
133−-- replace 'system' call with 'runProcess' which handles args better and allows env variable
134−-- "Elm_datadir" which is used by LoadLibraries to find docs.json
135−runElm :: LocalBuildInfo -> String -> IO FilePath
136−runElm lbi file = do
137− rts_c <- canonicalizePath (rts lbi) -- dist/data/elm-runtime.js
138− let js = jsDir lbi -- dist/js
139− let j = dropFileName (js </> file) -- dist/js/libraries/
140− createDirectoryIfMissing True j -- must do before any canonicalization
141− out_c <- canonicalizePath js -- dist/js (root folder)
142− elm_c <- canonicalizePath (elm lbi) -- dist/build/elm/elm
143− rtd_c <- canonicalizePath (rtsDir lbi) -- dist/data (for docs.json)
144− handle <- runProcess elm_c ["--only-js", "--no-prelude", "--output-directory="++out_c, file]
145− Nothing (Just [("Elm_datadir", rtd_c)]) Nothing Nothing Nothing
146− exitCode <- waitForProcess handle
147− return $ j </> replaceExtension (takeFileName file) ".js"
148−
149−
150−buildRuntime lbi = do
151− createDirectoryIfMissing False (rtsDir lbi) -- dist should already exist
152− writeFile (rts lbi) "Elm = {}; Elm.Native = {}; Elm.Native.Graphics = {};\n\
153− \Elm.Graphics = {}; ElmRuntime = {}; ElmRuntime.Render = {}\n"
154− removeDirectoryRecursive ("dist" </> "js")
155− mapM_ (appendJS lbi) =<< getFiles ".js" "libraries"
156− mapM_ (appendElm lbi) =<< getFiles ".elm" "libraries"
157− mapM_ (appendJS lbi) =<< getFiles ".js" "runtime"
compiler/Compiler.hs+11−7
@@ -87,9 +87,12 @@ elmi flags filePath = file flags filePath "elmi"
87buildFile :: Flags -> Int -> Int -> Interfaces -> FilePath -> IO ModuleInterface
87buildFile :: Flags -> Int -> Int -> Interfaces -> FilePath -> IO ModuleInterface
88buildFile flags moduleNum numModules interfaces filePath =
88buildFile flags moduleNum numModules interfaces filePath =
89 do compiled <- alreadyCompiled
89 do compiled <- alreadyCompiled
90+ if not compiled then compile
90− if compiled then Binary.decodeFile (elmi flags filePath) else compile
91+ else getInterface `fmap` Binary.decodeFile (elmi flags filePath)
91−
92 where
92 where
93+ getInterface :: (String, ModuleInterface) -> ModuleInterface
94+ getInterface = snd
95+
93 alreadyCompiled :: IO Bool
96 alreadyCompiled :: IO Bool
94 alreadyCompiled = do
97 alreadyCompiled = do
95 existsi <- doesFileExist (elmi flags filePath)
98 existsi <- doesFileExist (elmi flags filePath)
@@ -126,9 +129,8 @@ buildFile flags moduleNum numModules interfaces filePath =
126 iAliases = aliases metaModule
129 iAliases = aliases metaModule
127 }
130 }
128 createDirectoryIfMissing True . dropFileName $ elmi flags filePath
131 createDirectoryIfMissing True . dropFileName $ elmi flags filePath
129− Binary.encodeFile (elmi flags filePath) interface
132+ Binary.encodeFile (elmi flags filePath) (name,interface)
130− let js = jsModule metaModule
131− writeFile (elmo flags filePath) js
133+ writeFile (elmo flags filePath) (jsModule metaModule)
132 return interface
134 return interface
133
135
134toSrcTypes tipes = Traverse.traverse convert tipes
136toSrcTypes tipes = Traverse.traverse convert tipes
@@ -150,8 +152,10 @@ getRuntime flags =
150
152
151build :: Flags -> FilePath -> IO ()
153build :: Flags -> FilePath -> IO ()
152build flags rootFile = do
154build flags rootFile = do
155+ let noPrelude = no_prelude flags
153− files <- if make flags then getSortedModuleNames rootFile else return [rootFile]
156+ files <- if make flags then getSortedModuleNames noPrelude rootFile else return [rootFile]
157+ let ifaces = if noPrelude then Map.empty else Prelude.interfaces
154− interfaces <- buildFiles flags (length files) Prelude.interfaces files
158+ interfaces <- buildFiles flags (length files) ifaces files
155 js <- foldM appendToOutput "" files
159 js <- foldM appendToOutput "" files
156 case only_js flags of
160 case only_js flags of
157 True -> do
161 True -> do
compiler/Initialize.hs+7−6
@@ -52,9 +52,9 @@ buildFromSource interfaces source =
52 return $ metaModule { types = types }
52 return $ metaModule { types = types }
53
53
54
54
55−getSortedModuleNames :: FilePath -> IO [String]
55+getSortedModuleNames :: Bool -> FilePath -> IO [String]
56−getSortedModuleNames root =
56+getSortedModuleNames noPrelude root =
57− sortDeps =<< readDeps root
57+ sortDeps =<< readDeps noPrelude root
58
58
59type Deps = (String, [String])
59type Deps = (String, [String])
60
60
@@ -70,10 +70,11 @@ sortDeps depends =
70 mistakes = filter (\scc -> length scc > 1) sccs
70 mistakes = filter (\scc -> length scc > 1) sccs
71 msg = "A cyclical module dependency or was detected in: "
71 msg = "A cyclical module dependency or was detected in: "
72
72
73−readDeps :: FilePath -> IO [Deps]
73+readDeps :: Bool -> FilePath -> IO [Deps]
74−readDeps root = evalStateT (go root) Set.empty
74+readDeps noPrelude root = evalStateT (go root) Set.empty
75 where
75 where
76+ builtIns = if noPrelude then Set.empty
76− builtIns = Set.fromList (Map.keys Prelude.interfaces)
77+ else Set.fromList (Map.keys Prelude.interfaces)
77
78
78 go :: FilePath -> StateT (Set.Set String) IO [Deps]
79 go :: FilePath -> StateT (Set.Set String) IO [Deps]
79 go root = do
80 go root = do
compiler/Metadata/Prelude.hs+2−2
@@ -8,7 +8,7 @@ import System.Exit
8import System.FilePath
8import System.FilePath
9import System.IO.Unsafe (unsafePerformIO)
9import System.IO.Unsafe (unsafePerformIO)
10import SourceSyntax.Module
10import SourceSyntax.Module
11−import Data.Binary
11+import qualified Data.Binary as Binary
12
12
13
13
14add :: Module t v -> Module t v
14add :: Module t v -> Module t v
@@ -47,5 +47,5 @@ safeReadDocs name =
47readDocs :: FilePath -> IO Interfaces
47readDocs :: FilePath -> IO Interfaces
48readDocs name = do
48readDocs name = do
49 exists <- doesFileExist name
49 exists <- doesFileExist name
50− if exists then decodeFile name
50+ if exists then Map.fromList `fmap` Binary.decodeFile name
51 else ioError . userError $ "File Not Found"
51 else ioError . userError $ "File Not Found"