1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 00:17:11 -07:00

Revamping the plug-in class loading.

This commit is contained in:
Cedric Beust 2015-10-11 09:19:47 -07:00
parent bb8878ef63
commit 903f63b268
8 changed files with 260 additions and 155 deletions

View file

@ -9,6 +9,7 @@ import com.beust.kobalt.kotlin.ScriptCompiler
import com.beust.kobalt.maven.*
import com.beust.kobalt.misc.*
import com.beust.kobalt.SystemProperties
import com.beust.kobalt.kotlin.ScriptCompiler2
import com.beust.kobalt.plugin.publish.JCenterApi
import com.beust.kobalt.plugin.publish.UnauthenticatedJCenterApi
import com.beust.kobalt.wrapper.Wrapper
@ -35,6 +36,7 @@ public fun main(argv: Array<String>) {
private class Main @Inject constructor(
val scriptCompilerFactory: ScriptCompiler.IFactory,
val script2: ScriptCompiler2.IFactory,
val plugins: Plugins,
val taskManager: TaskManager,
val http: Http,
@ -115,20 +117,21 @@ private class Main @Inject constructor(
if (! buildFile.exists()) {
jc.usage()
} else {
// Install all the plugins found
plugins.installDynamicPlugins(arrayListOf(buildFile))
// Compile the build script
val output = scriptCompilerFactory.create(plugins.pluginJarFiles,
{ n: String, j: File? ->
plugins.instantiateClassName(n, j)
})
.compile(buildFile, buildFile.lastModified(), KFiles.findBuildScriptLocation(buildFile, SCRIPT_JAR))
val allProjects = script2.create(arrayListOf(buildFile)).findProjects()
// // Install all the plugins found
// val classLoaders = plugins.installDynamicPlugins(arrayListOf(buildFile))
//
// // Compile the build script
// val output = scriptCompilerFactory.create(plugins.pluginJarFiles,
// // @@
// { cl: ClassLoader, n: String -> plugins.instantiateClassName(classLoaders.get(0), n) }
// ).compile(buildFile, buildFile.lastModified(),
// KFiles.findBuildScriptLocation(buildFile, SCRIPT_JAR))
//
// Force each project.directory to be an absolute path, if it's not already
//
output.projects.forEach {
allProjects.forEach {
val fd = File(it.directory)
if (! fd.isAbsolute) {
it.directory =
@ -140,7 +143,7 @@ private class Main @Inject constructor(
}
}
plugins.applyPlugins(KobaltContext(args), output.projects)
plugins.applyPlugins(KobaltContext(args), allProjects)
if (args.tasks) {
//
@ -157,14 +160,14 @@ private class Main @Inject constructor(
}
println(sb.toString())
} else if (args.checkVersions) {
checkVersions.run(output.projects)
checkVersions.run(allProjects)
} else if (args.update) {
updateKobalt.updateKobalt()
} else {
//
// Launch the build
//
taskManager.runTargets(args.targets, output.projects)
taskManager.runTargets(args.targets, allProjects)
}
}
}

View file

@ -4,8 +4,6 @@ import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.internal.TaskManager
import com.beust.kobalt.internal.TaskResult
import com.beust.kobalt.kotlin.BuildFile
import com.beust.kobalt.kotlin.ScriptCompiler
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.maven.IClasspathDependency
import com.beust.kobalt.maven.KobaltException
@ -13,23 +11,17 @@ import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltExecutors
import com.beust.kobalt.misc.KobaltLogger
import com.beust.kobalt.misc.countChar
import com.beust.kobalt.plugin.DefaultPlugin
import com.beust.kobalt.plugin.java.JavaPlugin
import com.beust.kobalt.plugin.kotlin.KotlinPlugin
import com.beust.kobalt.plugin.packaging.PackagingPlugin
import com.beust.kobalt.plugin.publish.PublishPlugin
import com.google.inject.Provider
import java.io.File
import java.io.FileInputStream
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.net.URL
import java.net.URLClassLoader
import java.nio.charset.Charset
import java.nio.file.Paths
import java.util.ArrayList
import java.util.HashMap
import java.util.*
import java.util.jar.JarInputStream
import javax.inject.Inject
import javax.inject.Singleton
@ -39,27 +31,25 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider<Tas
val files: KFiles,
val depFactory: DepFactory,
val localRepo: LocalRepo,
val executors: KobaltExecutors,
val scriptCompilerFactory: ScriptCompiler.IFactory): KobaltLogger {
val executors: KobaltExecutors): KobaltLogger {
companion object {
public val MANIFEST_PLUGIN_CLASS : String = "Kobalt-Plugin-Class"
private var pluginMap = hashMapOf<String, Plugin>()
private var storageMap = HashMap<String, HashMap<String, Any>>()
fun storeValue(pluginName: String, key: String, value: Any) {
var values = storageMap.get(pluginName)
if (values == null) {
values = hashMapOf<String, Any>()
storageMap.put(pluginName, values)
}
values.put(key, value)
}
fun getValue(pluginName: String, key: String) : Any? {
return storageMap.get(pluginName)?.get(key)
}
// private var storageMap = HashMap<String, HashMap<String, Any>>()
// fun storeValue(pluginName: String, key: String, value: Any) {
// var values = storageMap.get(pluginName)
// if (values == null) {
// values = hashMapOf<String, Any>()
// storageMap.put(pluginName, values)
// }
// values.put(key, value)
// }
//
// fun getValue(pluginName: String, key: String) : Any? {
// return storageMap.get(pluginName)?.get(key)
// }
val defaultPlugin : Plugin get() = getPlugin(DefaultPlugin.NAME)!!
@ -112,7 +102,6 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider<Tas
plugin.apply(project, context)
}
var currentClass : Class<in Any> = plugin.javaClass
// Tasks can come from two different places: plugin classes and build files.
@ -180,67 +169,30 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider<Tas
return true
}
/**
* Jar files for all the plugins.
*/
public val pluginJarFiles : ArrayList<String> = arrayListOf()
val dependencies = arrayListOf<IClasspathDependency>()
/**
* Parse the build files, locate all the plugins, download them and make them available to be
* used on the classpath of the build file.
*/
fun installDynamicPlugins(files: List<BuildFile>) {
//
// Extract all the plugin() and repos() code into a separate script (pluginCode)
//
files.forEach {
val pluginCode = arrayListOf<String>()
var parenCount = 0
it.path.toFile().forEachLine(Charset.defaultCharset()) { line ->
if (line.startsWith("import")) {
pluginCode.add(line)
}
var index = line.indexOf("plugins(")
if (index == -1) index = line.indexOf("repos(")
if (parenCount > 0 || index >= 0) {
if (index == -1) index = 0
with(line.substring(index)) {
parenCount += line countChar '('
if (parenCount > 0) {
pluginCode.add(line)
}
parenCount -= line countChar ')'
}
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)
}
}
//
// Compile and run pluginCode, which contains all the plugins() calls extracted. This
// will add all the dynamic plugins found in this code to Plugins.dynamicPlugins
//
val pluginSourceFile = KFiles.createTempFile(".kt")
pluginSourceFile.writeText(pluginCode.join("\n"), Charset.defaultCharset())
log(2, "Saved ${pluginSourceFile.absolutePath}")
scriptCompilerFactory.create(pluginJarFiles,
{ n: String, j: File? -> instantiateClassName(n, j)
}).compile(BuildFile(Paths.get(pluginSourceFile.absolutePath), "Plugins"),
it.lastModified(),
KFiles.findBuildScriptLocation(it, "preBuildScript.jar"))
val allTasks : List<PluginTask>
get() {
val result = arrayListOf<PluginTask>()
Plugins.plugins.forEach { plugin ->
result.addAll(plugin.tasks)
}
return result
}
//
// Locate all the jar files for the dynamic plugins we just discovered
//
dependencies.addAll(dynamicPlugins.map {
pluginJarFiles.add(it.jarFile.get().absolutePath)
it
})
//
// Materialize all the jar files, instantiate their plugin main class and add it to Plugins
//
fun installPlugins(dependencies: List<IClasspathDependency>, classLoader: ClassLoader) {
val executor = executors.newExecutor("Plugins", 5)
dependencies.forEach {
//
@ -256,13 +208,13 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider<Tas
try {
fis = FileInputStream(it.jarFile.get())
jis = JarInputStream(fis)
val manifest = jis.getManifest()
val mainClass = manifest.getMainAttributes().getValue(Plugins.MANIFEST_PLUGIN_CLASS) ?:
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(pluginClassName)
val c = instantiateClassName(classLoader, pluginClassName)
@Suppress("UNCHECKED_CAST")
Plugins.addPlugin(c as Class<BasePlugin>)
log(1, "Added plugin ${c}")
@ -274,48 +226,4 @@ public class Plugins @Inject constructor (val taskManagerProvider : Provider<Tas
executor.shutdown()
}
public fun instantiateClassName(className : String, buildScriptJarFile: File? = null) : Class<*> {
// fun jarToUrl(jarAbsolutePath: String) = URL("file://" + jarAbsolutePath)
fun jarToUrl(path: String) = URL("jar", "", "file:${path}!/")
// We need the jar files to be first in the url list otherwise the Build.kt files resolved
// might be Kobalt's own
val urls = arrayListOf<URL>()
buildScriptJarFile?.let {
urls.add(jarToUrl(it.absolutePath))
}
urls.add(jarToUrl(files.kobaltJar))
urls.addAll(pluginJarFiles.map { jarToUrl(it) })
val classLoader = URLClassLoader(urls.toArray(arrayOfNulls<URL>(urls.size())))
try {
log(2, "Instantiating ${className}")
return classLoader.loadClass(className)
} catch(ex: Exception) {
throw KobaltException("Couldn't instantiate ${className}: ${ex}")
}
}
val allTasks : List<PluginTask>
get() {
val result = arrayListOf<PluginTask>()
Plugins.plugins.forEach { plugin ->
result.addAll(plugin.tasks)
}
return result
}
/**
* @return the tasks accepted by at least one project
*/
fun findTasks(task: String): List<PluginTask> {
val tasks = allTasks.filter { task == it.name }
if (tasks.isEmpty()) {
throw KobaltException("Couldn't find task ${task}")
} else {
return tasks
}
}
}

View file

@ -8,13 +8,13 @@ import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.maven.KobaltException
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltLogger
import com.beust.kobalt.plugin.kotlin.kotlinCompiler
import com.beust.kobalt.plugin.kotlin.kotlinCompilePrivate
import com.google.inject.assistedinject.Assisted
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.lang.reflect.Modifier
import java.net.URLClassLoader
import java.util.jar.JarInputStream
import javax.inject.Inject
import kotlin.properties.Delegates
@ -24,17 +24,17 @@ import kotlin.properties.Delegates
*/
public class ScriptCompiler @Inject constructor(
@Assisted("jarFiles") val jarFiles: List<String>,
@Assisted("instantiate") val instantiate: (String, File?) -> Class<*>,
@Assisted("instantiate") val instantiate: (ClassLoader, String) -> Class<*>,
val files: KFiles) : KobaltLogger {
interface IFactory {
fun create(@Assisted("jarFiles") jarFiles: List<String>,
@Assisted("instantiate") instantiate: (String, File?) -> Class<*>) : ScriptCompiler
@Assisted("instantiate") instantiate: (ClassLoader, String) -> Class<*>) : ScriptCompiler
}
private var buildScriptJarFile by Delegates.notNull<File>()
public class CompileOutput(val projects: List<Project>, val plugins: List<String>)
public class CompileOutput(val projects: List<Project>, val plugins: List<String>, val classLoader: ClassLoader)
public fun compile(buildFile: BuildFile, lastModified: Long, jarFileName: String) : CompileOutput {
@ -54,7 +54,8 @@ public class ScriptCompiler @Inject constructor(
log(2, "Need to recompile ${buildFile.name}")
generateJarFile(buildFile)
}
return CompileOutput(instantiateBuildFile(), arrayListOf<String>())
val pi = instantiateBuildFile()
return CompileOutput(pi.projects, arrayListOf<String>(), pi.classLoader)
}
private fun generateJarFile(buildFile: BuildFile) {
@ -66,10 +67,14 @@ public class ScriptCompiler @Inject constructor(
}.compile()
}
private fun instantiateBuildFile() : List<Project> {
class ProjectInfo(val projects: List<Project>, val classLoader: ClassLoader)
private fun instantiateBuildFile() : ProjectInfo {
val result = arrayListOf<Project>()
var stream : InputStream? = null
val classLoader = URLClassLoader(arrayOf(buildScriptJarFile.toURI().toURL()))
try {
log(1, "!!!!!!!!! CREATED CLASSLOADER FOR buildScriptJarFile: $classLoader")
stream = JarInputStream(FileInputStream(buildScriptJarFile))
var entry = stream.nextJarEntry
@ -78,7 +83,7 @@ public class ScriptCompiler @Inject constructor(
val name = entry.name;
if (name.endsWith(".class")) {
val className = name.substring(0, name.length() - 6).replace("/", ".")
var cl : Class<*>? = instantiate(className, buildScriptJarFile)
var cl : Class<*>? = instantiate(classLoader, className)
if (cl != null) {
classes.add(cl)
} else {
@ -123,7 +128,7 @@ public class ScriptCompiler @Inject constructor(
}
// Now that we all the projects, sort them topologically
return Kobalt.sortProjects(result)
return ProjectInfo(Kobalt.sortProjects(result), classLoader)
}
}

View file

@ -0,0 +1,178 @@
package com.beust.kobalt.kotlin
import com.beust.kobalt.Plugins
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.api.Plugin
import com.beust.kobalt.api.Project
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.maven.KobaltException
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltLogger
import com.beust.kobalt.misc.countChar
import com.beust.kobalt.plugin.kotlin.kotlinCompilePrivate
import com.google.inject.assistedinject.Assisted
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.lang.reflect.Modifier
import java.net.URL
import java.net.URLClassLoader
import java.nio.charset.Charset
import java.nio.file.Paths
import java.util.jar.JarInputStream
import javax.inject.Inject
public class ScriptCompiler2 @Inject constructor(@Assisted("buildFiles") val buildFiles: List<BuildFile>,
val files: KFiles, val plugins: Plugins) : KobaltLogger {
interface IFactory {
fun create(@Assisted("buildFiles") buildFiles: List<BuildFile>) : ScriptCompiler2
}
private val SCRIPT_JAR = "buildScript.jar"
fun findProjects(): List<Project> {
val result = arrayListOf<Project>()
buildFiles.forEach { buildFile ->
val pluginUrls = findPlugInUrls(buildFile)
val script = File(KFiles.findBuildScriptLocation(buildFile, SCRIPT_JAR))
log(1, "Compiling main build file ${buildFile.path}")
kotlinCompilePrivate {
classpath(files.kobaltJar)
classpath(pluginUrls.map { it.file })
sourceFiles(listOf(buildFile.path.toFile().absolutePath))
output = script.absolutePath
}.compile()
val output = parseBuildScriptJarFile(script, pluginUrls)
result.addAll(output.projects)
}
return result
}
private fun findPlugInUrls(buildFile: BuildFile): List<URL> {
val result = arrayListOf<URL>()
val pluginCode = arrayListOf(
"import com.beust.kobalt.*",
"import com.beust.kobalt.api.*"
)
var parenCount = 0
buildFile.path.toFile().forEachLine(Charset.defaultCharset()) { line ->
var index = line.indexOf("plugins(")
if (index == -1) index = line.indexOf("repos(")
if (parenCount > 0 || index >= 0) {
if (index == -1) index = 0
with(line.substring(index)) {
parenCount += line countChar '('
if (parenCount > 0) {
pluginCode.add(line)
}
parenCount -= line countChar ')'
}
}
}
//
// Compile and run pluginCode, which contains all the plugins() calls extracted. This
// will add all the dynamic plugins found in this code to Plugins.dynamicPlugins
//
val pluginSourceFile = KFiles.createTempFile(".kt")
pluginSourceFile.writeText(pluginCode.join("\n"), Charset.defaultCharset())
log(2, "Saved ${pluginSourceFile.absolutePath}")
//
// Compile to preBuildScript.jar
//
val buildScriptJar = KFiles.findBuildScriptLocation(buildFile, "preBuildScript.jar")
val buildScriptJarFile = File(buildScriptJar)
buildScriptJarFile.parentFile.mkdirs()
generateJarFile(BuildFile(Paths.get(pluginSourceFile.path), "Plugins"), buildScriptJarFile)
//
// Run preBuildScript.jar to initialize plugins and repos
//
val projectInfo = parseBuildScriptJarFile(buildScriptJarFile, arrayListOf<URL>())
//
// All the plug-ins are now in Plugins.dynamicPlugins, download them if they're not already
//
Plugins.dynamicPlugins.forEach {
result.add(it.jarFile.get().toURI().toURL())
}
return result
}
private fun generateJarFile(buildFile: BuildFile, buildScriptJarFile: File) {
kotlinCompilePrivate {
classpath(files.kobaltJar)
sourceFiles(buildFile.path.toFile().absolutePath)
output = buildScriptJarFile.absolutePath
}.compile()
}
class BuildScriptInfo(val projects: List<Project>, val classLoader: ClassLoader)
private fun parseBuildScriptJarFile(buildScriptJarFile: File, urls: List<URL>) : BuildScriptInfo {
val result = arrayListOf<Project>()
var stream : InputStream? = null
val allUrls = arrayListOf<URL>().plus(urls).plus(arrayOf(
buildScriptJarFile.toURI().toURL(),
File(files.kobaltJar).toURI().toURL()))
.toTypedArray()
val classLoader = URLClassLoader(allUrls)
//
// Install all the plugins
//
plugins.installPlugins(Plugins.dynamicPlugins, classLoader)
try {
stream = JarInputStream(FileInputStream(buildScriptJarFile))
var entry = stream.nextJarEntry
val classes = hashSetOf<Class<*>>()
while (entry != null) {
val name = entry.name;
if (name.endsWith(".class")) {
val className = name.substring(0, name.length() - 6).replace("/", ".")
var cl : Class<*>? = classLoader.loadClass(className)
if (cl != null) {
classes.add(cl)
} else {
throw KobaltException("Couldn't instantiate ${className}")
}
}
entry = stream.nextJarEntry;
}
// Invoke all the "val" found on the _DefaultPackage class (the Build.kt file)
classes.filter { cls ->
cls.name != "_DefaultPackage"
}.forEach { cls ->
cls.methods.forEach { method ->
// Invoke vals and see if they return a Project
if (method.name.startsWith("get") && Modifier.isStatic(method.modifiers)) {
val r = method.invoke(null)
if (r is Project) {
log(2, "Found project ${r} in class ${cls}")
result.add(r)
}
} else {
val taskAnnotation = method.getAnnotation(Task::class.java)
if (taskAnnotation != null) {
// Plugins.defaultPlugin.addTask(taskAnnotation, )
Plugins.defaultPlugin.methodTasks.add(Plugin.MethodTask(method, taskAnnotation))
}
}}
}
} finally {
stream?.close()
}
// Now that we all the projects, sort them topologically
return BuildScriptInfo(Kobalt.sortProjects(result), classLoader)
}
}

View file

@ -18,6 +18,11 @@ public class KFiles {
get() {
val jar = joinDir(distributionsDir, Kobalt.version, "kobalt/wrapper/kobalt-" + Kobalt.version + ".jar")
val jarFile = File(jar)
val envJar = System.getenv("KOBALT_JAR")
if (! jarFile.exists() && envJar != null) {
KobaltLogger.debug("Using kobalt jar $envJar")
return File(envJar).absolutePath
}
if (! jarFile.exists()) {
// Will only happen when building kobalt itself: the jar file might not be in the dist/ directory
// yet since we're currently building it. Instead, use the classes directly

View file

@ -20,6 +20,10 @@ public interface KobaltLogger {
fun warn(s: String, e: Throwable? = null) {
LoggerFactory.getLogger(KobaltLogger::class.java.simpleName).warn(s, e)
}
fun debug(s: String) {
LoggerFactory.getLogger(KobaltLogger::class.java.simpleName).debug(s)
}
}
final fun log(level: Int = 1, message: String) {

View file

@ -2,6 +2,7 @@ package com.beust.kobalt.misc
import com.beust.kobalt.Args
import com.beust.kobalt.kotlin.ScriptCompiler
import com.beust.kobalt.kotlin.ScriptCompiler2
import com.beust.kobalt.maven.ArtifactFetcher
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.maven.Pom
@ -41,6 +42,7 @@ public open class MainModule(val args: Args) : AbstractModule() {
JCenterApi.IFactory::class.java,
Pom.IFactory::class.java,
ScriptCompiler.IFactory::class.java,
ScriptCompiler2.IFactory::class.java,
ArtifactFetcher.IFactory::class.java)
.forEach {
install(builder.build(it))

View file

@ -17,7 +17,7 @@ import kotlin.properties.Delegates
* @since 08 03, 2015
*/
@Singleton
private class KotlinCompiler @Inject constructor(override val localRepo : LocalRepo,
class KotlinCompiler @Inject constructor(override val localRepo : LocalRepo,
override val files: com.beust.kobalt.misc.KFiles,
override val depFactory: DepFactory,
override val dependencyManager: DependencyManager,