Rev 32987 | Blame | Last modification | View Log | Download | RSS feed
#! @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.orguse Cwd;use File::Basename;use File::Compare;use File::Find;use File::Path;use File::Copy;use Getopt::Long;use IO::File;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.81 $ ';my $version;my $name;$revision =~ / ([\d\.]*) /;$version = $1;($name = $0) =~ s|.*/||;R::Vars::error("R_HOME", "R_EXE");my $WINDOWS = ($R::Vars::OSTYPE eq "windows");my @exclude_patterns = R::Utils::get_exclude_patterns();my @known_options = ("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});@known_options = ("help|h", "version|v", "binary", "docs:s","auto-zip","use-zip", "use-zip-help", "use-zip-data","force", "no-vignettes");}GetOptions(@known_options) or usage();R_version("R add-on package builder", $version) if $opt_version;usage() if $opt_help;## Use system default unless explicitly specified otherwise.$ENV{"R_DEFAULT_PACKAGES"} = "";my $startdir = R_cwd();my $R_platform = R_getenv("R_PLATFORM", "unknown-binary");my $tar = R_getenv("TAR", "tar");my $libdir = &file_path(${R::Vars::TMPDIR}, "Rinst.$$");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;$INSTALL_opts .= " --auto-zip" if $opt_auto_zip;} else {$INSTALL_opts .= " --no-docs" if $opt_no_docs;}## <FIXME>## Once we have a 'global' log file, use $log->warning() instead of just## print().if(!$opt_binary && $INSTALL_opts ne "") {print "** Options '$INSTALL_opts' only for '--binary' ignored\n";}## </FIXME>## This is the main loop over all packages to be packaged.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 &file_path($pkg, "DESCRIPTION")) {$description = new R::Dcf(&file_path($pkg, "DESCRIPTION"));$log->result("OK");}else {$log->result("NO");exit(1);}my @bundlepkgs;if($description->{"Contains"}) {$log->message("looks like '${pkg}' is a package bundle");$is_bundle = 1;@bundlepkgs = split(/\s+/, $description->{"Contains"});foreach my $ppkg (@bundlepkgs) {$log->message("preparing '$ppkg' in bundle '$pkg':");$log->setstars("**");chdir($startdir);prepare_pkg(&file_path("$pkg", "$ppkg"), $is_bundle,$description, $log);$log->setstars("*");}rmtree("$libdir") if (-d "$libdir");} 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(\&unlink_junk_files, $pkg);my $exclude = &file_path(${R::Vars::TMPDIR}, "Rbuild-exclude.$$");open(EXCLUDE, "> $exclude")or die "Error: cannot open file '$exclude' for writing\n";binmode EXCLUDE if $WINDOWS;## <NOTE>## For bundles, the .Rbuildignore mechanism is not consistent## between build and check: the latter always works on a per## package basis.if(-f &file_path($pkg, ".Rbuildignore")) {open(RBUILDIGNORE, &file_path($pkg, ".Rbuildignore"));while(<RBUILDIGNORE>) {chop;push(@exclude_patterns, $_) if $_;}close(RBUILDIGNORE);}## </NOTE>sub find_exclude_files {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(-d $_ && /^CVS$/);print EXCLUDE "$File::Find::name\n" if(-d $_ && /^\.svn$/);if($WINDOWS) {## exclude stray dependency filesprint EXCLUDE "$File::Find::name\n" if /\.d$/;print EXCLUDE "$File::Find::name\n" if /^Makedeps$/;## and the DLL resource filemy $resname = $pkgname . "_res.rc";print EXCLUDE "$File::Find::name\n" if /^$resname$/;}my $filename = $File::Find::name;$filename =~ s/^[^\/]*\///;foreach my $p (@exclude_patterns) {if($WINDOWS) {## Argh: Windows is case-honoring but not## case-insensitive ...print EXCLUDE "$File::Find::name\n"if($filename =~ /$p/i);}else {if($filename =~ /$p/) {## Seems that the Tar '-X' option uses exclude## *shell* patterns, where '*', '?', and '[...]'## are the usual shell wildcards and '\' escapes## them. Hence we need to escape the wildcard## characters in file names. On Windows, the## first two are invalid (and hence rejected by## R CMD check), and the last two do not need## escaping.$filename = "$File::Find::name";$filename =~ s/\[/\\\[/g;$filename =~ s/\]/\\\]/g;print EXCLUDE "$filename\n";}}}}chdir(&file_path("$pkg", ".."));find(\&find_exclude_files, "$pkgname");close(EXCLUDE);my $filename = "${pkgname}_" . $description->{"Version"} . ".tar";my $filepath = &file_path($startdir, $filename);## under Windows, need separate Cygwin and Windows versions of path.my $origfilepath = $filepath;if($WINDOWS) {## workaround for paths in Cygwin tar$filepath =~ s+^([A-Za-x]):+/cygdrive/\1+;}R_system(join(" ",("$tar chf",&shell_quote_file_path($filepath),"$pkgname")));my $tmpdir = &file_path(${R::Vars::TMPDIR}, "Rbuild.$$");rmtree($tmpdir) if(-d $tmpdir);mkdir("$tmpdir", 0755)or die "Error: cannot create directory '$tmpdir'\n";chdir($tmpdir);R_system(join(" ",("$tar xhf",&shell_quote_file_path($filepath))));## Remove exclude files.open(EXCLUDE, "< $exclude");while(<EXCLUDE>) {rmtree(glob($_));}close(EXCLUDE);unlink($exclude);unlink($origfilepath);## Fix permissions.sub fix_permissions {## Note that when called via File::Find::find, $_ holds the## file name within the current directory.if(-d $_) {## Directories should really be mode 00755 if possible.chmod(00755, $_);}elsif(-f $_) {## Files should be readable by everyone, and writable## only for user. This leaves a bit of uncertainty## about the execute bits.chmod(((stat $_)[2] | 00644) & 00755, $_);}}find(\&fix_permissions, "${pkgname}") if(!$WINDOWS);## Add build stamp to the DESCRIPTION file.&add_build_stamp_to_description_file(&file_path($pkgname,"DESCRIPTION"));$log->message("checking for LF line-endings in source files");if($is_bundle) {foreach my $ppkg (@bundlepkgs) {&fix_CRLF_in_source_files(&file_path($pkgname, $ppkg));}} else {&fix_CRLF_in_source_files($pkgname);}sub empty_dir_check {if(-d $_) {opendir(DIR, $_) or die "cannot open dir $File::Find::name: $!";my @files = readdir(DIR);closedir(DIR);if (@files <= 2) {print "WARNING: directory $File::Find::name is empty\n";if($File::Find::name =~ m+^$pkgname(/|\\)(help|html|latex|libs)$+) {print " A directory named '$_' should only occur in an installed package\n";}}}}$log->message("checking for empty directories");find(\&empty_dir_check, "${pkgname}");## Finalize.if($opt_binary) {$log->message("building binary distribution");chdir($startdir);if (!-d "$libdir") {mkdir("$libdir", 0755)or die "Error: cannot create directory '$libdir'\n";}my $srcdir = &file_path($tmpdir, $pkgname);my $cmd;if($WINDOWS) {$log->warning("some HTML links may not be found");$cmd = join(" ",("Rcmd.exe INSTALL -l",&shell_quote_file_path($libdir),"--build $INSTALL_opts",&shell_quote_file_path($srcdir)));if(R_system($cmd)) { $log->error("installation failed"); }} elsif($is_bundle) {$binfilename = "${pkgname}_" . $description->{"Version"} ."_R_${R_platform}.tar";my $filepath = &file_path($startdir, $binfilename);$cmd = join(" ",(&shell_quote_file_path(${R::Vars::R_EXE}),"CMD INSTALL -l",&shell_quote_file_path($libdir),"$INSTALL_opts",&shell_quote_file_path($srcdir)));if(R_system($cmd)) { $log->error("installation failed"); }chdir("$libdir");copy(&file_path($startdir, $pkg, "DESCRIPTION"),"DESCRIPTION");R_system(join(" ",("$tar chf ",&shell_quote_file_path($filepath),@bundlepkgs, "DESCRIPTION")));R_system(join(" ",("gzip -9f ",&shell_quote_file_path($filepath))));chdir($startdir);$log->message("packaged bundle '$pkgname' as '$binfilename.gz'");} else {$cmd = join(" ",(&shell_quote_file_path(${R::Vars::R_EXE}),"CMD INSTALL -l",&shell_quote_file_path($libdir),"--build $INSTALL_opts",&shell_quote_file_path($srcdir)));if(R_system($cmd)) { $log->error("installation failed"); }}} else {$log->message("building '$filename.gz'");R_system(join(" ",("$tar chf",&shell_quote_file_path($filepath),"$pkgname")));R_system(join(" ",("gzip -9f",&shell_quote_file_path($origfilepath))));}chdir($startdir);rmtree($tmpdir);$log->close();print("\n");}sub add_build_stamp_to_description_file {my ($dpath) = @_;my @lines = &read_lines($dpath);@lines = grep(!/^\s*$/, @lines); # Remove blank lines.my $user_name;if($WINDOWS) {$user_name = Win32::LoginName();}else {$user_name = (getpwuid($<))[0];}my $fh = new IO::File($dpath, "w")or die "Error: cannot open file '$dpath' for writing\n";## Do not keep previous build stamps.@lines = grep(!/^Packaged:/, @lines);$fh->print(join("\n", @lines), "\n");$fh->print("Packaged: ",scalar(localtime()), "; ",$user_name, "\n");$fh->close();}sub prepare_pkg {my ($pkg, $in_bundle, $description, $log) = @_;chdir($pkg);my $pkgdir = R_cwd();my $pkgname = basename($pkg);&R::Utils::check_package_description($pkgdir, $pkgname, $log,$in_bundle, 0, 1);if(-d "src") {chdir("src");$log->message("cleaning src");if($WINDOWS) {## A Windows Makefile.win might use## $(RHOME)/src/gnuwin32/MkRules.$ENV{RHOME} = $ENV{R_HOME};if(-r "Makefile.win") {R_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") {R_system("${R::Vars::MAKE} -f Makefile clean");} else {foreach my $file (<*.o *.s[lo] .dylib>) {unlink($file);}rmtree(".libs") if (-d ".libs");}}}chdir($pkgdir);if(!$WINDOWS && -x "./cleanup") {$log->message("running cleanup");R_system("./cleanup");}## Only update existing INDEX files.&update_Rd_index("INDEX", "man", $log) if(-f "INDEX");if((-d &file_path("inst", "doc"))&& &list_files_with_type(&file_path("inst", "doc"),"vignette")) {if(!$opt_no_vignettes) {my $doit = 1;## if we are in a bundle, need to install the whole bundle once.if ($in_bundle) {$pkgdir = $pkgdir . "/..";if(-d "$libdir") {$doit = 0;} else {$log->message("installing the *bundle* to re-build vignettes");mkdir("$libdir", 0755)or die "Error: cannot create directory '$libdir'\n";}} else {$log->message("installing the package to re-build vignettes");mkdir("$libdir", 0755)or die "Error: cannot create directory '$libdir'\n";}my $cmd;if($WINDOWS) {$cmd = join(" ",("Rcmd.exe INSTALL -l",&shell_quote_file_path($libdir),&shell_quote_file_path($pkgdir)));} else {$cmd = join(" ",(&shell_quote_file_path(${R::Vars::R_EXE}),"CMD INSTALL -l",&shell_quote_file_path($libdir),&shell_quote_file_path($pkgdir)));}if($doit && R_system($cmd)) {$log->error();$log->print("Installation failed.\n");exit(1);}my $R_LIBS = $ENV{'R_LIBS'};$ENV{'R_LIBS'} = env_path("$libdir", $R_LIBS);$log->creating("vignettes");my $Rcmd = "library(tools)\n";$Rcmd .= "buildVignettes(dir = '.')\n";my @out = R_runR($Rcmd, "--vanilla --no-save --quiet");rmtree("$libdir") unless $in_bundle;$ENV{'R_LIBS'} = $R_LIBS;my @err = grep(/^Error/, @out);if(scalar(@err) > 0) {@out = grep(!/^\>/, @out);$log->error();$log->print(join("\n", @out) . "\n");exit(1);}else {$log->result("OK");}}}1;}sub unlink_junk_files {unlink($_) if /^(\.RData|\.Rhistory)$/;if(/^DESCRIPTION$/) {unlink($_) if (-f "DESCRIPTION.in");}}sub update_index {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 update_Rd_index {my ($oldindex, $Rd_files, $log) = @_;my $newindex = ".Rbuildindex.$$";my $Rcmd = "Rdindex(\"${Rd_files}\", \"${newindex}\")\n";my %result =R_run_R($Rcmd, "--vanilla --quiet", "R_DEFAULT_PACKAGES=tools");if($result{"status"}) {## This is a bit silly ... but just in case this fails, we want## a decent error message.my @out = grep(!/^\>/, @{$result{"out"}});$log->message("computing Rd index");$log->error();$log->print(join("\n", @out) . "\n");exit(1);}update_index($oldindex, $newindex, $log);1;}sub fix_CRLF_in_source_files {my ($pkgname) = @_;if(-d "$pkgname/src") {my @src_files = &list_files_with_exts("$pkgname/src", "(c|h|f|cc|cpp)");foreach my $file (@src_files) {my $has_CRLF = 0;open(FILE, "< $file")or die "Error: cannot open '$file' for reading\n";open(TFILE, "> $file.tmp")or die "Error: cannot open '$file.tmp' for writing\n";binmode(FILE); binmode(TFILE); # for Windowswhile(<FILE>) {chop;$has_CRLF = 1 if $_ =~ /\r$/;$_ =~ s/\r$//;print TFILE "$_\n";}close(TFILE); close(FILE);if ($has_CRLF) {print(" file '$file' had CRLF line endings\n");unlink($file); # should not be necessary, but is on Windowsrename("$file.tmp", $file)or die "Error: cannot rename '$file.tmp'\n";} else {unlink("$file.tmp");}}}}sub usage {print STDERR <<END;Usage: R CMD $name [options] pkgdirsBuild R packages from package sources in the directories specified bypkgdirs.Options:-h, --help print short help message and exit-v, --version print 'build' version info and exit--force force overwriting of INDEX file--no-vignettes do not rebuild package vignettes--binary build pre-compiled binary packages, with options:ENDif($WINDOWS) {print STDERR " --docs=TYPE " ."type(s) of documentation to build and install\n";print STDERR " --auto-zip " ."select zipping of data and help based on size\n";} else {print STDERR " --no-docs " ."do not build and install documentation\n";}print STDERR <<END;--use-zip-data collect data files in zip archive--use-zip-help collect help and examples into zip archives--use-zip combine '--use-zip-data' and '--use-zip-help'Email bug reports to <r-bugs\@r-project.org>.ENDexit 0;}