#! @PERL@ #-*- perl -*- # Copyright (C) 2000-2002 R Development Core Team # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # A copy of the GNU General Public License is available via WWW at # http://www.gnu.org/copyleft/gpl.html. You can also obtain it by # writing to the Free Software Foundation, Inc., 59 Temple Place, # Suite 330, Boston, MA 02111-1307 USA. # Send any bug reports to r-bugs@r-project.org ## ## This is not portable: has Unix-style file paths and system(). (But ## we need to call tar/gzip and zip, respectively, so it is not clear ## how platform-independent the code can be. ## use Cwd; use File::Basename; use File::Compare; use File::Find; use File::Path; use Getopt::Long; use R::Dcf; use R::Logfile; use R::Rd; use R::Utils; use R::Vars; use Text::DelimMatch; use Text::Wrap; ## don't buffer output $| = 1; my $revision = ' $Revision: 1.44 $ '; my $version; my $name; $revision =~ / ([\d\.]*) /; $version = $1; ($name = $0) =~ s|.*/||; R::Vars::error("R_HOME", "R_CMD"); my $WINDOWS = ($R::Vars::OSTYPE eq "windows"); my @excludepatterns = R::Utils::get_exclude_patterns(); my $vignette_exts_re = "[rRsS](nw|tex)"; my @knownoptions = ("help|h", "version|v", "binary", "no-docs", "use-zip", "use-zip-help", "use-zip-data", "force", "no-vignettes"); if($WINDOWS) { die "Please set TMPDIR to a valid temporary directory\n" unless (-e ${R::Vars::TMPDIR}); @knownoptions = ("help|h", "version|v", "binary", "docs:s", "use-zip", "use-zip-help", "use-zip-data", "force"); } GetOptions (@knownoptions) or usage(); R_version("R add-on package builder", $version) if $opt_version; usage() if $opt_help; my $startdir = cwd(); my $R_platform = R_getenv("R_PLATFORM", "unknown-binary"); my $tar = R_getenv("TAR", "tar"); my $INSTALL_opts = ""; $INSTALL_opts .= " --use-zip" if $opt_use_zip; $INSTALL_opts .= " --use-zip-data" if $opt_use_zip_data; $INSTALL_opts .= " --use-zip-help" if $opt_use_zip_help; if($WINDOWS) { $INSTALL_opts .= " --docs=$opt_docs" if $opt_docs; } else { $INSTALL_opts .= " --no-docs" if $opt_no_docs; } ## ## Once we have a 'global' log file, use $log->warning() instead of just ## print(). if(!$opt_binary && $INSTALL_opts ne "") { print "** Options '$INSTALL_opts' for '--binary' ignored\n"; } ## ## This is the main loop over all packages to be checked. foreach my $pkg (@ARGV) { my $is_bundle = 0; $pkg =~ s/\/$//; my $pkgname = basename($pkg); chdir($startdir); my $log = new R::Logfile(); my $description; $log->checking("for file '$pkg/DESCRIPTION'"); if(-r "$pkg/DESCRIPTION") { $description = new R::Dcf("$pkg/DESCRIPTION"); $log->result("OK"); } else { $log->result("NO"); exit(1); } if($opt_binary) { my $libdir = "${R::Vars::TMPDIR}/Rbuild.$$"; mkdir("$libdir", 0755) or die "Cannot create directory '$libdir'\n"; if(system("${R::Vars::R_CMD} INSTALL -l $libdir " . "$INSTALL_opts $pkg")) { $log->error("installation failed"); } print("\n"); chdir($libdir); my $pkgs = $pkgname; if($description->{"Contains"}) { $log->message("Looks like '${pkg}' is a package bundle"); $is_bundle = 1; my @bundlepkgs = split(/\s+/, $description->{"Contains"}); $pkgs = join(" ", @bundlepkgs); } if($WINDOWS) { my $filename = "${pkgname}_" . $description->{"Version"}; $log->message("building '$filename.zip'"); system("zip -r9X $startdir/$filename.zip $pkgs"); chdir($startdir); } else { ## ## As R CMD INSTALL recursively copies all of `inst', we at ## least need to make sure that CVS subdirs are excluded. ## It is not clear whether more patterns should be excluded, ## and we also need to fix this under Windows. my $exclude = "${R::Vars::TMPDIR}/Rbuild-exclude.$$"; open(EXCLUDE, "> $exclude") or die "Cannot write to '$exclude'\n"; sub findExcludeFiles { print EXCLUDE "$File::Find::name\n" if /^CVS$/; } foreach my $p (split(/\s+/, $pkgs)) { find(\&findExcludeFiles, "$p"); } close(EXCLUDE); my $filename = "${pkgname}_" . $description->{"Version"} . "_R_${R_platform}.tar"; $log->message("building '$filename.gz'"); system("$tar chXf $exclude $startdir/$filename $pkgs"); chdir($startdir); system("gzip -9f $filename"); unlink($exclude); ## } rmtree($libdir); } else { if($description->{"Contains"}) { $log->message("Looks like '${pkg}' is a package bundle"); $is_bundle = 1; my @bundlepkgs = split(/\s+/, $description->{"Contains"}); foreach my $ppkg (@bundlepkgs) { $log->message("preparing '$ppkg' in bundle '$pkg':"); $log->setstars("**"); chdir($startdir); prepare_pkg("$pkg/$ppkg", $is_bundle, $description, $log); $log->setstars("*"); } } else { $is_bundle = 0; chdir($startdir); $log->message("preparing '$pkg':"); prepare_pkg("$pkg", $is_bundle, $description, $log); } chdir($startdir); $log->message("removing junk files"); find(\&deleteJunkFiles, $pkg); my $exclude = "${R::Vars::TMPDIR}/Rbuild-exclude.$$"; open(EXCLUDE, "> $exclude") or die "Cannot write to '$exclude'\n"; binmode EXCLUDE if $WINDOWS; if(-f "$pkg/.Rbuildignore") { open(RBUILDIGNORE, "$pkg/.Rbuildignore"); while() { chop; push(@excludepatterns, $_) if $_; } close(RBUILDIGNORE); } sub findExcludeFiles { print EXCLUDE "$File::Find::name\n" if(-d $_ && /^check$/); print EXCLUDE "$File::Find::name\n" if(-d $_ && /^chm$/); print EXCLUDE "$File::Find::name\n" if(-d $_ && /[Oo]ld$/); print EXCLUDE "$File::Find::name\n" if /^GNUMakefile$/; print EXCLUDE "$File::Find::name\n" if /^CVS$/; my $filename = $File::Find::name; $filename =~ s/^[^\/]*\///; foreach my $p (@excludepatterns) { if($WINDOWS) { ## Argh: Windows is case-honoring but not ## case-insensitive ... print EXCLUDE "$File::Find::name\n" if($filename =~ /$p/i); } else { print EXCLUDE "$File::Find::name\n" if($filename =~ /$p/); } } } chdir("$pkg/.."); find(\&findExcludeFiles, "$pkgname"); close(EXCLUDE); my $filename = "${pkgname}_" . $description->{"Version"} . ".tar"; $log->message("building '$filename.gz'"); my $filepath = "$startdir/$filename"; if($WINDOWS) { ## workaround for paths in Cygwin tar $filepath =~ s+^([A-Za-x]):+/cygdrive/\1+; } system("$tar chXf $exclude $filepath $pkgname"); chdir($startdir); system("gzip -9f $filename"); unlink($exclude); } $log->close(); print("\n"); } sub deleteJunkFiles { unlink($_) if /^(\.RData|\.Rhistory)$/; if(/^DESCRIPTION$/) { unlink($_) if (-f "DESCRIPTION.in"); } } #********************************************************** sub prepare_pkg { my ($pkg, $in_bundle, $description, $log) = @_; chdir($pkg); my $pkgdir = cwd(); my $pkgname = basename($pkg); if(-d "src") { chdir("src"); $log->message("cleaning src"); if($WINDOWS) { if(-r "Makefile.win") { system("${R::Vars::MAKE} -f Makefile.win clean"); } else { foreach my $file (<*.o $pkgname.a $pkgname.dll $pkgname.def>) { unlink($file); } rmtree("_libs") if (-d "_libs"); } } else { if(-r "Makefile") { system("${R::Vars::MAKE} -f Makefile clean"); } else { foreach my $file (<*.o *s[lo]>) { unlink($file); } rmtree(".libs") if (-d ".libs"); } } } chdir($pkgdir); if(!$WINDOWS && -x "./cleanup") { $log->message("running cleanup"); system("./cleanup"); } updateRdIndex("INDEX", "man", 0, $log); updateRdIndex("data/00Index", "man", 1, $log) if(-d "data"); if(!$opt_no_vignettes && (-d "inst/doc")) { if(list_files_with_exts("inst/doc", $vignette_exts_re)) { $log->creating("vignettes"); my $Rcmd = "library(tools); buildVignettes(dir='.')\n"; my @out = R_runR($Rcmd, "--vanilla --no-save --quiet"); $log->result("done"); updateVignetteIndex("inst/doc/00Index.dcf", "inst/doc", $log); } } 1; } #********************************************************** sub updateIndex { my ($oldindex, $newindex, $log) = @_; $log->checking("whether '$oldindex' is up-to-date"); if(-r $oldindex) { if(compare($oldindex, $newindex) != 0) { $log->result("NO"); if($opt_force) { $log->message("overwriting '${oldindex}' as " . "'--force' was given"); unlink $oldindex; rename($newindex, $oldindex); } else { $log->message("use '--force' to overwrite " . "the existing '${oldindex}'"); unlink $newindex; } } else { $log->result("OK"); unlink $newindex; } } else { $log->result("NO"); $log->message("creating new '$oldindex'"); unlink $oldindex; rename($newindex, $oldindex); } 1; } sub updateRdIndex { my ($oldindex, $Rdfiles, $dataset, $log) = @_; my $newindex = ".Rbuildindex.$$"; my $dataopt = "-d" if $dataset; ## ## Rdindex really is a Perl script, so we could work around this ## call to R_system(). R_system("${R::Vars::R_CMD} Rdindex $dataopt ${Rdfiles} " . "> ${newindex}"); ## updateIndex($oldindex, $newindex, $log); 1; } sub updateVignetteIndex { my ($oldindex, $dir, $log) = @_; my $newindex = ".Rbuildindex.$$"; buildVignetteIndex($dir, $newindex); updateIndex($oldindex, $newindex, $log); 1; } sub buildVignetteIndex { my ($dir, $newindex) = @_; my $delimcurly = new Text::DelimMatch("\\{", "\\}"); $delimcurly->escape("\\"); my @entries = (); my @Snwfiles = &list_files_with_exts("$dir", $vignette_exts_re); foreach my $file (@Snwfiles) { open(SNWFILE, "< $file") or die("Error: cannot open '$file' for reading"); my @text = ; @text = split(/\n\s*\%+\s*\\VignetteIndexEntry\{/, "\n" . join("\n", @text)); if($#text >= 1) { $delimcurly->match("\{" . $text[1]); $file =~ s/\.$vignette_exts_re$//; push(@entries, "$file.pdf: " . substr($delimcurly->matched, 1, -1)); } close(SNWFILE); } open(NEWINDEX, "> $newindex") or die("Error: cannot open '$newindex' for writing"); foreach my $entry (@entries) { print NEWINDEX wrap("", " ", $entry), "\n"; } close(NEWINDEX); 1; } #********************************************************** sub usage { print STDERR <. END exit 0; }