The rprojroot package solves a seemingly trivial but annoying problem that occurs sooner or later in any largish project: How to find files in subdirectories? Relative file paths are almost always preferable to absolute paths, but relative to what should they be? Unfortunately, we cannot always be sure about the value of the working directory: For instance, in RStudio, sometimes it’s the project root (when running R scripts), sometimes a subdirectory (when building vignettes), sometimes again the project root (when executing chunks of a vignette):

basename(getwd())
## [1] "vignettes"

If we could only get hold of our…

Project root

The root of a project is defined as a directory that contains a regular file whose name matches a given pattern and whose contents optionally match another pattern. Thus, the following method reliably finds our project root:

The Git version control system (and probably many other tools) use a similar approach: A Git command can be executed from within any subdirectory of a repository.

A simple example

Note that the following code produces identical results when building the vignette and when sourcing the chunk, provided that the project root is the current working directory or a parent thereof:

library(rprojroot)
basename(find_root("DESCRIPTION"))
## [1] "rprojroot"
file.exists(find_root_file("R", "root.R", filename = "DESCRIPTION"))
## [1] TRUE

Practical use

You can save some time for RStudio projects and packages:

print(find_package_root_file)
## function (..., path = getwd()) 
## {
##     find_root_file(..., filename = "^DESCRIPTION$", contents = "^Package: ", 
##         n = 1L, path = path)
## }
## <environment: 0x3bb73e8>
head(readLines(find_package_root_file("vignettes", "rprojroot.Rmd")))
## [1] "---"                               "title: \"Finding files reliably\""
## [3] "author: \"Kirill Müller\""         "date: \"`r Sys.Date()`\""         
## [5] "output: rmarkdown::html_vignette"  "vignette: >"

If you’re lazy, define a shortcut:

P <- find_package_root_file
file.exists(P("vignettes", "rprojroot.Rmd"))
## [1] TRUE

Legacy code can benefit immediately if the file.path function is overwritten.

You might also want to define your project root differently (this fails here – we look for a file named LICENSE which is absent):

R <- make_find_root_file(glob2rx("LICENSE"))
R
## function (..., path = getwd()) 
## {
##     find_root_file(..., filename = "^LICENSE$", contents = NULL, 
##         n = -1L, path = path)
## }
## <environment: 0x29de7b0>
class(try(dir.exists(R("man"))))
## [1] "try-error"

Fixed root

If there is only one root, things get even simpler: We can create a function that computes a path relative to the root at creation time.

F <- make_fix_root_file(glob2rx("DESCRIPTION"))
formals(F)
## $...
readLines(F("NAMESPACE"))
## [1] "# Generated by roxygen2 (4.1.1): do not edit by hand"
## [2] ""                                                    
## [3] "export(find_package_root_file)"                      
## [4] "export(find_root)"                                   
## [5] "export(find_root_file)"                              
## [6] "export(find_rstudio_root_file)"                      
## [7] "export(make_find_root_file)"                         
## [8] "export(make_fix_root_file)"

This even works if we later change the working directory to somewhere outside the project:

local({
  oldwd <- setwd("../..")
  on.exit(setwd(oldwd), add = TRUE)
  file.size(F("NAMESPACE"))
})
## [1] 212

Summary

The rprojroot package allows easy access to files below a project root which is supposed to be the only directory in the whole hierarchy that contains a specific file. This is a robust solution for finding project files in largish projects with a subdirectory hierarchy if the current working directory cannot be assumed fixed. (However, at least initially, the current working directory must be somewhere below the project root.)

Acknowledgement

This package was inspired by the “Stop working directory insanity” gist by Jennifer Bryan, and by the way Git knows where its files are.