package cbt
import java.io.File
import java.nio.file.{FileSystems, Files, Path}
import java.nio.file.Files._
import java.util.jar.JarFile
trait UberJar extends PackageJars {
lazy val uberJarLib = new UberJarLib(lib, logger.log("uberjar", _))
def uberJarName: String = name ++ "-" ++ version ++ "-uberjar"
def uberJarMainClass: Option[String] = None // to avoid asking interactively
def uberJar: Option[File] =
uberJarLib.create( target / uberJarName ++ ".jar", classpath, uberJarMainClass )
}
class UberJarLib(lib: cbt.Lib, log: String => Unit) {
/**
* Bundles the given classpath into a single jar (uber jar / fat jar).
*
* @param jarFile name of jar to create
* @param classpath class path to bundle into the jar
*/
def create( jarFile: File, classpath: ClassPath, mainClass: Option[String] ): Option[File] = {
System.err.println("Creating uber jar...")
log(s"Classpath: ${classpath.string}")
log( mainClass.map("Main class is: "+_).getOrElse("no Main class") )
val (dirs, jars) = classpath.files.partition(_.isDirectory)
log(s"Found ${jars.length} jars in classpath: \n${jars mkString "\n"}")
log(s"Found ${dirs.length} directories in classpath: \n${dirs mkString "\n"}")
val extracted = Files.createTempDirectory("unjars").toFile.getCanonicalFile
log("Extracting jars to $extracted")
jars.foreach( extractJar(_, extracted.toPath) )
log("Extracting jars - DONE")
log("Writing jar file...")
val uberJar = lib.createJar(jarFile, dirs :+ extracted, mainClass=mainClass)
log("Writing uber jar - DONE")
extracted.deleteRecursive
System.err.println(lib.green("Creating uber jar - DONE"))
uberJar
}
/**
* Extracts contents of single jar file into a destination directory.
* When extracting the jar, if the same file already exists, skips the file.
* TODO: allow custom strategies to resolve duplicate files, not only skipping.
* TODO: do not hard code excluded files.
*
* @param jarFile jar file to extract
*/
private def extractJar(jarFile: File, destination: Path): Unit = {
val excludeFilter: File => Boolean = {
f => f.getName == "META-INF" || Seq(".RSA",".DSA",".SF",".MF").exists(f.getName.endsWith)
}
log(s"Extracting $jarFile")
val jar = new JarFile(jarFile)
import collection.JavaConverters._
val entries = jar.entries.asScala.filterNot(_.isDirectory).toVector
val paths = entries.map( e => destination.resolve(e.getName) ) // JarFile.entry is the full path, not just file name
paths.map( _.getParent ).distinct.foreach( createDirectories(_) )
(entries zip paths).foreach{
case( entry, path ) =>
if( excludeFilter(path.toFile) ){
log(s"Excluded $path")
} else if (exists(path)) {
log(s"Already exists, skipping $path")
} else {
val is = jar.getInputStream(entry)
try{ Files.copy(is, path) }
finally{ is.close }
}
}
}
}