1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00
kobalt/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt
Cedric Beust 5630d32fac Comment.
2015-12-16 06:26:02 +04:00

453 lines
16 KiB
Kotlin

package com.beust.kobalt.plugin.packaging
import com.beust.kobalt.IFileSpec
import com.beust.kobalt.IFileSpec.FileSpec
import com.beust.kobalt.IFileSpec.Glob
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.Directive
import com.beust.kobalt.api.annotation.ExportedProjectProperty
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.glob
import com.beust.kobalt.internal.JvmCompilerPlugin
import com.beust.kobalt.maven.DependencyManager
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.misc.*
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
import java.nio.file.Paths
import java.util.jar.JarOutputStream
import java.util.zip.ZipOutputStream
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PackagingPlugin @Inject constructor(val dependencyManager : DependencyManager,
val executors: KobaltExecutors, val localRepo: LocalRepo)
: ConfigPlugin<InstallConfig>(), ITaskContributor {
companion object {
const val PLUGIN_NAME = "Packaging"
@ExportedProjectProperty(doc = "Where the libraries are saved", type = "String")
const val LIBS_DIR = "libsDir"
@ExportedProjectProperty(doc = "The name of the jar file", type = "String")
const val JAR_NAME = "jarName"
@ExportedProjectProperty(doc = "The list of packages produced for this project",
type = "List<PackageConfig>")
const val PACKAGES = "packages"
const val TASK_ASSEMBLE: String = "assemble"
const val TASK_INSTALL: String = "install"
}
override val name = PLUGIN_NAME
private val packages = arrayListOf<PackageConfig>()
val taskContributor : TaskContributor = TaskContributor()
override fun apply(project: Project, context: KobaltContext) {
super.apply(project, context)
project.projectProperties.put(LIBS_DIR, libsDir(project))
taskContributor.addVariantTasks(this, project, context, "assemble", runAfter = listOf("compile"),
runTask = { taskAssemble(project) })
}
private fun libsDir(project: Project) = KFiles.makeDir(KFiles.buildDir(project).path, "libs").path
@Task(name = TASK_ASSEMBLE, description = "Package the artifacts",
runAfter = arrayOf(JvmCompilerPlugin.TASK_COMPILE))
fun taskAssemble(project: Project) : TaskResult {
project.projectProperties.put(PACKAGES, packages)
packages.filter { it.project.name == project.name }.forEach { pkg ->
pkg.jars.forEach { generateJar(pkg.project, it) }
pkg.wars.forEach { generateWar(pkg.project, it) }
pkg.zips.forEach { generateZip(pkg.project, it) }
}
return TaskResult()
}
private fun generateWar(project: Project, war: War) : File {
//
// src/main/web app and classes
//
val allFiles = arrayListOf(
IncludedFile(From("src/main/webapp"), To(""), listOf(Glob("**"))),
IncludedFile(From("kobaltBuild/classes"), To("WEB-INF/classes"), listOf(Glob("**")))
)
val manifest = java.util.jar.Manifest()//FileInputStream(mf))
war.attributes.forEach { attribute ->
manifest.mainAttributes.putValue(attribute.first, attribute.second)
}
//
// The transitive closure of libraries goes into WEB-INF/libs.
// Copy them all in kobaltBuild/war/WEB-INF/libs and create one IncludedFile out of that directory
//
val allDependencies = dependencyManager.calculateDependencies(project, context, projects,
project.compileDependencies)
val WEB_INF = "WEB-INF/lib"
val outDir = project.buildDirectory + "/war"
val fullDir = outDir + "/" + WEB_INF
File(fullDir).mkdirs()
// Run through all the classpath contributors and add their contributions to the libs/ directory
context.pluginInfo.classpathContributors.map {
it.entriesFor(project)
}.map { deps : Collection<IClasspathDependency> ->
deps.forEach { dep ->
val jar = dep.jarFile.get()
KFiles.copy(Paths.get(jar.path), Paths.get(fullDir, jar.name))
}
}
// Add the regular dependencies to the libs/ directory
allDependencies.map { it.jarFile.get() }.forEach {
KFiles.copy(Paths.get(it.absolutePath), Paths.get(fullDir, it.name))
}
allFiles.add(IncludedFile(From(fullDir), To(WEB_INF), listOf(Glob("**"))))
val jarFactory = { os:OutputStream -> JarOutputStream(os, manifest) }
return generateArchive(project, war.name, ".war", allFiles,
false /* don't expand jar files */, jarFactory)
}
private fun generateJar(project: Project, jar: Jar) : File {
//
// Add all the applicable files for the current project
//
val buildDir = KFiles.buildDir(project)
val allFiles = arrayListOf<IncludedFile>()
val classesDir = KFiles.makeDir(buildDir.path, "classes")
if (jar.includedFiles.isEmpty()) {
// If no includes were specified, assume the user wants a simple jar file made of the
// classes of the project, so we specify a From("build/classes/"), To("") and
// a list of files containing everything under it
val relClassesDir = Paths.get(project.directory).relativize(Paths.get(classesDir.path))
val prefixPath = Paths.get(project.directory).relativize(Paths.get(classesDir.path + "/"))
// Class files
val files = KFiles.findRecursively(classesDir).map { File(relClassesDir.toFile(), it) }
val filesNotExcluded : List<File> = files.filter { ! KFiles.isExcluded(it, jar.excludes) }
val fileSpecs = arrayListOf<IFileSpec>()
filesNotExcluded.forEach {
fileSpecs.add(FileSpec(it.path.toString().substring(prefixPath.toString().length + 1)))
}
allFiles.add(IncludedFile(From(prefixPath.toString() + "/"), To(""), fileSpecs))
} else {
//
// The user specified an include, just use it verbatim
//
allFiles.addAll(findIncludedFiles(project.directory, jar.includedFiles, jar.excludes))
}
//
// If fatJar is true, add all the transitive dependencies as well: compile, runtime and dependent projects
//
if (jar.fatJar) {
log(2, "Creating fat jar")
val seen = hashSetOf<String>()
@Suppress("UNCHECKED_CAST")
val dependentProjects = project.projectProperties.get(JvmCompilerPlugin.DEPENDENT_PROJECTS)
as List<ProjectDescription>
val allDependencies = project.compileDependencies + project.compileRuntimeDependencies
val transitiveDependencies = dependencyManager.calculateDependencies(project, context, dependentProjects,
allDependencies)
transitiveDependencies.map {
it.jarFile.get()
}.forEach { file : File ->
if (! seen.contains(file.path)) {
seen.add(file.path)
if (! KFiles.isExcluded(file, jar.excludes)) {
allFiles.add(IncludedFile(arrayListOf(FileSpec(file.path))))
}
}
}
}
//
// Generate the manifest
//
val manifest = java.util.jar.Manifest()//FileInputStream(mf))
jar.attributes.forEach { attribute ->
manifest.mainAttributes.putValue(attribute.first, attribute.second)
}
val jarFactory = { os:OutputStream -> JarOutputStream(os, manifest) }
return generateArchive(project, jar.name, ".jar", allFiles,
true /* expandJarFiles */, jarFactory)
}
private fun findIncludedFiles(directory: String, files: List<IncludedFile>, excludes: List<IFileSpec.Glob>)
: List<IncludedFile> {
val result = arrayListOf<IncludedFile>()
files.forEach { includedFile ->
val includedSpecs = arrayListOf<IFileSpec>()
includedFile.specs.forEach { spec ->
val fromPath = directory + "/" + includedFile.from
if (File(fromPath).exists()) {
spec.toFiles(fromPath).forEach { file ->
File(fromPath, file.path).let {
if (! it.exists()) {
throw AssertionError("File should exist: $it")
}
}
if (! KFiles.isExcluded(file, excludes)) {
includedSpecs.add(FileSpec(file.path))
} else {
log(2, "Not adding ${file.path} to jar file because it's excluded")
}
}
} else {
log(2, "Directory $fromPath doesn't exist, not including it in the jar")
}
}
if (includedSpecs.size > 0) {
log(3, "Including specs $includedSpecs")
result.add(IncludedFile(From(includedFile.from), To(includedFile.to), includedSpecs))
}
}
return result
}
private fun generateZip(project: Project, zip: Zip) {
val allFiles = findIncludedFiles(project.directory, zip.includedFiles, zip.excludes)
generateArchive(project, zip.name, ".zip", allFiles)
}
private val DEFAULT_STREAM_FACTORY = { os : OutputStream -> ZipOutputStream(os) }
private fun generateArchive(project: Project,
archiveName: String?,
suffix: String,
includedFiles: List<IncludedFile>,
expandJarFiles : Boolean = false,
outputStreamFactory: (OutputStream) -> ZipOutputStream = DEFAULT_STREAM_FACTORY) : File {
val fullArchiveName = context.variant.archiveName(project, archiveName, suffix)
val archiveDir = File(libsDir(project))
val result = File(archiveDir.path, fullArchiveName)
val outStream = outputStreamFactory(FileOutputStream(result))
log(2, "Creating $result")
JarUtils.addFiles(project.directory, includedFiles, outStream, expandJarFiles)
log(2, text = "Added ${includedFiles.size} files to $result")
outStream.flush()
outStream.close()
log(1, " Created $result")
project.projectProperties.put(JAR_NAME, result.absolutePath)
return result
}
fun addPackage(p: PackageConfig) {
packages.add(p)
}
@Task(name = PackagingPlugin.TASK_INSTALL, description = "Install the artifacts",
runAfter = arrayOf(PackagingPlugin.TASK_ASSEMBLE))
fun taskInstall(project: Project) : TaskResult {
val config = configurationFor(project) ?: InstallConfig()
val buildDir = project.projectProperties.getString(LIBS_DIR)
val buildDirFile = File(buildDir)
if (buildDirFile.exists()) {
log(1, "Installing from $buildDir to ${config.libDir}")
val toDir = KFiles.makeDir(config.libDir)
KFiles.copyRecursively(buildDirFile, toDir, deleteFirst = true)
}
return TaskResult()
}
//ITaskContributor
override fun tasksFor(context: KobaltContext): List<DynamicTask> = taskContributor.dynamicTasks
}
@Directive
fun Project.install(init: InstallConfig.() -> Unit) {
InstallConfig().let {
it.init()
(Kobalt.findPlugin(PackagingPlugin.PLUGIN_NAME) as PackagingPlugin).addConfiguration(this, it)
}
}
class InstallConfig(var libDir : String = "libs")
@Directive
fun Project.assemble(init: PackageConfig.(p: Project) -> Unit) = let {
PackageConfig(this).apply { init(it) }
}
class PackageConfig(val project: Project) : AttributeHolder {
val jars = arrayListOf<Jar>()
val wars = arrayListOf<War>()
val zips = arrayListOf<Zip>()
init {
(Kobalt.findPlugin(PackagingPlugin.PLUGIN_NAME) as PackagingPlugin).addPackage(this)
}
@Directive
fun jar(init: Jar.(p: Jar) -> Unit) : Jar {
val jar = Jar()
jar.init(jar)
jars.add(jar)
return jar
}
@Directive
fun zip(init: Zip.(p: Zip) -> Unit) : Zip {
val zip = Zip()
zip.init(zip)
zips.add(zip)
return zip
}
@Directive
fun war(init: War.(p: War) -> Unit) : War {
val war = War()
war.init(war)
wars.add(war)
return war
}
/**
* Package all the jar files necessary for a maven repo: classes, sources, javadocs.
*/
@Directive
fun mavenJars(init: MavenJars.(p: MavenJars) -> Unit) : MavenJars {
val m = MavenJars(this)
m.init(m)
val mainJar = jar {
fatJar = m.fatJar
}
jar {
name = "${project.name}-${project.version}-sources.jar"
project.sourceDirectories.forEach {
include(from(it), to(""), glob("**${project.sourceSuffix}"))
}
}
jar {
name = "${project.name}-${project.version}-javadoc.jar"
include(from(project.buildDirectory + "/" + JvmCompilerPlugin.DOCS_DIRECTORY), to(""), glob("**"))
}
mainJarAttributes.forEach {
mainJar.addAttribute(it.first, it.second)
}
return m
}
val mainJarAttributes = arrayListOf<Pair<String, String>>()
override fun addAttribute(k: String, v: String) {
mainJarAttributes.add(Pair(k, v))
}
class MavenJars(val ah: AttributeHolder, var fatJar: Boolean = false, var manifest: Manifest? = null) :
AttributeHolder by ah {
public fun manifest(init: Manifest.(p: Manifest) -> Unit) : Manifest {
val m = Manifest(this)
m.init(m)
return m
}
}
}
open class Zip(open var name: String? = null) {
// internal val includes = arrayListOf<IFileSpec>()
internal val excludes = arrayListOf<Glob>()
@Directive
public fun from(s: String) = From(s)
@Directive
public fun to(s: String) = To(s)
@Directive
public fun exclude(vararg files: String) {
files.forEach { excludes.add(Glob(it)) }
}
@Directive
public fun exclude(vararg specs: Glob) {
specs.forEach { excludes.add(it) }
}
@Directive
public fun include(vararg files: String) {
includedFiles.add(IncludedFile(files.map { FileSpec(it) }))
}
@Directive
public fun include(from: From, to: To, vararg specs: String) {
includedFiles.add(IncludedFile(from, to, specs.map { FileSpec(it) }))
}
@Directive
public fun include(from: From, to: To, vararg specs: Glob) {
includedFiles.add(IncludedFile(from, to, listOf(*specs)))
}
/**
* Prefix path to be removed from the zip file. For example, if you add "build/lib/a.jar" to the zip
* file and the excludePrefix is "build/lib", then "a.jar" will be added at the root of the zip file.
*/
val includedFiles = arrayListOf<IncludedFile>()
}
interface AttributeHolder {
fun addAttribute(k: String, v: String)
}
/**
* A jar is exactly like a zip with the addition of a manifest and an optional fatJar boolean.
*/
open class Jar(override var name: String? = null, var fatJar: Boolean = false) : Zip(name), AttributeHolder {
@Directive
public fun manifest(init: Manifest.(p: Manifest) -> Unit) : Manifest {
val m = Manifest(this)
m.init(m)
return m
}
// Need to specify the version or attributes will just be dropped
@Directive
val attributes = arrayListOf(Pair("Manifest-Version", "1.0"))
override fun addAttribute(k: String, v: String) {
attributes.add(Pair(k, v))
}
}
class War(override var name: String? = null) : Jar(name), AttributeHolder {
init {
include(from("src/main/webapp"),to(""), glob("**"))
include(from("kobaltBuild/classes"), to("WEB-INF/classes"), glob("**"))
}
}
class Pom {
}
class Manifest(val jar: AttributeHolder) {
@Directive
public fun attributes(k: String, v: String) {
jar.addAttribute(k, v)
}
}