The R Project SVN R

Rev

Rev 9715 | Blame | Last modification | View Log | Download | RSS feed

#! @PERL@
#-*- perl -*-

use Cwd;
use File::Basename;
use Getopt::Long;
use R::Dcf;
use R::Utils;
#use strict;

## don't buffer output
$|=1;

my $revision = ' $Revision: 1.6 $ ';
my $version;
my $name;
$revision =~ / ([\d\.]*) /;
$version = $1;
($name = $0) =~ s|.*/||;

$latex = "@LATEX@";
$make = "@MAKE@";

$opt_clean = $opt_examples = $opt_tests = $opt_latex = 1;
my @knownoptions = ("help|h", "version|v", "outdir|o:s",
            "nsize:s", "vsize:s", "library|l:s",
            "clean!", "examples!", "tests!", "latex!");

GetOptions (@knownoptions) || usage();

R_version($name, $version) if $opt_version;
usage() if $opt_help;

my $startdir=getcwd();
$opt_outdir=$startdir unless $opt_outdir;
chdir($opt_outdir) || die "Cannot change to directory $opt_outdir\n";
my $outdir=getcwd();
chdir($startdir);

my $tmpdir = R_getenv("TMPDIR", "/tmp");
my $R_HOME = $ENV{'R_HOME'} ||
    die "Error: Environment variable R_HOME not found\n";

my $R_libs = $ENV{'R_LIBS'};
my $library;
if($opt_library){
    chdir($opt_library) ||
    die "Error: cannot change to directory \`$opt_library'\n";
    $library=getcwd();
    $ENV{'R_LIBS'} = "$library:$R_libs";
    chdir($startdir);
}

my $R_opts = "--vanilla";
$R_opts .= " --nsize=$opt_nsize" if $opt_nsize;
$R_opts .= " --vsize=$opt_vsize" if $opt_vsize;

