File Coverage

blib/lib/App/hopen/Gen.pm
Criterion Covered Total %
statement 67 80 83.7
branch 8 22 36.3
condition 0 2 0.0
subroutine 17 24 70.8
pod 8 8 100.0
total 100 136 73.5


line stmt bran cond sub pod time code
1             # App::hopen::Gen - base class for hopen generators
2             package App::hopen::Gen;
3 1     1   467 use Data::Hopen qw(:default $QUIET);
  1         2  
  1         125  
4 1     1   7 use strict; use warnings;
  1     1   2  
  1         19  
  1         4  
  1         2  
  1         23  
5 1     1   5 use Data::Hopen::Base;
  1         2  
  1         5  
6              
7             our $VERSION = '0.000012'; # TRIAL
8              
9 1     1   1280 use parent 'Data::Hopen::Visitor';
  1         3  
  1         6  
10             use Class::Tiny qw(proj_dir dest_dir), {
11             architecture => '',
12              
13             # private
14             _assets => undef, # A Data::Hopen::G::DAG of the assets
15 2         30 _assetop_by_asset => sub { +{} }, # Indexed by refaddr($asset)
16 1     1   1990 };
  1         2  
  1         10  
17              
18 1     1   781 use App::hopen::BuildSystemGlobals;
  1         2  
  1         113  
19 1     1   6 use App::hopen::Util::String qw(eval_here);
  1         2  
  1         48  
20 1     1   6 use Data::Hopen::G::DAG;
  1         2  
  1         34  
21 1     1   8 use Data::Hopen::Util::Data qw(forward_opts);
  1         2  
  1         61  
22 1     1   460 use File::pushd qw(pushd);
  1         1279  
  1         75  
23 1     1   8 use Path::Class ();
  1         2  
  1         34  
24 1     1   6 use Scalar::Util qw(refaddr);
  1         2  
  1         830  
