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

# Copyright (C) 2000-2004 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

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.177.2.7 $ ';
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_install = 1;
my $opt_vignettes = 1;

my $WINDOWS = ($R::Vars::OSTYPE eq "windows");

R::Vars::error("R_HOME", "R_CMD", "R_EXE");

my @known_options = ("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",
		    "install=s" => \$opt_install, "no-install",
		     "no-vignettes");
GetOptions(@known_options) 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_install = 0 if $opt_no_install;
$opt_vignettes = 0 if $opt_no_vignettes;

if($opt_install eq "fake") {
    ## If we fake installation, then we cannot *run* any code.
    $opt_examples = $opt_tests = $opt_vignettes = 0;
}
$opt_install = 0 if($opt_install eq "no");

my $opt_ff_calls = 1;
## The neverending story ... for the time being, allow to turn this off
## by setting the environment variable R_CHECK_FF_CALLS to a Perl 'null'
## value.
if(defined($ENV{"R_CHECK_FF_CALLS"})) {
    $opt_ff_calls = $ENV{"R_CHECK_FF_CALLS"};
}

## Use system default unless explicitly specified otherwise.
$ENV{"R_DEFAULT_PACKAGES"} = "";

my $startdir = R_cwd();
$opt_outdir = $startdir unless $opt_outdir;
chdir($opt_outdir)
    or die "Error: cannot change to directory '$opt_outdir'\n";
my $outdir = R_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 = R_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 open file 'Rtextest$$.tex' for writing\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(<Rtextest$$.*>);
    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 file 'doc/KEYWORDS.db' for reading\n";
while(<KEYWORDS>) {
    if(/^.*\|([^:]*):.*/) {
	$standard_keywords{$1} = 1;
    }
}
close(KEYWORDS);

## Get the names of base and recommended packages and stubs for later.
my @R_pkgs_base = ();
my @R_pkgs_recommended = ();
my @R_pkgs_stubs = ();
open(MAKEVARS,
     "< " . &file_path(${R::Vars::R_HOME}, "share", "make", "vars.mk"))
  or die "Error: cannot open file 'share/make/vars.mk' for reading\n";
while(<MAKEVARS>) {
    @R_pkgs_base =
	split(/ +/, $2) if(/^(R_PKGS_BASE *=)(.*)/);
    @R_pkgs_recommended =
	split(/ +/, $2) if(/^(R_PKGS_RECOMMENDED *=)(.*)/);
    @R_pkgs_stubs =
	split(/ +/, $2) if(/^(R_PKGS_STUBS *=)(.*)/);
}

## <FIXME>
## We now always redefine T/F when running the examples, but warn about
## an empty R_CHECK_WITH_T_N_F_AS_NULL environment variable intended to
## disable the redefinition.
## Remove this eventually, and also the test in massage-Examples.pl.
if(defined($ENV{'R_CHECK_WITH_T_N_F_AS_NULL'})
   && $ENV{'R_CHECK_WITH_T_N_F_AS_NULL'} eq "") {
    my $log = new R::Logfile();
    $log->message("redefining R_CHECK_WITH_T_N_F_AS_NULL to 'yes'");
    $log->close();
}
$ENV{'R_CHECK_WITH_T_N_F_AS_NULL'} = "yes";
## </FIXME>

