diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt index ddddaebd..61acb1e1 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt @@ -6,13 +6,12 @@ import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.Zip import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.aether.Scope -import com.beust.kobalt.misc.* +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.kobaltLog import com.google.inject.Inject import java.io.File import java.io.FileInputStream -import java.io.OutputStream import java.nio.file.Paths -import java.util.jar.JarOutputStream import java.util.jar.Manifest class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) : ArchiveGenerator { @@ -155,10 +154,8 @@ class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) } } - val jarFactory = { os: OutputStream -> JarOutputStream(os, manifest) } - return Archives.generateArchive(project, context, zip.name, ".jar", includedFiles, - true /* expandJarFiles */, jarFactory) + true /* expandJarFiles */, manifest) } } \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt index 19054e33..f8bd656c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt @@ -8,10 +8,7 @@ import com.beust.kobalt.misc.JarUtils import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.kobaltLog import java.io.File -import java.io.FileOutputStream -import java.io.OutputStream import java.util.* -import java.util.zip.ZipOutputStream class Archives { companion object { @@ -20,8 +17,6 @@ class Archives { @ExportedProjectProperty(doc = "The name of the a jar file with a main() method", type = "String") const val JAR_NAME_WITH_MAIN_CLASS = "jarNameWithMainClass" - private val DEFAULT_STREAM_FACTORY = { os : OutputStream -> ZipOutputStream(os) } - fun defaultArchiveName(project: Project) = project.name + "-" + project.version fun generateArchive(project: Project, @@ -30,15 +25,15 @@ class Archives { suffix: String, includedFiles: List, expandJarFiles : Boolean = false, - outputStreamFactory: (OutputStream) -> ZipOutputStream = DEFAULT_STREAM_FACTORY) : File { + manifest: java.util.jar.Manifest? = null) : File { val fullArchiveName = context.variant.archiveName(project, archiveName, suffix) val archiveDir = File(KFiles.libsDir(project)) val result = File(archiveDir.path, fullArchiveName) context.logger.log(project.name, 3, "Creating $result") if (! Features.USE_TIMESTAMPS || isOutdated(project.directory, includedFiles, result)) { try { - outputStreamFactory(FileOutputStream(result)).use { - JarUtils.addFiles(project.directory, includedFiles, it, expandJarFiles) + MetaArchive(result, manifest).use { metaArchive -> + JarUtils.addFiles(project.directory, includedFiles, metaArchive, expandJarFiles) context.logger.log(project.name, 2, "Added ${includedFiles.size} files to $result") context.logger.log(project.name, 1, " Created $result") } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt new file mode 100644 index 00000000..7ddc733d --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/MetaArchive.kt @@ -0,0 +1,74 @@ +package com.beust.kobalt.archive + +import com.beust.kobalt.Glob +import com.beust.kobalt.misc.KFiles +import org.apache.commons.compress.archivers.ArchiveEntry +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream +import java.io.Closeable +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.nio.file.Files +import org.apache.commons.compress.archivers.zip.ZipFile as ApacheZipFile + +/** + * Abstraction of a zip/jar/war archive that automatically manages the addition of expanded jar files. + * Uses ZipArchiveOutputStream for fast inclusion of expanded jar files. + */ +class MetaArchive(outputFile: File, val manifest: java.util.jar.Manifest?) : Closeable { + private val zos = ZipArchiveOutputStream(outputFile).apply { + encoding = "UTF-8" + } + + fun addFile(file: File, path: String) { + FileInputStream(file).use { inputStream -> + val entry = zos.createArchiveEntry(file, path) + maybeAddEntry(entry) { + addEntry(entry, inputStream) + } + } + } + + fun addArchive(jarFile: File) { + ApacheZipFile(jarFile).use { jar -> + val jarEntries = jar.entries + for (entry in jarEntries) { + maybeAddEntry(entry) { + zos.addRawArchiveEntry(entry, jar.getRawInputStream(entry)) + } + } + } + } + + private val DEFAULT_JAR_EXCLUDES = + Glob("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") + + private val seen = hashSetOf() + + private fun okToAdd(name: String): Boolean = ! seen.contains(name) + && ! KFiles.isExcluded(name, DEFAULT_JAR_EXCLUDES) + + override fun close() { + if (manifest != null) { + val manifestFile = Files.createTempFile("aaa", "bbb").toFile() + manifest.write(FileOutputStream(manifestFile)) + + val entry = zos.createArchiveEntry(manifestFile, "META-INF/MANIFEST.MF") + addEntry(entry, FileInputStream(manifestFile)) + } + zos.close() + } + + private fun addEntry(entry: ArchiveEntry, inputStream: FileInputStream) { + zos.putArchiveEntry(entry) + inputStream.copyTo(zos, 50 * 1024) + zos.closeArchiveEntry() + } + + private fun maybeAddEntry(entry: ArchiveEntry, action:() -> Unit) { + if (okToAdd(entry.name)) { + action() + } + seen.add(entry.name) + } +} diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt index 81f55b8e..81420a43 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt @@ -1,14 +1,16 @@ package com.beust.kobalt.misc -import com.beust.kobalt.* +import com.beust.kobalt.From +import com.beust.kobalt.IFileSpec +import com.beust.kobalt.IncludedFile +import com.beust.kobalt.To +import com.beust.kobalt.archive.MetaArchive import com.google.common.io.CharStreams -import java.io.* -import java.util.jar.JarEntry +import java.io.File +import java.io.FileOutputStream +import java.io.InputStreamReader import java.util.jar.JarFile -import java.util.jar.JarInputStream -import java.util.zip.ZipEntry import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream class JarUtils { companion object { @@ -19,18 +21,15 @@ class JarUtils { } } - fun addFiles(directory: String, files: List, target: ZipOutputStream, + fun addFiles(directory: String, files: List, metaArchive: MetaArchive, expandJarFiles: Boolean, onError: (Exception) -> Unit = DEFAULT_HANDLER) { files.forEach { - addSingleFile(directory, it, target, expandJarFiles, onError) + addSingleFile(directory, it, metaArchive, expandJarFiles, onError) } } - private val DEFAULT_JAR_EXCLUDES = - Glob("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA") - - fun addSingleFile(directory: String, file: IncludedFile, outputStream: ZipOutputStream, + fun addSingleFile(directory: String, file: IncludedFile, metaArchive: MetaArchive, expandJarFiles: Boolean, onError: (Exception) -> Unit = DEFAULT_HANDLER) { val foundFiles = file.allFromFiles(directory) foundFiles.forEach { foundFile -> @@ -49,42 +48,22 @@ class JarUtils { // Directory val includedFile = IncludedFile(From(""), To(""), listOf(IFileSpec.GlobSpec("**"))) - addSingleFile(localFile.path, includedFile, outputStream, expandJarFiles) + addSingleFile(localFile.path, includedFile, metaArchive, expandJarFiles) } else { - if (file.expandJarFiles && foundFile.name.endsWith(".jar") && ! file.from.contains("resources")) { - kobaltLog(2, " Writing contents of jar file $foundFile") - JarInputStream(FileInputStream(localFile)).use { stream -> - var entry = stream.nextEntry - while (entry != null) { - if (!entry.isDirectory && !KFiles.isExcluded(entry.name, DEFAULT_JAR_EXCLUDES)) { - addEntry(stream, JarEntry(entry), outputStream, onError) - } - entry = stream.nextEntry - } - } - } else { - val entryFileName = KFiles.fixSlashes(file.to(foundFile.path)) - val entry = JarEntry(entryFileName) - entry.time = localFile.lastModified() - FileInputStream(localFile).use { stream -> - addEntry(stream, entry, outputStream, onError) + try { + if (file.expandJarFiles && foundFile.name.endsWith(".jar") && !file.from.contains("resources")) { + kobaltLog(2, " Writing contents of jar file $foundFile") + metaArchive.addArchive(foundFile) + } else { + metaArchive.addFile(File(directory, fromFile.path), foundFile.path) } + } catch(ex: Exception) { + onError(ex) } } } } - private fun addEntry(inputStream: InputStream, entry: ZipEntry, outputStream: ZipOutputStream, - onError: (Exception) -> Unit = DEFAULT_HANDLER) { - try { - outputStream.putNextEntry(entry) - inputStream.copyTo(outputStream, 50 * 1024) - outputStream.closeEntry() - } catch(ex: Exception) { - onError(ex) - } - } - fun extractTextFile(zip : ZipFile, fileName: String) : String? { val enumEntries = zip.entries() while (enumEntries.hasMoreElements()) { diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt index 6556e033..4f42f6c4 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt @@ -88,7 +88,7 @@ class WarGenerator @Inject constructor(val dependencyManager: DependencyManager, val jarFactory = { os: OutputStream -> JarOutputStream(os, manifest) } return Archives.generateArchive(project, context, war.name, ".war", files, - false /* don't expand jar files */, jarFactory) + false /* don't expand jar files */, manifest) } }