/// <summary> /// Topologically sorts a set of targets. /// </summary> /// <param name="root">The name of the root target. The sort is created in such a way that the sequence of targets up to the root target is the minimum possible such sequence. Must not be <see langword="null" />.</param> /// <param name="targets">A collection of <see cref="Target" /> instances.</param> /// <returns> /// A collection of <see cref="Target" /> instances in sorted order. /// </returns> /// <exception cref="BuildException">There is a cyclic dependecy among the targets, or a named target does not exist.</exception> public TargetCollection TopologicalTargetSort(string root, TargetCollection targets) { TargetCollection executeTargets = new TargetCollection(); Hashtable state = new Hashtable(); Stack visiting = new Stack(); // We first run a DFS based sort using the root as the starting node. // This creates the minimum sequence of Targets to the root node. // We then do a sort on any remaining unVISITED targets. // This is unnecessary for doing our build, but it catches // circular dependencies or missing Targets on the entire // dependency tree, not just on the Targets that depend on the // build Target. TopologicalTargetSort(root, targets, state, visiting, executeTargets); Log(Level.Debug, "Build sequence for target `" + root + "' is " + executeTargets); foreach (Target target in targets) { string st = (string) state[target.Name]; if (st == null) { TopologicalTargetSort(target.Name, targets, state, visiting, executeTargets); } else if (st == Project.Visiting) { throw new Exception("Unexpected node in visiting state: " + target.Name); } } Log(Level.Debug, "Complete build sequence is " + executeTargets); return executeTargets; }
/// <summary> /// <para> /// Performs a single step in a recursive depth-first-search traversal /// of the target dependency tree. /// </para> /// <para> /// The current target is first set to the "visiting" state, and pushed /// onto the "visiting" stack. /// </para> /// <para> /// An exception is then thrown if any child of the current node is in /// the visiting state, as that implies a circular dependency. The /// exception contains details of the cycle, using elements of the /// "visiting" stack. /// </para> /// <para> /// If any child has not already been "visited", this method is called /// recursively on it. /// </para> /// <para> /// The current target is then added to the ordered list of targets. /// Note that this is performed after the children have been visited in /// order to get the correct order. The current target is set to the /// "visited" state. /// </para> /// <para> /// By the time this method returns, the ordered list contains the /// sequence of targets up to and including the current target. /// </para> /// </summary> /// <param name="root">The current target to inspect. Must not be <see langword="null" />.</param> /// <param name="targets">A collection of <see cref="Target" /> instances.</param> /// <param name="state">A mapping from targets to states The states in question are "VISITING" and "VISITED". Must not be <see langword="null" />.</param> /// <param name="visiting">A stack of targets which are currently being visited. Must not be <see langword="null" />.</param> /// <param name="executeTargets">The list to add target names to. This will end up containing the complete list of depenencies in dependency order. Must not be <see langword="null" />.</param> /// <exception cref="BuildException"> /// <para>A non-existent target is specified</para> /// <para>-or-</para> /// <para>A circular dependency is detected.</para> /// </exception> private void TopologicalTargetSort(string root, TargetCollection targets, Hashtable state, Stack visiting, TargetCollection executeTargets) { state[root] = Project.Visiting; visiting.Push(root); Target target = (Target) targets.Find(root); if (target == null) { // check if there's a wildcard target defined target = (Target) targets.Find(WildTarget); if (target != null) { // if a wildcard target exists, then treat the wildcard // target as the requested target target = target.Clone(); target.Name = root; } else { StringBuilder sb = new StringBuilder("Target '"); sb.Append(root); sb.Append("' does not exist in this project."); visiting.Pop(); if (visiting.Count > 0) { string parent = (string) visiting.Peek(); sb.Append(" "); sb.Append("It is used from target '"); sb.Append(parent); sb.Append("'."); } throw new BuildException(sb.ToString()); } } foreach (string dependency in target.Dependencies) { string m = (string) state[dependency]; if (m == null) { // Not been visited TopologicalTargetSort(dependency, targets, state, visiting, executeTargets); } else if (m == Project.Visiting) { // Currently visiting this node, so have a cycle throw CreateCircularException(dependency, visiting); } } string p = (string) visiting.Pop(); if (root != p) { throw new Exception("Unexpected internal error: expected to pop " + root + " but got " + p); } state[root] = Project.Visited; executeTargets.Add(target); }
private void PopulateExecutionGraph(string root, TargetCollection targets, ExecutionGraph graph) { Target target = targets.Find(root); ExecutionNode node = graph.GetNode(root); if (target == null) { target = targets.Find(WildTarget); } bool noDependencies = true; foreach (string dependencyName in target.Dependencies) { PopulateExecutionGraph(dependencyName, targets, graph); ExecutionNode dependencyNode = graph.GetNode(dependencyName); dependencyNode.RegisterDependantNode(node); noDependencies = false; } if (noDependencies) { graph.RegisterLeafNode(node); } }