Skip to contents

Introduction

This vignette contains code and annotations to develop a package, to prepare for package release, and to set up for a new version. It assumes the package has been set up as described in the vignette Package setup: utils::vignette("pkg_setup", package = "develcoder").

Adding functions

Set up

usethis::use_r("<func>")
devtools::document() # also runs devtools::load_all()
?<func>() # view help-page of the function

Do:

  • Manually update the NEWS file:
    ### Added functions
    - `<func>()` to ...
  • If the added function relies on other packages, update the dependencies given in the DESCRIPTION file: see section Update dependencies.
  • When adding a new function argument to an existing function, place the new argument after existing arguments and use a default value that matches the old behaviour: then code written for the old version will work the same as before the change even if it relies on positional matching, such that adding the new argument is a non-breaking change.
  • When renaming a function, also change its name in the _pkgdown.yml file if that file is used to create a custom index for the website: all exported functions should be included in the function index and all functions included in the index should be exported functions, otherwise pkgdown::build_reference() will throw an error (topic missing from index or must be a known topic name or alias, respectively) and the package website cannot be build.

Add documentation

Function files with file extension .R can be annotated using the ‘roxygen’ format. Then using devtools::document(), which itself calls roxygen2::roxygenise(), creates documentation files with file extension .Rd.

The vignettes of roxygen2 describe many aspects of package documentation, among others vignettes with a general introduction to roxygen2 and a general introduction to function documentation, see utils::browseVignettes(package = "roxygen2").

Section titles should be capitalized and not end in a period, use tools::toTitleCase(). It is best to avoid all markup in titles because they need to be automatically processed for various hypertext search systems. It is possible to create subheadings in roxygen blocks using ## and ###, i.e., usual Markdown syntax.

#' Title (Should be Capitalized and not End in a Period)
#'
#' Description
#'
#' @param x Vector of names to test.
#' @param allow_NA `TRUE` or `FALSE`: allow [NA]s of the correct type in `x`?
#' @param x,y Separate arguments by a comma without a space to create a single
#' description for multiple arguments.
#'
#' @details
#'
#' @returns
#' `TRUE` or `FALSE`, returned [invisibly][invisible].
#'
#' @section Side effects:
#' The directory indicated by the returned path is
#' [created][progutils::create_dir()] if it does not yet exist.
#'
#' @section Notes:
#' Some notes.
#'
#' @section Programming notes:
#' Some programming notes. Create a GitHub issue instead of To do / Wishlist
#' sections.
#'
#' @seealso
#' Section `Details` of [make.names()] and the [\R FAQ about valid names](
#' https://CRAN.R-project.org/doc/manuals/R-FAQ.html#What-are-valid-names_003f)
#' on the syntactical validity of names.
#'
#' @family collections of checks on type and length
#'
#' @examples
#' all_names(x = c("a", "b2a")) # TRUE
#'
#' @export
func_name <- function(x, allow_NA = TRUE) {
  stopifnot(checkinput::is_logical(allow_NA))
  <...>
}

Inherit documentation

It is possible to inherit sections of the documentation from other functions, see the introduction to function documentation from package roxygen2. Inherited sections are silently ignored if they are also defined in the file itself.

Unfortunately, inheriting section @seealso does not work if @family is also used. Instead, hardcode the family members in the @seealso section of one function and inherit that section in other functions using
#' @inherit fun seealso.

#' @inherit is_number return
#' @inherit is_number details
#' @inheritSection is_number Programming notes
#' @inheritSection is_number @details

Inherit parameters

It is possible to inherit the description of parameters from other functions. Since roxygen2 8.0.0, it is possible to specify which arguments to inherit (i.e., to filter arguments). For arguments that share a single description, filtering arguments works since roxygen2 8.1.1.

# inherit the description for all arguments from a function in the current
# package, ignoring arguments that do not occur in the target function
#' @inheritParams diagnose_test_files pattern

# inherit the description for argument 'fields' from function
# 'installed.packages' in package 'utils'
#' @inheritParams utils::installed.packages fields

Suppress documentation

Use the tag @keywords internal instead of @export to create an .Rd file (and thus a help page) while omitting the function from the function index. This is recommended for internal functions that are not supposed to be called by the user. Use the tag @noRd instead of @export to not generate an .Rd file at all (and thus omit the function from the index). In both cases, the function is not exported and thus should be called using the triple-colon operator, see help(":::"): <pkg>:::<func>().

