???? Managing dependencies in packages

[This article was first published on Iegor Rudnytskyi, PhD, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Managing usual dependencies of a package is clearly covered in R packages by Hadley Wickham. Typically, that would be the end of a tutorial or a post. However, teaching recently how to develop a package, I encountered a couple of super interesting and non-trivial questions that would not have a conventional solution. I guess this post would be a perfect place to share my thoughts on that meter, as well as a nice excuse to restart blogging.

Non-CRAN packages

When developing the package, the standard place to list dependencies (i.e., external packages that your package needs) is Imports: in DESCRIPTION. Full stop here. These packages are required to be installed so that your package works. And they will be installed automatically when installing your package via install.packages() (see default behavior of dependencies argument). However, packages in Imports: field are supposed to be published on CRAN. That could be an issue if your package uses functionality from packages that are not (yet) published on CRAN. This is the exact question I was asked by one of my students: where do I specify non-CRAN dependencies?

I was sure that there exists a common workflow to do it. After a minute of extensive research, I found out that CRAN policy explains it quite vaguely. Further, there were three Stackoverflow questions about it (see below in References). The answer that I found was quite satisfactory: Dirk Eddelbuettel proposes to list the package in Sugests: and specify the additional repository in special free-form filed Additional_repositories:. He also suggests using drat package to create CRAN-like R packages repository, which from my view is a bit overkill. So my solution would be to list the name of the package in Suggests: and mention the link to its GitHub repo (almost surely the source is stored on GitHub) in Additional_repositories:.

That would be the nice end of the story but how would you let know the end-user that you need this package to be pre-installed? The workaround I found is to rise a message from the function, where this dependence is used and ask the user to install it, for example:

my_function <- function() {
    if (!("nonCRANpkg" %in% rownames(installed.packages()))) {
        message("Please install package nonCRANpkg.")
    }
}

The problem is that the user should come back to the installation process at the point when they use my_function() . In addition, it probably affects the expected output of the function or even worse if the function is internal one and not exported into the namespace. That is why, from my personal view, the installation of all dependencies should be tackled way before the first call of my_function(). And here the function .onAttach() comes in handy. This function allows displaying messages when the package is loading. We simply need to inform the user that they need to install the dependence before using our package (mind the difference between message() and packageStartupMessage()):

.onAttach <- function(libname, pkgname) {
    
    if (!("nonCRANpkg" %in% rownames(installed.packages()))) {
        packageStartupMessage(
            paste0(
                "Please install `nonCRANpkg` by",
                " `devtools::install_github('username/nonCRANpkg')`"
            )
        )
    }
    
}

To summarize in a nutshell: mention the package name in the field Suggests: of DESCRIPTION, link to its repo in Additional_repositories: (in the same file), and write a simple .onAttach function.

Shiny demo app

It is always a cool idea to compliment the package with a Shiny app so that a user can have an interactive interface to play around with the functionality of the package. We typically store scripts of those demo apps in inst\shiny-examples\name_of_app and add a function runDemo() to run them (see a wonderful post by Dean Attali in the references for details). Those apps are very likely to have their own dependencies, as well as they definitely require shiny namespace to be loaded. That is why we see all these library() calls at the beginning of Shiny apps’ scripts.

Obviously, (1) we want to ensure that the user has all required packages installed, and (2) avoid using library() in package’s scripts. The solution is very simple – specify all Shiny app dependencies in Imports: and use the usual :: to access functions from respective namespaces.

To sum up all the previous take-home points, I created a dummypkg for illustration, which is stored at GitHub repo irudnyts\dummypkg. It contains a barebone example of non-CRAN dependencies, as well as a tiny Shiny app with dependencies. Managing those dependencies is super important since we do not want our packages to like like jack-in-the-boxes.

Many thanks go to Ana Lucy Bejarano Montalvo who inspired me by asking those questions.

References

  1. R packages by Hadley Wickham
  2. Include non-CRAN package in CRAN package
  3. R package building: How to import a function from a package not on CRAN
  4. How to make R package recommend a package hosted on GitHub?
  5. R package dependencies not installed from Additional_repositories
  6. Supplementing your R package with a Shiny app
  7. Lego PNG image with transparent background

To leave a comment for the author, please follow the link and comment on their blog: Iegor Rudnytskyi, PhD.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)