Blame devel/mktodo.pl

Packit 7d6a7d
#!/usr/bin/perl -w
Packit 7d6a7d
################################################################################
Packit 7d6a7d
#
Packit 7d6a7d
#  mktodo.pl -- generate baseline and todo files
Packit 7d6a7d
#
Packit 7d6a7d
################################################################################
Packit 7d6a7d
#
Packit 7d6a7d
#  Version 3.x, Copyright (C) 2004-2013, Marcus Holland-Moritz.
Packit 7d6a7d
#  Version 2.x, Copyright (C) 2001, Paul Marquess.
Packit 7d6a7d
#  Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
Packit 7d6a7d
#
Packit 7d6a7d
#  This program is free software; you can redistribute it and/or
Packit 7d6a7d
#  modify it under the same terms as Perl itself.
Packit 7d6a7d
#
Packit 7d6a7d
################################################################################
Packit 7d6a7d
Packit 7d6a7d
use strict;
Packit 7d6a7d
use Getopt::Long;
Packit 7d6a7d
use Data::Dumper;
Packit 7d6a7d
use IO::File;
Packit 7d6a7d
use IO::Select;
Packit 7d6a7d
use Config;
Packit 7d6a7d
use Time::HiRes qw( gettimeofday tv_interval );
Packit 7d6a7d
Packit 7d6a7d
require './devel/devtools.pl';
Packit 7d6a7d
Packit 7d6a7d
our %opt = (
Packit 7d6a7d
  debug   => 0,
Packit 7d6a7d
  base    => 0,
Packit 7d6a7d
  verbose => 0,
Packit 7d6a7d
  check   => 1,
Packit 7d6a7d
  shlib   => 'blib/arch/auto/Devel/PPPort/PPPort.so',
Packit 7d6a7d
);
Packit 7d6a7d
Packit 7d6a7d
GetOptions(\%opt, qw(
Packit 7d6a7d
            perl=s todo=s version=s shlib=s debug base verbose check!
Packit 7d6a7d
          )) or die;