Add examples

Examples are meant to show users how to use a function. Use devtools::run_examples() to run all examples in a package.

Examples rendered on a website created with pkgdown will display the output underneath the code. However, help files accessed through help(<func>) will not, such that it might be useful to include a short comment regarding what feature of the output is notable.

To not run or not display code in examples, you can use the dontrun and dontshow tags, see help("example", package = "utils"), the section Examples from the Writing R Extensions manual and the section about Examples in the ‘R packages’ book.

Examples that create output files should write them to a temporary directory that is cleaned up afterwards. See the section Usage in Practice in help("create_tempdir", package = "progutils") and section Add tests below.

Add tests

Background

For background on testing:

The code below shows how to run tests with tinytest and testthat.

tinytest::test_all() # Test all tests from tinytest
devtools::test() # Test all tests from testthat

# Run a specific test file, selecting the file by name
tinytest::run_test_file(fs::path_wd("inst", "tinytest", "test_funcname.R"))
testthat::test_file(fs::path_wd("tests", "testthat", "test-funcname.R"))

Expectations

tinytest::expect_silent(...) allows messages to be emitted (and thus tests that no errors or warnings are emitted), whereas testthat::expect_silent() does not allow messages to be emitted.

expect_silent(expect_true(func_name(x = x, arg = arg)))
expect_warning(
  expect_equal(func_name(x = "a", arg = arg), 3),
  pattern = "...", strict = TRUE, fixed = TRUE)
expect_error(func_name(x = "a", arg = arg),
             pattern = "is_number(x) is not TRUE", fixed = TRUE)

Interactive testing

To interactively run tests, attach the package that you want to test before running the tests:

  • use devtools::load_all(<pkg>) (with unloadNamespace("develcoder") to detach it)
  • or use attach(loadNamespace("<pkg>", versionCheck = list(op = ">=", version = package_version("1.2.3")))) to specify which version to load, with detach("<pkg>") to detach it.

pkgload::is_dev_package(<pkg>) shows if a package was loaded through devtools::load_all(<pkg>) and devtools::loaded_packages() shows the path from which packages were loaded, which can also be used to identify development packages. See the section Loading and attaching packages in the vignette R packages from package checkrpkgs for details on loading packages: utils::vignette("r_pkgs", package = "checkrpkgs").

Cleaning up

Tests that create output files should write them to a temporary directory that is cleaned up afterwards. See the section Usage in Practice in help("create_tempdir", package = "progutils") and the vignette test fixtures from package testthat for more details. In addition, changed options (help("options", package = "base")) and graphical parameters (help("par", package = "graphics")) should be restored to their original state to prevent influencing subsequent code. The following pattern shows how to do so for a single option or parameter, see this entry in the CRAN cookbook for details:

orig_opt <- options(<option> = <value>)
orig_par <- graphics::par(<parameter> = <value>)
<code>
par(orig_par)
options(<option> = orig_opt)

To reset all parameters at once, use orig_par <- par(no.readonly = TRUE). To restore options that are changed inside a function, add on.exit(par(orig_par), add = TRUE) immediately after changing the option.

Sorting order

Sorting order depends on the used locale, see ‘Details’ in help("sort"). For example, sorting using the C locale gives the order c("-", "!", "_", "|", "=", "1", "a", "A"), whereas sorting using UTF-8 gives c("!", "-", "1", "=", "A", "_", "a", "|"). Notably, the latter places uppercase characters before instead of after lowercase characters and places some symbols between the uppercase and lowercase characters, which also changes the sorting order from c("-", "_") to c("_", "-").

The used locale can be set through Sys.getlocale() but is also influenced by the used operating system and other settings in R, see help("locales") and the Programming Note about locales in checkinput::all_names() for more references.

Testing file paths

Testing file paths requires some thought because the file separator depends on the operating system (.Platform$file.sep gives the file separator on the running platform) and the backslash is used as escape character in R such that it needs to be escaped itself by doubling them. Thus, a check on the presence of two successive slashes or backslashes in string string would use grepl(pattern = "//", x = string, fixed = TRUE) and grepl(pattern = "\\\\", x = string, fixed = TRUE), respectively. The message to point out their presence would be written as message("Successive '/' or '\\'") which would be printed as Successive '/' or '\'.

