Keep your Packages Up to Date

I like to keep the packages in my library up to date. At the same time, I would like to know what has changed in a package before I install an update. Furthermore, I do not want to build packages from source. In this article, I share my approach and the associated code.

Author

Sebastian Carl

Published

June 8, 2025

Recently, Dirk Eddelbüttel wrote a post about The Two Cultures of Deploying Statistical Software in which he promotes keeping your R packages up-to-date.

R^4 Post #049: The Two Cultures of Deploying Statistical Software On how software may get installed but rarely updated dirk.eddelbuettel.com/blog/2025/06... #rstats #r2u

[image or embed]

— Dirk Eddelbuettel (@eddelbuettel.com) June 6, 2025 at 3:50 AM

I was pleased to see this because I feel the same way and it reminded me that I wanted to share the code I developed for it. That’s what I’m going to do below.

First, let’s keep it simple. If you want to update your packages, you can simply run update.packages() as advertised by Dirk.

I have several reasons why I do not simply do this:

  1. I don’t want to install packages from source because I don’t want to run into any long compilation processes

  2. I want to know before installation for which packages updates are available and

  3. I want to know what has changed via the associated package NEWS.

update.packages() only helps me to a limited extent. I can see in the console afterwards which packages have been installed and I have - theoretically - the possibility to retrieve the package news via news(). But honestly, who does that? Have you ever heard of news() before today?

So I wrote my own helper. My function update_my_pkgs() lives in my personal R package and offers me the following advantages.

As shown in Figure 1, I can see in an overview

> update_my_pkgs() ℹ There are binary updates for the following 3 packages available: • evaluate: 1.0.3 -> 1.0.4      at cloud.r-project.org     see NEWS or CRAN • ggforce : 0.4.2 -> 0.5.0      at cloud.r-project.org     see NEWS or CRAN • nflfastR: 5.1.0 -> 5.1.0.9000 at nflverse.r-universe.dev see NEWS or RUNIV ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ℹ Install them now? (Y/n)
Figure 1: The overview of packages that can be updated.

Also, only the binary packages are shown by default, so I don’t accidentally start installing from source. This also means that it can take a few days for a CRAN update to appear in the list, because the binaries available there may still belong to the old version.

Before installing updates, you are asked interactively whether the updates should really be installed. If yes, then the installation starts in a background job so that the session is not blocked. This is the point at which I actually call update.packages()!

And if you are a fan of the package manager pak, you also have the option of running the installation via it.

Now enough of the long explanations. Figure 2 shows a live example running the code. The corresponding code is given below.

Figure 2: Demonstration of the function implemented in the following code block
Note

Since I do not publish the code via a package, I do not document arguments or check dependencies. It is therefore important to know that the code only works if the following packages are available on your computer: cli, curl, glue, job, pak, purrr, rlang

update_my_pkgs <- function(
  lib.loc = .libPaths()[1],
  repos = getOption("repos"),
  type = c("binary", "source"),
  with_pak = TRUE,
  ...
) {
  # make sure all required packages are installed
  # it's ok to remove this line for slightly faster performance
  rlang::check_installed(c("cli", "curl", "glue", "job", "pak", "purrr", "rlang"))
  
  type <- rlang::arg_match(type)
  old <- old.packages(lib.loc = lib.loc, repos = repos, type = type, ...)
  behind <- as.data.frame(old)

  # Exit early if no packages are behind
  if (nrow(behind) == 0) {
    cli::cli_alert_success("All {.val {type}} packages are up-to-date!")
    return(invisible(NULL))
  }

  # The following runs only if there are packages behind

  cli::cli_alert_info(
    "There {cli::qty(nrow(behind))}{?is a/are} {type} \\
    {cli::qty(nrow(behind))}update{?s} for the following {cli::no(nrow(behind))} \\
    package{?s} available:"
  )

  # This prints an overview of packages that are behind. The form is
  # {pkg}: {installed version} -> {repo version} at {source url} see {NEWS url}
  # or {repo url}
  cli::cat_bullet(
    format(behind$Package),
    ": ",
    format(behind$Installed),
    " -> ",
    format(behind$ReposVer),
    " at ",
    cli::style_hyperlink(
      format(
        host <- purrr::map_chr(
          behind$Repository,
          ~ curl::curl_parse_url(.x)$host
        )
      ),
      behind$Repository
    ),
    " see ",
    # create hyperlink to news file of the CRAN mirror in r-universe
    # or directly in the r-universe repo
    cli::style_hyperlink(
      "NEWS",
      ifelse(
        runiverse <- grepl("r-universe\\.dev", behind$Repository),
        glue::glue("https://{host}/{behind$Package}/NEWS"),
        glue::glue("https://cran.r-universe.dev/{behind$Package}/NEWS")
      )
    ),
    " or ",
    # create hyperlink to CRAN or to r-universe
    cli::style_hyperlink(
      ifelse(runiverse, "RUNIV", "CRAN"),
      glue::glue("https://{host}/{behind$Package}"),
      glue::glue("https://cran.r-project.org/package={behind$Package}")
    )
  )
  cli::cat_rule()

  # Ask user if they would like to update now and wait for answer
  cli::cli_alert_info("Install {cli::qty(nrow(behind))}{?it/them} now? (Y/n)")
  ans <- readline()

  # If the user would like to update the "behind" packages, we will run
  # the update in a background job
  # The arg `with_pak` decides whether the update will run with the help
  # of the pak package or with base R
  if (tolower(ans) %in% c("", "y", "yes", "yeah", "yep")) {
    job::empty(
      {
        if (isFALSE(with_pak)) {
          update.packages(
            lib.loc = lib.loc,
            repos = repos,
            ask = FALSE,
            oldPkgs = old,
            type = type
          )
        } else if (isTRUE(with_pak)) {
          # pak has no repos argument so we have to set it
          # through options
          options(repos = repos)
          pak::pak(old[, 1])
        }
      },
      import = c("lib.loc", "repos", "old", "type", "with_pak"),
      title = "Updating Your Package(s)..."
    )
  }
  # return behind invisibly
  invisible(behind)
}

Finally, Peter Dalgaard - an R Core developer - provides us with another scenario for when update.packages() should not be used.

#rstats folks: If you have a habit of updating your library after an R upgrade by copying the old one and run update.packages(). --> DON'T DO THAT <-- Especially not with 4.5.0. You will overwrite base packages with ones from a previous version and as these are not on CRAN, things go bad.

— Peter Dalgaard (@pdalgd.bsky.social) April 24, 2025 at 7:29 PM