R/Attributes.R

Defines functions .extraRoutineRegistrations .sourceCppDynlibUniqueToken .sourceCppPlatformCacheDir .sourceCppFindCacheEntryIndex .sourceCppDynlibReadCache .sourceCppDynlibWriteCache .sourceCppDynlibLookup .sourceCppDynlibInsert .checkDevelTools .showBuildFailureDiagnostics .parseLinkingTo .pluginIncludes .linkingToIncludes .getHooksList .callBuildCompleteHook .callBuildHook .restoreEnvironment .buildClinkCppFlags .rtoolsPath .environmentWithRtools .setupBuildEnvironment .findPlugin .getInlinePlugin .readPkgDescField .splitDepends .validatePackages .isPackageSourceFile .getSourceCppDependencies .printVerboseOutput sourceCppFunction registerPlugin ] ] ] ] ] ] ] ] ] ] ] ] ] compileAttributes areMacrosDefined evalCpp print.bytes .type_manipulate cppFunction cleanupSourceCppCache sourceCpp

Documented in areMacrosDefined compileAttributes cppFunction evalCpp print.bytes registerPlugin sourceCpp

# Copyright (C) 2012 - 2022  JJ Allaire, Dirk Eddelbuettel and Romain Francois
# Copyright (C) 2023 - 2024  JJ Allaire, Dirk Eddelbuettel, Romain Francois and IƱaki Ucar
#
# This file is part of Rcpp.
#
# Rcpp 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 2 of the License, or
# (at your option) any later version.
#
# Rcpp 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 Rcpp.  If not, see <http://www.gnu.org/licenses/>.


# Source C++ code from a file
sourceCpp <- function(file = "",
                      code = NULL,
                      env = globalenv(),
                      embeddedR = TRUE,
                      rebuild = FALSE,
                      cacheDir = getOption("rcpp.cache.dir", tempdir()),
                      cleanupCacheDir = FALSE,
                      showOutput = verbose,
                      verbose = getOption("verbose"),
                      dryRun = FALSE,
                      windowsDebugDLL = FALSE,
                      echo = TRUE) {

    # use an architecture/version specific subdirectory of the cacheDir
    # (since cached dynlibs can now perist across sessions we need to be
    # sure to invalidate them when R or Rcpp versions change)
    cacheDir <- path.expand(cacheDir)
    cacheDir <- .sourceCppPlatformCacheDir(cacheDir)
    cacheDir <- normalizePath(cacheDir)

    # resolve code into a file if necessary. also track the working
    # directory to source the R embedded code chunk within
    if (!missing(code)) {
        rWorkingDir <- getwd()
        file <- tempfile(fileext = ".cpp", tmpdir = cacheDir)
        con <- file(file, open = "w")
        writeLines(code, con)
        close(con)
    } else {
        rWorkingDir <- normalizePath(dirname(file))
    }

    # resolve the file path
    file <- normalizePath(file, winslash = "/")

    # error if the file extension isn't one supported by R CMD SHLIB
    if (! tools::file_ext(file) %in% c("cc", "cpp")) {          # #nocov start
        stop("The filename '", basename(file), "' does not have an ",
             "extension of .cc or .cpp so cannot be compiled.")
    }                                                           # #nocov end

    # validate that there are no spaces in the path on windows
    if (.Platform$OS.type == "windows") {                       # #nocov start
        if (grepl(' ', basename(file), fixed=TRUE)) {
            stop("The filename '", basename(file), "' contains spaces. This ",
                 "is not permitted.")
        }
    } else {
        if (windowsDebugDLL) {
            if (verbose) {
                message("The 'windowsDebugDLL' toggle is ignored on ",
                        "non-Windows platforms.")
            }
            windowsDebugDLL <- FALSE    # now we do not need to deal with OS choice below
        }							# #nocov end
    }

    # get the context (does code generation as necessary)
    context <- .Call("sourceCppContext", PACKAGE="Rcpp",
                     file, code, rebuild, cacheDir, .Platform)

    # perform a build if necessary
    if (context$buildRequired || rebuild) {

        # print output for verbose mode
        if (verbose)
            .printVerboseOutput(context)						# #nocov

        # variables used to hold completed state (passed to completed hook)
        succeeded <- FALSE
        output <- NULL

        # build dependency list
        depends <- .getSourceCppDependencies(context$depends, file)

        # validate packages (error if package not found)
        .validatePackages(depends, context$cppSourceFilename)

        # temporarily modify environment for the build
        envRestore <- .setupBuildEnvironment(depends, context$plugins, file)

        # temporarily setwd to build directory
        cwd <- getwd()
        setwd(context$buildDirectory)

        # call the onBuild hook. note that this hook should always be called
        # after .setupBuildEnvironment so subscribers are able to examine
        # the build environment
        fromCode <- !missing(code)
        if (!.callBuildHook(context$cppSourcePath, fromCode, showOutput)) {
            .restoreEnvironment(envRestore)                     # #nocov start
            setwd(cwd)
            return (invisible(NULL))
        }                                                       # #nocov end

        # on.exit handler calls hook and restores environment and working dir
        on.exit({
            if (!succeeded)
                .showBuildFailureDiagnostics()					# #nocov
            .callBuildCompleteHook(succeeded, output)
            setwd(cwd)
            .restoreEnvironment(envRestore)
        })

        # unload and delete existing dylib if necessary
        if (file.exists(context$previousDynlibPath)) {          # #nocov start
            try(silent=TRUE, dyn.unload(context$previousDynlibPath))
            file.remove(context$previousDynlibPath)
        }                                                       # #nocov end

        # grab components we need to build command
        r <- file.path(R.home("bin"), "R")
        lib  <- context$dynlibFilename
        deps <- context$cppDependencySourcePaths
        src  <- context$cppSourceFilename

        # prepare the command (output if we are in showOutput mode)
        args <- c(
            "CMD", "SHLIB",
            if (windowsDebugDLL) "-d",
            if (rebuild) "--preclean",
            if (dryRun) "--dry-run",
            "-o", shQuote(lib),
            if (length(deps))
                paste(shQuote(deps), collapse = " "),
            shQuote(src)
        )

        if (showOutput)
            cat(paste(c(r, args), collapse = " "), "\n")		# #nocov

        # execute the build -- suppressWarnings b/c when showOutput = FALSE
        # we are going to explicitly check for an error and print the output
        so <- if (showOutput) "" else TRUE
        result <- suppressWarnings(system2(r, args, stdout = so, stderr = so))

        # check build results
        if(!showOutput) {
            # capture output
            output <- result
            attributes(output) <- NULL
            # examine status
            status <- attr(result, "status")
            if (!is.null(status)) {
                cat(result, sep = "\n")                         # #nocov start
                succeeded <- FALSE
                stop("Error ", status, " occurred building shared library.")
            } else if (!file.exists(context$dynlibFilename)) {
                cat(result, sep = "\n")
                succeeded <- FALSE
                stop("Error occurred building shared library.") # #nocov end
            } else {
                succeeded <- TRUE
            }
        }
        else if (!identical(as.character(result), "0")) {       # #nocov start
            succeeded <- FALSE
            stop("Error ", result, " occurred building shared library.")
        } else {
            succeeded <- TRUE                                   # #nocov end
        }
    }
    else {
        cwd <- getwd()
        on.exit({
            setwd(cwd)
        })
        if (verbose)											# #nocov start
            cat("\nNo rebuild required (use rebuild = TRUE to ",
                "force a rebuild)\n\n", sep="")
    }

    # return immediately if this was a dry run
    if (dryRun)
        return(invisible(NULL))									# #nocov end

    # load the module if we have exported symbols
    if (length(context$exportedFunctions) > 0 || length(context$modules) > 0) {

        # remove existing objects of the same name from the environment
        exports <- c(context$exportedFunctions, context$modules)
        removeObjs <- exports[exports %in% ls(envir = env, all.names = T)]
        remove(list = removeObjs, envir = env)

        # source the R script
        scriptPath <- file.path(context$buildDirectory, context$rSourceFilename)
        source(scriptPath, local = env)

    } else if (getOption("rcpp.warnNoExports", default=TRUE)) { # #nocov start
        warning("No Rcpp::export attributes or RCPP_MODULE declarations ",
                "found in source")                              # #nocov end
    }

    # source the embeddedR
    if (embeddedR && (length(context$embeddedR) > 0)) {
        srcConn <- textConnection(context$embeddedR)
        setwd(rWorkingDir) # will be reset by previous on.exit handler
        source(file = srcConn, local = env, echo = echo)
    }

    # cleanup the cache dir if requested
    if (cleanupCacheDir)
        cleanupSourceCppCache(cacheDir, context$cppSourcePath, context$buildDirectory)	# #nocov

    # return (invisibly) a list containing exported functions and modules
    invisible(list(functions = context$exportedFunctions,
                   modules = context$modules,
                   cppSourcePath = context$cppSourcePath,
                   buildDirectory = context$buildDirectory))
}