One approach to prevent problems with getting the correct type and number of slashes to compare with the path recorded in a message is to check only for fixed parts of the message (e.g., "Successive"), possibly followed by a check like tinytest::expect_true(fs::dir_exists(string)).

Update dependencies

First, consider if you really need a new dependency and which package that should be. Some additional effort in coding or in choosing dependencies might allow using dependencies that themselves have fewer dependencies, making a project more stable over time. See the chapters Dependencies: Mindset and Background and Dependencies: In Practice from the ‘R packages’ book for background and practical advice.

Check which packages are used

Package pkgstats can be used to check which packages, and which functions from those packages, are used in your package. This requires the system libraries ctags-universal and GNU global, which should be automatically installed with pkgstats on Windows and MacOS. Use pkgstats::ctags_test(quiet = FALSE, noerror = TRUE) to check if these are indeed installed and pkgstats::ctags_install() to install them if not.

if(requireNamespace("pkgstats", quietly = TRUE)) {
  overview_pkgstats <- pkgstats::pkgstats()
  overview_pkgstats$external_calls
}

Section Which packages are used in vignette R packages from package checkrpkgs indicates how to manually look for packages that are mentioned in comments: utils::vignette("r_pkgs", package = "checkrpkgs").

On reverse dependencies, see section Check reverse dependencies.

Importing packages or functions

The standard way to use functions from other packages is to import the package: use usethis::use_package(package = "<pkg>", type = "Imports", min_version = "<version>") to put the package in the Imports field of the DESCRIPTION file. It is possible to specify minimum package versions through argument min_version, see the next section.

usethis::use_package(package = "checkinput", type = "Imports", min_version = "1.0.0")

If the package is only used in examples or vignettes, or is considered an optional addition, you can use type = "Suggests" to place it in the Suggests field of the DESCRIPTION file. Code that uses that package should be guarded through if(requireNamespace(<pkg>, quietly = TRUE)) {<code>}, such that the code is only executed if the package is installed. This is not necessary for the base R packages that are always installed with R but is necessary for the recommended packages that are only automatically installed in binary installations, and for all other packages.

  • The ‘base’ R packages are base, compiler, datasets, graphics, grDevices, grid, methods, parallel, splines, stats, stats4, tcltk, tools, utils. Since R 4.4.0, these names are available through tools::standard_package_names()$base
  • The ‘recommended’ R packages are boot, class, cluster, codetools, foreign, KernSmooth, lattice, MASS, Matrix, mgcv, nlme, nnet, rpart, spatial, survival. Since R 4.4.0, these names are available through tools::standard_package_names()$recommended.

Besides putting the package in the DESCRIPTION file, the package name should be followed by two colons and the function name. For example, to use function all_names(), first import package checkinput as described above and then use checkinput::all_names() in code. Specifying the appropriate package through such notation is not possible if the function is an operator: then you need to also list the function in the NAMESPACE file: use usethis::use_import_from(package = "<pkg>", fun = "<func>") followed by devtools::document() to add a line like #' @importFrom utils ? to the file R/<pkg>-package.R that was created by usethis::use_package_doc(). usethis::use_import_from() does not have a min_version argument, so to specify one you have to use usethis::use_package() as described in the next paragraph.

# The first line is used to specify a minimum version
usethis::use_package(package = "utils", type = "Imports", min_version = "4.0.0")
usethis::use_import_from(package = "utils", fun = "?")
devtools::document()

Specifying minimum versions

Specifying a minimum package version in your DESCRIPTION file when importing packages ensures that used functions are actually present. To use the currently installed package version, use usethis::use_package(package = <pkg>, <...>, min_version = TRUE). However, that probably is too strict because functions are likely also present in older package versions.

To check in which version a used functions was introduced, you can

  • search the NEWS file of the relevant package for the function name to find the NEWS item announcing its introduction.
  • search for the function file on the GitHub-page of the package and look in its history to see when it was created, or use the Blame feature to get a fine-grained overview of when code in that file was changed to see when a function was introduced. Then click on the commit message to get an overview of the files that were changed in that commit and use the Browse files button to browse the repository as it was repository as it was at the time of the commit. Then click the DESCRIPTION file to see which version the package was in.
  • specify different minimum package versions in checks through GitHub actions and iteratively change them to find the minimum version that passes the check.

Since R 4.6.0, read.dcf() recognises lines starting with # as comment lines, making it possible to use comments in the DESCRIPTION file to indicate why a particular package version is needed.

