Mark is a graph advocate and field engineer for Neo Technology, the company behind the Neo4j graph database. As a field engineer, Mark helps customers embrace graph data and Neo4j building sophisticated solutions to challenging data problems. When he's not with customers Mark is a developer on Neo4j and writes his experiences of being a graphista on a popular blog at He tweets at @markhneedham. Mark is a DZone MVB and is not an employee of DZone and has posted 532 posts at DZone. You can read more from them at their website. View Full User Profile

Sbt: Zipping files without their directory structure

  • submit to reddit

We’re using SBT on our project and Pat and I have been trying to work out how to zip together some artifacts so that they’re all available from the top level of the zip file i.e. we don’t want to copy the directory structure where the files come from.

I’ve been playing around with this in the Scala REPL which we can launch with our project’s dependencies loaded with the following command:

./sbt console-project

Our original attempt to zip together the artifacts looked like this:"ops" / "deploy")), "", true, log)

But unfortunately that keeps the directory structure which isn’t what we want!

mneedham@markneedham.home ~/Projects/core$ unzip -l 
Length Date Time Name
-------- ---- ---- ----
0 06-04-11 17:52 ops/
0 06-04-11 17:52 ops/deploy/
2534 06-03-11 17:47 ops/deploy/
-------- -------
2534 3 files

Pat figured out that what we needed to do was make use of the ## function after our path so our code would read like this:"ops" / "deploy") ##), "", true, log)

Et voila:

mneedham@markneedham.home ~/Projects/core$ unzip -l 
Length Date Time Name
-------- ---- ---- ----
2534 06-03-11 17:47
-------- -------
2534 1 file

The ## function is defined like so and converts a path object into a BaseDirectory:

override def ## : Path = new BaseDirectory(this)

The code in FileUtilities that generates an entry for each file in the zip file looks like this:

     def makeFileEntry(path: Path) =
val relativePath = path.relativePathString("/")
log.debug("\tAdding " + path + " as " + relativePath + " ...")

val e = createEntry(relativePath)
e setTime path.lastModified

def addFileEntry(path: Path)
val file = path.asFile
output putNextEntry makeFileEntry(path)
transferAndClose(new FileInputStream(file), output, log)
log.warn("\tFile " + file + " does not exist.")

Line 179 is where the meta data is defined for the archive and it makes use of “relativePathString” which has been overriden by BaseDirectory to return “”:

private final class BaseDirectory(private[sbt] val path: Path) extends Path
override def ## : Path = this
override def toString = path.toString
def asFile = path.asFile
def relativePathString(separator: String) = ""
def projectRelativePathString(separator: String) = path.projectRelativePathString(separator)
private[sbt] def prependTo(s: String) = "." + sep + s

Line 176 returns the file in its original location so it can still be copied into the archive.

The problem with using an identifier like ## is that it’s very difficult to Google so you end up trawling the source code for its uses or hoping that you can find the explanation for its use in the documentation!


Published at DZone with permission of Mark Needham, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)