#! @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(/= 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; }