# Show the currently used R version
getRversion() # or: utils::packageVersion(pkg = "base")

# Declare a minimum version for R itself
usethis::use_package(package = "R", type = "Depends", min_version = "4.0.0")

# Declare a dependency on a package: 'use_package()' to declare
# a minimum version and 'use_dev_package()' to specify the
# remote repository to download the package from.
utils::packageVersion("checkinput")
usethis::use_package(package = "checkinput", type = "Imports", min_version = TRUE)
usethis::use_dev_package(package = "checkinput", type = "Imports",
                         remote = "github::JesseAlderliesten/checkinput")
utils::packageVersion("progutils")
usethis::use_package(package = "progutils", type = "Imports", min_version = TRUE)
usethis::use_dev_package(package = "progutils", type = "Imports",
                         remote = "github::JesseAlderliesten/progutils")
devtools::document()

Do:

Adding vignettes

Set up

usethis::use_vignette("my_vignette", title = "Some title")
# Add suggested dependencies on 'knitr' and 'rmarkdown'
usethis::use_package(package = "knitr", type = "Suggests")
usethis::use_package(package = "rmarkdown", type = "Suggests")
devtools::document()
pkgdown::build_article()
utils::browseVignettes(package = basename(getwd()))

If no vignettes are visible after running utils::browseVignettes(package = basename(getwd())), probably devtools::install() was run with the default argument build_vignettes = FALSE, use build_vignettes = TRUE instead:
devtools::install(quick = FALSE, upgrade = FALSE, build_vignettes = TRUE). Run tools::pkgVignettes(package = "<pkg>") for information about the vignettes that R finds.

Use utils::vignette("<vignette_title>") (e.g., utils::vignette("pkg_devel")) to render a vignette in the help pane of RStudio, and tools::Rd2txt("path/to/file.Rd") to do so with an Rd file.

Styling

See the vignette R Markdown and knitr on using R Markdown to style vignettes: utils::vignette("rmarkdown_knitr", package = "develcoder").

Put the next lines in the header of vignettes to get a table of contents:

output:
  rmarkdown::html_vignette:
  toc: true
toc_depth: 3

It is good practice to give the session information at the bottom of a vignette, e.g., using utils::sessionInfo(), sessioninfo::session_info(), or sessioncheck::sessionstate():

## session info
```{r session info, echo = FALSE}
if(requireNamespace("utils", quietly = TRUE)) {
  utils::sessionInfo()
}
```

Linking

  • Link to another section in the same document:
    [<link text>][<Section title>] or [<Section title>].
  • Link to a help page of a package:
    help("<func>", package = "<pkg>") (unfortunately, help("<pkg>::<func>") does not work).
  • Link from a help-page to a vignette:
    The vignette *<vignette title>*: `utils::vignette("<vignette>", package = "<pkg>")`
  • Link to another vignette in the same package:
    [<link text>](<vignette_filename>.html)

There is no official way to link from vignettes to help-pages or vice versa. pkgdown recognises calls like `<func>()` and `<pkg>::<func>()`, such that relevant links for such calls will be created on a package website. However, calls in which arguments are supplied like `<pkg>::<func>(arg = x)` will not be linked, see the documentation for details.

Adding miscellaneous files

Non-standard files or folders should be added in the inst directory of the package to pass R CMD check. Those files and folders will be in the top-level package folder of the installed package.

Preparing for updates

Check package name

Check if the correct package name is used in various files in the package directory. The best way to ensure the correct package name is used is to use dedicated functions to create the package structure and the relevant files, e.g., usethis::create_package(), cffr::cff_write(dependencies = FALSE), usethis::use_news_md(), and usethis::use_pkgdown() instead of copying old packages to start new packages.

devtools::document()
develcoder::pkg_name_correct()

Check tests

Check if there are functions for which no test file was written and run all test files. Use devtools::test() instead of tinytest::test_all() if you used package testthat to create tests.

devtools::document()
develcoder::check_tests()
# list with character(0) for elements 'missing' and 'extraneous' if all is fine
tinytest::test_all() # or devtools::test() if testthat was used to create tests

Automated checks

The next sections provide code to run checks from various packages.

Various packages

BiocCheck checks adherence to the Bioconductor Packages guide, which is required before submission to BioConductor. See the overview of checks in its vignette. Several of the checks are probably too strict if you do not intend to submit the package to BioConductor.

checktor provides various checks supplementing R CMD check.

