| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Build::Hopen::G::DAG - hopen build graph | 
| 2 |  |  |  |  |  |  | package Build::Hopen::G::DAG; | 
| 3 | 2 |  |  | 2 |  | 763 | use Build::Hopen::Base; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 14 |  | 
| 4 | 2 |  |  | 2 |  | 696 | use Build::Hopen qw(hlog $QUIET); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 194 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | our $VERSION = '0.000006'; # TRIAL | 
| 7 |  |  |  |  |  |  |  | 
| 8 | 2 |  |  | 2 |  | 12 | use parent 'Build::Hopen::G::Op'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 9 |  |  |  |  |  |  | use Class::Tiny { | 
| 10 | 0 |  |  |  |  | 0 | goals   => sub { [] }, | 
| 11 | 2 |  |  |  |  | 15 | default_goal => undef, | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | # Private attributes with simple defaults | 
| 14 |  |  |  |  |  |  | #_node_by_name => sub { +{} },   # map from node names to nodes in either | 
| 15 |  |  |  |  |  |  | #                                # _init_graph or _graph | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | # Private attributes - initialized by BUILD() | 
| 18 |  |  |  |  |  |  | _graph  => undef,   # L instance | 
| 19 |  |  |  |  |  |  | _final   => undef,  # The graph root - all goals have edges to this | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | #Initialization operations | 
| 22 |  |  |  |  |  |  | _init_graph => undef,   # L for initializations | 
| 23 |  |  |  |  |  |  | _init_first => undef,   # Graph node for initialization - the first | 
| 24 |  |  |  |  |  |  | # init operation to be performed. | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | # TODO? also support fini to run operations after _graph runs? | 
| 27 | 2 |  |  | 2 |  | 121 | }; | 
|  | 2 |  |  |  |  | 4 |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 | 2 |  |  | 2 |  | 2146 | use Build::Hopen::G::Goal; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 62 |  | 
| 30 | 2 |  |  | 2 |  | 352 | use Build::Hopen::G::Link; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 44 |  | 
| 31 | 2 |  |  | 2 |  | 9 | use Build::Hopen::G::Node; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 35 |  | 
| 32 | 2 |  |  | 2 |  | 718 | use Build::Hopen::G::PassthroughOp; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 60 |  | 
| 33 | 2 |  |  | 2 |  | 10 | use Build::Hopen::Util::Data qw(forward_opts); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 67 |  | 
| 34 | 2 |  |  | 2 |  | 9 | use Build::Hopen::Arrrgs; | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 54 |  | 
| 35 | 2 |  |  | 2 |  | 1257 | use Graph; | 
|  | 2 |  |  |  |  | 165191 |  | 
|  | 2 |  |  |  |  | 61 |  | 
| 36 | 2 |  |  | 2 |  | 18 | use Storable (); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 42 |  | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | # Class data {{{1 | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | use constant { | 
| 41 | 2 |  |  |  |  | 2896 | LINKS => 'link_list',    # Graph edge attr: array of BHG::Link instances | 
| 42 | 2 |  |  | 2 |  | 8 | }; | 
|  | 2 |  |  |  |  | 4 |  | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | # A counter used for making unique names | 
| 45 |  |  |  |  |  |  | my $_id_counter = 0;    # threads: make shared | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | # }}}1 | 
| 48 |  |  |  |  |  |  | # Docs {{{1 | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =head1 NAME | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | Build::Hopen::G::DAG - A hopen build graph | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | This class encapsulates the DAG for a particular set of one or more goals. | 
| 57 |  |  |  |  |  |  | It is itself a L so that it can be composed into | 
| 58 |  |  |  |  |  |  | other DAGs. | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | =head1 ATTRIBUTES | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =head2 goals | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | Arrayref of the goals for this DAG. | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | =head2 default_goal | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | The default goal for this DAG. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | =head2 _graph | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | The actual L.  If you find that you have to use it, please open an | 
| 73 |  |  |  |  |  |  | issue so we can see about providing a documented API for your use case! | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | =head2 _final | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | The node to which all goals are connected. | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | =head2 _init_graph | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | A separate L of operations that will run before all the operations | 
| 82 |  |  |  |  |  |  | in L.  This is because I don't want to add an edge to every | 
| 83 |  |  |  |  |  |  | single node just to force the topological sort to work out. | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =head2 _init_first | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | The first node to be run in _init_graph. | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | =cut | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | # }}}1 | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | =head2 run | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | Traverses the graph.  The DAG is similar to a subroutine in this respect. | 
| 98 |  |  |  |  |  |  | The outputs from all the goals | 
| 99 |  |  |  |  |  |  | of the DAG are aggregated and provided as the outputs of the DAG. | 
| 100 |  |  |  |  |  |  | The output is a hash keyed by the name of each goal, with each goal's outputs | 
| 101 |  |  |  |  |  |  | as the values under that name.  Usage: | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | my $hrOutputs = $dag->run(-scope=>$scope[, other options]) | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | C<$scope> is required, and must be a L or subclass. | 
| 106 |  |  |  |  |  |  | Other options are as L. | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | =cut | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | #    my $merger = Hash::Merge->new('RETAINMENT_PRECEDENT');  # TODO | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | sub run { | 
| 113 | 1 |  |  | 1 | 1 | 837 | my ($self, %args) = parameters('self', [qw(scope; phase generator)], @_); | 
| 114 | 1 |  |  |  |  | 11 | my $outer_scope = $args{scope};     # From the caller | 
| 115 | 1 |  |  |  |  | 3 | my $retval = {}; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | # The scope attached to the DAG takes precedence over the provided Scope. | 
| 118 |  |  |  |  |  |  | # This is realized by making $outer_scope the outer of our scope for | 
| 119 |  |  |  |  |  |  | # the duration of this call. | 
| 120 | 1 |  |  |  |  | 30 | my $dag_scope_saver = $self->scope->outerize($outer_scope); | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | # --- Get the initialization ops --- | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 1 |  |  |  |  | 2 | my @init_order = eval { $self->_init_graph->toposort }; | 
|  | 1 |  |  |  |  | 29 |  | 
| 125 | 1 | 50 |  |  |  | 1450 | die "Initializations contain a cycle!" if $@; | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | # --- Get the runtime ops --- | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 1 |  |  |  |  | 2 | my @order = eval { $self->_graph->toposort }; | 
|  | 1 |  |  |  |  | 24 |  | 
| 130 |  |  |  |  |  |  | # TODO someday support multi-core-friendly topo-sort, so nodes can run | 
| 131 |  |  |  |  |  |  | # in parallel until they block each other. | 
| 132 | 1 | 50 |  |  |  | 2883 | die "Graph contains a cycle!" if $@; | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | # Remove _final from the order for now - I don't yet know what it means | 
| 135 |  |  |  |  |  |  | # to traverse _final. | 
| 136 | 1 | 50 |  |  |  | 26 | die "Last item in order isn't _final!" | 
| 137 |  |  |  |  |  |  | unless $order[$#order] == $self->_final; | 
| 138 | 1 |  |  |  |  | 9 | pop @order; | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | # --- Traverse --- | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | # Note: while hacking, please make sure Goal nodes can appear | 
| 143 |  |  |  |  |  |  | # anywhere in the graph. | 
| 144 |  |  |  |  |  |  |  | 
| 145 | 1 |  |  | 1 |  | 21 | hlog { 'Traversing DAG ' . $self->name }; | 
|  | 1 |  |  |  |  | 10 |  | 
| 146 | 1 |  |  |  |  | 28 | my $graph = $self->_init_graph; | 
| 147 | 1 |  |  |  |  | 9 | foreach my $node (@init_order, undef, @order) { | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 4 | 100 |  |  |  | 33 | if(!defined($node)) {   # undef is the marker between init and run | 
| 150 | 1 |  |  |  |  | 22 | $graph = $self->_graph; | 
| 151 | 1 |  |  |  |  | 7 | next; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | # Inputs to this node.  TODO should the provided inputs be given | 
| 155 |  |  |  |  |  |  | # to each node?  Any node with no predecessors?  Currently each | 
| 156 |  |  |  |  |  |  | # node has the option. | 
| 157 | 3 |  |  |  |  | 11 | my $node_scope = Build::Hopen::Scope::Hash->new; | 
| 158 |  |  |  |  |  |  | # TODO make this a BH::Scope::Inputs once it's implemented | 
| 159 | 3 |  |  |  |  | 99 | $node_scope->outer($self->scope); | 
| 160 |  |  |  |  |  |  | # Data specifically being provided to the current node, e.g., | 
| 161 |  |  |  |  |  |  | # on input edges, beats the scope of the DAG as a whole. | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | # Iterate over each node's edges and process any Links | 
| 164 | 3 |  |  |  |  | 67 | foreach my $pred ($graph->predecessors($node)) { | 
| 165 | 1 |  |  | 1 |  | 178 | hlog { ('From', $pred->name, 'to', $node->name) }; | 
|  | 1 |  |  |  |  | 4 |  | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | # Goals do not feed outputs to other Goals.  This is so you can | 
| 168 |  |  |  |  |  |  | # add edges between Goals to set their order while keeping the | 
| 169 |  |  |  |  |  |  | # data for each Goal separate. | 
| 170 |  |  |  |  |  |  | # TODO add tests for this | 
| 171 | 1 | 50 |  |  |  | 7 | next if eval { $pred->DOES('Build::Hopen::G::Goal') }; | 
|  | 1 |  |  |  |  | 11 |  | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 1 |  |  |  |  | 7 | my $links = $graph->get_edge_attribute($pred, $node, LINKS); | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 1 | 50 |  |  |  | 752 | unless($links) {    # Simple case: predecessor's outputs become our inputs | 
| 176 | 0 |  |  |  |  | 0 | push @{$node_scope->inputs}, $pred->outputs; | 
|  | 0 |  |  |  |  | 0 |  | 
| 177 | 0 |  |  |  |  | 0 | next; | 
| 178 |  |  |  |  |  |  | } | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | # More complex case: Process all the links | 
| 181 | 1 |  |  |  |  | 4 | my $hrPredOutputs = $pred->outputs; # In one test, outputs was undef if not on its own line. | 
| 182 | 1 |  |  |  |  | 18 | my $link_scope = Build::Hopen::Scope::Hash->new->add(%{$hrPredOutputs}); | 
|  | 1 |  |  |  |  | 23 |  | 
| 183 |  |  |  |  |  |  | # All links get the same outer scope --- they are parallel, | 
| 184 |  |  |  |  |  |  | # not in series. | 
| 185 | 1 |  |  |  |  | 15 | $link_scope->outer($self->scope); | 
| 186 |  |  |  |  |  |  | # The links run at the same scope level as the node. | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 1 |  |  |  |  | 19 | foreach my $link (@$links) { | 
| 189 | 1 |  |  | 1 |  | 9 | hlog { ('From', $pred->name, 'via', $link->name, 'to', $node->name) }; | 
|  | 1 |  |  |  |  | 4 |  | 
| 190 | 1 |  |  |  |  | 12 | my $link_outputs = $link->run( | 
| 191 |  |  |  |  |  |  | -scope=>$link_scope, | 
| 192 |  |  |  |  |  |  | forward_opts(\%args, {'-'=>1}, 'phase') | 
| 193 |  |  |  |  |  |  | # Generator not passed to links. | 
| 194 |  |  |  |  |  |  | ); | 
| 195 | 1 |  |  |  |  | 12 | $node_scope->add($_, $link_outputs->{$_}) foreach keys %{$link_outputs}; | 
|  | 1 |  |  |  |  | 7 |  | 
| 196 |  |  |  |  |  |  | #say 'Link ', $link->name, ' outputs: ', Dumper($link_outputs);   # DEBUG | 
| 197 |  |  |  |  |  |  | } #foreach incoming link | 
| 198 |  |  |  |  |  |  | } #foreach predecessor node | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 3 |  |  | 0 |  | 372 | hlog { 'Node', $node->name, 'input', Dumper($node_scope->as_hashref) } 3; | 
|  | 0 |  |  |  |  | 0 |  | 
| 201 | 3 |  |  |  |  | 21 | my $step_output = $node->run(-scope=>$node_scope, | 
| 202 |  |  |  |  |  |  | forward_opts(\%args, {'-'=>1}, 'phase', 'generator') | 
| 203 |  |  |  |  |  |  | ); | 
| 204 | 3 |  |  |  |  | 49 | $node->outputs($step_output); | 
| 205 | 3 |  |  | 0 |  | 18 | hlog { 'Node', $node->name, 'output', Dumper($step_output) } 3; | 
|  | 0 |  |  |  |  | 0 |  | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | # Give the Generator a chance, and stash the results if necessary. | 
| 208 | 3 | 100 |  |  |  | 8 | if(eval { $node->DOES('Build::Hopen::G::Goal') }) { | 
|  | 3 |  |  |  |  | 45 |  | 
| 209 | 1 | 50 |  |  |  | 4 | $args{generator}->visit_goal($node) if $args{generator}; | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | # Save the result if there is one.  Don't save {}. | 
| 212 |  |  |  |  |  |  | # use $node->outputs, not $step_output, since the generator may | 
| 213 |  |  |  |  |  |  | # alter $node->outputs. | 
| 214 | 1 | 50 |  |  |  | 4 | $retval->{$node->name} = $node->outputs if keys %{$node->outputs}; | 
|  | 1 |  |  |  |  | 4 |  | 
| 215 |  |  |  |  |  |  | } else { | 
| 216 | 2 | 50 |  |  |  | 13 | $args{generator}->visit_node($node) if $args{generator}; | 
| 217 |  |  |  |  |  |  | } | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | } #foreach node | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 1 |  |  |  |  | 15 | return $retval; | 
| 222 |  |  |  |  |  |  | } #run() | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | =head1 ADDING DATA | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | =head2 goal | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | Creates a goal of the DAG.  Goals are names for sequences of operations, | 
| 229 |  |  |  |  |  |  | akin to top-level Makefile targets.  Usage: | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | my $goalOp = $dag->goal('name') | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | Returns a passthrough operation representing the goal.  Any inputs passed into | 
| 234 |  |  |  |  |  |  | that operation are provided as outputs of the DAG under the corresponding name. | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | TODO integrate | 
| 237 |  |  |  |  |  |  | A C file with no C calls will result in nothing | 
| 238 |  |  |  |  |  |  | happening when C is run. | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | The first call to C also sets L. | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | =cut | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | sub goal { | 
| 245 | 3 | 50 |  | 3 | 1 | 1015 | my $self = shift or croak 'Need an instance'; | 
| 246 | 3 | 50 |  |  |  | 11 | my $name = shift or croak 'Need a goal name'; | 
| 247 | 3 |  |  |  |  | 22 | my $goal = Build::Hopen::G::Goal->new(name => $name); | 
| 248 | 3 |  |  |  |  | 88 | $self->_graph->add_vertex($goal); | 
| 249 |  |  |  |  |  |  | #$self->_node_by_name->{$name} = $goal; | 
| 250 | 3 |  |  |  |  | 410 | $self->_graph->add_edge($goal, $self->_final); | 
| 251 | 3 | 100 |  |  |  | 1203 | $self->default_goal($goal) unless $self->default_goal; | 
| 252 | 3 |  |  |  |  | 52 | return $goal; | 
| 253 |  |  |  |  |  |  | } #goal() | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | =head2 connect | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | - C, , , )>: | 
| 258 |  |  |  |  |  |  | connects output C<< out-edge >> of operation C<< op1 >> as input C<< in-edge >> of | 
| 259 |  |  |  |  |  |  | operation C<< op2 >>.  No processing is done between output and input. | 
| 260 |  |  |  |  |  |  | - C<< out-edge >> and C<< in-edge >> can be anything usable as a table index, | 
| 261 |  |  |  |  |  |  | provided that table index appears in the corresponding operation's | 
| 262 |  |  |  |  |  |  | descriptor. | 
| 263 |  |  |  |  |  |  | - C, )>: creates a dependency edge from C<< op1 >> to | 
| 264 |  |  |  |  |  |  | C<< op2 >>, indicating that C<< op1 >> must be run before C<< op2 >>. | 
| 265 |  |  |  |  |  |  | Does not transfer any data from C<< op1 >> to C<< op2 >>. | 
| 266 |  |  |  |  |  |  | - C, , )>: Connects C<< op1 >> to | 
| 267 |  |  |  |  |  |  | C<< op2 >> via L C<< Link >>. | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | Returns the name of the edge?  The edge instance itself?  Maybe a | 
| 270 |  |  |  |  |  |  | fluent interface to the DAG for chaining C calls? | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | =cut | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | sub connect { | 
| 275 | 1 | 50 |  | 1 | 1 | 426 | my $self = shift or croak 'Need an instance'; | 
| 276 | 1 |  |  |  |  | 3 | my ($op1, $out_edge, $in_edge, $op2) = @_; | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 1 |  |  |  |  | 2 | my $link; | 
| 279 | 1 | 50 |  |  |  | 5 | if(!defined($in_edge)) {    # dependency edge | 
|  |  | 50 |  |  |  |  |  | 
| 280 | 0 |  |  |  |  | 0 | $op2 = $out_edge; | 
| 281 | 0 |  |  |  |  | 0 | $out_edge = false;      # No outputs | 
| 282 | 0 |  |  |  |  | 0 | $in_edge = false;       # No inputs | 
| 283 |  |  |  |  |  |  | } elsif(!defined($op2)) { | 
| 284 | 1 |  |  |  |  | 2 | $op2 = $in_edge; | 
| 285 | 1 |  |  |  |  | 2 | $link = $out_edge; | 
| 286 | 1 |  |  |  |  | 2 | $out_edge = false;      # No outputs TODO | 
| 287 | 1 |  |  |  |  | 2 | $in_edge = false;       # No inputs TODO | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | # Create the link | 
| 291 | 1 | 50 |  |  |  | 52 | unless($link) { | 
| 292 | 0 |  |  |  |  | 0 | $link = Build::Hopen::G::Link->new( | 
| 293 |  |  |  |  |  |  | name => 'link_' . $op1->name . '_' . $op2->name, | 
| 294 |  |  |  |  |  |  | in => [$out_edge],      # Output of op1 | 
| 295 |  |  |  |  |  |  | out => [$in_edge],      # Input to op2 | 
| 296 |  |  |  |  |  |  | ); | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  |  | 
| 299 | 1 |  |  | 1 |  | 7 | hlog { 'DAG::connect(): Edge from', $op1->name, 'via', $link->name, | 
| 300 | 1 |  |  |  |  | 10 | 'to', $op2->name }; | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | # Add it to the graph (idempotent) | 
| 303 | 1 |  |  |  |  | 28 | $self->_graph->add_edge($op1, $op2); | 
| 304 |  |  |  |  |  |  | #$self->_node_by_name->{$_->name} = $_ foreach ($op1, $op2); | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # Save the BHG::Link as an edge attribute (not idempotent!) | 
| 307 | 1 |  | 50 |  |  | 519 | my $attrs = $self->_graph->get_edge_attribute($op1, $op2, LINKS) || []; | 
| 308 | 1 |  |  |  |  | 1639 | push @$attrs, $link; | 
| 309 | 1 |  |  |  |  | 22 | $self->_graph->set_edge_attribute($op1, $op2, LINKS, $attrs); | 
| 310 |  |  |  |  |  |  |  | 
| 311 | 1 |  |  |  |  | 685 | return $link; | 
| 312 |  |  |  |  |  |  | } #connect() | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | =head2 add | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | Add a regular node to the graph.  An attempt to add the same node twice will be | 
| 317 |  |  |  |  |  |  | ignored.  Usage: | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | my $node = Build::Hopen::G::Op->new(name=>"whatever"); | 
| 320 |  |  |  |  |  |  | $dag->add($node); | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | Returns the node, for the sake of chaining. | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | =cut | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | sub add { | 
| 327 | 0 | 0 |  | 0 | 1 | 0 | my $self = shift or croak 'Need an instance'; | 
| 328 | 0 | 0 |  |  |  | 0 | my $node = shift or croak 'Need a node'; | 
| 329 | 0 | 0 |  |  |  | 0 | return if $self->_graph->has_vertex($node); | 
| 330 | 0 |  |  | 0 |  | 0 | hlog { __PACKAGE__, 'adding', Dumper($node) } 2; | 
|  | 0 |  |  |  |  | 0 |  | 
| 331 |  |  |  |  |  |  |  | 
| 332 | 0 |  |  |  |  | 0 | $self->_graph->add_vertex($node); | 
| 333 |  |  |  |  |  |  | #$self->_node_by_name->{$node->name} = $node if $node->name; | 
| 334 |  |  |  |  |  |  |  | 
| 335 | 0 |  |  |  |  | 0 | return $node; | 
| 336 |  |  |  |  |  |  | } #add() | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | =head2 init | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | Add an initialization operation to the graph.  Initialization operations run | 
| 341 |  |  |  |  |  |  | before all other operations.  An attempt to add the same initialization | 
| 342 |  |  |  |  |  |  | operation twice will be ignored.  Usage: | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | my $op = Build::Hopen::G::Op->new(name=>"whatever"); | 
| 345 |  |  |  |  |  |  | $dag->init($op[, $first]); | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | If C<$first> is truthy, the op will be run before anything already in the | 
| 348 |  |  |  |  |  |  | graph.  However, later calls to C with C<$first> set will push | 
| 349 |  |  |  |  |  |  | operations even before C<$op>. | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | Returns the node, for the sake of chaining. | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =cut | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | sub init { | 
| 356 | 0 | 0 |  | 0 | 1 | 0 | my $self = shift or croak 'Need an instance'; | 
| 357 | 0 | 0 |  |  |  | 0 | my $op = shift or croak 'Need an op'; | 
| 358 | 0 |  |  |  |  | 0 | my $first = shift; | 
| 359 | 0 | 0 |  |  |  | 0 | return if $self->_init_graph->has_vertex($op); | 
| 360 |  |  |  |  |  |  |  | 
| 361 | 0 |  |  |  |  | 0 | $self->_init_graph->add_vertex($op); | 
| 362 |  |  |  |  |  |  | #$self->_node_by_name->{$op->name} = $op; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 0 | 0 |  |  |  | 0 | if($first) {    # $op becomes the new _init_first node | 
| 365 | 0 |  |  |  |  | 0 | $self->_init_graph->add_edge($op, $self->_init_first); | 
| 366 | 0 |  |  |  |  | 0 | $self->_init_first($op); | 
| 367 |  |  |  |  |  |  | } else {    # Not first, so can happen anytime.  Add it after the | 
| 368 |  |  |  |  |  |  | # current first node. | 
| 369 | 0 |  |  |  |  | 0 | $self->_init_graph->add_edge($self->_init_first, $op); | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 0 |  |  |  |  | 0 | return $op; | 
| 373 |  |  |  |  |  |  | } #init() | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | =head1 ACCESSORS | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | =head2 empty | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | Returns truthy if the only nodes in the graph are internal nodes. | 
| 380 |  |  |  |  |  |  | Intended for use by hopen files. | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =cut | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | sub empty { | 
| 385 | 0 | 0 |  | 0 | 1 | 0 | my $self = shift or croak 'Need an instance'; | 
| 386 | 0 |  |  |  |  | 0 | return ($self->_graph->vertices == 1); | 
| 387 |  |  |  |  |  |  | # _final is the node in an empty() graph. | 
| 388 |  |  |  |  |  |  | # We don't check the _init_graph since empty() is intended | 
| 389 |  |  |  |  |  |  | # for use by hopen files, not toolsets. | 
| 390 |  |  |  |  |  |  | } #empty() | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | =head1 OTHER | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | =head2 BUILD | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | Initialize the instance. | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | =cut | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | sub BUILD { | 
| 401 |  |  |  |  |  |  | #use Data::Dumper; | 
| 402 |  |  |  |  |  |  | #say Dumper(\@_); | 
| 403 | 2 | 50 |  | 2 | 1 | 483 | my $self = shift or croak 'Need an instance'; | 
| 404 | 2 |  |  |  |  | 4 | my $hrArgs = shift; | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | # DAGs always have names | 
| 407 | 2 | 50 |  |  |  | 12 | $self->name('__R_DAG_' . $_id_counter++) unless $self->has_custom_name; | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | # Graph of normal operations | 
| 410 | 2 |  |  |  |  | 12 | my $graph = Graph->new( directed => true, | 
| 411 |  |  |  |  |  |  | refvertexed => true); | 
| 412 | 2 |  |  |  |  | 495 | my $final = Build::Hopen::G::Node->new( | 
| 413 |  |  |  |  |  |  | name => '__R_DAG_ROOT' . $_id_counter++); | 
| 414 | 2 |  |  |  |  | 206 | $graph->add_vertex($final); | 
| 415 | 2 |  |  |  |  | 389 | $self->_graph($graph); | 
| 416 | 2 |  |  |  |  | 47 | $self->_final($final); | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | # Graph of initialization operations | 
| 419 | 2 |  |  |  |  | 14 | my $init_graph = Graph->new( directed => true, | 
| 420 |  |  |  |  |  |  | refvertexed => true); | 
| 421 | 2 |  |  |  |  | 316 | my $init = Build::Hopen::G::PassthroughOp->new( | 
| 422 |  |  |  |  |  |  | name => '__R_DAG_INIT' . $_id_counter++); | 
| 423 | 2 |  |  |  |  | 28 | $init_graph->add_vertex($init); | 
| 424 |  |  |  |  |  |  |  | 
| 425 | 2 |  |  |  |  | 271 | $self->_init_graph($init_graph); | 
| 426 | 2 |  |  |  |  | 37 | $self->_init_first($init); | 
| 427 |  |  |  |  |  |  | } #BUILD() | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | 1; | 
| 430 |  |  |  |  |  |  | # Rest of the docs {{{1 | 
| 431 |  |  |  |  |  |  | __END__ |