-- 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 0.0.1 -- | 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 the value of a key, by finding the necessary -- dependencies using the provided fetch :: k -> f v -- callback. type 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 = forall f. c f => k -> Maybe ((k -> f v) -> f 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 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 using Monad, works beautifully and allows storing the key on -- the disk. selfTrackingM :: (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) -- | Given a build system that can work with single keys, generalise that -- to one that deals with multiple keys at a time. module Build.Multi -- | Given a build rule where you can build some combinations of multiple -- rules, use a partition to enable building lots of multiple rule -- subsets. 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] -- | The "free" structures for dependencies, providing either an -- applicative interface (for Depend) or a monadic interface (for -- Depends). By passing them to a suitable Task you can -- reconstruct all necessary dependencies. module Build.Task.Depend toDepend :: Task Applicative k v -> Depend k v v -- | A list of dependencies, and a function that when applied to those -- dependencies produces the result. data Depend k v r Depend :: [k] -> ([v] -> r) -> Depend k v r toDepends :: Task Monad k v -> Depends k v v -- | A list of dependencies, and a function that when applied to those -- dependencies either the result or more dependencies. data Depends k v r Depends :: [k] -> ([v] -> Depends k v r) -> Depends k v r Done :: r -> Depends k v r instance GHC.Base.Functor (Build.Task.Depend.Depends k v) instance GHC.Base.Functor (Build.Task.Depend.Depend k v) instance GHC.Base.Applicative (Build.Task.Depend.Depends k v) instance GHC.Base.Monad (Build.Task.Depend.Depends k v) instance GHC.Base.Applicative (Build.Task.Depend.Depend k v) -- | 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 on a pure store k -> v, tracking -- the dependencies. track :: Task Monad k v -> (k -> v) -> (v, [k]) -- | Execute a monadic task using an effectful fetch function k -> m -- v, tracking the dependencies. trackM :: forall m k v. Monad m => Task Monad k v -> (k -> m v) -> m (v, [k]) -- | Given a description of tasks, check if a key is input. isInput :: forall k v. Tasks Monad k v -> k -> Bool -- | Run a task with a pure lookup function. compute :: Task Monad k v -> (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 Typed version of dependencies 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) -- | This whole module is just a tiresome workaround for the lack of -- impredicative polymorphism. If GHC adds impredicative polymorphism, we -- can drop it entirely and simplify the rest of the code by removing -- unnecessary task unwrapping. module Build.Task.Wrapped -- | GTask is a generalised Task wrapped in a newtype. It is generalised in -- the sense that it computes a value of type a given a fetch of -- type k -> f v. newtype GTask c k v a GTask :: (forall f. c f => (k -> f v) -> f a) -> GTask c k v a [runGTask] :: GTask c k v a -> forall f. c f => (k -> f v) -> f a type Wrapped c k v = (k -> GTask c k v v) -> GTask c k v v unwrap :: forall c k v. Wrapped c k v -> Task c k v instance GHC.Base.Functor (Build.Task.Wrapped.GTask GHC.Base.Functor k v) instance GHC.Base.Functor (Build.Task.Wrapped.GTask GHC.Base.Applicative k v) instance GHC.Base.Functor (Build.Task.Wrapped.GTask GHC.Base.Alternative k v) instance GHC.Base.Functor (Build.Task.Wrapped.GTask GHC.Base.Monad k v) instance GHC.Base.Functor (Build.Task.Wrapped.GTask GHC.Base.MonadPlus k v) instance GHC.Base.Applicative (Build.Task.Wrapped.GTask GHC.Base.Applicative k v) instance GHC.Base.Applicative (Build.Task.Wrapped.GTask GHC.Base.Alternative k v) instance GHC.Base.Applicative (Build.Task.Wrapped.GTask GHC.Base.Monad k v) instance GHC.Base.Applicative (Build.Task.Wrapped.GTask GHC.Base.MonadPlus k v) instance GHC.Base.Monad (Build.Task.Wrapped.GTask GHC.Base.Monad k v) instance GHC.Base.Monad (Build.Task.Wrapped.GTask GHC.Base.MonadPlus k v) instance GHC.Base.Alternative (Build.Task.Wrapped.GTask GHC.Base.Alternative k v) instance GHC.Base.Alternative (Build.Task.Wrapped.GTask GHC.Base.MonadPlus k v) instance GHC.Base.MonadPlus (Build.Task.Wrapped.GTask GHC.Base.MonadPlus k 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 non-deterministic task with a pure lookup function, listing all -- possible results. computeND :: Task MonadPlus k v -> (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 -- | 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 h r Trace :: k -> [(k, h)] -> r -> Trace k h r [key] :: Trace k h r -> k [depends] :: Trace k h r -> [(k, h)] [result] :: Trace k h 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 :: (Hashable v, Monad m) => k -> v -> [k] -> (k -> m (Hash v)) -> VT k v -> m (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, Hashable v) => k -> 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 :: Monad m => k -> v -> [k] -> (k -> m (Hash v)) -> CT k v -> m (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 -> v -> (k -> m (Hash v)) -> CT k v -> m (Maybe v) -- | Invariant: if a DCT contains a trace for a key k, then it -- must also contain traces for each of its non-input dependencies. Input -- keys cannot appear in a DCT because they are never built. 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. (Hashable 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 deterministic constructive traces, return -- Just newValue if it is possible to reconstruct it from the -- traces. constructDCT :: forall k v m. (Hashable k, Hashable v, Monad m) => k -> (k -> m (Hash v)) -> DCT k v -> m (Maybe 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, Monad m) => Step -> k -> v -> [k] -> ST k v -> m (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 Data.Semigroup.Semigroup (Build.Trace.ST k v) instance GHC.Base.Monoid (Build.Trace.ST k v) 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 Data.Semigroup.Semigroup (Build.Trace.DCT k v) instance GHC.Base.Monoid (Build.Trace.DCT k v) instance Data.Traversable.Traversable Build.Trace.Tree instance GHC.Show.Show a => GHC.Show.Show (Build.Trace.Tree a) instance GHC.Classes.Ord a => GHC.Classes.Ord (Build.Trace.Tree a) instance GHC.Base.Functor Build.Trace.Tree instance Data.Foldable.Foldable Build.Trace.Tree instance GHC.Classes.Eq a => GHC.Classes.Eq (Build.Trace.Tree a) instance (GHC.Show.Show k, GHC.Show.Show v) => GHC.Show.Show (Build.Trace.CT k v) instance Data.Semigroup.Semigroup (Build.Trace.CT k v) instance GHC.Base.Monoid (Build.Trace.CT k v) instance Data.Semigroup.Semigroup (Build.Trace.VT k v) instance GHC.Base.Monoid (Build.Trace.VT k v) instance (GHC.Show.Show r, GHC.Show.Show h, GHC.Show.Show k) => GHC.Show.Show (Build.Trace.Trace k h r) instance Data.Semigroup.Semigroup Build.Trace.Step instance GHC.Base.Monoid Build.Trace.Step instance Build.Store.Hashable a => Build.Store.Hashable (Build.Trace.Tree a) -- | 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 -- | 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 = (Map k Time, Time) -- | This rebuilders uses approximate dependencies to decide whether a key -- needs to be rebuilt. Used by Excel. approximationRebuilder :: Ord k => Rebuilder Monad (ApproximationInfo k) k v data DependencyApproximation k SubsetOf :: [k] -> DependencyApproximation k Unknown :: DependencyApproximation k type ApproximationInfo k = (k -> Bool, k -> DependencyApproximation 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 deterministic constructive traces. dctRebuilder :: (Hashable 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 build and tasks, check that build -- produces a correct result for any initial store and a target key. correct :: (Ord k, Eq v) => Build Monad i k v -> Tasks Monad k v -> Bool -- | 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 -- | Check that a build system is idempotent, i.e. running it once -- or twice in a row leads to the same resulting Store. idempotent :: Eq v => Build Monad i k v -> Tasks Monad k v -> 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 :: Ord k => Rebuilder Applicative i k v -> Build Applicative 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. reordering :: forall i k v. Ord k => Rebuilder Monad i k v -> Build Monad (i, Chain k) 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. restarting :: forall i k v. Eq k => IsDirty i k v -> Rebuilder Monad i k v -> Build Monad i k v -- | This scheduler builds keys recursively: to build a key it first makes -- sure that all its dependencies are up to date and then executes the -- key's task. It stores the set of keys that have already been built as -- part of the state to avoid executing the same task twice. recursive :: forall i k v. Ord k => Rebuilder Monad i k v -> Build Monad 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 => Rebuilder Monad i k v -> Build Monad i 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 :: forall k v. 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 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 Buck: an applicative build system that uses deterministic -- constructive traces to check if a key is up to date as well as for -- caching build results. buck :: (Hashable 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 (Step, ST 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 Nix: a monadic build system that uses deterministic -- constructive traces to check if a key is up to date as well as for -- caching build results. nix :: (Hashable k, Hashable v) => Build Monad (DCT k v) k v