Make namespace exports conditional on presence of another package

I previously posted about idea to split some functions from my package into a separate one. I have gone ahead and done this and am pretty much done with all the heavy lifting. As I perused some slides from rstudio::conf, I started to get second thoughts about my deprecation strategy.

My original plan was to simply run a package startup message for a release or two, notifying users that certain functions have moved to this other package and to download that if needed. But I saw several rstudio::conf presenters mention the problem of old scripts not running due to package changes and knew that any such script, even my own available alongside some of my academic research, would fail for this same reason due to these changes.

I'm not keen on introducing a circular dependency, which is what would happen if I re-exported the new package's functions in the old one. The new package depends on the old one.

Another relatively straightforward strategy is to export functions of the same name that work like this:

my_deprecated_fun <- function(...) {
  if (requireNamespace("newpkg") {
    newpkg::my_deprecated_fun(...)
  } else {
    stop("download newpkg")
  }
}

But this has the downside of introducing clashing namespaces; if the old package is loaded after the new one, the deprecated functions with limited documentation mask the new package's functions.

I thought I had come up with a perfect strategy for dealing with this. I wrote the namespace for these functions manually to check for the presence of the new package. So the namespace entry would look like this:

if (!requireNamespace("newpkg") {
  export(my_deprecated_fun)
}

This sort of works, but (from what I can tell) only at the time of the installation of the old package. So if someone gets the message to install the new package and does that, it doesn't change the fact that the old package is exporting functions of the same name. It wouldn't stop exporting until the old package is updated/re-installed.

I looked into trying to delete these functions from my namespace during load (from the .onLoad function), but that doesn't seem possible — only changing functions, not adding/removing them, appears doable.

From what I can tell, I have these options:

  1. circular dependencies (would CRAN even allow this?)
  2. don't bother trying to catch uses of these outdated functions and hope users figure out where to find them by seeing the package startup message, etc.
  3. export functions of the same name and conditionally error or pass the arguments to the new package's function depending on whether the new package is installed
  4. do my namespace hack with 3. as a fallback after users install the new package but prior to updating the old one.

I'd be interested in whether there is something else I can do or advice on whether I'm trying too hard to avoid breaking old code.