pullup 3232
[pkgsrcv2.git] / mk / bulk / post-build
1 #!/usr/pkg/bin/perl
2 # $NetBSD: post-build,v 1.70 2008/01/04 15:49:08 rillig Exp $
3 #
4 # Collect stuff after a pkg bulk build
5 #
6 # (c) 2000 Hubert Feyrer, All Rights Reserved.
7 #
8
9 use File::Basename;
10 use POSIX qw(strftime);
11 use strict;
12 use warnings;
13
14 #
15 # Global variables
16 #
17
18 my %vars;
19 my $verbose = 1; # set to 2 to see more command execution detail
20
21 #
22 # Helper functions
23 #
24
25 sub pb_die($$) {
26         my ($fname, $msg) = @_;
27         my ($text, $sep);
28
29         $text = "[post-build] error: ";
30         $sep = "";
31         if (defined($fname)) {
32                 $text .= "${sep}${fname}";
33                 $sep = ": ";
34         }
35         $text .= "${sep}${msg}";
36         die "${text}\n";
37 }
38
39 sub my_system (@) {
40         print STDERR '> '.join(' ', @_)."\n" if ($verbose >= 2);
41         return system(@_);
42 }
43
44 sub readfirstline($) {
45         my ($fname) = @_;
46         my ($contents);
47
48         open(F, "<", $fname)
49         or pb_die($fname, "Cannot be read: $!");
50
51         defined($contents = <F>)
52         or pb_die($fname, "Must not be empty.");
53         chomp($contents);
54
55         close(F)
56         or pb_die($fname, "Cannot be closed: $!");
57
58         return $contents;
59 }
60
61 #
62 # Load configuration variables from the bulk.conf file, which is a shell
63 # script.
64 #
65
66 my $BULK_BUILD_CONF = $ENV{BULK_BUILD_CONF} || (dirname($0).'/build.conf');
67 $BULK_BUILD_CONF = "./$BULK_BUILD_CONF" if ($BULK_BUILD_CONF !~ m:^/:);
68
69 if (!-f $BULK_BUILD_CONF) {
70         pb_die($BULK_BUILD_CONF, "Does not exist.");
71 }
72
73 # Dig given variable out of config file, and set it
74 sub get_build_conf_vars(@) {
75         my (@varnames) = @_;
76         my ($is_set, $value);
77
78         foreach my $varname (@varnames) {
79                 my $cmd = join("\n", (
80                         #"set -eu",             # TODO: Should be enabled soon
81                         ". '${BULK_BUILD_CONF}'",
82                         ". mk/bulk/post-build-conf",
83                         "check_config_vars",
84                         "echo \"\${${varname}+set}\"",
85                         "echo \"\${${varname}-}\""
86                 ));
87
88                 open(CMD, "${cmd} |")
89                         or pb_die($BULK_BUILD_CONF, "Could not evaluate configuration file.");
90
91                 chomp($is_set = <CMD>);
92                 { local $/ = undef; $value = <CMD>; }
93                 chomp($value);  # This must be outside the above {...} block
94
95                 close(CMD) or pb_die($BULK_BUILD_CONF, "Could not evaluate configuration file (close).");
96
97                 #
98                 # Sanity checks
99                 #
100
101                 if ($is_set ne "set") {
102                         pb_die($BULK_BUILD_CONF, "${varname} must be set.");
103                 }
104                 if ($value =~ qr"^\s+$") {
105                         pb_die($BULK_BUILD_CONF, "${varname} must be non-empty.");
106                 }
107                 if ($value =~ qr"\n") {
108                         pb_die($BULK_BUILD_CONF, "${varname} must not contain newlines.");
109                 }
110
111                 $vars{$varname} = $value;
112                 if ($verbose >= 2) {
113                         print STDERR "> $varname=$vars{$varname}\n";
114                 }
115         }
116 }
117
118 get_build_conf_vars(
119         'ADMINSIG',             # "-Your Name"
120         'REPORTS_URL',          # "ftp://ftp.example.com/pub/pkgsrc/misc/pkgstat"
121         'REPORTS_DIR',          # "$HOME/bulk-logs"
122 # REPORT_BASEDIR often depends on a timestamp, which has been saved at
123 # the beginning of the bulk build in the BULK_BUILD_ID_FILE. It will be
124 # retrieved later.
125         'REPORT_HTML_FILE',     # "report.html"
126         'REPORT_TXT_FILE',      # "report.txt"
127         'USR_PKGSRC',           # "/usr/pkgsrc"
128         'osrev',                # `uname -r`
129 );
130
131
132 my $os = `uname -s`;
133 chomp $os;
134
135 my $BMAKE = $ENV{BMAKE} || pb_die(undef, "The BMAKE environment variable must be set.");
136
137 sub get_mk_conf_vars (@) {
138         my ($rest);
139
140         open(I, "set -e; . '$BULK_BUILD_CONF'; . '$vars{USR_PKGSRC}/mk/bulk/post-build-conf'; export_config_vars; cd $vars{USR_PKGSRC}/pkgtools/lintpkgsrc && $BMAKE show-vars BATCH=1 VARNAMES='".join(' ', @_)."' USE_TOOLS=\"pax\" |")
141                 or pb_die(undef, "Cannot get mk.conf variables.");
142
143         foreach my $var (@_) {
144                 chomp($vars{$var} = <I>);
145
146                 if ($vars{$var} eq "") {
147                         pb_die(undef, "\${$var} must be defined in your mk.conf");
148                 }
149
150                 print STDERR "> $var=$vars{$var}\n" if ($verbose >= 2);
151         }
152
153         { local $/ = undef; $rest = <I>; }
154         if (defined($rest) && $rest ne "") {
155                 pb_die(undef, "Output of $BMAKE show-vars too long:\n${rest}");
156         }
157
158         close(I) or die pb_die(undef, "Cannot get mk.conf variables (close).");
159 }
160
161 # Extract the names of the files used for the build log and broken build logs.
162 # These have defaults set by bsd.bulk-pkg.mk and may be overridden in
163 # /etc/mk.conf
164 get_mk_conf_vars(qw(
165         BROKENFILE
166         BROKENWRKLOG
167         BULKFILESDIR
168         BULK_DBFILE
169         DEPENDSFILE
170         DEPENDSTREEFILE
171         FIND
172         INDEXFILE
173         LOCALBASE
174         MACHINE_ARCH
175         NOT_AVAILABLE_FILE
176         ORDERFILE
177         PAX
178         PKG_DBDIR
179         PKGSRC_COMPILER
180         STARTFILE
181         SUPPORTSFILE
182         X11BASE
183         PKG_TOOLS_BIN
184         BULK_BUILD_ID_FILE
185 ));
186
187 my $bulk_dbfile_base = basename($vars{BULK_DBFILE});
188 my $dependstreefile_base = basename($vars{DEPENDSTREEFILE});
189 my $dependsfile_base = basename($vars{DEPENDSFILE});
190 my $supportsfile_base = basename($vars{SUPPORTSFILE});
191 my $indexfile_base = basename($vars{INDEXFILE});
192 my $orderfile_base = basename($vars{ORDERFILE});
193
194 my $reports_url = $vars{"REPORTS_URL"};
195 my $reports_dir = $vars{"REPORTS_DIR"};
196 my $report_basedir = readfirstline($vars{"BULK_BUILD_ID_FILE"});
197 my $report_html_file = $vars{"REPORT_HTML_FILE"};
198 my $report_txt_file = $vars{"REPORT_TXT_FILE"};
199
200 my $report_url = "${reports_url}/${report_basedir}";
201 my $report_dir = "${reports_dir}/${report_basedir}";
202 my $report_html_path = "${report_dir}/${report_html_file}";
203 my $report_txt_path = "${report_dir}/${report_txt_file}";
204
205 my $startdate = (stat($vars{STARTFILE}))[9];
206 my $enddate = '';
207 if (!defined($startdate) || $startdate == 0) {
208         $startdate = "unknown";
209 } else {
210         local $ENV{TZ} = "UTC";
211         $startdate = strftime("%c %Z", gmtime($startdate));
212         $enddate = strftime("%c %Z", gmtime(time()));
213 }
214
215 sub print_summary_line($$) {
216         my ($name, $value) = @_;
217
218         printf("        %-30s  %s\n", $name, $value);
219 }
220
221 sub print_report_line($$$) {
222         my ($pkgpath, $breaks, $maintainer) = @_;
223
224         printf("%-26s %-7s %s\n", $pkgpath, $breaks, $maintainer);
225 }
226
227 sub print_report_header() {
228         print_report_line("Package", "Breaks", "Maintainer");
229         print("--------------------------------------------------------------\n");
230 }
231
232 my_system("mkdir", "-p", "--", $report_dir);
233
234 # Copy over the output from the build process
235 chdir($vars{"BULKFILESDIR"}) or pb_die($vars{"BULKFILESDIR"}, "Cannot change directory.");
236 my_system("find . -name $vars{BROKENFILE} -print -o -name $vars{BROKENWRKLOG} -print | $vars{PAX} -r -w -X ${report_dir}");
237
238 # Copy over the cache files used during the build
239 foreach my $f qw(BULK_DBFILE DEPENDSTREEFILE DEPENDSFILE SUPPORTSFILE INDEXFILE ORDERFILE) {
240         if (-f $vars{$f}) {
241                 my_system("cp", "--", $vars{$f}, $report_dir);
242         }
243 }
244
245 chdir($report_dir) or pb_die($report_dir, "Cannot change directory.");
246 writeReport();
247
248 #
249 # Adjust "last" symlink
250 #
251 unlink("${reports_dir}/last");
252 symlink($report_basedir, "${reports_dir}/last");
253
254 #
255 # Generate leftovers-$vars{MACHINE_ARCH}.html: files not deleted
256 # Leftover files are copied to leftovers-$vars{MACHINE_ARCH} dir,
257 # and linked from leftovers-$vars{MACHINE_ARCH}.html
258 #
259 {
260         chdir($report_dir) or pb_die($report_dir, "Cannot change directory.");
261         my_system("mkdir", "-p", "leftovers-$vars{MACHINE_ARCH}");
262
263         # Find files since last build:
264         my $leftovers_txt = "leftovers-$vars{MACHINE_ARCH}.txt";
265         my $leftovers_html = "leftovers-$vars{MACHINE_ARCH}.html";
266
267         my_system("$vars{FIND} $vars{LOCALBASE}/ -newer $vars{STARTFILE} -type f -print >>$leftovers_txt");
268         my_system("$vars{FIND} $vars{X11BASE}/ -newer $vars{STARTFILE} -type f -print >>$leftovers_txt");
269
270         # Strip perl-files:
271         my $perlfiles;
272         {
273                 local $/;
274                 undef $/;
275                 $perlfiles = `$vars{PKG_TOOLS_BIN}/pkg_info -qL perl*`;
276         }
277
278         my $perlfiles_pattern = $perlfiles;
279         $perlfiles_pattern =~ s/\n/|/g;
280         $perlfiles_pattern =~ s/|$//;
281
282         open (LEFT, $leftovers_txt) or die "can't read $leftovers_txt: $!";
283         my @left = <LEFT>;
284         close (LEFT);
285         my @leftovers = grep(!/^(?:${perlfiles_pattern})$/, @left);
286
287         if (index($vars{PKG_DBDIR}, $vars{LOCALBASE}) == 0) {
288                 # If PKG_DBDIR is inside LOCALBASE, exclude it from the leftovers.
289                 @leftovers = grep { index($_, $vars{PKG_DBDIR}) != 0 } @leftovers;
290         }
291
292         open (LEFT, ">$leftovers_txt") or die "can't write $leftovers_txt: $!";
293         print LEFT @leftovers;
294         close (LEFT);
295
296         if (scalar(@leftovers)) {
297                 # Store leftovers, for easier identification:
298                 my_system("$vars{PAX} -r -w -X leftovers-$vars{MACHINE_ARCH} < $leftovers_txt");
299         }
300
301         # Add links to leftover list:
302         open (OUT, "> $leftovers_html")
303                 or die "can't write $leftovers_html";
304         print OUT <<EOOUT;
305 <html>
306 <body>
307 <pre>
308 EOOUT
309         foreach (@leftovers) {
310                 chomp;
311                 print OUT "<a href=\"${report_url}/leftovers-$vars{MACHINE_ARCH}$_\">$_</a>\n";
312         }
313         print OUT <<EOOUT2;
314 </pre>
315 </body>
316 </html>
317 EOOUT2
318         close(OUT);
319 }
320
321 # print the result of a single broken package
322 sub pkgResult ($$) {
323         my ($pinfo, $state) = @_;
324         my $pkg = $pinfo->{pkg};
325         my $nbrokenby = $pinfo->{nbrokenby};
326         my $nerrors = $pinfo->{nerrors};
327         my $pkgdirmissing = 0;
328         my $DIR;
329
330         if (not opendir($DIR, "$vars{USR_PKGSRC}/$pkg")) {
331                 $pkgdirmissing=1;
332         }
333         else {
334                 closedir($DIR);
335         }
336
337         my @idents = "";
338         if (not $pkgdirmissing) {
339                 @idents = `$vars{FIND} $vars{USR_PKGSRC}/$pkg -type f -print | xargs grep \\\$NetBSD`;
340         }
341         my $datetime = "";
342         my $file = "";
343         my $ver = "";
344         foreach my $ident (@idents) {
345                 $ident =~ /\$[N]etBSD: ([^ ]*),v [^ ]* ([^ ]*) ([^ ]*) [^ ]* Exp \$/;
346                 if (defined($2) && defined($3) && ("$2 $3" gt $datetime)) {
347                         $datetime = "$2 $3";
348                         $file = $1;
349                         $ver = $1;
350                 }
351         }
352
353         my $maintainer;
354         if (not $pkgdirmissing) {
355                 $maintainer = `grep ^MAINTAINER $vars{USR_PKGSRC}/$pkg/Makefile`;
356         } else {
357                 $maintainer = "directory_does_not_exist";
358         }
359         $maintainer =~ s/MAINTAINER=[ \t]*//;
360         if (! $maintainer) {
361                  $maintainer = `cd $vars{USR_PKGSRC}/$pkg ; $BMAKE show-var VARNAME=MAINTAINER`;
362         }
363         $maintainer =~ s/</&lt;/g;
364         $maintainer =~ s/>/&gt;/g;
365         chomp($maintainer);
366
367         (my $state_style = $state) =~ s/ //g;
368
369         my $nbrokenby_html = '<td>&nbsp;</td>';
370         $nbrokenby_html =
371                 '<td align="right" class="pkg-'.$state_style.'">'.$nbrokenby.'</td>'
372                 if $nbrokenby > 0;
373
374         if ($pinfo->{nerrors} != 0 && $verbose && ($state eq "broken" || $state eq "topten")) {
375                 print_report_line($pkg, $nbrokenby > 0 ? $nbrokenby : "", $maintainer);
376         }
377
378         return <<EOHTML;
379 <tr>
380   <td><a class="pkg-$state_style" href="$pinfo->{bf}" title="build log for $pkg">$pkg</a></td>
381   $nbrokenby_html
382   <td>$file</td>
383   <td>$maintainer</td>
384 </tr>
385
386 EOHTML
387 }
388
389 # write the build report
390 sub writeReport {
391         my $broken = getBroken();
392         my $nbroken = scalar(@{$broken->{"broken"}});
393         my $nbrokendep = scalar(@{$broken->{"broken depends"}});
394         my $nunpackaged = scalar(@{$broken->{"not packaged"}});
395         my $nnot_available = scalar(@{$broken->{"not available"}});
396         my $nbrokentot = $nbroken + $nbrokendep;
397         my $ntotal = $nunpackaged + $nbroken + $nbrokendep;
398
399         # determine the number of packages attempted, and then successful
400         open(ORDER, $vars{ORDERFILE}) || die "can't open $vars{ORDERFILE}: $!";
401         my @order = <ORDER>;
402         close(ORDER);
403         my $nattempted = scalar(@order);
404         my $nsuccessful = $nattempted - $ntotal;
405
406         if ($verbose) {
407                 print <<EOF;
408 pkgsrc bulk build results
409 $os $vars{osrev}/$vars{MACHINE_ARCH}
410 Compiler: $vars{PKGSRC_COMPILER}
411
412 Summary:
413
414 EOF
415                 print_summary_line("Build started:", $startdate);
416                 print_summary_line("Build ended:", $enddate);
417                 print("\n");
418                 print_summary_line("Successfully packaged:", $nsuccessful);
419                 print_summary_line("Packages really broken:", $nbroken);
420                 print_summary_line("Pkgs broken due to them:", $nbrokendep);
421                 print_summary_line("Total broken:", $nbrokentot);
422                 print_summary_line("Not packaged:", $nunpackaged);
423                 print_summary_line("Not available:", $nnot_available);
424                 print_summary_line("Total:", $ntotal);
425                 print <<EOF;
426
427 Packages not listed here resulted in a binary package. The build
428 report, including logs of failed/not-packaged is available from:
429
430 ${report_url}/${report_html_file}
431 EOF
432         }
433
434         open(HTML, ">", $report_html_path) or die "Can't write ${report_html_path}: $!";
435         print HTML <<EOHTML;
436 <html>
437 <head>
438 <title>$os $vars{osrev}/$vars{MACHINE_ARCH} bulk package build</title>
439 <style type="text/css">
440 <!--
441
442 body {
443         Font-Family: Tahoma, Verdana, sans-serif;
444         Line-Height: 1.3em;
445         Text-Decoration: None;
446         Color: black;
447         Background-Color: white;
448         Border-Width: 0;
449 }
450
451 table {
452         Border-Width: 0;
453 }
454
455 table td {
456         Font-Family: Tahoma, Verdana, sans-serif;
457         line-height: 1em;
458 }
459
460 a:link {
461         Color: #3535c5;
462 }
463
464 a:visited {
465         Color: #700080;
466 }
467
468 a:hover {
469         Color: #6565e5;
470         Text-Decoration: underline;
471 }
472
473 tr {
474         Vertical-Align: top;
475 }
476
477 td {
478         Vertical-Align: top;
479 }
480
481 h1 {
482         Font-Size: 3.5ex;
483         Line-Height: 1em;
484         Color: #000066;
485 }
486
487 h2 {
488         Font-Size: 2.5ex;
489         Line-Height: 1em;
490         Color: #660000;
491 }
492
493 h3 {
494         Font-Size: 2ex;
495         Color: #660066;
496 }
497
498 h4 {
499         Font-Size: 1.8ex;
500         Color: #006600;
501 }
502
503 tt.filename {
504         Line-Height: 1.3em;
505         Color: #AA0000;
506 }
507
508 .pkgname {
509         Font-Family: Arial, Helvetica, Courier, fixed;
510         Font-Style: Italic;
511         Text-Decoration: none;
512         Line-Height: 1.3em;
513 }
514
515 .pkg-broken {
516         Color: red;
517 }
518
519 .pkg-brokendepends {
520         Color: orange;
521 }
522
523 .pkg-notpackaged {
524         Color: blue;
525 }
526 -->
527 </style>
528 </head>
529
530 <body bgcolor="white" text="black" link="#3535c5" vlink="#700080" alink="#3535c5">
531
532 <a name="top"></a>
533 <h1>pkgsrc bulk build results</h1>
534 <h2>$os $vars{osrev}/$vars{MACHINE_ARCH}</h2>
535
536 <h3>Summary</h3>
537
538 <table>
539 <tr>
540   <td>Build started:                    <td align="right">$startdate</td>
541 </tr>
542 <tr>
543   <td>Build ended:                      <td align="right">$enddate</td>
544 </tr>
545 <tr>
546   <td>&nbsp;</td>                       <td>&nbsp;</td>
547 </tr>
548 <tr>
549   <td>Successfully packaged:</td>       <td align="right">$nsuccessful</td>
550 </tr>
551 <tr class="pkg-broken">
552   <td>Packages really broken:</td>      <td align="right">$nbroken</td>
553 </tr>
554 <tr class="pkg-brokendepends">
555   <td>Packages broken due to them:</td> <td align="right">$nbrokendep</td>
556 </tr>
557 <tr>
558   <td>Total broken:</td>                <td align="right">$nbrokentot</td>
559 </tr>
560 <tr class="pkg-notpackaged">
561   <td>Not packaged:</td>                <td align="right">$nunpackaged</td>
562 </tr>
563 <tr class="pkg-notavailable">
564   <td>Not available:</td>               <td align="right">$nnot_available</td>
565 </tr>
566 <tr>
567   <td>Total:</td>                       <td align="right">$ntotal</td>
568 </tr>
569 </table>
570
571 <p>
572   Packages not listed here resulted in a <a
573   href="../../packages/" title="binary packages for $os $vars{osrev}/$vars{MACHINE_ARCH}">binary
574   package</a>. Results of failed packages are available below.
575 </p>
576
577 <p>
578   Files leftover from the build (because of broken PLISTs, etc.) can be
579   found in <a href="leftovers-$vars{MACHINE_ARCH}.html" title="leftover files">this
580   list</a>.
581 </p>
582
583 <p>
584   Jump to:<br/>
585   <ul>
586     <li><a href="#topten">Top Ten Offenders</a></li>
587     <li><a href="#broken">Broken packages</a></li>
588     <li><a href="#broken depends">Broken dependencies</a></li>
589     <li><a href="#not packaged">Not packaged</a></li>
590     <li><a href="#not available">Not available</a></li>
591   </ul>
592 </p>
593
594 EOHTML
595
596         my %state_head = (
597                 "topten" => "Top Ten Offenders",
598                 "broken" => "Broken packages",
599                 "broken depends" => "Broken dependencies",
600                 "not packaged" => "Not packaged",
601                 "not available" => "Not available",
602         );
603
604         foreach my $state ("topten", "broken", "broken depends", "not packaged", "not available") {
605                 next unless scalar(@{$broken->{$state}});
606
607                 if ($verbose && ($state eq "topten" || $state eq "broken")) {
608                         print "\n\n$state_head{$state}\n\n";
609                         print_report_header();
610                 }
611
612
613                 print HTML <<EOHTML;
614
615 <a name="$state"></a>
616 <h2>$state_head{$state}</h2>
617 <table width="100%">
618 <tr align="left">
619   <th width="30%">Package</th>
620   <th>Breaks</th>
621   <th>File touched last</th>
622   <th>Maintainer</th>
623 </tr>
624
625 EOHTML
626                 foreach my $pinfo (@{$broken->{$state}}) {
627                         print HTML pkgResult($pinfo, $state);
628                 }
629
630                 print HTML <<EOHTML;
631 </table>
632 <hr>
633 <a href="#top">Up to top</a><br/>
634 <hr>
635 EOHTML
636         }
637
638         print HTML <<EOHTML;
639 <hr>
640 <p>
641 The following cache files were used during the build:
642 </p>
643 <ul>
644 <li>The <a href="$bulk_dbfile_base">SPECIFIC_PKGS bulk database file</a>.</li>
645 <li>The <a href="$dependstreefile_base">depends tree file</a>.</li>
646 <li>The <a href="$dependsfile_base">depends file</a>.</li>
647 <li>The <a href="$supportsfile_base">supports file</a>.</li>
648 <li>The <a href="$indexfile_base">index file</a>.</li>
649 <li>The <a href="$orderfile_base">build order file</a>.</li>
650 </ul>
651 <hr>
652
653 <p>
654 <ul>
655 <!-- <li>See the list of <a href="../index.html">all log files</a>. -->
656 <li>Visit the <a href="http://www.NetBSD.org">NetBSD web site</a>.
657 <li>Learn more about
658     <a href="http://www.NetBSD.org/docs/software/packages.html">
659     The NetBSD Packages Collection</a>.
660 </ul>
661 </p>
662 </body>
663 </html>
664 EOHTML
665         close(HTML);
666
667         if ($verbose) {
668                 print "\n\n$vars{ADMINSIG}\n\n";
669                 print "[* This message was created by the Packages Collection bulk build software *]\n";
670         }
671 }
672
673 # get and sort the broken packages
674 sub getBroken {
675         my $res = {
676                 'broken' => [],
677                 'broken depends' => [],
678                 'not packaged' => [],
679                 'topten' => [],
680                 "not available" => [],
681         };
682
683         open (BF, $vars{BROKENFILE}) || return $res;
684         my @in = <BF>;
685         close (BF);
686
687         foreach (@in) {
688                 chomp;
689                 my ($nerrors, $bf, $nbrokenby) = split;
690                 my $pkg = $bf;
691                 $pkg =~ s,/$vars{BROKENFILE},,;
692                 my $tmp = {
693                         bf => $bf,
694                         pkg => $pkg,
695                         nbrokenby => $nbrokenby,
696                         nerrors => $nerrors,
697                 };
698
699                 if (-f "$vars{BULKFILESDIR}/$pkg/$vars{NOT_AVAILABLE_FILE}") {
700                         push(@{$res->{"not available"}}, $tmp);
701                 } elsif ($nerrors > 0) {
702                         push(@{$res->{"broken"}}, $tmp);
703                 } elsif ($nerrors == -1) {
704                         push(@{$res->{"broken depends"}}, $tmp);
705                 } else {
706                         push(@{$res->{"not packaged"}}, $tmp);
707                 }
708         }
709
710         # sort pkgs in each state
711         foreach my $state ("broken", "broken depends", "not packaged", "not available") {
712                 $res->{$state} = [ sort { $a->{pkg} cmp $b->{pkg} } @{$res->{$state}} ];
713         }
714
715         $res->{"topten"} = [ sort { $b->{nbrokenby} <=> $a->{nbrokenby} } @{$res->{"broken"}} ];
716
717         for (my $count = $#{$res->{"topten"}}; $count >= 10; $count--) {
718                 pop(@{$res->{"topten"}});
719         }
720
721         return $res;
722 }