# Cleanup a directory used as the cache for a sourceCpp compilation. This will
# remove all files from the cache directory that aren't a result of the
# compilation that yielded the passed buildDirectory.
cleanupSourceCppCache <- function(cacheDir, cppSourcePath, buildDirectory) {
    # normalize cpp source path and build directory             # #nocov start
    cppSourcePath <- normalizePath(cppSourcePath)
    buildDirectory <- normalizePath(buildDirectory)

    # determine the parent dir that was used for the compilation then collect all the
    # *.cpp files and subdirectories therein
    cacheFiles <- list.files(cacheDir, pattern = glob2rx("*.cpp"), recursive = FALSE, full.names = TRUE)
    cacheFiles <- c(cacheFiles, list.dirs(cacheDir, recursive = FALSE, full.names = TRUE))
    cacheFiles <- normalizePath(cacheFiles)

    # determine the list of tiles that were not yielded by the passed sourceCpp
    # result and remove them
    oldCacheFiles <- cacheFiles[!cacheFiles %in% c(cppSourcePath, buildDirectory)]
    unlink(oldCacheFiles, recursive = TRUE)                     # #nocov end
}

# Define a single C++ function
cppFunction <- function(code,
                        depends = character(),
                        plugins = character(),
                        includes = character(),
                        env = parent.frame(),
                        rebuild = FALSE,
                        cacheDir = getOption("rcpp.cache.dir", tempdir()),
                        showOutput = verbose,
                        verbose = getOption("verbose"),
                        echo = TRUE) {

    # process depends
    if (!is.null(depends) && length(depends) > 0) {             # #nocov start
        depends <- paste(depends, collapse=", ")
        scaffolding <- paste("// [[Rcpp::depends(", depends, ")]]", sep="")
        scaffolding <- c(scaffolding, "", .linkingToIncludes(depends, FALSE),
                         recursive=TRUE)                        # #nocov end
    }
    else {
        scaffolding <- "#include <Rcpp.h>"
    }

    # process plugins
    if (!is.null(plugins) && length(plugins) > 0) {
        plugins <- paste(plugins, sep=", ")                     # #nocov start
        pluginsAttrib <- paste("// [[Rcpp::plugins(", plugins, ")]]", sep="")
        scaffolding <- c(scaffolding, pluginsAttrib)

        # append plugin includes
        for (pluginName in plugins) {
            plugin <- .findPlugin(pluginName)
            settings <- plugin()
            scaffolding <- c(scaffolding, settings$includes, recursive=TRUE)
        }                                                       # #nocov end
    }

    # remainder of scaffolding
    scaffolding <- c(scaffolding,
                     "",
                     "using namespace Rcpp;",
                     "",
                     includes,
                     "// [[Rcpp::export]]",
                     recursive = T)

    # prepend scaffolding to code
    code <- paste(c(scaffolding, code, recursive = T), collapse="\n")

    # print the generated code if we are in verbose mode
    if (verbose) {                                              # #nocov start
        cat("\nGenerated code for function definition:",
            "\n--------------------------------------------------------\n\n")
        cat(code)
        cat("\n")
    }

    # source cpp into specified environment. if env is set to NULL
    # then create a new one (the caller can get a hold of the function
    # via the return value)
    if (is.null(env))
        env <- new.env()										# #nocov end
    exported <- sourceCpp(code = code,
                          env = env,
                          rebuild = rebuild,
                          cacheDir = cacheDir,
                          showOutput = showOutput,
                          verbose = verbose,
                          echo = echo)

    # verify that a single function was exported and return it
    if (length(exported$functions) == 0)
        stop("No function definition found")					# #nocov
    else if (length(exported$functions) > 1)
        stop("More than one function definition")				# #nocov
    else {
        functionName <- exported$functions[[1]]
        invisible(get(functionName, env))
    }
}

