The R Project SVN R

Rev

Rev 35372 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#-*- perl -*-
# Copyright (C) 2000-5  R Development Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# A copy of the GNU General Public License is available via WWW at
# http://www.gnu.org/copyleft/gpl.html.  You can also obtain it by
# writing to the Free Software Foundation, Inc., 59 Temple Place,
# Suite 330, Boston, MA  02111-1307  USA.

# Send any bug reports to r-bugs@r-project.org

use Cwd;
use File::Basename;
use File::Path;
use Getopt::Long;
use R::Dcf;
use R::Utils;


my $revision = ' $Revision: 1.44.8.2 $ ';
my $version;
my $name;
$revision =~ / ([\d\.]*) /;
$version = $1;
($name = $0) =~ s|.*/||;

my $startdir = cwd();
my $lockdir;
my @bundlepkgs;
my $library;

$SIG{'INT'} = 'Cleanup';

sub Cleanup {
    print "*** user interrupt ***\n";
    if(defined @bundlepkgs) {
    if($is_bundle) { # back out the whole bundle
        for $pkgname (@bundlepkgs) {
        $pkgname .= "_" . $description->{"Version"}
        if $opt_with_package_versions;
        rmtree($library . "/" . $pkgname);
        $dppkg = $lockdir . "/" . $pkgname;
        system("mv $dppkg $library") if ($safe && -d $dppkg);
        }
    } else {
        print "Removing '$dpkg'\n";
        rmtree($dpkg);
        # move back any previously installed package
        $dppkg = $lockdir . "/" . $decpkgname;
        print "backup is $dppkg, exists is ", -d $dppkg, "\n";
        if ($safe && -d $dppkg) {
        print "Restoring previous '$dpkg'\n";
        system("mv $dppkg $library");
        }
    }
    }
    if (defined $lockdir) {
    print "Removing lock directory\n";
    rmtree($lockdir);
    }
    chdir($startdir);
    if (defined $tardir) {
    print "Removing untar-ed package sources\n";
    rmtree($tardir);
    }
    die "*** exiting after cleanup ***\n";
}

my @knownoptions = ("help|h", "version|v", "debug|d", "library|l:s",
            "clean|c", "docs:s", "save|s", "no-save",
            "with-package-versions",
            "use-zip", "use-zip-data", "use-zip-help",
            "auto-zip", "build", "fake", "unsafe",
            "lazy", "no-lazy", "lazy-data", "no-lazy-data");

##      topLevelFiles will be files that sit in the top level of a
##      zip/tarball along with any included directories.
##  This needs to be fixed: this file shouldn't do binary builds,
##  that's what build.in is for - DJM
##      No, that is what INSTALL --build is for, pace DJM

my $topLevelFiles = "DESCRIPTION";

GetOptions (@knownoptions) || usage();
R_version($name, $version) if $opt_version;

my $R_HOME = $ENV{'R_HOME'} ||
    die "Error: Environment variable R_HOME not found\n";

usage() if $opt_help;

## ===== to allow to be converted for use when cross-compiling =====
my $BUILD = "MINGW";
my $Rversion;
if($BUILD eq "CROSS") {
    my $startdir = cwd();
    chdir("../..");
    $R_HOME = cwd();
    chdir($startdir);
    open FILE, "< $R_HOME/VERSION";
    $Rversion = <FILE>;
    close FILE;
    $Rversion =~ s/ .*\n$//;
} else {
    $Rversion = $ENV{'R_VERSION'} ||
    die "Error: Environment variable R_VERSION not found\n";
}
## ===== end =====

if($opt_debug) {
    print "\nOption -d or --debug is accepted for compatibility with Unix.\n";
    print "However, it does less.\n\n";
}