25              
26             # Docs {{{1
27              
28             =head1 NAME
29              
30             Data::Hopen::Gen - Base class for hopen generators
31              
32             =head1 SYNOPSIS
33              
34             The code that generates blueprints for specific build systems
35             lives under C<Data::Hopen::Gen>. L<Data::Hopen::Phase::Gen> calls modules
36             under C<Data::Hopen::Gen> to create the blueprints. Those modules must
37             implement the interface defined here.
38              
39             =head1 ATTRIBUTES
40              
41             =head2 proj_dir
42              
43             (Required) A L<Path::Class::Dir> instance specifying the root directory of
44             the project.
45              
46             =head2 dest_dir
47              
48             (Required) A L<Path::Class::Dir> instance specifying where the generated output
49             (e.g., blueprint or other files) should be written.
50              
51             =head2 _assets (Internal)
52              
53             A L<Data::Hopen::G::DAG> of L<App::hopen::G::AssetOp> instances representing
54             the L<App::Hopen::Asset>s to be created when a build is run.
55              
56             =head1 FUNCTIONS
57              
58             A generator (C<Data::Hopen::Gen> subclass) is a Visitor plus some.
59              
60             B<Note>:
61             The generator does not have access to L<Data::Hopen::G::Link> instances.
62             That lack of access is the primary distinction between Ops and Links.
63              
64             =cut
65              
66             # }}}1
67              
68             =head2 asset
69              
70             Called by an Op (L<App::hopen::G::Op> subclass) to add an asset
71             (L<App::hopen::G::AssetOp> instance) to the build. Usage:
72              
73             $Generator->asset([-asset=>]$asset, [-from=>]$from[, [-how=>]$how]);
74              
75             If C<$how> is specified, it will be saved in the C<AssetOp> for use later.
76             Later calls with the same asset and a defined C<$how> will overwrite the
77             C<how> value in the C<AssetOp>. Specify 'UNDEF' as the C<$how> to
78             expressly undefine a C<how>.
79              
80             Returns the C<AssetOp>.
81              
82             =cut
83              
84             sub asset {
85 6     6 1 44 my ($self, %args) = getparameters('self', [qw(asset; how)], @_);
86 6     0   575 hlog { 'Generator adding asset at',refaddr($args{asset}),$args{asset} } 3;
  0         0  
87              
88 6         168 my $existing_op = $self->_assetop_by_asset->{refaddr($args{asset})};
89              
90             # Update an existing op
91 6 50       38 if(defined $existing_op) {
92 0 0 0     0 if( ($args{how}//'') eq 'UNDEF') {
    0          
93 0         0 $existing_op->how(undef);
94             } elsif(defined $args{how}) {
95 0         0 $existing_op->how($args{how});
96             }
97 0         0 return $existing_op;
98             }
99              
100             # Need to create an op. First, load its class.
101 6         24 my $class = $self->_assetop_class;
102              
103 6         34 eval_here <<EOT;
104             require $class;
105             EOT
106 6 50       34 die "$@" if $@;
107              
108             # Create a new op
109 6         30 my $op = $class->new(name => 'Op:<<' . $args{asset}->target . '>>',
110             forward_opts(\%args, qw(asset how)));
111 6         166 $self->_assetop_by_asset->{refaddr($args{asset})} = $op;
112 6         136 $self->_assets->add($op);
113 6         2926 return $op;
114             } #asset()
115              
116             =head2 connect
117              
118             Add a dependency edge between two assets or goals. Any assets must have already
119             been added using L</asset>. Usage:
120              
121             $Generator->connect([-from=>]$from, [-to=>$to]);
122              
123             TODO add missing assets automatically?
124              
125             =cut
126              
127             sub connect {
128 6     6 1 28 my ($self, %args) = getparameters('self', [qw(from to)], @_);
129 6         540 my %nodes;
130              
131             # Get the nodes if we were passed assets.
132 6         16 foreach my $field (qw(from to)) {
133 12 100       61 if(eval { $args{$field}->DOES('App::hopen::Asset') }) {
  12         62  
134 10         194 $nodes{$field} = $self->_assetop_by_asset->{refaddr($args{$field})};
135             } else {
136 2         7 $nodes{$field} = $args{$field};
137             }
138             }
139              
140             # TODO better error messages
141 6 50       431 croak "No From node for asset " . refaddr($args{from}) unless $nodes{from};
142 6 50       117 croak "No To node for asset " . refaddr($args{to}) unless $nodes{to};
143 6         206 $self->_assets->connect($nodes{from}, $nodes{to});
144             } #connect()
145              
146             =head2 run_build
147              
148             Runs the build tool for which this generator has created blueprint files.
149             Runs the tool with the destination directory as the current dir.
150              
151             =cut
152              
153             sub run_build {
154 0 0   0 1 0 my $self = shift or croak 'Need an instance';
155 0         0 my $abs_dir = $DestDir->absolute;
156             # NOTE: You have to call this *before* pushd() or chdir(), because
157             # it may be a relative path, and absolute() converts with respect
158             # to cwd at the time of the call.
159 0         0 my $dir = pushd($abs_dir);
160 0 0       0 say "Building in ${abs_dir}..." unless $QUIET;
161 0         0 $self->_run_build();
162             } #run_build()
163              
164             =head2 BUILD
165              
166             Constructor.
167              
168             =cut
169              
170             sub BUILD {
171 4     4 1 448 my ($self, $args) = @_;
172              
173             # Enforce the required argument types
174             croak "Need a project directory (Path::Class::Dir)"
175 4 50       7 unless eval { $self->proj_dir->DOES('Path::Class::Dir') };
  4         108  
176             croak "Need a destination directory (Path::Class::Dir)"
177 4 50       58 unless eval { $self->dest_dir->DOES('Path::Class::Dir') };
  4         82  
178              
179             # Create the asset graph
180 4         46 $self->_assets(hnew DAG => 'asset graph');
181             } #BUILD()
182              
183             =head1 FUNCTIONS TO BE IMPLEMENTED BY SUBCLASSES
184              
185             =head2 _assetop_class
186              
187             (Required) Returns the name of the L<App::hopen::G::AssetOp> subclass that
188             should be used to represent assets in the C<_assets> graph.
189              
190             =cut
191              
192 0     0     sub _assetop_class { ... }
193              
194             =head2 default_toolset
195              
196             (Required) Returns the package stem of the default toolset for this generator.
197              
198             When a hopen file invokes C<use language "Foo">, hopen will load
199             C<< Data::Hopen::T::<stem>::Foo >>, where C<< <stem> >> is the return
200             value of this function.
201              
202             As a sanity check, hopen will first try to load C<< Data::Hopen::T::<stem> >>,
203             so make sure that is a valid package.
204              
205             =cut
206              
207 0     0 1   sub default_toolset { ... }
208              
209             =head2 finalize
210              
211             (Optional)
212             Do whatever the generator wants to do to finish up. By default, no-op.
213             Is provided the L<Data::Hopen::G::DAG> instance as a parameter. Usage:
214              
215             $generator->finalize(-phase=>$Phase, -graph=>$dag)
216              
217             =cut
218              
219       0 1   sub finalize { }
220              
221             =head2 _run_build
222              
223             (Optional)
224             Implementation of L</run_build>. The default does not die, but does warn().
225              
226             =cut
227              
228             sub _run_build {
229 0     0     warn "This generator is not configured to run a build tool. Sorry!";
230             } #_run_build()
231              
232             =head2 visit_goal
233              
234             (Optional)
235             Do whatever the generator wants to do with a L<Data::Hopen::G::Goal>.
236             For example, the generator may change the goal's C<outputs>.
237             By default, no-op. Usage:
238              
239             $generator->visit_goal($goal);
240              
241             =cut
242              
243       0 1   sub visit_goal { }
244              
245             =head2 visit_node
246              
247             (Optional)
248             Do whatever the generator wants to do with a L<Data::Hopen::G::Node> that
249             is not a Goal (see L</visit_goal>). By default, no-op. Usage:
250              
251             $generator->visit_node($node)
252              
253             =cut
254              
255       6 1   sub visit_node { }
256              
257             1;
258             __END__
259             # vi: set fdm=marker: #