From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

$ExtUtils::Builder::Plan::VERSION = '0.016';
use strict;
use Carp ();
use Scalar::Util ();
sub new {
my ($class, %args) = @_;
return bless {
nodes => $args{nodes} // {}
}, $class;
}
sub node {
my ($self, $name) = @_;
return $self->{nodes}{$name};
}
sub nodes {
my $self = shift;
return @{$self->{nodes}}{ $self->node_names };
}
sub node_names {
my $self = shift;
return sort keys %{ $self->{nodes} };
}
sub _node_sorter {
my ($self, $name, $callback, $seen, $loop) = @_;
Carp::croak("$name has a circular dependency, aborting!\n") if exists $loop->{$name};
return if $seen->{$name}++;
if (my $node = $self->{nodes}{$name}) {
local $loop->{$name} = 1;
$self->_node_sorter($_, $callback, $seen, $loop) for $node->dependencies;
$callback->($name, $node);
} elsif (not -e $name) {
Carp::confess("Node $name doesn't exist")
}
return;
}
sub run {
my ($self, $targets, %options) = @_;
my @targets = ref($targets) ? @{$targets} : $targets;
my (%seen, %loop);
my $run_node = sub {
my ($name, $node) = @_;
return if $self->_up_to_date($node);
$node->execute(%options);
};
$self->_node_sorter($_, $run_node, \%seen, \%loop) for @targets;
return;
}
sub _up_to_date {
my ($self, $node) = @_;
return 0 if $node->type eq 'phony' or not -e $node->target;
my $mtime = -M _;
for my $dep_name (sort $node->dependencies) {
if (my $dep = $self->{nodes}{$dep_name}) {
return 0 unless $dep->newer_than($mtime);
} else {
return 0 unless -e $dep_name && $mtime <= -M _;
}
}
return 1;
}
sub merge {
my ($self, $other) = @_;
Carp::croak('Right side of merge is not a Plan') if not $other->isa(__PACKAGE__);
my $double = join ', ', grep { $other->{nodes}{$_} } keys %{ $self->{nodes} };
Carp::croak("Found key(s) $double on both sides of merge") if $double;
my %nodes = (%{ $self->{nodes} }, %{ $other->{nodes} });
return ref($self)->new(nodes => [ values %nodes ]);
}
sub phonies {
my ($self) = @_;
return sort map { $_->target } grep { $_->phony } values %{ $self->{nodes} };
}
1;
# ABSTRACT: An ExtUtils::Builder Plan
__END__
=pod
=encoding UTF-8
=head1 NAME
ExtUtils::Builder::Plan - An ExtUtils::Builder Plan
=head1 VERSION
version 0.016
=head1 SYNOPSIS
package Frobnicate;
sub plan {
my %nodes = ...;
return ExtUtils::Builder::Plan->new(
nodes => \%nodes,
);
}
my $plan = Frobnicate->plan(@args);
# various consumption methods
$plan->run('foo');
say $_->target for $plan->nodes;
=head1 DESCRIPTION
An object of this class describes a process. It contains one or more nodes. This is enough to describe whole building processes, in fact its C<run> method is a tiny C<make> engine.
=head1 ATTRIBUTES
=head2 nodes
This is the set of all nodes in this plan.
=head1 METHODS
=head2 run($target, %options)
This runs the process. Similar to C<make>, it checks for each node if it is necessary to run, and if not skips it. C<$target> may either be a single string or an array ref of strings for the targets to run.
=head2 node($target)
Returns the node for the specified C<$target>.
=head2 node_names()
Returns the names of all the nodes.
=head2 merge($other)
This merges this plan with another, and returns the new plan. Each entry may only exist on one side of the merge.
=head2 phonies()
This returns the names of all phony targets.
=for Pod::Coverage new
=head1 AUTHOR
Leon Timmermans <fawaka@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2013 by Leon Timmermans.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut