My favorites | Sign in
Project Home Downloads Wiki Issues
Search
for

sbt has moved

sbt has now completely moved to GitHub.

See https://github.com/harrah/xsbt/wiki.

Getting Started

Paths  
Updated Jul 27, 2011 by dmhar...@gmail.com

sbt has moved

sbt has now completely moved to GitHub.

See https://github.com/harrah/xsbt/wiki.

Examples

The examples are written in the context of a Project definition and assume the following directory structure:

project/
    .svn/
    src/
        .svn/
        main/
            .svn/
            resources/
                .svn/
                LICENSE
                logo.png
                left.png
                right.png
            scala/
                .svn/
                sbt/
                    .svn/
                    Project.scala
                    Analyzer.scala
    lib/
        ivy.jar
    target/
        sbt.jar
        classes/
            sbt/
                Project.class
                Analyzer.class

Simple Paths

val p = "src" / "main" / "scala" / "sbt" / "Analyzer.scala"

Here, "src" is converted to a Path relative to the project directory by an implicit conversion defined in Project. Alternatively, the implicit conversion may be called directly:

val p = path("src") / "main" / "scala" / "sbt" / "Analyzer.scala"

Either back or forward slashes may be used (they are equivalent):

val p = "src" \ "main" \ "scala" \ "sbt" \ "Analyzer.scala"

Typical usage in sbt is to build up paths:

    def sourcePath = path(sourceDirectoryName)
    def mainSourcePath = sourcePath / mainDirectoryName
    def mainScalaSourcePath = mainSourcePath / scalaDirectoryName

The argument to \ may not contain slashes. If you want to make a relative path that includes slashes, use Path.fromString:

    def aPath: Path = "some" / "path"
    def relPath: Path = Path.fromString(aPath, "a/sub/path")

To convert a java.io.File to a Path, use Path.fromFile. To convert a Path to a File, use asFile. For example:

    def aPath: Path = "some" / "path"
    def aFile: File = aPath.asFile

    def tmpFile: File = new File(System.getProperty(java.io.tmpdir))
    def pathFromFile: Path = Path.fromFile(tmpFile)

Path Finders

val scalaSources: PathFinder = "src" ** "*.scala"

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

val sourceSet: Set[Path] = scalaSources.get

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

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

val scalaSources = mainScalaSourcePath / "sbt" * "*.scala"

If a selector is used on a path that does not represent a directory, the path list will be empty:

val emptyFinder = "lib" / "ivy.jar" * "not_possible"

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 for more information.

Another operation is concatenation of PathFinders:

val multiPath = ("src" / "main") +++ "lib" +++ ("target" / "classes")

The concatenated finder supports all standard operators. For example,

val jars = ("lib" +++ "target") * "*.jar"

selects ivy.jar and sbt.jar.

There is a filter method that is non-strict:

val srcDirs = ("src" ** "*") filter { _.isDirectory }
val archivesOnly = somePathFinder filter ClasspathUtilities.isArchive

Path.emptyPathFinder is a PathFinder that returns the empty set when get is called. To construct a PathFinder from Iterable[Path], use Path.lazyPathFinder. This method is call-by-name, so its argument will be re-evaluated on each call to get.

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.
  • relativeString gets the relative paths of each component and separates them by the platform's path separator.
  • absString gets the absolute paths of each component and separates them by the platform's path separator.

Base Paths

In some situations, it is useful to define the directory a path is relative to. For example, the package action in sbt packages compiled classes and all files under resources. The full path name should not be used in the jar, however. This is where the ## operator comes in. The paths for this situation would look like:

