R/Attributes.R

# Copyright (C) 2012 JJ Allaire, Dirk Eddelbuettel and Romain Francois
#
# 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(), 
                      rebuild = FALSE,
                      showOutput = verbose,
                      verbose = getOption("verbose")) { 
    
    # resolve code into a file if necessary
    if (!missing(code)) {
        file <- tempfile(fileext = ".cpp")
        con <- file(file, open = "w")
        writeLines(code, con)
        close(con)
    }
    
    # resolve the file path
    file <- normalizePath(file, winslash = "/")
     
    # get the context (does code generation as necessary)
    context <- .Call("sourceCppContext", PACKAGE="Rcpp", 
                     file, code, rebuild, .Platform)
    
    # perform a build if necessary
    if (context$buildRequired || rebuild) {
    
        # print output for verbose mode 
        if (verbose) 
            .printVerboseOutput(context)   
        
        # 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, 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)
            setwd(cwd)
            return (invisible(NULL))
        }
        
        # on.exit handler calls hook and restores environment and working dir
        on.exit({
            if (!succeeded)
                .showBuildFailureDiagnostics()
            .callBuildCompleteHook(succeeded, output)
            setwd(cwd)
            .restoreEnvironment(envRestore)
        })
        
        # unload and delete existing dylib if necessary
        if (file.exists(context$previousDynlibPath)) {
            try(silent=T, dyn.unload(context$previousDynlibPath))
            file.remove(context$previousDynlibPath)
        }
           
        # prepare the command (output if we are in showOutput mode)
        cmd <- paste(R.home(component="bin"), .Platform$file.sep, "R ",
                     "CMD SHLIB ",  
                     "-o ", shQuote(context$dynlibFilename), " ",
                     ifelse(rebuild, "--preclean ", ""),
                     shQuote(context$cppSourceFilename), sep="")
        if (showOutput)
            cat(cmd, "\n")
        
        # execute the build -- suppressWarnings b/c when showOutput = FALSE
        # we are going to explicitly check for an error and print the output
        result <- suppressWarnings(system(cmd, intern = !showOutput))
        
        # check build results
        if(!showOutput) {
            # capture output
            output <- result
            attributes(output) <- NULL
            # examine status
            status <- attr(result, "status")
            if (!is.null(status)) {
                cat(result, "\n")
                succeeded <- FALSE
                stop("Error ", status, " occurred building shared library.")
            } else if (!file.exists(context$dynlibFilename)) {
                cat(result, "\n")
                succeeded <- FALSE
                stop("Error occurred building shared library.")
            } else {
                succeeded <- TRUE
            }
        } 
        else if (!identical(as.character(result), "0")) {
            succeeded <- FALSE
            stop("Error ", result, " occurred building shared library.")
        } else {
            succeeded <- TRUE
        }
    } 
    else {
        if (verbose)
            cat("\nNo rebuild required (use rebuild = TRUE to ",
                "force a rebuild)\n\n", sep="")
    }
    
    # load the module if we have exported functions (else there is no module)
    if (length(context$exportedFunctions) > 0) {
        
        # remove existing objects of the same name from the environment
        exports <- context$exportedFunctions
        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)) {
        warning("No Rcpp::export attributes found in source")
    }
    
    # source the embeddedR
    if (length(context$embeddedR) > 0) {
        srcConn <- textConnection(context$embeddedR)
        source(file=srcConn, echo=TRUE)
    }
    
    # return (invisibly) a list of exported functions
    invisible(context$exportedFunctions)
}

