aboutsummaryrefslogtreecommitdiff
path: root/plugins/uber-jar/src/UberJar.scala
blob: 32b5c4ac092ff16bb45f51e631f686a753271a1b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package cbt

import java.io.{File, FileOutputStream}
import java.nio.file._
import java.util.jar
import java.util.jar.JarOutputStream

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
    }
  }

  def uberJarMainClass: Option[String] = Some(runClass)

  def uberJarName: String = projectName

}

object UberJar extends JarUtils {
  import TryWithResources._

  /**
    * 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
    */
  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")
    log(s"Classpath is: $classpath")
    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)

      // 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)

      // TODO: make it in try-catch-finally style
      out.close()
      System.err.println(s"Uber jar created. You can grab it at $jarPath")
    }
  }

  //  Main-Class: classname
  //  Manifest-Version: 1.0
  //  Created-By: 1.7.0_06 (Oracle Corporation)
  // this template is taken from java tutorial oracle site. replace with actual value, if possible...if needed
  private def createManifest(mainClass: Option[String]): jar.Manifest = {
    val m = new jar.Manifest()
    m.getMainAttributes.putValue("Manifest-Version", "1.0")
    m.getMainAttributes.putValue("Created-By", "1.7.0_06 (Oracle Corporation)")
    mainClass foreach { className =>
      m.getMainAttributes.putValue("Main-Class", className)
    }
    m
  }

  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")
  }

  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")

    log("Writing dependencies...")
    writeFilesToJar(extractedJarsRoot, out)(log)
    log("Writing dependencies - DONE")
  }

}