aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Christopher Vogt <oss.nsp@cvogt.org>2016-06-24 19:44:45 -0400
committerGitHub <noreply@github.com>2016-06-24 19:44:45 -0400
commitb1b2195b13300d9b3057b96deebf24d9353a7344 (patch)
tree5bbb137c3de4cea3267c5fece06f095496b3b0a3
parentfd6c08a1fe03fee7ccb87200bf4ff4b717d42864 (diff)
parent0ed626b8764dd21085e935f6642343a163e1273e (diff)
downloadcbt-b1b2195b13300d9b3057b96deebf24d9353a7344.tar.gz
cbt-b1b2195b13300d9b3057b96deebf24d9353a7344.tar.bz2
cbt-b1b2195b13300d9b3057b96deebf24d9353a7344.zip
Merge pull request #159 from rockjam/wip/uber-jar
Uber-jar plugin initial implementation
-rw-r--r--examples/scalafmt-example/build/build.scala12
-rw-r--r--examples/scalariform-example/build/build.scala12
-rw-r--r--examples/uber-jar-example/README.md41
-rw-r--r--examples/uber-jar-example/build/build.scala16
-rw-r--r--examples/uber-jar-example/build/build/build.scala5
-rw-r--r--examples/uber-jar-example/src/Main.scala21
-rw-r--r--examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala11
-rw-r--r--plugins/uber-jar/build/build.scala3
-rw-r--r--plugins/uber-jar/src/UberJar.scala124
-rw-r--r--stage2/BuildBuild.scala1
-rw-r--r--stage2/Lib.scala18
-rw-r--r--test/test.scala4
12 files changed, 245 insertions, 23 deletions
diff --git a/examples/scalafmt-example/build/build.scala b/examples/scalafmt-example/build/build.scala
index 6f77108..a489616 100644
--- a/examples/scalafmt-example/build/build.scala
+++ b/examples/scalafmt-example/build/build.scala
@@ -15,14 +15,10 @@ class Build(val context: Context) extends BuildBuild with Scalafmt {
import scala.collection.JavaConverters._
val utf8 = Charset.forName("UTF-8")
sourceFiles foreach { file =>
- try {
- val path = file.toPath
- val fileLines = Files.readAllLines(path, utf8).asScala
- val brokenLines = fileLines map (_.dropWhile(_ ==' '))
- Files.write(path, brokenLines.asJava, utf8)
- } catch {
- case e: Exception => System.err.print(s"Error happend when breaking formatting: ${e}")
- }
+ val path = file.toPath
+ val fileLines = Files.readAllLines(path, utf8).asScala
+ val brokenLines = fileLines map (_.dropWhile(_ == ' '))
+ Files.write(path, brokenLines.asJava, utf8)
}
System.err.println("Done breaking formatting")
}
diff --git a/examples/scalariform-example/build/build.scala b/examples/scalariform-example/build/build.scala
index 5f7b7ff..91ff67a 100644
--- a/examples/scalariform-example/build/build.scala
+++ b/examples/scalariform-example/build/build.scala
@@ -19,14 +19,10 @@ class Build(val context: Context) extends BaseBuild with Scalariform {
import scala.collection.JavaConverters._
val utf8 = Charset.forName("UTF-8")
sourceFiles foreach { file =>
- try {
- val path = file.toPath
- val fileLines = Files.readAllLines(path, utf8).asScala
- val brokenLines = fileLines map (_.dropWhile(_ ==' '))
- Files.write(path, brokenLines.asJava, utf8)
- } catch {
- case e: Exception => System.err.print(s"Error happend when breaking formatting: ${e}")
- }
+ val path = file.toPath
+ val fileLines = Files.readAllLines(path, utf8).asScala
+ val brokenLines = fileLines map (_.dropWhile(_ == ' '))
+ Files.write(path, brokenLines.asJava, utf8)
}
System.err.println("Done breaking formatting")
}
diff --git a/examples/uber-jar-example/README.md b/examples/uber-jar-example/README.md
new file mode 100644
index 0000000..2460084
--- /dev/null
+++ b/examples/uber-jar-example/README.md
@@ -0,0 +1,41 @@
+### Uber-jar plugin example
+
+This example shows how to build uber jar(aka fat jar) with `UberJar` plugin.
+
+In order to create uber jar: execute `cbt uberJar`. Produced jar will be in target folder.
+
+By default, jar name is your `cbt projectName`, you can provide other name via overriding `uberJarName` task.
+
+By default, main class is `Main`. You can provide custom main class via overriding `uberJarMainClass` task.
+
+To run your main class you can execute `java -jar your-jar-name.jar`.
+
+You can also run scala REPL with your jar classpath and classes with this command: `scala -cp your-jar-name.jar`.
+
+In scala REPL you will have access to all your project classes and dependencies.
+
+```
+scala -cp uber-jar-example-0.0.1.jar
+Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_72).
+Type in expressions for evaluation. Or try :help.
+
+scala> import com.github.someguy.ImportantLib
+import com.github.someguy.ImportantLib
+
+scala> ImportantLib.add(1,2)
+res0: Int = 3
+
+scala> ImportantLib.currentDirectory
+Current directory is: /Users/rockjam/projects/cbt/examples/uber-jar-example/target
+
+scala> Main.main(Array.empty)
+fooo
+Current directory is: /Users/rockjam/projects/cbt/examples/uber-jar-example/target
+not empty list
+
+scala> import shapeless._
+import shapeless._
+
+scala> 1 :: "String" :: 3 :: HNil
+res3: shapeless.::[Int,shapeless.::[String,shapeless.::[Int,shapeless.HNil]]] = 1 :: String :: 3 :: HNil
+```
diff --git a/examples/uber-jar-example/build/build.scala b/examples/uber-jar-example/build/build.scala
new file mode 100644
index 0000000..fec58ae
--- /dev/null
+++ b/examples/uber-jar-example/build/build.scala
@@ -0,0 +1,16 @@
+import cbt._
+
+class Build(val context: Context) extends BaseBuild with UberJar {
+
+ override def projectName: String = "uber-jar-example"
+
+ override def dependencies = super.dependencies ++
+ Resolver( mavenCentral ).bind(
+ ScalaDependency("com.chuusai", "shapeless", "2.3.1"),
+ ScalaDependency("com.lihaoyi", "fansi", "0.1.3"),
+ ScalaDependency("org.typelevel", "cats", "0.6.0")
+ )
+
+ override def uberJarName = projectName + "-0.0.1" + ".jar"
+
+}
diff --git a/examples/uber-jar-example/build/build/build.scala b/examples/uber-jar-example/build/build/build.scala
new file mode 100644
index 0000000..2938ffd
--- /dev/null
+++ b/examples/uber-jar-example/build/build/build.scala
@@ -0,0 +1,5 @@
+import cbt._
+
+class Build(val context: Context) extends BuildBuild {
+ override def dependencies = super.dependencies :+ plugins.uberJar
+}
diff --git a/examples/uber-jar-example/src/Main.scala b/examples/uber-jar-example/src/Main.scala
new file mode 100644
index 0000000..f60634f
--- /dev/null
+++ b/examples/uber-jar-example/src/Main.scala
@@ -0,0 +1,21 @@
+import scala.concurrent.{ Await, Future }
+import scala.concurrent.duration._
+
+import com.github.someguy.ImportantLib
+
+object Main extends App {
+ println("fooo")
+ val futureRes = Await.result(Future.successful(1), 5.seconds)
+
+ ImportantLib.currentDirectory()
+
+ val hlist = {
+ import shapeless._
+ 1 :: "string" :: 3 :: HNil
+ }
+
+ List(1, 2, 4, 5, 6) match {
+ case h :: _ ⇒ println("not empty list")
+ case Nil ⇒ println("empty list")
+ }
+}
diff --git a/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala b/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala
new file mode 100644
index 0000000..34baf2f
--- /dev/null
+++ b/examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala
@@ -0,0 +1,11 @@
+package com.github.someguy
+
+import java.nio.file.Paths
+
+object ImportantLib {
+ def add(a: Int, b: Int): Int = a + b
+ def currentDirectory() = {
+ println(fansi.Color.Green(s"Current directory is: ${Paths.get("").toAbsolutePath}"))
+ }
+
+}
diff --git a/plugins/uber-jar/build/build.scala b/plugins/uber-jar/build/build.scala
new file mode 100644
index 0000000..0205cf8
--- /dev/null
+++ b/plugins/uber-jar/build/build.scala
@@ -0,0 +1,3 @@
+import cbt._
+
+class Build(val context: Context) extends Plugin
diff --git a/plugins/uber-jar/src/UberJar.scala b/plugins/uber-jar/src/UberJar.scala
new file mode 100644
index 0000000..c6815b4
--- /dev/null
+++ b/plugins/uber-jar/src/UberJar.scala
@@ -0,0 +1,124 @@
+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] = Some(runClass)
+
+ def uberJarName: String = projectName + ".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.jarFile(uberJarPath.toFile, dirs :+ extractedJarsRoot, 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")
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/stage2/BuildBuild.scala b/stage2/BuildBuild.scala
index dec438b..c57ce1b 100644
--- a/stage2/BuildBuild.scala
+++ b/stage2/BuildBuild.scala
@@ -13,6 +13,7 @@ trait BuildBuild extends BaseBuild{
final val scalaJs = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalajs" )
final val scalariform = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalariform" )
final val scalafmt = DirectoryDependency( managedContext.cbtHome ++ "/plugins/scalafmt" )
+ final val uberJar = DirectoryDependency( managedContext.cbtHome ++ "/plugins/uber-jar" )
}
override def dependencies =
diff --git a/stage2/Lib.scala b/stage2/Lib.scala
index 8dd6e72..620c009 100644
--- a/stage2/Lib.scala
+++ b/stage2/Lib.scala
@@ -214,15 +214,21 @@ 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] = {
+ Files.deleteIfExists(jarFile.toPath)
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 +241,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 +251,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)
diff --git a/test/test.scala b/test/test.scala
index 9df1abe..92d4abf 100644
--- a/test/test.scala
+++ b/test/test.scala
@@ -162,6 +162,7 @@ object Main{
compile("../plugins/scalajs")
compile("../plugins/scalariform")
compile("../plugins/scalatest")
+ compile("../plugins/uber-jar")
compile("../examples/scalafmt-example")
compile("../examples/scalariform-example")
compile("../examples/scalatest-example")
@@ -170,7 +171,8 @@ object Main{
compile("../examples/multi-project-example")
task("fastOptJS","../examples/scalajs-react-example/js")
task("fullOptJS","../examples/scalajs-react-example/js")
-
+ compile("../examples/uber-jar-example")
+
System.err.println(" DONE!")
System.err.println( successes.toString ++ " succeeded, "++ failures.toString ++ " failed" )
if(failures > 0) System.exit(1) else System.exit(0)