File Coverage

blib/lib/App/makedpkg.pm
Criterion Covered Total %
statement 39 160 24.3
branch 4 76 5.2
condition 3 25 12.0
subroutine 12 21 57.1
pod 3 10 30.0
total 61 292 20.8


line stmt bran cond sub pod time code
1             package App::makedpkg;
2             #ABSTRACT: Facilitate building Debian packages with templates
3             our $VERSION = '0.05'; #VERSION
4 3     3   71840 use strict;
  3         7  
  3         100  
5 3     3   37 use v5.10.0;
  3         9  
  3         126  
6              
7 3     3   16 use base qw(App::Cmd::Simple);
  3         6  
  3         3217  
8            
9 3     3   180307 use File::Path qw(make_path remove_tree);
  3         10  
  3         312  
10 3     3   21 use File::Basename;
  3         6  
  3         289  
11 3     3   3460 use File::Copy ();
  3         7641  
  3         65  
12 3     3   2878 use Text::Template qw(fill_in_file);
  3         9672  
  3         188  
13 3     3   2568 use Config::Any;
  3         3615  
  3         83  
14 3     3   2666 use File::ShareDir qw(dist_dir);
  3         20759  
  3         6425  
15              
16             our $dist_dir = dist_dir('App-makedpkg');
17              
18             sub opt_spec {
19             return (
20 13     13 1 142874 [ "config|c=s", "configuration file" ],
21             [ "verbose|v", "verbose output" ],
22             [ "templates|t=s", "template directory" ],
23             [ "dry|n", "don't build, just show" ],
24             [ "prepare|p", "prepare build" ],
25             [ "force|f", "use the force, Luke!" ],
26             [ "init", "initialize template directory makedpkg/" ],
27             );
28             }
29              
30             sub validate_args {
31 13     13 1 23587 my ($self, $opt, $args) = @_;
32            
33 13         50 $self->{config} = $self->read_config($opt->config);
34              
35 0 0       0 if (!defined $opt->templates) {
36 0 0       0 if (-d 'makedpkg') {
37 0         0 $opt->{templates} = 'makedpkg';
38             } else {
39 0         0 $opt->{templates} = $dist_dir;
40             }
41             }
42              
43 0 0       0 unless ( -d $opt->templates ) {
44 0         0 die "error reading template directory ".$opt->templates."\n";
45             }
46             }
47              
48             sub read_config {
49 13     13 0 153 my ($self, $file) = @_;
50              
51 13         24 my $config = eval {
52 13 100       45 if (defined $file) {
53 3         32 Config::Any->load_files({ files => [$file], use_ext => 1, flatten_to_hash => 1 });
54             } else {
55 10         127 Config::Any->load_stems({ stems => ['makedpkg'], use_ext => 1, flatten_to_hash => 1 });
56             }
57             };
58              
59 13 50 66     163139 if ($config && keys %$config) {
60 0         0 ($file) = keys %$config;
61 0         0 ($config) = values %$config;
62             } else {
63 13         32 $config = undef;
64             }
65              
66 13 50 50     117 if ( ref ($config // '') ne 'HASH' ) {
67 13         425 die "error reading config file $file\n";
68             }
69              
70 0           return $config;
71             }
72              
73             sub expand_command {
74 0     0 0   my ($cmd, $out) = @_;
75              
76             # use IPC::Open3;
77             # use File::Spec;
78             # use Symbol qw(gensym);
79             # open(NULL, ">", File::Spec->devnull);
80             # my $pid = open3(gensym, \*PH, ">&NULL", $cmd);
81             # while( ) { $out .= $_ }
82             # waitpid($pid, 0);
83              
84 0           $out = `$cmd`;
85 0 0         die "`$cmd` died with exit code ".($?>>8)."\n" if $?;
86 0           chomp $out;
87              
88 0           return $out;
89             }
90              
91             sub expand_config {
92 0     0 0   my $h = $_[0];
93 0 0 0       return if (ref $h || "") ne 'HASH';
94 0           foreach my $key (keys %$h) {
95 0           my $v = $h->{$key};
96 0 0 0       if ( !ref $v and $v =~ /^`(.+)`$/ ) {
97 0           $h->{$key} = expand_command($1);
98             } else {
99 0           expand_config($v);
100             }
101             }
102             }
103              
104             sub list_dir {
105 0     0 0   my ($dir) = @_;
106 0 0         opendir(my $dh, $dir) or die "failed to open $dir: $!\n";
107 0           my @files = map {
108 0           my $f = $_;
109 0           -d "$dir/$_" ?
110 0 0         map { "$f/$_" } @{ list_dir("$dir/$_") }
  0            
111             : $_;
112 0           } grep { /^[^.]+/ } readdir($dh);
113 0           closedir $dh;
114 0           return \@files;
115             }
116              
117             sub execute {
118 0     0 1   my ($self, $opt, $args) = @_;
119              
120 0           expand_config($self->{config});
121 0 0 0       $self->{config}->{verbose} ||= $opt->verbose ? 1 : 0;
122              
123 0 0         if ($opt->verbose) {
124 0           $self->_dump( $self->{config} );
125             }
126              
127 0 0         if ($opt->init) {
128 0           return $self->init_templates($opt, $args);
129             }
130              
131 0           $self->prepare_debuild($opt, $args);
132              
133 0           $self->exec_debuild($opt, $args);
134             }
135              
136             sub prepare_debuild {
137 0     0 0   my ($self, $opt, $args) = @_;
138              
139 0   0       $self->{config}{build} //= { };
140 0   0       $self->{config}{build}{directory} //= 'debuild';
141              
142 0           my $dir = $self->{config}{build}{directory};
143 0 0         say "building into $dir" if $opt->verbose;
144 0 0         return if $opt->dry;
145              
146 0           remove_tree($dir);
147 0           make_path("$dir/debian");
148              
149 0           my $conf = $self->{config};
150 0           my $build_dir = $conf->{build}{directory};
151              
152             # copy and fill in template files
153 0           my $template_dir = $opt->templates;
154 0           my $template_files = list_dir($template_dir);
155              
156             # say "templates in $template_dir\n";
157 0           foreach my $file (sort @$template_files) {
158 0           my $template = $opt->templates."/$file";
159 0 0         next unless -f $template;
160              
161 0           $self->_create_debian_file(
162             $opt, $file,
163             fill_in_file($template, HASH => $conf)
164             );
165             }
166              
167             # execute commands before build
168 0 0         foreach (@{ $self->{config}{build}{before} || [ ] }) {
  0            
169 0 0         say "before: $_" if $opt->verbose;
170 0           `$_`;
171 0 0         die "failed to run $_\n" if $?;
172             }
173              
174 0 0         if (my $files = $self->{config}{build}{files}) {
175 0           my @install;
176            
177 0 0         foreach my $source (sort keys %{ $files->{copy} || { } }) {
  0            
178 0 0         if ($source =~ qr{^(.*)/\*$}) {
179 0           make_path(my $path = "$build_dir/$1");
180 0           `cp -r $source $path`;
181             } else {
182 0 0         make_path($1) if $source =~ qr{^(.*)/[^/]+$};
183 0           `cp -r $source $build_dir/$source`;
184             }
185 0 0         die "failed to copy $source\n" if $?;
186            
187 0           push @install, "$source " . $files->{copy}->{$source};
188             }
189              
190 0 0 0       if ($files->{to} and $files->{from}) {
191 0           foreach my $from (@{ $files->{from} }) {
  0            
192 0 0         if ($from =~ qr{^(.*)/[^/]+$}) {
193 0           make_path("$build_dir/$1");
194             }
195 0           `cp -r $from $build_dir/$from`;
196              
197 0           my $target = $from;
198 0           $target =~ s{/?[^/]+$}{};
199 0 0         $target = "/$target" if $target ne '';
200 0           push @install, "$from ".$files->{to}.$target;
201             }
202             }
203              
204 0 0         unless ( grep { $_ eq 'install' } @$template_files ) {
  0            
205 0           $self->_create_debian_file( $opt,
206             'install', join("\n", @install, '') );
207             }
208             }
209             }
210              
211             sub _create_debian_file {
212 0     0     my ($self, $opt, $name, $contents) = @_;
213              
214 0           my $filename = $self->{config}{build}{directory} . "/debian/$name";
215 0           make_path(dirname($filename));
216              
217 0           open my $fh, ">", $filename;
218 0           print $fh $contents;
219 0           close $fh;
220              
221 0 0         say $filename if $opt->verbose;
222             }
223              
224             sub exec_debuild {
225 0     0 0   my ($self, $opt, $args) = @_;
226              
227 0 0         return if $opt->prepare;
228            
229 0   0       my $command = $self->{config}{build}{command} || 'debuild';
230              
231 0 0         if ($opt->dry) {
232 0           say "exec $command";
233             } else {
234 0           chdir $self->{config}{build}{directory};
235 0           exec $command;
236             }
237             }
238              
239             sub init_templates {
240 0     0 0   my ($self, $opt) = @_;
241              
242 0           my $template_dir = $opt->templates;
243 0 0         $template_dir = 'makedpkg' if $template_dir eq $dist_dir;
244 0 0         make_path($template_dir) unless $opt->dry;
245              
246 0           my $templates = list_dir($dist_dir);
247 0           foreach my $file (sort @$templates) {
248 0 0 0       if (-e "$template_dir/$file" and !$opt->force) {
249 0           say "kept $template_dir/$file";
250             } else {
251 0           say "created $template_dir/$file";
252 0 0         unless ($opt->dry) {
253 0 0         if ($file =~ /\//) {
254 0           make_path(dirname("$template_dir/$file"));
255             }
256 0           File::Copy::copy("$dist_dir/$file", "$template_dir/$file");
257             }
258             }
259             }
260              
261 0           return;
262             }
263              
264             sub _dump {
265 0     0     my ($self, $data) = @_;
266             # Config::Any requires any of 'YAML::XS', 'YAML::Syck', or 'YAML'
267 0           for my $pkg (qw(YAML::XS YAML::Syck YAML)) {
268 0           eval "require $pkg";
269 0 0         unless ( $@ ) {
270 0           my $dump = eval "${pkg}::Dump(\$data);";
271 0           $dump =~ s/\n$//m;
272 0           say "$dump\n---";
273 0           return;
274             }
275             }
276             }
277              
278             1;
279              
280             __END__