1#!/usr/bin/perl -w 2use strict; 3our (@Changed, $TAP); 4use File::Compare; 5use Symbol; 6use Carp; 7use Text::Wrap(); 8 9# Common functions needed by the regen scripts 10 11our $Needs_Write = $^O eq 'cygwin' || $^O eq 'os2' || $^O eq 'MSWin32'; 12 13our $Verbose = 0; 14@ARGV = grep { not($_ eq '-q' and $Verbose = -1) } 15 grep { not($_ eq '--tap' and $TAP = 1) } 16 grep { not($_ eq '-v' and $Verbose = 1) } @ARGV; 17 18END { 19 print STDOUT "Changed: @Changed\n" if @Changed; 20} 21 22sub safer_unlink { 23 my @names = @_; 24 my $cnt = 0; 25 26 my $name; 27 foreach $name (@names) { 28 next unless -e $name; 29 chmod 0777, $name if $Needs_Write; 30 ( CORE::unlink($name) and ++$cnt 31 or warn "Couldn't unlink $name: $!\n" ); 32 } 33 return $cnt; 34} 35 36# Open a new file. 37sub open_new { 38 my ($final_name, $mode, $header, $force) = @_; 39 my $name = $final_name . '-new'; 40 my $lang = 41 $final_name =~ /\.pod\z/ ? 'Pod' : 42 $final_name =~ /\.(?:c|h|inc|tab|act)\z/ ? 'C' : 43 $final_name =~ /\.gitignore\z/ ? 'None' : 44 'Perl'; 45 if ($force && -e $final_name) { 46 chmod 0777, $name if $Needs_Write; 47 CORE::unlink $final_name 48 or die "Couldn't unlink $final_name: $!\n"; 49 } 50 my $fh = gensym; 51 if (!defined $mode or $mode eq '>') { 52 if (-f $name) { 53 unlink $name or die "$name exists but can't unlink: $!"; 54 } 55 open $fh, '>', $name or die "Can't create $name: $!"; 56 } elsif ($mode eq '>>') { 57 open $fh, '>>', $name or die "Can't append to $name: $!"; 58 } else { 59 die "Unhandled open mode '$mode'"; 60 } 61 @{*$fh}{qw(name final_name lang force)} 62 = ($name, $final_name, $lang, $force); 63 binmode $fh; 64 print {$fh} read_only_top(lang => $lang, %$header) if $header; 65 $fh; 66} 67 68sub close_and_rename { 69 my $fh = shift; 70 my ($name, $final_name, $force) = @{*{$fh}}{qw(name final_name force)}; 71 close $fh or die "Error closing $name: $!"; 72 73 if ($TAP) { 74 # Don't use compare because if there are errors it doesn't give any 75 # way to generate diagnostics about what went wrong. 76 # These files are small enough to read into memory. 77 local $/; 78 # This is the file we just closed, so it should open cleanly: 79 open $fh, '<', $name 80 or die "Can't open '$name': $!"; 81 my $want = <$fh>; 82 die "Can't read '$name': $!" 83 unless defined $want; 84 close $fh 85 or die "Can't close '$name': $!"; 86 87 my $fail; 88 if (!open $fh, '<', $final_name) { 89 $fail = "Can't open '$final_name': $!"; 90 } else { 91 my $have = <$fh>; 92 if (!defined $have) { 93 $fail = "Can't read '$final_name': $!"; 94 close $fh; 95 } elsif (!close $fh) { 96 $fail = "Can't close '$final_name': $!"; 97 } elsif ($want ne $have) { 98 $fail = "'$name' and '$final_name' differ"; 99 } 100 } 101 # If someone wants to run t/porting/regen.t and keep the 102 # changes then they can set this env var, otherwise we 103 # unlink the generated file regardless. 104 my $keep_changes= $ENV{"REGEN_T_KEEP_CHANGES"}; 105 safer_unlink($name) unless $keep_changes; 106 if ($fail) { 107 print STDOUT "not ok - $0 $final_name\n"; 108 die "$fail\n"; 109 } else { 110 print STDOUT "ok - $0 $final_name\n"; 111 } 112 # If we get here then the file hasn't changed, and we should 113 # delete the new version if they have requested we keep changes 114 # as we wont have deleted it above like we would normally. 115 safer_unlink($name) if $keep_changes; 116 return; 117 } 118 unless ($force) { 119 if (compare($name, $final_name) == 0) { 120 warn "no changes between '$name' & '$final_name'\n" if $Verbose > 0; 121 safer_unlink($name); 122 return; 123 } 124 warn "changed '$name' to '$final_name'\n" if $Verbose > 0; 125 push @Changed, $final_name unless $Verbose < 0; 126 } 127 128 # Some DOSish systems can't rename over an existing file: 129 safer_unlink $final_name; 130 chmod 0600, $name if $Needs_Write; 131 rename $name, $final_name or die "renaming $name to $final_name: $!"; 132} 133 134my %lang_opener = ( 135 Perl => '# ', 136 Pod => '', 137 C => '/* ', 138 None => '# ', 139); 140 141sub read_only_top { 142 my %args = @_; 143 my $lang = $args{lang}; 144 die "Missing language argument" unless defined $lang; 145 die "Unknown language argument '$lang'" 146 unless exists $lang_opener{$lang}; 147 my $style = $args{style} ? " $args{style} " : ' '; 148 149 # Generate the "modeline" for syntax highlighting based on the language 150 my $raw = "-*- " . ($lang eq 'None' ? "" : "mode: $lang; ") . "buffer-read-only: t -*-\n"; 151 152 if ($args{file}) { 153 $raw .= "\n $args{file}\n"; 154 } 155 if ($args{copyright}) { 156 local $" = ', '; 157 $raw .= wrap(75, ' ', ' ', <<"EOM") . "\n"; 158 159Copyright (C) @{$args{copyright}} by\0Larry\0Wall\0and\0others 160 161You may distribute under the terms of either the GNU General Public 162License or the Artistic License, as specified in the README file. 163EOM 164 } 165 166 $raw .= "!!!!!!! DO NOT EDIT THIS FILE !!!!!!!\n"; 167 168 if ($args{by}) { 169 $raw .= "This file is built by $args{by}"; 170 if ($args{from}) { 171 my @from = ref $args{from} eq 'ARRAY' ? @{$args{from}} : $args{from}; 172 my $last = pop @from; 173 if (@from) { 174 $raw .= ' from ' . join (', ', @from) . " and $last"; 175 } else { 176 $raw .= " from $last"; 177 } 178 } 179 $raw .= ".\n"; 180 } 181 $raw .= "Any changes made here will be lost!\n"; 182 $raw .= $args{final} if $args{final}; 183 184 my $cooked = $lang eq 'C' 185 ? wrap(78, '/* ', $style, $raw) . " */\n\n" 186 : wrap(78, $lang_opener{$lang}, $lang_opener{$lang}, $raw) . "\n"; 187 $cooked =~ tr/\0/ /; # Don't break Larry's name etc 188 $cooked =~ s/ +$//mg; # Remove all trailing spaces 189 $cooked =~ s! \*/\n!$args{quote}!s if $args{quote}; 190 return $cooked; 191} 192 193sub read_only_bottom_close_and_rename { 194 my ($fh, $sources) = @_; 195 my ($name, $lang, $final_name) = @{*{$fh}}{qw(name lang final_name)}; 196 confess "bad fh in read_only_bottom_close_and_rename" unless $name; 197 die "No final name specified at open time for $name" 198 unless $final_name; 199 200 my $comment; 201 if ($sources) { 202 $comment = "Generated from:\n"; 203 foreach my $file (sort @$sources) { 204 my $digest = (-e $file) 205 ? digest($file) 206 # Use a random number that won't match the real 207 # digest, so will always show as out-of-date, so 208 # Porting tests likely will fail drawing attention 209 # to the problem. 210 : int(rand(1_000_000)); 211 $comment .= "$digest $file\n"; 212 } 213 } 214 $comment .= "ex: set ro" . ($lang eq 'None' ? "" : " ft=\L$lang\E") . ":"; 215 216 if ($lang eq 'Pod') { 217 # nothing 218 } elsif ($lang eq 'C') { 219 $comment =~ s/^/ * /mg; 220 $comment =~ s! \* !/* !; 221 $comment .= " */"; 222 } else { 223 $comment =~ s/^/# /mg; 224 } 225 print $fh "\n$comment\n"; 226 227 close_and_rename($fh); 228 229 return; 230} 231 232sub tab { 233 no warnings 'numeric'; 234 my ($l, $t) = @_; 235 $t .= "\t" x ($l - (length($t) + 1) / 8); 236 $t; 237} 238 239sub digest { 240 my $file = shift; 241 # Need to defer loading this, as the main regen scripts work back to 5.004, 242 # and likely we don't even have this module on every 5.8 install yet: 243 require Digest::SHA; 244 245 local ($/, *FH); 246 open FH, '<', $file or die "Can't open $file: $!"; 247 my $raw = <FH>; 248 close FH or die "Can't close $file: $!"; 249 return Digest::SHA::sha256_hex($raw); 250}; 251 252sub wrap { 253 local $Text::Wrap::columns = shift; 254 local $Text::Wrap::unexpand = 0; 255 Text::Wrap::wrap(@_); 256} 257 258sub columnarize_list { 259 my $listp = shift; 260 my $max_width = shift; 261 262 # Returns the list (pointed to by 'listp') of text items, but arranged 263 # tabularly, like the default output of the 'ls' command. The first few 264 # items of the list will be in the first column; the next batch in the 265 # second, etc, for as many columns as can fit in 'maxwidth' bytes, and as 266 # many rows as necessary. 267 268 use integer; 269 270 # Real data is unlikely to be able to fit more columns than this in a 271 # typical 80 byte window. But obviously this could be changed or passed 272 # in. 273 my $max_columns = 7; 274 275 my $min_spacer = 2; # Need this much space between columns 276 my $columns; 277 my $rows; 278 my @col_widths; 279 280 COLUMN: 281 # We start with more columns, and work down until we find a number that 282 # can accommodate all the data. This algorithm doesn't require the 283 # resulting columns to all have the same width. This can allow for 284 # as tight of packing as the data will possibly allow. 285 for ($columns = 7; $columns >= 1; $columns--) { 286 287 # For this many columns, we will need this many rows (final row might 288 # not be completely filled) 289 $rows = ($listp->@* + $columns - 1) / $columns; 290 291 # We only need to execute this final iteration to calculate the number 292 # of rows, as we can't get fewer than a single column. 293 last if $columns == 1; 294 295 my $row_width = 0; 296 my $i = 0; # Which element of the input list 297 298 # For each column ... 299 for my $col (0 .. $columns - 1) { 300 301 # Calculate how wide the column needs to be, which is based on the 302 # widest element in it 303 $col_widths[$col] = 0; 304 305 # Look through all the rows to find the widest element 306 for my $row (0 .. $rows - 1) { 307 308 # Skip if this row doesn't have an entry for this column 309 last if $i >= $listp->@*; 310 311 # This entry occupies this many bytes. 312 my $this_width = length $listp->[$i]; 313 314 # All but the final column need a spacer between it and the 315 # next column over. 316 $this_width += $min_spacer if $col < $columns - 1; 317 318 319 # This column will need to have enough width to accommodate 320 # this element 321 if ($this_width > $col_widths[$col]) { 322 323 # We can't have this many columns if the total width 324 # exceeds the available; bail now and try fewer columns 325 next COLUMN if $row_width + $this_width > $max_width; 326 327 $col_widths[$col] = $this_width; 328 } 329 330 $i++; # The next row will contain the next item 331 } 332 333 $row_width += $col_widths[$col]; 334 next COLUMN if $row_width > $max_width; 335 } 336 337 # If we get this far, this many columns works 338 last; 339 } 340 341 # Assemble the output 342 my $text = ""; 343 for my $row (0 .. $rows - 1) { 344 for my $col (0 .. $columns - 1) { 345 my $index = $row + $rows * $col; # Convert 2 dimensions to 1 346 347 # Skip if this row doesn't have an entry for this column 348 next if $index >= $listp->@*; 349 350 my $element = $listp->[$index]; 351 $text .= $element; 352 353 # Add alignment spaces for all but final column 354 $text .= " " x ($col_widths[$col] - length $element) 355 if $col < $columns - 1; 356 } 357 358 $text .= "\n"; # End of row 359 } 360 361 return $text; 362} 363 364# return the perl version as defined in patchlevel.h. 365# (we may be being run by another perl, so $] won't be right) 366# return e.g. (5, 14, 3, "5.014003") 367 368sub perl_version { 369 my $plh = 'patchlevel.h'; 370 open my $fh, "<", $plh or die "can't open '$plh': $!\n"; 371 my ($v1,$v2,$v3); 372 while (<$fh>) { 373 $v1 = $1 if /PERL_REVISION\s+(\d+)/; 374 $v2 = $1 if /PERL_VERSION\s+(\d+)/; 375 $v3 = $1 if /PERL_SUBVERSION\s+(\d+)/; 376 } 377 die "can't locate PERL_REVISION in '$plh'" unless defined $v1; 378 die "can't locate PERL_VERSION in '$plh'" unless defined $v2; 379 die "can't locate PERL_SUBVERSION in '$plh'" unless defined $v3; 380 return ($v1,$v2,$v3, sprintf("%d.%03d%03d", $v1, $v2, $v3)); 381} 382 383 3841; 385