Skip to content

Commit 4a133ee

Browse files
authored
Teach cpp_source() how to source multiple files (#492)
* Teach `cpp_source()` how to source multiple files * NEWS bullet * Link to PR
1 parent a9df50e commit 4a133ee

6 files changed

Lines changed: 68 additions & 19 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# cpp11 (development version)
22

3+
* `cpp_source()` now works with multiple `file`s (#492).
4+
35
# cpp11 0.5.4
46

57
* Removed non-API usage of `R_NamespaceRegistry`.

R/source.R

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
#' Compile C++ code
22
#'
3-
#' [cpp_source()] compiles and loads a single C++ file for use in R.
3+
#' [cpp_source()] compiles and loads one or more C++ files for use in R.
44
#' [cpp_function()] compiles and loads a single function for use in R.
55
#' [cpp_eval()] evaluates a single C++ expression and returns the result.
66
#'
77
#' Within C++ code you can use `[[cpp11::linking_to("pkgxyz")]]` to link to
88
#' external packages. This is equivalent to putting those packages in the
99
#' `LinkingTo` field in a package DESCRIPTION.
1010
#'
11-
#' @param file A file containing C++ code to compile
11+
#' @param file One or more files containing C++ code to compile
1212
#' @param code If non-null, the C++ code to compile
1313
#' @param env The R environment where the R wrapping functions should be defined.
1414
#' @param clean If `TRUE`, cleanup the files after sourcing
@@ -67,7 +67,9 @@
6767
#' @export
6868
cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, quiet = TRUE, cxx_std = Sys.getenv("CXX_STD", "CXX11"), dir = tempfile()) {
6969
stop_unless_installed(c("brio", "callr", "cli", "decor", "desc", "glue", "tibble", "vctrs"))
70-
if (!missing(file) && !file.exists(file)) {
70+
71+
if (!missing(file) && !all(file.exists(file))) {
72+
file <- file[!file.exists(file)][[1L]]
7173
stop("Can't find `file` at this path:\n", file, "\n", call. = FALSE)
7274
}
7375

@@ -83,12 +85,13 @@ cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, qu
8385
}
8486
brio::write_lines(code, file)
8587
}
88+
8689
if (!any(tools::file_ext(file) %in% c("cpp", "cc"))) {
8790
stop("`file` must have a `.cpp` or `.cc` extension")
8891
}
8992

90-
name <- generate_cpp_name(file)
91-
package <- tools::file_path_sans_ext(name)
93+
package <- generate_package_name()
94+
name <- vcapply(file, generate_cpp_name)
9295

9396
orig_dir <- normalizePath(dirname(file), winslash = "/")
9497
new_dir <- normalizePath(file.path(dir, "src"), winslash = "/")
@@ -112,7 +115,7 @@ cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, qu
112115
funs <- get_registered_functions(all_decorations, "cpp11::register", quiet = quiet)
113116
cpp_functions_definitions <- generate_cpp_functions(funs, package = package)
114117

115-
cpp_path <- file.path(dirname(new_file_path), "cpp11.cpp")
118+
cpp_path <- file.path(new_dir, "cpp11.cpp")
116119
brio::write_lines(c('#include "cpp11/declarations.hpp"', "using namespace ::cpp11;", cpp_functions_definitions), cpp_path)
117120

118121
linking_to <- union(get_linking_to(all_decorations), "cpp11")
@@ -130,7 +133,14 @@ cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, qu
130133
brio::write_lines(makevars_content, file.path(new_dir, "Makevars"))
131134

132135
source_files <- normalizePath(c(new_file_path, cpp_path), winslash = "/")
133-
res <- callr::rcmd("SHLIB", source_files, user_profile = TRUE, show = !quiet, wd = new_dir)
136+
137+
shared_lib_name <- paste0(package, .Platform$dynlib.ext)
138+
139+
shlib_args <- c(
140+
source_files,
141+
paste0("--output=", shared_lib_name)
142+
)
143+
res <- callr::rcmd("SHLIB", shlib_args, user_profile = TRUE, show = !quiet, wd = new_dir)
134144
if (res$status != 0) {
135145
error_messages <- res$stderr
136146

@@ -140,30 +150,51 @@ cpp_source <- function(file, code = NULL, env = parent.frame(), clean = TRUE, qu
140150
stop("Compilation failed.", call. = FALSE)
141151
}
142152

143-
shared_lib <- file.path(dir, "src", paste0(tools::file_path_sans_ext(new_file_name), .Platform$dynlib.ext))
144153
r_path <- file.path(dir, "R", "cpp11.R")
145154
brio::write_lines(r_functions, r_path)
146155
source(r_path, local = env)
147156

148-
dyn.load(shared_lib, local = TRUE, now = TRUE)
157+
shared_lib_path <- file.path(dir, "src", shared_lib_name)
158+
159+
dyn.load(shared_lib_path, local = TRUE, now = TRUE)
149160
}
150161

151162
the <- new.env(parent = emptyenv())
152163
the$count <- 0L
153164

154-
generate_cpp_name <- function(name, loaded_dlls = c("cpp11", names(getLoadedDLLs()))) {
165+
generate_cpp_name <- function(
166+
name,
167+
loaded_dlls = c("cpp11", names(getLoadedDLLs()))
168+
) {
155169
ext <- tools::file_ext(name)
156170
root <- tools::file_path_sans_ext(basename(name))
157-
count <- 2
158-
new_name <- root
159-
while(new_name %in% loaded_dlls) {
160-
new_name <- sprintf("%s_%i", root, count)
161-
count <- count + 1
162-
}
163-
sprintf("%s.%s", new_name, ext)
171+
root <- make_unique(root, loaded_dlls)
172+
sprintf("%s.%s", root, ext)
164173
}
165174

175+
generate_package_name <- function(
176+
loaded_dlls = c("cpp11", names(getLoadedDLLs()))
177+
) {
178+
name <- paste0(
179+
"package_",
180+
paste0(sample(letters, 10, replace = TRUE), collapse = "")
181+
)
182+
name <- make_unique(name, loaded_dlls)
183+
name
184+
}
185+
186+
# Adds `_2`, `_3`, etc to `x` until it is unique
187+
make_unique <- function(x, conflicts) {
188+
new <- x
166189

190+
count <- 2L
191+
while (new %in% conflicts) {
192+
new <- paste0(x, "_", count)
193+
count <- count + 1L
194+
}
195+
196+
new
197+
}
167198

168199
generate_include_paths <- function(packages) {
169200
out <- character(length(packages))

man/cpp_source.Rd

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[[cpp11::register]] int foo() { return 1; }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[[cpp11::register]] double bar() { return 1.0; }

tests/testthat/test-source.R

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ test_that("cpp_source works with files called `cpp11.cpp`", {
5454
expect_true(always_true())
5555
})
5656

57+
test_that("cpp_source works with multiple `file`s", {
58+
skip_on_os("solaris")
59+
60+
dll_info <- cpp_source(test_path(
61+
"fixtures",
62+
"test-two-files",
63+
c("one.cpp", "two.cpp")
64+
))
65+
on.exit(dyn.unload(dll_info[["path"]]), add = TRUE)
66+
67+
expect_identical(foo(), 1L)
68+
expect_identical(bar(), 1)
69+
})
70+
5771
test_that("cpp_source returns original file name on error", {
5872

5973
expect_output(try(cpp_source(test_path("single_error.cpp"), clean = TRUE), silent = TRUE),

0 commit comments

Comments
 (0)