install.packages("devtools")
library(devtools)
4 Development
Developing an R package is similar to building a Shiny app–both involve writing code to perform specific outputs. We evaluate the outputs against our expectations and adjust as needed (either to the code or our expectations). While Shiny app development involves saving the code files and re-running the app, R package creation involves additional steps, which this chapter will cover.
If you’d like a refresher on the Shiny and R package chapters, I’ve provided a refresher of these topics below:
If you’re new to package development, having a little background on the devtools
package is helpful. Earlier versions of devtools
contained most of the functions used for package development. In version 2.0, devtools
went under a conscious uncoupling, which means there was a “division of labor” for its core functionality:
The
usethis
package contains the functions for creating package folders and files (.R
files, tests, vignettes, etc.).usethis
is also automatically loaded when you calllibrary(devtools)
.Loading and building your app-package is handled by
pkgload
andpkgbuild
For app-packages destined for CRAN, the
R CMD check
is handled byrcmdcheck
andrevdepcheck
Installing packages from non-CRAN repositories (i.e.,
install_github()
) is handled byremotes
You don’t have to install all of these packages (they will be loaded with devtools
), but the information is essential because it affects the dependencies in your app-package:
‘Package developers who wish to depend on
devtools
features should also pay attention to which package the functionality is coming from and depend on that rather thandevtools
. In most cases, packages should not depend ondevtools
directly.’ - devtools 2.0.0, tidyverse blog
We will cover this topic more in the dependencies chapter..
4.1 Getting started
Before we can start developing, we need to install devtools
:
usethis
is automatically loaded/attached with devtools
.
Loading required package: usethis
Let’s assume we’re continuing with a Shiny project from the previous branch of sap
. Our Shiny project has a DESCRIPTION
file and the code has been placed in the R/
folder, so we’re ready to start developing our app-package with devtools
(the files and folders are below).
See the 03.1_description
branch of sap
.
sap/
├── DESCRIPTION
├── R
│ ├── mod_scatter_display.R
│ ├── mod_var_input.R
│ └── utils.R
├── README.md
├── app.R
├── man
├── movies.RData
├── sap.Rproj
└── www
└── shiny.png
4 directories, 9 files
- 1
-
The
DESCRIPTION
file contains the required fields:Package
,Version
,License
,Description
,Title
,Author
, andMaintainer
.
- 2
-
The
.Rproj
file is still configured to work with a Shiny project (not an R package).
4.1.1 Keyboard shortcuts
I strongly recommend using the keyboard shortcuts for each devtools
function. Shortcuts reduce typing and bundle all those keystrokes into a single action. They also create a kind of ‘muscle memory’ for each step.
In RStudio , new keyboard shortcuts can be created using the shrtcts
package or by clicking on Tools > Modify Keyboard Shortcuts.
In Positron , the devtools
functions covered below are already mapped to the keyboard shortcuts. Follow the instructions found in Positron’s Wiki to add new shortcuts.
4.1.2 Habits
The differences between developing an R package and a Shiny app can be boiled down to a handful of habits, each of which calls a devtools
function:
I’ll use this font style to indicate each habit and accompanying function.
Load all the functions and data in your app-package with
load_all()
Document the app-package functions and data with
document()
Install the app-package with
install()
In the sections below, I’ll cover each devtools
function and my habits around their use when my Shiny app transitions to an app-package.1
4.2 Load
load_all()
is the most common devtools
function used during development. I will load the package when anything changes in the R/
folder.
‘
load_all()
removes friction from the development workflow and eliminates the temptation to use workarounds that often lead to mistakes around namespace and dependency management’ - Benefits ofload_all()
, R Packages, 2ed
Ctrl/Cmd + Shift + L
=
devtools::load_all()
Using load_all()
is similar to calling library(sap)
because it loads the code in R/
along with any data files. load_all()
is also designed for iteration (unlike using source()
), and when it’s successful, the output is a single informative message:
ℹ Loading sap
4.3 Document
The document()
function from devtools
serves two purposes:
Writing the package
NAMESPACE
fileCreates the help files in the
man/
folder
I will document a package whenever I make changes to the roxygen2
syntax or DESCRIPTION
.
Ctrl/Cmd + Shift + D
=
devtools::document()
devtools
is smart enough to recognize the first time document()
is called, so when I initially run it in the Console, it prompts me that the roxygen2
version needs to be set in the DESCRIPTION
file:2
ℹ Updating sap documentation
First time using roxygen2. Upgrading automatically... Setting `RoxygenNote` to "7.3.2"
You may have noticed calling document()
also calls load_all()
, which scans the loaded package contents for special documentation syntax before writing the NAMESPACE
file (we’ll cover the NAMESPACE
in the Dependencies chapter).
ℹ Loading sap Writing NAMESPACE
If we open the NAMESPACE
file, we see it’s empty (and that we shouldn’t edit this file by hand).
The last few output lines warn us to include the Encoding
field in the DESCRIPTION
.
Warning message:
roxygen2 requires Encoding: "UTF-8" ℹ Current encoding is NA
devtools
won’t automatically add Encoding
(like it did with RoxygenNote
above), so we’ll need to add it to the DESCRIPTION
file manually:
Package: sap
Version: 0.0.0.9000
Type: Package
Title: Shiny App-Packages
Description: An R package with a collection of Shiny applications.
Author: John Smith [aut, cre]
Maintainer: John Smith <John.Smith@email.io>
License: GPL-3
RoxygenNote: 7.2.3
Encoding: UTF-8
- 1
-
The
Encoding
value shouldn’t include quotes like the warning message above (i.e.,UTF-8
)
After adding the required fields to the DESCRIPTION
file,3 we’ll document()
the package again and we should see the following:
==> devtools::document(roclets = c('rd', 'collate', 'namespace'))
ℹ Updating sap documentation
ℹ Loading sap Documentation completed
4.4 Install
The final development habit checking if our app-package can be installed locally with devtools::install()
or pak::local_install(upgrade = FALSE)
(depending on the IDE you’re using).
I will install a package after the initial setup, after major changes to the code, documentation, or dependencies, and before committing or sharing.
Ctrl/Cmd + Shift + B
=
devtools::install()
4.4.1 In RStudio
install()
will prompt the following output in the Build pane:
==> R CMD INSTALL --preclean --no-multiarch --with-keep.source sap
* installing to library ‘/path/to/local/install/sap-090c61fc/R-4.2/x86_64-apple-darwin17.0’
* installing *source* package ‘sap’ ...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
No man pages found in package ‘sap’
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path * DONE (sap)
- 1
-
We saw both of these
R CMD INSTALL
settings in thesap.Rproj
file from the previous chapter
- 2
- Full file path for installation
- 3
-
install()
attempts to install the package from the*source*
files and a ‘bundle’ or source tarball file (i.e.,.tar.gz
) - 4
-
No man pages found in package 'sap'
tells us none of the code inR/
has adequately been documented (which we’ll cover in theroxygen2
chapter)
- 5
-
Building the
?help
files
- 6
- Checks to see if package can be loaded from multiple locations and stores
- 7
- Checks to see if package stores the install location
- 8
-
DONE (sap)
meanssap
was successfully installed!
4.4.2 In Positron
In Positron, Ctrl/Cmd + Shift + B will call pak::local_install(upgrade = FALSE)
. This command will be run in a new Terminal window:
* Executing task: /Library/Frameworks/R.framework/Versions/4.4-x86_64/Resources/bin/R -e 'pak::local_install(upgrade = FALSE)'
R version 4.4.0 (2024-04-24) -- "Puppy Cup"
Copyright (C) 2024 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin20
R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.
Natural language support but running in an English locale
R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.
Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.
> pak::local_install(upgrade = FALSE)
✔ Updated metadata database: 7.50 MB in 12 files.
✔ Updating metadata database ... done
→ Will update 1 package.
→ The package (0 B) is cached.
+ sap 0.0.0.9000 → 0.0.0.9000 👷🏾♂️
ℹ No downloads are needed, 1 pkg is cached
✔ Got sap 0.0.0.9000 (source) (96 B)
ℹ Packaging sap 0.0.0.9000
✔ Packaged sap 0.0.0.9000 (18.2s)
ℹ Building sap 0.0.0.9000
✔ Built sap 0.0.0.9000 (3.1s)
✔ Installed sap 0.0.0.9000 (local) (63ms)
✔ 1 pkg + 54 deps: kept 54, upd 1, dld 1 (NA B) [53.2s]
>
> * Terminal will be reused by tasks, press any key to close it.
- 1
-
Name of task and terminal
- 2
- Starts new R session
- 3
-
Calls
pak::local_install(upgrade = FALSE)
- 4
-
pak
will check the package database for updates - 5
-
the
upgrade = FALSE
meanspak
is going to do “the minimum amount of work to give you the latest version(s) ofpkg
” - 6
-
Packaging
sap
- 7
-
Building
sap
- 8
-
Installing
sap
- 9
- Summary (‘kept 54 dependencies, updated 1, downloaded 1 package’)
- 10
- Close Terminal message
What’s the difference?
devtools::install()
focuses on helping package developers by managing all necessary steps for installation, including rebuilding documentation and running tests. install()
also automatically updates outdated dependencies during installation unless dependencies
is set to FALSE
.
pak::local_install()
is designed to use parallel downloads and more efficient dependency resolution, making it faster and more reliable than devtools::install()
in many cases.4 The upgrade = FALSE
installs a package without upgrading its dependencies, keeping the current package versions intact.
Launch app with the shinypak
package:
launch('04_devtools')
4.5 Check?
devtools::check()
performs a series of checks to ensure a package meets the standards set by CRAN. You can consider check()
as a ‘quality control’ function for documentation, NAMESPACE
dependencies, unnecessary or non-standard folders and files, etc. R Packages recommends using check()
often, but I agree with the advice in Mastering Shiny on using check()
with app-packages,
‘I don’t recommend that you [call
devtools::check()
] the first time, the second time, or even the third time you try out the package structure. Instead, I recommend that you get familiar with the basic structure and workflow before you take the next step to make a fully compliant package.’
However, I’ve included an example of running check()
on sap
in the callout box below to demonstrate how it works.
Recap
Creating an app-package involves adopting some new devtools
habits, and the initial contents of sap
hopefully helped demonstrate the purpose of each function.
The following section will cover documenting functions with roxygen2
The topics covered in this section shouldn’t be considered a replacement for the ‘Whole Game’ chapter in R packages (2 ed) or the ‘Workflow’ section of Mastering Shiny (and I highly recommend reading both).↩︎
devtools
relies onroxygen2
for package documentation, so theRoxygenNote
field is required in theDESCRIPTION
.↩︎Always leave an empty final line in the
DESCRIPTION
file.↩︎It stands to reason that installing a package with
pak::local_install()
in Positron would be faster than installing a package usingdevtools::install()
in RStudio, but this has not been my experience.↩︎By convention, files that begin with
.
(dot files) are considered hidden.↩︎