| 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__ |