diff --git a/src/main/kotlin/com/beust/kobalt/Plugins.kt b/src/main/kotlin/com/beust/kobalt/Plugins.kt index 58f486e4..52972fe0 100644 --- a/src/main/kotlin/com/beust/kobalt/Plugins.kt +++ b/src/main/kotlin/com/beust/kobalt/Plugins.kt @@ -12,13 +12,12 @@ import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.KobaltExecutors import com.beust.kobalt.misc.log import com.beust.kobalt.plugin.KobaltDefaultPlugin +import com.beust.kobalt.plugin.packaging.JarUtils import com.google.inject.Provider -import java.io.FileInputStream import java.lang.reflect.Method import java.lang.reflect.Modifier -import java.net.URLClassLoader import java.util.* -import java.util.jar.JarInputStream +import java.util.jar.JarFile import javax.inject.Inject import javax.inject.Singleton @@ -27,17 +26,12 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider() - fun addPlugin(pluginClass : Class) { - addPluginInstance(Kobalt.INJECTOR.getInstance(pluginClass)) - } - fun addPluginInstance(plugin: Plugin) { pluginMap.put(plugin.name, plugin) } @@ -141,18 +135,6 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider() - public fun instantiateClassName(classLoader: ClassLoader, className : String) : Class<*> { - try { - log(2, "Instantiating $className") - return classLoader.loadClass(className) - } catch(ex: Exception) { - val urls = Arrays.toString((classLoader as URLClassLoader).urLs) - val message = "Couldn't instantiate $className\n with classLoader $urls: $ex" - println(message) - throw KobaltException(message) - } - } - val allTasks : List get() = Plugins.plugins.flatMap { it.tasks } @@ -165,23 +147,19 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider - JarInputStream(fis).use { jis -> - val manifest = jis.manifest - val mainClass = manifest.mainAttributes.getValue(Plugins.MANIFEST_PLUGIN_CLASS) ?: - throw KobaltException("Couldn't find \"${Plugins.MANIFEST_PLUGIN_CLASS}\" in the " + - "manifest of $it") - - val pluginClassName = mainClass.removeSuffix(" ") - val c = instantiateClassName(classLoader, pluginClassName) - @Suppress("UNCHECKED_CAST") - Plugins.addPlugin(c as Class) - log(1, "Added plugin $c") + val pluginXml = JarUtils.extractTextFile(JarFile(it.jarFile.get()), PluginInfo.PLUGIN_XML) + if (pluginXml != null) { + val thisPluginInfo = PluginInfo.readPluginXml(pluginXml, classLoader) + pluginInfo.addPluginInfo(thisPluginInfo) + thisPluginInfo.plugins.forEach { + Plugins.addPluginInstance(it) } + } else { + throw KobaltException("Plugin $it doesn't contain a ${PluginInfo.PLUGIN_XML} file") } - } + } executor.shutdown() } diff --git a/src/main/kotlin/com/beust/kobalt/api/KobaltPluginXml.kt b/src/main/kotlin/com/beust/kobalt/api/KobaltPluginXml.kt index fe6a6542..dae3a3c2 100644 --- a/src/main/kotlin/com/beust/kobalt/api/KobaltPluginXml.kt +++ b/src/main/kotlin/com/beust/kobalt/api/KobaltPluginXml.kt @@ -1,6 +1,8 @@ package com.beust.kobalt.api import com.beust.kobalt.maven.IClasspathDependency +import com.beust.kobalt.misc.log +import java.io.ByteArrayInputStream import java.io.File import java.io.InputStream import java.io.OutputStream @@ -41,7 +43,10 @@ interface IFactory { fun instanceOf(c: Class) : T } -class ContributorFactory : IFactory { +/** + * If a plug-in didn't specify a factory, we use our own injector to instantiate all its components. + */ +class GuiceFactory : IFactory { override fun instanceOf(c: Class) : T = Kobalt.INJECTOR.getInstance(c) } @@ -116,7 +121,7 @@ class ClassNameXml { * all the contributors instantiated and other information that Kobalt can actually use. Kobalt code that * needs to access plug-in info can then just inject a PluginInfo object. */ -class PluginInfo(val xml: KobaltPluginXml) { +class PluginInfo(val xml: KobaltPluginXml, val classLoader: ClassLoader?) { val plugins = arrayListOf() val projectContributors = arrayListOf() val classpathContributors = arrayListOf() @@ -130,48 +135,71 @@ class PluginInfo(val xml: KobaltPluginXml) { // repos companion object { + val PLUGIN_XML = "META-INF/plugin.xml" // Plugins.PLUGIN_XML) + /** * Read Kobalt's own plugin.xml. */ - fun readKobaltPluginXml() : PluginInfo { + fun readKobaltPluginXml(): PluginInfo { // Note: use forward slash here since we're looking up this file in a .jar file - val pluginXml = "META-INF/plugin.xml" // Plugins.PLUGIN_XML) - val url = Kobalt::class.java.classLoader.getResource(pluginXml) + val url = Kobalt::class.java.classLoader.getResource(PLUGIN_XML) if (url != null) { return readPluginXml(url.openConnection().inputStream) } else { - throw AssertionError("Couldn't find $pluginXml") + throw AssertionError("Couldn't find $PLUGIN_XML") } } /** * Read a general plugin.xml. */ - private fun readPluginXml(ins: InputStream): PluginInfo { + fun readPluginXml(ins: InputStream, classLoader: ClassLoader? = null): PluginInfo { val jaxbContext = JAXBContext.newInstance(KobaltPluginXml::class.java) val kotlinPlugin: KobaltPluginXml = jaxbContext.createUnmarshaller().unmarshal(ins) as KobaltPluginXml - return PluginInfo(kotlinPlugin) + return PluginInfo(kotlinPlugin, classLoader) } + + fun readPluginXml(s: String, classLoader: ClassLoader? = null) + = readPluginXml(ByteArrayInputStream(s.toByteArray(Charsets.UTF_8)), classLoader) } init { - val factory = Class.forName(xml.factoryClassName).newInstance() as IFactory + val factory = if (xml.factoryClassName != null) { + Class.forName(xml.factoryClassName).newInstance() as IFactory + } else { + GuiceFactory() + } + + fun forName(className: String) = + if (classLoader != null) classLoader.loadClass(className) + else Class.forName(className) + xml.plugins?.className?.forEach { - plugins.add(factory.instanceOf(Class.forName(it)) as Plugin) + plugins.add(factory.instanceOf(forName(it)) as Plugin) } xml.classpathClassName?.className?.forEach { - classpathContributors.add(factory.instanceOf(Class.forName(it)) as IClasspathContributor) + classpathContributors.add(factory.instanceOf(forName(it)) as IClasspathContributor) } xml.projectClassName?.className?.forEach { - projectContributors.add(factory.instanceOf(Class.forName(it)) as IProjectContributor) + projectContributors.add(factory.instanceOf(forName(it)) as IProjectContributor) } xml.initClassName?.className?.forEach { - initContributors.add(factory.instanceOf(Class.forName(it)) as IInitContributor) + initContributors.add(factory.instanceOf(forName(it)) as IInitContributor) } xml.repoClassName?.className?.forEach { - repoContributors.add(factory.instanceOf(Class.forName(it)) as IRepoContributor) + repoContributors.add(factory.instanceOf(forName(it)) as IRepoContributor) } } + + fun addPluginInfo(pluginInfo: PluginInfo) { + log(2, "Found new plug-in, adding it to pluginInfo: $pluginInfo") + + plugins.addAll(pluginInfo.plugins) + classpathContributors.addAll(pluginInfo.classpathContributors) + projectContributors.addAll(pluginInfo.projectContributors) + initContributors.addAll(pluginInfo.initContributors) + repoContributors.addAll(pluginInfo.repoContributors) + } } diff --git a/src/main/kotlin/com/beust/kobalt/kotlin/BuildFileCompiler.kt b/src/main/kotlin/com/beust/kobalt/kotlin/BuildFileCompiler.kt index 2faed971..0e5eb9a6 100644 --- a/src/main/kotlin/com/beust/kobalt/kotlin/BuildFileCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/kotlin/BuildFileCompiler.kt @@ -172,7 +172,9 @@ public class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val b } private fun isSameVersionFile(directory: File) = - File(directory, VERSION_FILE).readText() == Kobalt.version + with(File(directory, VERSION_FILE)) { + ! exists() || (exists() && readText() == Kobalt.version) + } private fun generateJarFile(context: KobaltContext, buildFile: BuildFile, buildScriptJarFile: File) { val kotlintDeps = jvmCompiler.calculateDependencies(null, context, listOf()) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt index fc3e6133..badb7b48 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/JarUtils.kt @@ -2,11 +2,13 @@ package com.beust.kobalt.plugin.packaging import com.beust.kobalt.IFileSpec import com.beust.kobalt.misc.log +import com.google.common.io.CharStreams import java.io.* import java.util.jar.JarEntry 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 public class JarUtils { @@ -92,6 +94,20 @@ public class JarUtils { } } + fun extractTextFile(zip : ZipFile, fileName: String) : String? { + val enumEntries = zip.entries() + while (enumEntries.hasMoreElements()) { + val file = enumEntries.nextElement() + if (file.name == fileName) { + log(2, "Found $fileName in $zip") + zip.getInputStream(file).use { ins -> + return CharStreams.toString(InputStreamReader(ins, "UTF-8")) + } + } + } + return null + } + fun extractJarFile(jarFile: File, destDir: File) { val jar = java.util.jar.JarFile(jarFile) val enumEntries = jar.entries() diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 3c3f195b..f31ba26f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,6 +1,6 @@ kobalt - com.beust.kobalt.api.ContributorFactory + com.beust.kobalt.api.GuiceFactory com.beust.kobalt.plugin.android.AndroidPlugin com.beust.kobalt.plugin.application.ApplicationPlugin