-- File created: 2008-01-27 14:58:50moduleCoadjute.Task(Source,Target,Task(..),showTask,sameTarget,TaskGraph,splitImpossibles,splitCycles)whereimportControl.Exception(assert)importControl.Monad(filterM)importData.Graph.Inductive(Gr,delNodes,lab,scc,mkGraph)importData.List(findIndex)importData.Maybe(fromJust)importData.Set(Set)importqualifiedData.SetasSetimportCoadjute.CoDataimportCoadjute.Util.File(allPathsExist)importCoadjute.Util.List(length1)typeSource=FilePathtypeTarget=FilePath-- |An Task represents the process of building a target.dataTask=Task{-- |The name of the Rule where the Task is from.tName::String,-- |Any command line arguments which the build action may use. Archived-- in the database so that changed args incite a rebuild.---- TODO: support more than just Strings, arbitrary OptDescr would be-- nice.tArgs::SetString,-- |The target(s) which the Task will build. If there are more than one,-- they are all built by the action simultaneously.tTargets::[Target],-- |The dependencies which must be satisfied before the Task can be-- performed.tDeps::[Source],-- |The action, which builds the target on the assumption that the-- dependencies have been satisfied.tAction::IO()}-- |Tasks can't be Show since the tAction can't be output meaningfully. This is-- the next-best thing.showTask::Task->StringshowTaskTask{tTargets=t,tDeps=d,tArgs=a}=concat["Task { tTargets = ",showt,", tDeps = ",showd,", tArgs = ",showa," }"]sameTarget::Task->Task->BoolsameTarget(Task{tTargets=ts})(Task{tTargets=tz})=any(`elem`ts)tz-- Tasks can't be Eq since two Tasks may be the same in every way except for-- the tAction, which isn't Eq.---- So we define and use these instead, which compare only the targets. We can-- do that since we can assume that sameTarget-Tasks have been removed before-- we need to call this on them.---- An assertion is there just in case.(=~=),(!~=)::Task->Task->BoolTask{tName=n,tTargets=t,tDeps=d}=~=Task{tName=n',tTargets=t',tDeps=d'}=assert(t/=t'||(n==n'&&d==d'))(t==t')x!~=y=not(x=~=y)typeTaskGraph=GrTask()-- |Removes dependencies which cannot be satisfied --- that is to say,-- dependencies that do not exist and are not targeted. The second element of-- the tuple returned contains the removed dependencies.splitImpossibles::[Task]->CoData([Task],[Task])splitImpossiblests=dolettargets=concatMaptTargetstsdeps=map(\t->(t,tDepst))tsuntargetedDeps=filter(any(`notElem`targets).snd)depsimpossibleDeps<-io$filterM(fmapnot.allPathsExist.snd)untargetedDepsletimpossibleTasks=mapfstimpossibleDepsreturn(filter(\x->all(!~=x)impossibleTasks)ts,impossibleTasks)-- |Returns a (graph of tasks, cycles) pair. All cycles are removed from the-- graph.splitCycles::[Task]->(TaskGraph,[[Task]])splitCyclestasks=(delNodes(concatcycleNodes)graph,cycles)wherepairs=toPairstasksedges=toEdgestaskspairsgraph=mkGraph(zip[0..]tasks)edgescycleNodes=filter(not.length1).scc$graphcycles=(map.map)(fromJust.labgraph)cycleNodes-- |For each Task, finds all those which target any of its dependencies.toPairs::[Task]->[(Task,[Task])]toPairstasks=map(\t->(t,findDependenciest))taskswherefindDependencies::Task->[Task]findDependenciestask=filter(`targets`(tDepstask))taskstargets::Task->[Source]->Booltargets(Task{tTargets=ts})=any(`elem`ts)-- |fst of each resulting element is the index of the dependency in the pairs-- |snd of each resulting element is the index of the target in the pairstoEdges::[Task]->[(Task,[Task])]->[(Int,Int,())]toEdgestaskspairs=lettoIndext=fromJust$findIndex(=~=t)tasksinconcatMap(\(target,deps)->lettarIdx=toIndextargetinmap(\dep->(toIndexdep,tarIdx,()))deps)pairs