#! @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.187.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_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(); 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() { 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() { @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 *=)(.*)/); } ## 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); } ## ## 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); } ## 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). ## ## 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. ## 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); ## Ignore install time readLines() warnings about ## files with incomplete final lines. Most of these ## come from .install_package_indices(), and should be ## safe to ignore ... $warn_re = "Warning: incomplete final line " . "found by readLines"; @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(); my $exclude_file = ".Rbuildignore"; ## This is a bit tricky for bundles where the build ignore pattern ## file is in the top-level bundle dir. $exclude_file = &file_path(dirname($pkgdir), $exclude_file); if(-f $exclude_file) { open(RBUILDIGNORE, "< $exclude_file"); while() { chop; push(@exclude_patterns, $_) if $_; } close(RBUILDIGNORE); } ## 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)); } } if($in_bundle) { chdir(dirname($pkgdir)); # more portable than '..'? find(\&find_wrong_names, $pkgname); chdir($pkgname); } else { 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); } } if($in_bundle) { chdir(dirname($pkgdir)); # more portable than '..'? find(\&find_wrong_perms_A, $pkgname); chdir($pkgname); } else { 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 $log->checking("for file 'DESCRIPTION.in'"); if(-r "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); } ## check if this is a name clash with an existing or past base package. ## The stub packages are handled specially in library() if(!$is_base_pkg) { my $found = 0; foreach my $p (@R_pkgs_base) { if($description->{"Package"} eq $p) { $found = 1; break; } } if($found != 0) { $log->error(); $log->print("Invalid package name.\n"); $log->print("This is the name of a base package.\n"); exit(1); } $found = 0; foreach my $p (@R_pkgs_stubs) { if($description->{"Package"} eq $p) { $found = 1; break; } } if($found != 0) { $log->error(); $log->print("Invalid package name.\n"); $log->print(wrap("", "", ("This name was used for a base package", "and is remapped by library().\n"))); 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/Suggests, Namespace, Priority. ## These must be correct if present. if($description->{"Depends"} || $description->{"Suggests"}) { my @dependencies = split(/\,\s*/, ($description->{"Depends"}, $description->{"Suggests"})); 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 or Suggests 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. ## ## 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. ## 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:::.check_package_depends(\"${pkgname}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=tools"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { ## ## These should really all be errors ... ## Change this for 2.1 at least, once stubs are fully gone. 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)); } ## } 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 $Rcmd = "tools:::.check_demo_index(\"$dir\")\n"; my @out = R_runR($Rcmd, "${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 $Rcmd = "tools:::.check_vignette_index(\"$dir\")\n"; my @out = R_runR($Rcmd, "${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? ## ## If there is a Makefile (or a Makefile.win), we cannot assume ## that source files have the predefined extensions. ## 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"); ## ## We could/should really check *all* OS specific subdirs here. my @R_files = &list_files_with_type("R", "code"); ## 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. ## ## We could/should really check all OS specific subdirs here. @R_files = &list_files_with_type("R", "code"); ## } 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() { 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 @msg_replace_funs = ("In R, the argument of a replacement function", "which corresponds to the right hand side", "must be 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) { ## ## We really want to stop if we find offending replacement ## functions. But we cannot use error() because output may ## contain warnings ... $log->warning(); ## $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_replace_funs)); } 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 $Rcmd = "tools:::check_Rd_files_in_man_dir(\"man\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=tools"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { ## Output may indicate warnings or errors ... if(grep(/^Rd files with (syntax errors|missing or empty)/, @out)) { $log->error(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_writing_Rd)); exit(1); } else { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_writing_Rd)); } } else { $log->result("OK"); } } ## 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'"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $any++; $log->warning(); $log->print(join("\n", @out) . "\n"); } } ## 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 @msg_doc_files = ("Functions with \\usage entries", "need to have the appropriate \\alias entries,", "and all their arguments documented."); 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"); $log->print(wrap("", "", @msg_doc_files)); $log->print(wrap("", "", @msg_writing_Rd)); } else { $log->result("OK"); } } ## Check C sources/headers for CRLF line endings. ## ## 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. ## 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() { 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 $is_zipped = 0; my $cmd; if(-e &file_path($Rexdir, "Rex.zip")) { $is_zipped = 1; my $UNZIP = &R_getenv("R_UNZIPCMD", "unzip"); my $Rexfile = &file_path($Rexdir, "Rex.zip"); $cmd = join(" ", ("$UNZIP", "-q", &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($is_zipped) { 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 = ; #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 = ; #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); my $vignette_dir = &file_path($pkgdir, "inst", "doc"); if((-d $vignette_dir) && &list_files_with_type($vignette_dir, "vignette")) { $log->checking(join(" ", ("package vignettes in", &sQuote(&file_path("inst", "doc"))))); my $any = 0; ## Do PDFs exist for all package vignettes? my @vignette_files = &list_files_with_type($vignette_dir, "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 ## distribution size. *Note* that 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) { ## 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"); ## 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 $is_zipped = 0; # latex files might have been zipped if(-f &file_path($latex_dir, "Rhelp.zip")) { $is_zipped = 1; my $UNZIP = &R_getenv("R_UNZIPCMD", "unzip"); my $latex_file = &file_path($latex_dir, "Rhelp.zip"); $cmd = join(" ", ("$UNZIP", "-q", &shell_quote_file_path($latex_file), "-d", &shell_quote_file_path($latex_dir))); 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() { print MANUAL $_; } close(FILE); } print MANUAL "\\end\{document\}\n"; close(MANUAL); if($is_zipped) { unlink(&list_files_with_exts($latex_dir, "tex")); } $log->result("OK"); if($HAVE_LATEX) { $log->checking("${pkgname}-manual.tex"); ## ## 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"; ## 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 exit 0; }