@@ -23,15 +23,24 @@ sourceCpp <- function(file = "",
2323 env = globalenv(),
2424 embeddedR = TRUE ,
2525 rebuild = FALSE ,
26+ cacheDir = getOption(" rcpp.cache.dir" , tempdir()),
27+ cleanupCacheDir = FALSE ,
2628 showOutput = verbose ,
2729 verbose = getOption(" verbose" ),
2830 dryRun = FALSE ) {
2931
32+ # use an architecture/version specific subdirectory of the cacheDir
33+ # (since cached dynlibs can now perist across sessions we need to be
34+ # sure to invalidate them when R or Rcpp versions change)
35+ cacheDir <- path.expand(cacheDir )
36+ cacheDir <- .sourceCppPlatformCacheDir(cacheDir )
37+ cacheDir <- normalizePath(cacheDir )
38+
3039 # resolve code into a file if necessary. also track the working
3140 # directory to source the R embedded code chunk within
3241 if (! missing(code )) {
3342 rWorkingDir <- getwd()
34- file <- tempfile(fileext = " .cpp" )
43+ file <- tempfile(fileext = " .cpp" , tmpdir = cacheDir )
3544 con <- file(file , open = " w" )
3645 writeLines(code , con )
3746 close(con )
@@ -55,10 +64,10 @@ sourceCpp <- function(file = "",
5564 " is not permitted." )
5665 }
5766 }
58-
67+
5968 # get the context (does code generation as necessary)
6069 context <- .Call(" sourceCppContext" , PACKAGE = " Rcpp" ,
61- file , code , rebuild , .Platform )
70+ file , code , rebuild , cacheDir , .Platform )
6271
6372 # perform a build if necessary
6473 if (context $ buildRequired || rebuild ) {
@@ -105,7 +114,7 @@ sourceCpp <- function(file = "",
105114
106115 # unload and delete existing dylib if necessary
107116 if (file.exists(context $ previousDynlibPath )) {
108- try(silent = T , dyn.unload(context $ previousDynlibPath ))
117+ try(silent = TRUE , dyn.unload(context $ previousDynlibPath ))
109118 file.remove(context $ previousDynlibPath )
110119 }
111120
@@ -189,10 +198,37 @@ sourceCpp <- function(file = "",
189198 setwd(rWorkingDir ) # will be reset by previous on.exit handler
190199 source(file = srcConn , echo = TRUE )
191200 }
201+
202+ # cleanup the cache dir if requested
203+ if (cleanupCacheDir )
204+ cleanupSourceCppCache(cacheDir , context $ cppSourcePath , context $ buildDirectory )
192205
193206 # return (invisibly) a list containing exported functions and modules
194207 invisible (list (functions = context $ exportedFunctions ,
195- modules = context $ modules ))
208+ modules = context $ modules ,
209+ cppSourcePath = context $ cppSourcePath ,
210+ buildDirectory = context $ buildDirectory ))
211+ }
212+
213+
214+ # Cleanup a directory used as the cache for a sourceCpp compilation. This will
215+ # remove all files from the cache directory that aren't a result of the
216+ # compilation that yielded the passed buildDirectory.
217+ cleanupSourceCppCache <- function (cacheDir , cppSourcePath , buildDirectory ) {
218+ # normalize cpp source path and build directory
219+ cppSourcePath <- normalizePath(cppSourcePath )
220+ buildDirectory <- normalizePath(buildDirectory )
221+
222+ # determine the parent dir that was used for the compilation then collect all the
223+ # *.cpp files and subdirectories therein
224+ cacheFiles <- list.files(cacheDir , pattern = glob2rx(" *.cpp" ), recursive = FALSE , full.names = TRUE )
225+ cacheFiles <- c(cacheFiles , list.dirs(cacheDir , recursive = FALSE , full.names = TRUE ))
226+ cacheFiles <- normalizePath(cacheFiles )
227+
228+ # determine the list of tiles that were not yielded by the passed sourceCpp
229+ # result and remove them
230+ oldCacheFiles <- cacheFiles [! cacheFiles %in% c(cppSourcePath , buildDirectory )]
231+ unlink(oldCacheFiles , recursive = TRUE )
196232}
197233
198234# Define a single C++ function
@@ -202,6 +238,7 @@ cppFunction <- function(code,
202238 includes = character (),
203239 env = parent.frame(),
204240 rebuild = FALSE ,
241+ cacheDir = getOption(" rcpp.cache.dir" , tempdir()),
205242 showOutput = verbose ,
206243 verbose = getOption(" verbose" )) {
207244
@@ -258,6 +295,7 @@ cppFunction <- function(code,
258295 exported <- sourceCpp(code = code ,
259296 env = env ,
260297 rebuild = rebuild ,
298+ cacheDir = cacheDir ,
261299 showOutput = showOutput ,
262300 verbose = verbose )
263301
@@ -304,6 +342,7 @@ evalCpp <- function(code,
304342 plugins = character (),
305343 includes = character (),
306344 rebuild = FALSE ,
345+ cacheDir = getOption(" rcpp.cache.dir" , tempdir()),
307346 showOutput = verbose ,
308347 verbose = getOption( " verbose" ) ){
309348
@@ -312,7 +351,7 @@ evalCpp <- function(code,
312351 env <- new.env()
313352 cppFunction(code , depends = depends , plugins = plugins ,
314353 includes = includes , env = env ,
315- rebuild = rebuild , showOutput = showOutput , verbose = verbose )
354+ rebuild = rebuild , cacheDir = cacheDir , showOutput = showOutput , verbose = verbose )
316355 fun <- env [[" get_value" ]]
317356 fun()
318357}
@@ -989,3 +1028,92 @@ sourceCppFunction <- function(func, isVoid, dll, symbol) {
9891028 }
9901029 .hasDevelTools
9911030}
1031+
1032+
1033+ # insert a dynlib entry into the cache
1034+ .sourceCppDynlibInsert <- function (cacheDir , file , code , dynlib ) {
1035+ cache <- .sourceCppDynlibReadCache(cacheDir )
1036+ index <- .sourceCppFindCacheEntryIndex(cache , file , code )
1037+ if (is.null(index ))
1038+ index <- length(cache ) + 1
1039+ cache [[index ]] <- list (file = file , code = code , dynlib = dynlib )
1040+ .sourceCppDynlibWriteCache(cacheDir , cache )
1041+ }
1042+
1043+ # attempt to lookup a dynlib entry from the cache
1044+ .sourceCppDynlibLookup <- function (cacheDir , file , code ) {
1045+ cache <- .sourceCppDynlibReadCache(cacheDir )
1046+ index <- .sourceCppFindCacheEntryIndex(cache , file , code )
1047+ if (! is.null(index ))
1048+ cache [[index ]]$ dynlib
1049+ else
1050+ list ()
1051+ }
1052+
1053+ # write the cache to disk
1054+ .sourceCppDynlibWriteCache <- function (cacheDir , cache ) {
1055+ index_file <- file.path(cacheDir , " cache.rds" )
1056+ save(cache , file = index_file , compress = FALSE )
1057+ }
1058+
1059+ # read the cache from disk
1060+ .sourceCppDynlibReadCache <- function (cacheDir ) {
1061+ index_file <- file.path(cacheDir , " cache.rds" )
1062+ if (file.exists(index_file )) {
1063+ load(file = index_file )
1064+ get(" cache" )
1065+ } else {
1066+ list ()
1067+ }
1068+ }
1069+
1070+ # search the cache for an entry that matches the file or code argument
1071+ .sourceCppFindCacheEntryIndex <- function (cache , file , code ) {
1072+
1073+ if (length(cache ) > 0 ) {
1074+ for (i in 1 : length(cache )) {
1075+ entry <- cache [[i ]]
1076+ if ((nzchar(file ) && identical(file , entry $ file )) ||
1077+ (nzchar(code ) && identical(code , entry $ code ))) {
1078+ if (file.exists(entry $ dynlib $ cppSourcePath ))
1079+ return (i )
1080+ }
1081+ }
1082+ }
1083+
1084+ # none found
1085+ NULL
1086+ }
1087+
1088+ # generate an R version / Rcpp version specific cache dir for dynlibs
1089+ .sourceCppPlatformCacheDir <- function (cacheDir ) {
1090+
1091+ dir <- file.path(cacheDir ,
1092+ paste(R.version $ platform ,
1093+ utils :: packageVersion(" Rcpp" ),
1094+ sep = " -" ))
1095+ if (! dir.exists(dir ))
1096+ dir.create(dir , recursive = TRUE )
1097+
1098+ dir
1099+ }
1100+
1101+ # generate a unique token for a cacheDir
1102+ .sourceCppDynlibUniqueToken <- function (cacheDir ) {
1103+ # read existing token (or create a new one)
1104+ token_file <- file.path(cacheDir , " token.rds" )
1105+ if (file.exists(token_file ))
1106+ load(file = token_file )
1107+ else
1108+ token <- 0
1109+
1110+ # increment
1111+ token <- token + 1
1112+
1113+ # write it
1114+ save(token , file = token_file )
1115+
1116+ # return it as a string
1117+ as.character(token )
1118+ }
1119+
0 commit comments