Welcome to post 58 in the R4 series.
R and the CRAN repositories maintain a very
high level of what we might call “quality assurrance” by requiring that
newly-added code does not break any existing dependencies. This is
frequently called a “reverse-dependency check”. For any given CRAN package one can quickly
determine it reverse dependencies. Calling
tools::package_dependencies(pkgName, reverse=TRUE) will for
a scalar or vector-valued argument return a named list with the reverse
dependencies. It is then a matter of looping over this list. There are
helper functions in base R as well as in contributed packages on and off
CRAN. I also wrote my own with
package prrd which,
while possibly a wee bit specialised and under-documented has served me
well to check on Rcpp and
related packages which can indeed have a large number of
reverse dependencies.
I recently looked into one of these contributed runner packages, and
while I will refrain from naming its implementation language let me just
mention that the term “cargo cult” may be a real thing
here. What go me interested in this was the fact that if one
has a simple-to-use runner then the fact that r2u makes it “fast, easy,
reliable: pick all three” (to borrow its slogan) to deal with actual
depencies if Ubuntu has indeed been selected as the host. We will
maintain the position that if you can in fact integrate with
the system-wide package management then any alternative per-repo package
management approach not doing so will likely be dominated by an approach
that does integrate with the system facilities. Which is what precisely
what r2u does, and
offers. And why it is used enough to by now have shipped eighty eight
million binary packages. So I tested it for the reverse-dependency check
task.
What I learned by looking into the (much more complicated) runner was
that it at the end of the day it hands the actual task of running the
reverse dependecies off to a helper function rev_check that
is part of the xfun package
by Yuhui. I quickly found that besides xfun we would also need its
suggested dependency tinytex which in turn
would error unless the tlmgr binary was present. So as the
sole requirement (on an Ubuntu system with r2u) turns out to be
$ apt install r-cran-xfun r-cran-tinytex texlive-basewhere we do it all in one apt call (as root
in the container). (Given r2u we could also call
install.packages(c("xfun","tinytext")) followed by
apt install texlive-base but it is simpler for this setup
step to be just one call).
With that we are basically done. I did this (twice) using a
rocker/r2u container with r2u preinstalled, mounting
a local work and scrap directory for the container. In it we expand the
package to be tested (i.e. tar xaf pkgName_*tar.gz for a
given source package pkgName from CRAN) and then just call with the
package name and expanded direcrtory. I.e. I used this call to test my
package AsioHeaders (which has just three reverse
dependencies) to both name it and to point to the expanded source
directory created for this purposed:
> system.time( res <- xfun::rev_check("AsioHeaders", src="AsioHeaders") )
## ... earlier output omitted for brevity here ...
user system elapsed
35.732 3.333 149.683
> res
httpgd ipaddress websocket
0 0 0
> and about a good two minutes later I would get the timing result and
the summary in variable res. As I checked the current CRAN version, the check was as
expected free of concerns or issues.
To support this, r2u did indeed go off and install about sixty seven binary packages (and the total includes all binary dependencies fully resolved) delivering on the ‘just works’ promise by the r2u documentation.
As another check, I did the same for RcppAnnoy which has seven reverse dependencies and needed about two hundred CRAN packages to be installed. The full test took just over four minutes with the timing function reporting some nice gains from parallelisation as total user compute time was on the order of just under eight minutes. Again, test results were clean and free of worries as expected:
> system.time( res <- xfun::rev_check("RcppAnnoy", src="RcppAnnoy") )
## ... earlier output omitted for brevity here ...
user system elapsed
471.765 378.220 266.855
> res
bbknnR bigANNOY blocking scDHA Seurat uwot VectrixDB
0 0 0 0 0 0 0
> Overall this was a rather useful quick excursion as it demonstrates that - existing functions can be used to orchestrate a reverse dependency check - with ‘reasonable’ dependency scale we can do this on a single machine quite easily taking advantage of parallel computing on multi-core machines - using r2u gives us fast, easy, reliable package installation making testing of packages we might not otherwise use or know a breeze - doing this in an ephemeral Docker container facilitates easy build-up of required resources and leaves no side effects behind which might affect our normal development environment
This post by Dirk Eddelbuettel originated on his Thinking inside the box blog. If you like this or other open-source work I do, you can now sponsor me at GitHub.