devtools::load_all()

# This leaves a '<pkg>.BiocCheck' folder in the package repository:
BiocCheck::BiocCheck(package = getwd(), callr = requireNamespace("callr"))

# Return is invisible and silent if all is well
codetools::checkUsagePackage(
  pack = basename(getwd()), all = TRUE, suppressParamAssigns = TRUE)
checktor::checktor()
checktor::lab_description_function_quotes()
checktor::lab_format_names()
checktor::lab_title_starts_with_article()

# R cmd check also includes this check but it is faster to perform it beforehand
if(requireNamespace("tools", quietly = TRUE) &&
   utils::packageVersion("tools") >= "4.5.0") {
    tools::check_package_urls(dir = getwd(), verbose = TRUE)
}

devtools (local)

devtools::check() uses the preferred approach of first building the package before checking it. rcmdcheck::rcmdcheck() does not use that approach but can compare output from different checks.

See the CRAN cookbook and the appendix R CMD check from the R packages book on how to proceed if checks fail.

The call to devtools::check() used below is more strict than the default, following suggestions from the Writing R extensions manual and from R packages to set arguments and environment values:

  • Using TRUE for environment variable _R_CHECK_SUGGESTS_ONLY_ to run code while only using dependencies listed in the Depends, Imports and Suggests fields of the DESCRIPTION file.

  • Using TRUE for environment variable _R_CHECK_DEPENDS_ONLY_ to run code while only using dependencies listed in the Depends and Imports fields of the DESCRIPTION file. This catches cases where a package that is listed in the Suggests field of the DESCRIPTION file is used in a vignette without being run conditionally through if(requireNamespace(<pkg>)) {<code>}.

  • Using manual = TRUE to build and check the manual.

  • Using remote = TRUE (and thus incoming = TRUE) to run additional checks that are used by CRAN for new package submissions.

    These checks flag dependencies that are on GitHub, leading to NOTEs like
    Strong dependencies not in the CRAN or BioC software repositories: <pkg>,
    Suggests or Enhances not in mainstream repositories: <pkg>, and
    Unknown, possibly misspelled, fields in DESCRIPTION: 'Remotes'.
    You can ignore these NOTEs if you do not intend to submit to CRAN.

    These checks also identify problems with URLs that are used in the documentation. URLs:

    • should be accessible (e.g., point to public GitHub repositories, not to private repositories)
    • should be direct links, not redirects
    • should use the protocol https:// instead of http://
    • should use the canonical forms expected by CRAN, not the ‘simple’ forms displayed in the webbrowser:
      • https://CRAN.R-project.org/package=<pkg> for packages
      • https://CRAN.R-project.org/manuals.html to refer to manuals in general and URLs of the form https://CRAN.R-project.org/doc/manuals/r-release/R-exts.html to refer to a specific manual
      • https://CRAN.R-project.org/web/packages/policies.html to refer to the CRAN policies (R CMD check will still complain about this format).
devtools::document()
devtools::release_checks()
if(utils::packageVersion("devtools") >= "2.5.0") {
  # check for missing `\value` and `\examples` fields in `Rd` files
  devtools::check_doc_fields()
}
devtools::check(manual = TRUE, remote = TRUE)
devtools::check(manual = TRUE, remote = TRUE,
                env_vars = c("_R_CHECK_DEPENDS_ONLY_" = TRUE,
                             "_R_CHECK_SUGGESTS_ONLY_" = TRUE))

goodpractice

Runs checks of its own and checks from various other packages. I exclude checks that I do not find useful and checks that duplicate checks that already have been run above. Use goodpractice::all_check_groups() to see all groups and goodpractice::checks_by_group(<groups>) to show all checks in groups.

goodpractice_all_checks <- goodpractice::all_checks()
checks_to_exclude <- c(
  "complexity_function_length", # I write complex functions
  "covr", # time-consuming and wants 100% coverage
  "cyclocomp", # I write complex functions
  "lintr_if_not_else_linter", # I use the shortest branch first
  "lintr_line_length_linter",
  # The lintr issue `Use == instead of %in% for scalar comparison` should be
  # ignored for 'x %in% y' if 'x' or 'y' might contain 'NA' that should be
  # removed: 'x %in% NA' and 'NA %in% y' return 'FALSE', whereas 'x == NA' and
  # 'NA == y' return 'NA'.
  "lintr_outer_negation_linter", # changes handling of NAs
  "lintr_scalar_in_linter",
  "tidyverse_brace_linter",
  "tidyverse_function_left_parentheses_linter",
  "tidyverse_indentation_linter",
  "tidyverse_line_length_linter",
  "tidyverse_object_name_linter",
  "tidyverse_spaces_left_parentheses_linter",
  # does not find test files in inst/tinytest,
  # use develcoder::check_test_files() instead
  "tidyverse_test_file_names",
  "tidyverse_whitespace_linter",
  # Checks similar to those from 'rcmdcheck' and 'urlchecker' are
  # also used by devtools::check() that already has been run above
  goodpractice::checks_by_group(c("rcmdcheck", "urlchecker"))
)