Packit 7d6a7d
Packit 7d6a7d
identify();
Packit 7d6a7d
Packit 7d6a7d
print "\n", ident_str(), "\n\n";
Packit 7d6a7d
Packit 7d6a7d
my $fullperl = `which $opt{perl}`;
Packit 7d6a7d
chomp $fullperl;
Packit 7d6a7d
Packit 7d6a7d
$ENV{SKIP_SLOW_TESTS} = 1;
Packit 7d6a7d
Packit 7d6a7d
regen_all();
Packit 7d6a7d
Packit 7d6a7d
my %stdsym = map { ($_ => 1) } qw (
Packit 7d6a7d
  strlen
Packit 7d6a7d
  snprintf
Packit 7d6a7d
  strcmp
Packit 7d6a7d
  memcpy
Packit 7d6a7d
  strncmp
Packit 7d6a7d
  memmove
Packit 7d6a7d
  memcmp
Packit 7d6a7d
  tolower
Packit 7d6a7d
  exit
Packit 7d6a7d
  memset
Packit 7d6a7d
  vsnprintf
Packit 7d6a7d
  siglongjmp
Packit 7d6a7d
  sprintf
Packit 7d6a7d
);
Packit 7d6a7d
Packit 7d6a7d
my %sym;
Packit 7d6a7d
for (`$Config{nm} $fullperl`) {
Packit 7d6a7d
  chomp;
Packit 7d6a7d
  /\s+T\s+(\w+)\s*$/ and $sym{$1}++;
Packit 7d6a7d
}
Packit 7d6a7d
keys %sym >= 50 or die "less than 50 symbols found in $fullperl\n";
Packit 7d6a7d
Packit 7d6a7d
my %all = %{load_todo($opt{todo}, $opt{version})};
Packit 7d6a7d
my @recheck;
Packit 7d6a7d
Packit 7d6a7d
my $symmap = get_apicheck_symbol_map();
Packit 7d6a7d
Packit 7d6a7d
for (;;) {
Packit 7d6a7d
  my $retry = 1;
Packit 7d6a7d
  my $trynm = 1;
Packit 7d6a7d
  regen_apicheck();
Packit 7d6a7d
Packit 7d6a7d
retry:
Packit 7d6a7d
  my(@new, @tmp, %seen);
Packit 7d6a7d
Packit 7d6a7d
  my $r = run(qw(make));
Packit 7d6a7d
  $r->{didnotrun} and die "couldn't run make: $!\n";
Packit 7d6a7d
Packit 7d6a7d
  for my $l (@{$r->{stderr}}) {
Packit 7d6a7d
    if ($l =~ /_DPPP_test_(\w+)/) {
Packit 7d6a7d
      if (!$seen{$1}++) {
Packit 7d6a7d
        my @s = grep { exists $sym{$_} } $1, "Perl_$1", "perl_$1";
Packit 7d6a7d
        if (@s) {
Packit 7d6a7d
          push @tmp, [$1, "E (@s)"];
Packit 7d6a7d
        }
Packit 7d6a7d
        else {
Packit 7d6a7d
          push @new, [$1, "E"];
Packit 7d6a7d
        }
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  if ($r->{status} == 0) {
Packit 7d6a7d
    my @u;
Packit 7d6a7d
    my @usym;
Packit 7d6a7d
Packit 7d6a7d
    if ($trynm) {
Packit 7d6a7d
      @u = eval { find_undefined_symbols($fullperl, $opt{shlib}) };
Packit 7d6a7d
      warn "warning: $@" if $@;
Packit 7d6a7d
      $trynm = 0;
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
    unless (@u) {
Packit 7d6a7d
      $r = run(qw(make test));
Packit 7d6a7d
      $r->{didnotrun} and die "couldn't run make test: $!\n";
Packit 7d6a7d
      $r->{status} == 0 and last;
Packit 7d6a7d
Packit 7d6a7d
      for my $l (@{$r->{stderr}}) {
Packit 7d6a7d
        if ($l =~ /undefined symbol: (\w+)/) {
Packit 7d6a7d
          push @u, $1;
Packit 7d6a7d
        }
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
    for my $u (@u) {
Packit 7d6a7d
      for my $m (keys %{$symmap->{$u}}) {
Packit 7d6a7d
        if (!$seen{$m}++) {
Packit 7d6a7d
          my $pl = $m;
Packit 7d6a7d
          $pl =~ s/^[Pp]erl_//;
Packit 7d6a7d
          my @s = grep { exists $sym{$_} } $pl, "Perl_$pl", "perl_$pl";
Packit 7d6a7d
          push @new, [$m, @s ? "U (@s)" : "U"];
Packit 7d6a7d
        }
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  @new = grep !$all{$_->[0]}, @new;
Packit 7d6a7d
Packit 7d6a7d
  unless (@new) {
Packit 7d6a7d
    @new = grep !$all{$_->[0]}, @tmp;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  unless (@new) {
Packit 7d6a7d
    if ($retry > 0) {
Packit 7d6a7d
      $retry--;
Packit 7d6a7d
      regen_all();
Packit 7d6a7d
      goto retry;
Packit 7d6a7d
    }
Packit 7d6a7d
    print Dumper($r);
Packit 7d6a7d
    die "no new TODO symbols found...";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  # don't recheck undefined symbols reported by the dynamic linker
Packit 7d6a7d
  push @recheck, map { $_->[0] } grep { $_->[1] !~ /^U/ } @new;
Packit 7d6a7d
Packit 7d6a7d
  for (@new) {
Packit 7d6a7d
    sym('new', @$_);
Packit 7d6a7d
    $all{$_->[0]} = $_->[1];
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  write_todo($opt{todo}, $opt{version}, \%all);
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
if ($opt{check}) {
Packit 7d6a7d
  my $ifmt = '%' . length(scalar @recheck) . 'd';
Packit 7d6a7d
  my $t0 = [gettimeofday];
Packit 7d6a7d
Packit 7d6a7d
  RECHECK: for my $i (0 .. $#recheck) {
Packit 7d6a7d
    my $sym = $recheck[$i];
Packit 7d6a7d
    my $cur = delete $all{$sym};
Packit 7d6a7d
Packit 7d6a7d
    sym('chk', $sym, $cur, sprintf(" [$ifmt/$ifmt, ETA %s]",
Packit 7d6a7d
               $i + 1, scalar @recheck, eta($t0, $i, scalar @recheck)));
Packit 7d6a7d
Packit 7d6a7d
    write_todo($opt{todo}, $opt{version}, \%all);
Packit 7d6a7d
Packit 7d6a7d
    if ($cur eq "E (Perl_$sym)") {
Packit 7d6a7d
      # we can try a shortcut here
Packit 7d6a7d
      regen_apicheck($sym);
Packit 7d6a7d
Packit 7d6a7d
      my $r = run(qw(make test));
Packit 7d6a7d
Packit 7d6a7d
      if (!$r->{didnotrun} && $r->{status} == 0) {
Packit 7d6a7d
        sym('del', $sym, $cur);
Packit 7d6a7d
        next RECHECK;
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
    # run the full test
Packit 7d6a7d
    regen_all();
Packit 7d6a7d
Packit 7d6a7d
    my $r = run(qw(make test));
Packit 7d6a7d
Packit 7d6a7d
    $r->{didnotrun} and die "couldn't run make test: $!\n";
Packit 7d6a7d
Packit 7d6a7d
    if ($r->{status} == 0) {
Packit 7d6a7d
      sym('del', $sym, $cur);
Packit 7d6a7d
    }
Packit 7d6a7d
    else {
Packit 7d6a7d
      $all{$sym} = $cur;
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
write_todo($opt{todo}, $opt{version}, \%all);
Packit 7d6a7d
Packit 7d6a7d
run(qw(make realclean));
Packit 7d6a7d
Packit 7d6a7d
exit 0;
Packit 7d6a7d
Packit 7d6a7d
sub sym
Packit 7d6a7d
{
Packit 7d6a7d
  my($what, $sym, $reason, $extra) = @_;
Packit 7d6a7d
  $extra ||= '';
Packit 7d6a7d
  my %col = (
Packit 7d6a7d
    'new' => 'bold red',
Packit 7d6a7d
    'chk' => 'bold magenta',
Packit 7d6a7d
    'del' => 'bold green',
Packit 7d6a7d
  );
Packit 7d6a7d
  $what = colored("$what symbol", $col{$what});
Packit 7d6a7d
Packit 7d6a7d
  printf "[%s] %s %-30s # %s%s\n",
Packit 7d6a7d
         $opt{version}, $what, $sym, $reason, $extra;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub regen_all
Packit 7d6a7d
{
Packit 7d6a7d
  my @mf_arg = ('--with-apicheck', 'OPTIMIZE=-O0 -w');
Packit 7d6a7d
  push @mf_arg, qw( DEFINE=-DDPPP_APICHECK_NO_PPPORT_H ) if $opt{base};
Packit 7d6a7d
Packit 7d6a7d
  # just to be sure
Packit 7d6a7d
  run(qw(make realclean));
Packit 7d6a7d
  run($fullperl, "Makefile.PL", @mf_arg)->{status} == 0
Packit 7d6a7d
      or die "cannot run Makefile.PL: $!\n";
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub regen_apicheck
Packit 7d6a7d
{
Packit 7d6a7d
  unlink qw(apicheck.c apicheck.o);
Packit 7d6a7d
  runtool({ out => '/dev/null' }, $fullperl, 'apicheck_c.PL', map { "--api=$_" } @_)
Packit 7d6a7d
      or die "cannot regenerate apicheck.c\n";
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub load_todo
Packit 7d6a7d
{
Packit 7d6a7d
  my($file, $expver) = @_;
Packit 7d6a7d
Packit 7d6a7d
  if (-e $file) {
Packit 7d6a7d
    my $f = new IO::File $file or die "cannot open $file: $!\n";
Packit 7d6a7d
    my $ver = <$f>;
Packit 7d6a7d
    chomp $ver;
Packit 7d6a7d
    if ($ver eq $expver) {
Packit 7d6a7d
      my %sym;
Packit 7d6a7d
      while (<$f>) {
Packit 7d6a7d
        chomp;
Packit 7d6a7d
        /^(\w+)\s+#\s+(.*)/ or goto nuke_file;
Packit 7d6a7d
        exists $sym{$1} and goto nuke_file;
Packit 7d6a7d
        $sym{$1} = $2;
Packit 7d6a7d
      }
Packit 7d6a7d
      return \%sym;
Packit 7d6a7d
    }
Packit 7d6a7d
Packit 7d6a7d
nuke_file:
Packit 7d6a7d
    undef $f;
Packit 7d6a7d
    unlink $file or die "cannot remove $file: $!\n";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return {};
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub write_todo
Packit 7d6a7d
{
Packit 7d6a7d
  my($file, $ver, $sym) = @_;
Packit 7d6a7d
  my $f;
Packit 7d6a7d
Packit 7d6a7d
  $f = new IO::File ">$file" or die "cannot open $file: $!\n";
Packit 7d6a7d
  $f->print("$ver\n");
Packit 7d6a7d
Packit 7d6a7d
  for (sort keys %$sym) {
Packit 7d6a7d
    $f->print(sprintf "%-30s # %s\n", $_, $sym->{$_});
Packit 7d6a7d
  }
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub find_undefined_symbols
Packit 7d6a7d
{
Packit 7d6a7d
  my($perl, $shlib) = @_;
Packit 7d6a7d
Packit 7d6a7d
  my $ps = read_sym(file => $perl,  options => [qw( --defined-only   )]);
Packit 7d6a7d
  my $ls = read_sym(file => $shlib, options => [qw( --undefined-only )]);
Packit 7d6a7d
Packit 7d6a7d
  my @undefined;
Packit 7d6a7d
Packit 7d6a7d
  for my $sym (keys %$ls) {
Packit 7d6a7d
    unless (exists $ps->{$sym}) {
Packit 7d6a7d
      if ($sym !~ /\@/ and $sym !~ /^_/) {
Packit 7d6a7d
        push @undefined, $sym unless $stdsym{$sym};
Packit 7d6a7d
      }
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return @undefined;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub read_sym
Packit 7d6a7d
{
Packit 7d6a7d
  my %opt = ( options => [], @_ );
Packit 7d6a7d
Packit 7d6a7d
  my $r = run($Config{nm}, @{$opt{options}}, $opt{file});
Packit 7d6a7d
Packit 7d6a7d
  if ($r->{didnotrun} or $r->{status}) {
Packit 7d6a7d
    die "cannot run $Config{nm}";
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  my %sym;
Packit 7d6a7d
Packit 7d6a7d
  for (@{$r->{stdout}}) {
Packit 7d6a7d
    chomp;
Packit 7d6a7d
    my($adr, $fmt, $sym) = /^\s*([[:xdigit:]]+)?\s+([ABCDGINRSTUVW?-])\s+(\S+)\s*$/i
Packit 7d6a7d
                           or die "cannot parse $Config{nm} output:\n[$_]\n";
Packit 7d6a7d
    $sym{$sym} = { format => $fmt };
Packit 7d6a7d
    $sym{$sym}{address} = $adr if defined $adr;
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return \%sym;
Packit 7d6a7d
}
Packit 7d6a7d
Packit 7d6a7d
sub get_apicheck_symbol_map
Packit 7d6a7d
{
Packit 7d6a7d
  my $r;
Packit 7d6a7d
Packit 7d6a7d
  while (1) {
Packit 7d6a7d
    $r = run(qw(make apicheck.i));
Packit 7d6a7d
Packit 7d6a7d
    last unless $r->{didnotrun} or $r->{status};
Packit 7d6a7d
Packit 7d6a7d
    my %sym = map { /error: macro "(\w+)" (?:requires|passed) \d+ argument/ ? ($1 => 'A') : () }
Packit 7d6a7d
              @{$r->{stderr}};
Packit 7d6a7d
Packit 7d6a7d
    if (keys %sym) {
Packit 7d6a7d
      for my $s (sort keys %sym) {
Packit 7d6a7d
        sym('new', $s, $sym{$s});
Packit 7d6a7d
        $all{$s} = $sym{$s};
Packit 7d6a7d
      }
Packit 7d6a7d
      write_todo($opt{todo}, $opt{version}, \%all);
Packit 7d6a7d
      regen_apicheck();
Packit 7d6a7d
    }
Packit 7d6a7d
    else {
Packit 7d6a7d
      die "cannot run make apicheck.i ($r->{didnotrun} / $r->{status}):\n".
Packit 7d6a7d
          join('', @{$r->{stdout}})."\n---\n".join('', @{$r->{stderr}});
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  my $fh = IO::File->new('apicheck.i')
Packit 7d6a7d
           or die "cannot open apicheck.i: $!";
Packit 7d6a7d
Packit 7d6a7d
  local $_;
Packit 7d6a7d
  my %symmap;
Packit 7d6a7d
  my $cur;
Packit 7d6a7d
Packit 7d6a7d
  while (<$fh>) {
Packit 7d6a7d
    next if /^#/;
Packit 7d6a7d
    if (defined $cur) {
Packit 7d6a7d
      for my $sym (/\b([A-Za-z_]\w+)\b/g) {
Packit 7d6a7d
        $symmap{$sym}{$cur}++;
Packit 7d6a7d
      }
Packit 7d6a7d
      undef $cur if /^}$/;
Packit 7d6a7d
    }
Packit 7d6a7d
    else {
Packit 7d6a7d
      /_DPPP_test_(\w+)/ and $cur = $1;
Packit 7d6a7d
    }
Packit 7d6a7d
  }
Packit 7d6a7d
Packit 7d6a7d
  return \%symmap;
Packit 7d6a7d
}