diff options
author | Jan Christopher Vogt <oss.nsp@cvogt.org> | 2016-06-24 19:44:45 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-06-24 19:44:45 -0400 |
commit | b1b2195b13300d9b3057b96deebf24d9353a7344 (patch) | |
tree | 5bbb137c3de4cea3267c5fece06f095496b3b0a3 | |
parent | fd6c08a1fe03fee7ccb87200bf4ff4b717d42864 (diff) | |
parent | 0ed626b8764dd21085e935f6642343a163e1273e (diff) | |
download | cbt-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.scala | 12 | ||||
-rw-r--r-- | examples/scalariform-example/build/build.scala | 12 | ||||
-rw-r--r-- | examples/uber-jar-example/README.md | 41 | ||||
-rw-r--r-- | examples/uber-jar-example/build/build.scala | 16 | ||||
-rw-r--r-- | examples/uber-jar-example/build/build/build.scala | 5 | ||||
-rw-r--r-- | examples/uber-jar-example/src/Main.scala | 21 | ||||
-rw-r--r-- | examples/uber-jar-example/src/com/github/someguy/ImportantLib.scala | 11 | ||||
-rw-r--r-- | plugins/uber-jar/build/build.scala | 3 | ||||
-rw-r--r-- | plugins/uber-jar/src/UberJar.scala | 124 | ||||
-rw-r--r-- | stage2/BuildBuild.scala | 1 | ||||
-rw-r--r-- | stage2/Lib.scala | 18 | ||||
-rw-r--r-- | test/test.scala | 4 |
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) |