val allClasses = ("target" / "classes" ##) ** "*.class"
val allResources = ("src" / "main" / "resources" ##) ** "*"
val toPackage = allClasses +++ allResources

Ignoring the .svn directories, the paths in the jar would then look like:

LICENSE
logo.png
sbt/
    Project.class
    Analyzer.class

Note that the ## operator is also defined for PathFinders.

Excluding Paths

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

def sources = ("src" ** "*.scala") --- ("src" ** ".svn" ** "*.scala")

The first selector selects all Scala sources and the second removes all sources that are a descendent of a .svn directory. This pattern is common enough that there is a method descendentsExcept that does the same thing as the above:

def sources = "src".descendentsExcept("*.scala", ".svn")

Two methods are available for use in a project definition to consistently exclude paths. They are descendents and defaultExcludes. defaultExcludes provides a filter that descendents uses to exclude paths from its results. The definitions of descendents and defaultExcludes are:

def descendents(parent: PathFinder, include: FileFilter) = parent.descendentsExcept(include, defaultExcludes)
def defaultExcludes: FileFilter = (".*"  - ".") || HiddenFileFilter

You could use this for the above examples like:

def sources = descendents("src", "*.scala")

If you want to change the default exclusions, you can do so by overriding defaultExcludes. For example,

override def defaultExcludes = super.defaultExcludes || "*~"

Name and File Filters

There are some useful operations on NameFilters. The | operator declares alternative NameFilters:

def sources = "src" ** ("*.scala" | "*.java")

The - operator excludes a set of names:

def imageResources = "src"/"main"/"resources" * ("*.png" - "logo.png")

This will get right.png and left.png. - is also available as a prefix operator.

NameFilter can be used for filtering Strings in general using its accept method directly. Additionally NameFilter is a subclass of java.io.FileFilter by way of sbt.FileFilter, which is a thin wrapper around java.io.FileFilter that provides operators for combining filters. Infix operators for sbt.FileFilter are doubled to distinguish them from the operators for NameFilter.

For example:

def filterA: sbt.FileFilter = ...
def filterB: sbt.FileFilter = ...

def filterC = filterA -- filterB
def filterD = filterA || filterB
def filterE = filterA && filterB
Comment by saurabh....@gmail.com, Nov 25, 2009

There is an ant project whose build file copies "<project_root>/src/charset/java.nio.charset.spi.CharsetProvider?" to "META-INF/services/java.nio.charset.spi.CharsetProvider?"

So this is a resource located at: ("src" / "charset" ##) "" In short Path already has a semantics regarding how it is represented when packaged. How can I also specify that this resource should be put in "META-INF/services" and not in package root? Not crucial now ... but perhaps even renamed when packaged?

Comment by project member dmhar...@gmail.com, Nov 25, 2009

Indeed. This is probably the biggest limitation of the current Path API that I know of. What I'm trying experimental 0.6.x series looks something like:

"src/charset/java.nio.charset.spi.CharsetProvider" x rebase("src/charset/", "META-INF/services")

where rebase replaces its first argument with the second. More complicated mappings are possible (the 'x' method accepts a PathMapper or FileMapper depending on whether you want a String or File as output), but hopefully that illustrates the idea. Feedback is welcome!

-Mark

Comment by saurabh....@gmail.com, Nov 25, 2009

How experimental is the experimental 0.6.x series? I'll try using this feature. The typical use cases that I have seen are related to changing of base path & renaming of files (both in a fixed way (e.g. rename from a.xml to b.xml) & in a regex kind of way (e.g. removal of suffix)).

Comment by project member dmhar...@gmail.com, Nov 25, 2009

The main issue right now is that we are working on a uniform interface to test frameworks, so you have to use snapshot versions of test libraries. The 0.6.x series is no longer experimental in the sense that there is uncertainty about whether it will work out, though. It is more that there is some work to get it set up and there might still be some changes before it is stable. In particular:

  • you probably have to read the source for new APIs
  • watch the mailing list for subjects with 0.6 in them

Have a look in http://github.com/harrah/xsbt/tree/master/util/io/ to see the new I/O stuff. It operates on File and SetFile? instead of using a separate Path class. I'm happy to document/write examples on the new I/O stuff if you end up wanting to try it out. It lies in the xsbt namespace so it doesn't conflict with existing things.

See http://code.google.com/p/simple-build-tool/wiki/0_6_Summary for getting set up. The latest version is 0.6.3.

Thanks, Mark

Comment by saurabh....@gmail.com, Dec 1, 2009

Exactly where do these new xsbt classes integrate with the sbt? Functions such as mainResources, etc. expect a PathFinder?.

Comment by project member dmhar...@gmail.com, Dec 1, 2009

The new classes operate on File and SetFile?. The new FileUtilities? operates on these as well. So, you can directly call FileUtilities?.jar or zip.

I would have to replace Paths and PathFinder? everywhere in order to make mainResources use SetFile?. This is a big breaking change and would only happen if people use the new classes and like them much better.

If you need to go from SetFile? to PathFinder?, use a method like

 def asPath(paths: => Set[File]) =
   Path.lazyPathFinder( paths.map(Path.fromFile) )

-Mark

Comment by saurabh....@gmail.com, Dec 2, 2009

I see. So I would have to write my own package task where I will use set of (file,filename) obtained from PathMapper?'s. Also I would have to add the same files to watch path. I will try this.

It would be cool to see it work integrated with sbt. Any set timelines when I can expect it?

This would be a pretty big blocker for anyone migrating from Ant, I would guess. Since, unlike Maven, (for better or worse) people are more susceptible to pull resources from various folders, rename them, etc.

Comment by kru...@gmail.com, Aug 3, 2010

how do i access the resource path from my project? At first I thought it would be the default path where the jvm is looking for input files, but it isn't.

Comment by jcrinc...@gmail.com, Oct 1, 2010

how do i specify an absolute path???

Comment by jcrinc...@gmail.com, Oct 4, 2010

Never mind, Path.fromFile(path) can be used to specify an abs path.

Powered by Google Project Hosting