# Select checks
goodpractice_my_checks <- progutils::not_in(
  goodpractice_all_checks,
  checks_to_exclude
)

goodpractice_res <- goodpractice::goodpractice(
  path = ".", checks = goodpractice_my_checks)
goodpractice_res_failed <- goodpractice::failed_checks(goodpractice_res)
goodpractice_res_failed

# Can also show output of selected checks through
# goodpractice_res$checks$<check_name>
goodpractice_res

Other checks

devtools::check_win_release() and devtools::check_mac_release() are similar to devtools::check() and can be used to check packages on Windows or MacOS, respectively. However, these checks might malfunction if your package has dependencies that are not on CRAN or BioConductor. Such dependencies should have been flagged above by devtools::check(manual = TRUE, remote = TRUE) as not in the CRAN or BioC software repositories or as not in mainstream repositories.

These checks email with a link to the webpage showing the check results if the check is done, the webpage might display 504 Gateway Time-out if the checks have not finished yet.

devtools::check_win_release()
devtools::check_mac_release()

utils

Spell checking. This requires system libraries like Aspell.

Update package-wide documentation

DESCRIPTION

Run desc::desc_normalize() to normalize the DESCRIPTION file.

To show the DESCRIPTION file, use desc::desc() or utils::packageDescription(pkg = basename(getwd())). To only show the dependencies, use desc::desc_get_deps() or utils::packageDescription(pkg = basename(getwd()), fields = c("Depends", "Imports", "Suggests", "Enhances")).

Use usethis::use_version(which = NULL, push = FALSE) to increment the package version in the DESCRIPTION file and create a new NEWS header, interactively choosing which level of the package version to increment, without automatically committing the change (i.e., choose a negative option when presented with the question Is it ok to commit them?), to do so manually later on to simultaneously commit the updated CITATION, License, README and NEWS files.

See package lifecycle on how to add badges to packages and functions to indicate their life cycle and its vignette Lifecycle stages, chapter Lifecycle from the ‘R packages’ book, and the Semantic Versioning Specification on versioning.

desc::desc_normalize()
usethis::use_version(which = NULL, push = FALSE)

CITATION and License

Use the DESCRIPTION file to create a CITATION.cff citation file, or update such file to reflect the new package version. Add a license if none is present, or update it to have the correct year in it. usethis provides utility functions to add licenses, see help("licenses", package = "usethis"). The code below adds or updates a MIT license as an example.

cffr::cff_write(dependencies = FALSE) # Create or update a citation file
usethis::use_mit_license()

README

Install the development version of the package to ensure the updated version number without Git-commit ID will be included in the citation format in the README, using quick = FALSE and build_vignettes = TRUE to ensure the year will be displayed:

devtools::install(quick = FALSE, upgrade = FALSE, build_vignettes = TRUE)

Then Knit the README.Rmd to produce a README.md file. The citation in the README should then display the updated version number.

Notes:

  • The version badge at the top will still display the old version number, but that will change to the updated number when pushed to GitHub.
  • If the requirement that README.Rmd and README.md are staged at the same time was accidentally introduced, delete the the hidden file .git/hooks/pre-commit from the R-project folder to remove that requirement, see the section ‘DESCRIPTION’ in help("use_readme_rmd", package = "usethis").

NEWS

Restyle the NEWS file and replace the heading # <pkg> devel with the version number, e.g., # develcoder 1.0.0.

Check reverse dependencies

Acknowledgement

This section reproduces a contribution by Ivan Krylov to the R-pkg-devel mailing list of 26 May 2026.

To check if packages that use your package still pass their checks if you made breaking changes, install the new version of your package, run tools::package_dependencies(reverse = TRUE, which = "most", recursive = "strong") to get the list of reverse dependencies, then use utils::download.packages(...) to obtain their latest tarballs and finally run tools::check_packages_in_dir() to check them.

