1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00
kobalt/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt

184 lines
No EOL
7 KiB
Kotlin

package com.beust.kobalt
import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.IncrementalTask
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.internal.PluginInfo
import com.beust.kobalt.internal.TaskManager
import com.beust.kobalt.maven.DependencyManager
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.misc.JarUtils
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltExecutors
import com.beust.kobalt.misc.log
import com.google.inject.Provider
import java.io.File
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.net.URLClassLoader
import java.util.*
import java.util.jar.JarFile
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class Plugins @Inject constructor (val taskManagerProvider : Provider<TaskManager>,
val files: KFiles,
val depManager: DependencyManager,
val localRepo: LocalRepo,
val executors: KobaltExecutors,
val pluginInfo: PluginInfo,
val taskManager: TaskManager) {
companion object {
private var pluginMap = hashMapOf<String, IPlugin>()
fun addPluginInstance(plugin: IPlugin) {
pluginMap.put(plugin.name, plugin)
}
val plugins : List<IPlugin>
get() = ArrayList(pluginMap.values)
/**
* The list of plugins found in the build file.
*/
val dynamicPlugins : ArrayList<IClasspathDependency> = arrayListOf()
fun addDynamicPlugin(plugin: IClasspathDependency) = dynamicPlugins.add(plugin)
fun findPlugin(name: String) : IPlugin? = pluginMap[name]
}
fun shutdownPlugins() = plugins.forEach { it.shutdown() }
fun applyPlugins(context: KobaltContext, projects: List<Project>) {
plugins.forEach { plugin ->
addPluginInstance(plugin)
// We could inject this in the plug-in but since these will be written by external users,
// I want to keep the verbosity of plugins to a minimum, so instead, we do the injection
// manually here
if (plugin is BasePlugin) {
plugin.taskManager = taskManagerProvider.get()
plugin.plugins = this
}
// Call apply() on each plug-in that accepts a project
log(2, "Applying plug-in \"${plugin.name}\"")
projects.filter { plugin.accept(it) }.forEach { project ->
plugin.apply(project, context)
}
findStaticTasks(plugin, Task::class.java, { method -> isValidTaskMethod(method)}).forEach {
taskManager.addAnnotationTask(plugin, it.first, it.second)
}
findStaticTasks(plugin, IncrementalTask::class.java,
{ method -> isValidIncrementalTaskMethod(method)}).forEach {
taskManager.addIncrementalTask(plugin, it.first, it.second)
}
}
// Collect all the tasks from the task contributors
context.pluginInfo.taskContributors.forEach {
taskManager.dynamicTasks.addAll(it.tasksFor(context))
}
// Now that we have collected all static and dynamic tasks, turn them all into plug-in tasks
taskManager.computePluginTasks(projects)
}
private fun <T: Annotation> findStaticTasks(plugin: IPlugin, klass: Class<T>, validate: (Method) -> Boolean)
: List<Pair<Method, T>> {
val result = arrayListOf<Pair<Method, T>>()
var currentClass : Class<in Any>? = plugin.javaClass
// Tasks can come from two different places: plugin classes and build files.
// When a task is read from a build file, ScriptCompiler adds it right away to plugin.methodTasks.
// The following loop introspects the current plugin, finds all the tasks using the @Task annotation
// and adds them to plugin.methodTasks
while (currentClass != null && ! (klass.equals(currentClass))) {
currentClass.declaredMethods.map {
Pair(it, it.getAnnotation(klass))
}.filter {
it.second != null
}.filter {
validate(it.first)
}.forEach {
if (Modifier.isPrivate(it.first.modifiers)) {
throw KobaltException("A task method cannot be private: ${it.first}")
}
result.add(it)
}
currentClass = currentClass.superclass
}
return result
}
/**
* Make sure this task method has the right signature.
*/
private fun isValidIncrementalTaskMethod(method: Method): Boolean {
val t = "Task ${method.declaringClass.simpleName}.${method.name}: "
if (method.returnType != IncrementalTaskInfo::class.java) {
throw IllegalArgumentException("${t}should return a IncrementalTaskInfo")
}
return true
}
/**
* Make sure this task method has the right signature.
*/
private fun isValidTaskMethod(method: Method): Boolean {
val t = "Task ${method.declaringClass.simpleName}.${method.name}: "
if (method.returnType != TaskResult::class.java) {
throw IllegalArgumentException("${t}should return a TaskResult")
}
if (method.parameterTypes.size != 1) {
throw IllegalArgumentException("${t}should take exactly one parameter of type a Project")
}
with(method.parameterTypes) {
if (! Project::class.java.isAssignableFrom(get(0))) {
throw IllegalArgumentException("${t}first parameter should be of type Project," +
"not ${get(0)}")
}
}
return true
}
val dependencies = arrayListOf<IClasspathDependency>()
fun installPlugins(dependencies: List<IClasspathDependency>, scriptClassLoader: ClassLoader) {
val executor = executors.newExecutor("Plugins", 5)
dependencies.forEach {
//
// Load all the jar files synchronously (can't compile the build script until
// they are installed locally).
depManager.create(it.id)
//
// Open the jar, parse its kobalt-plugin.xml and add the resulting PluginInfo to pluginInfo
//
val file = it.jarFile.get();
val pluginXml = if (file.isDirectory()) {
File(file, PluginInfo.PLUGIN_XML).readText()
} else {
JarUtils.extractTextFile(JarFile(file), PluginInfo.PLUGIN_XML)
}
if (pluginXml != null) {
val pluginClassLoader = URLClassLoader(arrayOf(file.toURI().toURL()))
val thisPluginInfo = PluginInfo.readPluginXml(pluginXml, pluginClassLoader, scriptClassLoader)
pluginInfo.addPluginInfo(thisPluginInfo)
thisPluginInfo.plugins.forEach {
Plugins.addPluginInstance(it)
}
} else {
throw KobaltException("Plugin $it doesn't contain a ${PluginInfo.PLUGIN_XML} file")
}
}
executor.shutdown()
}
}