File Coverage

blib/lib/App/hopen/Gen.pm
Criterion Covered Total %
statement 60 73 82.1
branch 7 20 35.0
condition 0 2 0.0
subroutine 15 22 68.1
pod 8 8 100.0
total 90 125 72.0


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