File: Oracle.hs

package info (click to toggle)
haskell-shake 0.13.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 888 kB
  • ctags: 127
  • sloc: haskell: 6,388; makefile: 35; ansic: 25; sh: 2
file content (118 lines) | stat: -rw-r--r-- 4,747 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving, DeriveDataTypeable, ScopedTypeVariables #-}
-- Allows the user to violate the functional dependency, but it has a runtime check so still safe
{-# LANGUAGE UndecidableInstances #-}

{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 704
{-# LANGUAGE ConstraintKinds #-}
#endif

module Development.Shake.Rules.Oracle(
    addOracle, askOracle, askOracleWith
    ) where

import Development.Shake.Core
import Development.Shake.Classes


-- Use should type names, since the names appear in the Haddock, and are too long if they are in full
newtype OracleQ question = OracleQ question
    deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
newtype OracleA answer = OracleA answer
    deriving (Show,Typeable,Eq,Hashable,Binary,NFData)

instance (
#if __GLASGOW_HASKELL__ >= 704
    ShakeValue q, ShakeValue a
#else
    Show q, Typeable q, Eq q, Hashable q, Binary q, NFData q,
    Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a
#endif
    ) => Rule (OracleQ q) (OracleA a) where
    storedValue _ _ = return Nothing


-- | Add extra information which rules can depend on.
--   An oracle is a function from a question type @q@, to an answer type @a@.
--   As an example, we can define an oracle allowing you to depend on the current version of GHC:
--
-- @
-- newtype GhcVersion = GhcVersion () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
-- rules = do
--     'addOracle' $ \\(GhcVersion _) -> fmap 'Development.Shake.fromStdout' $ 'Development.Shake.cmd' \"ghc --numeric-version\"
--     ... rules ...
-- @
--
--   If a rule calls @'askOracle' (GhcVersion ())@, that rule will be rerun whenever the GHC version changes.
--   Some notes:
--
-- * We define @GhcVersion@ with a @newtype@ around @()@, allowing the use of @GeneralizedNewtypeDeriving@.
--   All the necessary type classes are exported from "Development.Shake.Classes".
--
-- * Each call to 'addOracle' must use a different type of question.
--
-- * Actions passed to 'addOracle' will be run in every build they are required,
--   but if their value does not change they will not invalidate any rules depending on them.
--   To get a similar behaviour using data stored in files, see 'Development.Shake.alwaysRerun'.
--
-- * If the value returned by 'askOracle' is ignored then 'askOracleWith' may help avoid ambiguous type messages.
--   Alternatively, use the result of 'addOracle', which is 'askOracle' restricted to the correct type.
--
--   As a more complex example, consider tracking Haskell package versions:
--
-- @
--newtype GhcPkgList = GhcPkgList () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
--newtype GhcPkgVersion = GhcPkgVersion String deriving (Show,Typeable,Eq,Hashable,Binary,NFData)
--
--rules = do
--    getPkgList \<- 'addOracle' $ \\GhcPkgList{} -> do
--        Stdout out <- 'Development.Shake.cmd' \"ghc-pkg list --simple-output\"
--        return [(reverse b, reverse a) | x <- words out, let (a,_:b) = break (== \'-\') $ reverse x]
--    --
--    getPkgVersion \<- 'addOracle' $ \\(GhcPkgVersion pkg) -> do
--        pkgs <- getPkgList $ GhcPkgList ()
--        return $ lookup pkg pkgs
--    --
--    \"myrule\" *> \\_ -> do
--        getPkgVersion $ GhcPkgVersion \"shake\"
--        ... rule using the shake version ...
-- @
--
--   Using these definitions, any rule depending on the version of @shake@
--   should call @getPkgVersion $ GhcPkgVersion \"shake\"@ to rebuild when @shake@ is upgraded.
addOracle :: (
#if __GLASGOW_HASKELL__ >= 704
    ShakeValue q, ShakeValue a
#else
    Show q, Typeable q, Eq q, Hashable q, Binary q, NFData q,
    Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a
#endif
    ) => (q -> Action a) -> Rules (q -> Action a)
addOracle act = do
    rule $ \(OracleQ q) -> Just $ fmap OracleA $ act q
    return askOracle


-- | Get information previously added with 'addOracle'. The question/answer types must match those provided
--   to 'addOracle'.
askOracle :: (
#if __GLASGOW_HASKELL__ >= 704
    ShakeValue q, ShakeValue a
#else
    Show q, Typeable q, Eq q, Hashable q, Binary q, NFData q,
    Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a
#endif
    ) => q -> Action a
askOracle question = do OracleA answer <- apply1 $ OracleQ question; return answer

-- | Get information previously added with 'addOracle'. The second argument is not used, but can
--   be useful to fix the answer type, avoiding ambiguous type error messages.
askOracleWith :: (
#if __GLASGOW_HASKELL__ >= 704
    ShakeValue q, ShakeValue a
#else
    Show q, Typeable q, Eq q, Hashable q, Binary q, NFData q,
    Show a, Typeable a, Eq a, Hashable a, Binary a, NFData a
#endif
    ) => q -> a -> Action a
askOracleWith question _ = askOracle question