## this is the main loop over all packages that shall be checked
my $pkg;
foreach $pkg (@ARGV){
    my $is_bundle=0;
    $pkg =~ s/\/$//;
    my $pkgname = basename($pkg);
    chdir($startdir);

    my $pkgoutdir="$outdir/$pkgname.Rcheck";
    system("rm -rf $pkgoutdir") if ($opt_clean && (-d $pkgoutdir)) ;
    if(! -d $pkgoutdir){
    if(! mkdir($pkgoutdir, 0755)){
        die("could not create directory $pkgoutdir\n");
        exit(1);
    }
    }

    my $log = new R::Logfile("$pkgoutdir/check.log");
    $log->message("using log directory $pkgoutdir");

    if(! $opt_library){
    $library = $pkgoutdir;
    $ENV{'R_LIBS'} = "$library:$R_libs";
    }

    my $description;
    $log->checking("for file \`$pkg/DESCRIPTION'");
    if(-r "$pkg/DESCRIPTION"){
    $description = new R::Dcf("$pkg/DESCRIPTION");
    $log->result("OK");
    }
    else{
    $log->result("NO");
    exit(1);
    }

    
    if($description->{"Contains"}){
    $log->message("Looks like \`${pkg}' is a package bundle");
    $is_bundle=1;
    my @bundlepkgs = split(/\s+/, $description->{"Contains"});
    my $ppkg="";
    foreach $ppkg (@bundlepkgs){
        $log->message("checking \`$ppkg' in bundle \`$pkg'");
        $log->setstars("**");
        chdir($startdir);
        check_pkg("$pkg/$ppkg", $is_bundle, $description, $log);
        $log->setstars("*");
    }
    }
    else{
    $is_bundle=0;
    chdir($startdir);
    check_pkg("$pkg", $is_bundle, $description, $log);
    }
    
    chdir($startdir);
    print "\n";
    if(system("${R_HOME}/bin/R INSTALL -l $library $pkg")){
    $log->error("installation failed");
    }
    print("\n");

    if(!$is_bundle){
    chdir($pkgoutdir);

    if($opt_examples && (-d "$library/$pkgname/R-ex")){
        $log->creating("examples.R");
        if(system("${R_HOME}/bin/massage-Examples ".
              "${pkgname} ${library}/${pkgname}/R-ex/*.R ".
              "> examples.R")){
        $log->error();
        exit(1);
        }
        $log->result("OK");
        $log->checking("examples");
        if(system("${R_HOME}/bin/R BATCH $R_opts examples.R")){
        $log->error("running examples.R failed");
        exit(1);
        }
        $log->result("OK");
    }

    if($opt_tests && (-d "$startdir/$pkg/tests")){
        my $testdir="$startdir/$pkg/tests";
        $log->checking("tests");
        my $makefiles="-f ${R_HOME}/etc/Makeconf-tests";
        my $makevars="";
        if(-r "$testdir/Makefile"){
        $makefiles .= " -f $testdir/Makefile";
        }
        if(-r "$testdir/Makevars"){
        $makevars = " -f $testdir/Makevars";
        }
        else{
        open makevars, "> Makevars";
        print makevars "test-src-1=";
        while(<$testdir/*.R>){
            print makevars "\\\n " . basename($_);
        }
        print makevars "\n";
        print makevars "test-src-auto=";
        while(<$testdir/*.Rin>){
            s/Rin$/R/;
            print makevars "\\\n " . basename($_);
        }
        print makevars "\n";
        close makevars;
        $makevars = " -f Makevars";
        }
        print "\n";
        if(<$testdir/*.save>){
        system("cp -f $testdir/*.save .");
        }
        if(system("$make $makefiles $makevars VPATH=$testdir")){
        $log->error();
        exit(1);
        }
        $log->result("OK");
    }
        
        


        
    if($opt_latex && (-d "$library/$pkgname/latex")){
        $ENV{'TEXINPUTS'}="$R_HOME/doc/manual:$ENV{'TEXINPUTS'}";
        $log->creating("manual.tex");
        open manual, "> manual.tex";
        print manual "\\documentclass\{article\}\n" .
        "\\usepackage\{Rd\}\n".
            "\\begin\{document\}\n";
        while(<$library/$pkgname/latex/*.tex>){
        my $file=$_;
        open file, "< $file" ||
            $log->error("cannot open file \`$file' for reading");
        while(<file>){
            print manual $_;
        }
        }
        print manual "\\end\{document\}\n";
        close manual;
        $log->result("OK");
        if(-x $latex){
        $log->checking("manual.tex");
        print "\n";
        if(system("$latex manual")){
            $log->error();
            exit(1);
        }
        $log->result("OK");
        }
    }
        
        
    }
    else{
    $log->message("Running examples, tests and latex " .
              "not yet implemented for bundles");
    }

    if($log->{"warnings"}){
    print("\n") ;
    $log->summary();
    }
    $log->close();
    print("\n");
}

    



#**********************************************************

## For updating INDEX and data/00Index
## should go into new build script

sub updateIndex {

    my ($oldindex, $Rdfiles) = @_;
    my $newindex = "$tmpdir/Rcheck.$$";
    system("${R_HOME}/bin/Rdindex ${Rdfiles} > ${newindex}");
    my $diff = `diff -b $oldindex $newindex`;
    if($diff){
    $log->result("NO");
    if($opt_force){
        $log->message("overwriting \`${oldindex}' as \`--force' was given");
        rename($newindex, $oldindex);
    }
    else{
        $log->message("use \`--force' to overwrite the existing \`${oldindex}'");
    }
    }
    else{
    $log->result("OK");
    }
    unlink $newindex;
}

#**********************************************************

sub check_pkg {

    my ($pkg, $in_bundle, $description, $log) = @_;

    $log->checking("package directory");
    my $dir;
    if(-d $pkg){
    chdir($pkg);
    $dir = getcwd();
    }
    else{
    $log->error("package dir \`${pkg}' does not exist");
    exit 1;
    }
    $log->result("OK");
    
    if($in_bundle){  # join DESCRIPTION and DESCRIPTION.in
    if(-r "DESCRIPTION.in"){
        $log->checking("for file \`DESCRIPTION.in'");
        my $description_in = new R::Dcf("DESCRIPTION.in");
        my $key;
        foreach $key (keys(%$description)){
        $description_in->{$key}=$description->{$key};
        }
        ## from now on use $description_in instead of $description
        ## in this subroutine
        $description = $description_in;
        $log->result("OK");
        $log->message("joining DESCRIPTION and DESCRIPTION.in");
    }
    else{
        $log->result("NO");
        exit(1);
    }
    
    }


    ## mandatory entries in DESCRIPTION:
    ##   Package, Title, Version, License, Author
    $log->checking("DESCRIPTION Package entry");
    if(! $description->{"Package"}){
    $log->error("no DESCRIPTION Package entry found");
    exit(1);
    }
    if($description->{"Package"} ne basename($dir)){
    $log->error("DESCRIPTION Package field differs from dir name");
    exit(1);
    }
    $log->result("OK");
    
    my $entry;
    foreach $entry (qw(Title Version License Author)){
    $log->checking("DESCRIPTION $entry entry");
    if(! $description->{$entry}){
        $log->error("no DESCRIPTION $entry entry found");
        exit(1);
    }
    $log->result("OK");
    }
    

    ## check Rd documentation files

    if(-d "man"){
    $log->checking("Rd files");
    my @rdfiles;
    my @badfiles;
    while(<man/*.[rR]d>){
        if(/</){
        push @badfiles, $_;
        }
        else{
        push @rdfiles, $_;
        }
    }
    if($#badfiles>=0){
        $log->error("  Cannot handle Rd file names containing \`<'.\n" .
            "  These are not legal file names on all R platforms.\n" .
            "  Please rename the following files and try again:");
        $log->message("    " . join("\n    ", @badfiles));
        exit(1);
    }
    
    @rdfiles = sort(@rdfiles);
    my $file;
    my @mandatoryTags = qw(name alias title description keyword);
    my @uniqueTags = qw(name title description usage arguments
                format details value references source
                seealso examples note author synopsis);
    my %badmandatory;
    my %badunique;

    ## create hash allTags with all tags found in mandatory Tags and
    ## uniqueTags 
    my $tag;
    my %allTags;
    foreach $tag (@mandatoryTags, @uniqueTags){
        $allTags{$tag}++;
    }
    
    foreach $rdfile (@rdfiles){
        open rdfile, "< $rdfile" ||
        die "cannot open \`$rdfile' for reading\n";
        
        my %tagcount;
        while(<rdfile>){
        my $line = $_;
        foreach $tag (keys %allTags) {
            if($line=~/^\s*\\$tag/){
            $tagcount{$tag}++;
            }
        }
        }
        close rdfile;
        
        foreach $tag (@mandatoryTags){
        push(@{$badmandatory{$tag}}, $rdfile)
            unless $tagcount{$tag}>0;
        }
        foreach $tag (@uniqueTags){
        push(@{$badunique{$tag}}, $rdfile)
            unless $tagcount{$tag}<=1;
        }
    }

    my $any;
    foreach $tag (@mandatoryTags){
        if(exists $badmandatory{$tag}){
        $log->warning("") unless $any;
        $any++;
        $log->message("  Rd files without \`${tag}':");
        $log->message("    " .
                  join("\n    ", @{$badmandatory{$tag}}));
        }
    }
    foreach $tag (@uniqueTags){
        if(exists $badunique{$tag}){
        $log->warning("") unless $any;
        $any++;
        $log->message("  Rd files with duplicate \`${tag}':");
        $log->message("    " .
                  join("\n    ", @{$badunique{$tag}}));
        }
    }
        
    $log->result("OK") unless $any;
    }



      
    
    
    ## Check for undocumented objects

    if(-d "R" && -d "man"){
    $log->checking("for undocumented objects");
    my @out = split(/\n/,
            `echo \"undoc(dir = \\\"${dir}\\\")\" | \
                         ${R_HOME}/bin/R ${R_opts}`);
    my @err = grep {s/^Error *//} @out;
    @out = grep {/^ *\[/} @out;
    if($#err<0){
        if($#out>=0){
        $log->warning("  " . join("\n  ", @out));
        }
        else{
        $log->result("OK");
        }
    }
    else{
        $log->error("  " . join("\n  ", @err));
        exit(1);
    }
    }

}
        

#**********************************************************

sub usage {
    print STDERR <<END;
Usage: $name [options] pkgdirs

Check R packages from package sources in the directories specified by
pkgdirs.  A variety of diagnostic checks on directory structure, index
and control files are performed. All examples provided by the
packages' documentation are tested if they run succesfully. Finally,
the package is installed into the log directory (which includes the
translation of all Rd files into several formats), and the Rd files are
tested by LaTeX (if available).

If necessary for passing the checks, use the \`--vsize' and \`--nsize'
options to increase R's memory (\`--vanilla' is used by default).

Options:
  -h, --help        print short help message and exit
  -v, --version     print version info and exit

  --vsize=N     set R's vector heap size to N bytes
  --nsize=N     set R's number of cons cells to N
  -l, --library         library directory used for test installation
                        of packages (default is outdir)

  -o, --outdir=dir      directory used for logfiles, R output, etc.
                        (default is \`pkg.Rcheck' in current directory)
  --clean --noclean     Clean outdir before using it?

  --examples --noexamples    Run all examples in the Rd files?
  --tests --notests     Run code in tests subdirectory?
  --latex --nolatex     Run latex on help files?

By default, all test sections are turned on.

Email bug reports to <r-bugs\@lists.r-project.org>.
END
    exit 0;
}