File Coverage

blib/lib/Stepford/Graph.pm
Criterion Covered Total %
statement 128 134 95.5
branch 15 20 75.0
condition 3 3 100.0
subroutine 22 23 95.6
pod 0 8 0.0
total 168 188 89.3


line stmt bran cond sub pod time code
1             package Stepford::Graph;
2              
3 39     39   316 use strict;
  39         123  
  39         1505  
4 39     39   224 use warnings;
  39         90  
  39         1977  
5 39     39   246 use namespace::autoclean;
  39         103  
  39         421  
6              
7             our $VERSION = '0.006001';
8              
9 39     39   3867 use List::AllUtils qw( all none );
  39         84  
  39         2952  
10 39     39   278 use Stepford::Error;
  39         123  
  39         1347  
11 39         399 use Stepford::Types qw(
12             ArrayRef
13             Bool
14             HashRef
15             Logger
16             Maybe
17             Num
18             Step
19 39     39   241 );
  39         119  
20 39     39   321701 use Try::Tiny qw( catch try );
  39         148  
  39         2568  
21              
22 39     39   266 use Moose;
  39         108  
  39         350  
23 39     39   272519 use MooseX::StrictConstructor;
  39         104  
  39         337  
24              
25             has config => (
26             is => 'ro',
27             isa => HashRef,
28             required => 1,
29             );
30              
31             has logger => (
32             is => 'ro',
33             isa => Logger,
34             required => 1,
35             );
36              
37             has step_class => (
38             is => 'ro',
39             isa => Step,
40             required => 1,
41             );
42              
43             has _step_object => (
44             is => 'ro',
45             isa => Step,
46             lazy => 1,
47             builder => '_build_step_object',
48             );
49              
50             has last_run_time => (
51             is => 'ro',
52             isa => Maybe [Num],
53             writer => 'set_last_run_time',
54             clearer => '_clear_last_run_time',
55             lazy => 1,
56             default => sub {
57             ## no critic (RequireInitializationForLocalVars)
58             local $_;
59             shift->_step_object->last_run_time;
60             },
61             );
62              
63             has step_productions_as_hashref => (
64             is => 'ro',
65             isa => HashRef,
66             writer => 'set_step_productions_as_hashref',
67             clearer => '_clear_step_productions_as_hashref',
68             lazy => 1,
69             default => sub {
70             ## no critic (RequireInitializationForLocalVars)
71             local $_;
72             shift->_step_object->productions_as_hashref;
73             },
74             );
75              
76             has _children_graphs => (
77             traits => ['Array'],
78             init_arg => 'children_graphs',
79             is => 'ro',
80             isa => ArrayRef ['Stepford::Graph'],
81             required => 1,
82             );
83              
84             has is_being_processed => (
85             is => 'ro',
86             isa => Bool,
87             default => 0,
88             writer => 'set_is_being_processed',
89             );
90              
91             has has_been_processed => (
92             is => 'ro',
93             isa => Bool,
94             default => 0,
95             writer => 'set_has_been_processed',
96             );
97              
98             sub _build_step_object {
99 619     619   1664 my $self = shift;
100 619         2143 my $args = $self->_constructor_args_for_class;
101              
102 619         15944 $self->logger->debug( $self->step_class . '->new' );
103 619         38629 return $self->step_class->new($args);
104             }
105              
106             sub _constructor_args_for_class {
107 619     619   1783 my $self = shift;
108              
109 619         16701 my $class = $self->step_class;
110 619         15939 my $config = $self->config;
111              
112 619         1256 my %args;
113 619         7628 for my $init_arg (
114 2154         5891 grep { defined }
115 2154         69873 map { $_->init_arg } $class->meta->get_all_attributes
116             ) {
117              
118             $args{$init_arg} = $config->{$init_arg}
119 2154 100       12895 if exists $config->{$init_arg};
120             }
121              
122 619         2675 my %productions = $self->_children_productions;
123              
124 619         5144 for my $dep ( map { $_->name } $class->dependencies ) {
  469         69295  
125 469 50       1327 next if exists $args{$dep};
126              
127             # XXX - I'm not sure this error is reachable. We already check that a
128             # class's declared dependencies can be satisfied while building the
129             # graph. That said, it doesn't hurt to leave this check in here, and it
130             # might help illuminate bugs in the Runner itself.
131             Stepford::Error->throw(
132             "Cannot construct a $class object. We are missing a required production: $dep"
133 469 50       1134 ) unless exists $productions{$dep};
134              
135 469         1203 $args{$dep} = $productions{$dep};
136             }
137              
138 619         92677 $args{logger} = $self->logger;
139              
140 619         2644 return \%args;
141             }
142              
143             # Note: this is intentionally depth-first traversal as this is required for
144             # the correct sort order when running steps.
145             sub traverse {
146 1257     1257 0 2330 my $self = shift;
147 1257         2079 my $cb = shift;
148              
149 1257         1737 for my $graph ( @{ $self->_children_graphs } ) {
  1257         35316  
150 997         4684 $graph->traverse($cb);
151             }
152 1215         4999 $cb->($self);
153 1188         10772 return;
154             }
155              
156             # This checks if the step/graph is in a state where we can run it, e.g.,
157             # it isn't being processed currently, the children have been processed, it
158             # hasn't already been processed. It does not do any checks on the internal
159             # state of the step (e.g., last run times). Rather, it is intended for
160             # completely internal flow control.
161             #
162             # This is called repeatedly in a multi-process build to figure out whether we
163             # are ready to consider running the step.
164             sub can_run_step {
165 1215     1215 0 3092 my $self = shift;
166              
167             # These checks are not logged as they are part of Stepford's internal
168             # flow control and might be run many times for a single step.
169             return
170 1215   100     36776 !$self->is_being_processed
171             && $self->children_have_been_processed
172             && !$self->has_been_processed;
173             }
174              
175             # This checks whether we should run the step. It is meant to be run after we
176             # determine that the step is in a state where it _can_ run. This primarily
177             # looks at the internal state of the step, e.g., last run times.
178             #
179             # This is generally only called once, immediately before we run the step or
180             # decide never to run it.
181             #
182             # can and should are separated as they serve two different purposes in the
183             # Runner's flow control.
184             sub step_is_up_to_date {
185 619     619 0 1410 my $self = shift;
186 619         1153 my $force_step_execution = shift;
187              
188 619         15977 my $step_class = $self->step_class;
189              
190 619 100       2332 if ($force_step_execution) {
191 4         101 $self->logger->info("Force execution is enabled for $step_class.");
192 4         121 return 0;
193             }
194              
195 615 100       17334 unless ( defined $self->last_run_time ) {
196 341         9270 $self->logger->debug("No last run time for $step_class.");
197 341         12639 return 0;
198             }
199              
200 274 100       460 unless ( @{ $self->_children_graphs } ) {
  274         7819  
201 135         3374 $self->logger->debug("No previous steps for $step_class.");
202 135         4433 return 1;
203             }
204              
205 139 50       255 if ( my @missing
206 205         5030 = grep { !defined $_->last_run_time } @{ $self->_children_graphs } ) {
  139         3517  
207             $self->logger->debug(
208             "A previous step for $step_class does not have a last run time: "
209 0         0 . join ', ', map { $_->step_class } @missing );
  0         0  
210 0         0 return 0;
211             }
212              
213 139         3671 my $step_last_run_time = $self->last_run_time;
214 205         4992 my @newer_children = grep { $_->last_run_time > $step_last_run_time }
215 139         198 @{ $self->_children_graphs };
  139         3463  
216 139 100       495 unless (@newer_children) {
217 102         2471 $self->logger->info("$step_class is up to date.");
218 102         3390 return 1;
219             }
220              
221             $self->logger->info(
222             "Last run time for $step_class is "
223             . $self->last_run_time
224             . '. The following children have newer last run times: '
225             . join ', ',
226 37         1013 map { $_->step_class . ' (' . $_->last_run_time . ')' }
  37         1236  
227             @newer_children,
228             );
229              
230 37         1240 return 0;
231             }
232              
233             sub run_step {
234 212     212 0 714 my $self = shift;
235              
236 212 50       1271 die 'Tried running '
237             . $self->step_class
238             . ' when not all children have been processed.'
239             unless $self->children_have_been_processed;
240              
241 212 50       7478 die 'Tried running '
242             . $self->step_class
243             . ' when it is currently being run'
244             if $self->is_being_processed;
245              
246 212         9523 $self->set_is_being_processed(1);
247              
248 212         6204 $self->logger->info( 'Running ' . $self->step_class );
249              
250             {
251             # We use many list functions that will modify the underlying array
252             # they are called on if $_ is modified. We localize it for safety.
253             ## no critic (RequireInitializationForLocalVars)
254 212         6537 local $_;
  212         443  
255 212         6406 $self->_step_object->run;
256             }
257 210         120921 $self->set_is_being_processed(0);
258 210         7859 $self->set_has_been_processed(1);
259 210         7707 $self->_clear_last_run_time;
260 210         8266 $self->_clear_step_productions_as_hashref;
261              
262 210         803 return;
263             }
264              
265             sub productions {
266 0     0 0 0 my $self = shift;
267              
268             return (
269             $self->_children_productions,
270 0         0 %{ $self->step_productions_as_hashref },
  0         0  
271             );
272             }
273              
274             sub _children_productions {
275 619     619   1664 my $self = shift;
276              
277             return
278 476         1057 map { %{ $_->step_productions_as_hashref } }
  476         15699  
279 619         1217 @{ $self->_children_graphs };
  619         19440  
280             }
281              
282             sub children_have_been_processed {
283 1400     1400 0 2753 my $self = shift;
284              
285 1400     1106   12103 all { $_->has_been_processed } @{ $self->_children_graphs };
  1106         34588  
  1400         42817  
286             }
287              
288             sub is_serializable {
289 201     201 0 647 my $self = shift;
290              
291             # A step can be serialized as long as it and all of its children do not
292             # implement Stepford::Role::Step::Unserializable
293 352     352   20380 none { $_->step_class->does('Stepford::Role::Step::Unserializable') }
294 201         2283 ( $self, @{ $self->_children_graphs } );
  201         6626  
295             }
296              
297             sub as_string {
298 152     152 0 464 my $self = shift;
299              
300             return join(
301             "\n",
302             (
303 152         672 map { sprintf( '[ %s ] --> [ %s ]', @$_ ) }
  596         3959  
304             _parent_child_pairs($self)
305             ),
306             q{},
307             );
308             }
309              
310             sub _parent_child_pairs {
311 152     152   506 my $parent = shift;
312              
313 152         341 my @pairs;
314 152         408 my @parents = ($parent);
315 152         286 my %seen;
316 152         494 while (@parents) {
317 738         1237 my $current_parent = shift @parents;
318 738         1028 my @children = @{ $current_parent->_children_graphs };
  738         19721  
319              
320 738         1618 my @children_step_classes = map { $_->step_class } @children;
  596         14953  
321 738         17567 my $current_parent_step_class = $current_parent->step_class;
322              
323             push @pairs,
324 738         1397 map { [ $current_parent_step_class, $_ ] } @children_step_classes;
  596         1747  
325              
326             push @parents,
327 738         1188 grep { !exists $seen{ $_->step_class } } @children;
  596         14824  
328              
329 738         2866 @seen{@children_step_classes} = ();
330             }
331              
332 152         719 return @pairs;
333             }
334              
335             __PACKAGE__->meta->make_immutable;
336              
337             1;
338              
339             # ABSTRACT: Contains the step dependency graph
340              
341             __END__
342              
343             =pod
344              
345             =encoding UTF-8
346              
347             =head1 NAME
348              
349             Stepford::Graph - Contains the step dependency graph
350              
351             =head1 VERSION
352              
353             version 0.006001
354              
355             =head1 DESCRIPTION
356              
357             This is an internal class and has no user-facing parts. Please do not use it.
358              
359             A C<Stepford::Graph> is a directed graph of the step dependency resolution.
360             Each step node has connections to all of the nodes for the steps that create
361             its dependencies. It is a graph rather than a tree as there may be multiple
362             paths between a step node and steps that create dependencies of dependency
363             steps. It is directed as each node only knows about the nodes for its own
364             dependencies, not the nodes that it provides dependencies for.
365              
366             =for Pod::Coverage .*
367              
368             =head1 SUPPORT
369              
370             Bugs may be submitted through L<https://github.com/maxmind/Stepford/issues>.
371              
372             =head1 AUTHOR
373              
374             Dave Rolsky <drolsky@maxmind.com>
375              
376             =head1 COPYRIGHT AND LICENSE
377              
378             This software is copyright (c) 2014 - 2023 by MaxMind, Inc.
379              
380             This is free software; you can redistribute it and/or modify it under
381             the same terms as the Perl 5 programming language system itself.
382              
383             =cut