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 functionDo:
- 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.ymlfile 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, otherwisepkgdown::build_reference()will throw an error (topic missing from indexormust 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 @detailsInherit 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 fieldsSuppress 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:
- Chapter Unit tests from the Bioconductor Packages guide
- Testing basics and subsequent chapters from the ‘R packages’ book.
- Code Testing and subsequent chapters from ‘The Turing Way handbook to reproducible, ethical and collaborative data science’.
- Section Packages
tests from the CRAN Task View
Package Development and Maintenance.
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>)(withunloadNamespace("develcoder")to detach it) - or use
attach(loadNamespace("<pkg>", versionCheck = list(op = ">=", version = package_version("1.2.3"))))to specify which version to load, withdetach("<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
Blamefeature 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 theBrowse filesbutton 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:
- Add the minimum declared R version to workflows for GitHub Actions.
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: 3It 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():
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 testsAutomated 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
TRUEfor environment variable_R_CHECK_SUGGESTS_ONLY_to run code while only using dependencies listed in theDepends,ImportsandSuggestsfields of the DESCRIPTION file.Using
TRUEfor environment variable_R_CHECK_DEPENDS_ONLY_to run code while only using dependencies listed in theDependsandImportsfields of the DESCRIPTION file. This catches cases where a package that is listed in theSuggestsfield of the DESCRIPTION file is used in a vignette without being run conditionally throughif(requireNamespace(<pkg>)) {<code>}.Using
manual = TRUEto build and check the manual.-
Using
remote = TRUE(and thusincoming = 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 likeStrong dependencies not in the CRAN or BioC software repositories: <pkg>,Suggests or Enhances not in mainstream repositories: <pkg>, andUnknown, possibly misspelled, fields in DESCRIPTION: 'Remotes'.
You can ignore theseNOTEs 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 ofhttp:// - 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.htmlto refer to manuals in general and URLs of the formhttps://CRAN.R-project.org/doc/manuals/r-release/R-exts.htmlto refer to a specific manual -
https://CRAN.R-project.org/web/packages/policies.htmlto refer to the CRAN policies (R CMD checkwill 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_resOther 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.
utils::aspell_package_Rd_files(dir = getwd())
utils::aspell_package_vignettes(dir = getwd())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-commitfrom the R-project folder to remove that requirement, see the section ‘DESCRIPTION’ inhelp("use_readme_rmd", package = "usethis").
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_levelsUpdating
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:
Branchicon > greenNew branchbutton > specify a name for the branch > greenCreate new branchbutton. - Go back to the GitHub page of the package, and at the green
Codebutton >copy URL to clipboard. - In RStudio:
File>New project>Version control>Gitand paste the copied URL in theRepository URL>Create Project. - Then (still in RStudio) in the
Gitmenu change frommainto the just-created branch.
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 therengine should be used and the code chunk should be nameduse-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 theSuggestsfield of the DESCRIPTION file) without running that code conditionally on the presence of<pkg>throughif(requireNamespace(<pkg>)) {<code>}. The latter is catched by using argumentenv_vars = c("_R_CHECK_DEPENDS_ONLY_" = TRUE)when runningdevtools::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 useutils::sessionInfo(),sessioninfo::session_info(), orsessioncheck::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
- The Writing R extensions manual, which is the official guide on extending R
- The CRAN Repository policy (with an overview of changes here) and BioConductor Packages, which are the official guides for packages published on CRAN and BioConductor, respectively.
- The blueprints for software development from EpiverseTrace
- The R development guide from the R Contribution Working Group
- The package development guide, the Statistical Software Peer Review guide, and a section Publish packages in the R universe documentation from rOpenSci
Other useful resources
Official documentation
- The R manuals
- The R developer page
- The R NEWS for the development branch
Books and chapters
- Advanced R by Wickham
- CRAN cookbook by Daly and Altmann
- R in production by Wickham and Masiello
- R packages by Wickham and Bryan
- What they forgot to teach you about R by Bryan, Hester, Pileggi and Aja
- Chapter Building R Packages of Mastering Software Development in R by Peng, Kross, and Anderson
Packages
- The CRAN Task View Package Development and Maintenance is an annotated thematic collection of R packages that assists in developing R packages
- checkrpkgs explains how to get information about to-be-installed and already-installed packages, and how to get the source code of R functions
- devtools
- pkgdown
- roxygen2
- usethis
Miscellaneous
- The slides from the workshop Building tidy tools by Rand and Lyttle
- The package development cheatsheet from Posit
- The Guide for Reproducible Research and GitHub Gardening from ‘The Turing Way handbook to reproducible, ethical and collaborative data science’