6d4f048c7de429fdfe814d87d272d10ee1707a5f
[ghc.git] / hadrian / src / Hadrian / Oracles / TextFile.hs
1 {-# LANGUAGE TypeFamilies #-}
2 -----------------------------------------------------------------------------
3 -- |
4 -- Module : Hadrian.Oracles.TextFile
5 -- Copyright : (c) Andrey Mokhov 2014-2017
6 -- License : MIT (see the file LICENSE)
7 -- Maintainer : andrey.mokhov@gmail.com
8 -- Stability : experimental
9 --
10 -- Read and parse text files, tracking their contents. This oracle can be used
11 -- to read configuration or package metadata files and cache the parsing.
12 -----------------------------------------------------------------------------
13 module Hadrian.Oracles.TextFile (
14 readTextFile, lookupValue, lookupValueOrEmpty, lookupValueOrError,
15 lookupValues, lookupValuesOrEmpty, lookupValuesOrError, lookupDependencies,
16 readCabalFile, textFileOracle
17 ) where
18
19 import Control.Monad
20 import qualified Data.HashMap.Strict as Map
21 import Data.Maybe
22 import Development.Shake
23 import Development.Shake.Classes
24 import Development.Shake.Config
25
26 import Hadrian.Haskell.Cabal.Parse
27 import Hadrian.Utilities
28
29 newtype TextFile = TextFile FilePath
30 deriving (Binary, Eq, Hashable, NFData, Show, Typeable)
31 type instance RuleResult TextFile = String
32
33 newtype CabalFile = CabalFile FilePath
34 deriving (Binary, Eq, Hashable, NFData, Show, Typeable)
35 type instance RuleResult CabalFile = Cabal
36
37 newtype KeyValue = KeyValue (FilePath, String)
38 deriving (Binary, Eq, Hashable, NFData, Show, Typeable)
39 type instance RuleResult KeyValue = Maybe String
40
41 newtype KeyValues = KeyValues (FilePath, String)
42 deriving (Binary, Eq, Hashable, NFData, Show, Typeable)
43 type instance RuleResult KeyValues = Maybe [String]
44
45 -- | Read a text file, caching and tracking the result. To read and track
46 -- individual lines of a text file use 'lookupValue' and its derivatives.
47 readTextFile :: FilePath -> Action String
48 readTextFile = askOracle . TextFile
49
50 -- | Lookup a value in a text file, tracking the result. Each line of the file
51 -- is expected to have @key = value@ format.
52 lookupValue :: FilePath -> String -> Action (Maybe String)
53 lookupValue file key = askOracle $ KeyValue (file, key)
54
55 -- | Like 'lookupValue' but returns the empty string if the key is not found.
56 lookupValueOrEmpty :: FilePath -> String -> Action String
57 lookupValueOrEmpty file key = fromMaybe "" <$> lookupValue file key
58
59 -- | Like 'lookupValue' but raises an error if the key is not found.
60 lookupValueOrError :: FilePath -> String -> Action String
61 lookupValueOrError file key = fromMaybe (error msg) <$> lookupValue file key
62 where
63 msg = "Key " ++ quote key ++ " not found in file " ++ quote file
64
65 -- | Lookup a list of values in a text file, tracking the result. Each line of
66 -- the file is expected to have @key value1 value2 ...@ format.
67 lookupValues :: FilePath -> String -> Action (Maybe [String])
68 lookupValues file key = askOracle $ KeyValues (file, key)
69
70 -- | Like 'lookupValues' but returns the empty list if the key is not found.
71 lookupValuesOrEmpty :: FilePath -> String -> Action [String]
72 lookupValuesOrEmpty file key = fromMaybe [] <$> lookupValues file key
73
74 -- | Like 'lookupValues' but raises an error if the key is not found.
75 lookupValuesOrError :: FilePath -> String -> Action [String]
76 lookupValuesOrError file key = fromMaybe (error msg) <$> lookupValues file key
77 where
78 msg = "Key " ++ quote key ++ " not found in file " ++ quote file
79
80 -- | The 'Action' @lookupDependencies depFile file@ looks up dependencies of a
81 -- @file@ in a (typically generated) dependency file @depFile@. The action
82 -- returns a pair @(source, files)@, such that the @file@ can be produced by
83 -- compiling @source@, which in turn also depends on a number of other @files@.
84 lookupDependencies :: FilePath -> FilePath -> Action (FilePath, [FilePath])
85 lookupDependencies depFile file = do
86 deps <- lookupValues depFile file
87 case deps of
88 Nothing -> error $ "No dependencies found for file " ++ quote file
89 Just [] -> error $ "No source file found for file " ++ quote file
90 Just (source : files) -> return (source, files)
91
92 -- | Read and parse a @.cabal@ file, caching and tracking the result.
93 readCabalFile :: FilePath -> Action Cabal
94 readCabalFile = askOracle . CabalFile
95
96 -- | This oracle reads and parses text files to answer 'readTextFile' and
97 -- 'lookupValue' queries, as well as their derivatives, tracking the results.
98 textFileOracle :: Rules ()
99 textFileOracle = do
100 text <- newCache $ \file -> do
101 need [file]
102 putLoud $ "| TextFile oracle: reading " ++ quote file ++ "..."
103 liftIO $ readFile file
104 void $ addOracle $ \(TextFile file) -> text file
105
106 kv <- newCache $ \file -> do
107 need [file]
108 putLoud $ "| KeyValue oracle: reading " ++ quote file ++ "..."
109 liftIO $ readConfigFile file
110 void $ addOracle $ \(KeyValue (file, key)) -> Map.lookup key <$> kv file
111
112 kvs <- newCache $ \file -> do
113 need [file]
114 putLoud $ "| KeyValues oracle: reading " ++ quote file ++ "..."
115 contents <- map words <$> readFileLines file
116 return $ Map.fromList [ (key, values) | (key:values) <- contents ]
117 void $ addOracle $ \(KeyValues (file, key)) -> Map.lookup key <$> kvs file
118
119 cabal <- newCache $ \file -> do
120 need [file]
121 putLoud $ "| CabalFile oracle: reading " ++ quote file ++ "..."
122 liftIO $ parseCabal file
123 void $ addOracle $ \(CabalFile file) -> cabal file