#! @PERL@
#-*- perl -*-
# Copyright (C) 2000-2002 R Development Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# A copy of the GNU General Public License is available via WWW at
# http://www.gnu.org/copyleft/gpl.html. You can also obtain it by
# writing to the Free Software Foundation, Inc., 59 Temple Place,
# Suite 330, Boston, MA 02111-1307 USA.
# Send any bug reports to r-bugs@r-project.org
##
## This is not portable: has system().
##
use Cwd;
use File::Basename;
use File::Copy;
use File::Find;
use File::Path;
use Getopt::Long;
use R::Dcf;
use R::Logfile;
use R::Rdtools;
use R::Utils;
use R::Vars;
use Text::DelimMatch;
use Text::Wrap;
## don't buffer output
$| = 1;
my $revision = ' $Revision: 1.110.2.1 $ ';
my $version;
my $name;
$revision =~ / ([\d\.]*) /;
$version = $1;
($name = $0) =~ s|.*/||;
## Options
my $opt_clean = 1;
my $opt_examples = 1;
my $opt_tests = 1;
my $opt_latex = 1;
my $opt_use_gct = 0;
my $opt_codoc = 1;
my $opt_perms = 1;
my $opt_install = 1;
my $opt_vignettes = 1;
my $WINDOWS = ($R::Vars::OSTYPE eq "windows");
R::Vars::error("R_HOME", "R_CMD", "R_EXE");
##
## This should really be in R::Utils so that it can be kept in sync
## between R CMD check and R CMD build.
my $data_exts_re = "(R|r|RData|rdata|rda|TXT|txt|tab|csv|CSV)";
my $demo_exts_re = "[Rr]";
my $vignette_exts_re = "[RrSs](nw|tex)";
##
my @knownoptions = ("help|h", "version|v", "outdir|o:s", "library|l:s",
"no-clean", "no-examples", "no-tests", "no-latex",
"use-gct" => \$opt_use_gct, "no-codoc", "no-perms",
"no-install", "no-vignettes");
GetOptions (@knownoptions) or usage();
R_version("R add-on package checker", $version) if $opt_version;
usage() if $opt_help;
$opt_clean = 0 if $opt_no_clean;
$opt_examples = 0 if $opt_no_examples;
$opt_tests = 0 if $opt_no_tests;
$opt_latex = 0 if $opt_no_latex;
$opt_codoc = 0 if $opt_no_codoc;
$opt_perms = 0 if $opt_no_perms;
$opt_install = 0 if $opt_no_install;
$opt_vignettes = 0 if $opt_no_vignettes;
my $startdir = cwd();
$opt_outdir = $startdir unless $opt_outdir;
chdir($opt_outdir)
or die "Error: cannot change to directory '$opt_outdir'\n";
my $outdir = cwd();
chdir($startdir);
my $R_LIBS = $ENV{'R_LIBS'};
my $library;
if($opt_library) {
chdir($opt_library)
or die "Error: cannot change to directory '$opt_library'\n";
$library = cwd();
$ENV{'R_LIBS'} = env_path($library, $R_LIBS);
chdir($startdir);
}
my $R_opts = "--vanilla";
if($opt_latex) {
my $log = new R::Logfile();
$log->checking("for working latex");
open(TEXFILE,
"> " . &file_path(${R::Vars::TMPDIR}, "Rtextest$$.tex"))
or die "Error: cannot write to Rtextest$$.tex\n";
print TEXFILE "\\documentclass\{article\}\\begin\{document\}" .
"test\\end\{document\}\n";
close TEXFILE;
chdir($R::Vars::TMPDIR);
if(R_system("${R::Vars::LATEX} Rtextest$$ > Rtextest$$.out")) {
$log->result("NO");
$HAVE_LATEX = 0;
} else {
$log->result("OK");
$HAVE_LATEX = 1;
}
unlink();
chdir($startdir);
$log->close();
}
## get the valid keywords for later ...
my %standard_keywords = ();
open(KEYWORDS,
"< " . &file_path(${R::Vars::R_HOME}, "doc", "KEYWORDS.db"))
or die "Error: cannot open 'KEYWORDS.db'";
while() {
if(/^.*\|([^:]*):.*/) {
$standard_keywords{$1} = 1;
}
}
close KEYWORDS;
## this is the main loop over all packages to be checked
$#ARGV >= 0 or die "Error: no packages were specified";
foreach my $pkg (@ARGV) {
## $pkg should be the path to the package (bundle) root source
## directory, either absolute or relative to $startdir.
## $pkgdir is the corresponding absolute path.
## $pkgname is the name of the package (bundle).
chdir($startdir);
$pkg =~ s/\/$//;
(-d $pkg) or die "Error: package dir '$pkg' does not exist";
chdir($pkg)
or die "Error: cannot change to directory '$pkg'\n";
my $pkgdir = cwd();
my $pkgname = basename($pkgdir);
chdir($startdir);
my $pkgoutdir = &file_path($outdir, "$pkgname.Rcheck");
rmtree($pkgoutdir) if ($opt_clean && (-d $pkgoutdir)) ;
if(! -d $pkgoutdir) {
if(! mkdir($pkgoutdir, 0755)) {
die("Error: cannot create directory '$pkgoutdir'\n");
exit(1);
}
}
$log = new R::Logfile(&file_path($pkgoutdir, "00check.log"));
$log->message("using log directory '$pkgoutdir'");
if(! $opt_library) {
$library = $pkgoutdir;
$ENV{'R_LIBS'} = env_path($library, $R_LIBS);
}
my $description;
my $is_base_pkg = 0;
## Package sources from the R distribution are special. They have a
## 'DESCRIPTION.in' file (instead of 'DESCRIPTION'), with Version
## field containing '@VERSION@' for substitution by configure. We
## test for such packages by looking for 'DESCRIPTION.in' with
## Priority 'base', and skip the installation test for such
## packages.
if(-r &file_path($pkgdir, "DESCRIPTION.in")) {
$description =
new R::Dcf(&file_path($pkgdir, "DESCRIPTION.in"));
if($description->{"Priority"} eq "base") {
$log->message("looks like '${pkgname}' is a base package");
$log->message("skipping installation test");
$is_base_pkg = 1;
}
}
if(!$is_base_pkg) {
$log->checking(join("",
("for file '",
&file_path($pkgname, "DESCRIPTION"),
"'")));
if(-r &file_path($pkgdir, "DESCRIPTION")) {
$description =
new R::Dcf(&file_path($pkgdir, "DESCRIPTION"));
$log->result("OK");
}
else {
$log->result("NO");
exit(1);
}
##
## This check should be adequate, but would not catch a manually
## installed package, nor one installed prior to 1.4.0.
##
$log->checking("if this is a source package");
if(!defined($description->{"Built"})) {
$log->result("OK")
} else {
$log->error();
$log->print("Only *source* packages can be checked.\n");
exit(1);
}
if($opt_install) {
print("\n");
if(system("${R::Vars::R_CMD} INSTALL -l $library $pkgdir")) {
$log->error();
$log->print("Installation failed.\n");
exit(1);
}
print("\n");
}
}
my $is_bundle = 0;
if($description->{"Contains"}) {
$log->message("looks like '${pkgname}' is a package bundle");
$is_bundle = 1;
my @bundlepkgs = split(/\s+/, $description->{"Contains"});
foreach my $ppkg (@bundlepkgs) {
$log->message("checking '$ppkg' in bundle '$pkgname'");
$log->setstars("**");
chdir($startdir);
check_pkg(&file_path($pkgdir, $ppkg), $pkgoutdir, $startdir,
$library, $is_bundle, $description, $log,
$is_base_pkg);
$log->setstars("*");
}
}
else {
$is_bundle = 0;
chdir($startdir);
check_pkg($pkgdir, $pkgoutdir, $startdir, $library,
$is_bundle, $description, $log, $is_base_pkg);
}
if($log->{"warnings"}) {
print("\n") ;
$log->summary();
}
$log->close();
print("\n");
}
#**********************************************************
sub check_pkg {
my ($pkg, $pkgoutdir, $startdir, $library,
$in_bundle, $description, $log, $is_base_pkg) = @_;
my ($pkgdir, $pkgname);
## $pkg is the argument we received from the main loop.
## $pkgdir is the corresponding absolute path,
## $pkgname the name of the package.
## Note that we need to do repeat the checking from the main loop in
## the case of package bundles (and we could check for this).
$log->checking("package directory");
chdir($startdir);
$pkg =~ s/\/$//;
if(-d $pkg) {
chdir($pkg)
or die "Error: cannot change to directory '$pkg'\n";
$pkgdir = cwd();
$pkgname = basename($pkgdir);
}
else {
$log->error("Error: package dir '$pkg' does not exist");
exit(1);
}
$log->result("OK");
chdir($pkgdir);
## Check for sufficient file permissions (Unix only).
## This is a bit controversial, and hence has an option '--no-perms'
## to turn this off. In fact, the code below does a bit too much.
## What would really be needed is the following:
## * All dirs or files must be at least 555 or 444, respectively.
## * Everything that gets installed must be writeable. This
## includes DESCRIPTION COPYING INDEX TITLE data/* demo/* exec/*
## and inst/*.
## * All directories used for building 'objects' must be at least
## 755. This includes '.' and src.
## Given time, the above could be implemented.
if($opt_perms && ($R::Vars::OSTYPE eq "unix")) {
$log->checking("for sufficient/correct file permissions");
my @badfiles = ();
my @excludepatterns = R::Utils::get_exclude_patterns();
if(-f "./.Rbuildignore") {
open(RBUILDIGNORE, "./.Rbuildignore");
while() {
chop;
push(@excludepatterns, $_) if $_;
}
close(RBUILDIGNORE);
}
## Phase A. Directories at least 755, files at least 644.
sub findWrongPermsA {
my $filename = $File::Find::name;
$filename =~ s/^[^\/]*\///;
foreach my $p (@excludepatterns) {
if($WINDOWS) {
## Argh: Windows is case-honoring but not
## case-insensitive ...
return 0 if($filename =~ /$p/i);
}
else {
return 0 if($filename =~ /$p/);
}
}
if(-d $_ && (((stat $_)[2] & 00755) < oct("755"))) {
push @badfiles, $File::Find::name;
}
if(-f $_ && (((stat $_)[2] & 00644) < oct("644"))) {
push @badfiles, $File::Find::name;
}
}
find(\&findWrongPermsA, ".");
if($#badfiles >= 0) {
$log->error();
$log->print("Found the following files with " .
"insufficient permissions:\n");
$log->print(" " . join("\n ", @badfiles) . "\n");
$log->print(wrap("", "",
("Permissions should be at least 755",
"for directories and 644 for files.\n")));
$log->print("Please fix permissions and try again.\n");
exit(1);
}
## Phase B. Text files (as determined by their extensions)
## should really be mode 644.
@badfiles = ();
my @textfile_patterns =
("\\.[RSdqr]\$", # R source code
"\\.[Rr]d\$", # Rd files
"\\.([Ccfh]|cc|cpp)\$",
# C/C++/FORTRAN sources and headers
"^(DESCRIPTION|INDEX|README|TITLE)\$");
sub findWrongPermsB {
my $filename = $File::Find::name;
$filename =~ s/^[^\/]*\///;
foreach my $p (@excludepatterns) {
return 0 if($filename =~ /$p/);
}
if(-f $_ && (((stat $_)[2] & 00777) != oct("644"))) {
foreach my $p (@textfile_patterns) {
if($filename =~ /$p/) {
push @badfiles, $File::Find::name;
last;
}
}
}
}
find(\&findWrongPermsB, ".");
if($#badfiles >= 0) {
$log->warning();
$log->print("Found the following text files with " .
"incorrect permissions:\n");
$log->print(" " . join("\n ", @badfiles) . "\n");
$log->print(wrap("", "",
("Permissions for text files",
"(including R, Rd, and C/C++/FORTRAN",
"sources) should be exactly 644.\n")));
$log->print("Please fix permissions.\n");
}
else {
$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");
foreach my $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);
}
}
## Check mandatory entries in DESCRIPTION:
## Package, Version, License, Description, Title, Author,
## Maintainer.
my @msg_DESCRIPTION
= ("See the information on DESCRIPTION files",
"in section 'Creating R packages'",
"of the 'Writing R Extensions' manual.\n");
$log->checking("DESCRIPTION Package field");
if(! $description->{"Package"}) {
$log->error();
$log->print("No DESCRIPTION Package field found.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
my $tmp = $description->{"Package"};
if($description->{"Package"} ne $pkgname) {
$log->error();
$log->print("DESCRIPTION Package field differs from dir name.\n");
exit(1);
}
if($description->{"Package"} !~ /^[a-z][a-z\d\.]*$/i) {
$log->error();
$log->print("Malformed package name.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
$log->result("OK");
$log->checking("DESCRIPTION Version field");
if(! $description->{"Version"}) {
$log->error();
$log->print("No DESCRIPTION Version field found.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
if(($description->{"Version"} !~ /^\d+([.-]\d+)+$/)
&& !$is_base_pkg) {
## Package sources from the R distribution have '@VERSION@' in
## their 'DESCRIPTION.in' files ...
$log->error();
$log->print("Malformed package version.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
$log->result("OK");
foreach my $field (qw(License Description Title Author)) {
$log->checking("DESCRIPTION $field field");
if(! $description->{$field}) {
$log->error();
$log->print("No DESCRIPTION $field field found.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
$log->result("OK");
}
$log->checking("DESCRIPTION Maintainer field");
if(!$description->{"Maintainer"}) {
$log->error();
$log->print("No DESCRIPTION Maintainer field found.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
elsif($description->{"Maintainer"} !~ /^[^<>]*<[^<>]+> *$/) {
$log->error();
$log->print("Malformed Maintainer field.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
else {
$log->result("OK");
}
if($description->{"Depends"}) {
$log->checking("DESCRIPTION Depends field");
my @dependencies = split(/\,/, $description->{"Depends"});
my $any = 0;
foreach my $dep (@dependencies) {
$dep =~ /^\s*([\w\.]+)(\s*\(([^) ]+)\s+([^) ]+)\))?\s*$/;
## The entry is malformed if there is no match, or there is
## a match but the dep_op is different from '<=' or '>', or
## the dep_version does not only consist of digits, '.' or
## '-'.
if(!($1) || ($2 && (!(($3 eq "<=") || ($3 eq ">="))
|| ($4 =~ /[^\d\.\-]/)))) {
$any++;
break;
}
}
if($any == 0) {
$log->result("OK")
}
else {
$log->error();
$log->print("Malformed Depends field.\n");
$log->print(wrap("", "", @msg_DESCRIPTION));
exit(1);
}
}
## Check index files.
$log->checking("index files");
my @badfiles = ();
sub checkIndex {
my $index = $_[0];
## Currently, only test whether the index exists and has size
## greater than 0.
push(@badfiles, $index) unless (-s $index);
## Could have more tests, e.g. for correct format ...
}
&checkIndex("INDEX");
&checkIndex(&file_path("data", "00Index"))
if(-d "data" && &list_files_with_exts("data", $data_exts_re));
&checkIndex(&file_path("demo", "00Index"))
if(-d "demo" && &list_files_with_exts("demo", $demo_exts_re));
&checkIndex(&file_path("inst", "doc", "00Index.dcf"))
if(-d &file_path("inst", "doc")
&& &list_files_with_exts(&file_path("inst", "doc"),
$vignette_exts_re));
if($#badfiles >= 0) {
$log->warning();
$log->print("The following index files are missing " .
"or have zero length:\n");
$log->print(" " . join("\n ", @badfiles) . "\n");
$log->print(wrap("", "",
("See the information on INDEX files and package",
"subdirectories in section 'Creating R packages'",
"of the 'Writing R Extensions' manual.\n")));
}
else {
$log->result("OK");
}
## Check contents of directory 'inst'.
if(-d "inst") {
$log->checking("contents of directory 'inst'");
my @R_system_subdirs = ("R", "data", "demo", "exec", "man",
"help", "html", "latex", "R-ex");
my @bad_dirs = ();
foreach my $dir (@R_system_subdirs) {
push(@bad_dirs, $dir)
if(-d &file_path("inst", $dir)
&& &list_files(file_path("inst", $dir)));
}
if($#bad_dirs >= 0) {
$log->warning();
$log->print(wrap("", "",
("Found the following non-empty",
"subdirectories also used by R:\n")));
$log->print(" " . join(" ", @bad_dirs) . "\n");
$log->print(wrap("", "",
("It is recommended not to interfere with",
"package subdirectories used by R.\n")));
}
else {
$log->result("OK");
}
}
## Check R code for syntax errors.
if(!$is_base_pkg && -d "R") {
$log->checking("R files for syntax errors");
my @Rfiles = &list_files_with_exts("R", "[RrSsq]");
my $Rcmd = "Rfiles <- c(\"";
$Rcmd .= join("\",\n\"", @Rfiles) . "\")\n";
$Rcmd .= "for(f in Rfiles)\n";
$Rcmd .= "if(inherits(try(parse(f)), \"try-error\")) stop(f)\n";
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(/^Error:/, @out);
if($#out >= 0) {
my $Rfile = $out[0];
$Rfile =~ s/^Error: *//;
$log->error();
$log->print("Syntax error in file " . $Rfile . "\n");
exit(1);
}
else {
$log->result("OK");
}
}
## Check usage of library.dynam (if any).
if(!$is_base_pkg && -d "R") {
$log->checking("R files for library.dynam");
my @Rfiles;
if($opt_install) {
## Only need to check the installed file (if installed).
@Rfiles = (&file_path($library, $pkgname, "R", $pkgname));
}
else {
## Otherwise (if the package was not installed), we need to
## check all R code files.
@Rfiles = &list_files_with_exts("R", "[RrSsq]");
}
my $any = 0;
my $ext;
foreach my $Rfile (@Rfiles) {
last if $any;
open(RFILE, "< $Rfile")
or die "Error: cannot open file '$Rfile' for reading";
while() {
if(/library.dynam\(\"(.*?)\"/o) {
my $arg = $1;
if($arg =~ /\.(so|sl|dll)$/) {
$ext = $1;
$any++;
last;
}
}
}
close(RFILE);
}
if($any == 0) {
$log->result("OK");
}
else {
$log->error();
$log->print("library.dynam() used with extension '.$ext'\n");
$log->print(wrap("", "",
("The system-specific extension for",
"shared libraries should not be added.\n")));
$log->print("See ?library.dynam\n");
exit(1);
}
}
## Check whether methods have all arguments of the corresponding
## generic.
if(-d "R") {
$log->checking("generic/method consistency");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "checkMethods(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "checkMethods(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(!/^\>/, @out);
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
}
## Check whether assignment functions have their final argument
## named 'value'.
if(-d "R") {
$log->checking("for assignment functions with final arg not " .
"named 'value'");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "checkAssignFuns(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "checkAssignFuns(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(!/^\>/, @out);
if($#out>=0) {
##
## Change back from warning() to error() and exit when we
## have output from loading packages under control.
$log->warning();
$log->print(join("\n", @out) . "\n");
## exit(1);
##
}
else {
$log->result("OK");
}
}
## Check foreign function calls.
if(-d "R" && $ENV{'R_CHECK_FF_CALLS'}) {
$log->checking("foreign function calls");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "checkFF(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "checkFF(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(!/^\>/, @out);
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
}
## Check R documentation files.
if(-d "man") {
$log->checking("Rd files");
my @rdfiles = &list_files_with_exts("man", "[Rr]d");
my $manOSdir = &file_path("man", $R::Vars::OSTYPE);
if(-d $manOSdir) {
@rdfiles = (@rdfiles,
&list_files_with_exts($manOSdir, "[Rr]d"));
}
my @badfiles = grep(/, @rdfiles);
if($#badfiles >= 0) {
$log->error();
$log->print("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:\n");
$log->print(" " . join("\n ", @badfiles) . "\n");
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 @badbraces;
my %badmandatory;
my %badunique;
my %badkeywords;
## create hash allTags with all tags found in mandatory Tags and
## uniqueTags
my %allTags;
foreach my $tag (@mandatoryTags, @uniqueTags) {
$allTags{$tag}++;
}
foreach my $rdfile (@rdfiles) {
open(RDFILE, "< $rdfile")
or die "Error: cannot open '$rdfile' for reading\n";
my %keywords;
my %tagcount;
while() {
my $line = $_;
foreach my $tag (keys %allTags) {
if($line =~ /^\s*\\$tag\{/) {
$tagcount{$tag}++;
}
}
if($line =~ /^\s*\\keyword\{\s*([^}]*[^}\s])\s*\}.*/) {
$keywords{$1} = 1;
}
}
close RDFILE;
foreach my $tag (@mandatoryTags) {
push(@{$badmandatory{$tag}}, $rdfile)
unless $tagcount{$tag}>0;
}
foreach my $tag (@uniqueTags) {
push(@{$badunique{$tag}}, $rdfile)
unless $tagcount{$tag}<=1;
}
foreach my $key (keys(%keywords)) {
push(@{$badkeywords{$rdfile}}, $key)
unless $standard_keywords{$key};
}
## Check for unbalanced braces.
my $text = "";
## Read the Rd file via Rdpp, and get rid of the \alias
## entries as these may have unbalanced braces (Paren.Rd).
my @lines = split(/\n/, &Rdpp($rdfile, $R::Vars::OSTYPE));
foreach my $line (@lines) {
$text .= "\n$line" unless ($line =~ /^\\alias/);
}
$text .= "\n";
my $delimcurly = new Text::DelimMatch;
$delimcurly->delim("\{", "\}");
$delimcurly->escape("\\");
$text =~ s/([^\\])%.*\n/$1\n/g; # ???
## Now loop through matching pairs of braces.
while($delimcurly->match($text)) {
$text = $delimcurly->post_matched;
}
## Get rid of trailing comments.
$text =~ s/^%.*\n/\n/gs;
$text =~ s/\n%.*\n/\n/gs;
## Anything left in $text means unbalanced.
if(!($text =~ /^\s*$/)) {
push(@badbraces, $rdfile);
}
}
my $any = 0;
if($#badbraces >= 0) {
$log->error();
$log->print("Rd files with unbalanced braces:\n");
$log->print(" " . join("\n ", @badbraces) . "\n");
$log->print(wrap("", "",
("Unbalanced braces are Rd syntax",
"errors, and result in incorrect",
"documentation.\n")));
exit(1);
}
my @msg_writing_Rd
= ("See chapter 'Writing R documentation'",
"in manual 'Writing R Extensions'.\n");
my $any_missing_mandatory_tag = 0;
foreach my $tag (@mandatoryTags) {
if(exists $badmandatory{$tag}) {
$log->warning("") unless $any;
$any++;
$any_missing_mandatory_tag++;
$log->print("Rd files without '${tag}':\n");
$log->print(" " .
join("\n ", @{$badmandatory{$tag}}) .
"\n");
}
}
if($any_missing_mandatory_tag) {
$log->print("These tags are required in an Rd file.\n");
$log->print(wrap("", "", @msg_writing_Rd));
}
my $any_duplicate_unique_tag = 0;
foreach my $tag (@uniqueTags) {
if(exists $badunique{$tag}) {
$log->warning("") unless $any;
$any++;
$any_duplicate_unique_tag++;
$log->print("Rd files with duplicate '${tag}':\n");
$log->print(" " .
join("\n ", @{$badunique{$tag}}) .
"\n");
}
}
if($any_duplicate_unique_tag) {
$log->print("These tags must be unique in an Rd file.\n");
$log->print(wrap("", "", @msg_writing_Rd));
}
if(keys(%badkeywords)) {
$log->warning("") unless $any;
$any++;
$log->print("Rd files with non-standard keywords:\n");
foreach my $file (keys(%badkeywords)) {
$log->print(wrap(" ", " ",
("'$file':",
@{$badkeywords{$file}},
"\n")));
}
$log->print(wrap("", "",
("Each '\\keyword' entry should specify",
"one of the standard keywords",
"(as listed in file 'KEYWORDS.db'",
"in the 'doc' subdirectory",
"of the R home directory).\n")));
$log->print(wrap("", "", @msg_writing_Rd));
}
$log->result("OK") unless $any;
}
## Check for undocumented objects.
if(((-d "R") || (-d "data")) && (-d "man")) {
$log->checking("for undocumented objects");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "undoc(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "undoc(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
my @err = grep(/^Error/, @out);
@out = grep(!/^\>/, @out);
if($#err<0) {
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
}
else {
$log->error();
$log->print(join("\n", @err) . "\n");
exit(1);
}
}
## Check for code/documentation mismatches.
if($opt_codoc && (-d "R") && (-d "man")) {
$log->checking("for code/documentation mismatches");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "codoc(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "codoc(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
##
## With the current heuristics codoc() may throw an error even
## though the documentation is valid. Change this when we know
## what must be valid R code, similar to the above for undoc().
@out = grep(!/^\>/, @out);
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
##
}
## Check whether all arguments shown in \usage are documented in
## \arguments.
if(-d "man") {
$log->checking("for undocumented arguments in \\usage");
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "checkDocArgs(package = \"${pkgname}\")\n";
}
else {
$Rcmd .= "checkDocArgs(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(!/^\>/, @out);
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
}
## Check C sources/headers for CRLF line endings.
if(-d "src") {
$log->checking("for CRLF line endings in C sources/headers");
my @srcfiles = &list_files_with_exts("src", "[ch]");
my @badfiles = ();
foreach my $file (@srcfiles) {
open(SRCFILE, "< $file")
or die "Error: cannot open '$file' for reading\n";
binmode(SRCFILE); # for Windows
while() {
chop;
if($_ =~ /\r$/) {
push(@badfiles, $file);
last;
}
}
close(SRCFILE);
}
if($#badfiles >= 0) {
$log->warning();
$log->print("Found the following C sources/headers with " .
"CRLF line endings:\n");
$log->print(" " . join("\n ", @badfiles) . "\n");
$log->print("ISO C requires CR line endings.\n");
}
else {
$log->result("OK");
}
}
chdir($pkgoutdir);
## Run the examples.
if($opt_examples && -d &file_path($library, $pkgname, "R-ex")) {
$log->creating("${pkgname}-Ex.R");
my $Rexdir = &file_path($library, $pkgname, "R-ex");
my $iszipped = 0;
my $cmd;
if(-e &file_path($Rexdir, "Rex.zip")) {
$iszipped = 1;
my $UNZIP = &R_getenv("R_UNZIPCMD", "unzip -q");
system(join(" ",
("$UNZIP",
&file_path($Rexdir, "Rex.zip"),
" -d $Rexdir")));
}
if($WINDOWS) {
## avoid Rcmd as line may be too long after expansion.
$cmd = "perl ${R::Vars::R_HOME}/bin/massage-Examples ".
"${pkgname} ${library}/${pkgname}/R-ex/*.R ".
"> ${pkgname}-Ex.R";
} else {
$cmd = join(" ",
("${R::Vars::R_CMD} perl",
&file_path(${R::Vars::R_HOME}, "share", "perl",
"massage-Examples.pl"),
"${pkgname} ${Rexdir} > ${pkgname}-Ex.R"));
}
if(R_system($cmd)) {
$log->error();
exit(1);
}
if($iszipped) {
unlink(&list_files_with_exts($Rexdir, "R"));
}
$log->result("OK");
$log->checking("examples");
if($opt_use_gct) {
$cmd = "(echo 'gctorture(TRUE)'; cat ${pkgname}-Ex.R) " .
"| ${R::Vars::R_EXE} ${R_opts} > ${pkgname}-Ex.Rout";
}
else {
$cmd = "${R::Vars::R_EXE} ${R_opts} " .
"< ${pkgname}-Ex.R > ${pkgname}-Ex.Rout";
}
if(R_system($cmd)) {
$log->error();
$log->print("Running examples failed.\n");
exit(1);
}
$log->result("OK");
}
## Run the package-specific tests.
if($opt_install && $opt_tests && (-d &file_path($pkgdir, "tests"))) {
$log->checking("tests");
my $testsrcdir = &file_path($pkgdir, "tests");
my $testdir = &file_path($pkgoutdir, "tests");
if(! -d $testdir) {
if(! mkdir($testdir, 0755)) {
die "Error: cannot create directory $testdir\n";
exit(1);
}
}
chdir($testdir);
foreach my $file (&list_files($testsrcdir)) {
copy($file, basename($file));
}
my $makefiles = "-f " . &file_path(${R::Vars::R_HOME},
"share", "make",
"tests.mk");
if($WINDOWS) {
$makefiles = "-f ${R::Vars::R_HOME}/share/make/wintests.mk";}
my $makevars = "";
if($WINDOWS && (-r "$testsrcdir/Makefile.win")) {
$makefiles .= " -f $testsrcdir/Makefile.win";
}
elsif(-r &file_path($testsrcdir, "Makefile")) {
$makefiles .= " -f " . &file_path($testsrcdir, "Makefile");
}
if($WINDOWS && (-r "$testsrcdir/Makevars.win")) {
$makevars = " -f $testsrcdir/Makevars.win";
}
elsif(-r &file_path($testsrcdir, "Makevars")) {
$makevars = " -f " . &file_path($testsrcdir, "Makevars");
}
else {
open MAKEVARS, "> Makevars";
print MAKEVARS "makevars = -f Makevars\n";
print MAKEVARS "srcdir = $testsrcdir\n";
## at least windows does not pass env correctly to make
print MAKEVARS "R_LIBS = $ENV{'R_LIBS'}\n";
print MAKEVARS "VPATH = \$(srcdir)\n\n";
print MAKEVARS "test-src-1 =";
foreach my $file (&list_files_with_exts($testdir, "R")) {
print MAKEVARS "\\\n " . basename($file);
}
print MAKEVARS "\n";
print MAKEVARS "test-src-auto =";
foreach my $file (&list_files_with_exts($testdir, "Rin")) {
$file =~ s/Rin$/R/;
print MAKEVARS "\\\n " . basename($file);
}
print MAKEVARS "\n";
print MAKEVARS "USE_GCT = $opt_use_gct\n";
close MAKEVARS;
$makevars = " -f Makevars";
}
print "\n";
if(system("$R::Vars::MAKE $makefiles $makevars")) {
$log->error();
exit(1);
}
chdir($pkgoutdir);
$log->result("OK");
}
chdir($pkgoutdir);
if($opt_vignettes
&& (-d &file_path($pkgdir, "inst", "doc"))
&& &list_files_with_exts(&file_path($pkgdir, "inst", "doc"),
$vignette_exts_re)) {
$log->checking(join("",
("Sweave files in '",
&file_path("inst", "doc"),
"'")));
my $Rcmd = "library(tools)\n";
if($opt_install && ($R::Vars::OSTYPE eq "unix")) {
$Rcmd .= "checkVignettes(package=\"${pkgname}\", " .
"lib.loc = \"$pkgoutdir\", " .
"workdir=\"src\")\n";
}
else {
$Rcmd .= "checkVignettes(dir = \"${pkgdir}\")\n";
}
my @out = R_runR($Rcmd, "${R_opts} --quiet");
@out = grep(!/^\>/, @out);
if($#out>=0) {
$log->warning();
$log->print(join("\n", @out) . "\n");
}
else {
$log->result("OK");
}
}
## Run LaTeX on the manual.
if($opt_latex) {
if(-d &file_path($library, $pkgname, "latex")) {
$ENV{'TEXINPUTS'} =
env_path(&file_path($R::Vars::R_HOME, "share", "texmf"),
$ENV{'TEXINPUTS'});
$log->creating("${pkgname}-manual.tex");
open MANUAL, "> ${pkgname}-manual.tex";
print MANUAL "\\documentclass\{article\}\n" .
"\\usepackage[ae,hyper]\{Rd\}\n\\begin\{document\}\n";
my @texfiles =
&list_files_with_exts(&file_path($library, $pkgname,
"latex"),
"tex");
foreach my $file (@texfiles) {
open(FILE, "< $file")
or die("Error: cannot open file '$file' for reading");
while() {
print MANUAL $_;
}
close FILE;
}
print MANUAL "\\end\{document\}\n";
close MANUAL;
$log->result("OK");
if($HAVE_LATEX) {
$log->checking("${pkgname}-manual.tex");
print "\n";
if(R_system("${R::Vars::LATEX} ${pkgname}-manual")) {
$log->error();
exit(1);
}
$log->result("OK");
}
}
else {
if($HAVE_LATEX) {
$log->checking("DVI version of manual");
my $Rd2dvi_opts = "--no-preview -o ${pkgname}-manual.dvi";
##
## Surely there is a better way?
$Rd2dvi_opts .= ">/dev/null 2>&1"
if($R::Vars::OSTYPE eq "unix");
##
if(R_system("${R::Vars::R_CMD} Rd2dvi " .
"${Rd2dvi_opts} $pkgdir")) {
$log->error();
$log->print("Could not create DVI version.\n");
$log->print("This typically indicates Rd problems.\n");
exit(1);
}
$log->result("OK");
}
}
}
}
#**********************************************************
sub usage {
print STDERR <.
END
exit 0;
}