package cbt
import java.io.File
import java.nio.file.{FileSystems, Files, Path}
import java.util.jar.JarFile
trait UberJar extends BaseBuild {
final def uberJar: ExitCode = {
System.err.println("Creating uber jar...")
new UberJarLib(logger).create(target, classpath, uberJarMainClass, uberJarName)
System.err.println(lib.green("Creating uber jar - DONE"))
ExitCode.Success
}
def uberJarMainClass: Option[String] = runClass
def uberJarName: String = name + ".jar"
}
class UberJarLib(logger: Logger) {
private val (jarFileMatcher, excludeFileMatcher) = {
val fs = FileSystems.getDefault
(fs.getPathMatcher("glob:**.jar"), fs.getPathMatcher("glob:**{.RSA,.DSA,.SF,.MF,META-INF}"))
}
private val log: String => Unit = logger.log("uber-jar", _)
private val lib = new cbt.Lib(logger)
/**
* Creates uber jar for given build.
*
* @param target build's target directory
* @param classpath build's classpath
* @param mainClass optional main class
* @param jarName name of resulting jar file
*/
def create(target: File,
classpath: ClassPath,
mainClass: Option[String],
jarName: String): Unit = {
log(s"Classpath is: $classpath")
log(s"Target directory is: $target")
log(s"Jar name is: $jarName")
mainClass foreach (c => log(s"Main class is is: $c"))
val (jars, dirs) = classpath.files partition (f => jarFileMatcher.matches(f.toPath))
log(s"Found ${jars.length} jar dependencies: \n ${jars mkString "\n"}")
log(s"Found ${dirs.length} directories in classpath: \n ${dirs mkString "\n"}")
log("Extracting jars...")
val extractedJarsRoot = extractJars(jars.distinct)(log).toFile
log("Extracting jars - DONE")
log("Writing jar file...")
val uberJarPath = target.toPath.resolve(jarName)
val uberJar = lib.createJar(uberJarPath.toFile, dirs :+ extractedJarsRoot, mainClass=mainClass) getOrElse {
throw new Exception("Jar file wasn't created!")
}
log("Writing jar file - DONE")
System.err.println(lib.green(s"Uber jar created. You can grab it at $uberJar"))
}
/**
* Extracts jars, and writes them on disk. Returns root directory of extracted jars
* TODO: in future we probably should save extracted jars in target directory, to reuse them on second run
*
* @param jars list of *.jar files
* @param log logger
* @return root directory of extracted jars
*/
private def extractJars(jars: Seq[File])(log: String => Unit): Path = {
val destDir = {
val path = Files.createTempDirectory("unjars")
path.toFile.deleteOnExit()
log(s"Extracted jars directory: $path")
path
}
jars foreach { jar => extractJar(jar, destDir)(log) }
destDir
}
/**
* Extracts content of single jar file to destination directory.
* When extracting jar, if same file already exists, we skip(don't write) this file.
* TODO: maybe skipping duplicates is not best strategy. Figure out duplicate strategy.
*
* @param jarFile jar file to extract
* @param destDir destination directory
* @param log logger
*/
private def extractJar(jarFile: File, destDir: Path)(log: String => Unit): Unit = {
log(s"Extracting jar: $jarFile")
val jar = new JarFile(jarFile)
val enumEntries = jar.entries
while (enumEntries.hasMoreElements) {
val entry = enumEntries.nextElement()
// log(s"Entry name: ${entry.getName}")
val entryPath = destDir.resolve(entry.getName)
if (excludeFileMatcher.matches(entryPath)) {
log(s"Excluded file ${entryPath.getFileName} from jar: $jarFile")
} else {
val exists = Files.exists(entryPath)
if (entry.isDirectory) {
if (!exists) {
Files.createDirectory(entryPath)
// log(s"Created directory: $entryPath")
}
} else {
if (exists) {
log(s"File $entryPath already exists, skipping.")
} else {
val is = jar.getInputStream(entry)
Files.copy(is, entryPath)
is.close()
// log(s"Wrote file: $entryPath")
}
}
}
}
}
}