@@ -1,26 +1,34 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2+{-# LANGUAGE BangPatterns, OverloadedStrings #-}
2module Elm.Details
3module Elm.Details
3 ( Details(..)
4 ( Details(..)
4 , ValidOutline(..)
5 , ValidOutline(..)
5 , Local(..)
6 , Local(..)
6 , Foreign(..)
7 , Foreign(..)
11import Control.Concurrent (forkIO)
13import Control.Concurrent (forkIO)
12−import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, readMVar)
14+import Control.Concurrent.MVar (MVar, newEmptyMVar, newMVar, putMVar, readMVar, takeMVar)
13import Control.Monad (liftM, liftM2, liftM3, liftM4)
15import Control.Monad (liftM, liftM2, liftM3, liftM4)
14import Data.Binary (Binary, get, put, getWord8, putWord8)
16import Data.Binary (Binary, get, put, getWord8, putWord8)
17+import Data.Foldable (traverse_)
15import qualified Data.Map as Map
18import qualified Data.Map as Map
16import qualified Data.Map.Merge.Strict as Map
19import qualified Data.Map.Merge.Strict as Map
20+import qualified Data.OneOrMore as OneOrMore
17import qualified Data.Set as Set
21import qualified Data.Set as Set
18import qualified Data.Utf8 as Utf8
22import qualified Data.Utf8 as Utf8
19−import System.Directory (doesDirectoryExist)
23+import qualified System.Directory as Dir
20−import System.FilePath ((</>))
24+import System.FilePath ((</>), (<.>))
26+import qualified AST.Source as Src
27+import qualified AST.Optimized as Opt
28+import qualified Compile
22import qualified Deps.Registry as Registry
29import qualified Deps.Registry as Registry
23import qualified Deps.Solver as Solver
30import qualified Deps.Solver as Solver
31+import qualified Deps.Website as Website
24import qualified Elm.Constraint as Con
32import qualified Elm.Constraint as Con
25import qualified Elm.Interface as I
33import qualified Elm.Interface as I
26import qualified Elm.ModuleName as ModuleName
34import qualified Elm.ModuleName as ModuleName
@@ -30,8 +38,8 @@ import qualified Elm.Version as V
30import qualified File
38import qualified File
31import qualified Http
39import qualified Http
32import qualified Json.Decode as D
40import qualified Json.Decode as D
41+import qualified Parse.Module as Parse
33import qualified Reporting.Exit as Exit
42import qualified Reporting.Exit as Exit
34−import qualified Reporting.Progress as Progress
35import qualified Reporting.Task as Task
43import qualified Reporting.Task as Task
36import qualified Stuff
44import qualified Stuff
@@ -67,6 +75,466 @@ data Foreign =
81+verify :: FilePath -> Task.Task Details
83+ do newTime <- Task.io $ File.getTime (root </> "elm.json")
84+ maybeDetails <- Task.io $ File.readBinary (root </> Stuff.details)
89+ Just details@(Details oldTime _ _ _) ->
90+ if oldTime == newTime
92+ else rebuild root newTime
99+rebuild :: FilePath -> File.Time -> Task.Task Details
101+ do (outline, info) <- rebuildHelp root
103+ Outline.Pkg pkg -> solvePkg info time pkg
104+ Outline.App app -> solveApp info time app
108+ Info Stuff.PackageCache Http.Manager Connection Registry.Registry
116+rebuildHelp :: FilePath -> Task.Task (Outline.Outline, Info)
119+ do cacheMVar <- fork Stuff.getPackageCache
120+ managerMVar <- fork Http.getManager
121+ registryMVar <- fork (Registry.read =<< readMVar cacheMVar)
122+ eitherOutline <- Outline.read root
123+ cache <- readMVar cacheMVar
124+ manager <- readMVar managerMVar
125+ maybeRegistry <- readMVar registryMVar
126+ case eitherOutline of
128+ return $ Left $ Exit.DetailsBadOutline problem
131+ case maybeRegistry of
133+ do eitherRegistry <- Registry.fetch manager cache
134+ case eitherRegistry of
135+ Right latestRegistry ->
136+ return $ Right (outline, Info cache manager Online latestRegistry)
139+ return $ Left $ Exit.DetailsCannotGetRegistry problem
141+ Just cachedRegistry ->
142+ do eitherRegistry <- Registry.update manager cache cachedRegistry
143+ case eitherRegistry of
144+ Right latestRegistry ->
145+ return $ Right (outline, Info cache manager Online latestRegistry)
148+ return $ Right (outline, Info cache manager Offline cachedRegistry)
155+solvePkg :: Info -> File.Time -> Outline.PkgOutline -> Task.Task Details
156+solvePkg info time outline@(Outline.PkgOutline _ _ _ _ _ direct testDirect elm) =
159+ do solution <- solve info =<< union noDups direct testDirect
160+ foreigns <- verifyDependencies info solution direct
161+ let vsns = Map.map (\(Solver.Details v _) -> v) solution
162+ return $ Details time (ValidPkg outline vsns) Map.empty foreigns
164+ Task.throw $ Exit.DetailsBadElmInPkg elm
171+solveApp :: Info -> File.Time -> Outline.AppOutline -> Task.Task Details
172+solveApp info time outline@(Outline.AppOutline elmVersion _ direct _ _ _) =
173+ if elmVersion == V.compiler
175+ do stated <- checkAppDeps outline
176+ actual <- solve info (Map.map Con.exactly stated)
177+ if Map.size stated == Map.size actual
179+ do foreigns <- verifyDependencies info actual direct
180+ return $ Details time (ValidApp outline) Map.empty foreigns
182+ Task.throw $ error "TODO solveApp"
184+ Task.throw $ Exit.DetailsBadElmInApp elmVersion
187+checkAppDeps :: Outline.AppOutline -> Task.Task (Map.Map Pkg.Name V.Version)
188+checkAppDeps (Outline.AppOutline _ _ direct indirect testDirect testIndirect) =
189+ do x <- union allowEqualDups indirect testDirect
190+ y <- union noDups direct testIndirect
198+solve :: Info -> Map.Map Pkg.Name Con.Constraint -> Task.Task (Map.Map Pkg.Name Solver.Details)
199+solve (Info cache manager connection registry) constraints =
200+ Task.eio (error "TODO solve problem") $
202+ Online -> Solver.online cache manager registry constraints
203+ Offline -> Solver.offline cache registry constraints
210+union :: (Ord k) => (k -> v -> v -> Task.Task v) -> Map.Map k v -> Map.Map k v -> Task.Task (Map.Map k v)
211+union tieBreaker deps1 deps2 =
212+ Map.mergeA Map.preserveMissing Map.preserveMissing (Map.zipWithAMatched tieBreaker) deps1 deps2
215+noDups :: k -> v -> v -> Task.Task v
217+ Task.throw Exit.DetailsHandEditedDependencies
220+allowEqualDups :: (Eq v) => k -> v -> v -> Task.Task v
221+allowEqualDups _ v1 v2 =
224+ else Task.throw Exit.DetailsHandEditedDependencies
231+fork :: IO a -> IO (MVar a)
233+ do mvar <- newEmptyMVar
234+ _ <- forkIO $ putMVar mvar =<< work
239+-- VERIFY DEPENDENCIES
242+verifyDependencies :: Info -> Map.Map Pkg.Name Solver.Details -> Map.Map Pkg.Name a -> Task.Task (Map.Map ModuleName.Raw Foreign)
243+verifyDependencies info solution directDeps =
244+ Task.eio (error "TODO verifyDependencies") $
245+ do mvar <- newEmptyMVar
246+ mvars <- Map.traverseWithKey (\k v -> fork (verifyDep info mvar solution k v)) solution
248+ results <- traverse readMVar mvars
249+ case sequence results of
251+ return (Left (error "TODO one of the dependencies did not build"))
255+ objs = Map.foldr gatherObjects Opt.empty artifacts
256+ ifaces = Map.foldrWithKey (gatherInterfaces directDeps) Map.empty artifacts
257+ foreigns = Map.map (OneOrMore.destruct Foreign) $ Map.foldrWithKey gatherForeigns Map.empty artifacts
259+ do File.writeBinary Stuff.objects objs
260+ File.writeBinary Stuff.interfaces ifaces
261+ return (Right foreigns)
264+gatherObjects :: Artifacts -> Opt.Graph -> Opt.Graph
265+gatherObjects (Artifacts _ objs) graph =
266+ Opt.union objs graph
269+gatherInterfaces :: Map.Map Pkg.Name a -> Pkg.Name -> Artifacts -> Map.Map ModuleName.Canonical I.DependencyInterface -> Map.Map ModuleName.Canonical I.DependencyInterface
270+gatherInterfaces directDeps pkg (Artifacts ifaces _) dependencyInterfaces =
271+ Map.union dependencyInterfaces $ Map.mapKeys (ModuleName.Canonical pkg) $
272+ if Map.member pkg directDeps
274+ else Map.map I.privatize ifaces
277+gatherForeigns :: Pkg.Name -> Artifacts -> Map.Map ModuleName.Raw (OneOrMore.OneOrMore Pkg.Name) -> Map.Map ModuleName.Raw (OneOrMore.OneOrMore Pkg.Name)
278+gatherForeigns pkg (Artifacts ifaces _) foreigns =
282+ I.Public _ -> Just (OneOrMore.one pkg)
283+ I.Private _ _ -> Nothing
285+ Map.unionWith OneOrMore.more foreigns (Map.mapMaybe isPublic ifaces)
294+ { _ifaces :: Map.Map ModuleName.Raw I.DependencyInterface
295+ , _objects :: Opt.Graph
300+ Either Problem Artifacts
304+ = BadDownload Exit.PackageProblem
309+verifyDep :: Info -> MVar (Map.Map Pkg.Name (MVar Result)) -> Map.Map Pkg.Name Solver.Details -> Pkg.Name -> Solver.Details -> IO Result
310+verifyDep (Info cache manager _ _) resultsMVar solution name details@(Solver.Details version directDeps) =
312+ fingerprint = Map.intersectionWith (\(Solver.Details v _) _ -> v) solution directDeps
314+ do exists <- Dir.doesDirectoryExist (Stuff.package cache name version </> "src")
317+ do maybeCache <- File.readBinary (Stuff.package cache name version </> "artifacts.dat")
320+ build cache resultsMVar name details (Set.singleton fingerprint)
322+ Just (ArtifactCache fingerprints artifacts) ->
323+ if Set.member fingerprint fingerprints
324+ then return (Right artifacts)
325+ else build cache resultsMVar name details (Set.insert fingerprint fingerprints)
327+ do result <- downloadPackage cache manager name version
329+ Left p -> return (Left (BadDownload p))
330+ Right () -> build cache resultsMVar name details (Set.singleton fingerprint)
339+ { _fingerprints :: Set.Set Fingerprint
340+ , _artifacts :: Artifacts
345+ Map.Map Pkg.Name V.Version
352+build :: Stuff.PackageCache -> MVar (Map.Map Pkg.Name (MVar Result)) -> Pkg.Name -> Solver.Details -> Set.Set Fingerprint -> IO Result
353+build cache resultsMVar name (Solver.Details version _) fingerprints =
354+ do eitherOutline <- Outline.read (Stuff.package cache name version </> "elm.json")
355+ case eitherOutline of
357+ return (Left BadBuild)
359+ Right (Outline.App _) ->
360+ return (Left BadBuild)
362+ Right (Outline.Pkg (Outline.PkgOutline _ _ _ _ exposed deps _ _)) ->
363+ do allResults <- readMVar resultsMVar
364+ directResults <- traverse readMVar (Map.intersection allResults deps)
365+ case sequence directResults of
367+ return (Left BadDependency)
369+ Right directArtifacts ->
370+ do mvar <- newMVar Map.empty
371+ let src = Stuff.package cache name version </> "src"
372+ let foreignDeps = gatherForeignInterfaces directArtifacts
373+ let exposedDict = Map.fromList $ map (\n -> (n,())) (Outline.flattenExposed exposed)
374+ traverse_ readMVar =<<
375+ Map.traverseWithKey (const . fork . buildModule foreignDeps mvar name src) exposedDict
376+ result <- traverse readMVar =<< readMVar mvar
377+ case Map.traverseMaybeWithKey checkStatus result of
379+ return (Left BadBuild)
381+ Just artifactsDict ->
383+ path = Stuff.package cache name version </> "artifacts.dat"
384+ ifaces = gatherPackageInterfaces exposedDict artifactsDict
385+ objects = Map.foldr (Opt.union . snd) Opt.empty artifactsDict
386+ artifacts = Artifacts ifaces objects
388+ do File.writeBinary path (ArtifactCache fingerprints artifacts)
389+ return (Right artifacts)
392+checkStatus :: ModuleName.Raw -> Status -> Maybe (Maybe (I.Interface, Opt.Graph))
393+checkStatus _ status =
396+ SLocal i g -> Just (Just (i,g))
397+ SForeign _ -> Just Nothing
400+gatherPackageInterfaces :: Map.Map ModuleName.Raw () -> Map.Map ModuleName.Raw (I.Interface, Opt.Graph) -> Map.Map ModuleName.Raw I.DependencyInterface
401+gatherPackageInterfaces exposed artifacts =
402+ Map.merge onLeft onRight onBoth exposed artifacts
404+ onLeft = Map.mapMissing (error "compiler bug manifesting in Elm.Details.gatherPackageInterfaces")
405+ onRight = Map.mapMissing (\_ (i,_) -> I.private i)
406+ onBoth = Map.zipWithMatched (\_ () (i,_) -> I.public i)
410+-- FOREIGN INTERFACES
413+data ForeignInterface
415+ | ForeignSpecific I.Interface
418+gatherForeignInterfaces :: Map.Map Pkg.Name Artifacts -> Map.Map ModuleName.Raw ForeignInterface
419+gatherForeignInterfaces directArtifacts =
420+ Map.map (OneOrMore.destruct finalize) $
421+ Map.foldrWithKey gather Map.empty directArtifacts
423+ finalize :: I.Interface -> [I.Interface] -> ForeignInterface
426+ [] -> ForeignSpecific i
427+ _:_ -> ForeignAmbiguous
429+ gather :: Pkg.Name -> Artifacts -> Map.Map ModuleName.Raw (OneOrMore.OneOrMore I.Interface) -> Map.Map ModuleName.Raw (OneOrMore.OneOrMore I.Interface)
430+ gather _ (Artifacts ifaces _) buckets =
431+ Map.unionWith OneOrMore.more buckets (Map.mapMaybe isPublic ifaces)
433+ isPublic :: I.DependencyInterface -> Maybe (OneOrMore.OneOrMore I.Interface)
436+ I.Public iface -> Just (OneOrMore.one iface)
437+ I.Private _ _ -> Nothing
445+ Map.Map ModuleName.Raw (MVar Status)
449+ = SLocal !I.Interface !Opt.Graph
450+ | SForeign I.Interface
454+buildModule :: Map.Map ModuleName.Raw ForeignInterface -> MVar StatusDict -> Pkg.Name -> FilePath -> ModuleName.Raw -> IO Status
455+buildModule foreignDeps mvar pkg src name =
456+ do let path = src </> ModuleName.toFilePath name <.> "elm"
457+ exists <- File.exists path
458+ case Map.lookup name foreignDeps of
459+ Just ForeignAmbiguous ->
462+ Just (ForeignSpecific iface) ->
464+ then return SFailure
465+ else return (SForeign iface)
469+ then return SFailure
471+ do result <- Parse.fromFile pkg path
473+ Right modul@(Src.Module (Just actualName) _ deps _ _ _ _ _) | name == actualName ->
474+ do statusDict <- takeMVar mvar
475+ let depsDict = Map.fromList $ map (\i -> (Src.getImport i, ())) deps
476+ let newsDict = Map.difference depsDict statusDict
477+ statusMVars <- Map.traverseWithKey (const . fork . buildModule foreignDeps mvar pkg src) newsDict
478+ putMVar mvar (Map.union statusMVars statusDict)
479+ statuses <- traverse readMVar statusMVars
480+ case traverse getInterface statuses of
485+ case Compile.compile pkg imports modul of
486+ (_, Right (Compile.Artifacts canonical annotations objects)) ->
487+ return (SLocal (I.fromModule pkg canonical annotations) objects)
496+getInterface :: Status -> Maybe I.Interface
497+getInterface status =
499+ SLocal iface _ -> Just iface
500+ SForeign iface -> Just iface
508+downloadPackage :: Stuff.PackageCache -> Http.Manager -> Pkg.Name -> V.Version -> IO (Either Exit.PackageProblem ())
509+downloadPackage cache manager name version =
510+ do eitherByteString <-
511+ Http.post manager (Website.metadata name version "endpoint.json") [] id (return . Right)
513+ case eitherByteString of
515+ return (Left (Exit.PP_BadEndpointRequest err))
518+ case D.fromByteString endpointDecoder byteString of
520+ return (Left Exit.PP_BadEndpointContent)
522+ Right (endpoint, expectedHash) ->
523+ Http.fetchArchive manager endpoint Exit.PP_BadArchiveRequest Exit.PP_BadArchiveContent $
525+ if expectedHash == Http.shaToChars sha
526+ then Right <$> File.writeArchive (Stuff.package cache name version) archive
527+ else return $ Left $ Exit.PP_BadArchiveHash expectedHash (Http.shaToChars sha)
530+endpointDecoder :: D.Decoder e (String, String)
532+ do url <- D.field "url" D.string
533+ hash <- D.field "hash" D.string
534+ return (Utf8.toChars url, Utf8.toChars hash)
@@ -100,5 +568,10 @@ instance Binary Foreign where
102instance Binary Artifacts where
570instance Binary Artifacts where
103− get = liftM3 Artifacts get get get
571+ get = liftM2 Artifacts get get
104− put (Artifacts a b c) = put a >> put b >> put c
572+ put (Artifacts a b) = put a >> put b
575+instance Binary ArtifactCache where
576+ get = liftM2 ArtifactCache get get
577+ put (ArtifactCache a b) = put a >> put b