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
|
|
537
|
use Data::Hopen qw(:default *QUIET); |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
130
|
|
4
|
1
|
|
|
1
|
|
10
|
use strict; use warnings; |
|
1
|
|
|
1
|
|
2
|
|
|
1
|
|
|
|
|
19
|
|
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
21
|
|
5
|
1
|
|
|
1
|
|
10
|
use Data::Hopen::Base; |
|
1
|
|
|
|
|
9
|
|
|
1
|
|
|
|
|
7
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.000013'; # TRIAL |
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
1273
|
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
|
3
|
|
|
|
|
43
|
_assetop_by_asset => sub { +{} }, # Indexed by refaddr($asset) |
16
|
1
|
|
|
1
|
|
1934
|
}; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
10
|
|
17
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
1245
|
use App::hopen::Asset; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
41
|
|
19
|
1
|
|
|
1
|
|
7
|
use App::hopen::BuildSystemGlobals; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
137
|
|
20
|
1
|
|
|
1
|
|
8
|
use App::hopen::Util::String qw(eval_here); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
54
|
|
21
|
1
|
|
|
1
|
|
8
|
use Data::Hopen::G::DAG; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
46
|
|
22
|
1
|
|
|
1
|
|
5
|
use Data::Hopen::Util::Data qw(forward_opts); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
48
|
|
23
|
1
|
|
|
1
|
|
574
|
use File::pushd qw(pushd); |
|
1
|
|
|
|
|
1325
|
|
|
1
|
|
|
|
|
53
|
|
24
|
1
|
|
|
1
|
|
14
|
use Path::Class (); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
20
|
|
25
|
1
|
|
|
1
|
|
9
|
use Scalar::Util qw(refaddr); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
1168
|
|
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
# Docs {{{1 |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 NAME |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
App::hopen::Gen - Base class for hopen generators |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 SYNOPSIS |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
The code that generates blueprints for specific build systems |
36
|
|
|
|
|
|
|
lives under C<App::hopen::Gen>. L<App::hopen> calls modules |
37
|
|
|
|
|
|
|
under C<App::hopen::Gen> to create the blueprints. Those modules must |
38
|
|
|
|
|
|
|
implement the interface defined here. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=head2 proj_dir |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
(Required) A L<Path::Class::Dir> instance specifying the root directory of |
45
|
|
|
|
|
|
|
the project. |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head2 dest_dir |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
(Required) A L<Path::Class::Dir> instance specifying where the generated output |
50
|
|
|
|
|
|
|
(e.g., blueprint or other files) should be written. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 _assets (Internal) |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
A L<Data::Hopen::G::DAG> of L<App::hopen::G::AssetOp> instances representing |
55
|
|
|
|
|
|
|
the L<App::Hopen::Asset>s to be created when a build is run. |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=head1 FUNCTIONS |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
A generator (C<App::hopen::Gen> subclass) is a Visitor plus some. |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
B<Note>: |
62
|
|
|
|
|
|
|
The generator does not have access to L<Data::Hopen::G::Link> instances. |
63
|
|
|
|
|
|
|
That lack of access is the primary distinction between Ops and Links. |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=cut |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# }}}1 |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 asset |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Called by an Op (L<App::hopen::G::Op> subclass) to add an asset |
72
|
|
|
|
|
|
|
(L<App::hopen::G::AssetOp> instance) to the build. Usage: |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
$Generator->asset([-asset=>]$asset, [-from=>]$from[, [-how=>]$how]); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
If C<$how> is specified, it will be saved in the C<AssetOp> for use later. |
77
|
|
|
|
|
|
|
Later calls with the same asset and a defined C<$how> will overwrite the |
78
|
|
|
|
|
|
|
C<how> value in the C<AssetOp>. Specify 'UNDEF' as the C<$how> to |
79
|
|
|
|
|
|
|
expressly undefine a C<how>. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Returns the C<AssetOp>. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=cut |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub asset { |
86
|
12
|
|
|
12
|
1
|
78
|
my ($self, %args) = getparameters('self', [qw(asset; how)], @_); |
87
|
12
|
|
|
0
|
|
1125
|
hlog { 'Generator adding asset at',refaddr($args{asset}),$args{asset} } 3; |
|
0
|
|
|
|
|
0
|
|
88
|
|
|
|
|
|
|
|
89
|
12
|
|
|
|
|
350
|
my $existing_op = $self->_assetop_by_asset->{refaddr($args{asset})}; |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# Update an existing op |
92
|
12
|
50
|
|
|
|
82
|
if(defined $existing_op) { |
93
|
0
|
0
|
0
|
|
|
0
|
if( ($args{how}//'') eq 'UNDEF') { |
|
|
0
|
|
|
|
|
|
94
|
0
|
|
|
|
|
0
|
$existing_op->how(undef); |
95
|
|
|
|
|
|
|
} elsif(defined $args{how}) { |
96
|
0
|
|
|
|
|
0
|
$existing_op->how($args{how}); |
97
|
|
|
|
|
|
|
} |
98
|
0
|
|
|
|
|
0
|
return $existing_op; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# Need to create an op. First, load its class. |
102
|
12
|
|
|
|
|
50
|
my $class = $self->_assetop_class; |
103
|
|
|
|
|
|
|
|
104
|
12
|
|
|
|
|
60
|
eval_here <<EOT; |
105
|
|
|
|
|
|
|
require $class; |
106
|
|
|
|
|
|
|
EOT |
107
|
12
|
50
|
|
|
|
66
|
die "$@" if $@; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Create a new op |
110
|
12
|
|
|
|
|
51
|
my $op = $class->new(name => 'Op:<<' . $args{asset}->target . '>>', |
111
|
|
|
|
|
|
|
forward_opts(\%args, qw(asset how))); |
112
|
12
|
|
|
|
|
323
|
$self->_assetop_by_asset->{refaddr($args{asset})} = $op; |
113
|
12
|
|
|
|
|
269
|
$self->_assets->add($op); |
114
|
12
|
|
|
|
|
3013
|
return $op; |
115
|
|
|
|
|
|
|
} #asset() |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 connect |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Add a dependency edge between two assets or goals. Any assets must have already |
120
|
|
|
|
|
|
|
been added using L</asset>. Usage: |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
$Generator->connect([-from=>]$from, [-to=>$to]); |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
TODO add missing assets automatically? |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
TODO rename the asset-graph public interface so it's more clear that it's |
127
|
|
|
|
|
|
|
the asset graph and not the command graph. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=cut |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub connect { |
132
|
12
|
|
|
12
|
1
|
141
|
my ($self, %args) = getparameters('self', [qw(from to)], @_); |
133
|
12
|
|
|
|
|
875
|
my %nodes; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# Get the nodes if we were passed assets. |
136
|
12
|
|
|
|
|
31
|
foreach my $field (qw(from to)) { |
137
|
24
|
100
|
|
|
|
110
|
if(eval { $args{$field}->DOES('App::hopen::Asset') }) { |
|
24
|
|
|
|
|
155
|
|
138
|
15
|
|
|
|
|
273
|
$nodes{$field} = $self->_assetop_by_asset->{refaddr($args{$field})}; |
139
|
|
|
|
|
|
|
} else { |
140
|
9
|
|
|
|
|
28
|
$nodes{$field} = $args{$field}; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
} |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# TODO better error messages |
145
|
12
|
50
|
|
|
|
78
|
croak "No From node for asset " . refaddr($args{from}) unless $nodes{from}; |
146
|
12
|
50
|
|
|
|
195
|
croak "No To node for asset " . refaddr($args{to}) unless $nodes{to}; |
147
|
12
|
|
|
|
|
337
|
$self->_assets->connect($nodes{from}, $nodes{to}); |
148
|
|
|
|
|
|
|
} #connect() |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
=head2 asset_default_goal |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
Read-only accessor for the default goal of the asset graph |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=cut |
155
|
|
|
|
|
|
|
|
156
|
4
|
|
|
4
|
1
|
81
|
sub asset_default_goal () { shift->_assets->default_goal } |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=head2 run_build |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Runs the build tool for which this generator has created blueprint files. |
161
|
|
|
|
|
|
|
Runs the tool with the destination directory as the current dir. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=cut |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
sub run_build { |
166
|
0
|
0
|
|
0
|
1
|
0
|
my $self = shift or croak 'Need an instance'; |
167
|
0
|
|
|
|
|
0
|
my $abs_dir = $DestDir->absolute; |
168
|
|
|
|
|
|
|
# NOTE: You have to call this *before* pushd() or chdir(), because |
169
|
|
|
|
|
|
|
# it may be a relative path, and absolute() converts with respect |
170
|
|
|
|
|
|
|
# to cwd at the time of the call. |
171
|
0
|
|
|
|
|
0
|
my $dir = pushd($abs_dir); |
172
|
0
|
0
|
|
|
|
0
|
say "Building in ${abs_dir}..." unless $QUIET; |
173
|
0
|
|
|
|
|
0
|
$self->_run_build(); |
174
|
|
|
|
|
|
|
} #run_build() |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
=head2 BUILD |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Constructor. |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=cut |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
sub BUILD { |
183
|
6
|
|
|
6
|
1
|
772
|
my ($self, $args) = @_; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Enforce the required argument types |
186
|
|
|
|
|
|
|
croak "Need a project directory (Path::Class::Dir)" |
187
|
6
|
50
|
|
|
|
14
|
unless eval { $self->proj_dir->DOES('Path::Class::Dir') }; |
|
6
|
|
|
|
|
163
|
|
188
|
|
|
|
|
|
|
croak "Need a destination directory (Path::Class::Dir)" |
189
|
6
|
50
|
|
|
|
114
|
unless eval { $self->dest_dir->DOES('Path::Class::Dir') }; |
|
6
|
|
|
|
|
131
|
|
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Create the asset graph |
192
|
6
|
|
|
|
|
79
|
$self->_assets(hnew DAG => 'asset graph'); |
193
|
6
|
|
|
|
|
6203
|
$self->_assets->goal('__R_asset_default_goal'); |
194
|
|
|
|
|
|
|
# Create and set default goal |
195
|
|
|
|
|
|
|
} #BUILD() |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head1 FUNCTIONS TO BE IMPLEMENTED BY SUBCLASSES |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 _assetop_class |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
(Required) Returns the name of the L<App::hopen::G::AssetOp> subclass that |
202
|
|
|
|
|
|
|
should be used to represent assets in the C<_assets> graph. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=cut |
205
|
|
|
|
|
|
|
|
206
|
0
|
|
|
0
|
|
0
|
sub _assetop_class { ... } |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=head2 default_toolset |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
(Required) Returns the package stem of the default toolset for this generator. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
When a hopen file invokes C<use language "Foo">, hopen will load |
213
|
|
|
|
|
|
|
C<< App::hopen::T::<stem>::Foo >>. C<< <stem> >> is the return |
214
|
|
|
|
|
|
|
value of this function unless the user has specified a different toolset. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
As a sanity check, hopen will first try to load C<< App::hopen::T::<stem> >>, |
217
|
|
|
|
|
|
|
so make sure that is a valid package. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=cut |
220
|
|
|
|
|
|
|
|
221
|
0
|
|
|
0
|
1
|
0
|
sub default_toolset { ... } |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 finalize |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
(Optional) |
226
|
|
|
|
|
|
|
Do whatever the generator wants to do to finish up. By default, no-op. |
227
|
|
|
|
|
|
|
Is provided the L<Data::Hopen::G::DAG> instance as a parameter. Usage: |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
$generator->finalize(-phase=>$Phase, -graph=>$Build, |
230
|
|
|
|
|
|
|
-data=>$data) |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
C<$dag> is the command graph, and C<$data> is the output from the |
233
|
|
|
|
|
|
|
command graph. |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
C<finalize> is always called with named parameters. |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=cut |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
0
|
1
|
|
sub finalize { } |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head2 _run_build |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
(Optional) |
244
|
|
|
|
|
|
|
Implementation of L</run_build>. The default does not die, but does warn(). |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
sub _run_build { |
249
|
0
|
|
|
0
|
|
0
|
warn "This generator is not configured to run a build tool. Sorry!"; |
250
|
|
|
|
|
|
|
} #_run_build() |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head2 visit_goal |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
Add a target corresponding to the name of the goal. Usage: |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
$Generator->visit_goal($node, $node_inputs); |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
This happens while the command graph is being run. |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
This can be overriden by a generator that wants to handle |
261
|
|
|
|
|
|
|
L<Data::Hopen::G::Goal> nodes differently. |
262
|
|
|
|
|
|
|
For example, the generator may want to change the goal's C<outputs>. |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=cut |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
sub visit_goal { |
267
|
3
|
|
|
3
|
1
|
9710
|
my ($self, %args) = getparameters('self', [qw(goal node_inputs)], @_); |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# --- Add the goal to the asset graph --- |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
#my $asset_goal = $self->_assets->goal($args{goal}->name); |
272
|
|
|
|
|
|
|
my $phony_asset = App::hopen::Asset->new( |
273
|
|
|
|
|
|
|
target => $args{goal}->name, |
274
|
3
|
|
|
|
|
263
|
made_by => $self, |
275
|
|
|
|
|
|
|
); |
276
|
3
|
|
|
|
|
61
|
my $phony_node = $self->asset(-asset => $phony_asset, -how => ''); |
277
|
|
|
|
|
|
|
# \p how defined but falsy => it's a goal |
278
|
3
|
|
|
|
|
32
|
$self->connect($phony_node, $self->asset_default_goal); |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# Pull the inputs. TODO refactor out the code in common with |
281
|
|
|
|
|
|
|
# AhG::Cmd::input_assets(). |
282
|
|
|
|
|
|
|
my $hrSourceFiles = |
283
|
3
|
|
50
|
|
|
1279
|
$args{node_inputs}->find(-name => 'made', |
284
|
|
|
|
|
|
|
-set => '*', -levels => 'local') // {}; |
285
|
|
|
|
|
|
|
die 'No input files to goal ' . $args{goal}->name |
286
|
3
|
50
|
|
|
|
669
|
unless scalar keys %$hrSourceFiles; |
287
|
|
|
|
|
|
|
|
288
|
3
|
|
|
|
|
12
|
my $lrSourceFiles = $hrSourceFiles->{(keys %$hrSourceFiles)[0]}; |
289
|
3
|
|
|
0
|
|
22
|
hlog { 'found inputs to goal', $args{goal}->name, Dumper($lrSourceFiles) } 2; |
|
0
|
|
|
|
|
0
|
|
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
# TODO? verify that all the assets are actually in the graph first? |
292
|
3
|
|
|
|
|
35
|
$self->connect($_, $phony_node) foreach @$lrSourceFiles; |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
} #visit_goal() |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head2 visit_node |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
(Optional) |
299
|
|
|
|
|
|
|
Do whatever the generator wants to do with a L<Data::Hopen::G::Node> that |
300
|
|
|
|
|
|
|
is not a Goal (see L</visit_goal>). By default, no-op. Usage: |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
$generator->visit_node($node) |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=cut |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
9
|
1
|
|
sub visit_node { } |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
1; |
309
|
|
|
|
|
|
|
__END__ |
310
|
|
|
|
|
|
|
# vi: set fdm=marker: # |