-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Build systems a la carte -- -- A library for experimenting with build systems and incremental -- computation frameworks, based on the ideas presented in the ICFP 2018 -- paper "Build Systems a la Carte". @package build @version 1.0 -- | An abstract key/value store. module Build.Store -- | A Hash is used for efficient tracking and sharing of build -- results. We use newtype Hash a = Hash a for prototyping. data Hash a class Ord a => Hashable a -- | Compute the hash of a given value. We typically assume cryptographic -- hashing, e.g. SHA256. hash :: Hashable a => a -> Hash a -- | An abstract datatype for a key/value store with build information of -- type i. data Store i k v -- | Read the value of a key. getValue :: k -> Store i k v -> v -- | Update the value of a key. putValue :: Eq k => k -> v -> Store i k v -> Store i k v -- | Read the hash of a key's value. In some cases may be implemented more -- efficiently than hash . getValue k. getHash :: Hashable v => k -> Store i k v -> Hash v -- | Read the build information. getInfo :: Store i k v -> i -- | Write the build information. putInfo :: i -> Store i k v -> Store i k v -- | Modify the build information. mapInfo :: (i -> j) -> Store i k v -> Store j k v -- | Initialise the store. initialise :: i -> (k -> v) -> Store i k v instance GHC.Show.Show a => GHC.Show.Show (Build.Store.Hash a) instance GHC.Classes.Ord a => GHC.Classes.Ord (Build.Store.Hash a) instance GHC.Classes.Eq a => GHC.Classes.Eq (Build.Store.Hash a) instance Build.Store.Hashable GHC.Types.Int instance Build.Store.Hashable GHC.Integer.Type.Integer instance Build.Store.Hashable a => Build.Store.Hashable [a] instance Build.Store.Hashable a => Build.Store.Hashable (Build.Store.Hash a) instance (Build.Store.Hashable a, Build.Store.Hashable b) => Build.Store.Hashable (a, b) instance GHC.Base.Functor Build.Store.Hash instance GHC.Base.Applicative Build.Store.Hash -- | The Task abstractions. module Build.Task -- | A Task is used to compute a value of type v, by -- finding the necessary dependencies using the provided fetch :: k -- -> f v callback. newtype Task c k v Task :: forall f. c f => (k -> f v) -> f v -> Task c k v [run] :: Task c k v -> forall f. c f => (k -> f v) -> f v -- | Tasks associates a Task with every non-input key. -- Nothing indicates that the key is an input. type Tasks c k v = k -> Maybe (Task c k v) -- | Compose two task descriptions, preferring the first one in case there -- are two tasks corresponding to the same key. compose :: Tasks Monad k v -> Tasks Monad k v -> Tasks Monad k v -- | Lift an applicative task to Task Monad. Use this function -- when applying monadic task combinators to applicative tasks. liftTask :: Task Applicative k v -> Task Monad k v -- | Lift a collection of applicative tasks to Tasks Monad. Use -- this function when building applicative tasks with a monadic build -- system. liftTasks :: Tasks Applicative k v -> Tasks Monad k v -- | This module defines two different strategies of self-tracking, based -- around the idea of storing task descriptions that can be parsed into a -- Task. -- -- module Build.SelfTracking -- | We assume that the fetch passed to a Task is consistent and returns -- values matching the keys. It is possible to switch to typed tasks to -- check this assumption at compile time, e.g. see -- Build.Task.Typed. data Key k Key :: k -> Key k KeyTask :: k -> Key k data Value v t Value :: v -> Value v t ValueTask :: t -> Value v t -- | A model for Monad, works beautifully and allows storing the key -- on the disk. selfTrackingM :: forall k v t. (t -> Task Monad k v) -> Tasks Monad k t -> Tasks Monad (Key k) (Value v t) -- | The applicative model requires every key to be able to associate with -- its environment (e.g. a reader somewhere). Does not support cutoff if -- a key changes. selfTrackingA :: (t -> Task Applicative k v) -> (k -> t) -> Tasks Applicative (Key k) (Value v t) -- | Support for multiple-output tasks. module Build.Multi -- | Defines a set partition. For a function to be a valid partition, if -- f k == ks, then: -- -- type Partition k = k -> [k] -- | Given a task description with individual multiple-output keys, compute -- its "closure" supporting all possible combinations of keys. multi :: Eq k => Partition k -> Tasks Applicative [k] [v] -> Tasks Applicative [k] [v] -- | Applicative tasks, as used by Make, Ninja and other applicative build -- systems. Dependencies of applicative tasks are known statically, -- before their execution. module Build.Task.Applicative -- | Find the dependencies of an applicative task. dependencies :: Task Applicative k v -> [k] -- | Functorial tasks, which have exactly one statically known dependency. -- Docker is an example of a functorial build system: Docker containers -- are organised in layers, where each layer makes changes to the -- previous one. module Build.Task.Functor -- | Find the dependency of a functorial task. dependency :: Task Functor k v -> k -- | Monadic tasks, as used by Excel, Shake and other build systems. -- Dependencies of monadic tasks can only be discovered dynamically, i.e. -- during their execution. module Build.Task.Monad -- | Execute a monadic task using an effectful fetch function k -> m -- v, tracking the dependencies. track :: forall m k v. Monad m => Task Monad k v -> (k -> m v) -> m (v, [(k, v)]) -- | Execute a monadic task on a pure store k -> v, tracking -- the dependencies. trackPure :: Task Monad k v -> (k -> v) -> (v, [k]) -- | Given a description of tasks, check if a key is input. isInput :: Tasks Monad k v -> k -> Bool -- | Run a task with a pure lookup function. computePure :: Task Monad k v -> (k -> v) -> v -- | Run a task in a given store. compute :: Task Monad k v -> Store i k v -> v -- | Convert a task with a total lookup function k -> m v into -- a task with a partial lookup function k -> m (Maybe v). -- This essentially lifts the task from the type of values v to -- Maybe v, where the result Nothing indicates that the -- task failed because of a missing dependency. partial :: Task Monad k v -> Task Monad k (Maybe v) -- | Convert a task with a total lookup function k -> m v into -- a task with a lookup function that can throw exceptions k -> m -- (Either e v). This essentially lifts the task from the type of -- values v to Either e v, where the result Left -- e indicates that the task failed because of a failed dependency -- lookup, and Right v yeilds the value otherwise. exceptional :: Task Monad k v -> Task Monad k (Either e v) -- | A version of monadic tasks with some support for non-determinism. module Build.Task.MonadPlus -- | An example of a non-deterministic task: generate a random number from -- a specified interval. random :: (Int, Int) -> Task MonadPlus k Int -- | Run a task in a given store. computeND :: Task MonadPlus k v -> Store i k v -> [v] -- | Given a description of tasks, an initial store, and -- a result produced by running a build system on a target -- key, this function returns True if the key's -- value is a possible result of running the associated task. correctBuildValue :: Eq v => Tasks MonadPlus k v -> Store i k v -> Store i k v -> k -> Bool -- | A model of polymorphic tasks, where the value type depends on the key. -- See the source for an example. module Build.Task.Typed -- | A typed build task. type Task c k = forall f. c f => (forall k. Key k => k -> f (Value k)) -> k -> Maybe (f (Value k)) -- | A type class for keys, equipped with an associated type family that -- can be used to determine the type of value corresponding to the key. class Key k where { type family Value k :: *; } -- | The name of the key. Useful for avoiding heterogeneous lists of keys. showKey :: Key k => k -> String -- | Extract the names of dependencies. showDependencies :: Task Applicative k -> k -> [String] instance GHC.Show.Show (Build.Task.Typed.ExampleKey a) instance Build.Task.Typed.Key (Build.Task.Typed.ExampleKey a) -- | Build traces that are used for recording information from previuos -- builds. module Build.Trace -- | A trace is parameterised by the types of keys k, hashes -- h, as well as the result r. For verifying traces, -- r = h; for constructive traces, Hash r = h. data Trace k v r Trace :: k -> [(k, Hash v)] -> r -> Trace k v r [key] :: Trace k v r -> k [depends] :: Trace k v r -> [(k, Hash v)] [result] :: Trace k v r -> r -- | An abstract data type for a set of verifying traces equipped with -- recordVT, verifyVT and a Monoid instance. data VT k v -- | Record a new trace for building a key with dependencies -- deps, obtaining the hashes of up-to-date values by using -- fetchHash. recordVT :: k -> Hash v -> [(k, Hash v)] -> VT k v -> VT k v -- | Given a function to compute the hash of a key's current value, a -- key, and a set of verifying traces, return True if the -- key is up-to-date. verifyVT :: (Monad m, Eq k, Eq v) => k -> Hash v -> (k -> m (Hash v)) -> VT k v -> m Bool -- | An abstract data type for a set of constructive traces equipped with -- recordCT, isDirtyCT, constructCT and a -- Monoid instance. data CT k v -- | Check if a given key is dirty w.r.t a store. isDirtyCT :: (Eq k, Hashable v) => k -> Store (CT k v) k v -> Bool -- | Record a new trace for building a key with dependencies -- deps, obtaining the hashes of up-to-date values by using -- fetchHash. recordCT :: k -> v -> [(k, Hash v)] -> CT k v -> CT k v -- | Given a function to compute the hash of a key's current value, a -- key, and a set of constructive traces, return Just -- newValue if it is possible to reconstruct it from the traces. -- Prefer reconstructing the currenct value, if it matches one of the -- traces. constructCT :: (Monad m, Eq k, Eq v) => k -> (k -> m (Hash v)) -> CT k v -> m [v] -- | Our current model has the same representation as CT, but -- requires an additional invariant: if a DCT contains a trace for a key -- k, then it must also contain traces for each of its non-input -- dependencies. data DCT k v -- | Record a new trace for building a key with dependencies -- deps, obtaining the hashes of up-to-date values from the -- given store. recordDCT :: forall k v m. (Eq k, Hashable v, Monad m) => k -> v -> [k] -> (k -> m (Hash v)) -> DCT k v -> m (DCT k v) -- | Given a function to compute the hash of a key's current value, a -- key, and a set of deep constructive traces, return Just -- newValue if it is possible to reconstruct it from the traces. constructDCT :: forall k v m. (Eq k, Hashable v, Monad m) => k -> (k -> m (Hash v)) -> DCT k v -> m [v] data Step -- | A step trace, records the resulting value, the step it last build, the -- step where it changed. data ST k v -- | Record a new trace for building a key with dependencies -- deps. recordST :: (Hashable v, Eq k) => Step -> k -> v -> [k] -> ST k v -> ST k v -- | Given a function to compute the hash of a key's current value, a -- key, and a set of verifying traces, return True if the -- key is up-to-date. verifyST :: (Monad m, Eq k, Hashable v) => k -> v -> (k -> m ()) -> m (ST k v) -> m Bool instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Build.Trace.ST k v) instance GHC.Base.Semigroup (Build.Trace.ST k v) instance GHC.Base.Monoid (Build.Trace.ST k v) instance (GHC.Show.Show k, GHC.Show.Show r) => GHC.Show.Show (Build.Trace.TraceST k r) instance GHC.Show.Show Build.Trace.Step instance GHC.Classes.Ord Build.Trace.Step instance GHC.Classes.Eq Build.Trace.Step instance GHC.Enum.Enum Build.Trace.Step instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Build.Trace.DCT k v) instance GHC.Base.Semigroup (Build.Trace.DCT k v) instance GHC.Base.Monoid (Build.Trace.DCT k v) instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Build.Trace.CT k v) instance GHC.Base.Semigroup (Build.Trace.CT k v) instance GHC.Base.Monoid (Build.Trace.CT k v) instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Build.Trace.VT k v) instance GHC.Base.Semigroup (Build.Trace.VT k v) instance GHC.Base.Monoid (Build.Trace.VT k v) instance (GHC.Show.Show k, GHC.Show.Show v, GHC.Show.Show r) => GHC.Show.Show (Build.Trace.Trace k v r) instance GHC.Base.Semigroup Build.Trace.Step instance GHC.Base.Monoid Build.Trace.Step -- | Rebuilders take care of deciding whether a key needs to be rebuild and -- running the corresponding task if need be. module Build.Rebuilder -- | Given a key-value pair and the corresponding task, a rebuilder returns -- a new task that has access to the build information and can use it to -- skip rebuilding a key if it is up to date. type Rebuilder c i k v = k -> v -> Task c k v -> Task (MonadState i) k v -- | Get an applicative rebuilder out of a monadic one. adaptRebuilder :: Rebuilder Monad i k v -> Rebuilder Applicative i k v -- | Always rebuilds the key. perpetualRebuilder :: Rebuilder Monad () k v -- | This rebuilder uses modification time to decide whether a key is dirty -- and needs to be rebuilt. Used by Make. modTimeRebuilder :: Ord k => Rebuilder Applicative (MakeInfo k) k v type Time = Integer type MakeInfo k = (Time, Map k Time) -- | If the key is dirty, rebuild it. Used by Excel. dirtyBitRebuilder :: Rebuilder Monad (k -> Bool) k v -- | If the key is dirty, rebuild it and clear the dirty bit. Used by -- Excel. dirtyBitRebuilderWithCleanUp :: Ord k => Rebuilder Monad (Set k) k v -- | This rebuilders uses approximate dependencies to decide whether a key -- needs to be rebuilt. approximateRebuilder :: (Ord k, Eq v) => Rebuilder Monad (ApproximationInfo k) k v -- | If there is an entry for a key, it is an conservative approximation of -- its dependencies. Otherwise, we have no reasonable approximation and -- assume the key is always dirty (e.g. it uses an INDIRECT reference). type ApproximateDependencies k = Map k [k] -- | A set of dirty keys and information about dependencies. type ApproximationInfo k = (Set k, ApproximateDependencies k) -- | This rebuilder relies on verifying traces. vtRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (VT k v) k v -- | This rebuilder relies on version/step traces. stRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (Step, ST k v) k v -- | This rebuilder relies on constructive traces. ctRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (CT k v) k v -- | This rebuilder relies on deep constructive traces. dctRebuilder :: (Eq k, Hashable v) => Rebuilder Monad (DCT k v) k v -- | Build systems and the properties they should ensure. module Build -- | A build system takes a description of Tasks, a target key, and -- a store, and computes a new store, where the key and its dependencies -- are up to date. type Build c i k v = Tasks c k v -> k -> Store i k v -> Store i k v -- | Given a description of tasks, an initial store, and -- a result produced by running a build system on a target -- key, this function returns True if the result -- is a correct build outcome. Specifically: * result and -- store must agree on the values of all inputs. In other words, -- no inputs were corrupted during the build. * result is -- consistent with the tasks, i.e. for every non-input -- key, the result of recomputing its task matches the value stored in -- the result. correctBuild :: (Ord k, Eq v) => Tasks Monad k v -> Store i k v -> Store i k v -> k -> Bool -- | Build schedulers execute task rebuilders in the right order. module Build.Scheduler -- | This scheduler constructs the dependency graph of the target key by -- extracting all (static) dependencies upfront, and then traversing the -- graph in the topological order, rebuilding keys using the supplied -- rebuilder. topological :: forall i k v. Ord k => Scheduler Applicative i i k v -- | A model of the scheduler used by Excel, which builds keys in the order -- used in the previous build. If a key cannot be build because its -- dependencies have changed and a new dependency is still dirty, the -- corresponding build task is abandoned and the key is moved at the end -- of the calculation chain, so it can be restarted when all its -- dependencies are up to date. restarting :: forall ir k v. Ord k => Scheduler Monad (ir, Chain k) ir k v -- | The so-called calculation chain: the order in which keys were -- built during the previous build, which is used as the best guess for -- the current build by Excel and other similar build systems. type Chain k = [k] -- | A model of the scheduler used by Bazel. We extract a key K from the -- queue and try to build it. There are now two cases: 1. The build fails -- because one of the dependencies of K is dirty. In this case we add the -- dirty dependency to the queue, listing K as blocked by it. 2. The -- build succeeds, in which case we add all keys that were previously -- blocked by K to the queue. restarting2 :: forall k v. (Hashable v, Eq k) => Scheduler Monad (CT k v) (CT k v) k v -- | This scheduler builds keys recursively: to build a key it executes the -- associated task, discovering its dependencies on the fly, and if one -- of the dependencies is dirty, the task is suspended until the -- dependency is rebuilt. It stores the set of keys that have already -- been built as part of the state to avoid executing the same task -- twice. suspending :: forall i k v. Ord k => Scheduler Monad i i k v -- | An incorrect scheduler that builds the target key without respecting -- its dependencies. It produces the correct result only if all -- dependencies of the target key are up to date. independent :: forall i k v. Eq k => Scheduler Monad i i k v instance GHC.Base.Monad (Build.Scheduler.Wrap i extra k v) instance GHC.Base.Applicative (Build.Scheduler.Wrap i extra k v) instance GHC.Base.Functor (Build.Scheduler.Wrap i extra k v) instance Control.Monad.State.Class.MonadState i (Build.Scheduler.Wrap i extra k v) -- | Models of several build systems. module Build.System -- | This is not a correct build system: given a target key, it simply -- rebuilds it, without rebuilding any of its dependencies. dumb :: Eq k => Build Monad () k v -- | This is a correct but non-minimal build system: given a target key it -- recursively rebuilds its dependencies, even if they are already up to -- date. There is no memoisation, therefore the a key may be built -- multiple times. busy :: forall k v. Eq k => Build Monad () k v -- | This is a correct but non-minimal build system: it will rebuild keys -- even if they are up to date. However, it performs memoization, -- therefore it never builds a key twice. memo :: Ord k => Build Monad () k v -- | A model of Make: an applicative build system that uses file -- modification times to check if a key is up to date. make :: Ord k => Build Applicative (MakeInfo k) k v -- | A model of Ninja: an applicative build system that uses verifying -- traces to check if a key is up to date. ninja :: (Ord k, Hashable v) => Build Applicative (VT k v) k v -- | A model of CloudBuild: an applicative build system that uses -- constructive traces to check if a key is up to date as well as for -- caching build results. cloudBuild :: (Ord k, Hashable v) => Build Applicative (CT k v) k v -- | A model of Buck: an applicative build system that uses deep -- constructive traces to check if a key is up to date as well as for -- caching build results. buck :: (Ord k, Hashable v) => Build Applicative (DCT k v) k v -- | A model of Excel: a monadic build system that stores the calculation -- chain from the previuos build and approximate dependencies. excel :: Ord k => Build Monad (ExcelInfo k) k v -- | A model of Shake: a monadic build system that uses verifying traces to -- check if a key is up to date. shake :: (Ord k, Hashable v) => Build Monad (VT k v) k v -- | A model of Cloud Shake: a monadic build system that uses constructive -- traces to check if a key is up to date as well as for caching build -- results. cloudShake :: (Ord k, Hashable v) => Build Monad (CT k v) k v -- | A model of Bazel: a monadic build system that uses constructive traces -- to check if a key is up to date as well as for caching build results. -- Note that Bazel currently does not allow users to write monadic build -- rules: only built-in rules have access to dynamic dependencies. bazel :: (Ord k, Hashable v) => Build Monad (CT k v) k v -- | A model of Nix: a monadic build system that uses deep constructive -- traces to check if a key is up to date as well as for caching build -- results. nix :: (Ord k, Hashable v) => Build Monad (DCT k v) k v