aboutsummaryrefslogtreecommitdiff
path: root/plugins/uber-jar/src/UberJar.scala
blob: 51edbf15228a76d5d77684ec0eaaec590830ee62 (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
129
130
131
132
133
134
135
136
137
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, compileTarget, classpath, uberJarMainClass, uberJarName)
    System.err.println("Creating uber jar - DONE")
    ExitCode.Success
  }

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

  def uberJarName: String = projectName

}

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 compileTarget directory where compiled classfiles are
    * @param classpath     build's classpath
    * @param jarName       name of resulting jar file
    */
  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"))

    log("Creating far file...")
    val file = createJarFile(target.toPath, jarName)
    log("Creating far file - DONE")

    val jars = classpath.files filter (f => jarFileMatcher.matches(f.toPath))
    log(s"Found ${jars.length} jar dependencies: \n ${jars mkString "\n"}")

    log("Extracting jars...")
    val extractedJarsRoot = extractJars(jars.distinct)(log).toFile
    log("Extracting jars - DONE")

    log("Writing jar file...")
    val optOutput = lib.jarFile(file, Seq(compileTarget, extractedJarsRoot), mainClass)
    log("Writing jar file - DONE")

    optOutput foreach { uberJar =>
      System.err.println(s"Uber jar created. You can grab it at $uberJar")
    }

  }

  private 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
    * @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")
          }
        }
      }
    }
  }

}