|
Paths
sbt has movedsbt has now completely moved to GitHub. See https://github.com/harrah/xsbt/wiki. ExamplesThe 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.classSimple Pathsval 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 / scalaDirectoryNameThe 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 Findersval 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.isArchivePath.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:
Base PathsIn 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 +++ allResourcesIgnoring the .svn directories, the paths in the jar would then look like: LICENSE
logo.png
sbt/
Project.class
Analyzer.classNote that the ## operator is also defined for PathFinders. Excluding PathsA 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 = (".*" - ".") || HiddenFileFilterYou 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 FiltersThere 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 | |
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?
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
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)).
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:
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
Exactly where do these new xsbt classes integrate with the sbt? Functions such as mainResources, etc. expect a PathFinder?.
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
-Mark
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.
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.
how do i specify an absolute path???
Never mind, Path.fromFile(path) can be used to specify an abs path.