tools::package_dependencies() and the related tools::dependsOnPkgs() only take packages from CRAN into account. devtools::revdep(pkg = <your pkg>, bioconductor = TRUE) also takes packages from BioConductor into account. To take other non-CRAN packages into account, use revdepcheck::revdep_check() or xfun::rev_check() (which has a GitHub Action: crandalf). If you find reverse dependencies that broke because of your changes, use devtools::revdep_maintainers(pkg = <your pkg>) to identify their maintainers and then manually notify them.

Locally test update

devtools::document()
.libPaths() # Check if output of .libPaths() is correct.
# If the next line leads to the error 'lazy-load database
# '.../R/win-library/<X.Y>/<pkg>/R/<pkg>.rdb' is corrupt',
# you should restart R and again run the next line.
devtools::install(quick = FALSE, upgrade = FALSE, build_vignettes = TRUE)

# Load the package and view the help files as usual outside devtools:
library(basename(getwd()), character.only = TRUE)
utils::browseVignettes(package = basename(getwd()))
?progutils::reorder_levels

Updating

Use GitHub Actions

Set up workflow_dispatch to manually trigger ‘GitHub Actions’ (‘GHA’), see the section Automate checks in the vignette Package setup: utils::vignette("pkg_setup", package = "develcoder").

To check if a reverse dependency (i.e., a package that depends on a package you changed) that you created is still working fine: go to the Actions tab of the reverse dependency, select the action you want to trigger (e.g., check-standard.yaml), and use the Run workflow button shown at the top of the overview with workflow runs to run the GitHub Action. You can select which branch it should run on but need to trigger it once manually on the main branch to be able to trigger it manually on other branches.

