1. Paths

Paths 

This page describes files, sequences of files, and file filters. The base type used is java.io.File, but several methods are augmented through implicits:

  • RichFile adds methods to File
  • PathFinder adds methods to File and Seq[File]
  • Path and IO provide general methods related to files and I/O.

Constructing a File 

sbt uses java.io.File to represent a file and defines the type alias File for java.io.File so that an extra import is not necessary. The file method is an alias for the single-argument File constructor to simplify constructing a new file from a String:

val source: File = file("/home/user/code/A.scala")

Additionally, sbt augments File with a / method, which is an alias for the two-argument File constructor for building up a path:

def readme(base: File): File = base / "README"

Relative files should only be used when defining the base directory of a Project, where they will be resolved properly.

val root = Project("root", file("."))

Elsewhere, files should be absolute or be built up from an absolute base File. The baseDirectory setting defines the base directory of the build or project depending on the scope.

For example, the following setting sets the unmanaged library directory to be the “custom_lib” directory in a project’s base directory:

unmanagedBase := baseDirectory.value /"custom_lib"

Or, more concisely:

unmanagedBase := baseDirectory.value /"custom_lib"

This setting sets the location of the shell history to be in the base directory of the build, irrespective of the project the setting is defined in:

historyPath := Some( (ThisBuild / baseDirectory).value / ".history"),

Path Finders 

A PathFinder computes a Seq[File] on demand. It is a way to build a sequence of files. There are several methods that augment File and Seq[File] to construct a PathFinder. Ultimately, call get on the resulting PathFinder to evaluate it and get back a Seq[File].

Selecting descendants 

The ** method accepts a java.io.FileFilter and selects all files matching that filter.

def scalaSources(base: File): PathFinder = (base / "src") ** "*.scala"

get 

This selects all files that end in .scala that are in src or a descendent directory. The list of files is not actually evaluated until get is called:

def scalaSources(base: File): Seq[File] = {
  val finder: PathFinder = (base / "src") ** "*.scala" 
  finder.get
}

If the filesystem changes, a second call to get on the same PathFinder object will reflect the changes. That is, the get method reconstructs the list of files each time. Also, get only returns Files that existed at the time it was called.

Selecting children 

Selecting files that are immediate children of a subdirectory is done with a single *:

def scalaSources(base: File): PathFinder = (base / "src") * "*.scala"

This selects all files that end in .scala that are in the src directory.

Existing files only 

If a selector, such as /, **, or *, is used on a path that does not represent a directory, the path list will be empty:

def emptyFinder(base: File) = (base / "lib" / "ivy.jar") * "not_possible"

Name Filter 

The argument to the child and descendent selectors * and ** is actually a NameFilter. An implicit is used to convert a String to a NameFilter that interprets * to represent zero or more characters of any value. See the Name Filters section below for more information.

Combining PathFinders 

Another operation is concatenation of PathFinders:

def multiPath(base: File): PathFinder =
   (base / "src" / "main") +++
   (base / "lib") +++
   (base / "target" / "classes")

When evaluated using get, this will return src/main/, lib/, and target/classes/. The concatenated finder supports all standard methods. For example,

def jars(base: File): PathFinder =
   (base / "lib" +++ base / "target") * "*.jar"

selects all jars directly in the “lib” and “target” directories.

A common problem is excluding version control directories. This can be accomplished as follows:

def sources(base: File) =
   ( (base / "src") ** "*.scala") --- ( (base / "src") ** ".svn" ** "*.scala")

The first selector selects all Scala sources and the second selects all sources that are a descendent of a .svn directory. The --- method removes all files returned by the second selector from the sequence of files returned by the first selector.

Filtering 

There is a filter method that accepts a predicate of type File => Boolean and is non-strict:

// selects all directories under "src"
def srcDirs(base: File) = ( (base / "src") ** "*") filter { _.isDirectory }

// selects archives (.zip or .jar) that are selected by 'somePathFinder'
def archivesOnly(base: PathFinder) = base filter ClasspathUtilities.isArchive

Empty PathFinder 

PathFinder.empty is a PathFinder that returns the empty sequence when get is called:

assert( PathFinder.empty.get == Seq[File]() )

PathFinder to String conversions 

Convert a PathFinder to a String using one of the following methods:

  • toString is for debugging. It puts the absolute path of each component on its own line.
  • absString gets the absolute paths of each component and separates them by the platform’s path separator.
  • getPaths produces a Seq[String] containing the absolute paths of each component

Mappings 

The packaging and file copying methods in sbt expect values of type Seq[(File,String)] and Seq[(File,File)], respectively. These are mappings from the input file to its (String) path in the jar or its (File) destination. This approach replaces the relative path approach (using the ## method) from earlier versions of sbt.

Mappings are discussed in detail on the Mapping-Files page.

File Filters 

The argument to * and ** is of type java.io.FileFilter. sbt provides combinators for constructing FileFilters.

First, a String may be implicitly converted to a FileFilter. The resulting filter selects files with a name matching the string, with a * in the string interpreted as a wildcard. For example, the following selects all Scala sources with the word “Test” in them:

def testSrcs(base: File): PathFinder =  (base / "src") * "*Test*.scala"

There are some useful combinators added to FileFilter. The || method declares alternative FileFilters. The following example selects all Java or Scala source files under “src”:

def sources(base: File): PathFinder  =  (base / "src") ** ("*.scala" || "*.java")

The -- method excludes a files matching a second filter from the files matched by the first:

def imageResources(base: File): PathFinder =
   (base/"src"/"main"/"resources") * ("*.png" -- "logo.png")

This will get right.png and left.png, but not logo.png, for example.