Tue, 04 Aug 2026

#058: Reverse Dependencies Made Easy, Fast, Reliable

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-base

where 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.

/code/r4 | permanent link