.type_manipulate <- function( what = "DEMANGLE", class = NULL ) {
    function( type = "int", ... ){                              # #nocov start
        code <- sprintf( '
        SEXP manipulate_this_type(){
            typedef %s type ;
            return wrap( %s(type) ) ;
        }', type, what )
        dots <- list( code, ... )
        dots[["env"]] <- environment()
        manipulate_this_type <- do.call( cppFunction, dots )
        res <- manipulate_this_type()
        if( ! is.null(class) ){
            class(res) <- class
        }
        res
    }                                                           # #nocov end
}

demangle <- .type_manipulate( "DEMANGLE" )
sizeof   <- .type_manipulate( "sizeof", "bytes" )

print.bytes <- function( x, ...){                               # #nocov start
    writeLines( sprintf( "%d bytes (%d bits)", x, 8 * x ) )
    invisible( x )
}                                                               # #nocov end

# Evaluate a simple c++ expression
evalCpp <- function(code,
                    depends = character(),
                    plugins = character(),
                    includes = character(),
                    rebuild = FALSE,
                    cacheDir = getOption("rcpp.cache.dir", tempdir()),
                    showOutput = verbose,
                    verbose = getOption( "verbose" ) ){

                                                                # #nocov start
    code <- sprintf( "SEXP get_value(){ return wrap( %s ) ; }", code )
    env <- new.env()
    cppFunction(code, depends = depends, plugins = plugins,
                includes = includes, env = env,
                rebuild = rebuild, cacheDir = cacheDir, showOutput = showOutput, verbose = verbose )
    fun <- env[["get_value"]]
    fun()                                                       # #nocov end
}

areMacrosDefined <- function(names,
                    depends = character(),
                    includes = character(),
                    rebuild = FALSE,
                    showOutput = verbose,
                    verbose = getOption( "verbose" ) ){


    code <- sprintf( '
        LogicalVector get_value(){

            return LogicalVector::create(
                %s
            ) ;
        }',

        paste( sprintf( '    _["%s"] =
                #if defined(%s)
                    true
                #else
                    false
                #endif
         ', names, names ), collapse = ",\n" )
    )
    env <- new.env()
    cppFunction(code, depends = depends, includes = includes, env = env,
                rebuild = rebuild, showOutput = showOutput, verbose = verbose )
    fun <- env[["get_value"]]
    fun()
}

# Scan the source files within a package for attributes and generate code
# based on the attributes.
compileAttributes <- function(pkgdir = ".", verbose = getOption("verbose")) {

    # verify this is a package and read the DESCRIPTION to get it's name
    pkgdir <- normalizePath(pkgdir, winslash = "/")
    descFile <- file.path(pkgdir,"DESCRIPTION")
    if (!file.exists(descFile))
        stop("pkgdir must refer to the directory containing an R package")		# #nocov
    pkgDesc <- read.dcf(descFile)[1,]
    pkgname = .readPkgDescField(pkgDesc, "Package")
    depends <- c(.readPkgDescField(pkgDesc, "Depends", character()),
                 .readPkgDescField(pkgDesc, "Imports", character()),
                 .readPkgDescField(pkgDesc, "LinkingTo", character()))
    depends <- unique(.splitDepends(depends))
    depends <- depends[depends != "R"]

    # check the NAMESPACE file to see if dynamic registration is enabled
    namespaceFile <- file.path(pkgdir, "NAMESPACE")
    if (!file.exists(namespaceFile))
        stop("pkgdir must refer to the directory containing an R package")		# #nocov
    pkgNamespace <- readLines(namespaceFile, warn = FALSE)
    registration <- any(grepl("^\\s*useDynLib.*\\.registration\\s*=\\s*TRUE.*$", pkgNamespace))

    # determine source directory
    srcDir <- file.path(pkgdir, "src")
    if (!file.exists(srcDir))
        return (FALSE)															# #nocov

    # create R directory if it doesn't already exist
    rDir <- file.path(pkgdir, "R")
    if (!file.exists(rDir))
        dir.create(rDir)														# #nocov

    # remove the old RcppExports.R file
    unlink(file.path(rDir, "RcppExports.R"))

    # get a list of all source files
    cppFiles <- list.files(srcDir, pattern = "\\.((c(c|pp)?)|(h(pp)?))$", ignore.case = TRUE)

    # don't include RcppExports.cpp
    cppFiles <- setdiff(cppFiles, "RcppExports.cpp")

    # locale independent sort for stable output
    locale <- Sys.getlocale(category = "LC_COLLATE")
    Sys.setlocale(category = "LC_COLLATE", locale = "C")
    cppFiles <- sort(cppFiles)
    Sys.setlocale(category = "LC_COLLATE", locale = locale)

    # derive base names (will be used for modules)
    cppFileBasenames <- tools::file_path_sans_ext(cppFiles)

    # expend them to their full paths
    cppFiles <- file.path(srcDir, cppFiles)
    cppFiles <- normalizePath(cppFiles, winslash = "/")

    # generate the includes list based on LinkingTo. Specify plugins-only
    # because we only need as/wrap declarations
    linkingTo <- .readPkgDescField(pkgDesc, "LinkingTo")
    includes <- .linkingToIncludes(linkingTo, TRUE)

    # if a master include file or types file is in inst/include then use it
    typesHeader <- c(paste0(pkgname, "_types.h"), paste0(pkgname, "_types.hpp"))
    pkgHeader <- c(paste0(pkgname, ".h"), typesHeader)
    pkgHeaderPath <- file.path(pkgdir, "inst", "include",  pkgHeader)
    pkgHeader <- pkgHeader[file.exists(pkgHeaderPath)]
    if (length(pkgHeader) > 0) {                                # #nocov start
        pkgInclude <- paste("#include \"../inst/include/",
                            pkgHeader, "\"", sep="")
        includes <- c(pkgInclude, includes)
    }                                                           # #nocov end

    # if a _types file is in src then include it
    pkgHeader <- typesHeader
    pkgHeaderPath <- file.path(pkgdir, "src", pkgHeader)
    pkgHeader <- pkgHeader[file.exists(pkgHeaderPath)]
    if (length(pkgHeader) > 0)
        includes <- c(paste0("#include \"", pkgHeader ,"\""), includes)		# #nocov

    # generate exports
    invisible(.Call("compileAttributes", PACKAGE="Rcpp",
                    pkgdir, pkgname, depends, registration, cppFiles, cppFileBasenames,
                    includes, verbose, .Platform))
}

# setup plugins environment
.plugins <- new.env()

# built-in C++98 plugin
.plugins[["cpp98"]] <- function() {
    if (getRversion() >= "3.4")         # with recent R versions, R can decide
        list(env = list(USE_CXX98 = "yes"))
    else
        list(env = list(PKG_CXXFLAGS ="-std=c++98"))
}
                                        # built-in C++11 plugin
.plugins[["cpp11"]] <- function() {
    if (getRversion() >= "3.4")         # with recent R versions, R can decide
        list(env = list(USE_CXX11 = "yes"))
    else if (getRversion() >= "3.1")    # with recent R versions, R can decide
        list(env = list(USE_CXX1X = "yes"))
    else if (.Platform$OS.type == "windows")
        list(env = list(PKG_CXXFLAGS = "-std=c++0x"))
    else                                # g++-4.8.1 or later
        list(env = list(PKG_CXXFLAGS ="-std=c++11"))
}

# built-in C++11 plugin for older g++ compiler
.plugins[["cpp0x"]] <- function() {
    list(env = list(PKG_CXXFLAGS ="-std=c++0x"))
}

## built-in C++14 plugin for C++14 standard
## this is the default in g++-6.1 and later
## per https://gcc.gnu.org/projects/cxx-status.html#cxx14
.plugins[["cpp14"]] <- function() {
    if (getRversion() >= "3.4")         # with recent R versions, R can decide
        list(env = list(USE_CXX14 = "yes"))
    else
        list(env = list(PKG_CXXFLAGS ="-std=c++14"))
}

# built-in C++1y plugin for C++14 and C++17 standard under development
.plugins[["cpp1y"]] <- function() {
    list(env = list(PKG_CXXFLAGS ="-std=c++1y"))
}

# built-in C++17 plugin for C++17 standard (g++-6 or later)
.plugins[["cpp17"]] <- function() {
    if (getRversion() >= "3.4")         # with recent R versions, R can decide
        list(env = list(USE_CXX17 = "yes"))
    else
        list(env = list(PKG_CXXFLAGS ="-std=c++17"))
}

# built-in C++20 plugin for C++20
.plugins[["cpp20"]] <- function() {
    if (getRversion() >= "4.2")         # with recent R versions, R can decide
        list(env = list(USE_CXX20 = "yes"))
    else
        list(env = list(PKG_CXXFLAGS ="-std=c++20"))
}

# built-in C++23 plugin for C++23
.plugins[["cpp23"]] <- function() {
    if (getRversion() >= "4.3")         # with recent R versions, R can decide
        list(env = list(USE_CXX23 = "yes"))
    else
        list(env = list(PKG_CXXFLAGS ="-std=c++23"))
}


## built-in C++1z plugin for C++17 standard under development
## note that as of Feb 2017 this is taken to be a moving target
## see https://gcc.gnu.org/projects/cxx-status.html
.plugins[["cpp1z"]] <- function() {
    list(env = list(PKG_CXXFLAGS ="-std=c++1z"))
}

## built-in C++2a plugin for g++ 8 and later
## cf https://gcc.gnu.org/projects/cxx-status.html as of Dec 2018
.plugins[["cpp2a"]] <- function() {
    list(env = list(PKG_CXXFLAGS ="-std=c++2a"))
}

## built-in C++2b plugin for compilers without C++23 support
.plugins[["cpp2b"]] <- function() {
    list(env = list(PKG_CXXFLAGS ="-std=c++2b"))
}

## built-in OpenMP plugin
.plugins[["openmp"]] <- function() {
    list(env = list(PKG_CXXFLAGS="-fopenmp",
                    PKG_LIBS="-fopenmp"))
}

.plugins[["unwindProtect"]] <- function() { # nocov start
    warning("unwindProtect is enabled by default and this plugin is deprecated.",
            " It will be removed in a future version of Rcpp.")
    list()
} # nocov end

# register a plugin
registerPlugin <- function(name, plugin) {
    .plugins[[name]] <- plugin                                  # #nocov
}


# Take an empty function body and connect it to the specified external symbol
sourceCppFunction <- function(func, isVoid, dll, symbol) {

    args <- names(formals(func))

    body <- quote( CALL_PLACEHOLDER ( EXTERNALNAME, ARG ) )[ c(1:2, rep(3, length(args))) ]

    for (i in seq(along.with = args))
        body[[i+2]] <- as.symbol(args[i])

    body[[1L]] <- quote(.Call)
    body[[2L]] <- getNativeSymbolInfo(symbol, dll)$address

    if (isVoid)
        body <- call("invisible", body)

    body(func) <- body

    func
}


# Print verbose output
.printVerboseOutput <- function(context) {                      # #nocov start

    cat("\nGenerated extern \"C\" functions",
        "\n--------------------------------------------------------\n")
    cat(context$generatedCpp, sep="")

    cat("\nGenerated R functions",
        "\n-------------------------------------------------------\n\n")
    cat(readLines(file.path(context$buildDirectory,
                            context$rSourceFilename)),
        sep="\n")

    cat("\nBuilding shared library",
        "\n--------------------------------------------------------\n",
        "\nDIR: ", context$buildDirectory, "\n\n", sep="")
}                                                               # #nocov end

# Add LinkingTo dependencies if the sourceFile is in a package
.getSourceCppDependencies <- function(depends, sourceFile) {

    # If the source file is in a package then simulate it being built
    # within the package by including it's LinkingTo dependencies,
    # the src directory (.), and the inst/include directory
    if (.isPackageSourceFile(sourceFile)) {                     # #nocov start
        descFile <- file.path(dirname(sourceFile), "..", "DESCRIPTION")
        DESCRIPTION <- read.dcf(descFile, all = TRUE)
        linkingTo <- .parseLinkingTo(DESCRIPTION$LinkingTo)
        unique(c(depends, linkingTo))                           # #nocov end
    } else {
        depends
    }
}


# Check whether a source file is in a package
.isPackageSourceFile <- function(sourceFile) {
    file.exists(file.path(dirname(sourceFile), "..", "DESCRIPTION"))
}

# Error if a package is not currently available
.validatePackages <- function(depends, sourceFilename) {
    unavailable <- depends[!depends %in% .packages(all.available=TRUE)]
    if (length(unavailable) > 0) {                              # #nocov start
        stop(paste("Package '", unavailable[[1]], "' referenced from ",
                    "Rcpp::depends in source file ",
                      sourceFilename, " is not available.",
                      sep=""),
                call. = FALSE)                                  # #nocov end
    }
}

# Split the depends field of a package description
.splitDepends <- function(x) {
    if (!length(x))
        return(character())										# #nocov
    x <- unlist(strsplit(x, ","))
    x <- sub("[[:space:]]+$", "", x)
    x <- unique(sub("^[[:space:]]*(.*)", "\\1", x))
    sub("^([[:alnum:].]+).*$", "\\1", x)
}

# read a field from a named package description character vector
.readPkgDescField <- function(pkgDesc, name, default = NULL) {
    if (name %in% names(pkgDesc))
        pkgDesc[[name]]
    else
        default
}


# Get the inline plugin for the specified package (return NULL if none found)
.getInlinePlugin <- function(package) {                         # #nocov start
    tryCatch(get("inlineCxxPlugin", asNamespace(package), inherits = FALSE),
             error = function(e) NULL)
}                                                               # #nocov end

# Lookup a plugin
.findPlugin <- function(pluginName) {

    plugin <- .plugins[[pluginName]]
    if (is.null(plugin))
        stop("Inline plugin '", pluginName, "' could not be found ",	# #nocov start
             "within the Rcpp package. You should be ",
             "sure to call registerPlugin before using a plugin.")		# #nocov end

    return(plugin)
}

# Setup the build environment based on the specified dependencies. Returns an
# opaque object that can be passed to .restoreEnvironment to reverse whatever
# changes that were made
.setupBuildEnvironment <- function(depends, plugins, sourceFile) {

    # setup
    buildEnv <- list()
    linkingToPackages <- c("Rcpp")

    # merge values into the buildEnv
    mergeIntoBuildEnv <- function(name, value) {

        # protect against null or empty string
        if (is.null(value) || !nzchar(value))
            return(invisible(NULL))

        # if it doesn't exist already just set it
        if (is.null(buildEnv[[name]])) {
            buildEnv[[name]] <<- value
        }
        # if it's not identical then append
        else if (!identical(buildEnv[[name]], value)) {			# #nocov
            buildEnv[[name]] <<- paste(buildEnv[[name]], value) # #nocov
        }
        else {
            # it already exists and it's the same value, this
            # likely means it's a flag-type variable so we
            # do nothing rather than appending it
        }
    }

    # update dependencies from a plugin
    setDependenciesFromPlugin <- function(plugin) {

        # get the plugin settings
        settings <- plugin()

        # merge environment variables
        pluginEnv <- settings$env
        for (name in names(pluginEnv)) {
            mergeIntoBuildEnv(name, pluginEnv[[name]])
        }

        # capture any LinkingTo elements defined by the plugin
        linkingToPackages <<- unique(c(linkingToPackages,
                                      settings$LinkingTo))
    }

    # add packages to linkingTo and introspect for plugins
    for (package in depends) {
                                                                # #nocov start
        # add a LinkingTo for this package
        linkingToPackages <- unique(c(linkingToPackages, package))

        # see if the package exports a plugin
        plugin <- .getInlinePlugin(package)
        if (!is.null(plugin))
           setDependenciesFromPlugin(plugin)                    # #nocov end
    }

    # process plugins
    for (pluginName in plugins) {
        plugin <- .findPlugin(pluginName)
        setDependenciesFromPlugin(plugin)
    }

    # if there is no buildEnv from a plugin then use the Rcpp plugin
    if (length(buildEnv) == 0) {
        buildEnv <- inlineCxxPlugin()$env
    }

    # set CLINK_CPPFLAGS based on the LinkingTo dependencies
    buildEnv$CLINK_CPPFLAGS <- .buildClinkCppFlags(linkingToPackages)

    # add the source file's directory to the compilation
    srcDir <- dirname(sourceFile)
    srcDir <- asBuildPath(srcDir)
    buildDirs <- srcDir

    # if the source file is in a package then add inst/include
    if (.isPackageSourceFile(sourceFile)) {                     # #nocov start
        incDir <- file.path(dirname(sourceFile), "..", "inst", "include")
        incDir <- asBuildPath(incDir)
        buildDirs <- c(buildDirs, incDir)                       # #nocov end
    }

    # set CLINK_CPPFLAGS with directory flags
    dirFlags <- paste0('-I"', buildDirs, '"', collapse=" ")
    buildEnv$CLINK_CPPFLAGS <- paste(buildEnv$CLINK_CPPFLAGS,
                                     dirFlags,
                                     collapse=" ")

    # merge existing environment variables
    for (name in names(buildEnv))
        mergeIntoBuildEnv(name, Sys.getenv(name))

    # add cygwin message muffler
    buildEnv$CYGWIN = "nodosfilewarning"

    # on windows see if we need to add Rtools to the path
    # (don't do this for RStudio since it has it's own handling)
    if (identical(Sys.info()[['sysname']], "Windows") &&
        !nzchar(Sys.getenv("RSTUDIO"))) {                       # #nocov start
        env <- .environmentWithRtools()
        for (var in names(env))
            buildEnv[[var]] <- env[[var]]                       # #nocov end
    }

    # create restore list
    restore <- list()
    for (name in names(buildEnv))
        restore[[name]] <- Sys.getenv(name, unset = NA)

    # set environment variables
    do.call(Sys.setenv, buildEnv)

    # return restore list
    return (restore)
}


# If we don't have the GNU toolchain already on the path then see if
# we can find Rtools and add it to the path
.environmentWithRtools <- function() {
                                                                # #nocov start
    # Only proceed if we don't have the required tools on the path
    hasRtools <- nzchar(Sys.which("ls.exe")) && nzchar(Sys.which("gcc.exe"))
    if (!hasRtools) {

        # Read the Rtools registry key
        key <- NULL
        try(key <- utils::readRegistry("SOFTWARE\\R-core\\Rtools",
                                       hive = "HLM", view = "32-bit"),
            silent = TRUE)

        # If we found the key examine it
        if (!is.null(key)) {

            # Check version
            ver <- key$`Current Version`
            if (ver %in% (c("2.15", "2.16", "3.0", "3.1", "3.2", "3.3", "3.4"))) {
                # See if the InstallPath leads to the expected directories
                # R version 3.3.0 alpha (2016-03-25 r70378)
                isGcc49 <- ver %in% c("3.3", "3.4") && as.numeric(R.Version()$`svn rev`) >= 70378

                rToolsPath <- key$`InstallPath`
                if (!is.null(rToolsPath)) {
                    # add appropriate path entries
                    path <- file.path(rToolsPath, "bin", fsep="\\")
                    if (!isGcc49)
                        path <- c(path, file.path(rToolsPath, "gcc-4.6.3", "bin", fsep="\\"))

                    # if they all exist then return a list with modified
                    # environment variables for the compilation
                    if (all(file.exists(path))) {
                        env <- list()
                        path <- paste(path, collapse = .Platform$path.sep)
                        env$PATH <- paste(path, Sys.getenv("PATH"), sep=.Platform$path.sep)
                        if (isGcc49) {
                            env$RTOOLS  <- .rtoolsPath(rToolsPath)
                            env$BINPREF <- file.path(env$RTOOLS, "mingw_$(WIN)/bin//", fsep = "/")
                        }
                        return(env)
                    }
                }
            }
        }
    }

    return(NULL)                                                # #nocov end
}


# Ensure that the path is suitable for passing as an RTOOLS
# environment variable
.rtoolsPath <- function(path) {
    path <- gsub("\\\\", "/", path)                             # #nocov start
    ## R 3.2.0 or later only:  path <- trimws(path)
    .localsub <- function(re, x) sub(re, "", x, perl = TRUE)
    path <- .localsub("[ \t\r\n]+$", .localsub("^[ \t\r\n]+", path))
    if (substring(path, nchar(path)) != "/")
        path <- paste(path, "/", sep="")
    path                                                        # #nocov end
}


# Build CLINK_CPPFLAGS from include directories of LinkingTo packages
.buildClinkCppFlags <- function(linkingToPackages) {
    pkgCxxFlags <- NULL
    for (package in linkingToPackages) {
        packagePath <- find.package(package, NULL, quiet=TRUE)
        packagePath <- asBuildPath(packagePath)
        pkgCxxFlags <- paste(pkgCxxFlags,
            paste0('-I"', packagePath, '/include"'),
            collapse=" ")
    }
    return (pkgCxxFlags)
}

.restoreEnvironment <- function(restore) {
    # variables to reset
    setVars <- restore[!is.na(restore)]
    if (length(setVars))
        do.call(Sys.setenv, setVars)							# #nocov

    # variables to remove
    removeVars <- names(restore[is.na(restore)])
    if (length(removeVars))
        Sys.unsetenv(removeVars)
}


# Call the onBuild hook. This hook is provided so that external tools
# can perform processing (e.g. lint checking or other diagnostics) prior
# to the execution of a build). The showOutput flag is there to inform the
# subscriber whether they'll be getting output in the onBuildComplete hook
# or whether it will need to be scraped from the console (for verbose=TRUE)
# The onBuild hook is always called from within the temporary build directory
.callBuildHook <- function(file, fromCode, showOutput) {

    for (fun in .getHooksList("sourceCpp.onBuild")) {

        if (is.character(fun))                                  # #nocov start
            fun <- get(fun)

        # allow the hook to cancel the build (errors in the hook explicitly
        # do not cancel the build since they are unexpected bugs)
        continue <- tryCatch(fun(file, fromCode, showOutput),
                             error = function(e) TRUE)

        if (!continue)
            return (FALSE)                                      # #nocov end
    }

    return (TRUE)
}

# Call the onBuildComplete hook. This hook is provided so that external tools
# can do analysis of build errors and (for example) present them in a
# navigable list. Note that the output parameter will be NULL when showOutput
# is TRUE. Tools can try to scrape the output from the console (in an
# implemenentation-dependent fashion) or can simply not rely on output
# processing in that case (since the user explicitly asked for output to be
# printed to the console). The onBuildCompleted hook is always called within
# the temporary build directory.
.callBuildCompleteHook <- function(succeeded, output) {

    # Call the hooks in reverse order to align sequencing with onBuild
    for (fun in .getHooksList("sourceCpp.onBuildComplete")) {

        if (is.character(fun))                                  # #nocov start
            fun <- get(fun)

        try(fun(succeeded, output))                             # #nocov end
    }
}

# The value for getHooks can be a single function or a list of functions,
# This function ensures that the result can always be processed as a list
.getHooksList <- function(name) {
    hooks <- getHook(name)
    if (!is.list(hooks))
        hooks <- list(hooks)									# #nocov
    hooks
}


# Generate list of includes based on LinkingTo. The pluginsOnly parameter
# to distinguish the case of capturing all includes needed for a compiliation
# (e.g. cppFunction) verses only needing to capture as/wrap converters which
# is the case for generation of shims (RcppExports.cpp) and Rcpp::interfaces
# package header files.
.linkingToIncludes <- function(linkingTo, pluginsOnly) {

    # This field can be NULL or empty -- in that case just return Rcpp.h
    if (is.null(linkingTo) || !nzchar(linkingTo))
        return (c("#include <Rcpp.h>"))							# #nocov

    # Look for Rcpp inline plugins within the list or LinkedTo packages
    include.before <- character()
    include.after <- character()
    linkingToPackages <- .parseLinkingTo(linkingTo)
    for (package in linkingToPackages) {

        # We already handle Rcpp internally
        if (identical(package, "Rcpp"))
            next

        # see if there is a plugin that we can extract includes from
        plugin <- .getInlinePlugin(package)                     # #nocov start
        if (!is.null(plugin)) {
            includes <- .pluginIncludes(plugin)
            if (!is.null(includes)) {
                include.before <- c(include.before, includes$before)
                include.after <- c(include.after, includes$after)
            }
        }
        # otherwise check for standard Rcpp::interfaces generated include
        else if (!pluginsOnly) {
            pkgPath <- find.package(package, NULL, quiet=TRUE)
            if (length(pkgPath) == 0) {
                stop(paste("Package '", package, "' referenced from ",
                           "LinkingTo directive is not available.", sep=""),
                     call. = FALSE)
            }
            pkgHeader <- paste(package, ".h", sep="")
            pkgHeaderPath <- file.path(pkgPath, "include",  pkgHeader)
            if (file.exists(pkgHeaderPath)) {
                pkgInclude <- paste("#include <", pkgHeader, ">", sep="")
                include.after <- c(include.after, pkgInclude)
            }
        }                                                       # #nocov end
    }

    # return the includes
    c(include.before, "#include <Rcpp.h>", include.after)
}

# Analyze the plugin's includes field to determine include.before and
# include.after. We are ONLY interested in plugins that work with Rcpp since
# the only types we need from includes are as/wrap marshallers. Therefore,
# we verify that the plugin was created using Rcpp.plugin.maker and then
# use that assumption to correctly extract include.before and include.after
.pluginIncludes <- function(plugin) {
                                                                # #nocov start
    # First determine the standard suffix of an Rcpp plugin by calling
    # Rcpp.plugin.maker. If the plugin$includes has this suffix we know
    # it's an Rcpp plugin
    token <- "include_after_token"
    stockRcppPlugin <- Rcpp.plugin.maker(include.after=token)
    includes <- stockRcppPlugin()$includes
    suffix <- strsplit(includes, token)[[1]][[2]]

    # now ask the plugin for it's includes, ensure that the plugin includes
    # are not null, and verify they have the Rcpp suffix before proceeding
    pluginIncludes <- plugin()$includes
    if (is.null(pluginIncludes))
        return (NULL)
    if (!grepl(suffix, pluginIncludes))
        return (NULL)

    # strip the suffix then split on stock Rcpp include to get before and after
    pluginIncludes <- strsplit(pluginIncludes, suffix)[[1]][[1]]
    pluginIncludes <- strsplit(pluginIncludes, c("#include <Rcpp.h>"))[[1]]

    # extract before and after and nix empty lines
    before <- pluginIncludes[[1]]
    before <- strsplit(before, "\n")[[1]]
    before <- before[nzchar(before)]
    after <- pluginIncludes[[2]]
    after <- strsplit(after, "\n")[[1]]
    after <- after[nzchar(after)]

    # return before and after
    list(before = before, after = after)                        # #nocov end
}

# Parse a LinkingTo field into a character vector
.parseLinkingTo <- function(linkingTo) {

    if (is.null(linkingTo))
        return (character())									# #nocov

    linkingTo <- strsplit(linkingTo, "\\s*\\,")[[1]]
    result <- gsub("\\s", "", linkingTo)
    gsub("\\(.*", "", result)
}

# show diagnostics for failed builds
.showBuildFailureDiagnostics <- function() {
                                                                # #nocov start
    # RStudio does it's own diagnostics so only do this for other environments
    if (nzchar(Sys.getenv("RSTUDIO")))
        return();

    # if we can't call R CMD SHLIB then notify the user they should
    # install the appropriate development tools
    if (!.checkDevelTools()) {
        msg <- paste("\nWARNING: The tools required to build C++ code for R ",
                     "were not found.\n\n", sep="")
        sysName <- Sys.info()[['sysname']]
        if (identical(sysName, "Windows")) {
            msg <- paste(msg, "Please download and install the appropriate ",
                              "version of Rtools:\n\n",
                              "http://cran.r-project.org/bin/windows/Rtools/\n",
                              sep="");

        } else if (identical(sysName, "Darwin")) {
            msg <- paste(msg, "Please install Command Line Tools for XCode ",
                         "(or equivalent).\n", sep="")
        } else {
            msg <- paste(msg, "Please install GNU development tools ",
                         "including a C++ compiler.\n", sep="")
        }
        message(msg)
    }                                                           # #nocov end
}

# check if R development tools are installed (cache successful result)
.hasDevelTools <- FALSE
.checkDevelTools <- function() {

    if (!.hasDevelTools) {                                      # #nocov start
        # create temp source file
        tempFile <- file.path(tempdir(), "foo.c")
        cat("void foo() {}\n", file = tempFile)
        on.exit(unlink(tempFile))

        # set working directory to tempdir (revert on exit)
        oldDir <- setwd(tempdir())
        on.exit(setwd(oldDir), add = TRUE)

        # attempt the compilation and note whether we succeed
        cmd <- paste(R.home(component="bin"), .Platform$file.sep, "R ",
                     "CMD SHLIB foo.c", sep = "")
        result <- suppressWarnings(system(cmd,
                                          ignore.stderr = TRUE,
                                          intern = TRUE))
        utils::assignInMyNamespace(".hasDevelTools", is.null(attr(result, "status")))

        # if we build successfully then remove the shared library
        if (.hasDevelTools) {
            lib <- file.path(tempdir(),
                             paste("foo", .Platform$dynlib.ext, sep=''))
            unlink(lib)
        }
    }
    .hasDevelTools                                              # #nocov end
}


# insert a dynlib entry into the cache
.sourceCppDynlibInsert <- function(cacheDir, file, code, dynlib) {
    cache <- .sourceCppDynlibReadCache(cacheDir)
    index <- .sourceCppFindCacheEntryIndex(cache, file, code)
    if (is.null(index))
        index <- length(cache) + 1
    cache[[index]] <- list(file = file, code = code, dynlib = dynlib)
    .sourceCppDynlibWriteCache(cacheDir, cache)
}

# attempt to lookup a dynlib entry from the cache
.sourceCppDynlibLookup <- function(cacheDir, file, code) {
    cache <- .sourceCppDynlibReadCache(cacheDir)
    index <- .sourceCppFindCacheEntryIndex(cache, file, code)
    if (!is.null(index))
        cache[[index]]$dynlib
    else
        list()
}

# write the cache to disk
.sourceCppDynlibWriteCache <- function(cacheDir, cache) {
    index_file <- file.path(cacheDir, "cache.rds")
    save(cache, file = index_file, compress = FALSE)
}

# read the cache from disk
.sourceCppDynlibReadCache <- function(cacheDir) {
    index_file <- file.path(cacheDir, "cache.rds")
    if (file.exists(index_file)) {
        load(file = index_file)
        get("cache")
    } else {
        list()
    }
}

# search the cache for an entry that matches the file or code argument
.sourceCppFindCacheEntryIndex <- function(cache, file, code) {

    if (length(cache) > 0) {
        for (i in 1:length(cache)) {
            entry <- cache[[i]]
            if ((nzchar(file) && identical(file, entry$file)) ||
                (nzchar(code) && identical(code, entry$code))) {
                if (file.exists(entry$dynlib$cppSourcePath))
                    return(i)
            }
        }
    }

    # none found
    NULL
}

# generate an R version / Rcpp version specific cache dir for dynlibs
.sourceCppPlatformCacheDir <- function(cacheDir) {

    dir <- file.path(cacheDir,
                     paste("sourceCpp",
                           R.version$platform,
                           utils::packageVersion("Rcpp"),
                           sep = "-"))
    if (!utils::file_test("-d", dir))
        dir.create(dir, recursive = TRUE)

    dir
}

# generate a unique token for a cacheDir
.sourceCppDynlibUniqueToken <- function(cacheDir) {
    # read existing token (or create a new one)
    token_file <- file.path(cacheDir, "token.rds")
    if (file.exists(token_file))
        load(file = token_file)
    else
        token <- 0

    # increment
    token <- token + 1

    # write it
    save(token, file = token_file)

    # return it as a string
    as.character(token)
}

.extraRoutineRegistrations <- function(targetFile, routines) {

    declarations = character()
    call_entries = character()

    # if we are running R 3.4 or higher we can use an internal utility function
    # to automatically discover additional native routines that require registration
    if (getRversion() >= "3.4") {

        # determine the package directory
        pkgdir <- dirname(dirname(targetFile))

        # get the generated code from R
        con <- textConnection(object = NULL, open = "w")
        on.exit(close(con), add = TRUE)
        tools::package_native_routine_registration_skeleton(
            dir = pkgdir,
            con = con,
            character_only = FALSE
        )
        code <- textConnectionValue(con)

        # look for lines containing call entries
        matches <- regexec('^\\s+\\{"([^"]+)",.*$', code)
        matches <- regmatches(code, matches)
        matches <- Filter(x = matches, function(x) {
            length(x) > 0											# #nocov start
        })
        for (match in matches) {
            routine <- match[[2]]
            if (!routine %in% routines) {
                declaration <- grep(sprintf("^extern .* %s\\(.*$", routine), code,
                                    value = TRUE)
                # FIXME: maybe we should extend this to *any* routine?
                # or is there any case in which `void *` is not SEXP for a .Call?
                if (routine == "run_testthat_tests")
                    declaration <- gsub("void *", "SEXP", declaration, fixed=TRUE)
                declarations <- c(declarations, sub("^extern", "RcppExport", declaration))
                call_entries <- c(call_entries, match[[1]])			# #nocov end
            }
        }
    }

    # return extra declaratiosn and call entries
    list(declarations = declarations,
         call_entries = call_entries)
}
RcppCore/Rcpp documentation built on March 7, 2024, 8:48 p.m.