## This is the main loop over all packages to be checked.
(scalar(@ARGV) > 0) or die "Error: no packages were specified\n";
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\n";
    chdir($pkg)
	or die "Error: cannot change to directory '$pkg'\n";
    my $pkgdir = R_cwd();
    my $pkgname = basename($pkgdir);
    chdir($startdir);

    my $pkgoutdir = &file_path($outdir, "$pkgname.Rcheck");
    rmtree($pkgoutdir) if ($opt_clean && (-d $pkgoutdir)) ;
    if(!(-d $pkgoutdir)) {
	mkdir($pkgoutdir, 0755)
	  or die("Error: cannot create directory '$pkgoutdir'\n");
    }

    $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);
    }
    if($WINDOWS) { ## need to avoid spaces in $library
	$library = Win32::GetShortPathName($library) if $library =~ / /;
    }

    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);
	}
	## <NOTE>
	## This check should be adequate, but would not catch a manually
	## installed package, nor one installed prior to 1.4.0.
	## </NOTE>
	$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);
	}
	## Option '--no-install' turns off installation and the tests
	## which require the package to be installed.  When testing
	## recommended packages bundled with R we can skip installation,
	## and do so if '--install=skip' was given.  If command line
	## option '--install' is of the form 'check:FILE', it is assumed
	## that installation was already performed with stdout/stderr to
	## FILE, the contents of which need to be checked (without
	## repeating the installation).
	## <NOTE>
	## In this case, one also needs to specify *where* the package
	## was installed to using command line option '--library'.
	## Perhaps we should check for that, although '--install=check'
	## is really only meant for repository maintainers.
	## </NOTE>
	if($opt_install) {
	    if($opt_install eq "skip") {
		$log->message("skipping installation test");
	    }
	    else {
		my $use_install_log =
		    (($opt_install =~ /^check/) ||
		     $ENV{"R_CHECK_USE_INSTALL_LOG"} ||
		     !(-t STDIN && -t STDOUT));
		my $cmd = "${R::Vars::R_CMD} INSTALL -l";
		$cmd .= " " . &shell_quote_file_path($library);
		$cmd .= " --fake" if($opt_install eq "fake");
		$cmd .= " " . &shell_quote_file_path($pkgdir);
		if(!$use_install_log) {
		    ## Case A: No redirection of stdout/stderr from
		    ## installation.
		    print("\n");
		    if(R_system($cmd)) {
			$log->error();
			$log->print("Installation failed.\n");
			exit(1);
		    }
		    print("\n");
		}
		else {
		    ## Case B. All output from installation redirected,
		    ## or already available in the log file.
		    $log->checking("whether package '$pkgname' " .
				   "can be installed");
		    my $out = &file_path($pkgoutdir, "00install.out");
		    my $install_error;
		    my @lines;
		    if($opt_install =~ /^check/) {
			copy(substr($opt_install, 6), $out);
			$opt_install = "check";
			@lines = &read_lines($out);
			$install_error =
			    ($lines[$#lines] !~ /^\* DONE/);
		    }
		    else {
			$cmd .= " >" .
			    &shell_quote_file_path($out) .
			    " 2>&1";
			$install_error = &R_system($cmd);
		    }
		    if($install_error) {
			$log->error();
			$log->print("Installation failed.\n");
			$log->print("See '$out' for details.\n");
			exit(1);
		    }
		    ## There could still be some important warnings that
		    ## we'd like to report.  For the time being, start
		    ## with compiler warnings about non ISO C code (Or
		    ## at least, what looks like it.)  In theory, we
		    ## should only do this when using GCC ...
		    @lines = &read_lines($out)
			unless($opt_install eq "check");
		    my $warn_re =
			"(" . join("|", ("^WARNING:",
					 "^Warning:",
					 ": warning: .*ISO C",
					 "missing link\\(s\\):")) . ")";
		    @lines = grep(/$warn_re/, @lines);
		    ## Package writers cannot really do anything about
		    ## non ISO C code in *system* headers.  Hence, try
		    ## to ignore these by default, but make it possible
		    ## to get all ISO C warnings via an environment
		    ## variable.
		    @lines = grep(!/^ *\/.*: warning: .*ISO C/, @lines)
			if(!$ENV{"R_CHECK_ALL_NON_ISO_C"});
		    if(scalar(@lines) > 0) {
			$log->warning();
			$log->print("Found the following " .
				    "significant warnings:\n");
			$log->print("  " . join("\n  ", @lines) . "\n");
		    }
		    else {
			$log->result("OK");
		    }
		}
	    }
	}

    }

    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 = R_cwd();
	$pkgname = basename($pkgdir);
    }
    else {
	$log->error();
	$log->print("Package directory '$pkg' does not exist.\n");
	exit(1);
    }
    $log->result("OK");

    chdir($pkgdir);

    ## Build list of exclude patterns.

    my @exclude_patterns = R::Utils::get_exclude_patterns();
    ## <FIXME>
    ## This is not right for bundles where the build ignore pattern
    ## file is in the top-level bundle dir.  We could fix this by
    ## using '../.Rbuildignore' instead, but then e.g.
    ##   find(\&find_wrong_perms_A, ".");
    ##   find(\&find_wrong_perms_B, ".");
    ## are wrong because the exclude patterns are relative to '..'.
    if(-f "./.Rbuildignore") {
	open(RBUILDIGNORE, "./.Rbuildignore");
	while(<RBUILDIGNORE>) {
	    chop;
	    push(@exclude_patterns, $_) if $_;
	}
	close(RBUILDIGNORE);
    }
    ## </FIXME>

    ## Check for portable file names.

    ## Ensure that the names of the files in the package are valid for
    ## at least the supported OS types.  Under Unix, we definitely
    ## cannot have '/'; under Windows, the control characters as well as
    ##   " * : < > ? \ |
    ## (i.e., ASCII characters 1 to 31 and 34, 36, 58, 60, 62, 63, 92,
    ## and 124) are or can be invalid.  (In addition, one cannot have
    ## one-character file names consisting of just ' ', '.', or '~'.)
    ## Based on information by Uwe Ligges, Duncan Murdoch, and Brian
    ## Ripley.

    ## Furthermore, Uwe Ligges says that Windows still does not allow
    ## the following DOS device names (by themselves or with possible
    ## extensions):
    ##
    ## Name    Function
    ## ----    --------
    ## CON     Keyboard and display
    ## PRN     System list device, usually a parallel port
    ## AUX     Auxiliary device, usually a serial port
    ## CLOCK$  System real-time clock
    ## NUL     Bit-bucket device
    ## COM1    First serial communications port
    ## COM2    Second serial communications port
    ## COM3    Third serial communications port
    ## COM4    Fourth serial communications port
    ## LPT1    First parallel printer port
    ## LPT2    Second parallel printer port
    ## LPT3    Third parallel printer port

    $log->checking("for portable file names");
    my @bad_files = ();
    sub find_wrong_names {
	my $file_path = $File::Find::name;
	$file_path =~ s/^[^\/]*\///;
	foreach my $p (@exclude_patterns) {
	    if($WINDOWS) {
		## Argh: Windows is case-honoring but not
		## case-insensitive ...
		return 0 if($file_path =~ /$p/i);
	    }
	    else {
		return 0 if($file_path =~ /$p/);
	    }
	}
	my $file_name = basename($file_path);
	if(grep(/[[:cntrl:]\"\*\/\:\<\>\?\\\|]/, $file_name)) {
	    push(@bad_files, $file_path);
	}
	else {
	    $file_name =~ tr/A-Z/a-z/;
	    $file_name =~ s/\..*//;
	    push(@bad_files, $file_path)
	      if(grep(/^(con|prn|aux|clock\$|nul|lpt[1-3]|com[1-4])$/,
		     $file_name));
	}
    }
    find(\&find_wrong_names, ".");
    if(scalar(@bad_files) > 0) {
	$log->error();
	$log->print("Found the following files with " .
		    "non-portable file names:\n");
	$log->print("  " . join("\n  ", @bad_files) . "\n");
	$log->print(wrap("", "",
			 ("These are not valid file names",
			  "on all R platforms.\n",
			  "Please rename the files and try again.\n",
			  "See section 'Package structure'",
			  "in manual 'Writing R Extensions'.\n")));
	exit(1);
    }

    $log->result("OK");

    ## Check for sufficient file permissions (Unix only).

    ## This used to be much more 'aggressive', requiring that dirs and
    ## files have mode >= 00755 and 00644, respectively (with an error
    ## if not), and that files know to be 'text' have mode 00644 (with a
    ## warning if not).  We now only require that dirs and files have
    ## mode >= 00700 and 00400, respectively, and try to fix
    ## insufficient permission in the INSTALL code (Unix only).
    ##
    ## In addition, we check whether files 'configure' and 'cleanup'
    ## exists in the top-level directory but are not executable, which
    ## is most likely not what was intended.

    if($R::Vars::OSTYPE eq "unix") {
	$log->checking("for sufficient/correct file permissions");
	my @bad_files = ();

	## Phase A.  Directories at least 700, files at least 400.
	sub find_wrong_perms_A {
	    my $filename = $File::Find::name;
	    $filename =~ s/^[^\/]*\///;
	    foreach my $p (@exclude_patterns) {
		## Unix only, so no special casing for Windows.
		return 0 if($filename =~ /$p/);
	    }
	    if(-d $_ && (((stat $_)[2] & 00700) < oct("700"))) {
		push(@bad_files, $filename);
	    }
	    if(-f $_ && (((stat $_)[2] & 00400) < oct("400"))) {
		push(@bad_files, $filename);
	    }
	}
        find(\&find_wrong_perms_A, ".");
        if(scalar(@bad_files) > 0) {
	    $log->error();
	    $log->print("Found the following files with " .
			"insufficient permissions:\n");
	    $log->print("  " . join("\n  ", @bad_files) . "\n");
	    $log->print(wrap("", "",
			     ("Permissions should be at least 700",
			      "for directories and 400 for files.\n",
			      "Please fix permissions",
			      "and try again.\n")));
	    exit(1);
	}

	## Phase B.  Top-level scripts 'configure' and 'cleanup' should
	## really be mode at least 500, or they will not be necessarily
	## be used (or should we rather change *that*?)
	@bad_files = ();
	foreach my $filename ("configure", "cleanup") {
	    ## This is a bit silly ...
	    my $ignore = 0;
	    foreach my $p (@exclude_patterns) {
		if($filename =~ /$p/) {
		    $ignore = 1;
		    last;
		}
	    }
	    if(!$ignore
	       && (-f $filename)
	       && (((stat $filename)[2] & 00500) < oct("500"))) {
		push(@bad_files, $filename);
	    }
	}
	if(scalar(@bad_files) > 0) {
	    $log->warning();
	    $log->print(wrap("", "",
			     "The following files should most likely",
			     "be executable (for the owner):\n"));
	    $log->print("  " . join("\n  ", @bad_files) . "\n");
	    $log->print(wrap("", "",
			     "Please fix permissions.\n"));
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check DESCRIPTION meta-information.

    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);
	}
    }

    my @msg_DESCRIPTION
      = ("See the information on DESCRIPTION files",
	 "in section 'Creating R packages'",
	 "of the 'Writing R Extensions' manual.\n");

    $log->checking("DESCRIPTION meta-information");

    ## Mandatory entries in DESCRIPTION:
    ##   Package, Version, License, Description, Title, Author,
    ##   Maintainer.

    foreach my $field (qw(Package Version License Description Title
			  Author Maintainer)) {
	if(!$description->{$field}) {
	    $log->error();
	    $log->print("No DESCRIPTION $field field found.\n");
	    $log->print(wrap("", "", @msg_DESCRIPTION));
	    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);
    }
    if($description->{"Package"} ne $pkgname) {
	$log->error();
	$log->print("DESCRIPTION Package field differs from dir	name.\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);
    }

    if(($description->{"Maintainer"} !~ /^[^<>]*<[^<>@]+@[^<>@]+> *$/) &&
       ($description->{"Maintainer"} !~ /ORPHANED/)) {
	$log->error();
	$log->print("Malformed Maintainer field.\n");
	$log->print(wrap("", "", @msg_DESCRIPTION));
	exit(1);
    }

    ## Optional entries in DESCRIPTION:
    ##   Depends, Namespace, Priority.
    ## These must be correct if present.

    ## <FIXME>
    ## We should also check Suggests here, but what precisely is allowed
    ## for this?
    ## </FIXME>

    if($description->{"Depends"}) {
	my @dependencies = split(/\,\s*/, $description->{"Depends"});
	my @matches;
	my ($is_bad_dep_op, $is_bad_dep_version);
	my ($bad_dep_value, @bad_dep_error);
	foreach my $dep (@dependencies) {
	    @matches =
	      ($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
	    ## '-'.
	    $is_bad_dep_op =
	      (($matches[2] ne "<=") && ($matches[2] ne ">="));
	    $is_bad_dep_version = ($matches[3] =~ /[^\d\.\-]/);
	    if(!($matches[0])
	       || ($matches[1]
		   && ($is_bad_dep_op || $is_bad_dep_version))) {
		$bad_dep_entry = $dep;
		if(!($matches[0])) {
		    @bad_dep_error =
		      ("Entries must be names of packages, optionally",
		       "followed by '<=' or '>=', white space, and a",
		       "valid version number in parentheses.\n");
		}
		elsif($is_bad_dep_op) {
		    @bad_dep_error =
			("Infeasible comparison operator '$matches[2]'.\n",
			 "Only operators '<=' and '>=' are possible.\n");
		}
		elsif($is_bad_dep_version) {
		    @bad_dep_error =
			("Infeasible version number '$matches[3]'.\n",
			 "Version numbers must only contain digits,",
			 "'.' or '-'.\n");
		}
		break;
	    }
	}
	if($bad_dep_entry) {
	    $log->error();
	    $log->print("Malformed Depends field.\n");
	    $log->print("Offending entry is: '$bad_dep_entry'.\n");
	    $log->print(wrap("", "", @bad_dep_error));
	    $log->print(wrap("", "", @msg_DESCRIPTION));
	    exit(1);
	}
    }

    if($description->{"Namespace"}) {
	if($description->{"Namespace"} ne $description->{"Package"}) {
	    $log->error();
	    $log->print("Namespace and Package fields differ.\n");
	    $log->print(wrap("", "", @msg_DESCRIPTION));
	    exit(1);
	}
    }

    if($description->{"Priority"}) {
	if($description->{"Priority"}
	   =~ /^(base|recommended|defunct-base)/i) {
	    ## Simplify matters by treating high priority packages and
	    ## base stubs together.
	    my $found = 0;
	    foreach my $p (@R_pkgs_base,
			   @R_pkgs_recommended,
			   @R_pkgs_stubs) {
		if($description->{"Package"} eq $p) {
		    $found = 1;
		    break;
		}
	    }
	    if(!$found) {
		$log->error();
		$log->print("Invalid Priority field.\n");
		$log->print(wrap("", "",
				 ("Packages with priorities 'base' or",
				  "'recommended' or 'defunct-base'",
				  "must already be known to R.\n")));
		$log->print(wrap("", "", @msg_DESCRIPTION));
		exit(1);
	    }
	}
    }

    $log->result("OK");

    ## Check package dependencies.

    ## Try figuring out whether the package dependencies can be resolved
    ## at run time.  Ideally, the installation mechanism would do this,
    ## and we also do not check versions ... also see whether vignette
    ## and namespace package dependencies are recorded in DESCRIPTION.

    ## <NOTE>
    ## When checking uninstalled base or bundle packages, there is no
    ## DESCRIPTION file in the package source directory.  Hence, for
    ## simplicity, we only run this check on "installed" packages.  We
    ## could work around this, but checking uninstalled packages is a
    ## bad thing anyway.
    ##
    ## Also, if --install=skip, bundles never get DESCRIPTION files
    ## made in the source dir which is what we are checking here.
    ## </NOTE>
    if($opt_install) {
	$log->checking("package dependencies");
	## According to FL, everything listed in Depends or Suggests
	## should be available for successfully running R CMD check.
	## \VignetteDepends{} entries not "required" by the package code
	## must be in Suggests.  Note also that some of us think that a
	## package vignette must require its own package, which otoh is
	## not required in the package DESCRIPTION file.
	## Namespace imports must really be in Depends.
	my $Rcmd = "tools:::.checkPackageDepends(\"${pkgname}\")\n";
	my @out = R_runR($Rcmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES=tools");
	@out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    ## <FIXME>
	    ## These should really all be errors ...
	    if(grep(/^Packages required but not available:/, @out)) {
		$log->error();
		$log->print(join("\n", @out) . "\n");
		$log->print(wrap("", "", @msg_DESCRIPTION));
		exit(1);
	    }
	    else {
		$log->warning();
		$log->print(join("\n", @out) . "\n");
		$log->print(wrap("", "", @msg_DESCRIPTION));
	    }
	    ## </FIXME>
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check index information.

    $log->checking("index information");
    my @msg_index = ("See the information on INDEX files and package",
		     "subdirectories in section 'Creating R packages'",
		     "of the 'Writing R Extensions' manual.\n");
    my $any = 0;
    if(-z "INDEX") {
	## If there is an empty INDEX file, we get no information about
	## the package contents ...
	$any++;
	$log->warning();
	$log->print("Empty file 'INDEX'.\n");
    }
    if((-d "demo")
       && &list_files_with_type("demo", "demo")) {
	my $index = &file_path("demo", "00Index");
	if(!(-s $index)) {
	    $log->warning() unless($any);
	    $any++;
	    $log->print("Empty or missing file '$index'.\n");
	}
	else {
	    my $dir = "demo";
	    my $cmd = ".checkDemoIndex(\"$dir\")\n";
	    my @out = R_runR($cmd, "${R_opts} --quiet",
			     "R_DEFAULT_PACKAGES=tools");
	    @out = grep(!/^\>/, @out);
	    if(scalar(@out) > 0) {
		$log->warning() unless($any);
		$any++;
		$log->print(join("\n", @out) . "\n");
	    }
	}
    }
    if((-d &file_path("inst", "doc"))
       && &list_files_with_type(&file_path("inst", "doc"),
				"vignette")) {
	my $dir = &file_path("inst", "doc");
	my $cmd = ".checkVignetteIndex(\"$dir\")\n";
	my @out = R_runR($cmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES=tools");
	@out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    $log->warning() unless($any);
	    $any++;
	    $log->print(join("\n", @out) . "\n");
	}
    }
    if($any) {
	$log->print(wrap("", "", @msg_index));
    }
    else {
	$log->result("OK");
    }

    ## Check package subdirectories.

    $log->checking("package subdirectories");
    my $any;
    if($ENV{"R_CHECK_SUBDIRS_NOCASE"}) {
	## Argh.  We often get submissions where 'R' comes out as 'r',
	## or 'man' comes out as 'MAN'.  Maybe we should warn about this
	## unconditionally ...
	if((-d "r")) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Found subdirectory 'r'.\n");
	    $log->print("Most likely, this should be 'R'.\n")
	}
	if((-d "MAN")) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Found subdirectory 'MAN'.\n");
	    $log->print("Most likely, this should be 'man'.\n")
	}
    }
    if((-d "data") || (-d "demo") || (-d "src") || (-d "inst")) {
	## Subdirectory 'data' without data sets?
	if((-d "data") && !&list_files_with_type("data", "data")) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Subdirectory 'data' contains no data sets.\n");
	}
	## Subdirectory 'demo' without demos?
	if((-d "demo") && !&list_files_with_type("demo", "demo")) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Subdirectory 'demo' contains no demos.\n");
	}
	## Subdirectory 'src' without sources?
	## <NOTE>
	## If there is a Makefile (or a Makefile.win), we cannot assume
	## that source files have the predefined extensions.
	## </NOTE>
	if((-d "src")
	   && !(&list_files_with_exts("src", "([Ccf]|cc|cpp)")
		|| (-f &file_path("src", "Makefile"))
		|| (-f &file_path("src", "Makefile.win")))) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Subdirectory 'src' contains no source files.\n");
	}
	## Do subdirectories of 'inst' interfere with R package system
	## subdirectories?
	if((-d "inst")) {
	    my @R_system_subdirs =
		("Meta", "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(scalar(@bad_dirs) > 0) {
		$log->warning() unless $any;
		$any++;
		$log->print(wrap("", "",
				 ("Found the following non-empty",
				  "subdirectories of 'inst' 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")));
	    }
	}
    }
    $log->result("OK") unless $any;

    ## Check R code for syntax errors.

    if(!$is_base_pkg && (-d "R")) {
	$log->checking("R files for syntax errors");
	## <NOTE>
	## We could/should really check *all* OS specific subdirs here.
	my @R_files = &list_files_with_type("R", "code");
	## </NOTE>
	my $Rcmd = "RFiles <- c(\"";
	$Rcmd .= join("\",\n\"", @R_files) . "\")\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(scalar(@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 @R_files;
	if($opt_install) {
	    ## Only need to check the installed file (if installed).
	    @R_files = (&file_path($library, $pkgname, "R", $pkgname));
	}
	else {
	    ## Otherwise (if the package was not installed), we need to
	    ## check all R code files.
	    ## <NOTE>
	    ## We could/should really check all OS specific subdirs here.
	    @R_files = &list_files_with_type("R", "code");
	    ## </NOTE>
	}
	my $any = 0;
	my $ext;
	foreach my $file (@R_files) {
	    last if $any;
	    open(FILE, "< $file")
	      or die "Error: cannot open file '$file' for reading\n";
	    while(<FILE>) {
		if(/library.dynam\(\"(.*?)\"/o) {
		    my $arg = $1;
		    if($arg =~ /\.(so|sl|dll)$/) {
			$ext = $1;
			$any++;
			last;
		    }
		}
	    }
	    close(FILE);
	}
	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",
			      "See ?library.dynam\n")));
	    exit(1);
	}
    }

    ## Check whether methods have all arguments of the corresponding
    ## generic.

    if(-d "R") {
	$log->checking("S3 generic/method consistency");

	my @msg_S3_methods =
	  ("See section 'Generic functions and methods'",
	   "of the 'Writing R Extensions' manual.\n");

	my $Rcmd;
	if($opt_install) {
	    $Rcmd .= "checkS3methods(package = \"${pkgname}\")\n";
	}
        else {
	    $Rcmd .= "checkS3methods(dir = \"${pkgdir}\")\n";
	}

        my @out = R_runR($Rcmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	@out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    $log->warning();
	    $log->print(join("\n", @out) . "\n");
	    $log->print(wrap("", "", @msg_S3_methods));
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check whether replacement functions have their final argument
    ## named 'value'.

    if(-d "R") {
	$log->checking("for replacement functions with final arg not " .
		       "named 'value'");
	my $Rcmd;
	if($opt_install) {
	    $Rcmd .= "checkReplaceFuns(package = \"${pkgname}\")\n";
	}
	else {
	    $Rcmd .= "checkReplaceFuns(dir = \"${pkgdir}\")\n";
	}

        my @out = R_runR($Rcmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
        @out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    ## <NOTE>
	    ## We really want to stop if we find offending replacement
	    ## functions.  But we cannot use error() because output may
	    ## contain warnings ...
	    $log->warning();
	    ## </NOTE>
	    $log->print(join("\n", @out) . "\n");
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check foreign function calls.

    if($opt_ff_calls && (-d "R")) {
	$log->checking("foreign function calls");

	my @msg_ff_calls =
	  ("See section 'System and foreign language interfaces'",
	   "of the 'Writing R Extensions' manual.\n");

	my $Rcmd;
	if($opt_install) {
	    $Rcmd .= "checkFF(package = \"${pkgname}\")\n";
	}
        else {
	    $Rcmd .= "checkFF(dir = \"${pkgdir}\")\n";
	}

        my @out = R_runR($Rcmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	@out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    $log->warning();
	    $log->print(join("\n", @out) . "\n");
	    $log->print(wrap("", "", @msg_ff_calls));
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check R documentation files.

    my @msg_writing_Rd
      = ("See chapter 'Writing R documentation files'",
	 "in manual 'Writing R Extensions'.\n");

    if(-d "man") {
	$log->checking("Rd files");

	my @Rd_files =
	  &list_files_with_type("man", "docs", $R::Vars::OSTYPE);

	@Rd_files = sort(@Rd_files);

	## <FIXME>
	## The code below tries to deal with common problems in Rd
	## files, such as unbalanced braces, missing mandatory tags or
	## duplicated tags which must be unique, etc.  This is tricky:
	## in particular, one needs to deal with Rd preprocessing (which
	## could e.g. eliminate mandatory tags or duplicated tags which
	## must be unique).  So at least in theory, for Rd files which
	## use preprocessor conditionals, one would need to repeat the
	## tests for at least all supported OS types.  Argh ...
	## </FIXME>

	my @mandatory_tags = qw(name alias title description keyword);
	my @unique_tags = qw(name title description usage arguments
			     format details value references source
			     seealso examples note author synopsis);
	my @bad_braces;
	my @bad_names;
	my @bad_titles;
	my %bad_keywords;
	my %bad_mandatory_tags;
	my %bad_unique_tags;

	## Create hash all_tags with all tags found in mandatory_tags
	## and unique_tags.
	my %all_tags;
	foreach my $tag (@mandatory_tags, @unique_tags) {
	    $all_tags{$tag}++;
	}

	foreach my $file (@Rd_files) {
	    ## Call the Rd preprocessor first.
	    my $text = &Rdpp($file, $R::Vars::OSTYPE);
	    ## And split into lines again.
	    my @lines = split(/\n/, $text);

	    ## Check tags and keywords.
	    my %keywords;
	    my %tagcount;
	    foreach my $line (@lines) {
		foreach my $tag (keys %all_tags) {
		    if($line =~ /^\s*\\$tag\{/) {
			$tagcount{$tag}++;
		    }
		}
                if($line =~ /^\s*\\keyword\{\s*([^}]*[^}\s])\s*\}.*/) {
                    $keywords{$1} = 1;
                }
	    }
	    foreach my $tag (@mandatory_tags) {
		push(@{$bad_mandatory_tags{$tag}}, $file)
		    unless ($tagcount{$tag} > 0);
	    }
	    foreach my $tag (@unique_tags) {
		push(@{$bad_unique_tags{$tag}}, $file)
		    unless ($tagcount{$tag} <= 1);
	    }
            foreach my $key (keys(%keywords)) {
                push(@{$bad_keywords{$file}}, $key)
                    unless $standard_keywords{$key};
            }

	    ## Check name and title.
	    ## Note that the code here is based on Rd::info().
	    ## (Not entirely safe ...)
	    $text =~ /\\name\{\s*([^\}]+)\s*\}/s;
	    push(@bad_names, $file) if($1 =~ /^\s*$/);
	    ## Using LaTeX special characters (# $ % & ~ _ ^ \ { })
	    ## causes the creation of PDF bookmarks to fail.
	    push(@bad_names, $file)
	      if($1 =~ /(\#|\$|\%|\&|\~|\_|\^|\\|\{|\})/);
	    $text =~ /\\title\{\s*([^\}]+)\s*\}/s;
	    push(@bad_titles, $file) if($1 =~ /^\s*$/);

	    ## Check for unbalanced braces.
	    $text = "";
	    ## Get rid of the \alias entries as these may have
	    ## unbalanced braces (Paren.Rd).
	    foreach my $line (@lines) {
		$text .= "\n$line" unless ($line =~ /^\s*\\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(@bad_braces, $file);
	    }

	}

	my $any = 0;

	if(scalar(@bad_braces) > 0) {
	    $log->error();
	    $log->print("Rd files with unbalanced braces:\n");
	    $log->print("  " . join("\n  ", @bad_braces) . "\n");
	    $log->print(wrap("", "",
			     ("Unbalanced braces are Rd syntax",
			      "errors, and result in incorrect",
			      "documentation.\n")));
	    exit(1);
	}

	if(scalar(@bad_names) > 0) {
	    $log->error();
	    $log->print("Rd files with missing or empty ".
			"or invalid '\\name':\n");
	    $log->print("  " . join("\n  ", @bad_names) . "\n");
	    $log->print(wrap("", "", @msg_writing_Rd));
	    exit(1);
	}
	if(scalar(@bad_titles) > 0) {
	    $log->error();
	    $log->print("Rd files with missing or empty '\\title':\n");
	    $log->print("  " . join("\n  ", @bad_titles) . "\n");
	    $log->print(wrap("", "", @msg_writing_Rd));
	    exit(1);
	}

	my $any_missing_mandatory_tag = 0;
	foreach my $tag (@mandatory_tags) {
	    if(exists $bad_mandatory_tags{$tag}) {
		$log->warning() unless $any;
		$any++;
		$any_missing_mandatory_tag++;
		$log->print("Rd files without '${tag}':\n");
		$log->print("  " .
			    join("\n  ", @{$bad_mandatory_tags{$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 (@unique_tags) {
	    if(exists $bad_unique_tags{$tag}) {
		$log->warning() unless $any;
		$any++;
		$any_duplicate_unique_tag++;
		$log->print("Rd files with duplicate '${tag}':\n");
		$log->print("  " .
			    join("\n  ", @{$bad_unique_tags{$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(%bad_keywords)) {
	    $log->warning() unless $any;
	    $any++;
	    $log->print("Rd files with non-standard keywords:\n");
            foreach my $file (keys(%bad_keywords)) {
                $log->print(wrap("  ", "    ",
				   ("'$file':",
				    @{$bad_keywords{$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 missing documentation entries.

    if(((-d "R") || (-d "data")) && (-d "man")) {
	$log->checking("for missing documentation entries");

	my $Rcmd;
	if($opt_install) {
	    $Rcmd .= "undoc(package = \"${pkgname}\")\n";
	}
	else {
	    $Rcmd .= "undoc(dir = \"${pkgdir}\")\n";
	}

        my @out = R_runR($Rcmd, "${R_opts} --quiet",
                         "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	my @err = grep(/^Error/, @out);
	@out = grep(!/^\>/, @out);
	if(scalar(@err) > 0) {
	    $log->error();
	    $log->print(join("\n", @err) . "\n");
	    exit(1);
	}
	elsif(scalar(@out) > 0) {
	    $log->warning();
	    $log->print(join("\n", @out) . "\n");
	    my $details;
	    $details = " (including S4 classes and methods)"
	      if(grep(/^Undocumented S4/, @out));
	    $log->print(wrap("", "",
			     ("All user-level objects",
			      "in a package${details} should",
			      "have documentation entries.\n")));
	    $log->print(wrap("", "", @msg_writing_Rd));
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check for code/documentation mismatches.

    if($opt_codoc && (-d "man")) {
	$log->checking("for code/documentation mismatches");
	my $any = 0;

	## Check for code/documentation mismatches in functions.
	if(-d "R") {
	    my $Rcmd;
	    if($opt_install) {
		$Rcmd .= "codoc(package = \"${pkgname}\")\n";
	    }
	    else {
		$Rcmd .= "codoc(dir = \"${pkgdir}\")\n";
	    }
	    my @out = R_runR($Rcmd, "${R_opts} --quiet",
			     "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");

	    ## <FIXME>
	    ## 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(scalar(@out) > 0) {
		$any++;
		$log->warning();
		$log->print(join("\n", @out) . "\n");
	    }
	    ## </FIXME>
	}

	## Check for code/documentation mismatches in data sets.
	if($opt_install) {
	    my $Rcmd = "codocData(package = \"${pkgname}\")\n";
	    my @out = R_runR($Rcmd, "${R_opts} --quiet",
			     "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	    @out = grep(!/^\>/, @out);
	    if(scalar(@out) > 0) {
		$log->warning() unless($any);
		$any++;
		$log->print(join("\n", @out) . "\n");
	    }
	}

	## Check for code/documentation mismatches in S4 classes.
	if($opt_install && (-d "R")) {
	    my $Rcmd = "codocClasses(package = \"${pkgname}\")\n";
	    my @out = R_runR($Rcmd, "${R_opts} --quiet",
			     "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	    @out = grep(!/^\>/, @out);
	    if(scalar(@out) > 0) {
		$log->warning() unless($any);
		$any++;
		$log->print(join("\n", @out) . "\n");
	    }
	}

	$log->result("OK") unless($any);

    }

    ## Check Rd files, for consistency of \usage with \arguments (are
    ## all arguments shown in \usage documented in \arguments?) and
    ## aliases (do all functions shown in \usage have an alias?)

    if(-d "man") {
	$log->checking("Rd \\usage sections");

	my $Rcmd;
	if($opt_install) {
	    $Rcmd .= "checkDocFiles(package = \"${pkgname}\")\n";
	}
	else {
	    $Rcmd .= "checkDocFiles(dir = \"${pkgdir}\")\n";
	}

        my @out = R_runR($Rcmd, "${R_opts} --quiet",
			 "R_DEFAULT_PACKAGES='utils,graphics,stats,tools'");
	@out = grep(!/^\>/, @out);
	if(scalar(@out) > 0) {
	    $log->warning();
	    $log->print(join("\n", @out) . "\n");
	}
	else {
	    $log->result("OK");
	}
    }

    ## Check C sources/headers for CRLF line endings.

    ## <FIXME>
    ## Does ISO C really require LF line endings?  (Reference?)
    ## We definitely know that Solaris cc will not accept CRLF or CR
    ## line endings.
    ## Note that we currently only check the CRLF part.
    ## </FIXME>

    if(!$is_base_pkg && (-d "src")) {
	$log->checking("for CRLF line endings in C sources/headers");
	my @src_files = &list_files_with_exts("src", "[ch]");
	my @bad_files = ();
	foreach my $file (@src_files) {
	    open(FILE, "< $file")
	      or die "Error: cannot open '$file' for reading\n";
            binmode(FILE);	# for Windows
	    while(<FILE>) {
		chop;
		if($_ =~ /\r$/) {
		    push(@bad_files, $file);
		    last;
		}
	    }
	    close(FILE);
	}
	if(scalar(@bad_files) > 0) {
	    $log->warning();
	    $log->print("Found the following C sources/headers with " .
			"CRLF line endings:\n");
	    $log->print("  " . join("\n  ", @bad_files) . "\n");
	    $log->print("ISO C requires LF 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");
        ## $Rexdir might contain spaces (not on Windows)
        my $iszipped = 0;
	my $cmd;

        if(-e &file_path($Rexdir, "Rex.zip")) {
            $iszipped = 1;
	    my $UNZIP = &R_getenv("R_UNZIPCMD", "unzip -q");
            my $Rexfile = &file_path($Rexdir, "Rex.zip");
	    $cmd = join(" ",
			("$UNZIP",
			 &shell_quote_file_path($Rexfile),
			 "-d",
			 &shell_quote_file_path($Rexdir)));
	    if(R_system($cmd)) {
		$log->error();
		$log->print("Cannot extract examples from ZIP archive.\n");
		exit(1);
	    }
        }
        if($WINDOWS) {
            ## avoid Rcmd as line may be too long after expansion.
            ## We've forced R_HOME and Rexdir to have no spaces already.
            $cmd = "perl ${R::Vars::R_HOME}/bin/massage-Examples ".
  		     "${pkgname} ${Rexdir} ".
  		     "> ${pkgname}-Ex.R";
        } else {
	    $cmd = join(" ",
			("${R::Vars::R_CMD} perl",
			 &file_path(${R::Vars::R_HOME}, "share", "perl",
				    "massage-Examples.pl"),
			 "${pkgname}",
			 &shell_quote_file_path(${Rexdir}),
			 "> ${pkgname}-Ex.R"));
        }

 	if(R_system($cmd)) {
  	    $log->error();
	    $log->print("Running massage-Examples to create " .
			"${pkgname}-Ex.R failed.\n");
  	    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 2>&1";
	}
	else {
            $cmd = "${R::Vars::R_EXE} ${R_opts} " .
	      "< ${pkgname}-Ex.R > ${pkgname}-Ex.Rout 2>&1";
	}
	if(R_system($cmd)) {
	    $log->error();
	    $log->print("Running examples in ${pkgname}-Ex.R failed.\n");
	    ## Try to spot the offending example right away.
	    my $txt = join("\n", &read_lines("${pkgname}-Ex.Rout"));
	    #open(FILE, "< ${pkgname}-Ex.Rout");
	    #my @lines = <FILE>;
	    #close(FILE);
	    #my $txt = join("", @lines);
	    ## Look for the header section anchored by a subsequent call
	    ## to flush(): needs to be kept in sync with the code in
	    ## massage-Examples.pl.  Should perhaps also be more
	    ## defensive about the prompt ...
	    my @chunks = split(/(> \#\#\# \* [^\n]+\n> \n> flush)/, $txt);
	    if(scalar(@chunks) > 2) {
		$log->print("The error most likely occurred in:\n\n");
		$log->print($chunks[$#chunks - 1]);
		$log->print($chunks[$#chunks] . "\n");
	    }
	    exit(1);
	}
	## Look at the output from running the examples.  For the time
	## being, report warnings about use of deprecated functions, as
	## the next release will make them defunct and hence using them
	## an error.
	my @lines = &read_lines("${pkgname}-Ex.Rout");
	#open(FILE, "< ${pkgname}-Ex.Rout");
	#my @lines = <FILE>;
	#close(FILE);
	@lines = grep(/^Warning: .*is deprecated.$/, @lines);
	if(scalar(@lines) > 0) {
	    $log->warning();
	    $log->print("Found the following significant warnings:\n");
	    $log->print("  " . join("\n  ", @lines) . "\n");
	    $log->print(wrap("", "",
			     ("Deprecated functions may be defunct as",
			      "soon as of the next release of R.\n",
			      "See ?Deprecated.\n")));
	}
	else {
	    $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)) {
            mkdir($testdir, 0755)
	      or die "Error: cannot create directory '$testdir'\n";
        }
        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(R_system("${R::Vars::MAKE} $makefiles $makevars")) {
            $log->error();
            exit(1);
        }
        chdir($pkgoutdir);
        $log->result("OK");
    }

    ## Check package vignettes.

    chdir($pkgoutdir);

    if((-d &file_path($pkgdir, "inst", "doc"))
       && &list_files_with_type(&file_path($pkgdir, "inst", "doc"),
				"vignette")) {
	$log->checking(join("",
			    ("package vignettes in '",
			     &file_path("inst", "doc"),
			     "'")));
	my $any = 0;

	## Do PDFs exist for all package vignettes?
	my @vignette_files =
	  &list_files_with_type(&file_path($pkgdir, "inst", "doc"),
				"vignette");
	my @bad_vignettes = ();
	foreach my $file (@vignette_files) {
	    my $pdf_file = $file;
	    $pdf_file =~ s/\.[[:alpha:]]+$/.pdf/;
	    push(@bad_vignettes, $file) unless(-f $pdf_file);
	}
        ## A base package may not have PDFs to avoid blowing
        ## out the ditribution size
        ## NOTE it is assumed that base packages can be
        ## woven (i.e., that they only contain "standard" LaTeX)
	if(!$is_base_pkg && scalar(@bad_vignettes) > 0) {
	    $log->warning();
	    $any++;
	    $log->print("Package vignettes without corresponding PDF:\n");
	    $log->print("  " . join("\n  ", @bad_vignettes) . "\n");
	}

	## Can we run the code in the vignettes?
	if($opt_vignettes) {
	    ## <FIXME>
	    ## Should checking the vignettes assume the system default
	    ## packages, or just base?
	    my $Rcmd = "library(tools)\n";
            ## A base package does not get installed during check
	    if(!$is_base_pkg && $opt_install) {
		$Rcmd .= "checkVignettes(package = \"${pkgname}\", " .
		         "lib.loc = \"${library}\", " .
		         "workdir = \"src\")\n";
	    }
	    else {
		$Rcmd .= "checkVignettes(dir = \"${pkgdir}\")\n";
	    }
	    my @out = R_runR($Rcmd, "${R_opts} --quiet");
	    ## </FIXME>
	    ## Vignette could redefine the prompt, e.g. to 'R>' ...
	    @out = grep(!/^[[:alnum:]]*\>/, @out);
	    if(scalar(@out) > 0) {
		$log->warning() unless($any);
		$any++;
		$log->print(join("\n", @out) . "\n");
	    }
	}

	$log->result("OK") unless($any);
    }

    ## Run LaTeX on the manual.

    if($opt_latex) {
        my $latex_dir = &file_path($library, $pkgname, "latex");
	if(-d $latex_dir) {
	    $ENV{'TEXINPUTS'} =
	      env_path(&file_path($R::Vars::R_HOME, "share", "texmf"),
		       $ENV{'TEXINPUTS'});
            my $was_zipped = 0;
            # latex files might have been zipped
            if(-f &file_path($library, $pkgname, "latex", "Rhelp.zip")) {
                $was_zipped = 1;
                my $cmd = "cd $latex_dir; unzip -qo Rhelp.zip";
                if(R_system($cmd)) {
		    $log->error();
		    $log->print("Unzipping latex files failed.\n");
		    exit(1);
                }
            }
	    $log->creating("${pkgname}-manual.tex");
	    open(MANUAL, "> ${pkgname}-manual.tex")
	      or die("Error: cannot open file '${pkgname}-manual.tex'" .
		     "for writing\n");
	    print MANUAL "\\documentclass\{article\}\n" .
	      "\\usepackage[ae,hyper]\{Rd\}\n\\begin\{document\}\n";
	    my @tex_files = &list_files_with_exts($latex_dir, "tex");
	    foreach my $file (@tex_files) {
		open(FILE, "< $file")
		  or die("Error: cannot open file '$file' for reading\n");
		while(<FILE>) {
		    print MANUAL $_;
		}
		close(FILE);
	    }
	    print MANUAL "\\end\{document\}\n";
	    close(MANUAL);
            if($was_zipped) {
                if(R_system("rm -f $latex_dir/*.tex")) {
		    $log->error();
		    $log->print("Removing unzipped latex files failed.\n");
		    exit(1);
                }
            }
	    $log->result("OK");
	    if($HAVE_LATEX) {
		$log->checking("${pkgname}-manual.tex");
		## <NOTE>
		## We use \nonstopmode{} so that LaTeX really gives an
	        ## error (and returns) if something is wrong, and all
	        ## info goes to ${pkgname}-manual.log.
		## We also suppress all output from running LaTeX.
                ## We could also write stdout to a tempfile and replay
                ## this in case of problems.  But does this really help?
		my $cmd = "${R::Vars::LATEX}";
		$cmd .= " '\\nonstopmode{}\\input{${pkgname}-manual.tex}'";
		$cmd .= " >/dev/null 2>&1";
		## </NOTE>
		if(R_system($cmd)) {
		    $log->error();
		    $log->print("LaTeX errors when creating DVI version.\n");
		    $log->print("This typically indicates Rd problems.\n");
		    exit(1);
		}
		$log->result("OK");
	    }
	}
	else {
	    if($HAVE_LATEX) {
		$log->checking("DVI version of manual");
		my $cmd = "${R::Vars::R_CMD} Rd2dvi --batch --no-preview";
		$cmd .= " -o ${pkgname}-manual.dvi >/dev/null 2>&1";
		$cmd .= " $pkgdir";
		if(R_system($cmd)) {
		    $log->error();
		    $log->print("LaTeX errors when creating DVI version.\n");
		    $log->print("This typically indicates Rd problems.\n");
		    exit(1);
		}
		$log->result("OK");
	    }
	}
    }
}


sub usage {
    print STDERR <<END;
Usage: R CMD $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.  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).  All
examples and tests provided by the package are tested if they run
successfully.

Options:
  -h, --help            print short help message and exit
  -v, --version         print version info and exit
  -l, --library=LIB     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,
			where 'pkg' is the name of the package checked)
      --no-clean        do not clean outdir before using it
      --no-codoc        do not check for code/documentation mismatches
      --no-examples     do not run the examples in the Rd files
      --no-install      skip installation and associated tests
      --no-tests        do not run code in tests subdirectory
      --no-vignettes    do not check vignettes in Sweave format
      --no-latex        do not run LaTeX on help files
      --use-gct         use 'gctorture(TRUE)' when running examples/tests

By default, all test sections are turned on.

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