From e709c5f02290680cd05c18e470b57decb931e62d Mon Sep 17 00:00:00 2001 From: rockjam <5min4eq.unity@gmail.com> Date: Fri, 24 Jun 2016 06:57:11 +0300 Subject: update uber jar PR --- examples/uber-jar-example/build/build.scala | 2 +- plugins/uber-jar/src/UberJar.scala | 137 +++++---------------- plugins/uber-jar/src/uberjar/JarUtils.scala | 67 ++++------ .../uber-jar/src/uberjar/TryWithResources.scala | 13 -- stage2/Lib.scala | 16 ++- 5 files changed, 66 insertions(+), 169 deletions(-) delete mode 100644 plugins/uber-jar/src/uberjar/TryWithResources.scala diff --git a/examples/uber-jar-example/build/build.scala b/examples/uber-jar-example/build/build.scala index fca7737..a36e27b 100644 --- a/examples/uber-jar-example/build/build.scala +++ b/examples/uber-jar-example/build/build.scala @@ -1,6 +1,6 @@ import cbt._ -class Build(val context: Context) extends BuildBuild with UberJar { +class Build(val context: Context) extends BaseBuild with UberJar { override def projectName: String = "uber-jar-example" diff --git a/plugins/uber-jar/src/UberJar.scala b/plugins/uber-jar/src/UberJar.scala index a26053a..473556f 100644 --- a/plugins/uber-jar/src/UberJar.scala +++ b/plugins/uber-jar/src/UberJar.scala @@ -1,36 +1,17 @@ package cbt -import java.io.{File, FileOutputStream} -import java.nio.file._ -import java.util.jar -import java.util.jar.JarOutputStream +import java.io.File +import java.nio.file.{Files, Path} import cbt.uberjar._ -import scala.util.{Failure, Success, Try} - trait UberJar extends BaseBuild { - private val log: String => Unit = logger.log("uber-jar", _) - final def uberJar: ExitCode = { - // we need compile first to produce target directory - compile System.err.println("Creating uber jar...") - UberJar.createUberJar( - targetDir = target, - compilerTarget = compileTarget, - classpath = classpath, - mainClass = uberJarMainClass, - jarName = uberJarName - )(log) match { - case Success(_) => - System.err.println("Creating uber jar - DONE") - ExitCode.Success - case Failure(e) => - System.err.println(s"Failed to create uber jar, cause: $e") - ExitCode.Failure - } + new UberJarLib(logger).create(target, compileTarget, classpath, uberJarMainClass, uberJarName) + System.err.println("Creating uber jar - DONE") + ExitCode.Success } def uberJarMainClass: Option[String] = Some(runClass) @@ -39,98 +20,46 @@ trait UberJar extends BaseBuild { } -object UberJar extends JarUtils { - import TryWithResources._ +class UberJarLib(logger: Logger) extends JarUtils { + private val log: String => Unit = logger.log("uber-jar", _) + private val lib = new cbt.Lib(logger) /** * Creates uber jar for given build. - * Uber jar construction steps: - * 1. create jar file with our customized MANIFEST.MF - * 2. write files from `compilerTarget` to jar file - * 3. get all jars from `classpath` - * 4. extract all jars, filter out their MANIFEST.MF and signatures files - * 5. write content of all jars to target jar file - * 6. Finalize everything, and return `ExitCode` * - * @param targetDir build's target directory - * @param compilerTarget directory where compiled classfiles are - * @param classpath build's classpath - * @param mainClass main class name(optional) - * @param jarName name of resulting jar file - * @param log logger - * @return `ExitCode.Success` if uber jar created and `ExitCode.Failure` otherwise + * @param target build's target directory + * @param compileTarget directory where compiled classfiles are + * @param classpath build's classpath + * @param jarName name of resulting jar file */ - def createUberJar(targetDir: File, - compilerTarget: File, - classpath: ClassPath, - mainClass: Option[String], - jarName: String - )(log: String => Unit): Try[Unit] = { - val targetPath = targetDir.toPath - log(s"Target directory is: $targetPath") - log(s"Compiler targer directory is: $compilerTarget") + def create(target: File, + compileTarget: File, + classpath: ClassPath, + mainClass: Option[String], + jarName: String): Unit = { + log(s"Compiler target directory is: $compileTarget") log(s"Classpath is: $classpath") + log(s"Target directory is: $target") mainClass foreach (c => log(s"Main class is is: $c")) - val jarPath = { - log("Creating jar file...") - val validJarName = if (jarName.endsWith("*.jar")) jarName else jarName + ".jar" - log(s"Jar name is: $validJarName") - val path = targetPath.resolve(validJarName) - Files.deleteIfExists(path) - Files.createFile(path) - log("Creating jar file - DONE") - path - } - withCloseable(new JarOutputStream(new FileOutputStream(jarPath.toFile), createManifest(mainClass))) { out => - writeTarget(compilerTarget, out)(log) + log("Creating far file...") + val file = createJarFile(target.toPath, jarName) + log("Creating far file - DONE") - // it will contain all jar files, including jars, that cbt depend on! Not good! - val jars = classpath.files filter (f => jarFileMatcher.matches(f.toPath)) - log(s"Found ${jars.length} jar dependencies: \n ${jars mkString "\n"}") - writeExtractedJars(jars, targetPath, out)(log) + val jars = classpath.files filter (f => jarFileMatcher.matches(f.toPath)) + log(s"Found ${jars.length} jar dependencies: \n ${jars mkString "\n"}") - // TODO: make it in try-catch-finally style - out.close() - System.err.println(s"Uber jar created. You can grab it at $jarPath") - } - } - - /** - * Writes three attributes to manifest file: - * - * Main-Class: classname - * Manifest-Version: 1.0 - * Created-By: java.runtime.version - * - * @param mainClass optional main class - * @return mainifest for jar - */ - private def createManifest(mainClass: Option[String]): jar.Manifest = { - val m = new jar.Manifest() - m.getMainAttributes.putValue("Manifest-Version", "1.0") - val createdBy = Option(System.getProperty("java.runtime.version")) getOrElse "1.7.0_06 (Oracle Corporation)" - m.getMainAttributes.putValue("Created-By", createdBy) - mainClass foreach { className => - m.getMainAttributes.putValue("Main-Class", className) - } - m - } + log("Extracting jars...") + val extractedJarsRoot = extractJars(jars.distinct)(log).toFile + log("Extracting jars - DONE") - private def writeTarget(compilerTargetDir: File, out: JarOutputStream)(log: String => Unit): Unit = { - log("Writing target directory...") - writeFilesToJar(compilerTargetDir.toPath, out)(log) - log("Writing target directory - DONE") - } + log("Writing jar file...") + val optOutput = lib.jarFile(file, Seq(compileTarget, extractedJarsRoot), mainClass) + log("Writing jar file - DONE") - private def writeExtractedJars(jars: Seq[File], targetDir: Path, out: JarOutputStream)(log: String => Unit): Unit = { - log("Extracting jars") - val extractedJarsRoot = extractJars(jars)(log) - log("Extracting jars - DONE") + optOutput foreach { uberJar => + System.err.println(s"Uber jar created. You can grab it at $uberJar") + } - log("Writing dependencies...") - writeFilesToJar(extractedJarsRoot, out)(log) - log("Writing dependencies - DONE") } - } diff --git a/plugins/uber-jar/src/uberjar/JarUtils.scala b/plugins/uber-jar/src/uberjar/JarUtils.scala index f73947e..fa3990c 100644 --- a/plugins/uber-jar/src/uberjar/JarUtils.scala +++ b/plugins/uber-jar/src/uberjar/JarUtils.scala @@ -2,64 +2,37 @@ package cbt.uberjar import java.io.File import java.nio.file._ -import java.nio.file.attribute.BasicFileAttributes -import java.util.jar.{JarFile, JarOutputStream} -import java.util.zip.{ZipEntry, ZipException} +import java.util.jar.JarFile private[cbt] trait JarUtils { - protected val (pathSeparator, jarFileMatcher, excludeFileMatcher) = { + protected val (jarFileMatcher, excludeFileMatcher) = { val fs = FileSystems.getDefault - (fs.getSeparator, fs.getPathMatcher("glob:**.jar"), fs.getPathMatcher("glob:**{.RSA,.DSA,.SF,.MF}")) + (fs.getPathMatcher("glob:**.jar"), fs.getPathMatcher("glob:**{.RSA,.DSA,.SF,.MF,META-INF}")) } - /** - * If `root` is directory: writes content of directory to jar with original mapping - * If `root` is file: writes this file to jar - * @param root parent directory, with content should go to jar, or file to be written - * @param out jar output stream - * @param log logger - * @return returns `root` - */ - protected def writeFilesToJar(root: Path, out: JarOutputStream)(log: String => Unit): Path = { - Files.walkFileTree(root, new SimpleFileVisitor[Path]() { - override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = { - // when we put part of compiled classes, zip already contains some entries. We should not duplicate them. - try { - out.putNextEntry(new ZipEntry(root.relativize(file).toString)) - Files.copy(file, out) - out.closeEntry() - } catch { - case e: ZipException => log(s"Failed to add entry, skipping cause: $e") - } - FileVisitResult.CONTINUE - } - - override def preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult = { - // when we put part compiled classes, zip already contains some entries. We should not duplicate them. - try { - out.putNextEntry(new ZipEntry(root.relativize(dir).toString + pathSeparator)) - out.closeEntry() - } catch { - case e: ZipException => log(s"Failed to add entry, skipping cause: $e") - } - FileVisitResult.CONTINUE - } - }) + protected def createJarFile(parent: Path, name: String): File = { + val path = parent.resolve(validJarName(name)) + Files.deleteIfExists(path) + Files.createFile(path) + path.toFile } + private def validJarName(name: String) = if (name.endsWith(".jar")) name else name + ".jar" + /** * 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 + * @param log logger * @return root directory of extracted jars */ protected def extractJars(jars: Seq[File])(log: String => Unit): Path = { val destDir = { val path = Files.createTempDirectory("unjars") path.toFile.deleteOnExit() - log(s"Unjars directory: $path") + log(s"Extracted jars directory: $path") path } jars foreach { jar => extractJar(jar, destDir)(log) } @@ -70,9 +43,10 @@ private[cbt] trait JarUtils { * 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 + * @param log logger */ private def extractJar(jarFile: File, destDir: Path)(log: String => Unit): Unit = { log(s"Extracting jar: $jarFile") @@ -85,12 +59,15 @@ private[cbt] trait JarUtils { if (excludeFileMatcher.matches(entryPath)) { log(s"Excluded file ${entryPath.getFileName} from jar: $jarFile") } else { - if (Files.exists(entryPath)) { - log(s"File $entryPath already exists, skipping.") - } else { - if (entry.isDirectory) { + 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) diff --git a/plugins/uber-jar/src/uberjar/TryWithResources.scala b/plugins/uber-jar/src/uberjar/TryWithResources.scala deleted file mode 100644 index c2f11f9..0000000 --- a/plugins/uber-jar/src/uberjar/TryWithResources.scala +++ /dev/null @@ -1,13 +0,0 @@ -package cbt.uberjar - -import java.io.Closeable - -import scala.util.Try - -private[cbt] object TryWithResources { - def withCloseable[T <: Closeable, R](t: T)(f: T => R): Try[R] = { - val result = Try(f(t)) - t.close() - result - } -} diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 8dd6e72..2e3c0e5 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -214,15 +214,19 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } yield file } - def jarFile( jarFile: File, files: Seq[File] ): Option[File] = { + def jarFile( jarFile: File, files: Seq[File], mainClass: Option[String] = None ): Option[File] = { if( files.isEmpty ){ None } else { jarFile.getParentFile.mkdirs logger.lib("Start packaging "++jarFile.string) - val manifest = new Manifest - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") - val jar = new JarOutputStream(new FileOutputStream(jarFile.toString), manifest) + val manifest = new Manifest() + manifest.getMainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0") + manifest.getMainAttributes.putValue("Created-By", + Option(System.getProperty("java.runtime.version")) getOrElse "1.7.0_06 (Oracle Corporation)") + mainClass foreach { className => manifest.getMainAttributes.put(Attributes.Name.MAIN_CLASS, className) + } + val jar = new JarOutputStream(new FileOutputStream(jarFile), manifest) try{ val names = for { base <- files.filter(_.exists).map(realpath) @@ -235,7 +239,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ entry.setTime(file.lastModified) jar.putNextEntry(entry) jar.write( readAllBytes( file.toPath ) ) - jar.closeEntry + jar.closeEntry() name } @@ -245,7 +249,7 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ s"Conflicting file names when trying to create $jarFile: "++duplicateFiles.mkString(", ") ) } finally { - jar.close + jar.close() } logger.lib("Done packaging " ++ jarFile.toString) -- cgit v1.2.3