29  Dependency hell

Mastering dependency management

Caution

The contents for section are being revised. Thank you for your patience.

This chapter covers a few packages and tools to help explore, understand and keep track of your app-package dependencies.1 It’s not likely you’ll build an application that only relies on shiny, so it’s important to 1) know the packages and versions required for your application to function, and 2) ensure these packages are included in the correct DESCRIPTION field (or NAMESPACE).

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = '^28')
## # A tibble: 1 × 2
##   branch             last_updated       
##   <chr>              <dttm>             
## 1 28_dependency-hell 2024-02-21 12:36:47

Launch the app:

launch(app = "28_dependency-hell")

Download the app:

get_app(app = "28_dependency-hell")

29.1 Exploring dependencies

The first package we’ll cover is pak, which is, “A Fresh Approach to R Package Installation.pak includes two tools I’ve found to be incredibly helpful for understanding the dependencies in a given package (or a package I’m building): dependency trees and the dependency explainer.

Launch app with the shinypak package:

launch('28_dependency-hell')

29.1.1 Trees

pak::pkg_deps_tree() shows us the dependencies for a particular package. To demonstrate how this function works, we’ll explore the dependencies in three packages:

  1. rlang: “Functions for Base Types and Core R and ‘Tidyverse’ Features”
  2. lifecycle: “Manage the Life Cycle of your Package Functions”, and
  3. vctrs: “Vector Helpers”

Let’s start with the rlang package:

pak::pkg_deps_tree("rlang")
rlang 1.1.1 ✨

Key:  ✨ new

rlang is “a collection of frameworks and APIs for programming with R” and it’s built with only base R packages (that’s why it’s DESCRIPTION file only Imports the utils package):

Imports:
    utils

Now lets look at lifecycle:

pak::pkg_deps_tree(pkg = "lifecycle")
lifecycle 1.0.3 ✨ ⬇ (123.60 kB)               
├─cli 3.6.1 ✨
├─glue 1.6.2 ✨
└─rlang 1.1.1 ✨

lifecycle depends on cli, glue, and rlang.

  • cli: “Helpers for Developing Command Line Interfaces”

  • glue: “Interpreted String Literals

If we look at the DESCRIPTION file for lifecycle, it also imports cli, glue, and rlang (and specifies versions for cli and rlang)

Imports:
    cli (>= 3.4.0),
    glue,
    rlang (>= 1.1.0)

Finally, lets look at the dependencies in the vctrs package. The DESCRIPTION file for vctrs imports cli, glue, lifecycle, and rlang

Imports:
    cli (>= 3.4.0),
    glue,
    lifecycle (>= 1.0.3),
    rlang (>= 1.1.0)

If we check the dependency tree, we see the cli, glue, and rlang are listed twice (once for vctrs, and again for lifecycle):

pak::pkg_deps_tree(pkg = "vctrs")
vctrs 0.6.4 ✨                               
├─cli 3.6.1 ✨
├─glue 1.6.2 ✨
├─lifecycle 1.0.3 ✨ ⬇ (123.60 kB)
│ ├─cli
│ ├─glue
│ └─rlang 1.1.1 ✨
└─rlang

Key:  ✨ new |  ⬇ download

vctrs depends on cli, glue, rlang, and lifecycle (which also depends on cli, glue, and rlang)

29.1.2 Explain

We can show dependency relationships with pak::pkg_deps_explain(). For example,

How does lifecycle depend on rlang?

pak::pkg_deps_explain("lifecycle", "rlang")
lifecycle -> rlang 

How does vctrs depend on rlang?

pak::pkg_deps_explain("vctrs", "rlang")
vctrs -> lifecycle -> rlang                                    
vctrs -> rlang

vctrs directly depends on rlang and lifecycle (which also depends on rlang).

29.1.3 Depends

So far we’ve been including add-on functions to the Imports field in the DESCRIPTION, which ensures the package is installed with our app-package, but not attached to the search list. However, if we include a package in the Depends field, it’s installed and attached.

This is rarely needed, but a great example is the relationship between devtools usethis:

pak::pkg_deps_explain("devtools", "usethis")
devtools -> usethis

In the DECRIPTION file for devtools, usethis is listed with a version number under Depends:

Depends: 
    usethis (>= 2.1.6)

29.1.4 Case study: devtools

The conscious uncoupling of devtools split package development across multiple packages. Let’s see how this works, starting with the commonly used devtools function load_all()

29.1.4.1 pkgload

load_all() is handled by the pkgload package, which “Simulate[s] Package Installation and Attach”.

How does devtools depend on pkgload?

pak::pkg_deps_explain("devtools", "pkgload")
devtools -> pkgload                                            
devtools -> roxygen2 -> pkgload
devtools -> testthat -> pkgload

This relationship shows the three actions that call load_all() during package development:

  1. devtools::load_all() actually calls pkgload::load_all()

  2. devtools::document() and devtools::test() also call pkgload::load_all()

pak::pkg_deps_explain("devtools", "roxygen2")
devtools -> roxygen2
pak::pkg_deps_explain("devtools", "testthat")
devtools -> testthat 

29.2 Tracking dependencies

The following packages will help keep your app-package dependencies managed in the DESCRIPTION file and the code below R/:

29.2.1 attachment

attachment was introduced in the golem chapter, but you don’t have to use the golem framework to take advantage of it’s functions. att_amend_desc() will update the package dependencies in the DESCRIPTION file.

attachment::att_amend_desc()
Saving attachment parameters to yaml config file
Updating moviesApp documentation
 Loading moviesApp
Writing NAMESPACE
Writing NAMESPACE
 Loading moviesApp
[+] 6 package(s) added: cli, tools, fst, ggplot2movies, glue, waldo.

attachment::att_amend_desc() will automatically create a dev/ folder with a YAML configuration file:

dev
└── config_attachment.yaml

1 directory, 1 file

config_attachment.yaml contents:

path.n: NAMESPACE
path.d: DESCRIPTION
dir.r: R
dir.v: vignettes
dir.t: tests
extra.suggests: ~
pkg_ignore: ~
document: yes
normalize: yes
inside_rmd: no
must.exist: yes
check_if_suggests_is_installed: yes

This can be deleted, but if you’re going to continue using attachment it’s worth customizing some of the options for your app-package.

29.2.2 sinew

The sinew package also warrants mentioning because it can help ensure you’re namespacing functions from add-on packages, although it’s not automated like attachment::att_amend_desc(). The primary function in sinew is pretty_namespace().

sinew::pretty_namespace(con = "app.R")

All Shiny app-packages will inherently depend on shiny, so including more dependencies can make developers justifiably uneasy. In this appendix, we’ll explore the package dependencies using the dependency lookup features from the pak package

29.2.3 desc

The desc package provides functions for creating, reading, writing, and manipulating DESCRIPTION files. You can include additional dependencies to your DESCRIPTION using the desc_set_dep() function.

library(desc)
desc_set_dep("glue", "Imports")
desc_get("Imports")
Imports:
    bslib,
    cli,
    glue,
    ggplot2,
    logger,
    rlang,
    sass,
    shiny,
    shinythemes,
    stringr,
    tools

  1. Try to avoid dependency hell.↩︎