# Define a single C++ function
cppFunction <- function(code, 
                        depends = character(),
                        includes = character(),
                        env = parent.frame(),
                        rebuild = FALSE,
                        showOutput = verbose,
                        verbose = getOption("verbose")) {
    
    # generate required scaffolding
    if (!is.null(depends) && length(depends) > 0) {
        depends <- paste(depends, sep=", ")
        scaffolding <- paste("// [[Rcpp::depends(", depends, ")]]", sep="")
        scaffolding <- c(scaffolding, "", .linkingToIncludes(depends, FALSE), 
                         recursive=T)
    }
    else {
        scaffolding <- "#include <Rcpp.h>"
    }
    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) {
        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()
    exported <- sourceCpp(code = code, 
                          env = env, 
                          rebuild = rebuild, 
                          showOutput = showOutput,
                          verbose = verbose)
    
    # verify that a single function was exported and return it
    if (length(exported) == 0)
        stop("No function definition found")
    else if (length(exported) > 1)
        stop("More than one function definition")
    else {
        functionName <- exported[[1]]
        invisible(get(functionName, env))
    }
}

# Evaluate a simple c++ expression
evalCpp <- function(code, 
                    depends = character(), 
                    includes = character(), 
                    rebuild = FALSE,
                    showOutput = verbose, 
                    verbose = getOption( "verbose" ) ){
 
                         
    code <- sprintf( "SEXP get_value(){ return wrap( %s ) ; }", code )
    env <- new.env()
    cppFunction(code, depends = depends, includes = includes, env = env, 
                rebuild = rebuild, showOutput = showOutput, verbose = verbose )
    fun <- env[["get_value"]]
    fun()
}

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")
    
    pkgInfo <- tools:::.split_description(tools:::.read_description(descFile))
    pkgname <- as.character(pkgInfo$DESCRIPTION["Package"])
    depends <- unique(names(pkgInfo$Depends))
    
    # determine source directory
    srcDir <- file.path(pkgdir, "src")
    if (!file.exists(srcDir))
        return (FALSE)
    
    # create R directory if it doesn't already exist
    rDir <- file.path(pkgdir, "R")
    if (!file.exists(rDir))
        dir.create(rDir)
    
    # get a list of all source files
    cppFiles <- list.files(srcDir, pattern=glob2rx("*.c*"))
    
    # 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 <- as.character(pkgInfo$DESCRIPTION["LinkingTo"])
    includes <- .linkingToIncludes(linkingTo, TRUE)
    
    # generate exports
    invisible(.Call("compileAttributes", PACKAGE="Rcpp", 
                    pkgdir, pkgname, depends, cppFiles, cppFileBasenames, 
                    includes, verbose, .Platform))
}


# Take an empty function body and connect it to the specified external symbol
sourceCppFunction <- function(func, dll, symbol) {
    
    args <- names(formals(func))
    
    body <- quote( .Call( EXTERNALNAME, ARG ) )[ c(1:2, rep(3, length(args))) ]
    
    for (i in seq(along = args)) 
        body[[i+2]] <- as.symbol(args[i])
    
    body[[1L]] <- .Call
    body[[2L]] <- getNativeSymbolInfo(symbol, dll)$address
    
    body(func) <- body
    
    func
}


# Print verbose output
.printVerboseOutput <- function(context) {
    
    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="")
}

# 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)) {
        descFile <- file.path(dirname(sourceFile), "..", "DESCRIPTION")
        DESCRIPTION <- read.dcf(descFile, all = TRUE)
        linkingTo <- .parseLinkingTo(DESCRIPTION$LinkingTo)
        unique(c(depends, linkingTo))
    } 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) {
        stop(paste("Package '", unavailable[[1]], "' referenced from ",
                    "Rcpp::depends in source file ",
                      sourceFilename, " is not available.", 
                      sep=""),
                call. = FALSE)
    }
}


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

