#! @PERL@ #-*- perl -*- # Copyright (C) 2000-2009 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 at # http://www.r-project.org/Licenses/ # Send any bug reports to r-bugs@r-project.org use Cwd; use File::Basename; use File::Copy; use File::Copy::Recursive qw(dircopy); use File::Find; use File::Path; use Getopt::Long; use IO::File; use R::Dcf; use R::Logfile; use R::Utils; use R::Vars; use Text::Wrap; ## Don't buffer output. $| = 1; my $revision = ' $Rev$ '; 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 $spec_install = 0; my $opt_vignettes = 1; my $opt_use_valgrind = 0; my $opt_rcfile = ""; # Only set this if $ENV{"HOME"} is set. $opt_rcfile = &file_path($ENV{"HOME"}, ".R", "check.conf") if defined($ENV{"HOME"}); my $opt_subdirs; my $HAVE_LATEX = 0; my $HAVE_PDFLATEX = 0; my $INSTALL_OPTS =""; my $WINDOWS = ($R::Vars::OSTYPE eq "windows"); R::Vars::error("R_HOME", "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", "use-valgrind" => \$opt_use_valgrind, "install-args=s" => \$opt_install_args, "rcfile=s" => \$opt_rcfile, "check-subdirs=s" => \$opt_subdirs); GetOptions(@known_options) or usage(); R_version("R add-on package checker", $version) if $opt_version; usage() if $opt_help; ## record some of the options used. my @opts; push @opts,"--install=fake" if $opt_install eq "fake"; push @opts,"--install=no" if $opt_install eq "no"; push @opts,"--no-install" if $opt_no_install; $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; $spec_install = 1; } $opt_install = 0 if($opt_install eq "no"); $spec_install = 1 if !$opt_install; 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"} = ""; ### Configurable variables my $R_check_use_install_log = &R_getenv("_R_CHECK_USE_INSTALL_LOG_", "TRUE"); my $R_check_subdirs_nocase = &R_getenv("_R_CHECK_SUBDIRS_NOCASE_", "FALSE"); my $R_check_all_non_ISO_C = &R_getenv("_R_CHECK_ALL_NON_ISO_C_", "FALSE"); my $R_check_weave_vignettes = &R_getenv("_R_CHECK_WEAVE_VIGNETTES_", "TRUE"); ## This needs texi2dvi. MiKTeX has a binary texi2dvi, but other ## Windows' LaTeX distributions do not. We check later. my $R_check_latex_vignettes = &R_getenv("_R_CHECK_LATEX_VIGNETTES_", "TRUE"); my $R_check_subdirs_strict = &R_getenv("_R_CHECK_SUBDIRS_STRICT_", "default"); my $R_check_Rd_xrefs = &R_getenv("_R_CHECK_RD_XREFS_", "TRUE"); my $R_check_use_codetools = &R_getenv("_R_CHECK_USE_CODETOOLS_", "TRUE"); my $R_check_force_suggests = &R_getenv("_R_CHECK_FORCE_SUGGESTS_", "TRUE"); my $R_check_Rd_style = &R_getenv("_R_CHECK_RD_STYLE_", "TRUE"); my $R_check_executables = &R_getenv("_R_CHECK_EXECUTABLES_", "TRUE"); my $R_check_executables_exclusions = &R_getenv("_R_CHECK_EXECUTABLES_EXCLUSIONS_", "TRUE"); # my $R_check_Rd_parse = # &R_getenv("_R_CHECK_RD_PARSE_", "TRUE"); ## This check needs codetools my $R_check_dot_internal = &R_getenv("_R_CHECK_DOT_INTERNAL_", "FALSE"); ## Only relevant when the package is loaded, thus installed. my $R_check_suppress_RandR_message = ($opt_install && &R_getenv("_R_CHECK_SUPPRESS_RANDR_MESSAGE_", "TRUE")); ## Maybe move basic configuration (and documentation) to ## &file_path($R::Vars::R_HOME, "etc", "check.conf") ## eventually ... for my $file ($opt_rcfile) { if(-r $file) { open(FILE, "< $file") or die "Error: cannot open file '$file' for reading\n"; my @lines = ; close(FILE); eval("@lines"); die "Error: failed to eval config file '$file'\n$@\n" if ($@); ## ## We prefer the above to the usual recommendation ## unless ($return = do($file)) { ## warn "couldn't parse $file: $@" if $@; ## warn "couldn't do $file: $!" unless defined $return; ## warn "couldn't run $file" unless $return; ## } ## as do(FILE) cannot see lexicals in the enclosing scope. ## } } $R_check_use_install_log = &config_val_to_logical($R_check_use_install_log); $R_check_subdirs_nocase = &config_val_to_logical($R_check_subdirs_nocase); $R_check_all_non_ISO_C = &config_val_to_logical($R_check_all_non_ISO_C); $R_check_weave_vignettes = &config_val_to_logical($R_check_weave_vignettes); $R_check_latex_vignettes = &config_val_to_logical($R_check_latex_vignettes); $R_check_Rd_xrefs = &config_val_to_logical($R_check_Rd_xrefs); $R_check_use_codetools = &config_val_to_logical($R_check_use_codetools); $R_check_Rd_style = &config_val_to_logical($R_check_Rd_style); $R_check_executables = &config_val_to_logical($R_check_executables); $R_check_executables_exclusions = &config_val_to_logical($R_check_executables_exclusions); # $R_check_Rd_parse = # &config_val_to_logical($R_check_Rd_parse); $R_check_dot_internal = &config_val_to_logical($R_check_dot_internal); ## ## This looks a bit strange, but tools:::.check_package_depends() ## currently gets the information about forcing suggests via an ## environment variable rather than an explicit argument. $R_check_force_suggests = &config_val_to_logical($R_check_force_suggests); ## And in fact, it gets even stranger ... ## ## Compatibility code for old-style interface: sanitize eventually. $R_check_force_suggests = $R_check_force_suggests ? "true" : "false"; $ENV{"_R_CHECK_FORCE_SUGGESTS_"} = $R_check_force_suggests; ## ## $R_check_suppress_RandR_message = &config_val_to_logical($R_check_suppress_RandR_message); $opt_subdirs = $R_check_subdirs_strict if $opt_subdirs eq ""; 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 $tar = R_getenv("TAR", "tar"); my $R_opts = "--vanilla"; if($opt_latex) { my $log = new R::Logfile(); $log->checking("for working pdflatex"); open(TEXFILE, "> " . &file_path(${R::Vars::TMPDIR}, "Rtextest$$.tex")) or die "Error: cannot open file 'Rtextest$$.tex' for writing\n"; print TEXFILE "\\nonstopmode\\documentclass\n\{article\}\n\\begin\{document\}\n" . "test\n\\end\{document\}\n"; close(TEXFILE); chdir($R::Vars::TMPDIR); ## print "\ntesting ${R::Vars::PDFLATEX}\n"; if(R_system("${R::Vars::PDFLATEX} Rtextest$$ > Rtextest$$.out")) { $log->result("NO"); } else { $log->result("OK"); $HAVE_PDFLATEX = 1; $HAVE_LATEX = 1; } unlink(); if(!$HAVE_PDFLATEX) { $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 "\\nonstopmode\\documentclass\n\{article\}\n\\begin\{document\}\n" . "test\n\\end\{document\}\n"; close(TEXFILE); chdir($R::Vars::TMPDIR); if(R_system("${R::Vars::LATEX} Rtextest$$ > Rtextest$$.out")) { $log->result("NO"); } else { $log->result("OK"); $HAVE_LATEX = 1; } unlink(); } chdir($startdir); $log->close(); } my @msg_DESCRIPTION = ("See the information on DESCRIPTION files", "in the chapter 'Creating R packages'", "of the 'Writing R Extensions' manual.\n"); ## 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. ## As from 2.1.0 it can also be a tarball ## $pkgdir is the corresponding absolute path. ## $pkgname is the name of the package (bundle). chdir($startdir); $pkg =~ s+/$++; # strip any trailing '/' my $pkgname = basename($pkg); my $opkgname = $pkgname; $is_ascii = 0; my $thispkg_subdirs = $opt_subdirs; ## is this a tar archive? my $istar = 0; if(-d $pkg) { $thispkg_subdirs = "no" if $thispkg_subdirs eq "default"; } else { $istar = 1; $thispkg_subdirs = "yes-maybe" if $thispkg_subdirs eq "default"; $pkgname =~ s/\.(tar\.gz|tgz|tar\.bz2)$//; $pkgname =~ s/_[0-9\.-]*$//; } 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"); } if($istar) { my $dir = &file_path("$pkgoutdir", "00_pkg_src"); mkdir($dir, 0755) or die("Error: cannot create directory '$dir'\n"); if($WINDOWS) { ## workaround for paths in Cygwin tar $pkg =~ s+^([A-Za-z]):+/cygdrive/\1+; } ## ATT 'tar x' did not support -C. ## Note that $pkg, $dir might contain spaces my $pkgq = &shell_quote_file_path($pkg); my $dirq = &shell_quote_file_path($dir); if($opkgname =~ /\.bz2$/) { my $bzip = R_getenv("R_BZIPCMD", "bzip2"); if(R_system("$bzip -dc $pkgq | $tar -xf - -C $dirq")) { die "Error: cannot untar '$pkg'\n"; } } elsif ($opkgname =~ /\.(tar\.gz|tgz)$/) { my $gzip = R_getenv("R_GZIPCMD", "gzip"); if(R_system("$gzip -dc $pkgq | $tar -xf - -C $dirq")) { die "Error: cannot untar '$pkg'\n"; } } else { ## let tar automagically fathom out compression (if any) if(R_system("$tar -xf $pkgq -C $dirq")) { die "Error: cannot untar '$pkg'\n"; } } $pkg = &file_path($dir, $pkgname); } $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); my $thispkg_src_subdir = $thispkg_subdirs; if($thispkg_src_subdir eq "yes-maybe") { ## now see if there is a 'configure' file ## configure files are only used if executable, but ## -x is always false on Windows. if($WINDOWS) { $thispkg_src_subdir = "no" if (-f "configure"); } else { $thispkg_src_subdir = "no" if (-x "configure"); } } chdir($startdir); $log = new R::Logfile(&file_path($pkgoutdir, "00check.log")); $log->message("using log directory '$pkgoutdir'"); my @out = R_runR("cat(R.version.string, '\\n', sep='')", "--slave --vanilla"); $log->message("using @out"); @out = R_runR("tools:::.find_charset()", "--slave --vanilla"); $log->message("using session @out"); $is_ascii = 1 if $out[0] =~ /ASCII$/; ## report options used push @opts,"--no-codoc" if !$opt_codoc; push @opts,"--no-examples" if !$opt_examples && !$spec_install; push @opts,"--no-tests" if !$opt_tests && !$spec_install; push @opts,"--no-vignettes" if !$opt_vignettes && !$spec_install; push @opts,"--use-gct" if $opt_use_gct; push @opts,"--use-valgrind" if $opt_use_valgrind; if (@opts == 1) { $log->message("using option '" . $opts[0] . "'"); } elsif (@opts) { $log->message("using options '" . join(" ", @opts) . "'"); } 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; my $is_bundle = 0; my $package_or_bundle = "package"; my $package_or_bundle_name; my $encoding; ## 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' (and 'Makefile.in') with ## Priority 'base', and skip the installation test for such ## packages. We have to check for Makefile.in in addition to DESCRIPTION.in ## so that we can allow packages to have a DESCRIPTION.in. Such DESCRIPTION.in files will ## have their content completed via the package's configuration which we have not yet run. ## The DESCRIPTION.in file may be malformed (according to R::Dcf()), e.g if it has a line ## @SYSTEM@ which is either empty or "System: ...." depending on the configuration script. if(-r &file_path($pkgdir, "DESCRIPTION.in") && -r &file_path($pkgdir, "Makefile.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"); $encoding = $description->{"Encoding"}; } else { $log->result("NO"); exit(1); } if($description->{"Type"}) { # standard packages do not have this $log->checking("extension type"); $log->result($description->{"Type"}); if($description->{"Type"} ne "Package") { $log->print("Only Type = Package extensions can be checked.\n"); exit(0); } } if($description->{"Bundle"}) { $is_bundle = 1; $log->message("looks like '${pkgname}' is a package bundle"); $package_or_bundle = "bundle"; $package_or_bundle_name = $description->{"Bundle"}; } else { $package_or_bundle_name = $description->{"Package"}; } $log->message("this is $package_or_bundle " . "'$package_or_bundle_name' " . "version '$description->{\"Version\"}'"); if(defined $encoding) {$log->message("package encoding: $encoding");} ## Check if we have any hope of installing my $OS_type = $description->{"OS_type"}; if($opt_install && $OS_type) { if($WINDOWS && $OS_type ne "windows") { $log->message("will not attempt to install this package on Windows"); $opt_install = 0; } if(!$WINDOWS && $OS_type eq "windows" && $opt_install == 1) { $log->message("this is a Windows-only package, skipping installation"); $opt_install = 0; } } ## Check CRAN incoming feasibility. if(&config_val_to_logical(&R_getenv("_R_CHECK_CRAN_INCOMING_", "FALSE"))) { $log->checking("CRAN incoming feasibility"); my $Rcmd = "tools:::.check_package_CRAN_incoming(\"${pkgdir}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { my $err = scalar(grep(/^Conflicting/, @out) > 0); if($err) { $log->error(); } elsif(scalar(grep(/^Insufficient/, @out) > 0)) { $log->warning(); } else { $log->note(); } $log->print(join("\n", @out) . "\n"); exit(1) if($err); } else { $log->result("OK"); } } ## Check package dependencies. ## ## We want to check for dependencies early, since missing ## dependencies may make installation fail, and in any case we ## give up if they are missing. But we don't check them if ## we are not going to install and hence not run any code. ## if($opt_install) { ## 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. ## ## We are not checking base packages here, so all packages do ## have a description file. Bundles should have dependencies ## only at the top level to be usable by install.packages and ## friends, and `Writing R Extensions' requires this. ## ## ## If a package has a namespace, checking dependencies will ## try making use of it without the NAMESPACE file ever ## being validated. Uncaught errors can lead to messages ## like ## * checking package dependencies ... ERROR ## Error in e[[1]] : object is not subsettable ## Execution halted ## which are not too helpful :-( ## Hence, we try to intercept this here. ## Note that for *bundles*, the respective namespaces are not ## checked this way (and perhaps should be lateron when the ## per-package checks are performed). if(-f &file_path($pkgdir, "NAMESPACE")) { $log->checking("package name space information"); my @msg_NAMESPACE = ("See section 'Package name spaces'", "of the 'Writing R Extensions' manual.\n"); my $Rcmd = "tools:::.check_namespace(\"${pkgdir}\")"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->error(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_NAMESPACE)); exit(1); } else { $log->result("OK"); } } $log->checking("$package_or_bundle dependencies"); ## Everything listed in Depends or Suggests or Imports ## 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 = "options(warn=1,warnEscapes=FALSE); tools:::.check_package_depends(\"${pkgdir}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->error(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_DESCRIPTION)); exit(1); } else { $log->result("OK"); } } ## ## 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_or_bundle"); if(defined($description->{"Built"})) { $log->error(); $log->print("Only *source* packages can be checked.\n"); exit(1); } elsif($opt_install !~ /^check/) { ## Check for package/bundle 'src' subdirectories with object ## files (but not if installation was already performed). my $any; my $pat = "(a|o|[ls][ao]|sl|obj)"; # Object file extensions. my @dirs; if($is_bundle) { foreach my $ppkg (split(/\s+/, $description->{"Contains"})) { push(@dirs, &file_path($ppkg, "src")); } } else { @dirs = ("src"); } foreach my $dir (@dirs) { if((-d &file_path($pkgdir, $dir)) && &list_files_with_exts(&file_path($pkgdir, $dir), $pat)) { $log->warning() unless $any; $any++; $dir = &file_path($pkgname, $dir); $log->print("Subdirectory '$dir' " . "contains object files.\n"); } } if($thispkg_src_subdir ne "no") { ## Recognized extensions for sources or headers. my $exts = "(" . &make_file_exts("sources") . "|" . &make_file_exts("headers") . ")"; foreach my $dir (@dirs) { if((-d &file_path($pkgdir, $dir))) { chdir(&file_path($pkgdir, $dir)); if(!(-f "Makefile") && !(-f "Makefile.win")) { opendir(DIR, ".") or die "cannot opendir $dir: $!"; @srcfiles = grep { !(/\.$exts$/ || /^Makevars/ || /-win\.def$/ ) && -f "$_" } readdir(DIR); closedir(DIR); if(@srcfiles) { $log->warning() unless $any; $any++; $log->print("Subdirectory '$dir' contains:\n"); $log->print(wrap(" ", " ", join(" ", sort @srcfiles) . "\n")); $log->print(wrap("", "", ("These are unlikely file names", "for src files.\n"))); } } chdir($startdir); } } } $log->result("OK") unless $any; } else { $log->result("OK"); } ## we need to do this before installation if ($R_check_executables) { ## this is tailored to the FreeBSD/Linux file, ## see http://www.darwinsys.com/file/ my $have_free_file = 0; my $tmpfile = R_tempfile("file"); ## version 4.21 writes to stdout, 4.23 to stderr ## and set an error status code R_system("file --version > $tmpfile 2>&1"); open FILE, "< $tmpfile"; my @lines = ; close(FILE); ## a reasonable check -- it does not identify itself well $have_free_file = 1 if grep(/^(file-[45]|magic file from)/, @lines); if ($have_free_file) { $log->checking("for executable files"); my @exec_files = (); sub find_execs { my $file_path = $File::Find::name; ## False positives: ## * foreign/tests/datefactor.dta gives ## setgid mc68020 pure executable not stripped return if $file_path =~ m+foreign/tests/datefactor.dta$+; ## * msProcess/inst/data[12]/*.txt give ## setgid MS-DOS executable, MZ for MS-DOS return if $file_path =~ m+msProcess/inst/data[12]/.*.txt$+; my $file_name = basename($file_path); my $bare_path = $file_path; $bare_path =~ s+^$pkgdir/++; ## watch out for spaces in file names here R_system("file '$file_name' > $tmpfile"); open FILE, "< $tmpfile"; my @lines = ; close(FILE); my $line = $lines[0]; my $t1 = $line =~ /executable/; my $t2 = $line =~ /script text/; push @exec_files, $bare_path if ($t1 && ! $t2); } find(\&find_execs, "$pkgdir"); if($R_check_executables_exclusions && -f "$pkgdir/BinaryFiles") { my %bin; open FILE, "< $pkgdir/BinaryFiles"; while () { chomp; s/\r$//; # careless people get Windows files on other OSes $bin{$_} = $_; } close(FILE); my @exec_files0 = @exec_files; @exec_files = (); foreach my $file (@exec_files0) { push @exec_files, $file unless $bin{$file}; } } if(($opt_install =~ /^check/) && (-f &file_path($pkgdir, ".install_timestamp"))) { my $its = (-M &file_path($pkgdir, ".install_timestamp")); my @exec_files_in = @exec_files; @exec_files = (); foreach my $path (@exec_files_in) { push @exec_files, $path unless ((-M &file_path($pkgdir, $path)) < $its); } } if(scalar(@exec_files) > 0) { $log->warning(); $log->print("Found the following executable file(s):\n"); $log->print(" " . join("\n ", @exec_files) . "\n"); $log->print(wrap("", "", ("Source packages should not contain undeclared executable files.\n", "See section 'Package structure'", "in manual 'Writing R Extensions'.\n"))); } else { $log->result("OK"); } } else { # no 'file', so just check extensions $log->checking("for .dll and .exe files"); my @exec_files = (); sub find_execs2 { my $file_path = $File::Find::name; my $bare_path = $file_path; $bare_path =~ s+^$pkgdir/++; push @exec_files, $bare_path if $file_path =~ /\.(exe|dll)$/; } find(\&find_execs2, "$pkgdir"); if(R_check_executables_exclusions && -f "$pkgdir/BinaryFiles") { my %bin; open FILE, "< $pkgdir/BinaryFiles"; while () { chomp; s/\r$//; $bin{$_} = $_; } close(FILE); my @exec_files0 = @exec_files; @exec_files = (); foreach my $file (@exec_files0) { push @exec_files, $file unless $bin{$file}; } } if(scalar(@exec_files) > 0) { $log->warning(); $log->print("Found the following executable file(s):\n"); $log->print(" " . join("\n ", @exec_files) . "\n"); $log->print(wrap("", "", ("Source packages should not contain executable files.\n", "See section 'Package structure'", "in manual 'Writing R Extensions'.\n"))); } else { $log->result("OK"); } } unlink $tmpfile; } ## 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/) || $R_check_use_install_log || !(-t STDIN && -t STDOUT)); $INSTALL_opts = $opt_install_args; ## don't use HTML, checkRd goes over the same ground. $INSTALL_opts = $INSTALL_opts . " --no-html"; #$INSTALL_opts = $INSTALL_opts . " --no-chm" if $WINDOWS; $INSTALL_opts = $INSTALL_opts . " --fake" if($opt_install eq "fake"); my $cmd; if($WINDOWS) { ## avoid some quoting hell my $pkgd = Win32::GetShortPathName($pkgdir); $cmd = join(" ", ("Rcmd.exe INSTALL -l", &shell_quote_file_path($library), "$INSTALL_opts", &shell_quote_file_path($pkgd))); } else { $cmd = join(" ", (&shell_quote_file_path(${R::Vars::R_EXE}), "CMD INSTALL -l", &shell_quote_file_path($library), "$INSTALL_opts", &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_or_bundle " . "'$package_or_bundle_name' " . "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); ## ## We used to have ## $install_error = ## ($lines[$#lines] !~ /^\* DONE/); ## but what if there is output from do_cleanup ## in (Unix) R CMD INSTALL? $install_error = (scalar(grep(/^\* DONE/, @lines)) == 0); ## } else { ## record in the log what options were used open(LOG, "> $out"); print LOG "* install options are '$INSTALL_opts'\n\n"; close(LOG); $cmd .= " >>" . &shell_quote_file_path($out) . " 2>&1"; $install_error = &R_system($cmd); if($WINDOWS) { ## MS Html Help Compiler gives lines terminated ## by CRCRLF, so we clean up the log file. my $line; ## read_lines does chomp. @lines = &read_lines($out); ## error codes seem unreliable, so check here $install_error = (scalar(grep(/^\* DONE/, @lines)) == 0); open(FILE, "> $out") or die "Error: cannot open file '$out' for re-writing\n"; foreach my $line (@lines) { $line =~ s/\r$//; print FILE $line, "\n"; } close(FILE); } } 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), and also include ## warnings resulting from the const char * CHAR() ## change in R 2.6.0. (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:", ## ## New style Rd conversion ## which may even show errors: "^Rd (warning|error): ", ## ": warning: .*ISO C", ": warning: implicit declaration of function", ": warning: .* discards qualifiers from pointer target type", ": warning: .* is used uninitialized", "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. Also, GCC ## 3.4 or better warns about function pointers ## casts which are "needed" for dlsym(), but it ## seems that all systems which have dlsym() also ## support the cast. Hence, try to ignore these by ## default, but make it possible to get all ISO C ## warnings via an environment variable. if(!$R_check_all_non_ISO_C) { @lines = grep(!/^ *\/.*: warning: .*ISO C/, @lines); $warn_re = "warning: *ISO C forbids.*" . "function pointer"; @lines = grep(!/$warn_re/, @lines); } ## Warnings spotted by gcc with ## '-Wimplicit-function-declaration', which is ## implied by '-Wall'. Currently only accessible ## via an internal environment variable. my $check_src_flag = &R_getenv("_R_CHECK_SRC_MINUS_W_IMPLICIT_", "FALSE"); ## (Not quite perfect, as the name should really ## include 'IMPLICIT_FUNCTION_DECLARATION'.) $check_src_flag = &config_val_to_logical($check_src_flag); if(!$check_src_flag) { my $warn_re = "warning: implicit declaration of function"; @lines = grep(!/$warn_re/, @lines); } ## Warnings spotted by gcc with '-Wunused', which is ## implied by '-Wall'. Currently only accessible ## via an internal environment variable. my $check_src_flag = &R_getenv("_R_CHECK_SRC_MINUS_W_UNUSED_", "FALSE"); $check_src_flag = &config_val_to_logical($check_src_flag); if(!$check_src_flag) { my $warn_re = "warning: unused"; @lines = grep(!/$warn_re/i, @lines); } ## (gfortran seems to use upper case.) ## Warnings spotted by gfortran 4.0 or better with ## -Wall. Justified in principle, it seems. Let's ## filter them for the time being, and maybe revert ## this lateron ... but make it possible to suppress ## filtering out by setting the internal environment ## variable _R_CHECK_WALL_FORTRAN_ to something ## "true". my $R_check_Wall_FORTRAN = &R_getenv("_R_CHECK_WALL_FORTRAN_", "FALSE"); $R_check_Wall_FORTRAN = &config_val_to_logical($R_check_Wall_FORTRAN); if(!$R_check_Wall_FORTRAN) { my $warn_re = "(" . join("|", ("Label .* at \\(1\\) defined but not used", "Line truncated at \\(1\\)", "ASSIGN statement at \\(1\\)", "Assigned GOTO statement at \\(1\\)", "arithmetic IF statement at \\(1\\)", "Nonconforming tab character (in|at)" )) . ")"; @lines = grep(!/$warn_re/, @lines); } ## 'Warning' from deldir 0.0-10 my $warn_re = "Warning: The process for determining duplicated points"; @lines = grep(!/$warn_re/, @lines); if(scalar(@lines) > 0) { $log->warning(); $log->print("Found the following " . "significant warnings:\n"); $log->print(" " . join("\n ", @lines) . "\n"); $log->print("See '$out' for details.\n"); } else { $log->result("OK"); } } } } } if($is_bundle) { my @bundlepkgs = split(/\s+/, $description->{"Contains"}); if(! $opt_install) { ## Since we are not installing, we have to use the source ## directories. Quite a bit of the R code needs to read the ## DESCRIPTION files for each package, so we do that bit ## of the install in Perl. my @lines0 = &read_lines(&file_path($pkgdir, "DESCRIPTION")); foreach my $ppkg (@bundlepkgs) { $dfile = &file_path($pkgdir, $ppkg, "DESCRIPTION"); my $fh = new IO::File($dfile, "w") or die "Error: cannot open file '$dpath' for writing\n"; my @lines = (@lines0, &read_lines(&file_path($pkgdir, $ppkg, "DESCRIPTION.in"))); @lines = grep(!/^\s*$/, @lines); # Remove blank lines. $fh->print(join("\n", @lines), "\n"); $fh->close(); } } 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("*"); } ## We could use the latex-ed pages, but we need the DESCRIPTION.in check_pkg_manual($pkgdir, basename($pkgdir), $log); } else { chdir($startdir); check_pkg($pkgdir, $pkgoutdir, $startdir, $library, $is_bundle, $description, $log, $is_base_pkg, $thispkg_subdirs); my $instdir = &file_path($library, $pkgname); if (-d &file_path($instdir, "help")) { check_pkg_manual($instdir, $description->{"Package"}, $log); } else { check_pkg_manual($pkgdir, $description->{"Package"}, $log); } } 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, $subdirs) = @_; 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 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: will not change to directory '$pkg'\n"; $pkgdir = R_cwd(); if($in_bundle) { $pkgname = basename($pkgdir); } else { $pkgname = $description->{"Package"}; } } 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() { chomp; s/\r$//; # careless people get Windows files on other OSes 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. ## In addition, Windows does not allow the following DOS type device ## names (by themselves or with possible extensions), see e.g. ## http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp ## and http://en.wikipedia.org/wiki/Filename (which as of 2007-04-22 ## is wrong about claiming that COM0 and LPT0 are disallowed): ## ## CON: Keyboard and display ## PRN: System list device, usually a parallel port ## AUX: Auxiliary device, usually a serial port ## NUL: Bit-bucket device ## CLOCK$: System real-time clock ## COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9: ## Serial communications ports 1-9 ## LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9: ## parallel printer ports 1-9 ## In addition, the names of help files get converted to HTML file ## names and so should be valid in URLs. We check that they are ## ASCII and do not contain %, which is what is known to cause ## troubles. $log->checking("for portable file names"); my @bad_files = (); my @non_ASCII_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 { ## Hmm, but Unix-alikes such as Mac OS X have such file systems return 0 if($file_path =~ /$p/); } } my $file_name = basename($file_path); { ## collation is ASCII as 'use locale' is not in effect. ## NB: the omission of ' ' is deliberate. if (grep(/[^-A-Za-z0-9._!\#\$%&+,;=@^\(\)\{\}\'\[\]]/, $file_name)) { push(@non_ASCII_files, $file_path); } } if(grep(/[[:cntrl:]\"\*\/\:\<\>\?\\\|]/, $file_name)) { push(@bad_files, $file_path); } elsif(dirname($file_path) =~ /man$/) { ## perhaps use ranges here, /([^ -~]|%)/ ? foreach my $ch (split //, $file_name) { ## collation is ASCII as 'use locale' is not in effect. if ($ch eq "%" || $ch lt " " || $ch gt "~") { push(@bad_files, $file_path); last; } } } else { $file_name =~ tr/A-Z/a-z/; # collation is ASCII $file_name =~ s/\..*//; push(@bad_files, $file_path) if(grep(/^(con|prn|aux|clock\$|nul|lpt[1-9]|com[1-9])$/, $file_name)); } } if($in_bundle) { chdir(dirname($pkgdir)); find(\&find_wrong_names, $pkgname); chdir($pkgname); } else { find(\&find_wrong_names, "."); } if(scalar(@bad_files) > 0) { $log->error(); $log->print("Found the following file(s) 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); } ## Next check for name clashes on case-insensitive file systems ## (that is on Windows). %seen = (); my @duplicated = (); sub check_case_names { my $file_path = lc($File::Find::name); if($seen{$file_path}) {push(@duplicated, $file_path);} $seen{$file_path} = 1; } if($in_bundle) { chdir(dirname($pkgdir)); find(\&check_case_names, $pkgname); chdir($pkgname); } else { find(\&check_case_names, "."); } if(scalar(@duplicated) > 0) { $log->error(); $log->print("Found the following file(s) with " . "duplicate lower-cased file names:\n"); $log->print(" " . join("\n ", @duplicated) . "\n"); $log->print(wrap("", "", ("File names must not differ just by case", "to be usable 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); } if(scalar(@non_ASCII_files) > 0) { $log->warning(); $log->print("Found the following file(s) with " . "non-portable file names:\n"); $log->print(" " . join("\n ", @non_ASCII_files) . "\n"); $log->print(wrap("", "", ("These are not fully portable file names\n", "See section 'Package structure'", "in manual 'Writing R Extensions'.\n"))); } else { $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)); 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 we just installed the package (via R CMD INSTALL), we already ## validated most of the package DESCRIPTION metadata. Otherwise, ## let us be defensive about this ... my $full = !$opt_install || ($opt_install eq "skip") || $is_base_pkg; &R::Utils::check_package_description($pkgdir, $pkgname, $log, $in_bundle, $is_base_pkg, $full); $log->checking("top-level files"); opendir(DIR, ".") or die "cannot opendir package: $!"; my @topfiles = grep { /^install.R$/ || /^R_PROFILE.R/ && -f "$_" } readdir(DIR); closedir(DIR); if(@topfiles) { $log->warning(); $log->print(join(" ", @topfiles) . "\n"); $log->print(wrap("", "", ("These files are deprecated.", "See manual 'Writing R Extensions'.\n"))); } else { $log->result("OK"); } ## Check index information. $log->checking("index information"); my @msg_index = ("See the information on INDEX files and package", "subdirectories in the chapter '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 = "options(warn=1)\ntools:::.check_demo_index(\"$dir\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @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 = "options(warn=1)\ntools:::.check_vignette_index(\"$dir\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @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"); } ## checks below look for R code. Package WWGbook has an empty R dir. my $haveR = -d "R"; ## Check package subdirectories. $log->checking("package subdirectories"); my $any; if ($haveR) { opendir(DIR, "R") or die "cannot open dir R\n"; my @files = readdir(DIR); closedir(DIR); my $valid = 0; foreach my $f (@files) { $valid = 1 if $f =~ /\.[RrSsq]/; } if(!$valid) { $haveR = 0; $log->warning() unless $any; $any++; $log->print("Found directory 'R' with no source files.\n"); } } if($R_check_subdirs_nocase) { ## Argh. We often get submissions where 'R' comes out as 'r', ## or 'man' comes out as 'MAN', and we've just ran into 'DATA' ## instead of 'data' (2007-03-31). Maybe we should warn about ## this unconditionally ... ## ## Actually, what we should really do is check whether there is ## any directory with lower-cased name matching a lower-cased ## name of a standard directory, while differing in name. ## 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")) { $log->warning() unless $any; $any++; $log->print("Found subdirectory 'DATA'.\n"); $log->print("Most likely, this should be 'data'.\n") } } ## several packages have had check dirs in the sources, e.g. ## ./languageR/languageR.Rcheck ## ./locfdr/man/locfdr.Rcheck ## ./clustvarsel/inst/doc/clustvarsel.Rcheck ## ./bicreduc/OldFiles/bicreduc.Rcheck ## ./waved/man/waved.Rcheck ## ./waved/..Rcheck my @check_files = (); sub find_check_names { my $file_path = $File::Find::name; my $file_name = basename($file_path); my $bare_path = $file_path; $bare_path =~ s+^\./++; push(@check_files, $bare_path) if -d $file_name && $file_path =~ /\.Rcheck$/; } find(\&find_check_names, "."); if(scalar(@check_files) > 0) { $log->warning() unless($any); $any++; $log->print("Found the following directory(s) with " . "names of check directories:\n"); $log->print(" " . join("\n ", @check_files) . "\n"); $log->print("Most likely, these were included erroneously.\n") } sub check_subdirs { my ($dpath) = @_; my $Rcmd = "tools:::.check_package_subdirs(\"$dpath\")\n"; ## We don't run this in the C locale, as we only require ## certain filenames to start with ASCII letters/digits, and not ## to be entirely ASCII. my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->warning() unless $any; $any++; $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", ("Please remove or rename the files.\n", "See section 'Package subdirectories'", "in manual 'Writing R Extensions'.\n"))); } } &check_subdirs(".") unless ($subdirs eq "no"); sub has_lazyload_data { my ($dir) = @_; my @files; my $ans = 0; if (-s &file_path($dir, "Rdata.rdb") && -s &file_path($dir, "Rdata.rds") && -s &file_path($dir, "Rdata.rdx")) { $ans = 1; } $ans; } ## Subdirectory 'data' without data sets? if((-d "data") && !&list_files_with_type("data", "data") && !&has_lazyload_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 'exec' without files? if((-d "exec") && !&list_files("exec")) { $log->warning() unless $any; $any++; $log->print("Subdirectory 'exec' contains no files.\n"); } ## Subdirectory 'inst' without files? if((-d "inst") && scalar(&list_files("inst", 1) < 3)) { $log->warning() unless $any; $any++; $log->print("Subdirectory 'inst' contains no files.\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") && !( (-f &file_path("src", "Makefile")) || (-f &file_path("src", "Makefile.win")) )) { if( !(&list_files_with_type("src", "sources"))) { $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", "libs", "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"))); } } ## Valid CITATION metadata? ## For the time being, only try parsing inst/CITATION ... my $file = &file_path("inst", "CITATION"); if(-f $file) { my $Rcmd = "tools:::.check_citation(\"${file}\")"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=utils"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->warning() unless $any; $any++; $log->print("Invalid citation information in 'inst/CITATION':\n"); $log->print(" " . join("\n ", @out) . "\n"); } } $log->result("OK") unless $any; ## Check R code for syntax errors and for non-ASCII chars which ## might be syntax errors in some locales. ## We need to do the non-ASCII check first to get the more ## specific warning message before an error. if(!$is_base_pkg && $haveR) { $log->checking("R files for non-ASCII characters"); my @out = R_runR("tools:::.check_package_ASCII_code('.')", "${R_opts} --slave"); if(scalar(@out) > 0) { if(defined($description->{"Encoding"})) { $log->note(); } else { $log->warning(); } $log->print(wrap("", "", ("Found the following files with", "non-ASCII characters:\n"))); $log->print(" " . join("\n ", @out) . "\n"); $log->print(wrap("", "", ("Portable packages must use only ASCII", "characters in their R code,\n", "except perhaps in comments.\n"))); } else { $log->result("OK"); } $log->checking("R files for syntax errors"); ## we do want to warn on unrecognized escapes here my $Rcmd = "options(warn=1);tools:::.check_package_code_syntax(\"R\")"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(grep(/^Error/, @out)) > 0) { $log->error(); $log->print(join("\n", @out) . "\n"); exit(1); } elsif(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); } else { $log->result("OK"); } } ## Check we can actually load the package if($opt_install) { $log->checking("whether the package can be loaded"); my $Rcmd = "library(${pkgname})"; my @out = R_runR($Rcmd, "${R_opts} --quiet"); @out = grep(!/^\>/, @out); if(scalar(grep(/^Error/, @out)) > 0) { $log->error(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", ("\nIt looks like this package", "has a loading problem: see the messages", "for details.\n"))); exit(1); } else { $log->result("OK"); } $log->checking("whether the package can be loaded with stated dependencies"); my $Rcmd = "library(${pkgname})"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(grep(/^Error/, @out)) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", ("\nIt looks like this package", "(or one of its dependent packages)", "has an unstated dependence on a standard", "package. All dependencies must be", "declared in DESCRIPTION.\n"))); $log->print(wrap("", "", @msg_DESCRIPTION)); } else { $log->result("OK"); } } ## and if it has a namespace, that we can load just the namespace if($opt_install && -f "${pkgdir}/NAMESPACE") { $log->checking("whether the name space can be loaded with stated dependencies"); my $Rcmd = "loadNamespace(\"${pkgname}\")"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(grep(/^Error/, @out)) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", ("\nA namespace must be able to be loaded", "with just the base namespace loaded:", "otherwise if the namespace gets loaded by a", "saved object, the session will be unable", "to start.\n\n", "Probably some imports need to be declared", "in the NAMESPACE file.\n"))); } else { $log->result("OK"); } } if(!$is_base_pkg && $haveR) { if($opt_install) { $log->checking("for unstated dependencies in R code"); my $Rcmd = "options(warn=1,warnEscapes=FALSE);tools:::.check_packages_used(package=\"${pkgname}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_DESCRIPTION)); } else { $log->result("OK"); } } else { ## this needs to read the package code, and will fail on ## syntax errors such as non-ASCII code. $log->checking("for unstated dependencies in R code"); my $Rcmd = "options(warn=1,warnEscapes=FALSE);tools:::.check_packages_used(dir=\"${pkgdir}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_DESCRIPTION)); } else { $log->result("OK"); } } } ## Check whether methods have all arguments of the corresponding ## generic. if($haveR) { $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 = "options(warn=1,warnEscapes=FALSE)\n"; $Rcmd .= "options(expressions=1000)\n"; if($opt_install) { $Rcmd .= "tools::checkS3methods(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::checkS3methods(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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($haveR) { $log->checking("replacement functions"); my @msg_replace_funs = ("In R, the argument of a replacement function", "which corresponds to the right hand side", "must be named 'value'.\n"); my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools::checkReplaceFuns(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::checkReplaceFuns(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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 && $haveR) { $log->checking("foreign function calls"); my @msg_ff_calls = ("See the chapter 'System and foreign language interfaces'", "of the 'Writing R Extensions' manual.\n"); my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools::checkFF(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::checkFF(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_ff_calls)); } else { $log->result("OK"); } } ## Check R code for possible problems, including tests based on LT's ## codetools package. if($haveR) { $log->checking("R code for possible problems"); my $any; if(!$is_base_pkg) { my $Rcmd = "options(warn=1,warnEscapes=FALSE);tools:::.check_package_code_shlib(\"R\")"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->error(); $log->print(wrap("", "", "Incorrect (un)loading of package", "shared libraries.\n")); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", ("The system-specific extension for", "shared libraries must not be added.\n", "See ?library.dynam.\n"))); exit(1); } } if($R_check_use_codetools && $opt_install) { my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; $Rcmd .= "tools:::.check_code_usage_in_package(package = \"${pkgname}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES="); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->note() unless $any; $any++; $log->print(join("\n", @out) . "\n"); } } if($R_check_use_codetools) { my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools:::.check_T_and_F(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools:::.check_T_and_F(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES="); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->note() unless $any; $any++; $log->print(join("\n", @out) . "\n"); } } ## .Internal is intended for base packages if((!$is_base_pkg) && $R_check_use_codetools && $R_check_dot_internal) { my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools:::.check_dotInternal(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools:::.check_dotInternal(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES="); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->note() unless $any; $any++; $log->print(join("\n", @out) . "\n"); } } $log->result("OK") unless $any; } ## Check R documentation files. my @msg_writing_Rd = ("See the chapter 'Writing R documentation files'", "in manual 'Writing R Extensions'.\n"); if(-d "man") { $log->checking("Rd files"); my $minlevel = &R_getenv("_R_CHECK_RD_CHECKRD_MINLEVEL_", "-1"); my $Rcmd = "options(warn=1)\ntools:::.check_package_parseRd('.', minlevel=$minlevel)\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { if(scalar(grep(!/^prepare.*Dropping empty section/, @out)) > 0) { $log->warning(); } else { $log->note(); } $log->print(join("\n", @out) . "\n"); } else { $log->result("OK"); } $log->checking("Rd metadata"); my $Rcmd = "options(warn=1)\n"; if($opt_install) { $Rcmd .= "tools:::.check_Rd_metadata(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools:::.check_Rd_metadata(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); } else { $log->result("OK"); } } ## Check cross-references in R documentation files. ## ## Installing a package warns about missing links (and hence R CMD ## check knows about this too provided an install log is used). ## However, under Windows the install-time check verifies the links ## against what is available in the default library, which might be ## considerably more than what can be assumed to be available. ## ## The formulations in section "Cross-references" of R-exts are not ## quite clear about this, but CRAN policy has for a long time ## enforced anchoring links to targets (aliases) from non-base ## packages. ## if($R_check_Rd_xrefs && (-d "man")) { $log->checking("Rd cross-references"); my $Rcmd = "options(warn=1)\n"; if($opt_install) { $Rcmd .= "tools:::.check_Rd_xrefs(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools:::.check_Rd_xrefs(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { if(scalar(grep(!/^Package\(s\) unavailable/, @out)) > 0) { $log->warning(); } else { $log->note(); } $log->print(join("\n", @out) . "\n"); } else { $log->result("OK"); } } ## Check for missing documentation entries. if($haveR || (-d "data")) { $log->checking("for missing documentation entries"); my $Rcmd= "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools::undoc(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::undoc(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); my @err = grep(/^Error/, @out); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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((-d "man")) { $log->checking("for code/documentation mismatches"); if(!$opt_codoc) { $log->result("SKIPPED"); } else { my $any = 0; ## Check for code/documentation mismatches in functions. if($haveR) { my $Rcmd = "options(warn=1,warnEscapes=FALSE)\n"; if($opt_install) { $Rcmd .= "tools::codoc(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::codoc(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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 = "options(warn=1,warnEscapes=FALSE)\ntools::codocData(package = \"${pkgname}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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 && $haveR) { my $Rcmd = "options(warn=1,warnEscapes=FALSE)\ntools::codocClasses(package = \"${pkgname}\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); 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 $any; my @msg_doc_files = ("Functions with \\usage entries", "need to have the appropriate \\alias entries,", "and all their arguments documented.\n", "The \\usage entries must correspond to syntactically", "valid R code.\n"); my $Rcmd = "options(warn=1)\n"; if($opt_install) { $Rcmd .= "tools::checkDocFiles(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::checkDocFiles(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $any++; $log->warning(); $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_doc_files)); $log->print(wrap("", "", @msg_writing_Rd)); } if($R_check_Rd_style && $haveR) { my @msg_doc_style = ("The \\usage entries for S3 methods should use", "the \\method markup and not their full name.\n"); $Rcmd = "options(warn=1)\n"; if($opt_install) { $Rcmd .= "tools::checkDocStyle(package = \"${pkgname}\")\n"; } else { $Rcmd .= "tools::checkDocStyle(dir = \"${pkgdir}\")\n"; } my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES='utils,grDevices,graphics,stats'"); @out = grep(!/^\>/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { $log->note() unless($any); $any++; $log->print(join("\n", @out) . "\n"); $log->print(wrap("", "", @msg_doc_style)); $log->print(wrap("", "", @msg_writing_Rd)); } } $log->result("OK") unless($any); } if(!$is_base_pkg && (-d "data")) { $log->checking("data for non-ASCII characters"); my @out = R_runR("tools:::.check_package_datasets('.')", "${R_opts} --slave"); @out = grep(!/Loading required package/, @out); my @bad = grep(/^Warning:/, @out); if(scalar(@out) > 0) { if(scalar(@bad)) { $log->warning(); } else { $log->note(); } $log->print(" " . join("\n ", @out) . "\n"); $log->print(wrap("", "", ("Portable packages use only ASCII", "characters in their datasets.\n"))); } else { $log->result("OK"); } } ## Check C/C++/Fortran sources/headers for CRLF line endings. ## ## Does ISO C really require LF line endings? (Reference?) ## We definitely know that some versions of Solaris cc and f77/f95 ## will not accept CRLF or CR line endings. ## (Sun Studio 12 definitely objects to CR in both C and Fortran). ## if(!$is_base_pkg && (-d "src")) { $log->checking("line endings in C/C++/Fortran sources/headers"); my @src_files = &list_files_with_type("src", "src_no_CRLF"); my @bad_files = (); foreach my $file (@src_files) { open(FILE, "< $file") or die "Error: cannot open '$file' for reading\n"; binmode(FILE); # for Windows ## with CR line endings we will just get one incomplete line ## so all we can do is look for CR. while() { if($_ =~ /\r/) { push(@bad_files, $file); last; } } close(FILE); } if(scalar(@bad_files) > 0) { $log->warning(); $log->print("Found the following sources/headers with " . "CR or CRLF line endings:\n"); $log->print(" " . join("\n ", @bad_files) . "\n"); $log->print("Some Unix compilers require LF line endings.\n"); } else { $log->result("OK"); } } ## Check src/Make* for LF line endings, as Sun make does not accept CRLF if(!$is_base_pkg && (-d "src")) { $log->checking("line endings in Makefiles"); ## .win files are not checked, as CR/CRLF work there my @src_files = ("src/Makevars", "src/Makevars.in", "src/Makefile", "src/Makefile.in"); my @bad_files = (); foreach my $file (@src_files) { open(FILE, "< $file") or next; binmode(FILE); # for Windows ## with CR line endings we will just get one incomplete line ## so all we can do is look for CR. while() { if($_ =~ /\r/) { push(@bad_files, $file); last; } } close(FILE); } if(scalar(@bad_files) > 0) { $log->warning(); $log->print("Found the following Makefiles with " . "CR or CRLF line endings:\n"); $log->print(" " . join("\n ", @bad_files) . "\n"); $log->print("Some Unix makes require LF line endings.\n"); } else { $log->result("OK"); } } ## Check src/Makevars[.in] for portable compilation flags. if((-f &file_path("src", "Makevars.in")) || (-f &file_path("src", "Makevars"))) { $log->checking("for portable compilation flags in Makevars"); my $Rcmd = "tools:::.check_make_vars(\"src\")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->warning(); $log->print(join("\n", @out) . "\n"); } else { $log->result("OK"); } } ## check src/Makevar*, src/Makefile* for correct use of BLAS_LIBS ## FLIBS is not needed on Windows, at least currently (as it is ## statically linked). if(-d "src") { my $any; $log->checking("for portable use of \$BLAS_LIBS"); my @makefiles; push(@makefiles, &file_path("src", "Makevars")) if (-f &file_path("src", "Makevars")); push(@makefiles, &file_path("src", "Makevars.in")) if (-f &file_path("src", "Makevars.in")); push(@makefiles, &file_path("src", "Makefile")) if (-f &file_path("src", "Makefile")); push(@makefiles, &file_path("src", "Makefile.in")) if (-f &file_path("src", "Makefile.in")); foreach my $f (@makefiles) { open(FILE, "< $f") or die("Error: cannot open file '$f' for reading\n"); while() { if(/PKG_LIBS/ && /\$[\{\(]{0,1}BLAS_LIBS/ && !/FLIBS/) { $log->warning() unless $any; $any++; chomp; s/\r$//; # some packages have CRLF terminators $log->print("apparently missing \$(FLIBS) in '".$_."'\n"); } } close(FILE); } $log->result("OK") unless($any); } chdir($pkgoutdir); ## Run the examples. ## This setting applies to vignettes below too. ${R_opts} = ${R_opts}." -d valgrind" if $opt_use_valgrind; ## this will be skipped if installation was if( -d &file_path($library, $pkgname, "help") ) { $log->checking("examples"); if(!$opt_examples) { $log->result("SKIPPED"); } else { my $pkgtopdir = &file_path($library, $pkgname); $cmd = join(" ", ("(echo 'tools:::.createExdotR(\"${pkgname}\", \"${pkgtopdir}\", silent = TRUE)'", "| LC_ALL=C", &shell_quote_file_path(${R::Vars::R_EXE}), "--vanilla --slave)" )); if(R_system($cmd)) { $log->error(); $log->print("Running massageExamples to create " . "'${pkgname}-Ex.R' failed.\n"); exit(1); } ## It ran, but did it create any examples? if(-f "${pkgname}-Ex.R") { my $enc = ""; if(defined($description->{"Encoding"})) { $enc = "--encoding " . $description->{"Encoding"}; if ($is_ascii) { print "\n* WARNING: ", "checking a package with encoding '", $description->{"Encoding"}, "' in an ASCII locale.\n"; } } ## might be diff-ing results against tests/Examples later if($opt_use_gct) { $cmd = join(" ", ("(echo 'gctorture(TRUE)';", "cat ${pkgname}-Ex.R) |", "LANGUAGE=en", &shell_quote_file_path(${R::Vars::R_EXE}), "${R_opts}", $enc, "> ${pkgname}-Ex.Rout 2>&1")); } else { $cmd = join(" ", ("LANGUAGE=en", &shell_quote_file_path(${R::Vars::R_EXE}), "${R_opts}", $enc, "< ${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")); ## Look for the header section anchored by a subsequent call ## to flush(): needs to be kept in sync with the code in ## massageExamples (in testing.R). 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. Also warn about loading defunct base package stubs, ## as load special-casing for these will be removed eventually. my @lines = &read_lines("${pkgname}-Ex.Rout"); my $any; my @bad_lines; @bad_lines = grep(/^Warning: .*is deprecated.$/, @lines); if(scalar(@bad_lines) > 0) { $log->warning(); $any++; $log->print("Found the following significant warnings:\n"); $log->print(" " . join("\n ", @bad_lines) . "\n"); $log->print(wrap("", "", ("Deprecated functions may be defunct as", "soon as of the next release of R.\n", "See ?Deprecated.\n"))); } $log->result("OK") unless($any); ## Try to compare results from running the examples to a saved ## previous version. my $Rex_out_save = &file_path($pkgdir, "tests", "Examples", "${pkgname}-Ex.Rout.save"); if(-f $Rex_out_save) { $log->checking("differences from '${pkgname}-Ex.Rout' to '${pkgname}-Ex.Rout.save'"); my @out = R_runR("invisible(tools::Rdiff('${pkgname}-Ex.Rout', '${Rex_out_save}',TRUE,TRUE))", "--slave --vanilla"); if(scalar(@out) > 0) { $log->print("\n" . join("\n", @out) . "\n"); } $log->result("OK"); } } else { ## no examples found $log->result("NONE"); } } } elsif(-d &file_path($pkgdir, "man")) { $log->checking("examples"); $log->result("SKIPPED"); } ## Run the package-specific tests. if((-d &file_path($pkgdir, "tests")) && &list_files(&file_path($pkgdir, "tests"))) { $log->checking("tests"); if ($opt_install && $opt_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); dircopy($testsrcdir, "."); if($opt_use_valgrind) { if($opt_use_gct) { $extra = "use_gct = TRUE, use_valgrind = TRUE"; } else { $extra = "use_valgrind = TRUE"; } } else { if($opt_use_gct) { $extra = "use_gct = TRUE"; } else { $extra = ""; } } $cmd = join(" ", ("(echo 'tools:::.runPackageTestsR($extra)' |", &shell_quote_file_path(${R::Vars::R_EXE}), "--vanilla --slave)") ); if(R_system($cmd)) { $log->error(); ## Don't just fail: try to log where the problem occurred. ## First, find the test which failed. my @bad_files = &list_files_with_exts($testdir, "Rout.fail"); if(scalar(@bad_files) > 0) { ## Maybe there was an error without a failing test. my $file = $bad_files[0]; ## Read in output from the failed test and retain at ## most the last 13 lines (13? why not?). my @lines = &read_lines($file); my $to = $#{lines}; my $from = ($to >= 12) ? ($to - 12) : 0; @lines = @lines[$from..$to]; $file =~ s/out\.fail//; $file = &file_path("tests", basename($file)); $log->print("Running the tests in '$file' failed.\n"); $log->print("Last 13 lines of output:\n"); $log->print(" " . join("\n ", @lines) . "\n"); } exit(1); } chdir($pkgoutdir); $log->result("OK"); } else { $log->result("SKIPPED"); } } ## 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 source 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_install && $opt_vignettes) { ## copy the inst directory to check directory ## so we can work in place. mkpath("inst/doc"); dircopy($vignette_dir, "inst/doc"); if ($R_check_latex_vignettes) { $R_check_latex_vignettes = 0 if R_system("texi2dvi --version > /dev/null"); } my $Rcmd = "options(warn=1)\nlibrary(tools)\n"; ## Should checking the vignettes assume the system default ## packages, or just base? $Rcmd .= "checkVignettes(dir = '$pkgoutdir', workdir='src'"; $Rcmd .= ", weave = FALSE" unless $R_check_weave_vignettes; $Rcmd .= ", tangle = FALSE" if $R_check_weave_vignettes; $Rcmd .= ", latex = TRUE" if $R_check_latex_vignettes; $Rcmd .= ")\n"; my @out = R_runR($Rcmd, "${R_opts} --quiet"); ## Vignette could redefine the prompt, e.g. to 'R>' ... @out = grep(!/^[[:alnum:]]*\>/, @out); ## Or to "empty". As empty lines in the output will most ## likely not indicate a problem ... @out = grep(!/^[[:space:]]*$/, @out); @out = grep(!/^Xlib: *extension "RANDR" missing on display/, @out) if($R_check_suppress_RandR_message); if(scalar(@out) > 0) { if(grep(/^\*\*\* (Tangle|Weave|Source) Errors \*\*\*$/, @out)) { $log->warning() unless($any); } else { $log->note() unless($any); } $any++; $log->print(join("\n", @out) . "\n"); } } else { $any++; $log->result("SKIPPED"); } $log->result("OK") unless($any); } } ## NB Bundles should have a bundle manual, not one for each package, ## so this is no longer part of check_pkg. sub check_pkg_manual { my ($pkgdir, $pkgname, $log) = @_; ## Run LaTeX on the manual, if there are man pages ## If it is installed with docs there is a 'help' dir ## and for a source package, there is a 'man' dir if ( (-d &file_path($pkgdir, "help")) || (-d &file_path($pkgdir, "man")) ) { if($opt_latex && $HAVE_LATEX) { $topdir = $pkgdir; my $Rd2dvi_opts = "--batch --no-preview"; my $fmt; if($HAVE_PDFLATEX) { $Rd2dvi_opts = $Rd2dvi_opts . " --pdf"; $fmt = "pdf"; } else { $fmt = "dvi"; } $log->checking("\U$fmt\E version of manual"); my $cmd0, $cmd; my $build_dir = R_tempfile("Rd2dvi"); if($WINDOWS) { $cmd0 = "Rcmd.exe"; } else { $cmd0 = &shell_quote_file_path("${R::Vars::R_EXE}"); $cmd0 = "$cmd0 CMD"; } $cmd = join(" ", ($cmd0, "Rd2dvi $Rd2dvi_opts", "--build-dir=${build_dir} --no-clean", "-o ${pkgname}-manual.${fmt} > Rdlatex.log 2>&1", "$topdir")); my $res = R_system($cmd); my $latex_log = &file_path($build_dir, "Rd2.log"); if(-f $latex_log) {copy($latex_log, "${pkgname}-manual.log");} if($res == 2816) { ## 11*256 $log->error(); $log->print("Rd conversion errors:\n"); @lines = &read_lines("Rdlatex.log"); foreach my $line (@lines) { $log->print($line . "\n") unless $line =~ /^(Hmm|Execution)/; } rmtree($build_dir); exit(1); } elsif($res) { my $latex_file = &file_path($build_dir, "Rd2.tex"); if(-f $latex_file) {copy($latex_file, "${pkgname}-manual.tex");} $log->warning(); $log->print("LaTeX errors when creating \U$fmt\E version.\n"); $log->print("This typically indicates Rd problems.\n"); ## If possible, indicate the problems found. my $latex_log = &file_path($build_dir, "Rd2.log"); ## Note that Rd2dvi works on 'Rd2.tex'. if(-f $latex_log) { my $Rcmd = "writeLines(tools:::.get_LaTeX_errors_from_log_file(\"${latex_log}\"))"; my @out = R_runR($Rcmd, "${R_opts} --quiet", "R_DEFAULT_PACKAGES=NULL"); @out = grep(!/^\>/, @out); if(scalar(@out) > 0) { $log->print("LaTeX errors found:\n"); $log->print(join("\n", @out) . "\n"); } } rmtree($build_dir); $log->checking("\U$fmt\E version of manual without index"); $cmd = join(" ", ($cmd0, "Rd2dvi $Rd2dvi_opts", "--build-dir=${build_dir} --no-clean --no-index", "-o ${pkgname}-manual.${fmt} >/dev/null 2>&1", "$topdir")); if(R_system($cmd)) { $log->error(); if(-f $latex_log) { copy($latex_log, "${pkgname}-manual.log"); } else { ## No log file and thus no chance to find out ## what went wrong. Hence, re-run without ## redirecting stdout/stderr and hope that this ## gives the same problem ... ## Ideally, we would have a version of ## R_system() similar to R_run_R() which returns ## both the exit status and stdout/stderr. $log->print("LaTeX error when running command:\n"); $log->print(wrap(" ", " ", ($cmd)) . "\n"); $log->print("Re-running with no redirection of stdout/stderr.\n"); $cmd = join(" ", ($cmd0, "Rd2dvi $Rd2dvi_opts", "--build-dir=${build_dir} --no-clean --no-index", "-o ${pkgname}-manual.${fmt} $topdir")); R_system($cmd); } rmtree($build_dir); exit(1); } } else { rmtree($build_dir); $log->result("OK"); } } } } sub usage { print <. END exit 0; }