Scheduled jobs that failed can be rerun through the button Re-run jobs > Re-run failed jobs > Re-run jobs. If the GitHub actions page (e.g., https://github.com/JesseAlderliesten/develcoder/actions) indicates the package passes R CMD check whereas the badge on the README indicates it fails, make sure R CMD check considers the main branch (i.e., if the devel branch passes but the main branch fails, the badge will have failing): use the Run workflow button to run the GitHub Action on the main branch, see the previous paragraph.

If the package declares a dependency on a minimum R version, it is useful to specify the minimum declared R version to run in addition to the ones that are by default used in the template: for example, add - {os: ubuntu-latest, r: '4.1.0'} to section matrix: config: to also run the workflow with R 4.1.0 if your package declares R 4.1.0 as minimum version.

GHA: documentation and help

For more background on ‘GitHub Actions’ (‘GHA’), including an explanation of the syntax used in .yaml files, see the section about GitHub Actions in the ‘R packages’ book, the GitHub reference and How-to, and the section Automate checks in the vignette Package setup: utils::vignette("pkg_setup", package = "develcoder"), and the overview of GitHub Actions and example Workflows in the documentation of the actions package.

For help debugging build failures, see the Appendix R CMD check in the ‘R packages’ book and the section Where to find help in the documentation of the actions package.

pkgdown website

To automatically update the package website, one can use a GitHub Action workflow (e.g., pkgdown.yaml) that updates the website after a pull request or push. To set this up, run usethis::use_pkgdown_github_pages().

To manually update the pkgdown website, use pkgdown::build_site() and open the file docs/index.html in a web browser to preview the website, or look at the files that constitute your package’s website in the local docs directory.

See the documentation about package pkgdown and chapter Website from the ‘R packages’ book for further details.

Styling

To customise the order in which the articles are listed, specify their order in the _pkgdown.yml file: run tools::pkgVignettes(package = "<pkg>")$names to get their names. See the pkgdown documentation, the _pkgdown.yml template from develcoder, and the non-alphabetical order of the articles in the checkrpkgs package for examples.

To create a custom function index (e.g., a thematic index instead of the default alphabetically ordered one), include a section reference: in the _pkgdown.yml file with the desired headings and topics: see the documentation and an example from pkgdown and the _pkgdown.yml file from the progutils package. Then build the reference with pkgdown::build_reference() and view it locally by opening the file "<pkg>\docs\index.html" and browse to ‘Reference’ to see the package index.

Committing

Commit the changes to the CITATION, License, README and NEWS files with the commit message Bump to version x.y.z, to make it easier to find changes back later on. Pull and Push.

Merge devel-branch with main

To merge the devel branch with main (i.e., to incorporate the changes you made to the devel branch in the main branch), go the the devel branch on the GitHub website of the package. Then use <Contribute> > Open Pull Request to do a pull request. Copy the updated section of the NEWS file in the DESCRIPTION field to describe the changes, and put the package name with the updated version number in the title of the pull request. Use the button Create pull request.

If the message No conflicts with base branch: Merging can be performed automatically, is displayed, you can use the green Merge pull request button, or change to Squash and merge.

Otherwise, the message This branch has conflicts that must be resolved will be displayed: follow the instructions to resolve the conflicts. Then push the button Commit merge. Then you should see No conflicts with base branch and you can proceed as described in the previous paragraph.

After a successful merge, you will see a message that you can delete the devel-branch, which you can do. To do so later, go to Pull requests, select the Closed tab, and scroll down to the button Delete branch.

Overwrite devel-branch after merge

In RStudio, open the relevant project to check there are no commits left. Then you can move to the main branch and Pull to get all updates you just committed to the main branch. Then click the New branch button in RStudio (besides the Switch branch icon indicating which branch, e.g., main or devel, you are using), use devel as branch name and click Create. You will be notified that devel already exists and asked if you want to overwrite it. If you have just merged devel into main, you can choose to overwrite it to have a new devel branch.

Set up a new branch

Instead of overwriting the devel-branch after merging into main, you can also create a completely new branch:

  • At the GitHub page of the package: Branch icon > green New branch button > specify a name for the branch > green Create new branch button.
  • Go back to the GitHub page of the package, and at the green Code button > copy URL to clipboard.
  • In RStudio: File > New project > Version control > Git and paste the copied URL in the Repository URL > Create Project.
  • Then (still in RStudio) in the Git menu change from main to the just-created branch.

Installing the updated package

Then you can delete the old package version from your PC (find.package("<pkg>") gives its location) and install the updated version following your own instructions on the GitHub pages of the relevant packages.

CRAN submission

See the policy documents mentioned in the Guidelines on package development and the chapter Releasing to CRAN from the ‘R packages’ book.

tools::CRAN_check_results(), tools::CRAN_check_details() and tools::CRAN_check_issues() give information about the current check status of CRAN packages. cransays (source code) gives information about the status of packages during package submission to CRAN.

Troubleshooting

  • If running checks lead to the warning Warning in get_engine(options$engine): Unknown language engine '<name>' (must be registered via knit_engines$set()), you probably forgot to indicate the knit-engine when naming a code chunk:

    ```{use-sum}
    1 + 1
    ```

    erroneously tries to use the engine use-sum, whereas the probably intended {r use-sum} correctly indicates that the r engine should be used and the code chunk should be named use-sum:

    ```{r use-sum}
    1 + 1
    ```

    See the output of sort(names(knitr::knit_engines$get())) for the available knit-engines.

  • If running checks lead to the error

    `Failed with error: 'there is no package called '<pkg>''`
    `Quitting from <filename>.Rmd:<line numbers> [<chunk name>]`

    you probably forgot to use library(<pkg>) in a code chunk, used a package that is not declared as dependency in the DESCRIPTION file, or used a package that is declared as suggested dependency (i.e., is in the Suggests field of the DESCRIPTION file) without running that code conditionally on the presence of <pkg> through if(requireNamespace(<pkg>)) {<code>}. The latter is catched by using argument env_vars = c("_R_CHECK_DEPENDS_ONLY_" = TRUE) when running devtools::check().

  • If checks fail because of LaTeX errors when building the manual, you can use manual = FALSE.

  • If running devtools::document() leads to the error @section Could not resolve link to topic ":blank:" in the dependencies or base packages, regex-classes in example code are being interpreted as links. To prevent this, use backticks (`) to format a line as code, or wrap consecutive lines in \code{...}.

  • Errors can arise if package development is not done from a clean R session. Use loadedNamespaces() to check which packages are loaded, or use utils::sessionInfo(), sessioninfo::session_info(), or sessioncheck::sessionstate() for more information about loaded packages.

  • See also section Troubleshooting in the vignette R packages from package checkrpkgs:
    utils::vignette("r_pkgs", package = "checkrpkgs").

Documentation and help

Guidelines on package development

Other useful resources

Official documentation

Books and chapters

Packages

Miscellaneous