File Coverage

blib/lib/MDV/Repsys.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # $Id: Repsys.pm 103942 2007-01-03 23:40:24Z nanardon $
2              
3             package MDV::Repsys;
4              
5 1     1   1154 use strict;
  1         2  
  1         46  
6 1     1   7 use warnings;
  1         2  
  1         37  
7 1     1   6 use Carp;
  1         2  
  1         99  
8 1     1   12730 use SVN::Client;
  0            
  0            
9             use RPM4;
10             use POSIX qw(getcwd);
11              
12             our $VERSION = '1.00';
13              
14             my $error = undef;
15             my $verbosity = 0;
16              
17             =head1 NAME
18              
19             MDV::Repsys
20              
21             =head1 SYNOPSYS
22              
23             Module to build rpm from a svn
24              
25             =head1 FUNCTIONS
26              
27             =cut
28              
29             my %b_macros = (
30             '_sourcedir' => 'SOURCES',
31             '_patchdir' => 'SOURCES',
32             '_specdir' => 'SPECS',
33             );
34              
35             my %optional_macros = (
36             '_builddir' => 'BUILD',
37             '_rpmdir' => 'RPMS',
38             '_srcrpmdir' => 'SRPMS',
39             );
40              
41             =head2 set_verbosity($level)
42              
43             Set the verbosity verbosity of the module:
44              
45             0 silent
46             1 progress message
47             2 debug message
48              
49             =cut
50              
51             sub set_verbosity {
52             my ($level) = @_;
53             $verbosity = $level || 0;
54             }
55              
56             sub _print_msg {
57             my ($level, $fmt, @args) = @_;
58             croak('No message given to _print_msg') unless($fmt);
59             return if ($level > $verbosity);
60             printf("$fmt\n", @args);
61             }
62              
63             =head2 set_rpm_dirs($dir)
64              
65             Set internals rpm macros that are used by rpm building functions:
66              
67             _sourcedir to $dir/SOURCES
68             _patchdir to $dir/SOURCES
69             _specdir to $dir/SPECS
70              
71             And, if their directories are not writable, these macros are set:
72             _rpmdir to $dir/RPMS
73             _srcrpmdir to $dir/SRPMS
74             _builddir to $dir/BUILD
75             =cut
76              
77             sub set_rpm_dirs {
78             my ($dir, %relative_dir) = @_;
79             if ($dir !~ m:^/:) {
80             $dir = getcwd() . "/$dir";
81             }
82             foreach my $m (keys %b_macros, keys %relative_dir) {
83             RPM4::add_macro(
84             sprintf(
85             '%s %s/%s',
86             $m, $dir,
87             (defined($relative_dir{$m}) ? $relative_dir{$m} : $b_macros{$m}) || '',
88             ),
89             );
90             }
91             foreach my $m (keys %optional_macros) {
92             if (! -w RPM4::expand('%' . $m)) {
93             RPM4::add_macro(
94             sprintf(
95             '%s %s/%s',
96             $m, $dir,
97             (defined($relative_dir{$m}) ? $relative_dir{$m} : $optional_macros{$m}) || '',
98             ),
99             );
100             }
101             }
102              
103             }
104              
105             =head2 create_rpm_dirs
106              
107             Create directories used by rpm building functions:
108              
109             _sourcedir
110             _patchdir
111             _specdir
112              
113             Return 1 on sucess, 0 on failure.
114              
115             =cut
116              
117             sub create_rpm_dirs {
118             foreach my $m (keys %b_macros, keys %optional_macros) {
119             my $dtc = RPM4::expand('%' . $m); # dir to create
120             if (! -d $dtc) {
121             _print_msg(2, 'Create directory %s', $dtc);
122             if (!mkdir($dtc)) {
123             $error = "can't create $dtc: $!";
124             return 0;
125             }
126             }
127             }
128             1;
129             }
130              
131             =head2 extract_srpm($rpmfile, $directory)
132              
133             Extract (install) a source package into $directory.
134              
135             =cut
136              
137             sub extract_srpm {
138             my ($rpmfile, $working_dir, %releative_dir) = @_;
139              
140             set_rpm_dirs($working_dir, %releative_dir);
141             create_rpm_dirs() or return 0;
142             _print_msg(2, 'Extracting %s', $rpmfile);
143             RPM4::installsrpm($rpmfile);
144             }
145              
146             sub _find_unsync_source {
147             my (%options) = @_;
148            
149             my $svn = $options{svn} || SVN::Client->new();
150             my $working_dir = $options{working_dir};
151              
152             my $spec = RPM4::specnew($options{specfile}, undef, '/', undef, 1, 0) or do {
153             $error = "Can't read specfile";
154             return;
155             };
156              
157             my %sources;
158             my $abs_spec = $spec->specfile;
159             if ($abs_spec !~ m:^/:) {
160             $abs_spec = getcwd() . "/$abs_spec";
161             }
162             $sources{$abs_spec} = 1;
163             $sources{$_} = 1 foreach (map { RPM4::expand("\%_sourcedir/$_") } $spec->sources);
164             eval {
165             $sources{$_} = 1 foreach (map { RPM4::expand("\%_sourcedir/$_") } $spec->icon);
166             };
167              
168             my @needadd;
169             $svn->status(
170             $working_dir,
171             'HEAD',
172             sub {
173             my ($entry, $status) = @_;
174             if ($status->text_status eq '2') {
175             if (grep { $entry eq $_ } (RPM4::expand('%_specdir'), RPM4::expand('%_sourcedir'))) {
176             push(@needadd, $entry);
177             }
178             }
179             },
180             0,
181             1,
182             0,
183             1,
184             );
185              
186             foreach my $toadd (@needadd) {
187             _print_msg(1, "Adding %s", $toadd);
188             $svn->add($toadd, 0);
189             }
190             @needadd = ();
191             my @needdel;
192              
193             foreach my $dir (RPM4::expand('%_specdir'), RPM4::expand('%_sourcedir')) {
194             $svn->status(
195             $dir,
196             'HEAD',
197             sub {
198             my ($entry, $status) = @_;
199             grep { $entry eq $_ } (
200             RPM4::expand('%_specdir'),
201             RPM4::expand('%_sourcedir')
202             ) and return;
203              
204             if ($status->text_status eq '2') {
205             if ($sources{$entry}) {
206             push(@needadd, $entry);
207             }
208             }
209             if (grep { $status->text_status eq $_ } ('3', '4', '5')) {
210             if(!$sources{$entry}) {
211             push(@needdel, $entry);
212             }
213             }
214             },
215             1, # recursive
216             1, # get_all
217             0, # update
218             1, # no_ignore
219             );
220             }
221            
222             return(\@needadd, \@needdel);
223             }
224              
225             sub _sync_source {
226             my (%options) = @_;
227              
228             my $svn = $options{svn} || SVN::Client->new();
229             my ($needadd, $needdel) = ($options{needadd}, $options{needdel});
230            
231             foreach my $toadd (sort @{$needadd || []}) {
232             _print_msg(1, "Adding %s", $toadd);
233             $svn->add($toadd, 0);
234             }
235             foreach my $todel (sort @{$needdel || []}) {
236             _print_msg(1, "Removing %s", $todel);
237             $svn->delete($todel, 1);
238             }
239              
240             1;
241             }
242              
243             =head2 find_unsync_files($working_dir, $specfile)
244              
245             Return two array ref of lists of files that should be added or removed
246             from the svn working copy to be sync with the specfile.
247              
248             =cut
249              
250             sub find_unsync_files {
251             my ($working_dir, $specfile, %relative_dir) = @_;
252              
253             if ($working_dir !~ m:^/:) {
254             $working_dir = getcwd() . "/$working_dir";
255             }
256              
257             set_rpm_dirs($working_dir, %relative_dir);
258             _print_msg(2, 'Looking sources from specfile %s', $specfile);
259            
260             my $svn = SVN::Client->new();
261              
262             _find_unsync_source(
263             svn => $svn,
264             specfile => $specfile,
265             working_dir => $working_dir,
266             );
267             }
268              
269             =head2 sync_svn_copy($add, $remove)
270              
271             Perform add or remove of files listed in both array ref.
272              
273             =cut
274              
275             sub sync_svn_copy {
276             my ($needadd, $needdel) = @_;
277              
278             my $svn = SVN::Client->new();
279              
280             _sync_source(
281             svn => $svn,
282             needadd => $needadd,
283             needdel => $needdel,
284             );
285             }
286              
287             =head2 sync_source($workingdir, $specfile)
288              
289             Synchronize svn content by performing add/remove on file need to build
290             the package. $workingdir should a svn directory. No changes are applied
291             to the repository, you have to commit yourself after.
292              
293             Return 1 on success, 0 on error.
294              
295             =cut
296              
297             sub sync_source {
298             my ($working_dir, $specfile, %relative_dir) = @_;
299              
300             if ($working_dir !~ m:^/:) {
301             $working_dir = getcwd() . "/$working_dir";
302             }
303              
304             set_rpm_dirs($working_dir, %relative_dir);
305             _print_msg(2, 'Looking sources from specfile %s', $specfile);
306            
307             my $svn = SVN::Client->new();
308              
309             my ($needadd, $needdel) = _find_unsync_source(
310             svn => $svn,
311             specfile => $specfile,
312             working_dir => $working_dir,
313             ) or return;
314              
315             _sync_source(
316             svn => $svn,
317             needadd => $needadd,
318             needdel => $needdel,
319             );
320             }
321              
322              
323              
324             sub _strip_changelog {
325             my ($specfile, $dh) = @_;
326              
327             my $changelog = '';
328             my $newspec = $dh || new File::Temp(
329             UNLINK => 1
330             ) or do {
331             $error = $!;
332             return;
333             };
334              
335             if (open(my $oldsfh, "<", $specfile)) {
336             my $ischangelog = 0;
337             my $emptyline = "";
338             while(my $line = <$oldsfh>) {
339             if ($line =~ /^\s*$/) {
340             $emptyline .= $line;
341             next;
342             }
343             if ($line =~ /^%changelog/i) {
344             $ischangelog = 1;
345             next;
346             }
347             if ($line =~ /^%(files|build|check|prep|post|pre|package|description)/i) {
348             $ischangelog = 0;
349             }
350             if ($ischangelog) {
351             $changelog .= $emptyline . $line;
352             $emptyline = "";
353             } else {
354             print $newspec $emptyline . $line;
355             $emptyline = "";
356             }
357             }
358             close($oldsfh);
359             } else {
360             $error = "Can't open $specfile: $!";
361             return;
362             }
363              
364             return($changelog, $newspec);
365             }
366              
367             =head2 strip_changelog($specfile)
368              
369             Remove the %changelog section from the specfile.
370              
371             =cut
372              
373             sub strip_changelog {
374             my ($specfile) = @_;
375            
376             _print_msg(1, 'removing changleog from %s', $specfile);
377             my ($changelog, $newspec) = _strip_changelog($specfile);
378              
379             $changelog or return 1;
380              
381             seek($newspec, 0, 0);
382             if (open(my $oldspec, ">", $specfile)) {
383             while (<$newspec>) {
384             print $oldspec $_;
385             }
386             close($oldspec);
387             } else {
388             $error = "can't open $specfile: $!";
389             return;
390             }
391              
392             1;
393             }
394              
395             =head2 build($dir, $what, %options)
396              
397             Build package locate in $dir. The type of packages to build is
398             set in the string $what: b for binaries, s for source.
399              
400             If $options{specfile} is set, the build is done from this specfile
401             and not the one contains in SPECS/ directory.
402              
403             =cut
404              
405             sub build {
406             my ($working_dir, $what, %options) = @_;
407              
408             set_rpm_dirs(
409             $working_dir,
410             $options{destdir} ?
411             (
412             _rpmdir => 'RPMS',
413             _srcrpmdir => 'SRPMS',
414             ) : ()
415             );
416             create_rpm_dirs() or return 0;
417              
418             my $specfile = $options{specfile} || (glob(RPM4::expand('%_specdir/*.spec')))[0];
419             if (!$specfile) {
420             $error = "Can't find specfile";
421             return;
422             }
423              
424             RPM4::del_macro("_signature"); # don't bother
425             my $spec = RPM4::specnew(
426             $specfile, undef,
427             $options{root} || '/',
428             undef, 0, 0) or do {
429             $error = "Can't read specfile $specfile";
430             return;
431             };
432              
433             if (! $options{nodeps}) {
434             my $db = RPM4::newdb();
435             my $sh = $spec->srcheader();
436             $db->transadd($sh, "", 0);
437             $db->transcheck;
438             my $pbs = $db->transpbs();
439            
440             if ($pbs) {
441             $pbs->init;
442             $error = "\nFailed dependencies:\n";
443             while($pbs->hasnext) {
444             $error .= "\t" . $pbs->problem() . "\n";
445             }
446             return;
447             }
448             }
449              
450             my @bflags = ();
451             my %results = ();
452            
453             if ($what =~ /b/) {
454             push(@bflags, qw(PREP BUILD INSTALL CHECK FILECHECK PACKAGEBINARY CLEAN RMBUILD));
455             if (!-d RPM4::expand('%_rpmdir')) {
456             mkdir RPM4::expand('%_rpmdir') or do {
457             $error = "Can't create " . RPM4::expand('%_rpmdir') . ": $!";
458             return;
459             };
460             }
461             foreach my $rpm ($spec->binrpm) {
462             push(@{$results{bin}}, $rpm);
463             my ($dirname) = $rpm =~ m:(.*)/:;
464             if (! -d $dirname) {
465             mkdir $dirname or do {
466             $error = "Can't create $dirname: $!";
467             return;
468             };
469             }
470             }
471             }
472             if ($what =~ /s/) {
473             push(@bflags, qw(PACKAGESOURCE));
474             if (!-d RPM4::expand('%_srcrpmdir')) {
475             mkdir RPM4::expand('%_srcrpmdir') or return;
476             }
477             foreach my $rpm ($spec->srcrpm) {
478             push(@{$results{src}}, $rpm);
479             my ($dirname) = $rpm =~ m:(.*)/:;
480             if (! -d $dirname) {
481             mkdir $dirname or do {
482             $error = "Can't create $dirname: $!";
483             return;
484             };
485             }
486             }
487             }
488              
489             RPM4::setverbosity('INFO') if ($verbosity);
490             $spec->build([ @bflags ]) and return;
491             RPM4::setverbosity('WARNING');
492              
493             return %results;
494             }
495              
496             =head2 repsys_error
497              
498             Return the last repsys error.
499              
500             =cut
501              
502              
503             sub repsys_error {
504             $error
505             }
506              
507             # Exemple of use:
508             # my $msg = "rere" ; MDV::Repsys::_commit_editor(\$msg); print $msg;
509              
510             sub _commit_editor {
511             my ($msg) = @_;
512            
513             my $tmp = new File::Temp();
514             $tmp->unlink_on_destroy(1);
515             printf $tmp <
516             %s
517             SVN: Line begining by SVN are ignored
518             SVN: MDV::Repsys $VERSION
519             EOF
520             close($tmp);
521             my ($editor) = map { $ENV{$_} } grep { $ENV{$_} } qw(SVN_EDITOR VISUAL EDITOR);
522             $editor ||= 'vi';
523             if (system($editor, $tmp->filename) == -1) {
524             warn "Cannot start $editor\n";
525             $$msg = undef;
526             return 0;
527             }
528             if (open(my $rh, "<", $tmp->filename)) {
529             my $rmsg = '';
530             while (<$rh>) {
531             m/^SVN:/ and next;
532             $rmsg .= $_;
533             }
534             close ($rh);
535             chomp($rmsg);
536             $$msg = $rmsg;
537             } else {
538             $$msg = undef;
539             return 0;
540             }
541             1;
542             }
543              
544             1;
545              
546             __END__