Teaser
withr
Due to the CRAN policy
of not writing "anywhere else on the
file system apart from the R session’s temporary directory",
throughout this vignette I use R's temporary directory, often by using
path ← file.path(tempdir(), "my_path")
followed by
withr::with_dir(path, …)
or the like.
I do this because this is a vignette and its codes are run on
CRAN.
In real life, we would skip the temporary directory stuff.
Creating Packages
To create a new package I use:
path <- file.path(tempdir(), "myFirstPackage") packager::create(path, fakemake = "check")
## Warning: `tests/testthat.R` already exists.
The package is built, tested, checked and committed into git:
list.files(path, recursive = FALSE)
## [1] "DESCRIPTION" "LICENSE"
## [3] "Makefile" "Meta"
## [5] "NAMESPACE" "NEWS.md"
## [7] "R" "README.Rmd"
## [9] "README.md" "TODO.md"
## [11] "devel" "devel.R"
## [13] "doc" "inst"
## [15] "log" "make.R"
## [17] "man" "man-roxygen"
## [19] "myFirstPackage.Rcheck" "myFirstPackage_0.1.0.tar.gz"
## [21] "tests" "vignettes"
gert::git_status(repo = path)
## # A tibble: 0 × 3
## # ℹ 3 variables: file <chr>, status <chr>, staged <lgl>
gert::git_log(repo = path)
## # A tibble: 24 × 6
## commit author time files merge message
## * <chr> <chr> <dttm> <int> <lgl> <chr>
## 1 479dc254ece9f9f153c8cd7a4189e… fvafr… 2023-08-16 14:10:03 31 FALSE "Packa…
## 2 36dc81ecff37211aa794c7ae42b36… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 3 5fedc1999e17c2e39e95942e7445a… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 4 437c26c4a83f1d999be5f9c4eb221… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 5 8d5ce9c064241b3dd893eedb662ca… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 6 0b133681934ae6634a89fa03aa383… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 7 219bd9c931e431cb30d8e82216e2f… fvafr… 2023-08-16 14:09:12 2 FALSE "Addin…
## 8 41ce888e165948f4cdea6b0c1a73f… fvafr… 2023-08-16 14:09:11 2 FALSE "Addin…
## 9 2a4ee9bb9627b6364de759ebf83f5… fvafr… 2023-08-16 14:09:11 1 FALSE "Addin…
## 10 e3697d04dabed25db90f967d7ddb1… fvafr… 2023-08-16 14:09:11 1 FALSE "Addin…
## # ℹ 14 more rows
We can look at some of the files
(the directory myFirstPackage.Rcheck
might be of interest):
cat(readLines(file.path(path, "log", "spell.Rout")), sep = "\n")
## WORD FOUND IN
## RStudio README.Rmd:8,9
## README.md:8,9
## gitlab README.Rmd:43
## README.md:43
## Spell check failed, see /tmp/RtmpKAoW6W/myFirstPackage/log/spell.Rout for details.
tail(readLines(file.path(path, "log", "check.Rout")), sep = "\n")
## [1] ""
## [2] "See"
## [3] " ‘/tmp/RtmpKAoW6W/myFirstPackage/myFirstPackage.Rcheck/00check.log’"
## [4] "for details."
## [5] ""
## [6] ""
And we see what is left to do:
cat(readLines(file.path(path, "TODO.md")), sep = "\n")
## - make sure https://gitlab.com/fvafrcu/myFirstPackage exists!
Customizing
We see that the package’s DESCRIPTION is filled with default values.
cat(readLines(file.path(path, "DESCRIPTION")), sep = "\n")
## Package: myFirstPackage
## Title: What it Does (One Line, Title Case)
## Version: 0.1.0
## Authors@R:
## person("Andreas Dominik", "Cullmann", , "fvafrcu@mailbox.org", role = c("aut", "cre"))
## Author: Who wrote it
## Description: More about what it does (maybe more than one line)
## License: BSD_2_clause + file LICENSE
## URL: https://gitlab.com/fvafrcu/myFirstPackage
## Depends:
## R (>= 4.4.0)
## Suggests:
## knitr,
## pkgload,
## rmarkdown,
## rprojroot,
## RUnit,
## testthat,
## tinytest
## VignetteBuilder: knitr
## Encoding: UTF-8
## RoxygenNote: 7.2.3
## Imports:
## fritools (>= 1.3.0)
We could set the package information on the existing package, but we rather create a new one now. So we get rid of our first package
unlink(path, recursive = TRUE) if ("myFirstPackage" %in% .packages()) detach("package:myFirstPackage", unload = TRUE)
and customize the package creation (but we skip the process of testing, building and checking for the sake of CPU time, we just build the docs):
package_title <- "myOtherPackage" path <- file.path(tempdir(), package_title) a <- utils::person(given = "Your", family = "Name", email = "some@whe.re", role = c("aut", "cre")) packager::create(path, author_at_r = a, title = package_title, description = "This is very important.", details = "At least to me.", fakemake = "roxygen2")
## Warning: `tests/testthat.R` already exists.
cat(readLines(file.path(path, "DESCRIPTION")), sep = "\n")
## Package: myOtherPackage
## Title: myOtherPackage
## Version: 0.1.0
## Authors@R:
## person("Your", "Name", , "some@whe.re", role = c("aut", "cre"))
## Author: Who wrote it
## Description: This is very important.
## License: BSD_2_clause + file LICENSE
## URL: https://gitlab.com/fvafrcu/myOtherPackage
## Depends:
## R (>= 4.4.0)
## Suggests:
## knitr,
## pkgload,
## rmarkdown,
## rprojroot,
## RUnit,
## testthat,
## tinytest
## VignetteBuilder: knitr
## Encoding: UTF-8
## RoxygenNote: 7.2.3
## Imports:
## fritools (>= 1.3.0)
The package’s man page is set up accordingly:
pkgload::load_all(path)
help(paste0(package_title, "-package"))
## ℹ Loading myOtherPackage
## myOtherPackage
##
## Description:
##
## This is very important.
##
## Details:
##
## You will find the details in
## 'vignette("An_Introduction_to_myOtherPackage", package =
## "myOtherPackage")'.
I use
adc <- utils::person(given = "Andreas Dominik", family = "Cullmann", email = "fvafrcu@mailbox.org", role = c("aut", "cre")) pop <- as.list(getOption("packager")) pop[["whoami"]] <- adc options(packager = pop)
in one of my startup files to set the author information globally.
Maintaining Packages Using fakemake
Our brand new package myOtherPackage is checked into git already:
gert::git_status(repo = path)
## # A tibble: 0 × 3
## # ℹ 3 variables: file <chr>, status <chr>, staged <lgl>
gert::git_log(repo = path)
## # A tibble: 24 × 6
## commit author time files merge message
## * <chr> <chr> <dttm> <int> <lgl> <chr>
## 1 79d0c8e0e58e14d1a11c3f0aa8b05… fvafr… 2023-08-16 14:10:04 14 FALSE "Packa…
## 2 6eade9762adb902e1c8b75c5932c0… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 3 5367d14957e87e30257476642144a… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 4 a455c62aeeb6b269d84ed9bf98e15… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 5 9a665b5510c95cf02fb2ebd46293c… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 6 efd18104e6a723fdbc029b5ccb38b… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 7 9b32bb123d30b45f6ffb149417c37… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 8 a8224dc4870ba11cd9cdd3e9417d1… fvafr… 2023-08-16 14:10:04 2 FALSE "Addin…
## 9 34d43c0ccbc9819192677901e1f40… fvafr… 2023-08-16 14:10:04 1 FALSE "Addin…
## 10 77cb138deabc3dea86f44eab5e8c6… fvafr… 2023-08-16 14:10:04 1 FALSE "Addin…
## # ℹ 14 more rows
but we have so far only built the documentation from the roxygen
comments:
list.files(file.path(path, "log"))
## [1] "dependencies.Rout" "roxygen2.Rout"
So we get a makelist
and look at its targets and aliases:
ml <- packager::get_package_makelist(is_cran = TRUE) cbind(lapply(ml, function(x) x[["target"]]), lapply(ml, function(x) x[["alias"]]))
## [,1] [,2]
## [1,] "log/roxygen2.Rout" "roxygen2"
## [2,] "log/dependencies.Rout" "dependencies"
## [3,] "log/spell.Rout" "spell"
## [4,] "log/cleanr.Rout" "cleanr"
## [5,] "log/lintr.Rout" "lint"
## [6,] "log/covr.Rout" "covr"
## [7,] "packager::get_pkg_archive_path(absolute = FALSE)" "build"
## [8,] ".log.Rout" ".log"
## [9,] "README.md" "README.md"
## [10,] "log/testthat.Rout" "testthat"
## [11,] "log/tinytest.Rout" "tinytest"
## [12,] "log/vignettes.Rout" "vignettes"
## [13,] "log/check_codetags.Rout" "check_codetags"
## [14,] "log/news_rd.Rout" "news_rd"
## [15,] "log/news.Rout" "news"
## [16,] "log/usage.Rout" "usage"
## [17,] "log/winbuilder.Rout" "winbuilder"
## [18,] "log/check.Rout" "check"
## [19,] "log/install.Rout" "install"
## [20,] "cran-comments.md" "cran_comments"
## [21,] "log/submit.Rout" "submit"
Building the Package
We choose to build the package:
suppressMessages(withr::with_dir(path,
print(fakemake::make("build", ml,
verbose = FALSE))))
## [1] "README.md" "log/check_codetags.Rout"
## [3] "log/cleanr.Rout" "log/covr.Rout"
## [5] "log/lintr.Rout" "log/news.Rout"
## [7] "log/news_rd.Rout" "log/spell.Rout"
## [9] "log/testthat.Rout" "log/tinytest.Rout"
## [11] "log/usage.Rout" "log/vignettes.Rout"
## [13] "myOtherPackage_0.1.0.tar.gz"
We see that now there are untracked files in our package’s log directory and that some files changed.
gert::git_status(repo = path)
## # A tibble: 18 × 3
## file status staged
## <chr> <chr> <lgl>
## 1 .Rbuildignore modified FALSE
## 2 .gitignore modified FALSE
## 3 README.md new FALSE
## 4 inst/NEWS.rd new FALSE
## 5 inst/vignettes_code/ new FALSE
## 6 log/build.Rout new FALSE
## 7 log/check_codetags.Rout new FALSE
## 8 log/cleanr.Rout new FALSE
## 9 log/covr.Rout new FALSE
## 10 log/lintr.Rout new FALSE
## 11 log/news.Rout new FALSE
## 12 log/news_rd.Rout new FALSE
## 13 log/readme.Rout new FALSE
## 14 log/spell.Rout new FALSE
## 15 log/testthat.Rout new FALSE
## 16 log/tinytest.Rout new FALSE
## 17 log/usage.Rout new FALSE
## 18 log/vignettes.Rout new FALSE
packager::git_diff(x = ".Rbuildignore", path = path)
## diff --git a/.Rbuildignore b/.Rbuildignore
## index 8a5d5c8..000c96d 100644
## --- a/.Rbuildignore
## +++ b/.Rbuildignore
## @@ -24,3 +24,5 @@
## ^man-roxygen/stop_on_error\.R$
## ^\.log\.Rout$
## ^log$
## +^doc$
## +^Meta$
After inspecting the change, we commit:
withr::with_dir(path, packager::git_add_commit(path = ".", untracked = TRUE, message = "make build"))
## [1] "63ab4421ecc707af9bb57b21035ad6c570d4dba9"
gert::git_status(repo = path)
## # A tibble: 0 × 3
## # ℹ 3 variables: file <chr>, status <chr>, staged <lgl>
Checking the Package
So now we want the check the package:
suppressMessages(withr::with_dir(path,
print(fakemake::make("check", ml,
verbose = FALSE))))
## [1] "log/cleanr.Rout" "log/covr.Rout"
## [3] "log/lintr.Rout" "log/testthat.Rout"
## [5] "log/tinytest.Rout" "myOtherPackage_0.1.0.tar.gz"
## [7] "log/check.Rout"
We again see new files and changes to old files.
gert::git_status(repo = path)
## # A tibble: 3 × 3
## file status staged
## <chr> <chr> <lgl>
## 1 log/check.Rout new FALSE
## 2 log/lintr.Rout modified FALSE
## 3 log/tinytest.Rout modified FALSE
Note that the RUnit
test files are run while checking the tarball, hence we
see output from RUnit
in our log directory.
We assume that we passed the check:
cat(tail(readLines(file.path(path, "log", "check.Rout")), n = 7), sep = "\n")
## Status: 4 NOTEs
##
## See
## ‘/tmp/RtmpKAoW6W/myOtherPackage/myOtherPackage.Rcheck/00check.log’
## for details.
check_log <- file.path(path, "log", "check.Rout") status <- packager::get_check_status(check_log) RUnit::checkEqualsNumeric(status[["status"]][["errors"]], 0)
## [1] TRUE
and commit again
withr::with_dir(path, packager::git_add_commit(path = ".", untracked = TRUE, message = "make check"))
## [1] "4989ab7d7504fc821f7400e1f468e1b35cf3c75b"
If we choose to rerun the check without touching any files "down the make chain" (i.e. no files that any of our make targets depend on), we see there’s nothing to be done:
system.time(withr::with_dir(path, print(fakemake::make("check", ml, verbose = FALSE))))
## NULL
## user system elapsed
## 1.274 0.000 1.275
This is the big difference between running the check via fakemake
with a set of dependencies (set up with packager
) and
running the check (be it using R CMD check
or rcmdcheck::rcmdcheck
or its wrapper devtools::check
) unconditionally: the latter method rebuilds and checks the whole package every time. This is why I wrote packager
and fakemake
.
Submitting the Package
Now we would like to submit our package to CRAN (which we will not do here, but we want to!).
We try and fail, because this vignette is built in batch mode and there’s a security query:
try(packager::submit(path))
## Warning in packager::submit(path): You have no upstream!
## Ready to submit?Error in utils::menu(qs[rand]) : menu() cannot be used non-interactively
Should you run this code interactively, you will be prompted for the security query
(as you might be used from devtools::release()
).
Best you know how to write R extensions and
the CRAN policies.
Anyway, we might want to tag the current commit and commence developing our package:
packager::git_tag(path = path, message = "A Tag")
## # A tibble: 1 × 3
## name ref commit
## <chr> <chr> <chr>
## 1 0.1.0 refs/tags/0.1.0 1ecf7d9bc596a43e1fe6c42b54fbe0e970de48b5
packager::use_dev_version(path = path)
## Package version bumped from '0.1.0' to '0.1.0.9000'
## [1] "ef798ce94a481f6b156b70cd3aab8b08623542f0"
desc::desc_get("Version", file = path)
## Version
## "0.1.0.9000"
cat(readLines(file.path(path, "NEWS.md")), sep = "\n")
## # myOtherPackage 0.1.0.9000
##
## * FIXME
##
## # myOtherPackage 0.1.0
##
## * Added a `NEWS.md` file to track changes to the package.