# 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, sourceFile) {
    
    # discover dependencies
    buildEnv <- list()
    linkingToPackages <- c("Rcpp")
    for (package in depends) {
        
        # 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)) {
            
            # get the plugin settings 
            settings <- plugin()
            
            # merge environment variables
            pluginEnv <- settings$env
            for (name in names(pluginEnv)) {
                # if it doesn't exist already just set it
                if (is.null(buildEnv[[name]])) {
                    buildEnv[[name]] <- pluginEnv[[name]]
                }
                # if it's not identical then append
                else if (!identical(buildEnv[[name]],
                                    pluginEnv[[name]])) {
                    buildEnv[[name]] <- paste(buildEnv[[name]], 
                                              pluginEnv[[name]]);
                }
                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
                }   
            }
            
            # capture any LinkingTo elements defined by the plugin
            linkingToPackages <- unique(c(linkingToPackages, 
                                          settings$LinkingTo))
        }
    }
    
    # if there is no buildEnv from a plugin then use the Rcpp plugin
    if (length(buildEnv) == 0) {
        buildEnv <- Rcpp:::inlineCxxPlugin()$env
    } else {
        # we are using a plugin -- confirm that the plugin includes the Rcpp
        # PKG_LIBS and if it doesn't then add them
        pkgLibs <- buildEnv$PKG_LIBS
        rcppLibs <- Rcpp:::RcppLdFlags()
        if (is.null(pkgLibs) || !grepl(rcppLibs, pkgLibs, fixed = TRUE))
            buildEnv$PKG_LIBS <- paste(pkgLibs, rcppLibs)
    }
    
    # set CLINK_CPPFLAGS based on the LinkingTo dependencies
    buildEnv$CLINK_CPPFLAGS <- .buildClinkCppFlags(linkingToPackages)
    
    # if the source file is in a package then add standard package
    # include directories
    if (.isPackageSourceFile(sourceFile)) {
        srcDir <- dirname(sourceFile)
        incDir <- file.path(dirname(sourceFile), "..", "inst", "include")
        buildEnv$CLINK_CPPFLAGS <- paste(buildEnv$CLINK_CPPFLAGS, 
                                         paste0('-I"', c(srcDir, incDir), '"'), 
                                         collapse=" ")
    }

    # 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"))) {
        path <- .pathWithRtools()
        if (!is.null(path))
            buildEnv$PATH <- path
    }
    
    # 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
.pathWithRtools <- function() {
    
    # 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 -- we only support 2.15 and 2.16 right now
            ver <- key$`Current Version`
            if (identical("2.15", ver) || identical("2.16", ver)) {
                
                # See if the InstallPath leads to the expected directories
                rToolsPath <- key$`InstallPath`
                if (!is.null(rToolsPath)) {
                    
                    # Return modified PATH if execpted directories exist
                    binPath <- file.path(rToolsPath, "bin", fsep="\\")
                    gccPath <- file.path(rToolsPath, "gcc-4.6.3", "bin", fsep="\\")
                    if (file.exists(binPath) && file.exists(gccPath))
                        return(paste(binPath, 
                                     gccPath, 
                                     Sys.getenv("PATH"), 
                                     sep=.Platform$path.sep))
                }  
            }
        }
    }
    
    return(NULL)
}


# 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)
        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)
    
    # 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)) 
            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)
    }    
    
    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)) 
            fun <- get(fun)
        
        try(fun(succeeded, output))
    }
}

# 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)
    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>"))
    
    # 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)
        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)
            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)
            } 
        }
    }
    
    # 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) {
      
    # 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:::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)
}

# Parse a LinkingTo field into a character vector
.parseLinkingTo <- function(linkingTo) {
    
    if (is.null(linkingTo))
        return (character())
    
    linkingTo <- strsplit(linkingTo, "\\s*\\,")[[1]]
    gsub("\\s", "", linkingTo)
}

# show diagnostics for failed builds
.showBuildFailureDiagnostics <- function() {
    
    # 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)
    }
}

# check if R development tools are installed (cache successful result)
.hasDevelTools <- FALSE
.checkDevelTools <- function() {  
    
    if (!.hasDevelTools) {     
        # 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))
        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
}
jjallaire/Rcpp documentation built on May 19, 2019, 11:37 a.m.