File Coverage

blib/lib/RPM/Make/DWIW.pm
Criterion Covered Total %
statement 128 160 80.0
branch 30 62 48.3
condition 2 3 66.6
subroutine 23 28 82.1
pod 3 25 12.0
total 186 278 66.9


line stmt bran cond sub pod time code
1             # $Header: /usr/local/cvsroot/apb/lib/RPM-Make-DWIW/lib/RPM/Make/DWIW.pm,v 1.4 2010-03-08 06:10:49 asher Exp $
2              
3             package RPM::Make::DWIW;
4 3     3   101924 use strict;
  3         9  
  3         158  
5 3     3   17 no warnings 'uninitialized';
  3         6  
  3         118  
6              
7 3     3   16 use vars qw( $VERSION );
  3         10  
  3         12691  
8             $VERSION = '0.2';
9              
10             my $FINAL_RPM_PATH;
11             my $TOP;
12              
13             ## validation: key => type, mandatory
14              
15             my $TOP_VAL = {
16             tags => [ {}, 1 ],
17             description => [ '', 1 ],
18             items => [ [], 1],
19             requirements => [ [], 0],
20             pre => [ '', 0 ],
21             post => [ '', 0 ],
22             preun => [ '', 0 ],
23             postun => [ '', 0 ],
24             cleanup => [ '', 0 ],
25             };
26              
27             my $TAGS_VAL = {
28             Summary => [ '', 1 ],
29             Name => [ '', 1 ],
30             Version => [ '', 1 ],
31             Release => [ '', 1 ],
32             License => [ '', 1 ],
33             Group => [ '', 1 ],
34             Source => [ '', 0 ],
35             URL => [ '', 0 ],
36             Distribution => [ '', 0 ],
37             Vendor => [ '', 0 ],
38             Packager => [ '', 0 ],
39             };
40              
41             my $ITEM_VAL = {
42             type => [ '', 1 ],
43             dest => [ '', 1 ],
44             src => [ '', 0 ],
45             mode => [ '', 1 ],
46             owner => [ '', 1 ],
47             group => [ '', 1 ],
48             defaults => [ '', 0 ],
49             config_p => [ '', 0 ],
50             };
51              
52             ## example spec
53              
54             my $X = {
55             tags => {
56             Summary => 'A CD player app that rocks!',
57             Name => 'cdplayer',
58             Version => '1.2',
59             Release => '3',
60             License => 'GPL',
61             Group => 'Applications/Sound',
62             #Source => 'ftp://ftp.gnomovision.com/pub/cdplayer/cdplayer-1.0.tgz',
63             #URL => 'http://www.gnomovision.com/cdplayer/cdplayer.html',
64             #Distribution => 'WSS Linux',
65             #Vendor => 'White Socks Software, Inc.',
66             #Packager => 'Santa Claus ',
67             },
68             description => 'abc def ghi',
69             items => [
70             {
71             defaults => 1,
72             owner => 'root',
73             group => 'admin',
74             mode => '0644',
75             },
76             {
77             src => 'abc.txt',
78             dest => '/usr/bin/abc.txt',
79             mode => '0755',
80             owner => 'cdplayer',
81             group => 'admin',
82             },
83             {
84             src => 'def.txt',
85             dest => '/usr/lib/def.txt',
86             },
87             {
88             src => 'def.txt',
89             dest => '/etc/cdplayer.conf',
90             config_p => 1,
91             },
92             {
93             dest => '/tmp/acme6',
94             type => 'dir',
95             mode => '0777',
96             },
97             ],
98             requirements => [
99             {
100             name => 'libxml2',
101             min_version => '2.6.0',
102             }
103             ],
104             post => '/sbin/ldconfig',
105             cleanup => 0,
106             };
107              
108             ## mkdir or die
109              
110             sub xmkdir {
111 13     13 0 64 my $dir = shift;
112 13 50       1903 mkdir($dir) or die "Can't mkdir $dir: $!";
113             }
114              
115             sub mk_dirs {
116 2     2 0 31 $TOP = "topdir-$$";
117 2         18558 system("rm -rf $TOP"); # just in case it exists
118 2         139 xmkdir($TOP);
119 2         29 xmkdir("$TOP/RPMS"); # where the rpm will end up
120 2         25 xmkdir("$TOP/BUILD"); # ??
121 2         89 xmkdir("$TOP/root"); # where rpmbuild will take files from
122             }
123              
124             sub rm_dirs {
125 0 0   0 0 0 die "top not defined" unless $TOP;
126 0         0 system("rm -rf $TOP");
127             }
128              
129             ## generate RPM spec file as string
130              
131             sub mk_spec {
132 2     2 0 7 my $x = shift;
133 2         832 my $t = scalar localtime;
134 2         37 my $res = "## autogenerated by $0 - $t\n\n";
135 2         23 my $tags = $x->{ tags };
136 2         73 foreach my $key(sort keys %$tags) {
137 17         95 $res .= "$key: $tags->{ $key }\n";
138             }
139              
140 2         25 $res .= "\n%description\n$x->{ description }\n\n";
141              
142 2         14 foreach my $dep(@{ $x->{ requirements } }) {
  2         14  
143 3 50       38 my $mv = defined $dep->{ min_ver } ? " >= $dep->{ min_ver }" : '';
144 3         27 $res .= "requires: $dep->{ name }$mv\n";
145             }
146              
147 2         7 $res .= "\n%files\n";
148              
149 2         79 my $items = get_items($x);
150 2         16 foreach my $item(@$items) {
151 6         24 $res .= mk_spec_file_line($item) . "\n";
152             }
153              
154 2         12 foreach my $section(qw( pre post preun postun )) {
155 8 100       42 $res .= "\n\n%$section\n$x->{ $section }\n\n" if $x->{ $section };
156             }
157 2         27 $res;
158             }
159              
160             ## given file (or dir) hashref, return specfile line
161              
162             sub mk_spec_file_line {
163 6     6 0 23 my $file = shift;
164 6         23 foreach my $k(qw( mode owner group dest )) {
165 24 50       366 die "Missing key: $k in item" unless defined $file->{ $k };
166             }
167 6         57 my $line = "%attr($file->{ mode } $file->{ owner } $file->{ group }) $file->{ dest }";
168 6 50       34 $line = "%config $line" if $file->{ config_p };
169 6         54 $line;
170             }
171              
172             ## given spec hashref, write specfile
173              
174             sub write_spec {
175 2     2 0 25 my $x = shift;
176 2         34 spew("$TOP/specfile", mk_spec($x));
177             }
178              
179             ## cp src file to dest or die; create dirs as needed
180              
181             sub cpx {
182 4     4 0 68 my($src, $dest, $mode) = @_;
183 4 50       161 die "Invalid mode '$mode'" unless $mode =~ /^\d{4}$/;
184 4 50       546 die "Not found: $src" unless -e $src;
185 4         2101 my @parts = split /\//, $dest;
186 4         20 pop @parts;
187 4         13 my @p2;
188 4         25 while(@parts) {
189 15         44 push @p2, shift @parts;
190 15         6981 my $dir = join('/', @p2);
191 15 100       565 unless(-e $dir) {
192 5         30 xmkdir($dir);
193             }
194             }
195 4 50       49213 system('/bin/cp', $src, $dest) && die "Failed to cp '$src' to '$dest'";
196             #system('/bin/chmod', $mode, $dest) && die "Failed to chmod '$dest'";
197             }
198            
199             ## given spec hashref, cp necessary files into tmp tree
200              
201             sub cp_files {
202 2     2 0 12 my $x = shift;
203 2         11 my $files = get_files($x);
204 2         18 foreach my $file(@$files) {
205 4 50       639 $file->{ dest } =~ m|^/| or die "Dest path must start with /";
206 4         73 cpx($file->{ src }, "$TOP/root$file->{ dest }", $file->{ mode });
207             }
208             }
209              
210             ## mk dirs explicitly requested
211             ## wait, is this any good? rpm copy dirs?
212              
213             sub mk_specified_dirs {
214 2     2 0 18 my $x = shift;
215 2         37 my $dirs = get_dirs($x);
216 2         21 foreach my $dir(@$dirs) {
217 2 50       11760 system("mkdir -p -m $dir->{ mode } $TOP/root$dir->{ dest }") && die "Failed to mkdir '$dir->{ dest }'";
218             }
219             }
220              
221             #rpmbuild -bb --root `pwd`/root --define "_topdir /space/asher/sand/rpm/cdplayer-example/topdir" specfile2
222              
223             ## create rpm or die
224              
225             sub xmk_rpm {
226 2     2 0 8666 chomp (my $here = `pwd`);
227 2         12211 my $rc = system(
228             qq[rpmbuild -bb --root $here/$TOP/root --define "_topdir $here/$TOP" $TOP/specfile > $TOP/rpm.out 2>&1]);
229 2 50       98 if($rc) {
230 2         386 print STDERR "Error: see $TOP/rpm.out\n";
231 2         766 exit -1;
232             }
233             }
234              
235             ## given x and RPM, check that RPM has the right files or die
236              
237             sub verify_rpm {
238 0     0 0 0 my($x, $rpm) = @_;
239 0         0 my $items = get_items($x);
240 0         0 my $want_files = join(' ', sort map { $_->{ dest } } @$items );
  0         0  
241 0         0 my $cmd = "rpm -q -p --filesbypkg $rpm";
242 0         0 chomp(my @res = `$cmd`);
243 0 0       0 my $have_files = join(' ', sort map { /\S+\s+(\S+)/ } @res)
  0         0  
244             or die "No files found with '$cmd'";
245 0 0       0 if($want_files ne $have_files) {
246 0         0 print STDERR "RPM $rpm does not have expected files:\nWant: $want_files\n\nHave: $have_files\n\n$cmd\n";
247 0         0 exit -1;
248             }
249             }
250              
251             sub get_rpm_path {
252 0     0 0 0 chomp(my @res = `find $TOP/RPMS -type f`);
253 0 0       0 die "RPM not found" unless @res; ## should never happen
254 0 0       0 die "more than 1 rpm found" if @res > 1;
255 0         0 $res[0];
256             }
257              
258             ## copy the new rpm up to this level or die
259              
260             sub xcp_rpm_here {
261 0     0 0 0 my $rpm_path = shift;
262 0 0       0 $rpm_path =~ m|([^/]+)$| or die "Invalid rpm_path: '$rpm_path'";
263 0         0 $FINAL_RPM_PATH = $1;
264 0 0       0 system("cp $rpm_path .") && exit -1;
265             }
266              
267             sub spew {
268 2     2 0 9 my($fn, $page) = @_;
269 2 50       405 open F, ">$fn" or die "Can't open $fn: $!";
270 2         40 print F $page;
271 2         149 close F;
272             }
273              
274             ## get files/dirs/all items, excluding defaults blocks
275              
276             sub get_files {
277 2     2 0 6 my $x = shift;
278 2 100       7 [ grep { $_->{ type } eq 'file' && !$_->{ defaults } } @{ $x->{ items } } ];
  9         115  
  2         16  
279             }
280              
281             sub get_dirs {
282 2     2 0 12 my $x = shift;
283 2 100       12 [ grep { $_->{ type } eq 'dir' && !$_->{ defaults } } @{ $x->{ items } } ];
  9         83  
  2         39  
284             }
285              
286             sub get_items {
287 4     4 0 12 my $x = shift;
288 4         546 [ grep { !$_->{ defaults } } @{ $x->{ items } } ];
  18         831  
  4         98  
289             }
290              
291             ## return error msg or '' if valid
292              
293             sub validate_hashref {
294 10     10 0 13 my($val, $x) = @_;
295 10         12 my @err;
296              
297 10         28 foreach my $key(keys %$x) {
298 67 50       131 if(!$val->{ $key }) {
299 0         0 push @err, "Unknown key: $key";
300             }
301 67         99 my $r0 = ref $val->{ $key }[0];
302 67         92 my $r1 = ref $x->{ $key };
303 67 50       125 if($r0 ne $r1) {
304 0         0 push @err, "Wrong type: $key (got '$r1', expected '$r0)";
305             }
306             }
307 10         34 foreach my $key(keys %$val) {
308 88 50 66     270 if($val->{ $key }[1] && !$x->{ $key }) { ## mand && missing
309 0         0 push @err, "Missing key: $key";
310             }
311             }
312 10         44 join('; ', @err);
313             }
314              
315             ## validate or die with msg
316              
317             sub xvalidate_hashref {
318 10     10 0 18 my($val, $x, $name) = @_;
319 10 50       19 my $err = validate_hashref($val, $x) or return;
320 0         0 print STDERR "Error in $name: $err\n";
321 0         0 exit -1;
322             }
323            
324             sub validate_spec {
325 2     2 0 5 my $spec = shift;
326 2         9 xvalidate_hashref($TOP_VAL, $spec, 'top level');
327 2         8 xvalidate_hashref($TAGS_VAL, $spec->{ tags }, 'tags');
328 2         3 my $n = 0;
329 2         8 my $items = get_items($spec);
330 2         8 foreach my $item(@$items) {
331 6         16 xvalidate_hashref($ITEM_VAL, $item, "item $n");
332 6         9 $n ++;
333             }
334 2         4 1;
335             }
336              
337             ## add default vals to any items that lack them
338             ## modifies spec
339              
340             sub apply_defaults {
341 2     2 0 5 my($x) = @_;
342 2         8 my %d = ( type => 'file' );
343 2         5 foreach my $item(@{ $x->{ items } }) {
  2         6  
344 9 100       20 if($item->{ defaults }) { # it is a defaults block; modify our defaults
345 3         14 while(my($k, $v) = each %$item) {
346 12 100       35 next if $k eq 'defaults';
347 9         26 $d{ $k } = $v;
348             }
349             }
350             else { # apply defaults to this item
351 6         17 while(my($k, $v) = each %d) {
352 24 100       88 $item->{ $k } = $v unless defined $item->{ $k };
353             }
354             }
355             }
356             }
357              
358             sub apply_global_defaults {
359 2     2 0 4 my($x) = @_;
360 2 50       11 $x->{ cleanup } = 1 unless exists $x->{ cleanup };
361             }
362              
363             ## public
364              
365             sub get_rpm_filename {
366 0     0 1 0 $FINAL_RPM_PATH;
367             }
368              
369             ## public
370              
371             sub get_example_spec {
372 1     1 1 11 $X;
373             }
374              
375             ## public
376             ## pass me a spec hashref
377              
378             sub write_rpm {
379 2     2 1 84 my($spec) = shift;
380 2         10 apply_global_defaults($spec);
381 2         766 apply_defaults($spec);
382 2         8 validate_spec($spec);
383 2         8 mk_dirs();
384 2         127 write_spec($spec);
385 2         34 cp_files($spec);
386 2         62 mk_specified_dirs($spec);
387 2         50 xmk_rpm();
388 0           my $rpm_path = get_rpm_path();
389 0           verify_rpm($spec, $rpm_path);
390 0           xcp_rpm_here($rpm_path);
391 0 0         rm_dirs() if $spec->{ cleanup };
392 0           1;
393             }
394              
395             1;
396             __END__