if($opt_library){
    # remove quotes around the library path
    $opt_library =~ s/^['"]//; $opt_library =~ s/['"]$//; #'"
    chdir($opt_library) ||
    die "Error: cannot change to directory \`$opt_library'\n";
    $library = cwd();
    $library = Win32::GetShortPathName($library) if $library =~ / /;
    chdir($startdir);
} else {
    $library = $R_HOME . "/library";
}

my $helpflags = "HELP=YES WINHELP=CHM";
if($opt_docs) {
    if ($opt_docs eq "none") {
    $helpflags = "HELP=NO";
    } elsif ($opt_docs eq "normal") {
    $helpflags = "HELP=YES WINHELP=NO";
    } elsif ($opt_docs eq "chm"|| ($opt_docs eq "all")) {
    $helpflags = "HELP=YES WINHELP=CHM";
    } else {
    die "ERROR: invalid --docs value `$opt_docs'\n";
    }
}

$safe = 1;
$safe = 0 if $opt_unsafe;
    print " *** safe is $safe ***\n" if $opt_debug;

if((-e "$R_HOME/doc/html/R.css") && !(-e "$library/R.css")) {
    printf "installing R.css in $library\n";
    system("cp $R_HOME/doc/html/R.css $library/R.css");
}

my $istar = 0; # any tar in the args

## this is the main loop over all packages/bundles to be installed
my $pkg;
foreach $pkg (@ARGV){
    # remove misguided trailing separator (/ or \)
    $pkg =~ s/\/$//; $pkg =~ s/\\$//;
    my $pkgname = basename($pkg);
    $is_bundle = 0;

    if (!(-e $pkg)) {
    warn "`$pkg' does not exist: skipping\n";
    next;
    }

## is this a tar archive?
    if($pkgname =~ /\.tar\.gz$/ || $pkgname =~ /\.tgz$/) {
    $pkgname =~ s/\.tar\.gz$//;
    $pkgname =~ s/\.tgz$//;
    $pkgname =~ s/_[0-9\.-]*$//;
    my $dir = "R.INSTALL";
    if(! -d $dir) {
        mkdir($dir, 0755) or die "Error: cannot create $dir\n";
    }
    $tardir = $dir;
    ## workaround for paths in Cygwin tar
    $pkg =~ s+^([A-Za-x]):+/cygdrive/\1+;
    if(system("tar -zxf '$pkg' -C $dir")) {
        die "Error: cannot untar the package\n";}
    $pkg = $dir."/".$pkgname;
    $istar = 1;
    }

    chdir($pkg) || die "Error: cannot change to directory \`$pkg'\n";;
    my $canonpkg = cwd();
    $pkgname = basename($canonpkg);
    chdir("..");
    my $pkgdir = cwd();
    chdir($startdir);

    $description = new R::Dcf("$pkg/DESCRIPTION");
    my $intname = $description->{"Package"};
    my $Type = $description->{"Type"};

    if($Type && $Type ne "Package") {
    if($Type eq "Translation") {
        chdir($pkg);
        system("cp -r ./share $R_HOME") if -d "share";
        system("cp -r ./library $R_HOME") if -d "library";
        if($opt_build) {
        my $filename = "${pkgname}_" . $description->{"Version"};
        $startdir = Win32::GetShortPathName($startdir) if $startdir =~ / /;
        chdir($pkgdir);
        system("rm -f  $startdir/$filename.zip");
        system("zip -r9Xq $startdir/$filename.zip $pkgname");
        }
        next;
    }
    die "cannot install package '${pkgname}' of type '$Type'\n"
    }
 
    die "no valid package name found\n" unless length($pkgname) > 0;
    my $pkgoutdir = "$library/$pkgname";
    die "  *** can't install into source directory ***\n"
    if $canonpkg eq $pkgoutdir;
    $pkgdir = Win32::GetShortPathName($pkgdir) if $pkgdir =~ / /;

    my $makecmd = "pkg";
    $makecmd = "pkgfake" if $opt_fake;

    ## do we have a package bundle?
    my $zippkgs = $pkgname;
    @bundlepkgs = ($pkgname);
    if($description->{"Contains"}) {
    print "\nLooks like \`${pkg}' is a package bundle\n";
    $is_bundle = 1;
    @bundlepkgs = split(/\s+/, $description->{"Contains"});
    $zippkgs = join(" ", @bundlepkgs);
    ## join up description files
    foreach $ppkg (@bundlepkgs) {
        open pkgdesc, "> $pkg/$ppkg/DESCRIPTION"
        || die "cannot write $ppkg/DESCRIPTION";
        if (open pkgdescin, "< $pkg/$ppkg/DESCRIPTION.in") {
        while(<pkgdescin>) { print pkgdesc $_; }
        close pkgdescin;
        } else {
        print "no DESCRIPTION.in found for package $ppkg\n";
        print pkgdesc "Package: $ppkg\n";
        }
        open bundledesc, "< $pkg/DESCRIPTION"
        || die "no DESCRIPTION found for bundle";
        while(<bundledesc>) {print pkgdesc $_;}
        close bundledesc;
        close pkgdesc;
    }
    $pkgdir .= "/$pkgname";
    $intname = $description->{"Bundle"};
    }
    print "\n";

    if($description->{"Depends"}) {
    my $depends = $description->{"Depends"};
    if($depends =~ /\bR *\(([^\) ]+) *([^\) ]+)\)/) { # regexp from check
            my $op = $1;
            my $ver = $2;
        ## <FIXME> This thinks 1.9.1 > 1.10.0 </FIXME>
        if ($op eq ">=" && $ver) {
        die "This R is version $Rversion\n".
            "    package \`$pkgname' requires R $ver or later\n"
            unless $Rversion ge $ver;
        } elsif ($op eq "<=" && $ver) {
        die "This R is version $Rversion\n".
            "     package \`$pkgname' requires R $ver or earlier\n"
            unless $Rversion le $ver;
        } else {
        printf "unsupported operator in dependence \"$depends\"\n";
        }
        }
    }
    ## Now we are ready to install
    if($safe) {
    $lockdir = $library . "/" . "00LOCK";
    if(-d $lockdir || -f $lockdir) {
        print "Failed to lock directory '${library}' for modifying\n";
        print "Try removing '${lockdir}'\n";
        exit 3;
    }
    mkpath($lockdir);
    if(! -d $lockdir) {
        print "Failed to lock directory '${library}' for modifying\n";
        print "Permission problem?\n";
        exit 3;
    }
    }

    my $res;
    ## loop over pkgs in the bundle
    for $pkgname (@bundlepkgs) {
    if($opt_with_package_versions) {
        $decpkgname = $pkgname . "_" . $description->{"Version"};
    } else {
        $decpkgname = $pkgname;
    }
    $dpkg = $library. "/" . $decpkgname;
    # if exists, move existing installation to $lockdir
    system("mv $dpkg $lockdir") if ($safe && -d $dpkg);

    $description = new R::Dcf("$pkgdir/$pkgname/DESCRIPTION");

    my $save = "CHECK";
    $save = "false" if $opt_no_save;
    $save = "true" if $opt_save;
    my $tmp = parse_description_field("SaveImage");
    $save = "false" if($tmp == 0);
    $save = "true" if($tmp == 1);
    print "*** save is $save ***\n" if($opt_debug);

    my $lazyload = -1;
    $lazyload = 1 if $opt_lazy;
    $lazyload = 0 if $opt_no_lazy;
    my $tmp = parse_description_field("LazyLoad");
    $lazyload = $tmp if($tmp >= 0);

    my $lazydata = 0;
    $lazydata = 1 if $opt_lazy_data;
    $lazydata = 0 if $opt_no_lazy_data;
    my $tmp = parse_description_field("LazyData");
    $lazydata = $tmp if($tmp >= 0);
    print "*** lazydata is $lazydata ***\n" if($opt_debug);

    my $opt = "";
    if($opt_auto_zip || $opt_build) {
        my $dir = $pkgdir. "/" . $pkgname . "/data";
        my $tmp = parse_description_field("ZipData");
        if(-d $dir && $tmp != 0) {
        my $Rout = "datacnt";
        my @tmp = `ls.exe -s1 $dir`;  # system > no longer works
        my $out = 0, $nodups=1, $prev="", $this;
        foreach $line (@tmp) {
            chomp $line;
            if($line =~ /^total/) {
            $line =~ s/^total //;
            $out = $line;
            } else {
            ## if there is only one file there will not be a total
            $this = $line;
            $this =~ s/([ 0-9]*)(.*)/$1/;
            $out = $this unless $out > 0;
            $line =~ s/\s*[0-9]+\ //;
            $line =~ s/\.[a-zA-Z]+$//;
            $nodups = 0 if $line eq $prev;
            $prev = $_;
            }
        }
        # print ("out was $out, nodups was $nodups\n");
        $opt = "$pkgname-ZIPDATA=zip"
            if ($tmp == 1) || ($out > 100 && $nodups);
        }
        $dir = $pkgdir. "/" . $pkgname . "/man";
        if(-d $dir) {
        opendir(DIR, $dir) or die "cannot opendir $dir: $!";
        @files = grep{ /\.Rd$/ } readdir(DIR);
        closedir(DIR);
        $opt = $opt . " $pkgname-HELP=ziponly" if @files > 20;
        }
        print "Using auto-selected zip options '$opt'\n";
    } else {
        $opt = "$pkgname-ZIPDATA=zip"
        if ($opt_use_zip || $opt_use_zip_data);
        $opt = $opt . " $pkgname-HELP=ziponly"
        if ($opt_use_zip || $opt_use_zip_help);
    }
    $opt = $opt . " DPKG=$dpkg";
    $opt = $opt . " $pkgname-LAZYDATA=true" if $lazydata;
#   print "make --no-print-directory -C $R_HOME/src/gnuwin32 PKGDIR=$pkgdir RLIB=$library $pkgname-SAVE=$save $opt $helpflags $makecmd-$pkgname\n";
        $res = system("make --no-print-directory -C $R_HOME/src/gnuwin32 PKGDIR=$pkgdir RLIB=$library $pkgname-SAVE=$save $opt $helpflags $makecmd-$pkgname");
    if($res == 0) {
        if(! -f "$library/$decpkgname/R/$pkgname") {$lazyload = 0;}
        if($lazyload == -1) { # choose based on code size
        my $codesize = `wc -c $library/$decpkgname/R/$pkgname`;
        $codesize =~ / *([0-9]+)/;
        $codesize = $1;
        print "*** codesize is $codesize ***\n" if $opt_debug;
        if($codesize > 25000) {$lazyload = 1;} else {$lazyload = 0;}
        }
        print "*** lazyload is $lazyload ***\n" if $opt_debug;
        if($lazyload) {
        my $spkg = $pkg;
        $spkg = Win32::GetShortPathName($spkg) if $spkg =~ / /;
        $spkg =~ s+\\+/+g; # seems some systems need this
        my $cmd = "make --no-print-directory -f $R_HOME/src/gnuwin32/MakePkg ";
        $cmd .= "RHOME=$R_HOME SPKG=$spkg BUILD=$BUILD" .
            " DPKG=$library/$decpkgname PKG=$decpkgname" .
            " RLIB=$library lazyload";
#       print "$cmd\n";
        $res = system($cmd);
        }
        ## add MD5 sums last of all
        my $cmd = "make --no-print-directory -f $R_HOME/src/gnuwin32/MakePkg ";
        $cmd .= "BUILD=$BUILD RHOME=$R_HOME DPKG=$library/$decpkgname md5sums";
#       print "$cmd\n";
        $res = system($cmd) if($res == 0);

    } # end of if($res)
    if($res) {
        printf "*** Installation of $pkgname failed ***\n";
    }
    system("make --no-print-directory -C $R_HOME/src/gnuwin32 PKGDIR=$pkgdir RLIB=$library pkgclean-$pkgname") if ($opt_clean);

    print("\n");
    if($res) {
        if($is_bundle) { # back out the whole bundle
        for $pkgname (@bundlepkgs) {
            $pkgname .= "_" . $description->{"Version"}
            if $opt_with_package_versions;
            rmtree($library . "/" . $pkgname);
            $dppkg = $lockdir . "/" . $pkgname;
            system("mv $dppkg $library") if ($safe && -d $dppkg);
        }
        } else {
        print "Removing '$dpkg'\n";
        rmtree($dpkg);
        # move back any previously installed package
        $dppkg = $lockdir . "/" . $decpkgname;
        if ($safe && -d $dppkg) {
            print "Restoring previous '$dpkg'\n";
            system("mv $dppkg $library");
        }
        }
        last;
    }
    } # end of loop over bundle

    if(!$res && $opt_build) {
    my $filename = "${pkgname}_" . $description->{"Version"};
    chdir($library);
    $startdir = Win32::GetShortPathName($startdir) if $startdir =~ / /;
    system("rm -f  $startdir/$filename.zip");
    system("zip -r9Xq $startdir/$filename.zip $zippkgs");
    if ($is_bundle) {
        ## Need to get the toplevel files in there
        $currentDir = cwd();
        chdir($canonpkg);
        system("zip $startdir/$filename.zip $topLevelFiles");
        chdir($currentDir);
    }
    chdir($startdir);
    print "packaged installation of ";
    if ($is_bundle) {print "bundle";} else {print "package";};
    print " '$pkgname' as $filename.zip\n";
    }
    # wipe out lockdir
    rmtree($lockdir) if $safe;
    exit $res if $res;
    print("\* DONE ($intname)\n");
}

