Warning, /Utilities/ReleaseScripts/ignominy/xmerge is written in an unsupported language. File is not indexed.
0001 #!/usr/bin/env perl
0002 use warnings;
0003 use strict;
0004
0005 my $me = $0;
0006 $me =~ s|.*/||;
0007
0008 ######################################################################
0009 my $SUM_INC_OUT_EDGES = 0;
0010 my $SUM_INC_OUT_COUNT = 1;
0011 my $SUM_INC_IN_EDGES = 2;
0012 my $SUM_INC_IN_COUNT = 3;
0013 my $SUM_LIB_OUT_EDGES = 4;
0014 my $SUM_LIB_OUT_COUNT = 5;
0015 my $SUM_LIB_IN_EDGES = 6;
0016 my $SUM_LIB_IN_COUNT = 7;
0017 my $SUM_LOG_OUT_EDGES = 8;
0018 my $SUM_LOG_IN_EDGES = 9;
0019 my $SUM_ALL_IN_EDGES = 10;
0020 my $SUM_ALL_OUT_EDGES = 11;
0021 my $SUM_N = 12;
0022
0023 my $CELL_INC_OUT_COUNT = 0;
0024 my $CELL_INC_IN_COUNT = 1;
0025 my $CELL_LIB_OUT_COUNT = 2;
0026 my $CELL_LIB_IN_COUNT = 3;
0027 my $CELL_LOG_OUT_EDGES = 4;
0028 my $CELL_LOG_IN_EDGES = 5;
0029 my $CELL_N = 6;
0030
0031 my @SUM_LABELS = ("Number of outgoing edges from includes",
0032 "Total count of outgoing includes",
0033 "Number of incoming edges from includes",
0034 "Total count of incoming includes",
0035 "Number of outgoing edges from symbols",
0036 "Total count of outgoing symbols",
0037 "Number of incoming edges from symbols",
0038 "Total count of incoming symbols",
0039 "Number of (possibly overlapping) outgoing logical edges",
0040 "Number of (possibly overlapping) incoming logical edges",
0041 "Total count of incoming edges",
0042 "Total count of outgoing edges");
0043
0044 sub min {
0045 my $min = shift;
0046 foreach my $val (@_) {
0047 $min = $min > $val ? $val : $min;
0048 }
0049 return $min;
0050 }
0051
0052 sub max {
0053 my $max = shift;
0054 foreach my $val (@_) {
0055 $max = $max > $val ? $max : $val;
0056 }
0057 return $max;
0058 }
0059
0060 sub rgb2hsv {
0061 my ($r, $g, $b) = @_;
0062 my ($h, $s, $v);
0063 my $max = &max($r, $g, $b);
0064 my $min = &min($r, $g, $b);
0065
0066 $v = $max;
0067 $s = ($max ? ($max - $min)/$max : 0.0);
0068 if (! $s) {
0069 $h = 0;
0070 } else {
0071 my $d = $max - $min;
0072 if ($r == $max) {
0073 $h = 0. + ($g - $b)/$d;
0074 } elsif ($g == $max) {
0075 $h = 2. + ($b - $r)/$d;
0076 } elsif ($b == $max) {
0077 $h = 4. + ($r - $g)/$d;
0078 }
0079 $h /= 6.;
0080 $h += 1. if ($h < 0.);
0081 }
0082
0083 $h = int($h * 1000 + .5)/1000;
0084 $s = int($s * 1000 + .5)/1000;
0085 $v = int($v * 1000 + .5)/1000;
0086
0087 return ($h, $s, $v);
0088 }
0089
0090 sub rgb2yiq {
0091 my ($r, $g, $b) = @_;
0092 return (.299 * $r + .587 * $g + .115 * $b,
0093 .596 * $r - .275 * $g - .321 * $b,
0094 .212 * $r - .523 * $g + .311 * $b);
0095 }
0096
0097 sub hsv2rgb {
0098 my ($h, $s, $v) = @_;
0099 if ($s eq 0.) {
0100 die "bad HSV H value: $h -- should be 0\n" if $h;
0101 return ($v, $v, $v);
0102 } else {
0103 $h = 0. if $h eq 1.;
0104 $h *= 6.;
0105 my $i = int($h);
0106 my $f = $h - $i;
0107 my $p = $v * (1. - $s);
0108 my $q = $v * (1. - $s * $f);
0109 my $t = $v * (1. - $s * (1. - $f));
0110
0111 return ($v, $t, $p) if $i == 0;
0112 return ($q, $v, $p) if $i == 1;
0113 return ($p, $v, $t) if $i == 2;
0114 return ($p, $q, $v) if $i == 3;
0115 return ($t, $p, $v) if $i == 4;
0116 return ($v, $p, $q) if $i == 5;
0117 die;
0118 }
0119 }
0120
0121 sub validate_flag {
0122 my ($value, $opt) = @_;
0123
0124 return 1 if (grep(lc($value) eq $_, qw(1 y yes)));
0125 return 0 if (grep(lc($value) eq $_, qw(0 n no)));
0126 die "$opt must be (1, y, yes) or (0, n, no)\n";
0127 }
0128
0129 sub file_part {
0130 my $name = shift;
0131 $name =~ s|.*/||;
0132 return $name;
0133 }
0134
0135 sub dir_part {
0136 my $name = shift;
0137 return '' if $name !~ m|/|;
0138 $name =~ s|/[^/]+$||;
0139 return $name;
0140 }
0141
0142 sub check_xedge {
0143 my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0144 return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0145
0146 my $matches = 0;
0147 foreach my $xrx (@$xrx) {
0148 $matches += scalar grep ($_ ne ':hard'
0149 && ($xinclusive ? $_ =~ /$xrx/ : $_ !~ /$xrx/),
0150 @{$edges->{$source}->{$target}});
0151 }
0152 return $matches;
0153 }
0154
0155 sub include_edge {
0156 my ($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target) = @_;
0157 return 0 if !exists $nodes->{$target};
0158 return 0 if (grep($target =~ /$_/, @$rrx) && 1) ne $rinclusive;
0159 return 1 if grep($_ eq ':hard', @{$edges->{$source}->{$target}});
0160 return 2 if &check_xedge($edges,$rinclusive,$rrx,$xinclusive,$xrx,$source,$target) ne 0;
0161 return 0;
0162 }
0163
0164 sub do_node {
0165 my ($gedges,$gnodes, $edges,$nodes, $rinclusive,$rrx, $xinclusive,$xrx, $from,$to,$include)=@_;
0166 return if ! $include;
0167
0168 if (defined $from) {
0169 $gedges->{$from} ||= {};
0170 $gedges->{$from}->{$to} = $include
0171 if (! exists $gedges->{$from}->{$to}
0172 || $gedges->{$from}->{$to} > $include);
0173 }
0174
0175 return if (exists $gnodes->{$to});
0176
0177 $gnodes->{$to} = 1;
0178 foreach (keys %{$edges->{$to}}) {
0179 &do_node($gedges, $gnodes, $edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_,
0180 &include_edge($edges, $nodes, $rinclusive, $rrx, $xinclusive, $xrx, $to, $_));
0181 }
0182 }
0183
0184 sub group_node {
0185 my ($grouping, $groups, $node, $default) = @_;
0186 foreach my $group (@$groups) {
0187 my $i = 0;
0188 foreach (@{$group->[2]}) {
0189 if ($node =~ /$_/) {
0190 $grouping->{$node} = [ $group, $i ];
0191 return;
0192 }
0193 $i++;
0194 }
0195 }
0196 $grouping->{$node} = [ $default, 0 ];
0197 }
0198
0199 my %renames_results = ();
0200 sub rename_node {
0201 my ($renames, $node) = @_;
0202 if (exists $renames_results{$node}){return $renames_results{$node};}
0203 for (@$renames){
0204 if ($node =~ /$_->[0]/){
0205 my $tmp=eval $_->[1];
0206 $renames_results{$node}=$tmp;
0207 $renames_results{$tmp}=$tmp;
0208 return $tmp;
0209 }
0210 }
0211 $renames_results{$node}=$node;
0212 return $node;
0213 }
0214
0215 sub dep_count {
0216 my ($table, $i, $j, $row) = @_;
0217 my $x = $table->[$i]->[$j];
0218 my $y = $table->[$j]->[$i];
0219
0220 my ($libs, $incs);
0221 if ($row->[3] eq 'libraries') {
0222 $libs = $row->[2];
0223 $incs = $row->[5];
0224 } else {
0225 $libs = $row->[5];
0226 $incs = $row->[2];
0227 }
0228
0229 $x->[$CELL_LIB_OUT_COUNT] += $libs;
0230 $y->[$CELL_LIB_IN_COUNT] += $libs;
0231
0232 $x->[$CELL_INC_OUT_COUNT] += $incs;
0233 $y->[$CELL_INC_IN_COUNT] += $incs;
0234 }
0235
0236 sub dep_xcount {
0237 my ($edges, $rinclusive, $rrx, $xinclusive, $xrx, $labels, $table, $i, $j) = @_;
0238 my $source = $labels->[$i];
0239 my $target = $labels->[$j];
0240 my $matches = &check_xedge($edges, $rinclusive, $rrx, $xinclusive, $xrx, $source, $target);
0241 if ($matches ne 0) {
0242 $table->[$i]->[$j]->[$CELL_LOG_OUT_EDGES] += $matches;
0243 $table->[$j]->[$i]->[$CELL_LOG_IN_EDGES] += $matches;
0244 }
0245 }
0246
0247 sub dep_summarise {
0248 my ($summary, $labels, $table, $i) = @_;
0249
0250 foreach my $cell (@{$table->[$i]}) {
0251 $summary->[$i]->[$SUM_INC_OUT_EDGES]++ if ($cell->[$CELL_INC_OUT_COUNT]);
0252 $summary->[$i]->[$SUM_INC_OUT_COUNT] += $cell->[$CELL_INC_OUT_COUNT];
0253 $summary->[$i]->[$SUM_INC_IN_EDGES]++ if ($cell->[$CELL_INC_IN_COUNT]);
0254 $summary->[$i]->[$SUM_INC_IN_COUNT] += $cell->[$CELL_INC_IN_COUNT];
0255
0256 $summary->[$i]->[$SUM_LIB_OUT_EDGES]++ if ($cell->[$CELL_LIB_OUT_COUNT]);
0257 $summary->[$i]->[$SUM_LIB_OUT_COUNT] += $cell->[$CELL_LIB_OUT_COUNT];
0258 $summary->[$i]->[$SUM_LIB_IN_EDGES]++ if ($cell->[$CELL_LIB_IN_COUNT]);
0259 $summary->[$i]->[$SUM_LIB_IN_COUNT] += $cell->[$CELL_LIB_IN_COUNT];
0260
0261 $summary->[$i]->[$SUM_LOG_OUT_EDGES] += $cell->[$CELL_LOG_OUT_EDGES];
0262 $summary->[$i]->[$SUM_LOG_IN_EDGES] += $cell->[$CELL_LOG_IN_EDGES];
0263
0264 $summary->[$i]->[$SUM_ALL_IN_EDGES]++
0265 if ($cell->[$CELL_INC_IN_COUNT]
0266 || $cell->[$CELL_LIB_IN_COUNT]
0267 || $cell->[$CELL_LOG_IN_EDGES]);
0268
0269 $summary->[$i]->[$SUM_ALL_OUT_EDGES]++
0270 if ($cell->[$CELL_INC_OUT_COUNT]
0271 || $cell->[$CELL_LIB_OUT_COUNT]
0272 || $cell->[$CELL_LOG_OUT_EDGES]);
0273 }
0274 }
0275
0276 sub dep_metrics_mark {
0277 my ($edges, $tedges, $n,$m) = @_;
0278 return if defined $m && exists $tedges->{$n}->{$m};
0279 if (defined $m) {
0280 $tedges->{$n}->{$m} = 1;
0281 map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$m}};
0282 }
0283 map { &dep_metrics_mark($edges, $tedges, $n, $_) } keys %{$edges->{$n}};
0284 }
0285
0286 sub dep_metrics {
0287 # see ftp://ftp.aw.com/cp/lakos/idep_ldep.c
0288 my ($edges, $nodes, $metrics) = @_;
0289
0290 my @nodes = keys %$nodes;
0291 my $n = scalar @nodes;
0292 my @levels = (0) x $n;
0293 my $nlevels = 1;
0294 my @levelnums = (0) x $n;
0295 my @mapping = (0) x $n;
0296 my $nmapping = 0;
0297
0298 my @lowerthan = map { [ (0) x $n ] } @nodes, 'foo', 'bar';
0299
0300 # calculate transitive closure
0301 my %tedges = map { $_ => {} } keys %$edges;
0302 map { &dep_metrics_mark($edges, \%tedges, $_, undef) } keys %$edges;
0303
0304 # determine and label all members of all cycles
0305 my @cycles = (-1) x $n;
0306 my @weights = (0) x $n;
0307 my @cindices = (-1) x $n;
0308 my $ncycles = 0;
0309 my $nmembers = 0;
0310 for (my $i = 0; $i < $n; ++$i) {
0311 next if $cycles[$i] >= 0;
0312 my $found = 0;
0313 $cycles[$i] = $i;
0314 for (my $j = $i + 1; $j < $n; ++$j) {
0315 next if $cycles[$j] >= 0;
0316 if ($tedges{$nodes[$i]}->{$nodes[$j]} && $tedges{$nodes[$j]}->{$nodes[$i]}) {
0317 $found = 1;
0318 $cycles[$j] = $i;
0319 }
0320 }
0321 if ($found) {
0322 my $weight = 0;
0323 for (my $j = $i; $j < $n; ++$j) {
0324 ++$weight if $cycles[$j] == $i;
0325 }
0326 for (my $j = $i; $j < $n; ++$j) {
0327 $weights[$j] = $weight if $cycles[$j] == $i;
0328 }
0329 $nmembers += $weight;
0330 $ncycles++;
0331 } else {
0332 $cycles[$i] = -1;
0333 }
0334 }
0335
0336 # sort packages into levelized order; strip principal cycle
0337 # members from their dependencies on other cycle members
0338 for (my $i = 0; $i < $n; ++$i) {
0339 next if $cycles[$i] != $i;
0340 for (my $j = $i + 1; $j < $n; ++$j) {
0341 next if $cycles[$j] != $i;
0342 delete $tedges{$nodes[$i]}->{$nodes[$j]};
0343 $lowerthan[1]->[$j] = 1;
0344 }
0345 }
0346
0347 # construct levelized array of package indices
0348 while ($nmapping < $n) {
0349 my $count = 0;
0350 my @current = (0) x $n;
0351 for (my $i = 0; $i < $n; ++$i) {
0352 next if $cycles[$i] >= 0 && $cycles[$i] != $i;
0353 next if $lowerthan[$nlevels]->[$i];
0354
0355 my $weight = 1;
0356 if ($cycles[$i] == $i) {
0357 next if $weights[$i] > $nlevels + 1;
0358 $weight = $weights[$i];
0359 }
0360
0361 my $level = $nlevels + 1 - $weight;
0362 my $j;
0363 for ($j = 0; $j < $n; ++$j) {
0364 next if $i == $j;
0365 last if $tedges{$nodes[$i]}->{$nodes[$j]} && !$lowerthan[$level]->[$j];
0366 }
0367 next if $j < $n;
0368
0369 $current[$i] = 1;
0370 $mapping[$nmapping++] = $i;
0371 $count++;
0372 if ($cycles[$i] == $i) {
0373 for ($j = $i + 1; $j < $n; ++$j) {
0374 next if $cycles[$j] != $i;
0375 $mapping[$nmapping++] = $j;
0376 $count++;
0377 $tedges{$nodes[$i]}->{$nodes[$j]} = 1;
0378 }
0379 }
0380 }
0381 for (my $i = 0; $i < $n; ++$i) {
0382 $current[$i] ||= $lowerthan[$nlevels][$i];
0383 }
0384 $levels[$nlevels++] = $count;
0385 @{$lowerthan[$nlevels]} = @current;
0386 @current = (0) x $n;
0387 }
0388 die "internal error" if $nmapping != $n;
0389
0390 # start loading level number array
0391 my $start = 0;
0392 for (my $i = 1; $i < $nlevels; ++$i) {
0393 my $top = $start + $levels[$i];
0394 for (my $j = $start; $j < $top; ++$j) {
0395 $levelnums[$mapping[$j]] = $i;
0396 }
0397 $start = $top;
0398 }
0399
0400 # sort packages on each level lexicographically
0401 $start = 0;
0402 for (my $k = 1; $k < $nlevels; ++$k) {
0403 my $top = $start + $levels[$k];
0404 for (my $i = $start + 1; $i < $top; ++$i) {
0405 for (my $j = $start; $j < $i; ++$j) {
0406 if (($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0) {
0407 my $tmp = $mapping[$i];
0408 $mapping[$i] = $mapping[$j];
0409 $mapping[$j] = $tmp;
0410 }
0411 }
0412 }
0413 $start = $top;
0414 }
0415
0416 # create @cindices from cycles array and the level map
0417 my $ncycle = 0;
0418 for (my $i = 0; $i < $n; ++$i) {
0419 my $label = $cycles[$mapping[$i]];
0420 next if $label < 0;
0421 my $index = $cindices[$mapping[$i]];
0422 next if $index >= 0 && $index < $ncycle;
0423 for (my $j = $i; $j < $n; ++$j) {
0424 $cindices[$mapping[$j]] = $ncycle if $label == $cycles[$mapping[$j]];
0425 }
0426 $ncycle++;
0427 }
0428 die "internal error" if $ncycle != $ncycles;
0429
0430 # sort packages on each level again but now grouping
0431 # cyclically dependent packages together
0432 $start = 0;
0433 for (my $k = 1; $k < $nlevels; ++$k) {
0434 my $top = $start + $levels[$k];
0435 for (my $i = $start + 1; $i < $top; ++$i) {
0436 for (my $j = $start; $j < $i; ++$j) {
0437 my $ci = $cindices[$mapping[$i]];
0438 my $cj = $cindices[$mapping[$j]];
0439 if ($ci < $cj || ($ci == $cj && ($nodes[$mapping[$i]] cmp $nodes[$mapping[$j]]) < 0)) {
0440 my $tmp = $mapping[$i];
0441 $mapping[$i] = $mapping[$j];
0442 $mapping[$j] = $tmp;
0443 }
0444 }
0445 }
0446 $start = $top;
0447 }
0448
0449 # calculate ccd
0450 my %tmp = map { $_ => {} } keys %tedges;
0451 foreach my $n (keys %tedges) {
0452 map { $tmp{$n}->{$_} = 1 } keys %{$tedges{$n}};
0453 }
0454
0455 for (my $i = 0; $i < $n; ++$i) {
0456 if ($levelnums[$i] == 0) {
0457 for (my $j = 0; $j < $n; ++$j) {
0458 delete $tmp{$nodes[$j]}->{$nodes[$i]};
0459 }
0460 } else {
0461 $tmp{$nodes[$i]}->{$nodes[$i]} = 1;
0462 }
0463 }
0464
0465 my $ccd = 0;
0466 map { $ccd += scalar keys %{$tmp{$_}} } keys %tmp;
0467
0468 if (0) {
0469 # make canonical representation: remove all redundant (transitive0edges
0470 my @tmp = map { [ (0) x $n ] } @nodes;
0471 for (my $i = 0; $i < $n; ++$i) {
0472 my $u = $mapping[$i];
0473 for (my $j = 0; $j < $n; ++$j) {
0474 my $v = $mapping[$j];
0475 my $bit = $tedges{$nodes[$u]}->{$nodes[$v]};
0476 $tmp[$i]->[$j] = $bit;
0477 }
0478 }
0479 # FIXME;
0480 }
0481
0482 $metrics->{NODES} = \@nodes;
0483 $metrics->{N} = $n;
0484 $metrics->{LEVELS} = \@levels;
0485 $metrics->{NLEVELS} = $nlevels;
0486 $metrics->{LEVELNUMS} = \@levelnums;
0487 $metrics->{MAPPING} = \@mapping;
0488 $metrics->{NMAPPINGS} = $nmapping;
0489
0490 $metrics->{TEDGES} = \%tedges;
0491
0492 $metrics->{CYCLES} = \@cycles;
0493 $metrics->{WEIGHTS} = \@weights;
0494 $metrics->{CINDICES} = \@cindices;
0495 $metrics->{NCYCLES} = $ncycles;
0496 $metrics->{NMEMBERS} = $nmembers;
0497
0498 $metrics->{CCD} = $ccd;
0499
0500 return $nmembers;
0501 }
0502
0503 my %url_results = ();
0504 sub node_url {
0505 my ($urls, $name) = @_;
0506 if (exists $url_results{$name}){return $url_results{$name};}
0507 foreach my $mapping (@$urls) {
0508 if ($name =~ /$mapping->[0]/) {
0509 my $N = $name;
0510 my $tmp = eval $mapping->[1];
0511 $url_results{$name} = $tmp;
0512 return $tmp;
0513 }
0514 }
0515 $url_results{$name} = undef;
0516 return undef;
0517 }
0518
0519 sub read_report {
0520 my ($edges, $nodes, $file, $part) = @_;
0521 my $node = undef;
0522
0523 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0524 while (<FILE>) {
0525 last if /^\# $part/;
0526 }
0527
0528 while (<FILE>) {
0529 chomp;
0530 last if (/^\#\#\#\#\#/);
0531 next if (/^\s*$/);
0532
0533 if (/^\S/) {
0534 chop;
0535 s% ([^/]+)$%%;
0536 $node = $_;
0537 $nodes->{$_} = 1;
0538 $edges->{$_} ||= {};
0539 } elsif (/^\s+\S/) {
0540 print STDERR "$file: warning: no current node for a dependency\n"
0541 if ! $node;
0542
0543 s/\s//g;
0544 $nodes->{$_} = 1;
0545 $edges->{$_} ||= {};
0546 $edges->{$node}->{$_} ||= [];
0547 push(@{$edges->{$node}->{$_}}, ':hard');
0548 } else {
0549 print STDERR "$file: warning: unrecognized line: `$_'\n";
0550 }
0551 }
0552 close(FILE);
0553 }
0554
0555 sub keep_nodes {
0556 my ($kept_nodes, $edges, $node) = @_;
0557 $kept_nodes->{$node} = 1;
0558 foreach my $edge (keys %{$edges->{$node}}) {
0559 if (!exists $kept_nodes->{$edge}){
0560 &keep_nodes ($kept_nodes, $edges, $edge);
0561 }
0562 }
0563 }
0564
0565 sub remove_nodes {
0566 my ($edges, $nodes, $spart) = @_;
0567 my %kept_nodes = ();
0568 my $node;
0569 foreach $node (keys %$nodes) {
0570 if (($node=~/$spart/) && (!exists $kept_nodes{$node})){
0571 &keep_nodes (\%kept_nodes, $edges, $node);
0572 }
0573 }
0574 foreach $node (keys %$nodes) {
0575 if (!exists $kept_nodes{$node}){
0576 delete $nodes->{$node};
0577 delete $edges->{$node};
0578 }
0579 }
0580 }
0581
0582 sub recalculate {
0583 my ($renames, $edges, $nodes, $vector) = @_;
0584 my %indexs = ();
0585 my @newvec = ();
0586 foreach my $item (@$vector){
0587 my $node = &rename_node($renames, $item->[0]);
0588 my $edge = &rename_node($renames, $item->[1]);
0589 my $count = $item->[2];
0590 next if (!exists $edges->{$node}->{$edge});
0591 if (!exists $indexs{"$node:$edge"}){
0592 $indexs{"$node:$edge"} = @newvec;
0593 $item->[0]=$node;
0594 $item->[1]=$edge;
0595 push(@newvec, $item);
0596 }
0597 else{
0598 my $index = $indexs{"$node:$edge"};
0599 my $pcount = $newvec[$index]->[2];
0600 $newvec[$index]->[2] = $pcount + $count;
0601 }
0602 }
0603 while (@$vector > 0){pop @$vector;}
0604 foreach my $item (@newvec){push (@$vector, $item);}
0605 }
0606
0607 sub apply_renames {
0608 my ($renames, $edges, $nodes) = @_;
0609 my %nnodes = ();
0610 foreach my $node (keys %$nodes){
0611 my $nnode = &rename_node($renames, $node);
0612 $nnodes{$nnode} ||= {};
0613 foreach my $edge (keys %{$edges->{$node}}){
0614 my $nedge = &rename_node($renames, $edge);
0615 $nnodes{$nnode}->{$nedge} = 1;
0616 }
0617 delete $nodes->{$node};
0618 delete $edges->{$node};
0619 }
0620 foreach my $node (keys %nnodes){
0621 foreach my $edge (keys %{$nnodes{$node}}){
0622 $nodes->{$node} = 1; $edges->{$node} ||= {};
0623 $nodes->{$edge} = 1; $edges->{$edge} ||= {};
0624 $edges->{$node}->{$edge} ||= [];
0625 if(!exists $edges->{$node}->{$edge}->[0]){
0626 push(@{$edges->{$node}->{$edge}}, ':hard');
0627 }
0628 }
0629 }
0630 }
0631
0632 sub read_counts {
0633 my ($renames, $edges, $nodes, $vector, $note, $file) = @_;
0634 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0635 while (<FILE>) {
0636 chomp;
0637 if (! /^\s*(\d+)\s+(\S+)\s+(\S+(,\s+\S+)?)(\s+via\s+(\S+))?$/) {
0638 print STDERR "$file: error: bad line `$_'\n";
0639 next;
0640 }
0641 my $node=$2;
0642 my $edge=$3;
0643 next if &rename_node($renames, $node) eq &rename_node($renames, $edge);
0644 $nodes->{$node} = 1; $edges->{$node} ||= {};
0645 $nodes->{$edge} = 1; $edges->{$edge} ||= {};
0646 $edges->{$node}->{$edge} ||= [];
0647 push(@{$edges->{$node}->{$edge}}, ':hard');
0648 push(@$vector, [ $node, $edge, $1, $note, $5 || '', -1]);
0649 }
0650 close(FILE);
0651 }
0652
0653 sub read_loglinks {
0654 my ($edges, $nodes, $file) = @_;
0655 my @xtargets = ();
0656 my @xreasons = ();
0657
0658 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0659 while (<FILE>) {
0660 chomp; s/\#.*//; s/\s*$//;
0661 next if (/^\s*$/);
0662
0663 if (/^(\S+(\s*,\s*\S+)*)(\s*:\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0664 @xreasons = split(/\s*,\s*/, $1);
0665 @xtargets = split(/\s*,\s*/, $4 || '');
0666 } elsif (/^\s+(\S+(\s*,\s*\S+)*)(\s*->\s*(\S+(\s*,\s*\S+)*))?$/) {
0667 my @sources = split(/\s*,\s*/, $1);
0668 my @targets = ($4 ? split(/\s*,\s*/, $4) : @xtargets);
0669 foreach my $source (grep(exists $nodes->{$_}, @sources)) {
0670 $edges->{$source} ||= {};
0671 map { $edges->{$source}->{$_} ||= [];
0672 push(@{$edges->{$source}->{$_}}, @xreasons); } @targets;
0673 }
0674 } else {
0675 print STDERR "$file: warning: unrecognized line: `$_'\n";
0676 }
0677 }
0678 close(FILE);
0679 }
0680
0681 sub read_groups {
0682 my ($groups, $file) = @_;
0683 my $i = 0;
0684
0685 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0686 while (<FILE>) {
0687 chomp;
0688 while (/\\$/ && !eof(FILE)) {
0689 chop; $_ .= <FILE>; chomp;
0690 }
0691 if (/\\$/) {
0692 print STDERR "$file: warning: incomplete last line\n";
0693 chop;
0694 }
0695 s/\#.*//; s/^\s*//; s/\s*$//;
0696 next if (/^$/);
0697
0698 if (/^group\s+(\S+)\s+(.*)\s+--\s+(.*)$/) {
0699 push(@$groups, [ $i++, $1, [ split(/\s+/, $2) ], $3 ]);
0700 } else {
0701 print STDERR "$file: warning: unrecognized line: `$_'\n";
0702 }
0703 }
0704 close(FILE);
0705 }
0706
0707 sub read_renames {
0708 my ($renames, $file) = @_;
0709 my $i = 0;
0710
0711 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0712 while (<FILE>) {
0713 chomp;
0714 while (/\\$/ && !eof(FILE)) {
0715 chop; $_ .= <FILE>; chomp;
0716 }
0717 if (/\\$/) {
0718 print STDERR "$file: warning: incomplete last line\n";
0719 chop;
0720 }
0721 s/\#.*//; s/^\s*//; s/\s*$//;
0722 next if (/^$/);
0723
0724 if (/^rename\s+([^:]+):(.*)$/) {
0725 push(@$renames, [ $1, $2 ]);
0726 } else {
0727 print STDERR "$file: warning: unrecognized line: `$_'\n";
0728 }
0729 }
0730 close(FILE);
0731 }
0732
0733 sub read_urls {
0734 my ($urls, $file) = @_;
0735 my $i = 0;
0736
0737 open(FILE, $file) || die "$file: error: cannot open: $!\n";
0738 while (<FILE>) {
0739 chomp;
0740 while (/\\$/ && !eof(FILE)) {
0741 chop; $_ .= <FILE>; chomp;
0742 }
0743 if (/\\$/) {
0744 print STDERR "$file: warning: incomplete last line\n";
0745 chop;
0746 }
0747 s/^\s*//; s/\s*$//;
0748 next if (/^$/);
0749
0750 if (/^([^:]+):(.*)$/) {
0751 push(@$urls, [ $1, $2 ]);
0752 } else {
0753 print STDERR "$file: warning: unrecognized line: `$_'\n";
0754 }
0755 }
0756 close(FILE);
0757 }
0758
0759 ######################################################################
0760 my $usage =
0761 "usage: $me [--text|--html|--side-by-side] [--group=FILE] [--rename=FILE]\n"
0762 . " INCS LIBS FILTER INCLUSIVE? [RE...]\n"
0763 . " [! RESTRICT-INCLUSIVE? RESTRICT-RE...]\n"
0764 . " [-- EXTRA EXTRA-INCLUSIVE? EXTRA-RE...]\n";
0765
0766 my $mode = 'text';
0767 my $groupfile = '/dev/null';
0768 my $renamefile = '/dev/null';
0769 my $default_group = [ -1, 'other', [ '.*' ], '1 1 1' ];
0770
0771 while (scalar @ARGV && $ARGV[0] =~ /^--(.*)$/) {
0772 my $arg = $1;
0773 if ($1 eq 'text' || $1 eq 'html' || $1 eq 'side-by-side') {
0774 $mode = $1;
0775 } elsif ($1 =~ /^group=(.*)/) {
0776 $groupfile = $1;
0777 } elsif ($1 =~ /^rename=(.*)/) {
0778 $renamefile = $1;
0779 } else {
0780 print STDERR "unrecognised option `$arg'\n";
0781 die $usage;
0782 }
0783 shift(@ARGV);
0784 }
0785
0786 die $usage if (scalar @ARGV < 3);
0787 my ($incfile, $libfile, $filter, $inclusive, @restargs) = @ARGV;
0788 my ($rinclusive, $xfile, $xinclusive, @rx, @rrx, @xrx) = (1, "/dev/null", 1);
0789 $inclusive = &validate_flag($inclusive, "INCLUSIVE?");
0790
0791 while (scalar @restargs) {
0792 my $arg = shift(@restargs);
0793 if ($arg eq '!') {
0794 die $usage if (scalar @restargs < 2);
0795 $rinclusive = &validate_flag(shift(@restargs), "RESTRICT-INCLUSIVE?");
0796 while (scalar @restargs && $restargs[0] ne '--') {
0797 push(@rrx, shift(@restargs));
0798 }
0799 } elsif ($arg eq '--') {
0800 die $usage if (scalar @restargs < 2);
0801 $xfile = shift(@restargs);
0802 $xinclusive = &validate_flag(shift(@restargs), "EXTRA-INCLUSIVE?");
0803 @xrx = @restargs;
0804 $xrx[0] ||= '.*';
0805 last;
0806 } else {
0807 push(@rx, $arg);
0808 }
0809 }
0810 $rx[0] ||= '.*';
0811 $rrx[0] ||= '.*';
0812
0813 ######################################################################
0814 my @incdeps = ();
0815 my @libdeps = ();
0816 my %nodes = ();
0817 my %gnodes = ();
0818 my %edges = ();
0819 my %gedges = ();
0820 my @roots = ();
0821 my @all = ();
0822 my @groups = ();
0823 my %grouping = ();
0824 my @renames = ();
0825
0826 # Read in renames.
0827 &read_renames(\@renames, $renamefile);
0828
0829 # Read in the files. The count files are output from "sort | uniq -c".
0830 &read_counts(\@renames, \%edges, \%nodes, \@incdeps, 'includes', $incfile);
0831 &read_counts(\@renames, \%edges, \%nodes, \@libdeps, 'libraries', $libfile);
0832
0833 # Read in the extra-deps file.
0834 &read_loglinks(\%edges, \%nodes, $xfile);
0835
0836 # Remove the unwanted nodes
0837 &remove_nodes(\%edges, \%nodes, $filter) if ($filter ne "");
0838 &apply_renames(\@renames, \%edges, \%nodes);
0839
0840 # recalculate the incdeps, libdeps
0841 &recalculate (\@renames, \%edges, \%nodes, \@incdeps);
0842 &recalculate (\@renames, \%edges, \%nodes, \@libdeps);
0843
0844 # Read in grouping.
0845 &read_groups(\@groups, $groupfile);
0846
0847 # Calculate which nodes can be reached.
0848 foreach my $node (keys %nodes) {
0849 push(@roots, $node) if (grep($node =~ /$_/, @rx) && 1) eq $inclusive;
0850 }
0851
0852 foreach (@roots) {
0853 &do_node(\%gedges,\%gnodes, \%edges,\%nodes, $rinclusive,\@rrx, $xinclusive,\@xrx, undef,$_,1);
0854 }
0855 @all = grep(exists $gnodes{$_->[0]}, @incdeps, @libdeps);
0856
0857 # Group nodes
0858 map { &group_node(\%grouping, \@groups, $_, $default_group) } keys %nodes;
0859
0860 ######################################################################
0861 # Interleave and merge dependency counts.
0862 my @mixed = sort { $a->[0] cmp $b->[0]
0863 || $a->[1] cmp $b->[1]
0864 || $a->[3] cmp $b->[3]
0865 || $a->[4] cmp $b->[4] } @all;
0866
0867 for (my ($i, $skip) = (0, 0); $i < scalar @mixed; ++$i) {
0868 if ($skip) {
0869 $skip = 0;
0870 next;
0871 }
0872
0873 if ($mixed[$i]->[3] eq 'includes'
0874 && $mixed[$i]->[4] eq ''
0875 && $i < scalar @mixed - 1
0876 && $mixed[$i+1]->[0] eq $mixed[$i]->[0]
0877 && $mixed[$i+1]->[1] eq $mixed[$i]->[1]) {
0878 $mixed[$i]->[5] = $mixed[$i+1]->[2];
0879 $skip = 1;
0880 } else {
0881 $mixed[$i]->[5] = 0;
0882 }
0883 }
0884
0885 my @merged = grep { $_->[5] >= 0 } @mixed;
0886
0887 ######################################################################
0888 # Tabulate dependency counts.
0889 my %packages = ();
0890 map { $packages{$_->[0]} = 1; $packages{$_->[1]} = 1; } @merged;
0891
0892 my $i = 0;
0893 my @labels = ();
0894 foreach (sort { $a->[1] cmp $b->[1] }
0895 map { [ $_, &rename_node(\@renames, $_) ] }
0896 keys %packages) {
0897 push(@labels, $_->[0]);
0898 $packages{$_->[0]} = $i++;
0899 }
0900
0901 my $nlabels = $i;
0902 my $longest = &max(map { length($_) } @labels) || 0;
0903 my @table = map { [ map { [ (0) x $CELL_N ] } @labels ] } @labels;
0904 my @summary = map { [ (0) x $SUM_N ] } @labels;
0905
0906 foreach (@merged) {
0907 &dep_count(\@table, $packages{$_->[0]}, $packages{$_->[1]}, $_);
0908 }
0909
0910 for (my $i = 0; $i < $nlabels; ++$i) {
0911 for (my $j = 0; $j < $nlabels; ++$j) {
0912 &dep_xcount(\%edges, $rinclusive, \@rrx, $xinclusive, \@xrx, \@labels, \@table, $i, $j);
0913 }
0914 }
0915
0916 for (my $i = 0; $i < $nlabels; ++$i) {
0917 &dep_summarise(\@summary, \@labels, \@table, $i);
0918 }
0919
0920 ######################################################################
0921 # Print out dependency counts.
0922 my $clear = '';
0923 my $shadow = ' bgcolor="#eeeeee"';
0924
0925 sub cell_label {
0926 my ($cell, $part) = @_;
0927 die "internal error: no such part $part"
0928 if ! grep($part eq $_, qw(includes libraries logical all));
0929
0930 if ($part eq 'all') {
0931 my $val = "";
0932 if ($cell->[$CELL_LIB_OUT_COUNT] || $cell->[$CELL_INC_OUT_COUNT]) {
0933 $val .= "$cell->[$CELL_LIB_OUT_COUNT]/$cell->[$CELL_INC_OUT_COUNT]";
0934 }
0935 if ($cell->[$CELL_LOG_OUT_EDGES]) {
0936 $val .= "/" if $val;
0937 $val .= "x$cell->[$CELL_LOG_OUT_EDGES]";
0938 }
0939 return $val;
0940 } elsif ($part eq 'libraries') {
0941 return $cell->[$CELL_LIB_OUT_COUNT];
0942 } elsif ($part eq 'includes') {
0943 return $cell->[$CELL_INC_OUT_COUNT];
0944 } elsif ($part eq 'logical') {
0945 return $cell->[$CELL_LOG_OUT_EDGES];
0946 }
0947 }
0948
0949 sub print_html_table {
0950 my ($part, $caption, $dobreak) = @_;
0951
0952 my $break = $dobreak ? " class='page-break'" : "";
0953 print "<?page-break?>\n" if $dobreak;
0954 print "<H3$break>$caption</H3>\n" if $caption;
0955 for (my $N = 0; $N*40 < $nlabels; ++$N) {
0956 print "<TABLE BORDER='0'>\n";
0957 print " <TR><TH$shadow> </TH><TH$shadow> </TH>";
0958 for (my $j = $N*40; $j < $nlabels && $j < ($N+1)*40; ++$j) {
0959 print "<TH$shadow><A onMouseOver=\"window.status=\'".&rename_node(\@renames, $labels[$j])."\'; return true\">" . ($j+1) . "</A></TH>";
0960 }
0961 print "</TR>\n";
0962
0963 for (my $i = 0; $i < $nlabels; ++$i) {
0964 print " <TR><TH$shadow>" . ($i+1) . "</TH><TH align='left'$shadow>"
0965 . &rename_node(\@renames, $labels[$i]) . "</TH>";
0966 for (my $j = $N*40; $j < $nlabels && $j < ($N+1)*40; ++$j) {
0967 print "<TD" . (($i + $j) % 2 ? $shadow : $clear) . "><A onMouseOver=\"window.status=\'".&rename_node(\@renames, $labels[$j])."\'; return true\">";
0968 print (&cell_label($table[$i]->[$j], $part) || ' ');
0969 print "</A></TD>";
0970 }
0971 print "</TR>\n";
0972 }
0973 print "</TABLE>\n";
0974 }
0975 }
0976
0977 sub print_html_summary {
0978 my $caption = shift;
0979
0980 print "<?page-break?>\n";
0981 print "<H3 class='page-break'>$caption</H3>\n" if $caption;
0982 print "<OL>\n";
0983 print map { " <LI> $_\n" } @SUM_LABELS;
0984 print "</OL>\n";
0985 print "<TABLE BORDER='0'>\n";
0986 print " <TR><TH$shadow> </TH>";
0987 for (my $i = 0; $i < $SUM_N; ++$i) {
0988 print "<TH$shadow><A onMouseOver=\"window.status=\'".$SUM_LABELS[$i]."\'; return true\">". ($i+1) . "</A></TH>";
0989 }
0990 print "</TR>\n";
0991
0992 my $in = 0;
0993 my $out = 0;
0994 for (my $i = 0; $i < $nlabels; ++$i) {
0995 $in += $summary[$i]->[$SUM_ALL_IN_EDGES];
0996 $out += $summary[$i]->[$SUM_ALL_OUT_EDGES];
0997
0998 print " <TR><TH align='left'$shadow>" . &rename_node(\@renames, $labels[$i]) . "</TH>";
0999 for(my $x=0;$x<scalar(@{$summary[$i]});$x++)
1000 {
1001 my $v=$summary[$i][$x];
1002 print "<TD" . ($i % 8 >= 4 ? $shadow : "") . "><A onMouseOver=\"window.status=\'".$SUM_LABELS[$x]."\'; return true\">$v</TD>";
1003 #print map { "<TD" . ($i % 8 >= 4 ? $shadow : "") . ">$_</TD>" }
1004 # @{$summary[$i]};
1005 }
1006 print "</TR>\n";
1007 }
1008 # NB: $in == $out by now
1009 print " <TR>\n";
1010 print " <TD> </TD>\n";
1011 print " <TD COLSPAN='@{[$SUM_N-2]}'>Average # of outgoing edges</TD>\n";
1012 if ($nlabels){
1013 print " <TD COLSPAN='2'>" . sprintf("%.2f", $out / $nlabels) . "</TD>\n";
1014 }
1015 print " </TR>\n";
1016 print "</TABLE>\n";
1017 }
1018
1019 if ($mode eq 'text') {
1020 print '#' x 70, "\n# Mixed dependencies\n\n";
1021 foreach (@mixed) {
1022 print sprintf "%-12s", "$_->[2]";
1023 print "$_->[0] $_->[1] $_->[3] $_->[4]\n";
1024 }
1025
1026 print "\n", '#' x 70, "\n# Merged dependencies\n\n";
1027 foreach (@merged) {
1028 print sprintf "%-12s",
1029 &cell_label($table[$packages{$_->[0]}] ->[$packages{$_->[1]}], 'all');
1030 print "$_->[0] $_->[1] $_->[4]\n";
1031 }
1032
1033 print "\n", '#' x 70, "\n# Edge summary\n";
1034 for (my $i = 0; $i < $SUM_N; ++$i) {
1035 print "# (@{[$i+1]}) $SUM_LABELS[$i]\n";
1036 }
1037 print "\n";
1038 my $width = (int(($longest+2)/8)+1)*8;
1039 my $format = "%-" . $width . "s";
1040 print sprintf $format, "# PACKAGE";
1041 print join("\t", map { "($_)" } (1 .. $SUM_N)), "\n";
1042 print "# ", '-' x ($width + $SUM_N * 8 - 2), "\n";
1043
1044 my $out = 0;
1045 for (my $i = 0; $i < $nlabels; ++$i) {
1046 $out += $summary[$i]->[$SUM_ALL_OUT_EDGES];
1047 print sprintf $format, $labels[$i];
1048 print join("\t", @{$summary[$i]}), "\n";
1049 }
1050 print "# ", '-' x ($width + $SUM_N * 8 - 2), "\n";
1051 print sprintf $format, "# PACKAGE";
1052 print join("\t", map { "($_)" } (1 .. $SUM_N)), "\n";
1053 if ($nlabels){
1054 print "\nAverage # of outgoing edges: " . sprintf("%.2f", $out / $nlabels) . "\n";
1055 }
1056 } elsif ($mode eq 'html') {
1057 &print_html_table('libraries', "Symbol dependencies", 0);
1058 &print_html_table('includes', "Header file dependencies", 1);
1059 &print_html_table('logical', "Logical dependencies", 1) if $xfile ne '/dev/null';
1060 &print_html_table('all', "All dependencies", 1);
1061 &print_html_summary("Summary");
1062 } elsif ($mode eq 'side-by-side') {
1063 # reorder the nodes according to grouping; use group color
1064 my @order = sort { $grouping{$a}->[0]->[0] cmp $grouping{$b}->[0]->[0]
1065 || $grouping{$a}->[1] cmp $grouping{$b}->[1]
1066 || &file_part($a) cmp &file_part($b)
1067 || $a cmp $b } @labels;
1068 my %ordering = ();
1069 for (my $i = 0; $i < $nlabels; ++$i) {
1070 $ordering{$order[$i]} = $i;
1071 }
1072
1073 my $maxheight = 800;
1074 my $maxwidth = 550;
1075 my $fontsize = 8;
1076 my $boxheight = $fontsize + 4;
1077 my $freeheight= max(0, $maxheight - ($nlabels+1) * $boxheight);
1078 my $boxsep = int(100 * $freeheight / ($nlabels+1))/100;
1079 my $boxwidth = $fontsize * 20;
1080 my $boxoff = 2;
1081 my $sidesplit = $maxwidth - 2*$boxwidth;
1082 my $height = ($nlabels + 1) * ($boxheight + $boxsep);
1083 my $llx = 20;
1084 my $lly = 20;
1085 my @bbox = ($llx, $lly,
1086 int($llx + $sidesplit + $boxwidth*2 + .5),
1087 int($lly + $height + .5));
1088 my %boxes = ();
1089
1090 for (my $i = 0; $i < $nlabels; ++$i) {
1091 $boxes{$order[$i]} =
1092 [ $llx, $lly + $height - ($i+1)*($boxheight+$boxsep) - $boxheight ];
1093 }
1094
1095 my $ps_preamble =
1096 ("/dobox {\n" .
1097 " /tb exch def /ts exch def /th exch def\n" .
1098 " /bb exch def /bs exch def /bh exch def\n" .
1099 " currentpoint /y exch def /x exch def\n" .
1100 " newpath x y moveto boxwidth 0 rlineto 0 boxheight rlineto\n" .
1101 " boxwidth neg 0 rlineto closepath\n" .
1102 " gsave bh bs bb sethsbcolor fill grestore\n".
1103 " 0 0 0 sethsbcolor stroke\n" .
1104 " x y moveto boxoff boxoff rmoveto\n" .
1105 " th ts tb sethsbcolor show } bind def\n" .
1106 "/dashed { [9 9] 0 setdash } bind def\n" .
1107 "/dotted { [1 6] 0 setdash } bind def\n" .
1108 "/solid { [] 0 setdash } bind def\n" .
1109 "/doline { moveto lineto cvx sethsbcolor stroke pop } bind def");
1110
1111 print "%!PS-Adobe-3.0\n";
1112 print "%%BoundingBox: ", join(" ", @bbox), "\n";
1113 print "%%DocumentNeededFonts: font Helvetica Helvetica-Bold\n";
1114 print "%%BeginProlog\n$ps_preamble\n%%EndProlog\n";
1115 print "%%Pages: 1\n";
1116 print "%%Page: 1 1\n";
1117 print "/boxwidth $boxwidth def\n";
1118 print "/boxheight $boxheight def\n";
1119 print "/boxoff $boxoff def\n";
1120 print "save $llx $lly moveto\n";
1121 print "(Helvetica-Bold) findfont 8 scalefont setfont\n";
1122
1123 foreach (@order) {
1124 my $name = &rename_node(\@renames, $_);
1125 my @hsvcolor = split(/\s+/, $grouping{$_}->[0]->[3]);
1126 my $y = (&rgb2yiq(&hsv2rgb(@hsvcolor)))[0];
1127 my $textcolor = ($y < .5 ? "0 0 1" : "0 0 0");
1128 print "\n($name) $boxes{$_}->[0] $boxes{$_}->[1] moveto "
1129 . join(" ", @hsvcolor) . " $textcolor dobox";
1130 print "\n($name) " . ($boxwidth+$sidesplit+$boxes{$_}->[0])
1131 . " $boxes{$_}->[1] moveto " . join(" ", @hsvcolor) . " $textcolor dobox";
1132 }
1133 print "\n\n(Helvetica-Bold) findfont 10 scalefont setfont 0 0 0 sethsbcolor\n";
1134 print sprintf "%d %d moveto (%s) show\n",
1135 $llx, $lly + $height - $boxheight, "Out";
1136 print sprintf "%d %d moveto (%s) show\n",
1137 $llx + $sidesplit + $boxwidth, $lly + $height - $boxheight, "In";
1138 print "(Helvetica) findfont 8 scalefont setfont\n";
1139
1140 for (my $i = 0; $i < $nlabels; ++$i) {
1141 for (my $j = 0; $j < $nlabels; ++$j) {
1142 my $val = &cell_label($table[$i]->[$j], 'all');
1143 next if (! $val);
1144
1145 print sprintf "(%s) %s %d %d %d %d doline\n",
1146 $val, ($val =~ m|^x| ? '0 0 .5' : '0 0 0'),
1147 $boxes{$order[$ordering{$labels[$i]}]}->[0] + $boxwidth,
1148 $boxes{$order[$ordering{$labels[$i]}]}->[1] + $boxheight / 2,
1149 $boxes{$order[$ordering{$labels[$j]}]}->[0] + $sidesplit + $boxwidth,
1150 $boxes{$order[$ordering{$labels[$j]}]}->[1] + $boxheight / 2;
1151 }
1152 }
1153 print "restore showpage\n";
1154 print "%%EOF\n";
1155 } else {
1156 die "internal error: unknown mode $mode";
1157 }