--- /dev/null
+Package: pkgdev
+Type: Package
+Version: 0.0.1
+Date: 2013-XX-XX
+Title: Package development helper
+Author: Benjamin Auder
+Maintainer: Benjamin Auder <Benjamin.Auder@gmail.com>
+Depends: R (>= 2.15.1)
+Description: Allow nested subfolders in R/ directory,
+ and somewhat simplify testing process
+License: GPL (>= 3)
+LazyLoad: yes
--- /dev/null
+# Export all R functions
+export (pkgdev.wipeAll,pkgdev.load,pkgdev.unload,pkgdev.clean,pkgdev.rtest,pkgdev.ctest,pkgdev.tocran,pkgdev.check)
+
+# Import all packages listed as Imports or Depends
+#import (methods)
--- /dev/null
+# Functions to assert that a condition is true or false in C tests
+
+# Macros to pass file name and line number (in user files [TOFIX])
+#define ASSERT_TRUE(x,y) assertTrue(__FILE__,__LINE__,x,y)
+#define ASSERT_FALSE(x,y) assertFalse(__FILE__,__LINE__,x,y)
+
+CunitTestsMacros = '
+#include <stdlib.h>
+#include <stdio.h>
+
+// Functions to assert that a condition is true or false [to be extended]
+void assertTrue(char* fileName, int lineNumber, int condition, char* message) {
+ if (!condition) {
+ printf(">>> Failure in file %s at line %i: %s\\n", fileName, lineNumber, message);
+ }
+}
+
+void assertFalse(char* fileName, int lineNumber, int condition, char* message) {
+ assertTrue(fileName, lineNumber, !condition, message);
+}
+'
--- /dev/null
+# Functions to assert that conditions are true or false in R unit tests
+
+RunitTestsMethods = '
+# Functions to assert that a condition is true or false [to be extended]
+assertTrue = function(condition, message, context="") {
+ if (!condition) {
+ cat("Failure: ", message, sep=\'\')
+ if (context != "") cat(" [",context,"]")
+ cat("\\n")
+ }
+}
+
+assertFalse = function(condition, message, context="") {
+ assertTrue(!condition, message, context)
+}
+'
--- /dev/null
+# Core function to load a package (source R files and compile/load C libraries)
+# @param path Location of the (non-standard) package to be loaded
+# @param cc Compilator to be used (e.g. 'gcc -std=gnu99' [default])
+.pkgdev.load = function(path, cc) {
+
+ # Get package name from path
+ pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
+ pkgName = pathTokens[length(pathTokens)]
+
+ # Create base directory for pkgName under R_HOME_USER/pkgdev/pkgs/pkgName (if not existing)
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ dir.create(file.path(pkdev_path,"pkgs",pkgName), showWarnings=FALSE)
+
+ # R code first
+ # Warning: R/tests folder should not be sourced
+ forbiddenPath = file.path(path,"R","tests")
+ for (fileOrDir in list.files( file.path(path,"R"),full.names=TRUE )) {
+ if (fileOrDir != forbiddenPath) {
+ if (file.info(fileOrDir)$isdir) {
+ rFiles = list.files(fileOrDir, pattern="\\.[RrSsq]$",
+ full.names=TRUE, recursive=TRUE)
+ # NOTE: potential unexported functions are not hidden;
+ # the developer is assumed to handle this
+ lapply(rFiles, source)
+ }
+ else source(fileOrDir)
+ }
+ }
+
+ # Also load datasets (if any)
+ rData = list.files(file.path(path,"data"),pattern="\\.R(d|D)ata$",full.names=TRUE)
+ lapply(rData, load)
+
+ # This file tells if the package is currently loaded
+ pkgLoadFile = file.path(pkdev_path,"pkgs",pkgName,"loaded")
+
+ if (file.exists(file.path(path,"src"))) {
+ # C code -- Warning: src/tests folder should not be listed
+ cFiles = c(
+ list.files( file.path(path,"src","adapters"), pattern="\\.[cChH]$",
+ full.names=TRUE, recursive=TRUE ),
+ list.files( file.path(path,"src","sources"), pattern="\\.[cChH]$",
+ full.names=TRUE, recursive=TRUE ))
+
+ # Create folder R_HOME_USER/pkgdev/pkgs/pkgName/src (if not existing)
+ dir.create(file.path(pkdev_path,"pkgs",pkgName,"src"), showWarnings=FALSE)
+
+ # Generate suitable Makefile (all object files go at R_HOME_USER/pkgdev/pkgs/pkgName/src)
+ .generateMakefileLoad(path, cFiles, pkgName, cc)
+
+ # Compile in the right folder (R_HOME_USER/pkgdev/pkgs/pkgName/src)
+ save_wd = getwd()
+ setwd( file.path(pkdev_path,"pkgs",pkgName,"src") )
+ library(parallel)
+ system( paste( Sys.getenv("MAKE"), "depend", sep=' ') )
+ system( paste( Sys.getenv("MAKE"), "-j", detectCores(), "all", sep=' ') )
+ setwd(save_wd)
+
+ # Finally load library
+ sharedLib =
+ file.path(pkdev_path,"pkgs",pkgName,paste(pkgName,.Platform$dynlib.ext,sep=''))
+ if (file.exists(pkgLoadFile)) dyn.unload(sharedLib)
+ dyn.load(sharedLib)
+ }
+
+ # Mark package as 'loaded'
+ writeLines("loaded",pkgLoadFile)
+}
+
+# Generate appropriate Makefile under R_HOME_USER/pkgdev/pkgs/pkgName/src
+.generateMakefileLoad = function(path, cFiles, pkgName, cc) {
+
+ # Preparation: separate cFiles into codes and headers
+ codeFiles = grep(".*(c|C)$", cFiles, value=TRUE)
+ headerFiles = grep(".*(h|H)$", cFiles, value=TRUE)
+
+ # objectFiles = all .o files in current folder, duplicating file structure under path/src/
+ basePathFrom = file.path(path, "src")
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ basePathTo = file.path(pkdev_path,"pkgs",pkgName,"src")
+ for (fileOrDir in list.files(basePathFrom, recursive=TRUE, include.dirs=TRUE)) {
+ if (file.info(file.path(basePathFrom,fileOrDir))$isdir) {
+ # Process folders only
+ dir.create(file.path(basePathTo,fileOrDir),showWarnings=FALSE,recursive=TRUE)
+ }
+ }
+ objectFiles = c()
+ for (codeFile in codeFiles) {
+ objectFiles = c(
+ objectFiles,
+ sub("(.*)\\.(c|C)$","\\1\\.o", sub(basePathFrom,basePathTo,codeFile,fixed=TRUE)))
+ }
+
+ # Build Makefile
+ makefile = paste('
+CC = ', cc, '
+INCLUDES = -I/usr/include/R/ -I/usr/local/include -I/usr/share/R/include
+LIBRARIES = -L/usr/lib -L/usr/lib/R/lib -lR -lm
+CFLAGS = -DNDEBUG -fpic -march=native -mtune=generic -O2 -pipe \\
+ -fstack-protector --param=ssp-buffer-size=4 -D_FORTIFY_SOURCE=2
+LDFLAGS = -shared -Wl,-O1,--sort-common,--as-needed,-z,relro
+LIB = ', paste(file.path("..",pkgName), .Platform$dynlib.ext, sep=''), '
+SRCS = ', paste(codeFiles, sep='', collapse=' '), '
+HEDS = ', paste(headerFiles, sep='', collapse=' '), '
+OBJS = ', paste(objectFiles, sep='', collapse= ' '), '
+all: $(LIB)
+$(LIB) : $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $(LIB) $(LIBRARIES)', sep='')
+ compileObjects = ""
+for (i in 1:length(codeFiles)) {
+ compileObjects = paste(compileObjects, '
+', objectFiles[i], ' : ', codeFiles[i], '
+ $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='')
+}
+ makefile = paste(makefile, compileObjects, '
+.PHONY: clean delib depend
+clean:
+ rm -f $(OBJS) ./.depend
+delib:
+ rm -f $(LIB)
+depend: .depend
+.depend: $(SRCS) $(HEDS)
+ rm -f ./.depend
+ $(CC) -MM $^ > ./.depend
+include .depend
+', sep='')
+
+ # Write it to disk
+ writeLines(makefile, file.path(pkdev_path,"pkgs",pkgName,"src","Makefile"))
+}
--- /dev/null
+# Core function to execute C unit tests (or just show functions names)
+.pkgdev.ctest = function(path, prefix, show, cc) {
+
+ # Initial step: list every potential unit test under path/src/tests.
+ allFuncNames = .parseCunitTests(file.path(path,"src","tests"))
+
+ # Filter functions names matching prefix
+ funcNames = grep( paste("^test_",prefix,sep=''), allFuncNames, value=TRUE )
+ if (length(funcNames) == 0) return #shortcut: nothing to do...
+
+ # If show==TRUE, display every potential test starting with prefix, and exit
+ if (show) {
+ #display in alphabetic order
+ return (paste(sort(funcNames), sep='\n'))
+ }
+
+ # Get package name from path
+ pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
+ pkgName = pathTokens[length(pathTokens)]
+
+ # Generate main.c to run exactly the tests asked by user
+ .generateMainCall(funcNames, pkgName)
+
+ # Get all C source files (src/tests/*, src/sources/* ...)
+ cFilesUser = c(
+ list.files(file.path(path,"src","tests"),pattern="\\.[cChH]$",
+ full.names=TRUE,recursive=TRUE),
+ list.files(file.path(path,"src","sources"),pattern="\\.[cChH]$",
+ full.names=TRUE,recursive=TRUE))
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ cFilesPkdev = c(
+ "main.c", #above generated main.c
+ file.path(pkdev_path,"src","tests","unitTestsMacros.c")) #C unit tests macros
+
+ # Now generate appropriate Makefile based on C sources and headers
+ pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
+ pkgName = pathTokens[length(pathTokens)]
+ .generateMakefileTest(path, cFilesUser, cFilesPkdev, pkgName, cc)
+
+ # Run selected tests (after 'funcs' filter applied)
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ save_wd = getwd()
+ setwd( file.path(pkdev_path,"pkgs",pkgName,"src","tests") )
+ library(parallel)
+ system( paste( Sys.getenv("MAKE"), "depend", sep=' ') )
+ system( paste( Sys.getenv("MAKE"), "-j", detectCores(), "all", sep=' ') )
+ system("./runTests")
+ setwd(save_wd)
+}
+
+# Recursively explore initial path to parse source files for unit tests.
+.parseCunitTests = function(path) {
+
+ # Unit test names to return
+ funcNames = c()
+
+ # For each file in current folder
+ for (fileName in list.files(path, full.names=TRUE, recursive=TRUE)) {
+
+ # If the file is not a source, skip
+ if ( length( grep("\\.[CcHh]$", fileName) ) == 0) next
+
+ # Every test function has a name starting with "test_"
+ matches = grep(
+ "^[ \t]*void[ \t]*test_[a-zA-Z0-9_]*[ \t]*\\(.*",
+ scan(fileName, what="character", sep='\n'),
+ value = TRUE)
+
+ # We matched more to be 100% sure we got test functions, but need to strip now
+ funcNames = c(funcNames, sub(
+ "^[ \t]*void[ \t]*(test_[a-zA-Z0-9_]*)[ \t]*\\(.*",
+ "\\1",
+ matches))
+ }
+
+ return (funcNames)
+}
+
+# Generate main.c file under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests
+.generateMainCall = function(funcNames, pkgName) {
+
+ # Build text file main.c
+ mainDotC = '
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h> // to print timings
+
+void main() {
+ clock_t start, end;
+'
+ for (funcName in funcNames) {
+ mainDotC = paste(mainDotC, "printf(\">>> Running ",funcName,"\\n\");\n",sep='')
+ mainDotC = paste(mainDotC, "start = clock();\n", sep='')
+ mainDotC = paste(mainDotC, funcName, "();\n", sep='')
+ mainDotC = paste(mainDotC, "end = clock();\n", sep='')
+ mainDotC = paste(mainDotC, "printf(\">>> ... completed in %.3fs.\\n\",((double) (end - start)) / CLOCKS_PER_SEC);\n", sep='')
+ }
+ mainDotC = paste(mainDotC, "}\n", sep='')
+
+ # Write it on disk
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ dir.create(file.path(pkdev_path,"pkgs",pkgName,"src","tests"), recursive=TRUE, showWarnings=FALSE)
+ writeLines(mainDotC, file.path(pkdev_path,"pkgs",pkgName,"src","tests","main.c"))
+}
+
+# Generate appropriate Makefile under R_HOME_USER/pkgdev/pkgs/pkgName/src/tests
+.generateMakefileTest = function(path, cFilesUser, cFilesPkdev, pkgName, cc) {
+
+ # Preparation: separate cFiles into codes and headers
+ codeFilesUser = grep(".*(c|C)$", cFilesUser, value=TRUE)
+ codeFilesPkdev = grep(".*(c|C)$", cFilesPkdev, value=TRUE)
+ headerFiles = grep(".*(h|H)$", c(cFilesUser,cFilesPkdev), value=TRUE)
+
+ # objectFiles = all .o files in current folder, duplicating file structure under path/src/
+ basePathFrom = file.path(path, "src")
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ basePathTo = file.path(pkdev_path,"pkgs",pkgName,"src","tests")
+ for (fileOrDir in list.files(basePathFrom, recursive=TRUE, include.dirs=TRUE)) {
+ if (file.info(file.path(basePathFrom,fileOrDir))$isdir) {
+ # Process folders only
+ dir.create(file.path(basePathTo,fileOrDir),showWarnings=FALSE,recursive=TRUE)
+ }
+ }
+ objectFiles = c()
+ for (codeFileUser in codeFilesUser) {
+ objectFiles = c(
+ objectFiles,
+ sub("(.*)\\.(c|C)$","\\1\\.o", sub(basePathFrom,basePathTo,codeFileUser,fixed=TRUE)))
+ }
+ for (codeFilePkdev in codeFilesPkdev) {
+ objectFiles = c(
+ objectFiles,
+ sub("(.*)\\.(c|C)$","\\1\\.o", codeFilePkdev))
+ }
+
+ # Build Makefile
+ makefile = paste('
+CC = ', cc, '
+INCLUDES =
+LIBRARIES = -lm
+CFLAGS = -g
+LDFLAGS =
+EXEC = runTests
+SRCS = ', paste(
+ paste(codeFilesUser,sep='',collapse=' '),
+ paste(codeFilesPkdev,sep='',collapse=' '),
+ sep=' '), '
+HEDS = ', paste(headerFiles, sep='', collapse=' '), '
+OBJS = ', paste(objectFiles, sep='', collapse= ' '), '
+all: $(EXEC)
+$(EXEC) : $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $(EXEC) $(LIBRARIES)', sep='')
+ compileObjects = ""
+lengthCodeFilesUser = length(codeFilesUser)
+for (i in 1:lengthCodeFilesUser) {
+ compileObjects = paste(compileObjects, '
+', objectFiles[i], ' : ', codeFilesUser[i], '
+ $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='')
+}
+for (i in 1:length(codeFilesPkdev)) {
+ compileObjects = paste(compileObjects, '
+', objectFiles[i+lengthCodeFilesUser], ' : ', codeFilesPkdev[i], '
+ $(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@', sep='')
+}
+ makefile = paste(makefile, compileObjects, '
+.PHONY: clean delex depend
+clean:
+ rm -f $(OBJS) ./.depend
+delex:
+ rm -f $(EXEC)
+depend: .depend
+.depend: $(SRCS) $(HEDS)
+ rm -f ./.depend
+ $(CC) -MM $^ > ./.depend
+include .depend
+', sep='')
+
+ # Write it to disk
+ writeLines(makefile, file.path(pkdev_path,"pkgs",pkgName,"src","tests","Makefile"))
+}
--- /dev/null
+# Core function to execute R unit tests (or just show functions names)
+.pkgdev.rtest = function(path, prefix, show, cc) {
+
+ # Initial step: list every potential unit test under path/R/tests.
+ allFuncNames = .parseRunitTests(file.path(path,"R","tests"))
+
+ # Filter functions names matching prefix
+ funcNames = grep( paste("^test\\.",prefix,sep=''), allFuncNames, value=TRUE )
+ if (length(funcNames) == 0) return #shortcut: nothing to do...
+
+ # If show==TRUE, display every potential test starting with prefix, and exit
+ if (show) {
+ #display in alphabetic order
+ return (paste(sort(funcNames), sep='\n'))
+ }
+
+ # Source all R unit test files
+ rFiles = list.files(file.path(path,"R","tests"),
+ pattern="\\.[RrSsq]$", full.names=TRUE, recursive=TRUE)
+ lapply(rFiles, source)
+
+ # Source R file containing unit tests methods
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ source(file.path(pkdev_path,"R","tests","unitTestsMethods.R"))
+
+ # Get package name from path
+ pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
+ pkgName = pathTokens[length(pathTokens)]
+
+ # This file tells if the package is currently loaded
+ pkgLoadFile = file.path(pkdev_path,"pkgs",pkgName,"loaded")
+
+ # Now manually load package (may not be installable if non-flat)
+ pkgAlreadyLoaded = file.exists(pkgLoadFile)
+ .pkgdev.load(path, cc)
+
+ # Run selected tests (after 'funcs' filter applied)
+ for (funcName in funcNames) {
+ cat(">>> Running ",funcName,"\n",sep='')
+ func = match.fun(funcName)
+ execTime = as.numeric(system.time(func())[1])
+ cat(">>> ... completed in ",execTime,"s.\n",sep='')
+ }
+
+ # Unload shared library if it wasn't already loaded
+ if (!pkgAlreadyLoaded) .pkgdev.unload(path)
+}
+
+# Recursively explore initial path to parse source files for unit tests.
+.parseRunitTests = function(path) {
+
+ # Unit test names to return
+ funcNames = c()
+
+ # For each file in current folder
+ for (fileName in list.files(path, full.names=TRUE, recursive=TRUE)) {
+
+ # If the file is not a source, skip
+ if ( length( grep("\\.[RrSsq]$", fileName) ) == 0) next
+
+ # Every test function has a name starting with "test."
+ matches = grep(
+ "^[ \t]*test\\.[a-zA-Z0-9_]*[ \t]*(=|<-)[ \t]*function.*",
+ scan(fileName, what="character", sep='\n', quiet=TRUE),
+ value = TRUE)
+
+ # We matched more to be 100% sure we got test functions, but need to strip now
+ funcNames = c(funcNames, sub(
+ "^[ \t]*(test\\.[a-zA-Z0-9_]*)[ \t]*(=|<-)[ \t]*function.*",
+ "\\1",
+ matches))
+ }
+
+ return (funcNames)
+}
--- /dev/null
+# Setup a file structure under R_HOME/pkgdev/ to run later tests
+# @param atInstall Logical, TRUE if invoked at package installation
+.pkgdev.setup = function(reset=FALSE) {
+
+ # environment variable R_HOME_USER must be set: everything starts here
+ if (Sys.getenv("R_HOME_USER") == "") {
+ cat("*** WARNING: for pkgdev to work properly, you need to specify\n")
+ cat("*** an environment variable R_HOME_USER in a .Renviron file.\n")
+ cat("*** Standard choice is /home/userName/.R under UNIX systems,\n")
+ cat("*** or maybe C:/Users/userName/Documents/R under Windows")
+ stop("Please specify R_HOME_USER before using pkgdev")
+ }
+
+ # create convenient folders and files, if not already existing
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+
+ # clean up: wipe possibly existing pkgdev/ contents
+ if (reset) {
+ unlink(pkdev_path, recursive=TRUE)
+ unlink(pkdev_path, recursive=TRUE) #bug?
+ }
+
+ # copy file structure only if directory is absent
+ if (file.exists(pkdev_path)) return (NULL)
+
+ # create testing file structure under pkgdev/
+ dir.create(pkdev_path)
+ dir.create( file.path(pkdev_path,"R") )
+ dir.create( file.path(pkdev_path,"R","tests") )
+ writeLines(RunitTestsMethods, file.path(pkdev_path,"R","tests","unitTestsMethods.R"))
+ dir.create( file.path(pkdev_path,"src") )
+ dir.create( file.path(pkdev_path,"src","tests") )
+ writeLines(CunitTestsMacros, file.path(pkdev_path,"src","tests","unitTestsMacros.c"))
+ dir.create( file.path(pkdev_path,"pkgs") )
+}
--- /dev/null
+# Convert a package with custom sub-folders to a valid CRAN package
+# @param inPath Input path: location of the package to flatten
+# @param outPath Output path: location of the package to create
+.pkgdev.tocran = function(inPath, outPath) {
+
+ # Gather all R source files (no tests)
+ fullPathRfiles = c()
+ forbiddenPath = file.path(inPath,"R","tests")
+ for (fileOrDir in list.files(file.path(inPath,"R"),full.names=TRUE)) {
+ if (fileOrDir != forbiddenPath) {
+ if (file.info(fileOrDir)$isdir) {
+ fullPathRfiles = c(
+ fullPathRfiles,
+ list.files(fileOrDir,pattern="\\.[RrSsq]$",
+ recursive=TRUE,full.names=TRUE))
+ }
+ else fullPathRfiles = c(fullPathRfiles, fileOrDir)
+ }
+ }
+ # Truncate paths: only suffix in pkgname/R/suffix is useful
+ fullPathRfiles = sub(
+ paste(inPath,.Platform$file.sep,"R",.Platform$file.sep,sep=''),
+ "",
+ fullPathRfiles,
+ fixed=TRUE)
+
+ # Transform rFiles until no folder separator can be found
+ rFiles = fullPathRfiles
+ while (length(grep(.Platform$file.sep,rFiles)) > 0) {
+ rFiles = lowerFileDepth(rFiles)
+ }
+
+ # Create and fill every non-sensible folder on output path
+ unlink(outPath, recursive=TRUE) #in case of [TODO: warn user]
+ dir.create(outPath, showWarnings=FALSE)
+ forbiddenPath = "R"
+ for (fileOrDir in list.files(inPath)) {
+ if (fileOrDir != forbiddenPath) {
+ if (file.info(file.path(inPath,fileOrDir))$isdir) {
+ dir.create(file.path(outPath,fileOrDir), showWarnings=FALSE, recursive=TRUE)
+ file.copy(file.path(inPath,fileOrDir), file.path(outPath),recursive=TRUE)
+ }
+ else file.copy(file.path(inPath,fileOrDir), file.path(outPath))
+ }
+ }
+
+ # Prepare R folder (empty for the moment)
+ dir.create( file.path(outPath,"R") )
+
+ # Copy "flattened" files to R/
+ for (i in 1:length(rFiles)) {
+ file.copy( file.path(inPath,"R",fullPathRfiles[i]),
+ file.path(outPath,"R",rFiles[i]) )
+ }
+
+ # Optional processing if /src is present
+ if (file.exists(file.path(inPath,"src"))) {
+
+ # Gather all C code files (NOT including headers; no tests)
+ cCodeFiles = c()
+ forbiddenPath = file.path(inPath,"src","tests")
+ for (fileOrDir in list.files(file.path(inPath,"src"),full.names=TRUE)) {
+ if (fileOrDir != forbiddenPath) {
+ if (file.info(fileOrDir)$isdir) {
+ cCodeFiles = c(
+ cCodeFiles,
+ list.files(fileOrDir,pattern="\\.[Cc]$",
+ recursive=TRUE,full.names=TRUE))
+ }
+ else cCodeFiles = c(cCodeFiles, fileOrDir)
+ }
+ }
+ # Truncate paths: only suffix in pkgname/R/suffix is useful
+ cCodeFiles = sub(
+ paste(inPath,.Platform$file.sep,"src",.Platform$file.sep,sep=''),
+ "",
+ cCodeFiles,
+ fixed=TRUE)
+
+ # Add a 'Makevars' file under src/ to allow compilation by R CMD INSTALL
+ makevars = paste(
+ paste("SOURCES","=",paste(cCodeFiles,sep='',collapse=' '),"\n",sep=' '),
+ paste("OBJECTS","=","$(SOURCES:.c=.o)","\n",sep=' '),
+ sep='\n')
+ writeLines(makevars, file.path(outPath,"src","Makevars"))
+ }
+}
+
+# NOTE: rule = sort according to first subfolder, then place 1 for the first ...etc;
+# for example tr1/tr2/tr3/file.c --> tr2/tr3/file_1.c --> tr3/file_12.c ...etc
+lowerFileDepth = function(files) {
+
+ # Sort files according to their prefix paths
+ sortedFiles = sort(files, index.return=TRUE)
+
+ # Truncate paths if required
+ folderKount = 0
+ lastFolder = ""
+ for (i in 1:length(files)) {
+ if (length(grep(.Platform$file.sep, sortedFiles$x[i], fixed=TRUE)) > 0) {
+ prefix = strsplit(sortedFiles$x[i], c(.Platform$file.sep))[[1]][1]
+ if (prefix != lastFolder) {
+ folderKount = folderKount + 1
+ lastFolder = prefix
+ }
+ sortedFiles$x[i] = paste(
+ #truncate base name
+ substr(sortedFiles$x[i],nchar(prefix)+2,nchar(sortedFiles$x[i])-2),
+ #add [sub-]folder identifier
+ folderKount,
+ #add suffix (.R, .r ...) de
+ substr(sortedFiles$x[i],nchar(sortedFiles$x[i])-1,nchar(sortedFiles$x[i])),
+ sep='')
+ }
+ }
+
+ # Return transformed files list ranked as in input (unsorted)
+ for (i in 1:length(files)) {
+ files[ sortedFiles$ix[i] ] = sortedFiles$x[i]
+ }
+ return (files)
+}
--- /dev/null
+# Core function to unload a package (UNsource R files and unload C libraries)
+# @param path Location or name of the (non-standard) package to be unloaded
+.pkgdev.unload = function(path) {
+
+ # Get package name from path
+ pathTokens = strsplit(path, c(.Platform$file.sep))[[1]]
+ pkgName = pathTokens[length(pathTokens)]
+
+ # This file tells if the package is currently loaded
+ pkdev_path = file.path(Sys.getenv("R_HOME_USER"), "pkgdev")
+ pkgLoadFile = file.path(pkdev_path,"pkgs",pkgName,"loaded")
+
+ # Unload shared library (if any)
+ if (file.exists(pkgLoadFile)) {
+ sharedLib = file.path(pkdev_path,"pkgs",pkgName,paste(pkgName,.Platform$dynlib.ext,sep=''))
+ dyn.unload(sharedLib)
+ unlink(pkgLoadFile)
+ }
+}
--- /dev/null
+# Main wrapped entry point: call package function 'func'
+.execMethod = function(func, ...) {
+
+ # check for R_HOME_USER variable, and for R_HOME_USER/pkgdev folder
+ .pkgdev.setup()
+
+ # get appropriate core function and call it
+ func = match.fun(func)
+ func(...)
+}
+
+#' Reset pkgdev folder under R_HOME_USER
+#' WARNING: all loaded packages will have to be rebuilt
+pkgdev.wipeAll = function() {
+ .pkgdev.setup(reset=TRUE)
+}
+
+#' Load a package containing arbitrary file structures under R/ and src/{adapters,sources}
+#'
+#' @param path Location of the package to load
+#' @param cc Compilator to be used (e.g. 'gcc -std=gnu99' [default])
+pkgdev.load = function(path, cc="gcc -std=gnu99") {
+ path = normalizePath(path) #allow to give only pkg name (in pkg/.. folder)
+ .execMethod(".pkgdev.load", path, cc)
+}
+
+#' Unload a package containing arbitrary file structures under R/ and src/{adapters,sources}
+#'
+#' @param path Location or name of the package to unload
+pkgdev.unload = function(path) {
+ path = normalizePath(path) #allow to give only pkg name (in pkg/.. folder)
+ .execMethod(".pkgdev.unload", path)
+}
+
+#' Wipe a specific package under R_HOME_USER/pkgdev/pkgs/
+#' NOTE: when this package will be loaded again, it will be completely rebuilt
+#'
+#' @param pkgName Name of the package to be removed
+pkgdev.clean = function(pkgName) {
+ unlink(file.path(Sys.getenv("R_HOME_USER"),"pkgdev","pkgs",pkgName), recursive=TRUE)
+ unlink(file.path(Sys.getenv("R_HOME_USER"),"pkgdev","pkgs",pkgName), recursive=TRUE) #bug?
+}
+
+#' Launch R unit tests (arbitrary file structure under R/tests), or display the list of test functions
+#'
+#' @param path Location of the package containing tests (under /R/tests)
+#' @param prefix Prefix for names of the functions to be tested; leave empty to test all (default)
+#' @param show Logical, TRUE to display the list of unit tests (default: FALSE)
+pkgdev.rtest = function(path, prefix="", show=FALSE, cc="gcc -std=gnu99") {
+ path = normalizePath(path) #allow to give only pkg name (in pkg/.. folder)
+ .execMethod(".pkgdev.rtest", path, prefix, show, cc)
+}
+
+#' Launch C unit tests (arbitrary file structure under src/tests), or display the list of test functions
+#'
+#' @param path Location of the package containing tests (under /src/tests)
+#' @param prefix Prefix for names of the functions to be tested; leave empty to test all (default)
+#' @param show Logical, TRUE to display the list of unit tests (default: FALSE)
+pkgdev.ctest = function(path, prefix="", show=FALSE, cc="gcc -std=gnu99") {
+ .execMethod(".pkgdev.ctest", path, prefix, show, cc)
+}
+
+#' "Flatten" a package: gather all sources under R/ and src/ without hierarchical file structure
+#'
+#' @param inPath Input path: location of the package to flatten
+#' @param outPath Output path: location of the package to create [default: inPath_cran]
+pkgdev.tocran = function(inPath, outPath=NULL) {
+ inPath = normalizePath(inPath) #allow to give only pkg name (in pkg/.. folder)
+ if (is.null(outPath)) outPath = paste(inPath, "_cran", sep='')
+ .execMethod(".pkgdev.tocran", inPath, outPath)
+}
+
+#' Invoke R CMD check on a flat package (maybe with optional arguments, like --as-cran)
+#'
+#' @param opts Vector of strings arguments to pass to R CMD CHECK
+pkgdev.check = function(path, opts) {
+ path = normalizePath(path) #allow to give only pkg name (in pkg/.. folder)
+ system( paste("R CMD check", opts, path, sep=' ') )
+}