## remove unpack directory
if ($istar > 0) { chdir($startdir); rmtree("R.INSTALL"); }

sub parse_description_field { # fieldname
    my ($field) = @_;
    my $tmp = $description->{$field}, $res = -1;
    if($tmp ne "") {
    $tmp = lc $tmp;
    $res = 1 if ($tmp eq "yes") || ($tmp eq "true");
    $res = 0 if ($tmp eq "no") || ($tmp eq "false");
    # or throw an error, but then we need to recover
    }
    return $res;
}

sub usage {
    print STDERR <<END;
Usage: R CMD $name [options] pkgs

Install the add-on packages specified by pkgs into the default R library
tree ($R_HOME/library) or the tree specified via '--library'.  The
elements of pkgs can be relative or absolute paths to directories with
the package (bundle) sources, or to gzipped package 'tar' archives.
Then optionally pack the installed package into a zip file.

Options:
  -h, --help        print short help message and exit
  -v, --version     print version info and exit
  -c, --clean       remove all files created during installation
  -s, --save            save the package source as an image file, and arrange for
                        this file to be loaded when the library is attached.
  --no-save             do not save the package source as an image file.

  --lazy                use lazy loading
  --no-lazy             do not use lazy loading
  --lazy-data           use lazy loading for data
  --no-lazy-data        do not use lazy loading for data (current default)

  --fake        do minimal install for testing purposes
  --unsafe      install on top of any existing installation
  -d, --debug       [x] turn on shell and build-help debugging
  -l, --library=LIB install packages to library tree LIB
  --docs=TYPE           type(s) of documentation to build and install
  --with-package-versions
            allow for multiple versions of the same package
  --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\'
  --auto-zip            select whether to zip automatically
  --build       zip-up the installation.  Implies --auto-zip

TYPE can be "none" or "normal" or "chm" (the default) or "all"

Report bugs to <r-bugs\@r-project.org>.
END
    exit 0;
}