Sync your R packages with Dropbox or SpiderOak Hive

Introduction

If you have R installed on multiple computers like I do, and using it on different machines at different times, you will sooner or later find it useful to guarantee that you have the same R packages available everywhere. You could, in theory, host your R package library on Dropbox, as explained here. A more private version of that might be SpiderOak Hive.

That would be easiest. If it works for you, tl;dr right here.

My solution

Hosting your R library directly on Dropbox would cut into your quota; not a lot, but it could be significant if you're promiscuously adding new libraries all the time. In addition, maybe you want to sync your R packages with friends who don't want their libraries stored in a shared Dropbox folder. A solution that would address both of these use cases would keep tabs on the installed R packages in the synced/shared folder, but the packages themselves would be somewhere else, like the default spot where R puts them.

I think the function below does that. Between all the comments there, it should be self-explanatory:

# 2014.01.28 This is a grand package unifier: a function that ensures that
# you have the same set of R packages installed across all computers that
# this function may be called from.  Prerequisites: you must keep your R
# packages listed in a synced folder, like Dropbox or SpiderOak Hive.  To
# keep things neat, since those synced folders can get pretty messy, put
# your R package lists in a subfolder of their own, say named syncR.  The
# name of the synced folder ('SpiderOak Hive' or 'Dropbox' is the function
# argument).
syncPacks <- function(syncfolder = "Dropbox") {
    # Get this computer's info
    thisPuter <- Sys.info()

    # Find the path of the sync folder. Default spot: in your home folder on a
    # Mac, in Documents on a PC.  If your setup is different, or you need to
    # sync R also on Linux or FreeBSD machines, fiddle with this block of code
    # accordingly:
    root <- paste("/Users", thisPuter["user"], syncfolder, sep = "/")
    if (thisPuter["sysname"] == "Windows") {
        root <- paste("c:/users", thisPuter["user"], "documents", syncfolder, 
            sep = "/")
    }
    if (!file.exists(root)) {
        stop(paste("Could not find the folder", syncfolder, "on this computer.", 
            sep = " "))
    }
    # Also if your syncR folder is called something else, fiddle here:
    root <- paste(root, "/syncR", sep = "")

    # collect the working directory
    mywd <- getwd()
    # Refresh the packages data set for the computer you're on
    setwd(root)
    fi <- paste(thisPuter["nodename"], ".packs.RData", sep = "")
    packs <- as.data.frame(installed.packages())
    save(packs, file = fi)

    # You may already have R package lists from other computers in your sync
    # folder:
    namelist <- dir(getwd())[grep("RData", dir(getwd()))]
    namelist <- gsub(".packs.RData", "", namelist[grep("RData", namelist)])
    namelist <- union(namelist, thisPuter["nodename"])

    # Install any packages present on any other computer but missing on this
    # one. 3 steps:
    installMissing <- function(puter) {
        # Step 2: Find what you need to install.
        runMySetdiff <- function(puter) {
            others <- setdiff(namelist, puter)
            # Step 1: return packages on all computers as a list of as many elements as
            # computers.
            getMyPacks <- function() {
                out <- list()
                for (i in namelist) {
                  packz <- paste(i, "packs.Rdata", sep = ".")
                  load(packz)
                  out[[i]] <- as.character(packs$Package)
                }
                return(out)
            }
            mypacks <- getMyPacks()
            # Combine packages from all other computers in one vector.
            others <- unique(unlist(mypacks[others]))
            mine <- unlist(mypacks[[puter]])
            # Return the list of packages missing on this computer.
            toadd <- setdiff(others, mine)
            print(paste(length(toadd), "packages to add.", sep = " "))
            return(toadd)
        }
        needThese <- runMySetdiff(puter)
        if (length(needThese) > 0) {
            install.packages(needThese)
        } else {
            print("good to go.")
        }
    }
    # Step 3: run the installer function for this computer
    installMissing(thisPuter["nodename"])

    # Refresh the package list again to reflect any new additions
    packs <- as.data.frame(installed.packages())
    save(packs, file = fi)
    # restore the working directory to whatever it was
    setwd(mywd)
}
# Now just run the whole thing
syncPacks()

Limitations

This will take care of CRAN packages. Other repositories – such as IQSS for the Zelig suite, Bioconductor, devtools or Rforge – are not supported, but it's a matter of what's practical for you. You can tweak the script above for these additional repositories if you find that you use their packages a lot, or you can just read the warnings and install such packages individually by hand, like I do.