aboutsummaryrefslogtreecommitdiff
path: root/plugins/uber-jar/src/UberJar.scala
blob: b1c613c181ae0b30083fe572ef4a3bba29548eca (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
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")
          }
        }
      }
    }
  }

}