line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Beam::Make; |
2
|
|
|
|
|
|
|
our $VERSION = '0.001'; |
3
|
|
|
|
|
|
|
# ABSTRACT: Recipes to declare and resolve dependencies between things |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
#pod =head1 SYNOPSIS |
6
|
|
|
|
|
|
|
#pod |
7
|
|
|
|
|
|
|
#pod ### container.yml |
8
|
|
|
|
|
|
|
#pod # This Beam::Wire container stores shared objects for our recipes |
9
|
|
|
|
|
|
|
#pod dbh: |
10
|
|
|
|
|
|
|
#pod $class: DBI |
11
|
|
|
|
|
|
|
#pod $method: connect |
12
|
|
|
|
|
|
|
#pod $args: |
13
|
|
|
|
|
|
|
#pod - dbi:SQLite:RECENT.db |
14
|
|
|
|
|
|
|
#pod |
15
|
|
|
|
|
|
|
#pod ### Beamfile |
16
|
|
|
|
|
|
|
#pod # This file contains our recipes |
17
|
|
|
|
|
|
|
#pod # Download a list of recent changes to CPAN |
18
|
|
|
|
|
|
|
#pod RECENT-6h.json: |
19
|
|
|
|
|
|
|
#pod commands: |
20
|
|
|
|
|
|
|
#pod - curl -O https://www.cpan.org/RECENT-6h.json |
21
|
|
|
|
|
|
|
#pod |
22
|
|
|
|
|
|
|
#pod # Parse that JSON file into a CSV using an external program |
23
|
|
|
|
|
|
|
#pod RECENT-6h.csv: |
24
|
|
|
|
|
|
|
#pod requires: |
25
|
|
|
|
|
|
|
#pod - RECENT-6h.json |
26
|
|
|
|
|
|
|
#pod commands: |
27
|
|
|
|
|
|
|
#pod - yfrom json RECENT-6h.json | yq '.recent.[]' | yto csv > RECENT-6h.csv |
28
|
|
|
|
|
|
|
#pod |
29
|
|
|
|
|
|
|
#pod # Build a SQLite database to hold the recent data |
30
|
|
|
|
|
|
|
#pod RECENT.db: |
31
|
|
|
|
|
|
|
#pod $class: Beam::Make::DBI::Schema |
32
|
|
|
|
|
|
|
#pod dbh: { $ref: 'container.yml:dbh' } |
33
|
|
|
|
|
|
|
#pod schema: |
34
|
|
|
|
|
|
|
#pod - table: recent |
35
|
|
|
|
|
|
|
#pod columns: |
36
|
|
|
|
|
|
|
#pod - path: VARCHAR(255) |
37
|
|
|
|
|
|
|
#pod - epoch: DOUBLE |
38
|
|
|
|
|
|
|
#pod - type: VARCHAR(10) |
39
|
|
|
|
|
|
|
#pod |
40
|
|
|
|
|
|
|
#pod # Load the recent data CSV into the SQLite database |
41
|
|
|
|
|
|
|
#pod cpan-recent: |
42
|
|
|
|
|
|
|
#pod $class: Beam::Make::DBI::CSV |
43
|
|
|
|
|
|
|
#pod requires: |
44
|
|
|
|
|
|
|
#pod - RECENT.db |
45
|
|
|
|
|
|
|
#pod - RECENT-6h.csv |
46
|
|
|
|
|
|
|
#pod dbh: { $ref: 'container.yml:dbh' } |
47
|
|
|
|
|
|
|
#pod table: recent |
48
|
|
|
|
|
|
|
#pod file: RECENT-6h.csv |
49
|
|
|
|
|
|
|
#pod |
50
|
|
|
|
|
|
|
#pod ### Load the recent data into our database |
51
|
|
|
|
|
|
|
#pod $ beam make cpan-recent |
52
|
|
|
|
|
|
|
#pod |
53
|
|
|
|
|
|
|
#pod =head1 DESCRIPTION |
54
|
|
|
|
|
|
|
#pod |
55
|
|
|
|
|
|
|
#pod C<Beam::Make> allows an author to describe how to build some thing (a |
56
|
|
|
|
|
|
|
#pod file, some data in a database, an image, a container, etc...) and the |
57
|
|
|
|
|
|
|
#pod relationships between things. This is similar to the classic C<make> |
58
|
|
|
|
|
|
|
#pod program used to build some software packages. |
59
|
|
|
|
|
|
|
#pod |
60
|
|
|
|
|
|
|
#pod Each thing is a C<recipe> and can depend on other recipes. A user runs |
61
|
|
|
|
|
|
|
#pod the C<beam make> command to build the recipes they want, and |
62
|
|
|
|
|
|
|
#pod C<Beam::Make> ensures that the recipe's dependencies are satisfied |
63
|
|
|
|
|
|
|
#pod before building the recipe. |
64
|
|
|
|
|
|
|
#pod |
65
|
|
|
|
|
|
|
#pod This class is a L<Beam::Runnable> object and can be embedded in other |
66
|
|
|
|
|
|
|
#pod L<Beam::Wire> containers. |
67
|
|
|
|
|
|
|
#pod |
68
|
|
|
|
|
|
|
#pod =head2 Recipe Classes |
69
|
|
|
|
|
|
|
#pod |
70
|
|
|
|
|
|
|
#pod Unlike C<make>, C<Beam::Make> recipes can do more than just execute |
71
|
|
|
|
|
|
|
#pod a series of shell scripts. Each recipe is a Perl class that describes |
72
|
|
|
|
|
|
|
#pod how to build the desired thing and how to determine if that thing needs |
73
|
|
|
|
|
|
|
#pod to be rebuilt. |
74
|
|
|
|
|
|
|
#pod |
75
|
|
|
|
|
|
|
#pod These recipe classes come with C<Beam::Make>: |
76
|
|
|
|
|
|
|
#pod |
77
|
|
|
|
|
|
|
#pod =over |
78
|
|
|
|
|
|
|
#pod |
79
|
|
|
|
|
|
|
#pod =item * L<File|Beam::Make::File> - The default recipe class that creates |
80
|
|
|
|
|
|
|
#pod a file using one or more shell commands (a la C<make>) |
81
|
|
|
|
|
|
|
#pod |
82
|
|
|
|
|
|
|
#pod =item * L<DBI|Beam::Make::DBI> - Write data to a database |
83
|
|
|
|
|
|
|
#pod |
84
|
|
|
|
|
|
|
#pod =item * L<DBI::Schema|Beam::Make::DBI::Schema> - Create a database |
85
|
|
|
|
|
|
|
#pod schema |
86
|
|
|
|
|
|
|
#pod |
87
|
|
|
|
|
|
|
#pod =item * L<DBI::CSV|Beam::Make::DBI::CSV> - Load data from a CSV into |
88
|
|
|
|
|
|
|
#pod a database table |
89
|
|
|
|
|
|
|
#pod |
90
|
|
|
|
|
|
|
#pod =back |
91
|
|
|
|
|
|
|
#pod |
92
|
|
|
|
|
|
|
#pod Future recipe class ideas are: |
93
|
|
|
|
|
|
|
#pod |
94
|
|
|
|
|
|
|
#pod =over |
95
|
|
|
|
|
|
|
#pod |
96
|
|
|
|
|
|
|
#pod =item * |
97
|
|
|
|
|
|
|
#pod |
98
|
|
|
|
|
|
|
#pod B<Template rendering>: Files could be generated from a configuration |
99
|
|
|
|
|
|
|
#pod file or database and a template. |
100
|
|
|
|
|
|
|
#pod |
101
|
|
|
|
|
|
|
#pod =item * |
102
|
|
|
|
|
|
|
#pod |
103
|
|
|
|
|
|
|
#pod B<Docker image, container, compose>: A Docker container could depend on |
104
|
|
|
|
|
|
|
#pod a Docker image. When the image is updated, the container would get |
105
|
|
|
|
|
|
|
#pod rebuilt and restarted. The Docker image could depend on a directory and |
106
|
|
|
|
|
|
|
#pod get rebuilt if the directory or its Dockerfile changes. |
107
|
|
|
|
|
|
|
#pod |
108
|
|
|
|
|
|
|
#pod =item * |
109
|
|
|
|
|
|
|
#pod |
110
|
|
|
|
|
|
|
#pod B<System services (init daemon, systemd service, etc...)>: Services |
111
|
|
|
|
|
|
|
#pod could depend on their configuration files (built with a template) and be |
112
|
|
|
|
|
|
|
#pod restarted when their configuration file is updated. |
113
|
|
|
|
|
|
|
#pod |
114
|
|
|
|
|
|
|
#pod =back |
115
|
|
|
|
|
|
|
#pod |
116
|
|
|
|
|
|
|
#pod =head2 Beamfile |
117
|
|
|
|
|
|
|
#pod |
118
|
|
|
|
|
|
|
#pod The C<Beamfile> defines the recipes. To avoid the pitfalls of C<Makefile>, this is |
119
|
|
|
|
|
|
|
#pod a YAML file containing a mapping of recipe names to recipe configuration. Each |
120
|
|
|
|
|
|
|
#pod recipe configuration is a mapping containing the attributes for the recipe class. |
121
|
|
|
|
|
|
|
#pod The C<$class> special configuration key declares the recipe class to use. If no |
122
|
|
|
|
|
|
|
#pod C<$class> is specified, the default L<Beam::Wire::File> recipe class is used. |
123
|
|
|
|
|
|
|
#pod All recipe classes inherit from L<Beam::Class::Recipe> and have the L<name|Beam::Class::Recipe/name> |
124
|
|
|
|
|
|
|
#pod and L<requires|Beam::Class::Recipe/requires> attributes. |
125
|
|
|
|
|
|
|
#pod |
126
|
|
|
|
|
|
|
#pod For examples, see the L<Beam::Wire examples directory on |
127
|
|
|
|
|
|
|
#pod Github|https://github.com/preaction/Beam-Make/tree/master/eg>. |
128
|
|
|
|
|
|
|
#pod |
129
|
|
|
|
|
|
|
#pod =head2 Object Containers |
130
|
|
|
|
|
|
|
#pod |
131
|
|
|
|
|
|
|
#pod For additional configuration, create a L<Beam::Wire> container and |
132
|
|
|
|
|
|
|
#pod reference the objects inside using C<< $ref: "<container>:<service>" >> |
133
|
|
|
|
|
|
|
#pod as the value for a recipe attribute. |
134
|
|
|
|
|
|
|
#pod |
135
|
|
|
|
|
|
|
#pod =head1 TODO |
136
|
|
|
|
|
|
|
#pod |
137
|
|
|
|
|
|
|
#pod =over |
138
|
|
|
|
|
|
|
#pod |
139
|
|
|
|
|
|
|
#pod =item Target names in C<Beamfile> should be regular expressions |
140
|
|
|
|
|
|
|
#pod |
141
|
|
|
|
|
|
|
#pod This would work like Make's wildcard recipes, but with Perl regexp. The |
142
|
|
|
|
|
|
|
#pod recipe object's name is the real name, but the recipe chosen is the one |
143
|
|
|
|
|
|
|
#pod the matches the regexp. |
144
|
|
|
|
|
|
|
#pod |
145
|
|
|
|
|
|
|
#pod =item Environment variables should interpolate into all attributes |
146
|
|
|
|
|
|
|
#pod |
147
|
|
|
|
|
|
|
#pod Right now, the C<< NAME=VALUE >> arguments to C<beam make> only work in |
148
|
|
|
|
|
|
|
#pod recipes that use shell scripts (like L<Beam::Make::File>). It would be |
149
|
|
|
|
|
|
|
#pod nice if they were also interpolated into other recipe attributes. |
150
|
|
|
|
|
|
|
#pod |
151
|
|
|
|
|
|
|
#pod =item Recipes should be able to require wildcards and directories |
152
|
|
|
|
|
|
|
#pod |
153
|
|
|
|
|
|
|
#pod Recipe requirements should be able to depend on patterns, like all |
154
|
|
|
|
|
|
|
#pod C<*.conf> files in a directory. It should also be able to depend on |
155
|
|
|
|
|
|
|
#pod a directory, which would be the same as depending on every file, |
156
|
|
|
|
|
|
|
#pod recursively, in that directory. |
157
|
|
|
|
|
|
|
#pod |
158
|
|
|
|
|
|
|
#pod This would allow rebuilding a ZIP file when something changes, or |
159
|
|
|
|
|
|
|
#pod rebuilding a Docker image when needed. |
160
|
|
|
|
|
|
|
#pod |
161
|
|
|
|
|
|
|
#pod =item Beam::Wire should support the <container>:<service> syntax |
162
|
|
|
|
|
|
|
#pod for references |
163
|
|
|
|
|
|
|
#pod |
164
|
|
|
|
|
|
|
#pod The L<Beam::Wire> class should handle the C<BEAM_PATH> environment |
165
|
|
|
|
|
|
|
#pod variable directly and be able to resolve services from other files |
166
|
|
|
|
|
|
|
#pod without building another C<Beam::Wire> object in the container. |
167
|
|
|
|
|
|
|
#pod |
168
|
|
|
|
|
|
|
#pod =item Beam::Wire should support resolving objects in arbitrary data |
169
|
|
|
|
|
|
|
#pod structures |
170
|
|
|
|
|
|
|
#pod |
171
|
|
|
|
|
|
|
#pod L<Beam::Wire> should have a class method that one can pass in a hash and |
172
|
|
|
|
|
|
|
#pod get back a hash with any C<Beam::Wire> object references resolved, |
173
|
|
|
|
|
|
|
#pod including C<$ref> or C<$class> object. |
174
|
|
|
|
|
|
|
#pod |
175
|
|
|
|
|
|
|
#pod =back |
176
|
|
|
|
|
|
|
#pod |
177
|
|
|
|
|
|
|
#pod =head1 SEE ALSO |
178
|
|
|
|
|
|
|
#pod |
179
|
|
|
|
|
|
|
#pod L<Beam::Wire> |
180
|
|
|
|
|
|
|
#pod |
181
|
|
|
|
|
|
|
#pod =cut |
182
|
|
|
|
|
|
|
|
183
|
2
|
|
|
2
|
|
183721
|
use v5.20; |
|
2
|
|
|
|
|
25
|
|
184
|
2
|
|
|
2
|
|
19
|
use warnings; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
73
|
|
185
|
2
|
|
|
2
|
|
926
|
use Log::Any qw( $LOG ); |
|
2
|
|
|
|
|
16744
|
|
|
2
|
|
|
|
|
9
|
|
186
|
2
|
|
|
2
|
|
5297
|
use Moo; |
|
2
|
|
|
|
|
19515
|
|
|
2
|
|
|
|
|
8
|
|
187
|
2
|
|
|
2
|
|
3916
|
use experimental qw( signatures postderef ); |
|
2
|
|
|
|
|
6916
|
|
|
2
|
|
|
|
|
18
|
|
188
|
2
|
|
|
2
|
|
1629
|
use Time::Piece; |
|
2
|
|
|
|
|
19507
|
|
|
2
|
|
|
|
|
9
|
|
189
|
2
|
|
|
2
|
|
1023
|
use YAML (); |
|
2
|
|
|
|
|
13962
|
|
|
2
|
|
|
|
|
52
|
|
190
|
2
|
|
|
2
|
|
1291
|
use Beam::Wire; |
|
2
|
|
|
|
|
1266848
|
|
|
2
|
|
|
|
|
92
|
|
191
|
2
|
|
|
2
|
|
20
|
use Scalar::Util qw( blessed ); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
104
|
|
192
|
2
|
|
|
2
|
|
12
|
use List::Util qw( max ); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
180
|
|
193
|
2
|
|
|
2
|
|
969
|
use Beam::Make::Cache; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
91
|
|
194
|
2
|
|
|
2
|
|
15
|
use File::stat; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
8
|
|
195
|
|
|
|
|
|
|
with 'Beam::Runnable'; |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
has conf => ( is => 'ro', default => sub { YAML::LoadFile( 'Beamfile' ) } ); |
198
|
|
|
|
|
|
|
# Beam::Wire container objects |
199
|
|
|
|
|
|
|
has _wire => ( is => 'ro', default => sub { {} } ); |
200
|
|
|
|
|
|
|
|
201
|
7
|
|
|
7
|
0
|
46814
|
sub run( $self, @argv ) { |
|
7
|
|
|
|
|
29
|
|
|
7
|
|
|
|
|
33
|
|
|
7
|
|
|
|
|
14
|
|
202
|
7
|
|
|
|
|
27
|
my ( @targets, %vars ); |
203
|
|
|
|
|
|
|
|
204
|
7
|
|
|
|
|
34
|
for my $arg ( @argv ) { |
205
|
9
|
100
|
|
|
|
53
|
if ( $arg =~ /^([^=]+)=([^=]+)$/ ) { |
206
|
1
|
|
|
|
|
25
|
$vars{ $1 } = $2; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
else { |
209
|
8
|
|
|
|
|
26
|
push @targets, $arg; |
210
|
|
|
|
|
|
|
} |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
7
|
|
|
|
|
35
|
local @ENV{ keys %vars } = values %vars; |
214
|
7
|
|
|
|
|
30
|
my $conf = $self->conf; |
215
|
7
|
|
|
|
|
230
|
my $cache = Beam::Make::Cache->new; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# Targets must be built in order |
218
|
|
|
|
|
|
|
# Prereqs satisfied by original target remain satisfied |
219
|
7
|
|
|
|
|
19
|
my %recipes; # Built recipes |
220
|
|
|
|
|
|
|
my @target_stack; |
221
|
|
|
|
|
|
|
# Build a target (if necessary) and return its last modified date. |
222
|
|
|
|
|
|
|
# Each dependent will be checked against their depencencies' last |
223
|
|
|
|
|
|
|
# modified date to see if they need to be updated |
224
|
15
|
|
|
15
|
|
23
|
my $build = sub( $target ) { |
|
15
|
|
|
|
|
152
|
|
|
15
|
|
|
|
|
71
|
|
225
|
15
|
|
|
|
|
107
|
$LOG->debug( "Want to build: $target" ); |
226
|
15
|
50
|
|
|
|
179
|
if ( grep { $_ eq $target } @target_stack ) { |
|
8
|
|
|
|
|
32
|
|
227
|
0
|
|
|
|
|
0
|
die "Recursion at @target_stack"; |
228
|
|
|
|
|
|
|
} |
229
|
|
|
|
|
|
|
# If we already have the recipe, it must already have been run |
230
|
15
|
100
|
|
|
|
65
|
if ( $recipes{ $target } ) { |
231
|
1
|
|
|
|
|
32
|
$LOG->debug( "Nothing to do: $target already built" ); |
232
|
1
|
|
|
|
|
17
|
return $recipes{ $target }->last_modified; |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# If there is no recipe for the target, it must be a source |
236
|
|
|
|
|
|
|
# file. Source files cannot be built, but we do want to know |
237
|
|
|
|
|
|
|
# when they were last modified |
238
|
14
|
100
|
|
|
|
45
|
if ( !$conf->{ $target } ) { |
239
|
1
|
50
|
|
|
|
68
|
$LOG->debug( |
240
|
|
|
|
|
|
|
"$target has no recipe and " |
241
|
|
|
|
|
|
|
. ( -e $target ? 'exists as a file' : 'does not exist as a file' ) |
242
|
|
|
|
|
|
|
); |
243
|
1
|
50
|
|
|
|
17
|
return stat( $target )->mtime if -e $target; |
244
|
1
|
|
|
|
|
33
|
die $LOG->errorf( q{No recipe for target "%s" and file does not exist}."\n", $target ); |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
# Resolve any references in the recipe object via Beam::Wire |
248
|
|
|
|
|
|
|
# containers. |
249
|
13
|
|
|
|
|
67
|
my $target_conf = $self->_resolve_ref( $conf->{ $target } ); |
250
|
13
|
|
100
|
|
|
96
|
my $class = delete( $target_conf->{ '$class' } ) || 'Beam::Make::File'; |
251
|
13
|
|
|
|
|
88
|
$LOG->debug( "Building recipe object $target ($class)" ); |
252
|
13
|
|
|
|
|
1385
|
eval "require $class"; |
253
|
13
|
|
|
|
|
341
|
my $recipe = $recipes{ $target } = $class->new( |
254
|
|
|
|
|
|
|
$target_conf->%*, |
255
|
|
|
|
|
|
|
name => $target, |
256
|
|
|
|
|
|
|
cache => $cache, |
257
|
|
|
|
|
|
|
); |
258
|
|
|
|
|
|
|
|
259
|
13
|
|
|
|
|
1989
|
my $requires_modified = 0; |
260
|
13
|
100
|
|
|
|
84
|
if ( my @requires = $recipe->requires->@* ) { |
261
|
7
|
|
|
|
|
61
|
$LOG->debug( "Checking requirements for $target: @requires" ); |
262
|
7
|
|
|
|
|
66
|
push @target_stack, $target; |
263
|
7
|
|
|
|
|
18
|
for my $require ( @requires ) { |
264
|
7
|
|
|
|
|
51
|
$requires_modified = max $requires_modified, __SUB__->( $require ); |
265
|
|
|
|
|
|
|
} |
266
|
7
|
|
|
|
|
1166
|
pop @target_stack; |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Do we need to build this recipe? |
270
|
13
|
100
|
100
|
|
|
75
|
if ( $requires_modified > ( $recipe->last_modified || -1 ) ) { |
271
|
9
|
|
|
|
|
544
|
$LOG->debug( "Building $target" ); |
272
|
9
|
|
|
|
|
121
|
$recipe->make( %vars ); |
273
|
9
|
|
|
|
|
161
|
$LOG->info( "$target updated" ); |
274
|
|
|
|
|
|
|
} |
275
|
|
|
|
|
|
|
else { |
276
|
4
|
|
|
|
|
982
|
$LOG->info( "$target up-to-date" ); |
277
|
|
|
|
|
|
|
} |
278
|
13
|
|
|
|
|
230
|
return $recipe->last_modified; |
279
|
7
|
|
|
|
|
77
|
}; |
280
|
7
|
|
|
|
|
29
|
$build->( $_ ) for @targets; |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# Resolve any references via Beam::Wire container lookups |
284
|
115
|
|
|
115
|
|
145
|
sub _resolve_ref( $self, $conf ) { |
|
115
|
|
|
|
|
157
|
|
|
115
|
|
|
|
|
159
|
|
|
115
|
|
|
|
|
128
|
|
285
|
115
|
100
|
66
|
|
|
455
|
return $conf if !ref $conf || blessed $conf; |
286
|
66
|
100
|
|
|
|
224
|
if ( ref $conf eq 'HASH' ) { |
|
|
50
|
|
|
|
|
|
287
|
41
|
100
|
|
|
|
121
|
if ( grep { $_ !~ /^\$/ } keys %$conf ) { |
|
69
|
|
|
|
|
214
|
|
288
|
34
|
|
|
|
|
49
|
my %resolved; |
289
|
34
|
|
|
|
|
88
|
for my $key ( keys %$conf ) { |
290
|
62
|
|
|
|
|
12681
|
$resolved{ $key } = $self->_resolve_ref( $conf->{ $key } ); |
291
|
|
|
|
|
|
|
} |
292
|
34
|
|
|
|
|
233
|
return \%resolved; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
else { |
295
|
|
|
|
|
|
|
# All keys begin with '$', so this must be a reference |
296
|
|
|
|
|
|
|
# XXX: We should add the 'file:path' syntax to |
297
|
|
|
|
|
|
|
# Beam::Wire directly. We could even call it as a class |
298
|
|
|
|
|
|
|
# method! We should also move BEAM_PATH resolution to |
299
|
|
|
|
|
|
|
# Beam::Wire directly... |
300
|
|
|
|
|
|
|
# A single Beam::Wire->resolve( $conf ) should recursively |
301
|
|
|
|
|
|
|
# resolve the refs in a hash (like this entire subroutine |
302
|
|
|
|
|
|
|
# does), but also allow defining inline objects (with |
303
|
|
|
|
|
|
|
# $class) |
304
|
7
|
|
|
|
|
40
|
my ( $file, $service ) = split /:/, $conf->{ '$ref' }, 2; |
305
|
7
|
|
|
|
|
34
|
my $wire = $self->_wire->{ $file }; |
306
|
7
|
100
|
|
|
|
18
|
if ( !$wire ) { |
307
|
1
|
|
|
|
|
5
|
for my $path ( split /:/, $ENV{BEAM_PATH} ) { |
308
|
1
|
50
|
|
|
|
43
|
next unless -e join '/', $path, $file; |
309
|
1
|
|
|
|
|
17
|
$wire = $self->_wire->{ $file } = Beam::Wire->new( file => join '/', $path, $file ); |
310
|
|
|
|
|
|
|
} |
311
|
|
|
|
|
|
|
} |
312
|
7
|
|
|
|
|
36862
|
return $wire->get( $service ); |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
elsif ( ref $conf eq 'ARRAY' ) { |
316
|
25
|
|
|
|
|
39
|
my @resolved; |
317
|
25
|
|
|
|
|
74
|
for my $i ( 0..$#$conf ) { |
318
|
40
|
|
|
|
|
124
|
$resolved[$i] = $self->_resolve_ref( $conf->[$i] ); |
319
|
|
|
|
|
|
|
} |
320
|
25
|
|
|
|
|
80
|
return \@resolved; |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
1; |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
__END__ |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=pod |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=head1 NAME |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Beam::Make - Recipes to declare and resolve dependencies between things |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
=head1 VERSION |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
version 0.001 |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=head1 SYNOPSIS |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
### container.yml |
341
|
|
|
|
|
|
|
# This Beam::Wire container stores shared objects for our recipes |
342
|
|
|
|
|
|
|
dbh: |
343
|
|
|
|
|
|
|
$class: DBI |
344
|
|
|
|
|
|
|
$method: connect |
345
|
|
|
|
|
|
|
$args: |
346
|
|
|
|
|
|
|
- dbi:SQLite:RECENT.db |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
### Beamfile |
349
|
|
|
|
|
|
|
# This file contains our recipes |
350
|
|
|
|
|
|
|
# Download a list of recent changes to CPAN |
351
|
|
|
|
|
|
|
RECENT-6h.json: |
352
|
|
|
|
|
|
|
commands: |
353
|
|
|
|
|
|
|
- curl -O https://www.cpan.org/RECENT-6h.json |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# Parse that JSON file into a CSV using an external program |
356
|
|
|
|
|
|
|
RECENT-6h.csv: |
357
|
|
|
|
|
|
|
requires: |
358
|
|
|
|
|
|
|
- RECENT-6h.json |
359
|
|
|
|
|
|
|
commands: |
360
|
|
|
|
|
|
|
- yfrom json RECENT-6h.json | yq '.recent.[]' | yto csv > RECENT-6h.csv |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# Build a SQLite database to hold the recent data |
363
|
|
|
|
|
|
|
RECENT.db: |
364
|
|
|
|
|
|
|
$class: Beam::Make::DBI::Schema |
365
|
|
|
|
|
|
|
dbh: { $ref: 'container.yml:dbh' } |
366
|
|
|
|
|
|
|
schema: |
367
|
|
|
|
|
|
|
- table: recent |
368
|
|
|
|
|
|
|
columns: |
369
|
|
|
|
|
|
|
- path: VARCHAR(255) |
370
|
|
|
|
|
|
|
- epoch: DOUBLE |
371
|
|
|
|
|
|
|
- type: VARCHAR(10) |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# Load the recent data CSV into the SQLite database |
374
|
|
|
|
|
|
|
cpan-recent: |
375
|
|
|
|
|
|
|
$class: Beam::Make::DBI::CSV |
376
|
|
|
|
|
|
|
requires: |
377
|
|
|
|
|
|
|
- RECENT.db |
378
|
|
|
|
|
|
|
- RECENT-6h.csv |
379
|
|
|
|
|
|
|
dbh: { $ref: 'container.yml:dbh' } |
380
|
|
|
|
|
|
|
table: recent |
381
|
|
|
|
|
|
|
file: RECENT-6h.csv |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
### Load the recent data into our database |
384
|
|
|
|
|
|
|
$ beam make cpan-recent |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=head1 DESCRIPTION |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
C<Beam::Make> allows an author to describe how to build some thing (a |
389
|
|
|
|
|
|
|
file, some data in a database, an image, a container, etc...) and the |
390
|
|
|
|
|
|
|
relationships between things. This is similar to the classic C<make> |
391
|
|
|
|
|
|
|
program used to build some software packages. |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
Each thing is a C<recipe> and can depend on other recipes. A user runs |
394
|
|
|
|
|
|
|
the C<beam make> command to build the recipes they want, and |
395
|
|
|
|
|
|
|
C<Beam::Make> ensures that the recipe's dependencies are satisfied |
396
|
|
|
|
|
|
|
before building the recipe. |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
This class is a L<Beam::Runnable> object and can be embedded in other |
399
|
|
|
|
|
|
|
L<Beam::Wire> containers. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=head2 Recipe Classes |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
Unlike C<make>, C<Beam::Make> recipes can do more than just execute |
404
|
|
|
|
|
|
|
a series of shell scripts. Each recipe is a Perl class that describes |
405
|
|
|
|
|
|
|
how to build the desired thing and how to determine if that thing needs |
406
|
|
|
|
|
|
|
to be rebuilt. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
These recipe classes come with C<Beam::Make>: |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=over |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
=item * L<File|Beam::Make::File> - The default recipe class that creates |
413
|
|
|
|
|
|
|
a file using one or more shell commands (a la C<make>) |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=item * L<DBI|Beam::Make::DBI> - Write data to a database |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=item * L<DBI::Schema|Beam::Make::DBI::Schema> - Create a database |
418
|
|
|
|
|
|
|
schema |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=item * L<DBI::CSV|Beam::Make::DBI::CSV> - Load data from a CSV into |
421
|
|
|
|
|
|
|
a database table |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=back |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
Future recipe class ideas are: |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=over |
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
=item * |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
B<Template rendering>: Files could be generated from a configuration |
432
|
|
|
|
|
|
|
file or database and a template. |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=item * |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
B<Docker image, container, compose>: A Docker container could depend on |
437
|
|
|
|
|
|
|
a Docker image. When the image is updated, the container would get |
438
|
|
|
|
|
|
|
rebuilt and restarted. The Docker image could depend on a directory and |
439
|
|
|
|
|
|
|
get rebuilt if the directory or its Dockerfile changes. |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=item * |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
B<System services (init daemon, systemd service, etc...)>: Services |
444
|
|
|
|
|
|
|
could depend on their configuration files (built with a template) and be |
445
|
|
|
|
|
|
|
restarted when their configuration file is updated. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=back |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=head2 Beamfile |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
The C<Beamfile> defines the recipes. To avoid the pitfalls of C<Makefile>, this is |
452
|
|
|
|
|
|
|
a YAML file containing a mapping of recipe names to recipe configuration. Each |
453
|
|
|
|
|
|
|
recipe configuration is a mapping containing the attributes for the recipe class. |
454
|
|
|
|
|
|
|
The C<$class> special configuration key declares the recipe class to use. If no |
455
|
|
|
|
|
|
|
C<$class> is specified, the default L<Beam::Wire::File> recipe class is used. |
456
|
|
|
|
|
|
|
All recipe classes inherit from L<Beam::Class::Recipe> and have the L<name|Beam::Class::Recipe/name> |
457
|
|
|
|
|
|
|
and L<requires|Beam::Class::Recipe/requires> attributes. |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
For examples, see the L<Beam::Wire examples directory on |
460
|
|
|
|
|
|
|
Github|https://github.com/preaction/Beam-Make/tree/master/eg>. |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=head2 Object Containers |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
For additional configuration, create a L<Beam::Wire> container and |
465
|
|
|
|
|
|
|
reference the objects inside using C<< $ref: "<container>:<service>" >> |
466
|
|
|
|
|
|
|
as the value for a recipe attribute. |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=head1 TODO |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=over |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=item Target names in C<Beamfile> should be regular expressions |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
This would work like Make's wildcard recipes, but with Perl regexp. The |
475
|
|
|
|
|
|
|
recipe object's name is the real name, but the recipe chosen is the one |
476
|
|
|
|
|
|
|
the matches the regexp. |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=item Environment variables should interpolate into all attributes |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
Right now, the C<< NAME=VALUE >> arguments to C<beam make> only work in |
481
|
|
|
|
|
|
|
recipes that use shell scripts (like L<Beam::Make::File>). It would be |
482
|
|
|
|
|
|
|
nice if they were also interpolated into other recipe attributes. |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=item Recipes should be able to require wildcards and directories |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
Recipe requirements should be able to depend on patterns, like all |
487
|
|
|
|
|
|
|
C<*.conf> files in a directory. It should also be able to depend on |
488
|
|
|
|
|
|
|
a directory, which would be the same as depending on every file, |
489
|
|
|
|
|
|
|
recursively, in that directory. |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
This would allow rebuilding a ZIP file when something changes, or |
492
|
|
|
|
|
|
|
rebuilding a Docker image when needed. |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
=item Beam::Wire should support the <container>:<service> syntax |
495
|
|
|
|
|
|
|
for references |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
The L<Beam::Wire> class should handle the C<BEAM_PATH> environment |
498
|
|
|
|
|
|
|
variable directly and be able to resolve services from other files |
499
|
|
|
|
|
|
|
without building another C<Beam::Wire> object in the container. |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
=item Beam::Wire should support resolving objects in arbitrary data |
502
|
|
|
|
|
|
|
structures |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
L<Beam::Wire> should have a class method that one can pass in a hash and |
505
|
|
|
|
|
|
|
get back a hash with any C<Beam::Wire> object references resolved, |
506
|
|
|
|
|
|
|
including C<$ref> or C<$class> object. |
507
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
=back |
509
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=head1 SEE ALSO |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
L<Beam::Wire> |
513
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
=head1 AUTHOR |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
Doug Bell <preaction@cpan.org> |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
This software is copyright (c) 2020 by Doug Bell. |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
523
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
=cut |