-
-
Notifications
You must be signed in to change notification settings - Fork 21
/
rstan_create_package.R
308 lines (278 loc) · 11.2 KB
/
rstan_create_package.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# Part of the rstantools package
# Copyright (C) 2018, 2019 Martin Lysy
# Copyright (C) 2019 Trustees of Columbia University
#
# rstantools is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# rstantools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#' Create a new \R package with compiled Stan programs
#'
#' @aliases rstan_package_skeleton
#'
#' @description
#' \if{html}{\figure{stanlogo.png}{options: width="25" alt="https://mc-stan.org/about/logo/"}}
#' The `rstan_create_package()` function helps get you started developing a
#' new \R package that interfaces with Stan via the \pkg{rstan} package. First
#' the basic package structure is set up via [usethis::create_package()].
#' Then several adjustments are made so the package can include Stan programs
#' that can be built into binary versions (i.e., pre-compiled Stan C++ code).
#'
#' The **Details** section below describes the process and the
#' **See Also** section provides links to recommendations for developers
#' and a step-by-step walk-through.
#'
#' As of version `2.0.0` of \pkg{rstantools} the
#' `rstan_package_skeleton()` function is defunct and only
#' `rstan_create_package()` is supported.
#'
#' @export
#' @param path The path to the new package to be created (terminating in the
#' package name).
#' @param fields,rstudio,open Same as [usethis::create_package()]. See
#' the documentation for that function, especially the note in the
#' **Description** section about the side effect of changing the active
#' project.
#' @param stan_files A character vector with paths to `.stan` files to include
#' in the package.
#' @param roxygen Should \pkg{roxygen2} be used for documentation? Defaults to
#' `TRUE`. If so, a file `R/{pkgname}-package.R` is added to the package with
#' roxygen tags for the required import lines. See the **Note** section below
#' for advice specific to the latest versions of \pkg{roxygen2}.
#' @param travis Should a `.travis.yml` file be added to the package directory?
#' This argument is now deprecated. We recommend using GitHub Actions to set
#' up automated testings for your package. See
#' https://github.com/r-lib/actions for useful templates.
#' @template args-license
#' @template args-auto_config
#'
#' @details
#' This function first creates a regular \R package using
#' `usethis::create_package()`, then adds the infrastructure required to compile
#' and export `stanmodel` objects. In the package root directory, the user's
#' Stan source code is located in:
#' \preformatted{
#' inst/
#' |_stan/
#' | |_include/
#' |
#' |_include/
#' }
#' All `.stan` files containing instructions to build a `stanmodel`
#' object must be placed in `inst/stan`. Other `.stan` files go in
#' any `stan/` subdirectory, to be invoked by Stan's `#include`
#' mechanism, e.g.,
#' \preformatted{
#' #include "include/mylib.stan"
#' #include "data/preprocess.stan"
#' }
#' See \pkg{rstanarm} for many examples.
#'
#' The folder `inst/include` is for all user C++ files associated with the
#' Stan programs. In this folder, the only file to directly interact with the
#' Stan C++ library is `stan_meta_header.hpp`; all other `#include`
#' directives must be channeled through here.
#'
#' The final step of the package creation is to invoke
#' [rstan_config()], which creates the following files for
#' interfacing with Stan objects from \R:
#' \itemize{
#' \item `src` contains the `stan_ModelName{.cc/.hpp}` pairs
#' associated with all `ModelName.stan` files in `inst/stan` which
#' define `stanmodel` objects.
#' \item `src/Makevars[.win]` which link to the `StanHeaders` and
#' Boost (`BH`) libraries.
#' \item `R/stanmodels.R` loads the C++ modules containing the
#' `stanmodel` class definitions, and assigns an \R instance of each
#' `stanmodel` object to a `stanmodels` list (with names
#' corresponding to the names of the Stan files).
#' }
#' @template details-auto_config
#' @template details-license
#' @details Authors willing to license their Stan programs of general interest
#' under the GPL are invited to contribute their `.stan` files and
#' supporting \R code to the \pkg{rstanarm} package.
#'
#' @template section-running-stan
#'
#' @note For \pkg{devtools} users, because of changes in the latest versions of
#' \pkg{roxygen2} it may be necessary to run `pkgbuild::compile_dll()`
#' once before `devtools::document()` will work.
#'
#' @seealso
#' * [use_rstan()] for adding Stan functionality to an existing
#' \R package and [rstan_config()] for updating an existing package
#' when its Stan files are changed.
#' * The \pkg{rstanarm} package [repository](https://github.com/stan-dev/rstanarm)
#' on GitHub.
#' @template seealso-vignettes
#' @template seealso-get-help
#'
rstan_create_package <- function(path,
fields = NULL,
rstudio = TRUE,
open = TRUE,
stan_files = character(),
roxygen = TRUE,
travis = FALSE,
license = TRUE,
auto_config = TRUE) {
if (!requireNamespace("usethis", quietly = TRUE)) {
stop("Please install package 'usethis' to use function 'rstan_create_package'.",
call. = FALSE)
}
DIR <- dirname(path)
name <- basename(path)
.check_stan_ext(stan_files)
if (rstudio && !requireNamespace("rstudioapi", quietly = TRUE)) {
stop("Please install package 'rstudioapi' to use option 'rstudio = TRUE'.",
call. = FALSE)
rstudio <- rstudio && rstudioapi::isAvailable()
}
if (open && rstudio) {
on.exit(rstudioapi::openProject(file.path(DIR, name), newSession = TRUE))
}
# run usethis::create_package()
if (file.exists(path)) {
stop("Directory '", path, "' already exists.", call. = FALSE)
}
message("Creating package skeleton for package: ", name, domain = NA)
suppressMessages(
usethis::create_package(
path = path,
fields = fields,
rstudio = rstudio,
open = FALSE
)
)
# add Stan-specific functionality to the new package
pkgdir <- .check_pkgdir(file.path(DIR, name))
.rstan_make_pkg(pkgdir, stan_files, roxygen, travis, license, auto_config)
invisible(NULL)
}
#--- helper functions ----------------------------------------------------------
# check stan extensions
.check_stan_ext <- function(stan_files) {
if (length(stan_files) && !all(grepl("\\.stan$", stan_files))) {
stop("All files named in 'stan_files' must end ",
"with a '.stan' extension.", call. = FALSE)
}
}
# add travis file
.add_travis <- function(pkgdir) {
travis_file <- readLines(.system_file("travis.yml"))
.add_stanfile(gsub("RSTAN_PACKAGE_NAME", basename(pkgdir), travis_file),
pkgdir, ".travis.yml",
noedit = FALSE, msg = TRUE, warn = FALSE)
}
# add .gitignore and .Rbuildignore files
.add_gitignore_Rbuildignore <- function(pkgdir, travis) {
gitignore_files <- c("^rcppExports.cpp$", "^stanExports_*")
.add_stanfile(gitignore_files, pkgdir, ".gitignore",
noedit = FALSE, msg = TRUE, warn = FALSE)
Rbuildignore_files <- c(gitignore_files, if (travis) "^\\.travis\\.yml$")
.add_stanfile(Rbuildignore_files, pkgdir, ".Rbuildignore",
noedit = FALSE, msg = TRUE, warn = FALSE)
}
# add R/mypkg-package.R file with roxygen import comments
# also add Encoding: UTF-8 to DESCRIPTION
.add_roxygen <- function(pkgdir) {
pkg_file <- readLines(.system_file("rstanpkg-package.R"))
pkg_file <- gsub("RSTAN_PACKAGE_NAME", basename(pkgdir), pkg_file)
pkg_file <- gsub("RSTAN_REFERENCE", .rstan_reference(), pkg_file)
.add_stanfile(pkg_file, pkgdir,
"R", paste0(basename(pkgdir), "-package.R"),
noedit = FALSE, msg = TRUE, warn = FALSE)
desc_pkg <- desc::description$new(file.path(pkgdir, "DESCRIPTION"))
desc_pkg$set(Encoding = "UTF-8")
desc_pkg$write()
}
# reference to rstan package
.rstan_reference <- function() {
has_version <- utils::packageDescription("rstan", fields = "Version")
version_year <- substr(utils::packageDescription("rstan", fields = "Date"), 1, 4)
paste0(
"Stan Development Team (", version_year,"). ",
"RStan: the R interface to Stan. ",
"R package version ", has_version, ". ",
"https://mc-stan.org"
)
}
# add stan functionality to package
.rstan_make_pkg <- function(pkgdir, stan_files,
roxygen, travis, license, auto_config) {
use_rstan(pkgdir, license = license, auto_config = auto_config)
file.copy(
from = stan_files,
to = file.path(pkgdir, "inst", "stan", basename(stan_files))
)
if (roxygen) .add_roxygen(pkgdir)
if (travis) {
.add_travis(pkgdir)
warning(
"The 'travis' argument is deprecated. ",
"We recommend using GitHub Actions instead.",
call. = FALSE
)
}
.add_gitignore_Rbuildignore(pkgdir, travis)
message("Configuring Stan compile and module export instructions ...")
rstan_config(pkgdir)
# open = "at" implies text is appended
con <- file(file.path(pkgdir, "Read-and-delete-me"), open = "at")
writeLines(readLines(.system_file("Read_and_delete_me")), con)
close(con)
message(
domain = NA,
sprintf(
"Further Stan-specific steps are described in '%s'.",
file.path(basename(pkgdir), "Read-and-delete-me")
)
)
}
#-------------------------------------------------------------------------------
## outline of steps:
## 1. run package skeleton.
## - rm man files
## 2. create stan_dirs.
## - inst/stan
## - inst/stan/include
## - inst/include
## - src/stan_files
## - if (!exists) message
## 3. add default files.
## - src/stan_init.cpp
## - inst/include/stan_meta_header.cpp
## 4. update NAMESPACE
## - if (default) modify else message(remaining steps)
## 5. update DESCRIPTION
## - if (modified) message else do_nothing
## 4. add stan_files
## 5. configure build
## - make src/stan_files/*.{cc/hpp}
## only overwrite if different
## - add src/Makevars[.win]
## only overwrite if different
## - add R/stanmodels.R
## - if (is_empty(inst/stan)) no Makevars, empty stanmodels.R
## messages:
## - when creating directories
## - when updating DESCRIPTION or NAMESPACE
## - when adding files? sometimes
## warnings:
## - when attempting to overwrite non-stan file with stan file of same name
## error:
## - when {dir/file}.create fails even though it doesn't exist
## ok stan_meta_header.hpp is problematic, because want to warn if already exists, but only if it's there from before...
## so .add_stanfile(file_lines, pkgdir, ...,
## noedit = TRUE, msg = FALSE, warn = TRUE)