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

Merge branch 'master' of github.com:cbeust/kobalt

This commit is contained in:
Cedric Beust 2015-12-17 18:37:26 -08:00
commit 8acd8b8fce
133 changed files with 861 additions and 125 deletions

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kobalt (Compile)" level="project" />
<orderEntry type="library" name="kobalt-plugin-api (Compile)" level="project" />
<orderEntry type="library" scope="TEST" name="kobalt-plugin-api (Test)" level="project" />
</component>
</module>

View file

@ -0,0 +1,60 @@
package com.beust.kobalt
import com.beust.jcommander.Parameter
class Args {
@Parameter
var targets: List<String> = arrayListOf()
@Parameter(names = arrayOf("-bf", "--buildFile"), description = "The build file")
var buildFile: String? = null
@Parameter(names = arrayOf("--checkVersions"), description = "Check if there are any newer versions of the " +
"dependencies")
var checkVersions = false
@Parameter(names = arrayOf("--client"))
var client: Boolean = false
@Parameter(names = arrayOf("--dev"), description = "Turn of dev mode, resulting in a more verbose log output")
var dev: Boolean = false
@Parameter(names = arrayOf("--download"), description = "Force a download from the downloadUrl in the wrapper")
var download: Boolean = false
@Parameter(names = arrayOf("--dryRun"), description = "Display all the tasks that will get run without " +
"actually running them")
var dryRun: Boolean = false
@Parameter(names = arrayOf("--help", "--usage"), description = "Display the help")
var usage: Boolean = false
@Parameter(names = arrayOf("-i", "--init"), description = "Create a build file based on the current project")
var init: Boolean = false
@Parameter(names = arrayOf("--log"), description = "Define the log level (1-3)")
var log: Int = 1
companion object {
const val DEFAULT_SERVER_PORT = 1234
}
@Parameter(names = arrayOf("--port"), description = "Port, if --server was specified")
var port: Int = DEFAULT_SERVER_PORT
@Parameter(names = arrayOf("--profiles"), description = "Comma-separated list of profiles to run")
var profiles: String? = null
@Parameter(names = arrayOf("--resolve"), description = "Resolve the given dependency and display its tree")
var dependency: String? = null
@Parameter(names = arrayOf("--server"), description = "Run in server mode")
var serverMode: Boolean = false
@Parameter(names = arrayOf("--tasks"), description = "Display the tasks available for this build")
var tasks: Boolean = false
@Parameter(names = arrayOf("--update"), description = "Update to the latest version of Kobalt")
var update: Boolean = false
}

View file

@ -0,0 +1,97 @@
package com.beust.kobalt
import com.beust.kobalt.misc.log
import java.util.*
class AsciiArt {
companion object {
private val BANNERS = arrayOf(
" __ __ __ __ __ \n" +
" / //_/ ____ / /_ ____ _ / / / /_\n" +
" / ,< / __ \\ / __ \\ / __ `/ / / / __/\n" +
" / /| | / /_/ / / /_/ // /_/ / / / / /_ \n" +
" /_/ |_| \\____/ /_.___/ \\__,_/ /_/ \\__/ ",
" _ __ _ _ _ \n" +
" | |/ / ___ | |__ __ _ | | | |_ \n" +
" | ' / / _ \\ | '_ \\ / _` | | | | __|\n" +
" | . \\ | (_) | | |_) | | (_| | | | | |_ \n" +
" |_|\\_\\ \\___/ |_.__/ \\__,_| |_| \\__| "
)
val banner : String get() = BANNERS[Random().nextInt(BANNERS.size)]
// fun box(s: String) : List<String> = box(listOf(s))
val horizontalSingleLine = "\u2500\u2500\u2500\u2500\u2500"
val horizontalDoubleLine = "\u2550\u2550\u2550\u2550\u2550"
// fun horizontalLine(n: Int) = StringBuffer().apply {
// repeat(n, { append("\u2500") })
// }.toString()
fun box(strings: List<String>) : List<String> {
val ul = "\u2554"
val ur = "\u2557"
val h = "\u2550"
val v = "\u2551"
val bl = "\u255a"
val br = "\u255d"
fun r(n: Int, w: String) : String {
with(StringBuffer()) {
repeat(n, { append(w) })
return toString()
}
}
val maxString: String = strings.maxBy { it.length } ?: ""
val max = maxString.length
val result = arrayListOf(ul + r(max + 2, h) + ur)
result.addAll(strings.map { "$v ${center(it, max - 2)} $v" })
result.add(bl + r(max + 2, h) + br)
return result
}
private fun fill(n: Int) = StringBuffer().apply { repeat(n, { append(" ")})}.toString()
val defaultLog : (s: String) -> Unit = { log(1, " $it") }
fun logBox(strings: List<String>, print: (String) -> Unit = defaultLog) {
box(strings).forEach {
print(it)
}
}
fun logBox(s: String, print: (String) -> Unit = defaultLog) {
logBox(listOf(s), print)
}
fun center(s: String, width: Int) : String {
val diff = width - s.length
val spaces = diff / 2 + 1
return fill(spaces) + s + fill(spaces + if (diff % 2 == 1) 1 else 0)
}
const val RESET = "\u001B[0m"
const val BLACK = "\u001B[30m"
const val RED = "\u001B[31m"
const val GREEN = "\u001B[32m"
const val YELLOW = "\u001B[33m";
const val BLUE = "\u001B[34m"
const val PURPLE = "\u001B[35m"
const val CYAN = "\u001B[36m"
const val WHITE = "\u001B[37m"
private fun wrap(s: String, color: String) = color + s + RESET
private fun blue(s: String) = wrap(s, BLUE)
private fun red(s: String) = wrap(s, RED)
private fun yellow(s: String) = wrap(s, YELLOW)
fun taskColor(s: String) = s
fun errorColor(s: String) = red(s)
fun warnColor(s: String) = red(s)
}
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt
import com.beust.kobalt.api.IPlugin
import com.beust.kobalt.api.PluginTask
import com.beust.kobalt.api.Project
public abstract class BasePluginTask(override val plugin: IPlugin,
override val name: String,
override val doc: String,
override val project: Project)
: PluginTask()

View file

@ -0,0 +1,61 @@
package com.beust.kobalt
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.api.annotation.Directive
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.maven.dependency.FileDependency
import java.io.File
@Directive
fun homeDir(vararg dirs: String) : String = SystemProperties.homeDir +
File.separator + dirs.toArrayList().joinToString(File.separator)
@Directive
fun localMavenRepo() = homeDir(".m2" + File.separator + "repository/")
@Directive
fun file(file: String) : String = FileDependency.PREFIX_FILE + file
@Directive
fun plugins(vararg dependency : IClasspathDependency) {
dependency.forEach { Plugins.addDynamicPlugin(it) }
}
@Directive
fun plugins(vararg dependencies : String) {
val factory = Kobalt.INJECTOR.getInstance(DepFactory::class.java)
dependencies.forEach {
Plugins.addDynamicPlugin(factory.create(it))
}
}
data class HostConfig(var url: String = "", var username: String? = null, var password: String? = null) {
fun hasAuth() : Boolean {
return (! username.isNullOrBlank()) && (! password.isNullOrBlank())
}
override fun toString() : String {
return url + if (username != null) {
"username: $username, password: ***"
} else {
""
}
}
}
@Directive
fun repos(vararg repos : String) {
repos.forEach { Kobalt.addRepo(HostConfig(it)) }
}
@Directive
fun authRepos(vararg repos : HostConfig) {
repos.forEach { Kobalt.addRepo(it) }
}
@Directive
fun authRepo(init: HostConfig.() -> Unit) = HostConfig().apply { init() }
@Directive
fun glob(g: String) : IFileSpec.Glob = IFileSpec.Glob(g)

View file

@ -0,0 +1,14 @@
package com.beust.kobalt
object Constants {
val BUILD_FILE_NAME = "Build.kt"
val BUILD_FILE_DIRECTORY = "kobalt/src"
internal val DEFAULT_REPOS = listOf(
"http://repo1.maven.org/maven2/",
"https://maven-central.storage.googleapis.com/",
"https://repository.jboss.org/nexus/content/repositories/root_repository/",
"https://jcenter.bintray.com/"
)
}

View file

@ -0,0 +1,38 @@
package com.beust.kobalt
import com.beust.kobalt.misc.log
import java.io.File
import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
sealed class IFileSpec {
abstract fun toFiles(directory: String): List<File>
class FileSpec(val spec: String) : IFileSpec() {
override public fun toFiles(directory: String) = arrayListOf(File(spec))
override public fun toString() = spec
}
class Glob(val spec: String) : IFileSpec() {
override public fun toFiles(directory: String): List<File> {
val result = arrayListOf<File>()
val matcher = FileSystems.getDefault().getPathMatcher("glob:${spec}")
Files.walkFileTree(Paths.get(directory), object : SimpleFileVisitor<Path>() {
override public fun visitFile(path: Path, attrs: BasicFileAttributes): FileVisitResult {
val rel = Paths.get(directory).relativize(path)
if (matcher.matches(rel)) {
log(3, "Adding ${rel.toFile()}")
result.add(rel.toFile())
}
return FileVisitResult.CONTINUE
}
})
return result
}
override public fun toString() = spec
}
}

View file

@ -0,0 +1,30 @@
package com.beust.kobalt
import java.io.File
abstract public class JavaInfo {
public var javaExecutable: File? = null
get() = findExecutable("java")
public var javacExecutable: File? = null
get() = findExecutable("javac")
public var javadocExecutable: File? = null
get() = findExecutable("javadoc")
abstract public var javaHome: File?
abstract public var runtimeJar: File?
abstract public var toolsJar: File?
abstract public fun findExecutable(command: String) : File
companion object {
fun create(javaBase: File?): Jvm {
val vendor = System.getProperty("java.vm.vendor")
if (vendor.toLowerCase().startsWith("apple inc.")) {
return AppleJvm(OperatingSystem.Companion.current(), javaBase!!)
}
if (vendor.toLowerCase().startsWith("ibm corporation")) {
return IbmJvm(OperatingSystem.Companion.current(), javaBase!!)
}
return Jvm(OperatingSystem.Companion.current(), javaBase)
}
}
}

View file

@ -0,0 +1,160 @@
package com.beust.kobalt
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import java.io.File
import java.io.IOException
public open class Jvm constructor(
val os: OperatingSystem,
var javaBase: File? = null) : JavaInfo() {
private var _javaHome: File? = null
override public var javaHome: File? = null
get() = _javaHome!!
override public var runtimeJar: File? = null
private fun findRuntimeJar() : File? {
var runtimeJar = File(javaBase, "lib/rt.jar")
if (runtimeJar.exists()) {
return runtimeJar
}
runtimeJar = File(javaBase, "jre/lib/rt.jar")
return if (runtimeJar.exists()) runtimeJar else null
}
override public var toolsJar: File? = null
private var userSupplied: Boolean? = false
private var javaVersion: String? = null
init {
if (javaBase == null) {
//discover based on what's in the sys. property
try {
javaBase = File(System.getProperty("java.home")).canonicalFile
} catch (e: IOException) {
throw KobaltException(e)
}
_javaHome = findJavaHome(javaBase!!)
javaVersion = SystemProperties.Companion.javaVersion
userSupplied = false
} else {
//precisely use what the user wants and validate strictly further on
_javaHome = javaBase!!
userSupplied = true
javaVersion = null
}
toolsJar = findToolsJar(javaBase!!)
runtimeJar = findRuntimeJar()
}
private fun findJavaHome(javaBase: File): File {
val toolsJar = findToolsJar(javaBase)
if (toolsJar != null) {
return toolsJar.parentFile.parentFile
} else if (javaBase.name.equals("jre", true) && File(javaBase.parentFile,
"bin/java").exists()) {
return javaBase.parentFile
} else {
return javaBase
}
}
private fun findToolsJar(jh: File): File? {
javaHome = jh
var toolsJar = File(javaHome, "lib/tools.jar")
if (toolsJar.exists()) {
return toolsJar
}
if (javaHome!!.name.equals("jre", true)) {
javaHome = javaHome!!.parentFile
toolsJar = File(javaHome, "lib/tools.jar")
if (toolsJar.exists()) {
return toolsJar
}
}
if (os.isWindows()) {
val version = SystemProperties.Companion.javaVersion
if (javaHome!!.name.toRegex().matches("jre\\d+")
|| javaHome!!.name == "jre$version") {
javaHome = File(javaHome!!.parentFile, "jdk$version")
toolsJar = File(javaHome, "lib/tools.jar")
if (toolsJar.exists()) {
return toolsJar
}
}
}
return null
}
// open public fun isIbmJvm(): Boolean {
// return false
// }
override public fun findExecutable(command: String): File {
if (javaHome != null) {
val jdkHome = if (javaHome!!.endsWith("jre")) javaHome!!.parentFile else javaHome
val exec = File(jdkHome, "bin/" + command)
var executable = File(os.getExecutableName(exec.absolutePath))
if (executable.isFile) {
return executable
}
}
// if (userSupplied) {
// //then we want to validate strictly
// throw JavaHomeException(String.format("The supplied javaHome seems to be invalid." + " I cannot find the %s executable. Tried location: %s", command, executable.getAbsolutePath()))
// }
val pathExecutable = os.findInPath(command)
if (pathExecutable != null) {
log(1, "Unable to find the $command executable using home: " +
"$javaHome but found it on the PATH: $pathExecutable.")
return pathExecutable
}
warn("Unable to find the $command executable. Tried the java home: $javaHome" +
" and the PATH. We will assume the executable can be ran in the current " +
"working folder.")
return java.io.File(os.getExecutableName(command))
}
}
class AppleJvm : Jvm {
override var runtimeJar: File? = File(javaHome!!.getParentFile(), "Classes/classes.jar")
override var toolsJar: File? = File(javaHome!!.getParentFile(), "Classes/tools.jar")
constructor(os: OperatingSystem) : super(os) {
}
constructor(current: OperatingSystem, javaHome: File) : super(current, javaHome) {
}
/**
* {@inheritDoc}
*/
// fun getInheritableEnvironmentVariables(envVars: Map<String, *>): Map<String, *> {
// val vars = HashMap<String, Any>()
// for (entry in envVars.entrySet()) {
// if (entry.getKey().toRegex().matches("APP_NAME_\\d+") ||
// entry.getKey().toRegex().matches("JAVA_MAIN_CLASS_\\d+")) {
// continue
// }
// vars.put(entry.getKey(), entry.getValue())
// }
// return vars
// }
}
class IbmJvm(os: OperatingSystem, suppliedJavaBase: File) : Jvm(os, suppliedJavaBase) {
override var runtimeJar: File? = throw IllegalArgumentException("Not implemented")
override var toolsJar: File? = throw IllegalArgumentException("Not implemented")
// override fun isIbmJvm(): Boolean {
// return true
// }
}

View file

@ -0,0 +1,7 @@
package com.beust.kobalt
class KobaltException(s: String? = null, ex: Throwable? = null, val docUrl: String? = null)
: RuntimeException(s, ex) {
constructor(ex: Throwable?) : this(null, ex, null)
}

View file

@ -0,0 +1,298 @@
package com.beust.kobalt
import java.io.File
abstract class OperatingSystem {
override fun toString(): String {
return getName() + " " + getVersion() + " " + System.getProperty("os.arch")
}
fun getName(): String {
return System.getProperty("os.name")
}
fun getVersion(): String {
return System.getProperty("os.version")
}
open fun isWindows(): Boolean {
return false
}
open fun isUnix(): Boolean {
return false
}
open fun isMacOsX(): Boolean {
return false
}
open fun isLinux(): Boolean {
return false
}
abstract fun getNativePrefix(): String
abstract fun getScriptName(scriptPath: String): String
abstract fun getExecutableName(executablePath: String): String
abstract fun getSharedLibraryName(libraryName: String): String
abstract fun getStaticLibraryName(libraryName: String): String
abstract fun getFamilyName(): String
/**
* Locates the given executable in the system path. Returns null if not found.
*/
fun findInPath(name: String): File? {
val exeName = getExecutableName(name)
if (exeName.contains(File.separator)) {
val candidate = File(exeName)
if (candidate.isFile) {
return candidate
}
return null
}
for (dir in path) {
val candidate = File(dir, exeName)
if (candidate.isFile) {
return candidate
}
}
return null
}
// fun findAllInPath(name: String): List<File> {
// val all = LinkedList<File>()
//
// for (dir in getPath()) {
// val candidate = File(dir, name)
// if (candidate.isFile) {
// all.add(candidate)
// }
// }
//
// return all
// }
val path: List<File>
get() {
return (System.getenv(getPathVar()) ?: return emptyList()).split(File.pathSeparator).map { File(it) }
}
open fun getPathVar(): String {
return "PATH"
}
class Windows : OperatingSystem() {
override fun isWindows(): Boolean {
return true
}
override fun getFamilyName(): String {
return "windows"
}
override fun getScriptName(scriptPath: String): String {
return withSuffix(scriptPath, ".bat")
}
override fun getExecutableName(executablePath: String): String {
return withSuffix(executablePath, ".exe")
}
override fun getSharedLibraryName(libraryName: String): String {
return withSuffix(libraryName, ".dll")
}
override fun getStaticLibraryName(libraryName: String): String {
return withSuffix(libraryName, ".lib")
}
override fun getNativePrefix(): String {
var arch = System.getProperty("os.arch")
if ("i386" == arch) {
arch = "x86"
}
return "win32-" + arch
}
private fun withSuffix(executablePath: String, extension: String): String {
if (executablePath.toLowerCase().endsWith(extension)) {
return executablePath
}
return removeExtension(executablePath) + extension
}
private fun removeExtension(executablePath: String): String {
val fileNameStart = Math.max(executablePath.lastIndexOf('/'), executablePath.lastIndexOf('\\'))
val extensionPos = executablePath.lastIndexOf('.')
if (extensionPos > fileNameStart) {
return executablePath.substring(0, extensionPos)
}
return executablePath
}
override fun getPathVar(): String {
return "Path"
}
}
open class Unix : OperatingSystem() {
override fun getScriptName(scriptPath: String): String {
return scriptPath
}
override fun getFamilyName(): String {
return "unknown"
}
override fun getExecutableName(executablePath: String): String {
return executablePath
}
override fun getSharedLibraryName(libraryName: String): String {
return getLibraryName(libraryName, getSharedLibSuffix())
}
private fun getLibraryName(libraryName: String, suffix: String): String {
if (libraryName.endsWith(suffix)) {
return libraryName
}
val pos = libraryName.lastIndexOf('/')
if (pos >= 0) {
return libraryName.substring(0, pos + 1) + "lib" + libraryName.substring(pos + 1) + suffix
} else {
return "lib" + libraryName + suffix
}
}
protected open fun getSharedLibSuffix(): String {
return ".so"
}
override fun getStaticLibraryName(libraryName: String): String {
return getLibraryName(libraryName, ".a")
}
override fun isUnix(): Boolean {
return true
}
override fun getNativePrefix(): String {
val arch = getArch()
var osPrefix = getOsPrefix()
osPrefix += "-" + arch
return osPrefix
}
protected open fun getArch(): String {
var arch = System.getProperty("os.arch")
if ("x86" == arch) {
arch = "i386"
}
if ("x86_64" == arch) {
arch = "amd64"
}
if ("powerpc" == arch) {
arch = "ppc"
}
return arch
}
protected open fun getOsPrefix(): String {
var osPrefix = getName().toLowerCase()
val space = osPrefix.indexOf(" ")
if (space != -1) {
osPrefix = osPrefix.substring(0, space)
}
return osPrefix
}
}
class MacOs : Unix() {
override fun isMacOsX(): Boolean {
return true
}
override fun getFamilyName(): String {
return "os x"
}
override fun getSharedLibSuffix(): String {
return ".dylib"
}
override fun getNativePrefix(): String {
return "darwin"
}
}
class Linux : Unix() {
override fun isLinux(): Boolean {
return true
}
override fun getFamilyName(): String {
return "linux"
}
}
class FreeBSD : Unix()
class Solaris : Unix() {
override fun getFamilyName(): String {
return "solaris"
}
override fun getOsPrefix(): String {
return "sunos"
}
override fun getArch(): String {
val arch = System.getProperty("os.arch")
if (arch == "i386" || arch == "x86") {
return "x86"
}
return super.getArch()
}
}
companion object {
val WINDOWS: Windows = Windows()
val MAC_OS: MacOs = MacOs()
val SOLARIS: Solaris = Solaris()
val LINUX: Linux = Linux()
val FREE_BSD: FreeBSD = FreeBSD()
val UNIX: Unix = Unix()
fun current(): OperatingSystem {
return forName(System.getProperty("os.name"))
}
fun forName(os: String): OperatingSystem {
val osName = os.toLowerCase()
if (osName.contains("windows")) {
return WINDOWS
} else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) {
return MAC_OS
} else if (osName.contains("sunos") || osName.contains("solaris")) {
return SOLARIS
} else if (osName.contains("linux")) {
return LINUX
} else if (osName.contains("freebsd")) {
return FREE_BSD
} else {
// Not strictly true
return UNIX
}
}
}
}

View file

@ -0,0 +1,150 @@
package com.beust.kobalt
import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.internal.PluginInfo
import com.beust.kobalt.internal.TaskManager
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltExecutors
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.JarUtils
import com.google.inject.Provider
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.util.*
import java.util.jar.JarFile
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class Plugins @Inject constructor (val taskManagerProvider : Provider<TaskManager>,
val files: KFiles,
val depFactory: DepFactory,
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 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)
}
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.equals(Any::class.java))) {
currentClass.declaredMethods.map {
Pair(it, it.getAnnotation(Task::class.java))
}.filter {
it.second != null
}.filter {
isValidTaskMethod(it.first)
}.forEach {
if (Modifier.isPrivate(it.first.modifiers)) {
throw KobaltException("A task method cannot be private: ${it.first}")
}
val annotation = it.second
taskManager.staticTasks.add(TaskManager.StaticTask(plugin, it.first, annotation))
}
currentClass = currentClass.superclass
}
}
// Collect all the tasks from the task contributors
context.pluginInfo.taskContributors.forEach {
taskManager.dynamicTasks.addAll(it.tasksFor(context).map { TaskManager.PluginDynamicTask(it.plugin, it) })
}
// Now that we have collected all static and dynamic tasks, turn them all into plug-in tasks
taskManager.computePluginTasks(plugins, projects)
}
/**
* 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>, classLoader: 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).
depFactory.create(it.id, executor)
//
// Open the jar, parse its kobalt-plugin.xml and add the resulting PluginInfo to pluginInfo
//
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()
}
}

View file

@ -0,0 +1,81 @@
package com.beust.kobalt
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.maven.MavenId
import com.beust.kobalt.maven.RepoFinder
import com.beust.kobalt.maven.SimpleDep
import com.beust.kobalt.maven.dependency.MavenDependency
import com.beust.kobalt.misc.Node
import com.beust.kobalt.misc.log
import com.google.inject.Inject
import java.util.*
/**
* Display information about a Maven id.
*/
class ResolveDependency @Inject constructor(val repoFinder: RepoFinder, val localRepo: LocalRepo) {
val increment = 8
val leftFirst = "\u2558"
val leftMiddle = "\u255f"
val leftLast = "\u2559"
val vertical = "\u2551"
class Dep(val dep: IClasspathDependency, val level: Int)
fun run(id: String) {
val repoResult = repoFinder.findCorrectRepo(id)
val indent = -1
val originalDep = MavenDependency.create(id)
// We want to display the dependencies of the id we found, not the one we queries
val dep = MavenDependency.create(originalDep.shortId + repoResult.version)
val root = Node(Dep(dep, indent))
val seen = hashSetOf(id)
root.addChildren(findChildren(root, seen))
val simpleDep = SimpleDep(MavenId.create(id))
val url = repoResult.hostConfig.url + simpleDep.toJarFile(repoResult)
val localFile = localRepo.toFullPath(simpleDep.toJarFile(repoResult))
AsciiArt.logBox(listOf(id, url, localFile).map { " $it" }, {s -> println(s) })
display(root.children)
println("")
}
private fun display(nodes: List<Node<Dep>>) {
nodes.withIndex().forEach { indexNode ->
val node = indexNode.value
with(node.value) {
val left =
if (indexNode.index == nodes.size - 1) leftLast
else leftMiddle
val indent = level * increment
for(i in 0..indent - 2) {
if (i % increment == 0) print(vertical)
else print(" ")
}
println(left + " " + dep.id)
display(node.children)
}
}
}
private fun findChildren(root: Node<Dep>, seen: HashSet<String>): List<Node<Dep>> {
val result = arrayListOf<Node<Dep>>()
root.value.dep.directDependencies().forEach {
if (! seen.contains(it.id)) {
val dep = Dep(it, root.value.level + 1)
val node = Node(dep)
log(2, "Found dependency ${dep.dep.id} level: ${dep.level}")
result.add(node)
seen.add(it.id)
node.addChildren(findChildren(node, seen))
}
}
log(2, "Children for ${root.value.dep.id}: ${result.size}")
return result
}
}

View file

@ -0,0 +1,14 @@
package com.beust.kobalt
public class SystemProperties {
companion object {
val javaBase = System.getProperty("java.home") ?:
(System.getenv("JAVA_HOME") ?: throw IllegalArgumentException("JAVA_HOME not defined"))
val javaVersion = System.getProperty("java.version")
val homeDir = System.getProperty("user.home")
val tmpDir = System.getProperty("java.io.tmpdir")
val currentDir = System.getProperty("user.dir")
val username = System.getProperty("user.name")
}
}

View file

@ -0,0 +1,3 @@
package com.beust.kobalt
open public class TaskResult(val success: Boolean = true, val errorMessage: String? = null)

View file

@ -0,0 +1,20 @@
package com.beust.kobalt
import java.io.BufferedReader
import java.io.PrintWriter
import java.io.Reader
public class Template(val reader: Reader, val writer: PrintWriter, val map: Map<String, Any>) {
public fun execute() {
BufferedReader(reader).let {
it.forEachLine { line ->
var replacedLine = line
map.keys.forEach { key ->
replacedLine = replacedLine.replace("{{$key}}", map.get(key).toString(), false)
}
writer.append(replacedLine).append("\n")
}
}
}
}

View file

@ -0,0 +1,23 @@
package com.beust.kobalt
import com.beust.kobalt.api.Project
import com.beust.kobalt.api.annotation.Directive
class TestConfig(val project: Project) {
fun args(vararg arg: String) {
project.testArgs.addAll(arg)
}
fun jvmArgs(vararg arg: String) {
project.testJvmArgs.addAll(arg)
}
}
@Directive
fun Project.test(init: TestConfig.() -> Unit) : TestConfig {
val result = TestConfig(this)
result.init()
return result
}

View file

@ -0,0 +1,201 @@
package com.beust.kobalt
import com.beust.kobalt.api.*
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.log
import java.io.File
/**
* Capture the product flavor and the build type of a build.
*/
class Variant(val initialProductFlavor: ProductFlavorConfig? = null,
val initialBuildType: BuildTypeConfig? = null) {
val productFlavor: ProductFlavorConfig by lazy {
initialProductFlavor ?: Variant.DEFAULT_PRODUCT_FLAVOR
}
val buildType: BuildTypeConfig by lazy {
initialBuildType ?: Variant.DEFAULT_BUILD_TYPE
}
val isDefault : Boolean
get() = productFlavor == DEFAULT_PRODUCT_FLAVOR && buildType == DEFAULT_BUILD_TYPE
fun toTask(taskName: String) = taskName + productFlavor.name.capitalize() + buildType.name.capitalize()
/**
* for {internal, release}, return [internalRelease, internal, release]
*/
fun allDirectories(project: Project): List<String> {
val result = arrayListOf<String>()
result.add(toCamelcaseDir())
if (productFlavor != null) result.add(productFlavor.name)
if (buildType != null) result.add(buildType.name)
return result
}
fun resDirectories(project: Project) : List<File> = sourceDirectories(project, "res")
fun sourceDirectories(project: Project) : List<File> =
sourceDirectories(project, project.projectInfo.sourceDirectory)
/**
* suffix is either "java" (to find source files) or "res" (to find resources)
*/
private fun sourceDirectories(project: Project, suffix: String) : List<File> {
val result = arrayListOf<File>()
val sourceDirectories = project.sourceDirectories.map { File(it) }
if (isDefault) {
result.addAll(sourceDirectories)
} else {
result.addAll(allDirectories(project).map {
File(KFiles.joinDir("src", it, suffix))
}.filter {
it.exists()
})
// // The ordering of files is: 1) build type 2) product flavor 3) default
buildType.let {
val dir = File(KFiles.joinDir("src", it.name, project.projectInfo.sourceDirectory))
log(3, "Adding source for build type ${it.name}: ${dir.path}")
result.add(dir)
}
productFlavor.let {
val dir = File(KFiles.joinDir("src", it.name, project.projectInfo.sourceDirectory))
log(3, "Adding source for product flavor ${it.name}: ${dir.path}")
result.add(dir)
}
// Now that all the variant source directories have been added, add the project's default ones
result.addAll(sourceDirectories)
return result
}
// Generated directory, if applicable
generatedSourceDirectory?.let {
result.add(it)
}
return result
}
fun archiveName(project: Project, archiveName: String?, suffix: String) : String {
val result =
if (isDefault) {
archiveName ?: project.name + "-" + project.version + suffix
} else {
val base = if (archiveName != null) archiveName.substring(0, archiveName.length - suffix.length)
else project.name + "-" + project.version
val result: String =
base + "-${productFlavor.name}" + "-${buildType.name}"
result
}
return result
}
val shortArchiveName = if (isDefault) "" else "-" + productFlavor.name + "-" + buildType.name
var generatedSourceDirectory: File? = null
private fun findBuildTypeBuildConfig(project: Project, variant: Variant?) : BuildConfig? {
val buildTypeName = variant?.buildType?.name
return project.buildTypes.getRaw(buildTypeName)?.buildConfig ?: null
}
private fun findProductFlavorBuildConfig(project: Project, variant: Variant?) : BuildConfig? {
val buildTypeName = variant?.productFlavor?.name
return project.productFlavors.getRaw(buildTypeName)?.buildConfig ?: null
}
/**
* Return a list of the BuildConfigs found on the productFlavor{}, buildType{} and project{} (in that order).
*/
private fun findBuildConfigs(project: Project, variant: Variant?) : List<BuildConfig> {
val result = listOf(
findBuildTypeBuildConfig(project, variant),
findProductFlavorBuildConfig(project, variant),
project.buildConfig)
.filterNotNull()
return result
}
/**
* Generate BuildConfig.java if requested. Also look up if any BuildConfig is defined on the current build type,
* product flavor or main project, and use them to generate any additional field (in that order to
* respect the priorities). Return the generated file if it was generated, null otherwise.
*/
fun maybeGenerateBuildConfig(project: Project, context: KobaltContext) : File? {
val buildConfigs = findBuildConfigs(project, this)
if (buildConfigs.size > 0) {
val pkg = project.packageName ?: project.group
?: throw KobaltException(
"packageName needs to be defined on the project in order to generate BuildConfig")
val code = project.projectInfo.generateBuildConfig(project, context, pkg, this, buildConfigs)
val result = KFiles.makeDir(KFiles.generatedSourceDir(project, this, "buildConfig"))
// Make sure the generatedSourceDirectory doesn't contain the project.directory since
// that directory will be added when trying to find recursively all the sources in it
generatedSourceDirectory = File(result.relativeTo(File(project.directory)))
val outputGeneratedSourceDirectory = File(result, pkg.replace('.', File.separatorChar))
val outputDir = File(outputGeneratedSourceDirectory, "BuildConfig" + project.sourceSuffix)
KFiles.saveFile(outputDir, code)
log(2, "Generated ${outputDir.path}")
return result
} else {
return null
}
}
override fun toString() = toTask("")
companion object {
val DEFAULT_PRODUCT_FLAVOR = ProductFlavorConfig("")
val DEFAULT_BUILD_TYPE = BuildTypeConfig(null, "")
fun allVariants(project: Project): List<Variant> {
val result = arrayListOf<Variant>()
if (project.buildTypes.size > 0) {
project.buildTypes.keys.forEach {
val bt = project.buildTypes[it]
if (project.productFlavors.size > 0) {
project.productFlavors.keys.forEach {
result.add(Variant(project.productFlavors[it], bt))
}
} else {
result.add(Variant(null, bt))
}
}
} else if (project.productFlavors.size > 0) {
project.productFlavors.keys.forEach {
val pf = project.productFlavors[it]
if (project.buildTypes.size > 0) {
project.buildTypes.keys.forEach {
result.add(Variant(pf, project.buildTypes[it]))
}
} else {
result.add(Variant(pf, null))
}
}
}
return result
}
}
fun toCamelcaseDir() : String {
val pfName = productFlavor.name
val btName = buildType.name
return pfName[0].toLowerCase() + pfName.substring(1) + btName[0].toUpperCase() + btName.substring(1)
}
fun toIntermediateDir() : String {
if (isDefault) {
return ""
} else {
return KFiles.joinDir(productFlavor.name, buildType.name)
}
}
}

View file

@ -0,0 +1,25 @@
package com.beust.kobalt.api
import com.beust.kobalt.Plugins
import com.beust.kobalt.internal.TaskManager
abstract public class BasePlugin : IPlugin {
lateinit var context: KobaltContext
override fun accept(project: Project) = true
override fun apply(project: Project, context: KobaltContext) {
this.context = context
}
/**
* The list of projects depended upon (e.g. val p = javaProject(dependentProject)).
*/
protected val projects = arrayListOf<ProjectDescription>()
fun addProject(project: Project, dependsOn: Array<out Project>) =
projects.add(ProjectDescription(project, dependsOn.toList()))
override lateinit var taskManager: TaskManager
lateinit var plugins: Plugins
}

View file

@ -0,0 +1,14 @@
package com.beust.kobalt.api
import java.io.File
/**
* Describe the information necessary to run a compiler.
*/
data class CompilerActionInfo(val directory: String?,
val dependencies: List<IClasspathDependency>,
val sourceFiles: List<String>,
val outputDir: File,
val compilerArgs: List<String>)

View file

@ -0,0 +1,12 @@
package com.beust.kobalt.api
/**
* A plug-in that has some per-project configuration in the build file.
*/
abstract public class ConfigPlugin<T> : BasePlugin() {
private val configurations = hashMapOf<String, T>()
fun configurationFor(project: Project?) = if (project != null) configurations[project.name] else null
fun addConfiguration(project: Project, configuration: T) = configurations.put(project.name, configuration)
}

View file

@ -0,0 +1,14 @@
package com.beust.kobalt.api
import com.google.common.collect.ArrayListMultimap
/**
* A plug-in that has some per-project list of configurations in the build file.
*/
abstract public class ConfigsPlugin<T> : BasePlugin() {
private val configurations = ArrayListMultimap.create<String, T>()
fun configurationFor(project: Project) = configurations[project.name]
fun addConfiguration(project: Project, configuration: T) = configurations.put(project.name, configuration)
}

View file

@ -0,0 +1,15 @@
package com.beust.kobalt.api
/**
* Base interface for affinity interfaces.
*/
interface IAffinity {
companion object {
/**
* The recommended default affinity if your plug-in can run this project. Use a higher
* number if you expect to compete against other actors and you'd like to win over them.
*/
const val DEFAULT_POSITIVE_AFFINITY = 100
}
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.api
class BuildConfigField(val type: String, val name: String, val value: Any)
/**
* Plug-ins that want to add fields to BuildConfig need to implement this interface.
*/
interface IBuildConfigFieldContributor {
fun fieldsFor(project: Project, context: KobaltContext) : List<BuildConfigField>
}

View file

@ -0,0 +1,9 @@
package com.beust.kobalt.api
/**
* Plug-ins can alter the build directory by implementing this interface.
*/
interface IBuildDirectoryIncerceptor : IInterceptor {
fun intercept(project: Project, context: KobaltContext, buildDirectory: String) : String
}

View file

@ -0,0 +1,12 @@
package com.beust.kobalt.api
import com.beust.kobalt.api.IClasspathDependency
/**
* Plugins that export classpath entries need to implement this interface.
*/
interface IClasspathContributor : IContributor {
fun entriesFor(project: Project?) : Collection<IClasspathDependency>
}

View file

@ -0,0 +1,27 @@
package com.beust.kobalt.api
import org.apache.maven.model.Dependency
import java.io.File
import java.util.concurrent.Future
/**
* Encapsulate a dependency that can be put on the classpath. This interface
* has two subclasses: FileDependency, a physical file, and MavenDependency,
* which represents a dependency living in a Maven repo.
*/
interface IClasspathDependency {
/** Identifier for this dependency */
val id: String
/** Absolute path to the jar file on the local file system */
val jarFile: Future<File>
/** Convert to a Maven <dependency> model tag */
fun toMavenDependencies() : Dependency
/** The list of dependencies for this element (not the transitive closure */
fun directDependencies(): List<IClasspathDependency>
/** Used to only keep the most recent version for an artifact if no version was specified */
val shortId: String
}

View file

@ -0,0 +1,8 @@
package com.beust.kobalt.api
/**
* Modify a list of dependencies before Kobalt starts using them.
*/
interface IClasspathInterceptor : IInterceptor {
fun intercept(project: Project, dependencies: List<IClasspathDependency>) : List<IClasspathDependency>
}

View file

@ -0,0 +1,9 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.IClasspathDependency
import java.io.File
interface ICompilerContributor : IProjectAffinity {
fun compile(project: Project, context: KobaltContext, info: CompilerActionInfo) : TaskResult
}

View file

@ -0,0 +1,8 @@
package com.beust.kobalt.api
/**
* Plugins that add compiler flags.
*/
interface ICompilerFlagContributor : IContributor {
fun flagsFor(project: Project, context: KobaltContext, currentFlags: List<String>): List<String>
}

View file

@ -0,0 +1,8 @@
package com.beust.kobalt.api
/**
* Plug-ins can alter what is passed to the compiler by implementing this interface.
*/
interface ICompilerInterceptor : IInterceptor {
fun intercept(project: Project, context: KobaltContext, actionInfo: CompilerActionInfo) : CompilerActionInfo
}

View file

@ -0,0 +1,8 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
interface IDocContributor : IProjectAffinity {
fun generateDoc(project: Project, context: KobaltContext, info: CompilerActionInfo) : TaskResult
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.api
/**
* The factory function to use to instantiate all the contributors and other entities
* found in kobalt-plugin.xml.
*/
interface IFactory {
fun <T> instanceOf(c: Class<T>) : T
}

View file

@ -0,0 +1,15 @@
package com.beust.kobalt.api
import java.io.OutputStream
/**
* Plugins that want to participate in the --init process (they can generate files to initialize
* a new project).
*/
interface IInitContributor<T> : ISimpleAffinity<T> {
/**
* Generate the Build.kt file into the given OutputStream.
*/
fun generateBuildFile(os: OutputStream)
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.api
import com.beust.kobalt.maven.MavenId
/**
* Plug-ins can rewrite Maven id's before Kobalt sees them with this interface.
*/
interface IMavenIdInterceptor: IInterceptor {
fun intercept(mavenId: MavenId) : MavenId
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.api
import com.beust.kobalt.internal.TaskManager
public interface IPlugin : IPluginActor {
val name: String
fun accept(project: Project) : Boolean
fun apply(project: Project, context: KobaltContext) {}
var taskManager : TaskManager
}

View file

@ -0,0 +1,9 @@
package com.beust.kobalt.api
interface IPluginActor
interface IContributor : IPluginActor
interface IInterceptor : IPluginActor

View file

@ -0,0 +1,12 @@
package com.beust.kobalt.api
/**
* An affinity interface that uses a project and a context to calculate its affinity.
*/
interface IProjectAffinity : IAffinity {
/**
* @return an integer indicating the affinity of your actor for the given project. The actor that returns
* the highest affinity gets selected.
*/
fun affinity(project: Project, context: KobaltContext) : Int
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.api
/**
* Plugins that create projects need to implement this interface.
*/
interface IProjectContributor : IContributor {
fun projects() : List<ProjectDescription>
}
class ProjectDescription(val project: Project, val dependsOn: List<Project>)

View file

@ -0,0 +1,16 @@
package com.beust.kobalt.api
import com.beust.kobalt.HostConfig
/**
* Plugins that add their own repos.
*/
interface IRepoContributor : IContributor {
/**
* Note that the project passed might be null because this contributor is called twice:
* before the build file gets parsed (so we don't have any projects yet) and after the
* build file has been parsed (then it gets called once for each project discovered).
*/
fun reposFor(project: Project?) : List<HostConfig>
}

View file

@ -0,0 +1,14 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.IClasspathDependency
/**
* Plugins that can run a project (task "run" or "test") should implement this interface.
*/
interface IRunnerContributor : IContributor, IProjectAffinity {
/**
* Run the project.
*/
fun run(project: Project, context: KobaltContext, classpath: List<IClasspathDependency>) : TaskResult
}

View file

@ -0,0 +1,12 @@
package com.beust.kobalt.api
/**
* An affinity interface that gets run without a project nor a context.
*/
interface ISimpleAffinity<T> : IAffinity {
/**
* @return an integer indicating the affinity of your actor. The actor that returns
* the highest affinity gets selected.
*/
fun affinity(arg: T) : Int
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.api
import java.io.File
/**
* Plug-ins can alter the source directories by implementing this interface.
*/
interface ISourceDirectoryIncerceptor : IInterceptor {
fun intercept(project: Project, context: KobaltContext, sourceDirectories: List<File>) : List<File>
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.api
import java.io.File
/**
* Plug-ins that add source directories to be compiled need to implement this interface.
*/
interface ISourceDirectoryContributor {
fun sourceDirectoriesFor(project: Project, context: KobaltContext): List<File>
}

View file

@ -0,0 +1,17 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
/**
* Plug-ins that need to add dynamic tasks (tasks that are not methods annotated with @Task) need
* to implement this interface.
*/
interface ITaskContributor : IContributor {
fun tasksFor(context: KobaltContext) : List<DynamicTask>
}
class DynamicTask(val plugin: IPlugin, val name: String, val description: String = "",
val runBefore: List<String> = listOf<String>(),
val runAfter: List<String> = listOf<String>(),
val alwaysRunAfter: List<String> = listOf<String>(),
val closure: (Project) -> TaskResult)

View file

@ -0,0 +1,14 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
/**
* Plugins that can run a project (task "run" or "test") should implement this interface.
*/
interface ITestRunnerContributor : IContributor, IProjectAffinity {
/**
* Run the project.
*/
fun run(project: Project, context: KobaltContext, classpath: List<IClasspathDependency>) : TaskResult
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.api
import java.io.File
/**
* Plug-ins that add tets source directories to be compiled need to implement this interface.
*/
interface ITestSourceDirectoryContributor {
fun testSourceDirectoriesFor(project: Project, context: KobaltContext): List<File>
}

View file

@ -0,0 +1,24 @@
package com.beust.kobalt.api
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.misc.KobaltExecutors
import java.io.File
import java.util.concurrent.Future
public class JarFinder {
companion object {
/**
* @return a Future for the jar file corresponding to this id.
*/
fun byIdFuture(id: String) : Future<File> {
val executor = Kobalt.INJECTOR.getInstance(KobaltExecutors::class.java).miscExecutor
val depFactory = Kobalt.INJECTOR.getInstance(DepFactory::class.java)
return depFactory.create(id, executor).jarFile
}
/**
* @return the jar file corresponding to this id. This might cause a network call.
*/
fun byId(id: String) = byIdFuture(id).get()
}
}

View file

@ -0,0 +1,67 @@
package com.beust.kobalt.api
import com.beust.kobalt.Constants
import com.beust.kobalt.HostConfig
import com.beust.kobalt.Plugins
import com.google.inject.Injector
import java.io.InputStream
import java.util.*
public class Kobalt {
companion object {
// This injector will eventually be replaced with a different injector initialized with the
// correct arguments (or with a TestModule) but it's necessary to give it a default value
// here so the kobalt-plugin.xml file can be read since this is done very early
lateinit var INJECTOR : Injector
var context: KobaltContext? = null
val repos = HashSet<HostConfig>(Constants.DEFAULT_REPOS.map { HostConfig(it) })
fun addRepo(repo: HostConfig) = repos.add(
if (repo.url.endsWith("/")) repo
else repo.copy(url = (repo.url + "/")))
private val PROPERTY_KOBALT_VERSION = "kobalt.version"
private val KOBALT_PROPERTIES = "kobalt.properties"
/** kobalt.properties */
private val kobaltProperties: Properties by lazy { readProperties() }
/**
* Read the content of kobalt.properties
*/
private fun readProperties() : Properties {
val result = Properties()
// kobalt.properties is internal to Kobalt
val url = Kobalt::class.java.classLoader.getResource(KOBALT_PROPERTIES)
if (url != null) {
readProperties(result, url.openConnection().inputStream)
} else {
throw AssertionError("Couldn't find $KOBALT_PROPERTIES")
}
// local.properties can be used by external users
// Paths.get(LOCAL_PROPERTIES).let { path ->
// if (Files.exists(path)) {
// Files.newInputStream(path).use {
// readProperties(result, it)
// }
// }
// }
return result
}
private fun readProperties(properties: Properties, ins: InputStream) {
properties.load(ins)
ins.close()
properties.forEach { es -> System.setProperty(es.key.toString(), es.value.toString()) }
}
val version = kobaltProperties.getProperty(PROPERTY_KOBALT_VERSION)
fun findPlugin(name: String) = Plugins.findPlugin(name)
}
}

View file

@ -0,0 +1,30 @@
package com.beust.kobalt.api
import com.beust.kobalt.Args
import com.beust.kobalt.Plugins
import com.beust.kobalt.Variant
import com.beust.kobalt.internal.PluginInfo
import com.beust.kobalt.maven.DependencyManager
import com.beust.kobalt.misc.KobaltExecutors
public class KobaltContext(val args: Args) {
var variant: Variant = Variant()
val profiles = arrayListOf<String>()
init {
args.profiles?.split(",")?.filterNotNull()?.forEach {
profiles.add(it)
}
}
fun findPlugin(name: String) = Plugins.findPlugin(name)
//
// Injected
//
lateinit var pluginInfo: PluginInfo
lateinit var pluginProperties: PluginProperties
lateinit var dependencyManager: DependencyManager
lateinit var executors: KobaltExecutors
}

View file

@ -0,0 +1,19 @@
package com.beust.kobalt.api
import com.google.inject.Inject
import java.util.*
/**
* Plugins can publish to and read from this object in order to exchange information. Keys stored in
* these maps should be annotated with @ExportedPluginProperty.
*/
class PluginProperties @Inject constructor() {
private val pluginProperties = hashMapOf<String, HashMap<String, Any>>()
fun put(pluginName: String, key: String, value: Any) =
pluginProperties.getOrPut(pluginName) { hashMapOf<String, Any>() }.put(key, value)
fun get(pluginName: String, key: String) = pluginProperties[pluginName]?.get(key)
fun getString(pluginName: String, key: String) = get(pluginName, key) as String
}

View file

@ -0,0 +1,16 @@
package com.beust.kobalt.api
import com.beust.kobalt.internal.TaskResult2
import com.beust.kobalt.misc.toString
import java.util.concurrent.Callable
abstract public class PluginTask : Callable<TaskResult2<PluginTask>> {
abstract val plugin: IPlugin
open val name: String = ""
open val doc: String = ""
abstract val project: Project
override public fun toString() : String {
return toString("PluginTask", "id", project.name + ":" + name)
}
}

View file

@ -0,0 +1,208 @@
package com.beust.kobalt.api
import com.beust.kobalt.api.annotation.Directive
import com.beust.kobalt.internal.IProjectInfo
import com.beust.kobalt.maven.dependency.MavenDependency
import com.beust.kobalt.misc.KFiles
import java.util.*
open class Project(
@Directive open var name: String,
@Directive open var version: String? = null,
@Directive open var directory: String = ".",
@Directive open var buildDirectory: String = KFiles.KOBALT_BUILD_DIR,
@Directive open var group: String? = null,
@Directive open var artifactId: String? = null,
@Directive open var packaging: String? = null,
@Directive open var dependencies: Dependencies? = null,
@Directive open var sourceSuffix : String = "",
@Directive open var description : String = "",
@Directive open var scm : Scm? = null,
@Directive open var url: String? = null,
@Directive open var licenses: List<License> = arrayListOf<License>(),
@Directive open var packageName: String? = group,
val projectInfo: IProjectInfo) : IBuildConfig {
override var buildConfig : BuildConfig? = null //BuildConfig()
val testArgs = arrayListOf<String>()
val testJvmArgs = arrayListOf<String>()
val projectProperties = ProjectProperties()
override fun equals(other: Any?): Boolean {
return name == (other as Project).name
}
override fun hashCode(): Int {
return name.hashCode()
}
//
// Directories
//
@Directive
fun sourceDirectories(init: Sources.() -> Unit) : Sources {
val sources = Sources(this, sourceDirectories)
sources.init()
return sources
}
var sourceDirectories : HashSet<String> = hashSetOf()
get() = if (field.isEmpty()) projectInfo.defaultSourceDirectories else field
set(value) {
field = value
}
@Directive
fun sourceDirectoriesTest(init: Sources.() -> Unit) : Sources {
val sources = Sources(this, sourceDirectoriesTest)
sources.init()
return sources
}
var sourceDirectoriesTest : HashSet<String> = hashSetOf()
get() = if (field.isEmpty()) projectInfo.defaultTestDirectories
else field
set(value) {
field = value
}
//
// Dependencies
//
@Directive
fun dependencies(init: Dependencies.() -> Unit) : Dependencies {
dependencies = Dependencies(this, compileDependencies, compileProvidedDependencies, compileRuntimeDependencies)
dependencies!!.init()
return dependencies!!
}
val compileDependencies : ArrayList<IClasspathDependency> = arrayListOf()
val compileProvidedDependencies : ArrayList<IClasspathDependency> = arrayListOf()
val compileRuntimeDependencies : ArrayList<IClasspathDependency> = arrayListOf()
@Directive
fun dependenciesTest(init: Dependencies.() -> Unit) : Dependencies {
dependencies = Dependencies(this, testDependencies, testProvidedDependencies, compileRuntimeDependencies)
dependencies!!.init()
return dependencies!!
}
val testDependencies : ArrayList<IClasspathDependency> = arrayListOf()
val testProvidedDependencies : ArrayList<IClasspathDependency> = arrayListOf()
/** Used to disambiguate various name properties */
@Directive
val projectName: String get() = name
val productFlavors = hashMapOf<String, ProductFlavorConfig>()
fun addProductFlavor(name: String, pf: ProductFlavorConfig) {
productFlavors.put(name, pf)
}
val buildTypes = hashMapOf<String, BuildTypeConfig>()
fun addBuildType(name: String, bt: BuildTypeConfig) {
buildTypes.put(name, bt)
}
fun classesDir(context: KobaltContext): String {
val initial = KFiles.joinDir(buildDirectory, "classes")
val result = context.pluginInfo.buildDirectoryInterceptors.fold(initial, { dir, intercept ->
intercept.intercept(this, context, dir)
})
return result
}
}
class Sources(val project: Project, val sources: HashSet<String>) {
@Directive
fun path(vararg paths: String) {
sources.addAll(paths)
}
}
class Dependencies(val project: Project, val dependencies: ArrayList<IClasspathDependency>,
val providedDependencies: ArrayList<IClasspathDependency>,
val runtimeDependencies: ArrayList<IClasspathDependency>) {
@Directive
fun compile(vararg dep: String) {
dep.forEach { dependencies.add(MavenDependency.create(it)) }
}
@Directive
fun provided(vararg dep: String) {
dep.forEach { providedDependencies.add(MavenDependency.create(it))}
}
@Directive
fun runtime(vararg dep: String) {
dep.forEach { runtimeDependencies.add(MavenDependency.create(it))}
}
}
class Scm(val connection: String, val developerConnection: String, val url: String)
class License(val name: String, val url: String) {
fun toMavenLicense() : org.apache.maven.model.License {
val result = org.apache.maven.model.License()
result.name = name
result.url = url
return result
}
}
class BuildConfig {
class Field(val name: String, val type: String, val value: Any)
val fields = arrayListOf<Field>()
fun field(type: String, name: String, value: Any) {
fields.add(Field(name, type, value))
}
}
interface IBuildConfig {
var buildConfig: BuildConfig?
fun buildConfig(init: BuildConfig.() -> Unit) {
buildConfig = BuildConfig().apply {
init()
}
}
}
class ProductFlavorConfig(val name: String) : IBuildConfig {
var applicationId: String? = null
override var buildConfig : BuildConfig? = BuildConfig()
}
@Directive
fun Project.productFlavor(name: String, init: ProductFlavorConfig.() -> Unit) = ProductFlavorConfig(name).apply {
init()
addProductFlavor(name, this)
}
class BuildTypeConfig(val project: Project?, val name: String) : IBuildConfig {
var minifyEnabled = false
var applicationIdSuffix: String? = null
var proguardFile: String? = null
// fun getDefaultProguardFile(name: String) : String {
// val androidPlugin = Plugins.findPlugin(AndroidPlugin.PLUGIN_NAME) as AndroidPlugin
// return Proguard(androidPlugin.androidHome(project)).getDefaultProguardFile(name)
// }
override var buildConfig : BuildConfig? = BuildConfig()
}
@Directive
fun Project.buildType(name: String, init: BuildTypeConfig.() -> Unit) = BuildTypeConfig(this, name).apply {
init()
addBuildType(name, this)
}

View file

@ -0,0 +1,20 @@
package com.beust.kobalt.api
/**
* Plugins can add and read properties from the project by using this class, found on the Project class.
* Keys stored in this map by plug-ins should be annotated with @ExportedProjectProperty.
*/
class ProjectProperties {
private val properties = hashMapOf<String, Any>()
fun put(key: String, value: Any) = properties.put(key, value)
fun get(key: String) = properties[key]
fun getString(key: String) : String? {
val result = get(key)
return if (result != null) result as String
else null
}
}

View file

@ -0,0 +1,9 @@
package com.beust.kobalt.api
import com.beust.kobalt.misc.toString
data public class Task(val pluginName: String, val taskName: String) {
override public fun toString() : String {
return toString("Task", pluginName, taskName)
}
}

View file

@ -0,0 +1,40 @@
package com.beust.kobalt.api
import com.beust.kobalt.TaskResult
import com.beust.kobalt.Variant
/**
* Plug-ins that are ITaskContributor can use this class to manage their collection of tasks and
* implement the interface by delegating to an instance of this class (if injection permits).
*/
class TaskContributor : ITaskContributor {
val dynamicTasks = arrayListOf<DynamicTask>()
/**
* Register dynamic tasks corresponding to the variants found in the project,e.g. assembleDevDebug,
* assembleDevRelease, etc...
*
* TODO: this should be done automatically so that users don't have to invoke it themselves.
* Certain tasks could have a boolean flag "hasVariants" and any task that depends on it automatically
* depends on variants of that task.
*/
fun addVariantTasks(plugin: IPlugin, project: Project, context: KobaltContext, taskName: String,
runBefore : List<String> = emptyList(),
runAfter : List<String> = emptyList(),
alwaysRunAfter : List<String> = emptyList(),
runTask: (Project) -> TaskResult) {
Variant.allVariants(project).forEach { variant ->
val variantTaskName = variant.toTask(taskName)
dynamicTasks.add(DynamicTask(plugin, variantTaskName, variantTaskName,
runBefore = runBefore.map { variant.toTask(it) },
runAfter = runAfter.map { variant.toTask(it) },
alwaysRunAfter = alwaysRunAfter.map { variant.toTask(it) },
closure = { p: Project ->
context.variant = variant
runTask(project)
}))
}
}
override fun tasksFor(context: KobaltContext) : List<DynamicTask> = dynamicTasks
}

View file

@ -0,0 +1,47 @@
package com.beust.kobalt.api.annotation
/**
* Plugins that export directives should annotated those with this annotation so they can be documented and also
* receive special treatment for auto completion in the plug-in.
*/
annotation class Directive
@Retention(AnnotationRetention.RUNTIME)
annotation class Task(
val name: String,
val description: String = "",
/** Tasks that this task depends on */
val runBefore: Array<String> = arrayOf(),
/** Tasks that this task will run after if they get run */
val runAfter: Array<String> = arrayOf(),
/** Tasks that this task will always run after */
val alwaysRunAfter: Array<String> = arrayOf()
)
/**
* Plugins that export properties should annotate those with this annotation so they can be documented.
*/
@Retention(AnnotationRetention.RUNTIME)
annotation class ExportedPluginProperty(
/** Documentation for this property */
val doc: String = "",
/** The type of this property */
val type: String = ""
)
/**
* Plugins that export properties on the Project instance should annotate those with this annotation so
* they can be documented.
*/
@Retention(AnnotationRetention.RUNTIME)
annotation class ExportedProjectProperty(
/** Documentation for this property */
val doc: String = "",
/** The type of this property */
val type: String = ""
)

View file

@ -0,0 +1,22 @@
package com.beust.kobalt.internal
import com.beust.kobalt.api.IProjectAffinity
import com.beust.kobalt.api.ISimpleAffinity
import com.beust.kobalt.api.KobaltContext
import com.beust.kobalt.api.Project
class ActorUtils {
companion object {
/**
* Return the plug-in actor with the highest affinity.
*/
fun <T : IProjectAffinity> selectAffinityActor(project: Project, context: KobaltContext, actors: List<T>)
= actors.maxBy { it.affinity(project, context) }
/**
* Return the plug-in actor with the highest affinity.
*/
fun <T : ISimpleAffinity<A>, A> selectAffinityActor(actors: List<T>, arg: A) = actors.maxBy { it.affinity(arg) }
}
}

View file

@ -0,0 +1,11 @@
package com.beust.kobalt.internal
class DocUrl {
companion object {
private const val HOST = "http://beust.com/kobalt/"
private fun url(path: String) = HOST + path
val PUBLISH_PLUGIN_URL = url("plug-ins/index.html#publishing")
}
}

View file

@ -0,0 +1,285 @@
package com.beust.kobalt.internal
import com.beust.kobalt.KobaltException
import com.beust.kobalt.TaskResult
import com.beust.kobalt.misc.NamedThreadFactory
import com.beust.kobalt.misc.error
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.toString
import com.google.common.collect.HashMultimap
import java.lang.reflect.InvocationTargetException
import java.util.concurrent.*
open class TaskResult2<T>(success: Boolean, errorMessage: String?, val value: T) : TaskResult(success, errorMessage) {
override fun toString() = toString("TaskResult", "value", value, "success", success)
}
public interface IWorker<T> : Callable<TaskResult2<T>> {
/**
* @return list of tasks this worker is working on.
*/
// val tasks : List<T>
/**
* @return the priority of this task.
*/
val priority : Int
}
public interface IThreadWorkerFactory<T> {
/**
* Creates {@code IWorker} for specified set of tasks. It is not necessary that
* number of workers returned be same as number of tasks entered.
*
* @param nodes tasks that need to be executed
* @return list of workers
*/
fun createWorkers(nodes: List<T>) : List<IWorker<T>>
}
public class DynamicGraphExecutor<T>(val graph: DynamicGraph<T>,
val factory: IThreadWorkerFactory<T>) {
val executor = Executors.newFixedThreadPool(5, NamedThreadFactory("DynamicGraphExecutor"))
val completion = ExecutorCompletionService<TaskResult2<T>>(executor)
/**
* @return 0 if all went well, > 0 otherwise
*/
public fun run() : Int {
var lastResult = TaskResult()
var gotError = false
var nodesRunning = 0
while (graph.freeNodes.size > 0 && ! gotError) {
log(3, "Current node count: ${graph.nodeCount}")
synchronized(graph) {
val freeNodes = graph.freeNodes
freeNodes.forEach { graph.setStatus(it, DynamicGraph.Status.RUNNING)}
log(3, " ==> Submitting " + freeNodes)
val callables : List<IWorker<T>> = factory.createWorkers(freeNodes)
callables.forEach { completion.submit(it) }
nodesRunning += callables.size
// When a callable ends, see if it freed a node. If not, keep looping
while (graph.nodesRunning.size > 0 && graph.freeNodes.size == 0 && ! gotError) {
try {
val future = completion.take()
val taskResult = future.get(2, TimeUnit.SECONDS)
lastResult = taskResult
log(3, " <== Received task result $taskResult")
graph.setStatus(taskResult.value,
if (taskResult.success) {
DynamicGraph.Status.FINISHED
} else {
DynamicGraph.Status.ERROR
})
} catch(ex: TimeoutException) {
log(2, "Time out")
} catch(ex: Exception) {
if (ex.cause is InvocationTargetException) {
val ite = ex.cause
if (ite.targetException is KobaltException) {
throw (ex.cause as InvocationTargetException).targetException
} else {
error("Error: ${ite.cause?.message}", ite.cause)
gotError = true
}
} else {
error("Error: ${ex.message}", ex)
gotError = true
}
}
}
}
}
executor.shutdown()
if (graph.freeNodes.size == 0 && graph.nodesReady.size > 0) {
throw KobaltException("Couldn't find any free nodes but a few nodes still haven't run, there is " +
"a cycle in the dependencies.\n Nodes left: " + graph.dump(graph.nodesReady))
}
return if (lastResult.success) 0 else 1
}
}
/**
* Representation of the graph of methods.
*/
public class DynamicGraph<T> {
val nodesReady = linkedSetOf<T>()
val nodesRunning = linkedSetOf<T>()
private val nodesFinished = linkedSetOf<T>()
private val nodesInError = linkedSetOf<T>()
private val nodesSkipped = linkedSetOf<T>()
private val dependedUpon = HashMultimap.create<T, T>()
private val dependingOn = HashMultimap.create<T, T>()
/**
* Define a comparator for the nodes of this graph, which will be used
* to order the free nodes when they are asked.
*/
// public val comparator : Comparator<T>? = null
enum class Status {
READY, RUNNING, FINISHED, ERROR, SKIPPED
}
/**
* Add a node to the graph.
*/
public fun addNode(value: T) : T {
nodes.add(value)
nodesReady.add(value)
return value
}
/**
* Add an edge between two nodes, which don't have to already be in the graph
* (they will be added by this method). Makes "to" depend on "from".
*/
public fun addEdge(from: T, to: T) {
nodes.add(from)
nodes.add(to)
val fromNode = addNode(from)
val toNode = addNode(to)
dependingOn.put(toNode, fromNode)
dependedUpon.put(fromNode, toNode)
}
/**
* @return a set of all the nodes that don't depend on any other nodes.
*/
public val freeNodes : List<T>
get() {
val result = arrayListOf<T>()
nodesReady.forEach { m ->
// A node is free if...
val du = dependedUpon.get(m)
// - no other nodes depend on it
if (! dependedUpon.containsKey(m)) {
result.add(m)
} else if (getUnfinishedNodes(du).size == 0) {
result.add(m)
}
}
// Sort the free nodes if requested (e.g. priorities)
// if (! result.isEmpty()) {
// if (comparator != null) {
// Collections.sort(result, comparator)
// debug("Nodes after sorting:" + result.get(0))
// }
// }
log(3, " freeNodes: $result")
return result
}
/**
* @return a list of all the nodes that have a status other than FINISHED.
*/
private fun getUnfinishedNodes(nodes: Set<T>) : Collection<T> {
val result = hashSetOf<T>()
nodes.forEach { node ->
if (nodesReady.contains(node) || nodesRunning.contains(node)) {
result.add(node);
}
}
return result;
}
/**
* Set the status for a set of nodes.
*/
public fun setStatus(nodes: Collection<T>, status: Status) {
nodes.forEach { setStatus(it, status) }
}
/**
* Mark all dependees of this node SKIPPED
*/
private fun setSkipStatus(node: T, status: Status) {
dependingOn.get(node).forEach {
if (! nodesSkipped.contains(it)) {
log(3, "Node skipped: $it")
nodesSkipped.add(it)
nodesReady.remove(it)
setSkipStatus(it, status)
}
}
}
/**
* Set the status for a node.
*/
public fun setStatus(node: T, status: Status) {
removeNode(node);
when(status) {
Status.READY -> nodesReady.add(node)
Status.RUNNING -> nodesRunning.add(node)
Status.FINISHED -> nodesFinished.add(node)
Status.ERROR -> {
log(3, "Node in error: $node")
nodesReady.remove(node)
nodesInError.add(node)
setSkipStatus(node, status)
}
else -> {
throw IllegalArgumentException()
}
}
}
private fun removeNode(node: T) {
if (! nodesReady.remove(node)) {
if (! nodesRunning.remove(node)) {
nodesFinished.remove(node)
}
}
}
/**
* @return the number of nodes in this graph.
*/
public val nodeCount: Int
get() = nodesReady.size + nodesRunning.size + nodesFinished.size
override public fun toString() : String {
val result = StringBuilder("[DynamicGraph ")
result.append("\n Ready:" + nodesReady)
result.append("\n Running:" + nodesRunning)
result.append("\n Finished:" + nodesFinished)
result.append("\n Edges:\n")
// dependingOn.entrySet().forEach { es ->
// result.append(" " + es.getKey() + "\n");
// es.getValue().forEach { t ->
// result.append(" " + t + "\n");
// }
// }
result.append("]");
return result.toString();
}
val nodes = hashSetOf<T>()
fun dump(nodes: Collection<T>) : String {
val result = StringBuffer()
result.append("************ Graph dump ***************\n")
val free = arrayListOf<T>()
nodes.forEach { node ->
val d = dependedUpon.get(node)
if (d == null || d.isEmpty()) {
free.add(node)
}
}
result.append("Free nodes: $free").append("\n Dependent nodes:\n")
nodes.forEach {
result.append(" $it -> ${dependedUpon.get(it)}\n")
}
return result.toString()
}
fun dump() = dump(nodesReady)
}

View file

@ -0,0 +1,87 @@
package com.beust.kobalt.internal
import com.beust.kobalt.JavaInfo
import com.beust.kobalt.SystemProperties
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.*
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.log
import java.io.File
import java.net.URLClassLoader
/**
* Base class for testing frameworks that are invoked from a main class with arguments. Test runners can
* subclass this class and override mainClass, args and the name of the dependency that should trigger this runner.
*/
abstract class GenericTestRunner : ITestRunnerContributor {
abstract val dependencyName : String
abstract val mainClass: String
abstract fun args(project: Project, classpath: List<IClasspathDependency>) : List<String>
override fun run(project: Project, context: KobaltContext, classpath: List<IClasspathDependency>)
= TaskResult(runTests(project, classpath))
override fun affinity(project: Project, context: KobaltContext) =
if (project.testDependencies.any { it.id.contains(dependencyName)}) IAffinity.DEFAULT_POSITIVE_AFFINITY
else 0
protected fun findTestClasses(project: Project, classpath: List<IClasspathDependency>): List<String> {
val path = KFiles.joinDir(project.directory, project.buildDirectory, KFiles.TEST_CLASSES_DIR)
val result = KFiles.findRecursively(File(path), arrayListOf(File(".")), {
file -> file.endsWith(".class")
}).map {
it.replace("/", ".").replace("\\", ".").replace(".class", "").substring(2)
}.filter {
try {
// Only keep classes with a parameterless constructor
val urls = arrayOf(File(path).toURI().toURL()) +
classpath.map { it.jarFile.get().toURI().toURL() }
URLClassLoader(urls).loadClass(it).getConstructor()
true
} catch(ex: Exception) {
log(2, "Skipping non test class $it: ${ex.message}")
false
}
}
log(2, "Found ${result.size} test classes")
return result
}
/**
* @return true if all the tests passed
*/
fun runTests(project: Project, classpath: List<IClasspathDependency>) : Boolean {
val jvm = JavaInfo.create(File(SystemProperties.javaBase))
val java = jvm.javaExecutable
val args = args(project, classpath)
if (args.size > 0) {
val allArgs = arrayListOf<String>().apply {
add(java!!.absolutePath)
addAll(project.testJvmArgs)
add("-classpath")
add(classpath.map { it.jarFile.get().absolutePath }.joinToString(File.pathSeparator))
add(mainClass)
addAll(args)
}
val pb = ProcessBuilder(allArgs)
pb.directory(File(project.directory))
pb.inheritIO()
log(1, "Running tests with classpath size ${classpath.size}")
log(2, "Launching " + allArgs.joinToString(" "))
val process = pb.start()
val errorCode = process.waitFor()
if (errorCode == 0) {
log(1, "All tests passed")
} else {
log(1, "Test failures")
}
return errorCode == 0
} else {
log(2, "Couldn't find any test classes")
return true
}
}
}

View file

@ -0,0 +1,14 @@
package com.beust.kobalt.internal
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Project
open public class JUnitRunner() : GenericTestRunner() {
override val mainClass = "org.junit.runner.JUnitCore"
override val dependencyName = "junit"
override fun args(project: Project, classpath: List<IClasspathDependency>) = findTestClasses(project, classpath)
}

View file

@ -0,0 +1,57 @@
package com.beust.kobalt.internal
import com.beust.kobalt.KobaltException
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.CompilerActionInfo
import com.beust.kobalt.api.KobaltContext
import com.beust.kobalt.api.Project
import com.beust.kobalt.maven.DependencyManager
import com.google.inject.Inject
import java.io.File
import java.util.*
/**
* Abstract the compilation process by running an ICompilerAction parameterized by a CompilerActionInfo.
* Also validates the classpath and run all the contributors.
*/
class JvmCompiler @Inject constructor(val dependencyManager: DependencyManager) {
/**
* Take the given CompilerActionInfo and enrich it with all the applicable contributors and
* then pass it to the ICompilerAction.
*/
fun doCompile(project: Project?, context: KobaltContext?, action: ICompilerAction, info: CompilerActionInfo)
: TaskResult {
// Dependencies
val allDependencies = (info.dependencies
+ dependencyManager.calculateDependencies(project, context!!, allDependencies = info.dependencies))
.distinct()
// Plugins that add flags to the compiler
val contributorFlags = if (project != null) {
context.pluginInfo.compilerFlagContributors.flatMap {
it.flagsFor(project, context, info.compilerArgs)
}
} else {
emptyList()
}
val addedFlags = contributorFlags + ArrayList(info.compilerArgs)
validateClasspath(allDependencies.map { it.jarFile.get().absolutePath })
return action.compile(info.copy(dependencies = allDependencies, compilerArgs = addedFlags))
}
private fun validateClasspath(cp: List<String>) {
cp.forEach {
if (! File(it).exists()) {
throw KobaltException("Couldn't find $it")
}
}
}
}
interface ICompilerAction {
fun compile(info: CompilerActionInfo): TaskResult
}

View file

@ -0,0 +1,226 @@
package com.beust.kobalt.internal
import com.beust.kobalt.KobaltException
import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.*
import com.beust.kobalt.api.annotation.ExportedProjectProperty
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.maven.DependencyManager
import com.beust.kobalt.maven.LocalRepo
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltExecutors
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import java.io.File
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
/**
* Base classes for plug-ins that compile files on the JVM. This base class requires the bare minimum
* contributors (source files, projects and tasks). Subclasses can add more as they see fit (e.g. test
* source directory, etc...).
*/
@Singleton
abstract class JvmCompilerPlugin @Inject constructor(
open val localRepo: LocalRepo,
open val files: KFiles,
open val depFactory: DepFactory,
open val dependencyManager: DependencyManager,
open val executors: KobaltExecutors,
open val jvmCompiler: JvmCompiler,
val taskContributor : TaskContributor = TaskContributor())
: BasePlugin(), ISourceDirectoryContributor, IProjectContributor, ITaskContributor by taskContributor {
companion object {
@ExportedProjectProperty(doc = "Projects this project depends on", type = "List<ProjectDescription>")
const val DEPENDENT_PROJECTS = "dependentProjects"
@ExportedProjectProperty(doc = "Compiler args", type = "List<String>")
const val COMPILER_ARGS = "compilerArgs"
const val TASK_COMPILE = "compile"
const val TASK_COMPILE_TEST = "compileTest"
const val TASK_CLEAN = "clean"
const val TASK_TEST = "test"
const val SOURCE_SET_MAIN = "main"
const val SOURCE_SET_TEST = "test"
const val DOCS_DIRECTORY = "docs/javadoc"
}
/**
* Log with a project.
*/
protected fun lp(project: Project, s: String) {
log(2, "${project.name}: $s")
}
override fun apply(project: Project, context: KobaltContext) {
super.apply(project, context)
project.projectProperties.put(DEPENDENT_PROJECTS, projects())
taskContributor.addVariantTasks(this, project, context, "compile", runTask = { taskCompile(project) })
}
@Task(name = TASK_TEST, description = "Run the tests",
runAfter = arrayOf(JvmCompilerPlugin.TASK_COMPILE, JvmCompilerPlugin.TASK_COMPILE_TEST))
fun taskTest(project: Project) : TaskResult {
lp(project, "Running tests")
val runContributor = ActorUtils.selectAffinityActor(project, context,
context.pluginInfo.testRunnerContributors)
if (runContributor != null && runContributor.affinity(project, context) > 0) {
return runContributor.run(project, context, dependencyManager.testDependencies(project, context,
projects()))
} else {
log(1, "Couldn't find a test runner for project ${project.name}, not running any tests")
return TaskResult()
}
}
@Task(name = TASK_CLEAN, description = "Clean the project", runBefore = arrayOf("compile"))
fun taskClean(project : Project ) : TaskResult {
java.io.File(project.directory, project.buildDirectory).let { dir ->
if (! dir.deleteRecursively()) {
warn("Couldn't delete $dir")
}
}
return TaskResult()
}
/**
* Copy the resources from a source directory to the build one
*/
protected fun copyResources(project: Project, sourceSet: String) {
val sourceDirs: ArrayList<String> = arrayListOf()
var outputDir: String?
if (sourceSet == JvmCompilerPlugin.SOURCE_SET_MAIN) {
sourceDirs.addAll(project.sourceDirectories.filter { it.contains("resources") })
outputDir = KFiles.CLASSES_DIR
} else if (sourceSet == JvmCompilerPlugin.SOURCE_SET_TEST) {
sourceDirs.addAll(project.sourceDirectoriesTest.filter { it.contains("resources") })
outputDir = KFiles.TEST_CLASSES_DIR
} else {
throw IllegalArgumentException("Custom source sets not supported yet: $sourceSet")
}
if (sourceDirs.size > 0) {
lp(project, "Copying $sourceSet resources")
val absOutputDir = File(KFiles.joinDir(project.directory, project.buildDirectory, outputDir))
sourceDirs.map { File(project.directory, it) }.filter {
it.exists()
} .forEach {
log(2, "Copying from $sourceDirs to $absOutputDir")
KFiles.copyRecursively(it, absOutputDir, deleteFirst = true)
}
} else {
lp(project, "No resources to copy for $sourceSet")
}
}
protected fun compilerArgsFor(project: Project) : List<String> {
val result = project.projectProperties.get(COMPILER_ARGS)
if (result != null) {
@Suppress("UNCHECKED_CAST")
return result as List<String>
} else {
return emptyList()
}
}
fun addCompilerArgs(project: Project, vararg args: String) {
project.projectProperties.put(COMPILER_ARGS, arrayListOf(*args))
}
@Task(name = JvmCompilerPlugin.TASK_COMPILE, description = "Compile the project")
fun taskCompile(project: Project) : TaskResult {
// Set up the source files now that we have the variant
sourceDirectories.addAll(context.variant.sourceDirectories(project))
val sourceDirectory = context.variant.maybeGenerateBuildConfig(project, context)
if (sourceDirectory != null) {
sourceDirectories.add(sourceDirectory)
}
val info = createCompilerActionInfo(project, context, isTest = false)
val compiler = ActorUtils.selectAffinityActor(project, context, context.pluginInfo.compilerContributors)
if (compiler != null) {
return compiler.compile(project, context, info)
} else {
throw KobaltException("Couldn't find any compiler for project ${project.name}")
}
}
override fun projects() = projects
@Task(name = "doc", description = "Generate the documentation for the project")
fun taskJavadoc(project: Project) : TaskResult {
val docGenerator = ActorUtils.selectAffinityActor(project, context, context.pluginInfo.docContributors)
if (docGenerator != null) {
return docGenerator.generateDoc(project, context, createCompilerActionInfo(project, context,
isTest = false))
} else {
warn("Couldn't find any doc contributor for project ${project.name}")
return TaskResult()
}
}
/**
* Create a CompilerActionInfo (all the information that a compiler needs to know) for the given parameters.
* Runs all the contributors and interceptors relevant to that task.
*/
protected fun createCompilerActionInfo(project: Project, context: KobaltContext, isTest: Boolean) :
CompilerActionInfo {
copyResources(project, JvmCompilerPlugin.SOURCE_SET_MAIN)
val classpath = if (isTest)
dependencyManager.testDependencies(project, context, projects)
else
dependencyManager.dependencies(project, context, projects)
val projectDirectory = File(project.directory)
val buildDirectory = if (isTest) KFiles.makeOutputTestDir(project)
else File(project.classesDir(context))
buildDirectory.mkdirs()
val initialSourceDirectories = arrayListOf<File>()
// Source directories from the contributors
initialSourceDirectories.addAll(
if (isTest) {
context.pluginInfo.testSourceDirContributors.flatMap { it.testSourceDirectoriesFor(project, context) }
} else {
context.pluginInfo.sourceDirContributors.flatMap { it.sourceDirectoriesFor(project, context) }
})
// Transform them with the interceptors, if any
val sourceDirectories = if (isTest) {
initialSourceDirectories
} else {
context.pluginInfo.sourceDirectoriesInterceptors.fold(initialSourceDirectories.toList(),
{ sd, interceptor -> interceptor.intercept(project, context, sd) })
}.filter {
File(project.directory, it.path).exists()
}
// Now that we have the final list of source dirs, find source files in them
val sourceFiles = files.findRecursively(projectDirectory, sourceDirectories,
{ it .endsWith(project.sourceSuffix) })
.map { File(projectDirectory, it).path }
// Finally, alter the info with the compiler interceptors before returning it
val initialActionInfo = CompilerActionInfo(projectDirectory.path, classpath, sourceFiles, buildDirectory,
emptyList())
val result = context.pluginInfo.compilerInterceptors.fold(initialActionInfo, { ai, interceptor ->
interceptor.intercept(project, context, ai)
})
return result
}
val sourceDirectories = arrayListOf<File>()
// ISourceDirectoryContributor
override fun sourceDirectoriesFor(project: Project, context: KobaltContext)
= if (accept(project)) sourceDirectories else arrayListOf()
}

View file

@ -0,0 +1,192 @@
package com.beust.kobalt.internal
import com.beust.kobalt.api.*
import com.beust.kobalt.misc.log
import java.io.ByteArrayInputStream
import java.io.File
import java.io.InputStream
import javax.xml.bind.JAXBContext
import javax.xml.bind.annotation.XmlElement
import javax.xml.bind.annotation.XmlRootElement
//
// Operations related to the parsing of kobalt-plugin.xml: XML parsing, PluginInfo, etc...
//
/**
* If a plug-in didn't specify a factory, we use our own injector to instantiate all its components.
*/
class GuiceFactory : IFactory {
override fun <T> instanceOf(c: Class<T>) : T = Kobalt.INJECTOR.getInstance(c)
}
/////
// XML parsing
//
// The following classes are used by JAXB to parse the kobalt-plugin.xml file.
/**
* The root element of kobalt-plugin.xml
*/
@XmlRootElement(name = "kobalt-plugin")
class KobaltPluginXml {
@XmlElement @JvmField
var name: String? = null
@XmlElement(name = "plugin-actors") @JvmField
var pluginActors : ClassNameXml? = null
@XmlElement(name = "factory-class-name") @JvmField
var factoryClassName: String? = null
}
class ContributorXml {
@XmlElement @JvmField
val name: String? = null
}
class ClassNameXml {
@XmlElement(name = "class-name") @JvmField
var className: List<String> = arrayListOf()
}
/**
* Turn a KobaltPluginXml (the raw content of kobalt-plugin.xml mapped to POJO's) into a PluginInfo object, which
* contains 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, val classLoader: ClassLoader?) {
val plugins = arrayListOf<IPlugin>()
val projectContributors = arrayListOf<IProjectContributor>()
val classpathContributors = arrayListOf<IClasspathContributor>()
val initContributors = arrayListOf<IInitContributor<File>>()
val repoContributors = arrayListOf<IRepoContributor>()
val compilerFlagContributors = arrayListOf<ICompilerFlagContributor>()
val compilerInterceptors = arrayListOf<ICompilerInterceptor>()
val sourceDirectoriesInterceptors = arrayListOf<ISourceDirectoryIncerceptor>()
val buildDirectoryInterceptors = arrayListOf<IBuildDirectoryIncerceptor>()
val runnerContributors = arrayListOf<IRunnerContributor>()
val testRunnerContributors = arrayListOf<ITestRunnerContributor>()
val classpathInterceptors = arrayListOf<IClasspathInterceptor>()
val compilerContributors = arrayListOf<ICompilerContributor>()
val docContributors = arrayListOf<IDocContributor>()
val sourceDirContributors = arrayListOf<ISourceDirectoryContributor>()
val testSourceDirContributors = arrayListOf<ITestSourceDirectoryContributor>()
val buildConfigFieldContributors = arrayListOf<IBuildConfigFieldContributor>()
val taskContributors = arrayListOf<ITaskContributor>()
val mavenIdInterceptors = arrayListOf<IMavenIdInterceptor>()
companion object {
/**
* Where plug-ins define their plug-in actors.
*/
val PLUGIN_XML = "META-INF/kobalt-plugin.xml"
/**
* Kobalt's core XML file needs to be different from kobalt-plugin.xml because classloaders
* can put a plug-in's jar file in front of Kobalt's, which means we'll read
* that one instead of the core one.
*/
val PLUGIN_CORE_XML = "META-INF/kobalt-core-plugin.xml"
/**
* Read Kobalt's own kobalt-plugin.xml.
*/
fun readKobaltPluginXml(): PluginInfo {
// Note: use forward slash here since we're looking up this file in a .jar file
val url = Kobalt::class.java.classLoader.getResource(PLUGIN_CORE_XML)
log(2, "URL for core kobalt-plugin.xml: $url")
if (url != null) {
return readPluginXml(url.openConnection().inputStream)
} else {
throw AssertionError("Couldn't find $PLUGIN_XML")
}
}
/**
* Read a general kobalt-plugin.xml.
*/
fun readPluginXml(ins: InputStream, classLoader: ClassLoader? = null): PluginInfo {
val jaxbContext = JAXBContext.newInstance(KobaltPluginXml::class.java)
val kotlinPlugin: KobaltPluginXml = jaxbContext.createUnmarshaller().unmarshal(ins)
as KobaltPluginXml
log(2, "Parsed plugin XML file, found: " + kotlinPlugin.name)
return PluginInfo(kotlinPlugin, classLoader)
}
fun readPluginXml(s: String, classLoader: ClassLoader? = null)
= readPluginXml(ByteArrayInputStream(s.toByteArray(Charsets.UTF_8)), classLoader)
}
init {
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)
//
// Populate pluginInfo with what was found in Kobalt's own kobalt-plugin.xml
//
@Suppress("UNCHECKED_CAST")
xml.pluginActors?.className?.forEach {
with(factory.instanceOf(forName(it))) {
// Note: can't use "when" here since the same instance can implement multiple interfaces
if (this is IBuildConfigFieldContributor) buildConfigFieldContributors.add(this)
if (this is IBuildDirectoryIncerceptor) buildDirectoryInterceptors.add(this)
if (this is IClasspathContributor) classpathContributors.add(this)
if (this is IClasspathInterceptor) classpathInterceptors.add(this)
if (this is ICompilerContributor) compilerContributors.add(this)
if (this is ICompilerFlagContributor) compilerFlagContributors.add(this)
if (this is ICompilerInterceptor) compilerInterceptors.add(this)
if (this is IDocContributor) docContributors.add(this)
if (this is IInitContributor<*>) initContributors.add(this as IInitContributor<File>)
if (this is IPlugin) plugins.add(this)
if (this is IProjectContributor) projectContributors.add(this)
if (this is IRepoContributor) repoContributors.add(this)
if (this is IRunnerContributor) runnerContributors.add(this)
if (this is ISourceDirectoryContributor) sourceDirContributors.add(this)
if (this is ISourceDirectoryIncerceptor) sourceDirectoriesInterceptors.add(this)
if (this is ITaskContributor) taskContributors.add(this)
if (this is ITestRunnerContributor) testRunnerContributors.add(this)
// Not documented yet
if (this is IMavenIdInterceptor) mavenIdInterceptors.add(this)
if (this is ITestSourceDirectoryContributor) testSourceDirContributors.add(this)
}
}
}
/**
* Populate pluginInfo with what was found in the plug-in's kobalt-plugin.xml
*/
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)
compilerFlagContributors.addAll(pluginInfo.compilerFlagContributors)
compilerInterceptors.addAll(pluginInfo.compilerInterceptors)
sourceDirectoriesInterceptors.addAll(pluginInfo.sourceDirectoriesInterceptors)
buildDirectoryInterceptors.addAll(pluginInfo.buildDirectoryInterceptors)
runnerContributors.addAll(pluginInfo.runnerContributors)
testRunnerContributors.addAll(pluginInfo.testRunnerContributors)
classpathInterceptors.addAll(pluginInfo.classpathInterceptors)
compilerContributors.addAll(pluginInfo.compilerContributors)
docContributors.addAll(pluginInfo.docContributors)
sourceDirContributors.addAll(pluginInfo.sourceDirContributors)
buildConfigFieldContributors.addAll(pluginInfo.buildConfigFieldContributors)
taskContributors.addAll(pluginInfo.taskContributors)
testSourceDirContributors.addAll(pluginInfo.testSourceDirContributors)
mavenIdInterceptors.addAll(pluginInfo.mavenIdInterceptors)
}
}

View file

@ -0,0 +1,7 @@
package com.beust.kobalt.internal
import java.util.jar.JarInputStream
public class PluginLoader(val jarStream: JarInputStream) {
}

View file

@ -0,0 +1,38 @@
package com.beust.kobalt.internal
import com.beust.kobalt.Variant
import com.beust.kobalt.api.BuildConfig
import com.beust.kobalt.api.BuildConfigField
import com.beust.kobalt.api.KobaltContext
import com.beust.kobalt.api.Project
import java.util.*
/**
* Data that is useful for projects to have but should not be specified in the DSL.
*/
interface IProjectInfo {
/** Used to determine the last directory segment of the flavored sources, e.g. src/main/JAVA */
val sourceDirectory : String
val defaultSourceDirectories: HashSet<String>
val defaultTestDirectories: HashSet<String>
/**
* If at least one build config was found either on the project or the variant, this function
* will be used to generate the BuildConfig file with the correct language.
*/
fun generateBuildConfig(project: Project, context: KobaltContext, packageName: String, variant: Variant,
buildConfigs: List<BuildConfig>) : String
}
interface BaseProjectInfo : IProjectInfo {
fun generate(field: BuildConfigField) : String
fun generate(type: String, name: String, value: Any) = generate(BuildConfigField(type, name, value))
fun generateFieldsFromContributors(project: Project, context: KobaltContext)
= context.pluginInfo.buildConfigFieldContributors.flatMap {
it.fieldsFor(project, context)
}.map {
generate(it)
}
}

View file

@ -0,0 +1,10 @@
package com.beust.kobalt.internal
/**
* SpekRunner triggers if it finds a dependency on org.jetbrains.spek but other than that, it just
* uses the regular JUnitRunner.
*/
public class SpekRunner() : JUnitRunner() {
override val dependencyName = "org.jetbrains.spek"
}

View file

@ -0,0 +1,323 @@
package com.beust.kobalt.internal
import com.beust.kobalt.*
import com.beust.kobalt.api.DynamicTask
import com.beust.kobalt.api.IPlugin
import com.beust.kobalt.api.PluginTask
import com.beust.kobalt.api.Project
import com.beust.kobalt.api.annotation.Task
import com.beust.kobalt.Args
import com.beust.kobalt.misc.log
import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.Multimap
import com.google.common.collect.TreeMultimap
import java.lang.reflect.Method
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class TaskManager @Inject constructor(val args: Args) {
private val runBefore = TreeMultimap.create<String, String>()
private val alwaysRunAfter = TreeMultimap.create<String, String>()
/**
* Called by plugins to indicate task dependencies defined at runtime. Keys depend on values.
* Declare that `task1` depends on `task2`.
*
* Note: there is no runAfter on this class since a runAfter(a, b) in a task simply translates
* to a runBefore(b, a) here.
*/
fun runBefore(task1: String, task2: String) {
runBefore.put(task1, task2)
}
fun alwaysRunAfter(task1: String, task2: String) {
alwaysRunAfter.put(task1, task2)
}
data class TaskInfo(val id: String) {
constructor(project: String, task: String) : this(project + ":" + task)
val project: String?
get() = if (id.contains(":")) id.split(":")[0] else null
val taskName: String
get() = if (id.contains(":")) id.split(":")[1] else id
fun matches(projectName: String) = project == null || project == projectName
}
public fun runTargets(taskNames: List<String>, projects: List<Project>) : Int {
var result = 0
projects.forEach { project ->
val projectName = project.name
// There can be multiple tasks by the same name (e.g. PackagingPlugin and AndroidPlugin both
// define "install"), so use a multimap
val tasksByNames = ArrayListMultimap.create<String, PluginTask>()
tasks.filter {
it.project.name == project.name
}.forEach {
tasksByNames.put(it.name, it)
}
AsciiArt.logBox("Building project ${project.name}")
log(3, "Tasks:")
tasksByNames.keys().forEach {
log(3, " $it: " + tasksByNames.get(it))
}
val graph = DynamicGraph<PluginTask>()
taskNames.forEach { taskName ->
val ti = TaskInfo(taskName)
if (! tasksByNames.keys().contains(ti.taskName)) {
throw KobaltException("Unknown task: $taskName")
}
if (ti.matches(projectName)) {
tasksByNames[ti.taskName].forEach { task ->
if (task != null && task.plugin.accept(project)) {
val reverseAfter = hashMapOf<String, String>()
alwaysRunAfter.keys().forEach { from ->
val tasks = alwaysRunAfter.get(from)
tasks.forEach {
reverseAfter.put(it, from)
}
}
//
// If the current target is free, add it as a single node to the graph
//
val allFreeTasks = calculateFreeTasks(tasksByNames, reverseAfter)
val currentFreeTask = allFreeTasks.filter {
TaskInfo(projectName, it.name).taskName == task.name
}
if (currentFreeTask.size == 1) {
currentFreeTask[0].let {
graph.addNode(it)
}
}
//
// Add the transitive closure of the current task as edges to the graph
//
val transitiveClosure = calculateTransitiveClosure(project, tasksByNames, ti)
transitiveClosure.forEach { pluginTask ->
val rb = runBefore.get(pluginTask.name)
rb.forEach {
val tos = tasksByNames[it]
if (tos != null && tos.size > 0) {
tos.forEach { to ->
graph.addEdge(pluginTask, to)
}
} else {
log(2, "Couldn't find node $it: not applicable to project ${project.name}")
}
}
}
//
// If any of the nodes in the graph has an "alwaysRunAfter", add that edge too
//
val allNodes = arrayListOf<PluginTask>()
allNodes.addAll(graph.nodes)
allNodes.forEach { node ->
val other = alwaysRunAfter.get(node.name)
other?.forEach { o ->
tasksByNames[o]?.forEach {
graph.addEdge(it, node)
}
}
}
}
}
}
}
//
// Now that we have a full graph, run it
//
log(3, "About to run graph:\n ${graph.dump()} ")
val factory = object : IThreadWorkerFactory<PluginTask> {
override public fun createWorkers(nodes: List<PluginTask>): List<IWorker<PluginTask>> {
val thisResult = arrayListOf<IWorker<PluginTask>>()
nodes.forEach {
thisResult.add(TaskWorker(arrayListOf(it), args.dryRun))
}
return thisResult
}
}
val executor = DynamicGraphExecutor(graph, factory)
val thisResult = executor.run()
if (result == 0) {
result = thisResult
}
}
return result
}
/**
* Find the free tasks of the graph.
*/
private fun calculateFreeTasks(tasksByNames: Multimap<String, PluginTask>, reverseAfter: HashMap<String, String>)
: Collection<PluginTask> {
val freeTaskMap = hashMapOf<String, PluginTask>()
tasksByNames.keys().forEach {
if (! runBefore.containsKey(it) && ! reverseAfter.containsKey(it)) {
tasksByNames[it].forEach { t ->
freeTaskMap.put(it, t)
}
}
}
return freeTaskMap.values
}
/**
* Find the transitive closure for the given TaskInfo
*/
private fun calculateTransitiveClosure(project: Project, tasksByNames: Multimap<String, PluginTask>, ti: TaskInfo):
HashSet<PluginTask> {
log(3, "Processing ${ti.taskName}")
val transitiveClosure = hashSetOf<PluginTask>()
val seen = hashSetOf(ti.taskName)
val toProcess = hashSetOf(ti)
var done = false
while (! done) {
val newToProcess = hashSetOf<TaskInfo>()
log(3, "toProcess size: " + toProcess.size)
toProcess.forEach { target ->
val currentTask = TaskInfo(project.name, target.taskName)
val thisTask = tasksByNames[currentTask.taskName]
if (thisTask != null) {
transitiveClosure.addAll(thisTask)
val dependencyNames = runBefore.get(currentTask.taskName)
dependencyNames.forEach { dependencyName ->
if (!seen.contains(dependencyName)) {
newToProcess.add(currentTask)
seen.add(dependencyName)
}
}
dependencyNames.forEach {
newToProcess.add(TaskInfo(project.name, it))
}
} else {
log(2, "Couldn't find task ${currentTask.taskName}: not applicable to project ${project.name}")
}
}
done = newToProcess.isEmpty()
toProcess.clear()
toProcess.addAll(newToProcess)
}
return transitiveClosure
}
/////
// Manage the tasks
//
class StaticTask(val plugin: IPlugin, val method: Method, val taskAnnotation: Task)
class PluginDynamicTask(val plugin: IPlugin, val task: DynamicTask)
val tasks = arrayListOf<PluginTask>()
val staticTasks = arrayListOf<StaticTask>()
val dynamicTasks = arrayListOf<PluginDynamicTask>()
/**
* Turn all the static and dynamic tasks into plug-in tasks, which are then suitable to be executed.
*/
fun computePluginTasks(plugins: List<IPlugin>, projects: List<Project>) {
addStaticTasks(projects)
addDynamicTasks(projects)
}
private fun addDynamicTasks(projects: List<Project>) {
dynamicTasks.forEach { dynamicTask ->
val task = dynamicTask.task
projects.filter { dynamicTask.plugin.accept(it) }.forEach { project ->
addTask(dynamicTask.plugin, project, task.name, task.description, task.runBefore, task.runAfter,
task.alwaysRunAfter, task.closure)
}
}
}
private fun addStaticTasks(projects: List<Project>) {
staticTasks.forEach { staticTask ->
val method = staticTask.method
val annotation = staticTask.taskAnnotation
val methodName = method.declaringClass.toString() + "." + method.name
log(3, " Found task:${annotation.name} method: $methodName")
fun toTask(m: Method, project: Project, plugin: IPlugin): (Project) -> TaskResult {
val result: (Project) -> TaskResult = {
m.invoke(plugin, project) as TaskResult
}
return result
}
val plugin = staticTask.plugin
projects.filter { plugin.accept(it) }.forEach { project ->
addStaticTask(plugin, project, staticTask.taskAnnotation, toTask(method, project, plugin))
}
}
}
private fun addStaticTask(plugin: IPlugin, project: Project, annotation: Task, task: (Project) -> TaskResult) {
addTask(plugin, project, annotation.name, annotation.description, annotation.runBefore.toList(),
annotation.runAfter.toList(), annotation.alwaysRunAfter.toList(), task)
}
fun addTask(plugin: IPlugin, project: Project, name: String, description: String = "",
runBefore: List<String> = listOf<String>(),
runAfter: List<String> = listOf<String>(),
alwaysRunAfter: List<String> = listOf<String>(),
task: (Project) -> TaskResult) {
tasks.add(
object : BasePluginTask(plugin, name, description, project) {
override fun call(): TaskResult2<PluginTask> {
val taskResult = task(project)
return TaskResult2(taskResult.success, taskResult.errorMessage, this)
}
})
runBefore.forEach { runBefore(it, name) }
runAfter.forEach { runBefore(name, it) }
alwaysRunAfter.forEach { alwaysRunAfter(it, name)}
}
//
//
/////
}
class TaskWorker(val tasks: List<PluginTask>, val dryRun: Boolean) : IWorker<PluginTask> {
// override fun compareTo(other: IWorker2<PluginTask>): Int {
// return priority.compareTo(other.priority)
// }
override fun call() : TaskResult2<PluginTask> {
if (tasks.size > 0) {
tasks[0].let {
log(1, AsciiArt.taskColor(AsciiArt.horizontalSingleLine + " ${it.project.name}:${it.name}"))
}
}
var success = true
val errorMessages = arrayListOf<String>()
tasks.forEach {
val tr = if (dryRun) TaskResult() else it.call()
success = success and tr.success
if (tr.errorMessage != null) errorMessages.add(tr.errorMessage)
}
return TaskResult2(success, errorMessages.joinToString("\n"), tasks[0])
}
// override val timeOut : Long = 10000
override val priority: Int = 0
}

View file

@ -0,0 +1,27 @@
package com.beust.kobalt.internal
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Project
import com.beust.kobalt.misc.KFiles
import java.io.File
public class TestNgRunner() : GenericTestRunner() {
override val mainClass = "org.testng.TestNG"
override val dependencyName = "org.testng"
override fun args(project: Project, classpath: List<IClasspathDependency>) = arrayListOf<String>().apply {
if (project.testArgs.size > 0) {
addAll(project.testArgs)
} else {
val testngXml = File(project.directory, KFiles.joinDir("src", "test", "resources", "testng.xml"))
if (testngXml.exists()) {
add(testngXml.absolutePath)
} else {
add("-testclass")
add(findTestClasses(project, classpath).joinToString(","))
}
}
}
}

View file

@ -0,0 +1,17 @@
package com.beust.kobalt.internal.build
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
/**
* Sometimes, build files are moved to temporary files, so we give them a specific name for clarity.
*/
class BuildFile(val path: Path, val name: String) {
public fun exists() : Boolean = Files.exists(path)
public val lastModified : Long
get() = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime().toMillis()
public val directory : File get() = path.toFile().directory
}

View file

@ -0,0 +1,20 @@
package com.beust.kobalt.internal.build
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.misc.KFiles
import java.io.File
class VersionFile {
companion object {
private val VERSION_FILE = "version.txt"
fun generateVersionFile(directory: File) {
KFiles.saveFile(File(directory, VERSION_FILE), Kobalt.version)
}
fun isSameVersionFile(directory: File) =
with(File(directory, VERSION_FILE)) {
! exists() || (exists() && readText() == Kobalt.version)
}
}
}

View file

@ -0,0 +1,13 @@
package com.beust.kobalt.internal.remote
import com.beust.kobalt.Constants
import java.io.PrintWriter
import java.net.Socket
fun main(argv: Array<String>) {
val socket = Socket("localhost", 1234)
(PrintWriter(socket.outputStream, true)).use { out ->
out.println("""{ "name" : "getDependencies", "buildFile":
"/Users/beust/kotlin/kobalt/kobalt/src/${Constants.BUILD_FILE_NAME}"}""")
}
}

View file

@ -0,0 +1,44 @@
package com.beust.kobalt.internal.remote
import com.google.gson.JsonObject
/**
* All commands implement this interface.
*/
interface ICommand {
/**
* The name of this command.
*/
val name: String
/**
* Run this command based on the information received from the client. When done, use
* the sender object to send back a response.
*/
fun run(sender: ICommandSender, received: JsonObject)
fun toCommandData(data: String) = CommandData(name, data)
}
/**
* Passed to a command in its `run` method so it can send information back to the caller.
* @param The string content that will be sent in the "data" field.
*/
interface ICommandSender {
fun sendData(commandData: CommandData)
}
/**
* The JSON payload that commands exchange follow the following pattern:
* {
* name: "nameOfTheCommand"
* data: a JSON string containing the payload itself
* }
* This allows commands to be tested for their name first, after which each command can
* decode its own specific payload by parsing the JSON in the "data" field and mapping
* it into a Kotlin *Data class. The downside of this approach is a double parsing,
* but since the data part is parsed as a string first, this is probably not a huge deal.
*/
class CommandData(val name: String, val data: String?, val error: String? = null)

View file

@ -0,0 +1,24 @@
package com.beust.kobalt.internal.remote
import com.beust.kobalt.misc.log
import com.google.gson.Gson
import com.google.gson.JsonObject
/**
* A simple command that returns its own content.
* Payload:
* {
* "name" : "ping"
* }
*
*/
class PingCommand() : ICommand {
override val name = "ping"
override fun run(sender: ICommandSender, received: JsonObject) {
sender.sendData(toCommandData(Gson().toJson(PingData(received.toString()))))
}
class PingData(val received: String)
}

View file

@ -0,0 +1,55 @@
package com.beust.kobalt.kotlin
import java.net.URL
import java.net.URLClassLoader
/**
* A parent-last classloader that will try the child classloader first and then the parent.
* Used by the wrapper to launch a new Kobalt with not interferences from its own classes.
* Will probably be made obsolete by making the wrapper a standalone module instead of
* being inside Kobalt itself.
*/
public class ParentLastClassLoader(val classpath: List<URL>)
: ClassLoader(Thread.currentThread().contextClassLoader) {
private val childClassLoader: ChildURLClassLoader
init {
val urls : Array<URL> = classpath.toTypedArray()
childClassLoader = ChildURLClassLoader(urls, FindClassClassLoader(this.parent))
}
/**
* This class makes it possible to call findClass on a classloader
*/
private class FindClassClassLoader(parent: ClassLoader) : ClassLoader(parent) {
override public fun findClass(name: String) = super.findClass(name)
}
/**
* This class delegates (child then parent) for the findClass method for a URLClassLoader.
* We need this because findClass is protected in URLClassLoader
*/
private class ChildURLClassLoader(urls: Array<URL>, val realParent: FindClassClassLoader)
: URLClassLoader(urls, null) {
override public fun findClass(name: String) : Class<*> {
try {
// first try to use the URLClassLoader findClass
return super.findClass(name)
} catch(e: ClassNotFoundException) {
// if that fails, we ask our real parent classloader to load the class (we give up)
return realParent.loadClass(name)
}
}
}
override public @Synchronized fun loadClass(name: String, resolve: Boolean) : Class<*> {
try {
// first we try to find a class inside the child classloader
return childClassLoader.findClass(name)
} catch(e: ClassNotFoundException) {
// didn't find it, try the parent
return super.loadClass(name, resolve)
}
}
}

View file

@ -0,0 +1,83 @@
package com.beust.kobalt.maven
import com.beust.kobalt.HostConfig
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import com.google.inject.assistedinject.Assisted
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DownloadManager @Inject constructor(val factory: ArtifactFetcher.IFactory) {
class Key(val hostInfo: HostConfig, val fileName: String, val executor: ExecutorService) {
override fun equals(other: Any?): Boolean {
return (other as Key).hostInfo.url == hostInfo.url
}
override fun hashCode(): Int {
return hostInfo.url.hashCode()
}
}
private val CACHE : LoadingCache<Key, Future<File>> = CacheBuilder.newBuilder()
.build(object : CacheLoader<Key, Future<File>>() {
override fun load(key: Key): Future<File> {
return key.executor.submit(factory.create(key.hostInfo, key.fileName))
}
})
fun download(hostInfo: HostConfig, fileName: String, executor: ExecutorService)
: Future<File> = CACHE.get(Key(hostInfo, fileName, executor))
}
/**
* Fetches an artifact (a file in a Maven repo, .jar, -javadoc.jar, ...) to the given local file.
*/
class ArtifactFetcher @Inject constructor(@Assisted("hostInfo") val hostInfo: HostConfig,
@Assisted("fileName") val fileName: String,
val files: KFiles) : Callable<File> {
interface IFactory {
fun create(@Assisted("hostInfo") hostInfo: HostConfig, @Assisted("fileName") fileName: String) : ArtifactFetcher
}
override fun call() : File {
val k = Kurl(hostInfo.copy(url = hostInfo.url + ".md5"))
val remoteMd5 =
if (k.exists) k.string.trim(' ', '\t', '\n').substring(0, 32)
else null
val tmpFile = Paths.get(fileName + ".tmp")
val file = Paths.get(fileName)
val url = hostInfo.url
if (! Files.exists(file)) {
with(tmpFile.toFile()) {
parentFile.mkdirs()
Kurl(hostInfo).toFile(this)
}
log(2, "Done downloading, renaming $tmpFile to $file")
Files.move(tmpFile, file, StandardCopyOption.REPLACE_EXISTING)
log(1, " Downloaded $url")
log(2, " to $file")
}
val localMd5 = Md5.toMd5(file.toFile())
if (remoteMd5 != null && remoteMd5 != localMd5) {
warn("MD5 not matching for $url")
} else {
log(2, "No md5 found for $url, skipping md5 check")
}
return file.toFile()
}
}

View file

@ -0,0 +1,13 @@
package com.beust.kobalt.maven
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
public class CompletedFuture<T>(val value: T) : Future<T> {
override fun cancel(mayInterruptIfRunning: Boolean) = true
override fun get(): T = value
override fun get(timeout: Long, unit: TimeUnit): T = value
override fun isCancelled(): Boolean = false
override fun isDone(): Boolean = true
}

View file

@ -0,0 +1,60 @@
package com.beust.kobalt.maven
import com.beust.kobalt.KobaltException
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.maven.dependency.FileDependency
import com.beust.kobalt.maven.dependency.MavenDependency
import com.beust.kobalt.misc.DependencyExecutor
import com.beust.kobalt.misc.KobaltExecutors
import com.google.inject.Key
import java.util.concurrent.ExecutorService
import javax.inject.Inject
public class DepFactory @Inject constructor(val localRepo: LocalRepo,
val remoteRepo: RepoFinder,
val executors: KobaltExecutors,
val downloadManager: DownloadManager,
val pomFactory: Pom.IFactory) {
companion object {
val defExecutor : ExecutorService by lazy {
Kobalt.INJECTOR.getInstance(Key.get(ExecutorService::class.java, DependencyExecutor::class.java))
}
}
/**
* Parse the id and return the correct IClasspathDependency
*/
public fun create(id: String, executor: ExecutorService = defExecutor, localFirst : Boolean = true)
: IClasspathDependency {
if (id.startsWith(FileDependency.PREFIX_FILE)) {
return FileDependency(id.substring(FileDependency.PREFIX_FILE.length))
} else {
val mavenId = MavenId.create(id)
var tentativeVersion = mavenId.version
var packaging = mavenId.packaging
var repoResult: RepoFinder.RepoResult?
val version =
if (tentativeVersion != null && ! MavenId.isRangedVersion(tentativeVersion)) tentativeVersion
else {
var localVersion: String? = tentativeVersion
if (localFirst) localVersion = localRepo.findLocalVersion(mavenId.groupId, mavenId.artifactId, mavenId.packaging)
if (localFirst && localVersion != null) {
localVersion
} else {
repoResult = remoteRepo.findCorrectRepo(id)
if (!repoResult.found) {
throw KobaltException("Couldn't resolve $id")
} else {
repoResult.version?.version
}
}
}
return MavenDependency(MavenId.create(mavenId.groupId, mavenId.artifactId, packaging, version),
executor, localRepo, remoteRepo, pomFactory, downloadManager)
}
}
}

View file

@ -0,0 +1,151 @@
package com.beust.kobalt.maven
import com.beust.kobalt.api.*
import com.beust.kobalt.maven.dependency.FileDependency
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.KobaltExecutors
import com.google.common.collect.ArrayListMultimap
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class DependencyManager @Inject constructor(val executors: KobaltExecutors,
val depFactory: DepFactory){
/**
* @return the classpath for this project, including the IClasspathContributors.
* allDependencies is typically either compileDependencies or testDependencies
*/
fun calculateDependencies(project: Project?, context: KobaltContext,
dependentProjects: List<ProjectDescription> = emptyList(),
vararg allDependencies: List<IClasspathDependency>): List<IClasspathDependency> {
var result = arrayListOf<IClasspathDependency>()
allDependencies.forEach { dependencies ->
result.addAll(transitiveClosure(dependencies))
}
result.addAll(runClasspathContributors(project, context))
result.addAll(dependentProjectDependencies(dependentProjects, project, context))
return result
}
private fun runClasspathContributors(project: Project?, context: KobaltContext) :
Collection<IClasspathDependency> {
val result = hashSetOf<IClasspathDependency>()
context.pluginInfo.classpathContributors.forEach { it: IClasspathContributor ->
result.addAll(it.entriesFor(project))
}
return result
}
/**
* Return the transitive closure of the dependencies *without* running the classpath contributors.
* TODO: This should be private, everyone should be calling calculateDependencies().
*/
fun transitiveClosure(dependencies : List<IClasspathDependency>): List<IClasspathDependency> {
var executor = executors.newExecutor("JvmCompiler}", 10)
var result = hashSetOf<IClasspathDependency>()
dependencies.forEach { projectDependency ->
result.add(projectDependency)
projectDependency.id.let {
result.add(depFactory.create(it, executor))
val downloaded = transitiveClosure(projectDependency.directDependencies())
result.addAll(downloaded)
}
}
val result2 = reorderDependencies(result).filter {
// Only keep existent files (nonexistent files are probably optional dependencies or parent poms
// that point to other poms but don't have a jar file themselves)
it.jarFile.get().exists()
}
executor.shutdown()
return result2
}
/**
* Reorder dependencies so that if an artifact appears several times, only the one with the higest version
* is included.
*/
public fun reorderDependencies(dependencies: Collection<IClasspathDependency>): List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
val map : ArrayListMultimap<String, IClasspathDependency> = ArrayListMultimap.create()
// The multilist maps each artifact to a list of all the versions found
// (e.g. {org.testng:testng -> (6.9.5, 6.9.4, 6.1.1)}), then we return just the first one
dependencies.forEach {
map.put(it.shortId, it)
}
for (k in map.keySet()) {
val l = map.get(k)
Collections.sort(l, Collections.reverseOrder())
result.add(l.get(0))
}
return result
}
/**
* If this project depends on other projects, we need to include their jar file and also
* their own dependencies
*/
private fun dependentProjectDependencies(projectDescriptions: List<ProjectDescription>,
project: Project?, context: KobaltContext) :
List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
projectDescriptions.filter {
it.project.name == project?.name
}.forEach { pd ->
pd.dependsOn.forEach { p ->
result.add(FileDependency(KFiles.joinDir(p.directory, p.classesDir(context))))
val otherDependencies = calculateDependencies(p, context, projectDescriptions,
p.compileDependencies)
result.addAll(otherDependencies)
}
}
return result
}
/**
* @return the compile dependencies for this project, including the contributors.
*/
fun dependencies(project: Project, context: KobaltContext,
projects: List<ProjectDescription>) : List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
result.add(FileDependency(KFiles.makeOutputDir(project).absolutePath))
result.add(FileDependency(KFiles.makeOutputTestDir(project).absolutePath))
with(project) {
arrayListOf(compileDependencies, compileProvidedDependencies).forEach {
result.addAll(calculateDependencies(project, context, projects, it))
}
}
val result2 = reorderDependencies(result)
return result2
}
/**
* @return the test dependencies for this project, including the contributors.
*/
fun testDependencies(project: Project, context: KobaltContext,
projects: List<ProjectDescription>) : List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
result.add(FileDependency(KFiles.makeOutputDir(project).absolutePath))
result.add(FileDependency(KFiles.makeOutputTestDir(project).absolutePath))
with(project) {
arrayListOf(compileDependencies, compileProvidedDependencies, testDependencies,
testProvidedDependencies).forEach {
result.addAll(calculateDependencies(project, context, projects, it))
}
}
val result2 = reorderDependencies(result)
return result2
}
}

View file

@ -0,0 +1,74 @@
package com.beust.kobalt.maven
import com.beust.kobalt.OperatingSystem
import com.beust.kobalt.misc.error
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.warn
import com.google.inject.Singleton
import java.io.BufferedReader
import java.io.File
import java.io.InputStreamReader
@Singleton
public class Gpg {
val COMMANDS = listOf("gpg", "gpg2")
fun findGpgCommand() : String? {
val path = System.getenv("PATH")
if (path != null) {
path.split(File.pathSeparator).forEach { dir ->
COMMANDS.map {
File(dir, if (OperatingSystem.current().isWindows()) it + ".exe" else it)
}.firstOrNull {
it.exists()
}?.let {
return it.absolutePath
}
}
}
return null
}
/**
* @return the .asc files
*/
fun runGpg(files: List<File>) : List<File> {
val result = arrayListOf<File>()
val gpg = findGpgCommand()
if (gpg != null) {
val directory = files.get(0).parentFile.absoluteFile
files.forEach { file ->
val ascFile = File(file.absolutePath + ".asc")
ascFile.delete()
val allArgs = arrayListOf<String>()
allArgs.add(gpg)
allArgs.add("-ab")
allArgs.add(file.absolutePath)
val pb = ProcessBuilder(allArgs)
pb.directory(directory)
log(2, "Signing file: " + allArgs.joinToString(" "))
val process = pb.start()
val br = BufferedReader(InputStreamReader(process.errorStream))
val errorCode = process.waitFor()
if (errorCode != 0) {
var line = br.readLine()
while (line != null) {
error(line)
line = br.readLine()
}
warn("Couldn't sign file $file")
} else {
result.add(ascFile)
}
}
return result
} else {
warn("Couldn't find the gpg command, make sure it is on your PATH")
warn("Signing of artifacts with PGP (.asc) disabled")
return arrayListOf()
}
}
}

View file

@ -0,0 +1,75 @@
package com.beust.kobalt.maven
import com.beust.kobalt.KobaltException
import com.beust.kobalt.misc.CountingFileRequestBody
import com.beust.kobalt.misc.log
import com.squareup.okhttp.*
import retrofit.mime.TypedFile
import java.io.IOException
import javax.inject.Singleton
@Singleton
public class Http {
public fun get(user: String?, password: String?, url: String) : Response {
val client = OkHttpClient();
val request = Request.Builder().url(url)
if (user != null) {
request.header("Authorization", Credentials.basic(user, password))
}
try {
return client.newCall(request.build()).execute()
} catch(ex: IOException) {
throw KobaltException("Could not load URL $url, error: " + ex.message, ex)
}
}
public fun get(url: String) : Response {
return get(null, null, url)
}
fun percentProgressCallback(totalSize: Long) : (Long) -> Unit {
return { num: Long ->
val progress = num * 100 / totalSize
log(1, "\rUploaded: $progress%", newLine = false)
}
}
val DEFAULT_ERROR_RESPONSE = { r: Response ->
error("Couldn't upload file: " + r.message())
}
public fun uploadFile(user: String? = null, password: String? = null, url: String, file: TypedFile,
post: Boolean,
progressCallback: (Long) -> Unit = {},
headers: Headers = Headers.of(),
success: (Response) -> Unit = {},
error: (Response) -> Unit = DEFAULT_ERROR_RESPONSE) {
val fullHeaders = Headers.Builder()
fullHeaders.set("Content-Type", file.mimeType())
headers.names().forEach { fullHeaders.set(it, headers.get(it)) }
user?.let {
fullHeaders.set("Authorization", Credentials.basic(user, password))
}
val requestBuilder = Request.Builder()
.headers(fullHeaders.build())
.url(url)
val request =
(if (post)
requestBuilder.post(CountingFileRequestBody(file.file(), file.mimeType(), progressCallback))
else
requestBuilder.put(CountingFileRequestBody(file.file(), file.mimeType(), progressCallback)))
.build()
log(2, "Uploading $file to $url")
val response = OkHttpClient().newCall(request).execute()
if (! response.isSuccessful) {
error(response)
} else {
success(response)
}
}
}

View file

@ -0,0 +1,137 @@
package com.beust.kobalt.maven
import com.beust.kobalt.HostConfig
import com.beust.kobalt.KobaltException
import com.beust.kobalt.maven.dependency.FileDependency
import com.beust.kobalt.misc.LocalProperties
import java.io.*
import java.net.HttpURLConnection
import java.net.URL
import java.net.URLConnection
import java.util.*
/**
* Abstracts a URL so that it works transparently on either http:// or file://
*/
class Kurl(val hostInfo: HostConfig) {
companion object {
const val KEY = "authUrl"
const val VALUE_USER = "username"
const val VALUE_PASSWORD = "password"
}
init {
// See if the URL needs to be authenticated. Look in local.properties for keys
// of the format authUrl.<host>.user=xxx and authUrl.<host>.password=xxx
val properties = LocalProperties().localProperties
val host = java.net.URL(hostInfo.url).host
properties.entries.forEach {
val key = it.key.toString()
if (key == "$KEY.$host.$VALUE_USER") {
hostInfo.username = properties.getProperty(key)
} else if (key == "$KEY.$host.$VALUE_PASSWORD") {
hostInfo.password = properties.getProperty(key)
}
}
fun error(s1: String, s2: String) {
throw KobaltException("Found \"$s1\" but not \"$s2\" in local.properties for $KEY.$host",
docUrl = "http://beust.com/kobalt/documentation/index.html#maven-repos-authenticated")
}
if (! hostInfo.username.isNullOrBlank() && hostInfo.password.isNullOrBlank()) {
error("username", "password")
} else if(hostInfo.username.isNullOrBlank() && ! hostInfo.password.isNullOrBlank()) {
error("password", "username")
}
}
val connection : URLConnection
get() {
val result = URL(hostInfo.url).openConnection()
if (hostInfo.hasAuth()) {
val userPass = hostInfo.username + ":" + hostInfo.password
val basicAuth = "Basic " + String(Base64.getEncoder().encode(userPass.toByteArray()))
result.setRequestProperty("Authorization", basicAuth)
}
return result
}
val inputStream : InputStream by lazy {
connection.inputStream
}
val exists : Boolean
get() {
val url = hostInfo.url
val result =
if (connection is HttpURLConnection) {
val responseCode = (connection as HttpURLConnection).responseCode
checkResponseCode(responseCode)
responseCode == 200
} else if (url.startsWith(FileDependency.PREFIX_FILE)) {
val fileName = url.substring(FileDependency.PREFIX_FILE.length)
File(fileName).exists()
} else {
false
}
return result
}
private fun checkResponseCode(responseCode: Int) {
if (responseCode == 401) {
if (hostInfo.hasAuth()) {
error("Bad credentials supplied for ${hostInfo.url}")
} else {
error("This repo requires authentication: ${hostInfo.url}")
}
}
}
/** The Kotlin compiler is about 17M and downloading it with the default buffer size takes forever */
private val estimatedSize: Int
get() = if (hostInfo.url.contains("kotlin-compiler")) 18000000 else 1000000
fun toOutputStream(os: OutputStream, progress: (Long) -> Unit) = copy(inputStream, os, progress)
fun toFile(file: File, progress: (Long) -> Unit = {}) = FileOutputStream(file).use {
toOutputStream(it, progress)
}
private fun copy(from: InputStream, to: OutputStream, progress: (Long) -> Unit = {}) : Long {
val estimate =
if (connection is HttpURLConnection) {
(connection as HttpURLConnection).let {
it.contentLength
}
} else {
estimatedSize
}
val buf = ByteArray(estimatedSize)
var total: Long = 0
while (true) {
val r = from.read(buf)
if (r == -1) {
break
}
to.write(buf, 0, r)
total += r.toLong()
progress(total * 100 / estimate)
}
return total
}
val string: String
get() {
val sb = StringBuilder()
val reader = BufferedReader(InputStreamReader(inputStream))
var line: String? = reader.readLine()
while (line != null) {
sb.append(line).append('\n')
line = reader.readLine()
}
return sb.toString()
}
}

View file

@ -0,0 +1,18 @@
package com.beust.kobalt.maven
import com.beust.kobalt.maven.CompletedFuture
import com.beust.kobalt.misc.Strings
import java.io.File
import java.util.concurrent.Future
import kotlin.properties.Delegates
open public class LocalDep(override val mavenId: MavenId, open val localRepo: LocalRepo)
: SimpleDep(mavenId) {
fun toAbsoluteJarFilePath(v: String) = localRepo.toFullPath(toJarFile(v))
fun toAbsolutePomFile(v: String): String {
return localRepo.toFullPath(Strings.Companion.join("/", arrayListOf(toPomFile(v))))
}
}

View file

@ -0,0 +1,58 @@
package com.beust.kobalt.maven
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.Versions
import java.io.File
import java.util.Collections
import javax.inject.Singleton
@Singleton
open public class LocalRepo(open val localRepo: String = KFiles.localRepo) {
init {
val l = File(localRepo)
if (! l.exists()) {
l.mkdirs()
}
}
fun existsPom(d: LocalDep, v: String) : Boolean {
return File(d.toAbsolutePomFile(v)).exists()
}
fun existsJar(d: LocalDep, v: String) : Boolean {
return File(d.toAbsoluteJarFilePath(v)).exists()
}
/**
* If the dependency is local, return the correct version for it
*/
fun findLocalVersion(groupId: String, artifactId: String, packaging: String? = null) : String? {
// No version: look at all the directories under group/artifactId, pick the latest and see
// if it contains a maven and jar file
val dir = toFullPath(KFiles.joinDir(groupId.replace(".", File.separator), artifactId))
val files = File(dir).listFiles()
if (files != null) {
val directories = files.filter { it.isDirectory }
if (directories.size > 0) {
Collections.sort(directories, { f1, f2 ->
val v1 = Versions.toLongVersion(f1.name)
val v2 = Versions.toLongVersion(f2.name)
v2.compareTo(v1) // we want the most recent at position 0
})
val result = directories[0].name
val newDep = LocalDep(MavenId.create(groupId, artifactId, packaging, result), this)
if (existsPom(newDep, result) && existsJar(newDep, result)) {
return result
}
}
}
return null
}
fun toFullPath(path: String) : String {
return localRepo + File.separatorChar + path
}
}

View file

@ -0,0 +1,86 @@
package com.beust.kobalt.maven
import com.beust.kobalt.api.Kobalt
/**
* Encapsulate a Maven id captured in one string, as used by Gradle or Ivy, e.g. "org.testng:testng:6.9.9".
* These id's are somewhat painful to manipulate because on top of containing groupId, artifactId
* and version, they also accept an optional packaging (e.g. "aar") and qualifier (e.g. "no_aop").
* Determining which is which in an untyped string separated by colons is not clearly defined so
* this class does a best attempt at deconstructing an id but there's surely room for improvement.
*
* This class accepts a versionless id, which needs to end with a :, e.g. "com.beust:jcommander:" (which
* usually means "latest version") but it doesn't handle version ranges yet.
*/
class MavenId private constructor(val groupId: String, val artifactId: String, val packaging: String?,
val version: String?) {
companion object {
fun isMavenId(id: String) = with(id.split(":")) {
size == 3 || size == 4
}
private fun isVersion(s: String): Boolean {
return Character.isDigit(s[0]) || isRangedVersion(s)
}
fun isRangedVersion(s: String): Boolean {
return s.first() in listOf('[', '(') && s.last() in listOf(']', ')')
}
/**
* Similar to create(MavenId) but don't run IMavenIdInterceptors.
*/
fun createNoInterceptors(id: String) : MavenId {
var groupId: String? = null
var artifactId: String? = null
var version: String? = null
var packaging: String? = null
if (!isMavenId(id)) {
throw IllegalArgumentException("Illegal id: $id")
}
val c = id.split(":")
groupId = c[0]
artifactId = c[1]
if (!c[2].isEmpty()) {
val split = c[2].split('@')
if (isVersion(split[0])) {
version = split[0]
}
packaging = if (split.size == 2) split[1] else null
}
return MavenId(groupId, artifactId, packaging, version)
}
/**
* The main entry point to create Maven Id's. Id's created by this function
* will run through IMavenIdInterceptors.
*/
fun create(id: String) : MavenId {
var originalMavenId = createNoInterceptors(id)
var interceptedMavenId = originalMavenId
val interceptors = Kobalt.context?.pluginInfo?.mavenIdInterceptors
if (interceptors != null) {
interceptedMavenId = interceptors.fold(originalMavenId, {
id, interceptor -> interceptor.intercept(id) })
}
return interceptedMavenId
}
fun create(groupId: String, artifactId: String, packaging: String?, version: String?) =
create(toId(groupId, artifactId, packaging, version))
fun toId(groupId: String, artifactId: String, packaging: String? = null, version: String?) =
"$groupId:$artifactId:$version" +
(if (packaging != null) "@$packaging" else "")
}
val hasVersion = version != null
val toId = MavenId.toId(groupId, artifactId, packaging, version)
}

View file

@ -0,0 +1,16 @@
package com.beust.kobalt.maven
import java.io.File
import java.security.MessageDigest
import javax.xml.bind.DatatypeConverter
public class Md5 {
companion object {
fun toMd5(file: File) = MessageDigest.getInstance("MD5").let { md5 ->
file.forEachBlock { bytes, size ->
md5.update(bytes, 0, size)
}
DatatypeConverter.printHexBinary(md5.digest()).toLowerCase()
}
}
}

View file

@ -0,0 +1,107 @@
package com.beust.kobalt.maven
import com.beust.kobalt.misc.log
import com.beust.kobalt.misc.toString
import com.google.inject.assistedinject.Assisted
import kotlinx.dom.childElements
import org.w3c.dom.Element
import org.w3c.dom.NodeList
import org.xml.sax.InputSource
import java.io.FileReader
import javax.xml.xpath.XPathConstants
public class Pom @javax.inject.Inject constructor(@Assisted val id: String,
@Assisted documentFile: java.io.File) {
val XPATH_FACTORY = javax.xml.xpath.XPathFactory.newInstance()
val XPATH = XPATH_FACTORY.newXPath()
var groupId: String? = null
var artifactId: String? = null
var packaging: String? = null
var version: String? = null
var name: String? = null
var properties = sortedMapOf<String, String>()
var repositories = listOf<String>()
public interface IFactory {
fun create(@Assisted id: String, @Assisted documentFile: java.io.File): Pom
}
data public class Dependency(val groupId: String, val artifactId: String, val packaging: String?,
val version: String, val optional: Boolean = false, val scope: String? = null) {
/** When a variable is used in a maven file, e.g. ${version} */
private val VAR = "$" + "{"
val mustDownload: Boolean
get() = !optional && "provided" != scope && "test" != scope
val isValid: Boolean
get() {
var result = false
if (version.contains(VAR)) {
log(3, "Skipping variable version ${this}")
} else if (groupId.contains(VAR)) {
log(3, "Skipping variable groupId ${this}")
} else if (artifactId.contains(VAR)) {
log(3, "Skipping variable artifactId ${this}")
} else {
result = true
}
return result
}
val id: String = "$groupId:$artifactId:$version"
}
val dependencies = arrayListOf<Dependency>()
init {
val DEPENDENCIES = XPATH.compile("/project/dependencies/dependency")
val document = kotlinx.dom.parseXml(InputSource(FileReader(documentFile)))
groupId = XPATH.compile("/project/groupId").evaluate(document)
artifactId = XPATH.compile("/project/artifactId").evaluate(document)
version = XPATH.compile("/project/version").evaluate(document)
name = XPATH.compile("/project/name").evaluate(document)
var repositoriesList = XPATH.compile("/project/repositories").evaluate(document, XPathConstants.NODESET)
as NodeList
var repoElem = repositoriesList.item(0) as Element?
repositories = repoElem.childElements().map({ it.getElementsByTagName("url").item(0).textContent })
val propertiesList = XPATH.compile("/project/properties").evaluate(document, XPathConstants.NODESET) as NodeList
var propsElem = propertiesList.item(0) as Element?
propsElem.childElements().forEach {
properties.put(it.nodeName, it.textContent)
}
val deps = DEPENDENCIES.evaluate(document, XPathConstants.NODESET) as NodeList
for (i in 0..deps.length - 1) {
val d = deps.item(i) as NodeList
var groupId: String? = null
var artifactId: String? = null
var packaging: String? = null
var version: String = ""
var optional: Boolean? = false
var scope: String? = null
for (j in 0..d.length - 1) {
val e = d.item(j)
if (e is Element) {
when (e.tagName) {
"groupId" -> groupId = e.textContent
"artifactId" -> artifactId = e.textContent
"type" -> packaging = e.textContent
"version" -> version = e.textContent
"optional" -> optional = "true".equals(e.textContent, true)
"scope" -> scope = e.textContent
}
}
}
log(3, "Done parsing: $groupId $artifactId $version")
val tmpDependency = Dependency(groupId!!, artifactId!!, packaging, version, optional!!, scope)
dependencies.add(tmpDependency)
}
}
override public fun toString() = toString("Pom", id, "id")
}

View file

@ -0,0 +1,65 @@
package com.beust.kobalt.maven
import com.beust.kobalt.SystemProperties
import com.beust.kobalt.api.Project
import com.beust.kobalt.misc.KFiles
import com.beust.kobalt.misc.log
import com.google.inject.assistedinject.Assisted
import org.apache.maven.model.Developer
import org.apache.maven.model.Model
import org.apache.maven.model.Scm
import org.apache.maven.model.io.xpp3.MavenXpp3Writer
import java.io.File
import java.io.StringWriter
import java.nio.charset.Charset
import javax.inject.Inject
public class PomGenerator @Inject constructor(@Assisted val project: Project) {
interface IFactory {
fun create(project: Project) : PomGenerator
}
fun generate() {
requireNotNull(project.version, { "version mandatory on project ${project.name}" })
requireNotNull(project.group, { "group mandatory on project ${project.name}" })
requireNotNull(project.artifactId, { "artifactId mandatory on project ${project.name}" })
val m = Model().apply {
name = project.name
artifactId = project.artifactId
groupId = project.group
version = project.version
description = project.description
licenses = project.licenses.map { it.toMavenLicense() }
url = project.url
scm = Scm().apply {
project.scm?.let {
url = it.url
connection = it.connection
developerConnection = it.developerConnection
}
}
}
with(Developer()) {
name = SystemProperties.username
m.addDeveloper(this)
}
val dependencies = arrayListOf<org.apache.maven.model.Dependency>()
m.dependencies = dependencies
project.compileDependencies.forEach { dep ->
dependencies.add(dep.toMavenDependencies())
}
val s = StringWriter()
MavenXpp3Writer().write(s, m)
val buildDir = KFiles.makeDir(project.directory, project.buildDirectory)
val outputDir = KFiles.makeDir(buildDir.path, "libs")
val mavenId = MavenId.create(project.group!!, project.artifactId!!, project.packaging, project.version!!)
val pomFile = SimpleDep(mavenId).toPomFileName()
val outputFile = File(outputDir, pomFile)
outputFile.writeText(s.toString(), Charset.defaultCharset())
log(1, " Wrote $outputFile")
}
}

View file

@ -0,0 +1,213 @@
package com.beust.kobalt.maven
import com.beust.kobalt.HostConfig
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.maven.dependency.FileDependency
import com.beust.kobalt.misc.*
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import kotlinx.dom.asElementList
import kotlinx.dom.parseXml
import org.w3c.dom.NodeList
import java.io.File
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorCompletionService
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
/**
* Find the repo that contains the given dependency among a list of repos. Searches are performed in parallel and
* cached so we never make a network call for the same dependency more than once.
*/
public class RepoFinder @Inject constructor(val executors: KobaltExecutors) {
public fun findCorrectRepo(id: String): RepoResult {
return FOUND_REPOS.get(id)
}
data class RepoResult(val hostConfig: HostConfig, val found: Boolean, val version: Version? = null,
val hasJar: Boolean = true, val snapshotVersion: Version? = null)
private val FOUND_REPOS: LoadingCache<String, RepoResult> = CacheBuilder.newBuilder()
.build(object : CacheLoader<String, RepoResult>() {
override fun load(key: String): RepoResult {
return loadCorrectRepo(key)
}})
/**
* Schedule an HTTP request to each repo in its own thread.
*/
private fun loadCorrectRepo(id: String): RepoResult {
val executor = executors.newExecutor("RepoFinder-$id", Kobalt.repos.size)
val cs = ExecutorCompletionService<RepoResult>(executor)
val results = arrayListOf<RepoResult>()
try {
log(2, "Looking for $id")
Kobalt.repos.forEach { cs.submit(RepoFinderCallable(id, it)) }
for (i in 0..Kobalt.repos.size - 1) {
try {
val result = cs.take().get(2000, TimeUnit.MILLISECONDS)
log(2, " Result for repo #$i: $result")
if (result.found) {
log(2, "Located $id in ${result.hostConfig.url}")
results.add(result)
}
} catch(ex: Exception) {
warn("Error: $ex")
}
}
} finally {
executor.shutdownNow()
}
if (results.size > 0) {
// results.sortByDescending { Versions.toLongVersion(it.version) }
results.sort({ left, right -> left.version!!.compareTo(right.version!!) })
return results[0]
} else {
return RepoResult(HostConfig(""), false, Version.of(id))
}
}
/**
* Execute a single HTTP request to one repo.
*/
inner class RepoFinderCallable(val id: String, val repo: HostConfig) : Callable<RepoResult> {
override fun call(): RepoResult {
val repoUrl = repo.url
log(2, " Checking $repoUrl for $id")
val mavenId = MavenId.create(id)
val groupId = mavenId.groupId
val artifactId = mavenId.artifactId
if (mavenId.version == null) {
val ud = UnversionedDep(groupId, artifactId)
val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE)
val foundVersion = findCorrectVersionRelease(ud.toMetadataXmlPath(false, isLocal), repoUrl)
if (foundVersion != null) {
return RepoResult(repo, true, Version.of(foundVersion))
} else {
return RepoResult(repo, false)
}
} else {
val version = Version.of(mavenId.version)
if (version.isSnapshot()) {
val dep = SimpleDep(mavenId)
val isLocal = repoUrl.startsWith(FileDependency.PREFIX_FILE)
val snapshotVersion = if (isLocal) version
else findSnapshotVersion(dep.toMetadataXmlPath(false, isLocal, version.version), repoUrl)
if (snapshotVersion != null) {
return RepoResult(repo, true, version, true /* hasJar, potential bug here */,
snapshotVersion)
} else {
return RepoResult(repo, false)
}
} else if (version.isRangedVersion() ) {
val foundVersion = findRangedVersion(SimpleDep(mavenId), repoUrl)
if (foundVersion != null) {
return RepoResult(repo, true, foundVersion)
} else {
return RepoResult(repo, false)
}
} else {
val dep = SimpleDep(mavenId)
// Try to find the jar file
val urlJar = repo.copy(url = repo.url + dep.toJarFile(dep.version))
val hasJar = Kurl(urlJar).exists
val found =
if (! hasJar) {
// No jar, try to find the directory
val url = repo.copy(url = repoUrl
+ File(dep.toJarFile(dep.version)).parentFile.path.replace("\\", "/"))
Kurl(url).exists
} else {
true
}
log(2, "Result for $repoUrl for $id: $found")
return RepoResult(repo, found, Version.of(dep.version), hasJar)
}
}
}
}
val XPATH_FACTORY = XPathFactory.newInstance();
val XPATH = XPATH_FACTORY.newXPath();
fun findCorrectVersionRelease(metadataPath: String, repoUrl: String): String? {
val XPATHS = arrayListOf(
XPATH.compile("/metadata/version"),
XPATH.compile("/metadata/versioning/latest"),
XPATH.compile("/metadata/versioning/release"))
// No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists
val url = repoUrl + metadataPath
try {
val doc = parseXml(url)
arrayListOf(XPATHS.forEach {
val result = it.evaluate(doc, XPathConstants.STRING) as String
if (! result.isEmpty()) {
return result
}
})
} catch(ex: Exception) {
log(2, "Couldn't find metadata at $url: ${ex.message}")
}
return null
}
fun findRangedVersion(dep: SimpleDep, repoUrl: String): Version? {
val l = listOf(dep.groupId.replace(".", "/"), dep.artifactId.replace(".", "/"), "maven-metadata.xml")
var metadataPath = l.joinToString("/")
val versionsXpath = XPATH.compile("/metadata/versioning/versions/version")
// No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists
val url = repoUrl + metadataPath
try {
val doc = parseXml(url)
val version = Version.of(dep.version)
if(version.isRangedVersion()) {
val versions = (versionsXpath.evaluate(doc, XPathConstants.NODESET) as NodeList)
.asElementList().map { Version.of(it.textContent) }
return version.select(versions)
} else {
return Version.of(XPATH.compile("/metadata/versioning/versions/version/$version")
.evaluate(doc, XPathConstants.STRING) as String)
}
} catch(ex: Exception) {
log(2, "Couldn't find metadata at ${url}")
}
return null
}
fun findSnapshotVersion(metadataPath: String, repoUrl: String): Version? {
val timestamp = XPATH.compile("/metadata/versioning/snapshot/timestamp")
val buildNumber = XPATH.compile("/metadata/versioning/snapshot/buildNumber")
// No version in this dependency, find out the most recent one by parsing maven-metadata.xml, if it exists
val url = repoUrl + metadataPath
try {
val doc = parseXml(url)
val ts = timestamp.evaluate(doc, XPathConstants.STRING)
val bn = buildNumber.evaluate(doc, XPathConstants.STRING)
if (! Strings.isEmpty(ts.toString()) && ! Strings.isEmpty(bn.toString())) {
return Version.of(ts.toString() + "-" + bn.toString())
} else {
val lastUpdated = XPATH.compile("/metadata/versioning/lastUpdated")
if (! lastUpdated.toString().isEmpty()) {
return Version.of(lastUpdated.toString())
}
}
} catch(ex: Exception) {
log(2, "Couldn't find metadata at $url")
}
return null
}
}

View file

@ -0,0 +1,36 @@
package com.beust.kobalt.maven
import com.beust.kobalt.misc.Strings
open class SimpleDep(open val mavenId: MavenId) : UnversionedDep(mavenId.groupId, mavenId.artifactId) {
companion object {
fun create(id: String) = MavenId.create(id).let {
SimpleDep(it)
}
}
val version: String get() = mavenId.version!!
private fun toFile(v: String, snapshotVersion: String?, suffix: String) : String {
val fv = if (v.contains("SNAPSHOT")) v.replace("SNAPSHOT", "") else v
val result = Strings.join("/", arrayListOf(toDirectory(v, false) +
artifactId + "-" + fv + (snapshotVersion ?: "") + suffix))
return result
}
fun toPomFile(v: String) = toFile(v, "", ".pom")
fun toPomFile(r: RepoFinder.RepoResult) = toFile(r.version!!.version, r.snapshotVersion?.version, ".pom")
fun toJarFile(v: String = version) = toFile(v, "", suffix)
fun toJarFile(r: RepoFinder.RepoResult) = toFile(r.version!!.version, r.snapshotVersion?.version, suffix)
fun toPomFileName() = "$artifactId-$version.pom"
val suffix : String
get() {
val packaging = mavenId.packaging
return if (packaging != null && ! packaging.isNullOrBlank()) ".$packaging" else ".jar"
}
}

View file

@ -0,0 +1,31 @@
package com.beust.kobalt.maven
import com.beust.kobalt.misc.Strings
import java.io.File
/**
* Represents a dependency that doesn't have a version: "org.testng:testng:". Such dependencies
* eventually resolve to the latest version of the artifact.
*/
open class UnversionedDep(open val groupId: String, open val artifactId: String) {
open fun toMetadataXmlPath(fileSystem: Boolean = true, isLocal: Boolean, version: String? = null) : String {
var result = toDirectory("", fileSystem) + if (isLocal) "maven-metadata-local.xml" else "maven-metadata.xml"
if (! File(result).exists() && version != null) {
result = toDirectory("", fileSystem) + version + File.separator +
if (isLocal) "maven-metadata-local.xml" else "maven-metadata.xml"
}
return result
}
/**
* Turn this dependency to a directory. If fileSystem is true, use the file system
* dependent path separator, otherwise, use '/' (used to create URL's). The returned
* string always ends with the path separator.
*/
fun toDirectory(v: String, fileSystem: Boolean = true): String {
val sep = if (fileSystem) File.separator else "/"
val l = listOf(groupId.replace(".", sep), artifactId, v)
val result = Strings.Companion.join(sep, l)
return if (result.endsWith(sep)) result else result + sep
}
}

View file

@ -0,0 +1,44 @@
package com.beust.kobalt.maven.dependency
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.maven.CompletedFuture
import org.apache.maven.model.Dependency
import java.io.File
open public class FileDependency(open val fileName: String) : IClasspathDependency, Comparable<FileDependency> {
companion object {
val PREFIX_FILE: String = "file://"
}
override val id = PREFIX_FILE + fileName
override val jarFile = CompletedFuture(File(fileName))
override fun toMavenDependencies(): Dependency {
with(Dependency()) {
systemPath = jarFile.get().absolutePath
return this
}
}
override val shortId = fileName
override fun directDependencies() = arrayListOf<IClasspathDependency>()
override fun compareTo(other: FileDependency) = fileName.compareTo(other.fileName)
override fun toString() = fileName
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as FileDependency
if (id != other.id) return false
return true
}
override fun hashCode() = id.hashCode()
}

View file

@ -0,0 +1,94 @@
package com.beust.kobalt.maven.dependency
import com.beust.kobalt.HostConfig
import com.beust.kobalt.KobaltException
import com.beust.kobalt.api.IClasspathDependency
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.maven.*
import com.beust.kobalt.misc.DependencyExecutor
import com.beust.kobalt.misc.Versions
import com.beust.kobalt.misc.warn
import com.google.inject.Key
import org.apache.maven.model.Dependency
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import javax.inject.Inject
import kotlin.properties.Delegates
public class MavenDependency @Inject constructor(mavenId: MavenId,
val executor: ExecutorService,
override val localRepo: LocalRepo,
val repoFinder: RepoFinder,
val pomFactory: Pom.IFactory,
val downloadManager: DownloadManager)
: LocalDep(mavenId, localRepo), IClasspathDependency, Comparable<MavenDependency> {
override var jarFile: Future<File> by Delegates.notNull()
var pomFile: Future<File> by Delegates.notNull()
init {
val jar = File(localRepo.toFullPath(toJarFile(version)))
val pom = File(localRepo.toFullPath(toPomFile(version)))
if (jar.exists() && pom.exists()) {
jarFile = CompletedFuture(jar)
pomFile = CompletedFuture(pom)
} else {
val repoResult = repoFinder.findCorrectRepo(mavenId.toId)
if (repoResult.found) {
jarFile =
if (repoResult.hasJar) {
downloadManager.download(HostConfig(url = repoResult.hostConfig.url + toJarFile(repoResult)),
jar.absolutePath, executor)
} else {
CompletedFuture(File("nonexistentFile")) // will be filtered out
}
pomFile = downloadManager.download(HostConfig(url = repoResult.hostConfig.url + toPomFile(repoResult)),
pom.absolutePath, executor)
} else {
throw KobaltException("Couldn't resolve ${mavenId.toId}")
}
}
}
companion object {
val executor = Kobalt.INJECTOR.getInstance(Key.get(ExecutorService::class.java, DependencyExecutor::class.java))
val depFactory = Kobalt.INJECTOR.getInstance(DepFactory::class.java)
fun create(id: String, ex: ExecutorService = executor) = depFactory.create(id, ex)
fun create(mavenId: MavenId, ex: ExecutorService = executor) = depFactory.create(mavenId.toId, ex)
}
override fun toString() = mavenId.toId
override val id = mavenId.toId
override fun toMavenDependencies(): Dependency {
return Dependency().apply {
setGroupId(groupId)
setArtifactId(artifactId)
setVersion(version)
}
}
override fun compareTo(other: MavenDependency): Int {
return Versions.toLongVersion(version).compareTo(Versions.toLongVersion(other.version))
}
override val shortId = "$groupId:$artifactId:"
override fun directDependencies() : List<IClasspathDependency> {
val result = arrayListOf<IClasspathDependency>()
try {
pomFactory.create(id, pomFile.get()).dependencies.filter {
it.mustDownload && it.isValid
}.forEach {
result.add(create(MavenId.toId(it.groupId, it.artifactId, it.packaging, it.version)))
}
} catch(ex: Exception) {
warn("Exception when trying to resolve dependencies for $id: " + ex.message)
}
return result
}
}

View file

@ -0,0 +1,8 @@
package com.beust.kobalt.misc
public fun benchmark(run: () -> Unit) : Long {
val start = System.currentTimeMillis()
run()
return (System.currentTimeMillis() - start) / 1000
}

View file

@ -0,0 +1,49 @@
package com.beust.kobalt.misc
import com.beust.kobalt.KobaltException
import com.beust.kobalt.api.Project
import com.beust.kobalt.maven.DepFactory
import com.beust.kobalt.maven.MavenId
import com.beust.kobalt.maven.dependency.MavenDependency
import javax.inject.Inject
/**
* Find out if any newer versions of the dependencies are available.
*/
public class CheckVersions @Inject constructor(val depFactory : DepFactory,
val executors : KobaltExecutors) {
fun run(projects: List<Project>) {
val executor = executors.newExecutor("CheckVersions", 5)
val newVersions = hashSetOf<String>()
projects.forEach {
listOf(it.compileDependencies, it.testDependencies).forEach { cds ->
cds.forEach { compileDependency ->
if (MavenId.isMavenId(compileDependency.id)) {
try {
val dep = depFactory.create(compileDependency.shortId, executor, localFirst = false)
if (dep is MavenDependency) {
val other = compileDependency as MavenDependency
if (dep.id != compileDependency.id
&& Versions.toLongVersion(dep.version) > Versions.toLongVersion(other.version)) {
newVersions.add(dep.id)
}
}
} catch(e: KobaltException) {
log(1, " Cannot resolve ${compileDependency.shortId}. ignoring")
}
}
}
}
}
if (newVersions.size > 0) {
log(1, "New versions found:")
newVersions.forEach { log(1, " $it") }
} else {
log(1, "All dependencies up to date")
}
executor.shutdown()
}
}

View file

@ -0,0 +1,65 @@
package com.beust.kobalt.misc
import com.squareup.okhttp.MediaType
import com.squareup.okhttp.RequestBody
import okio.BufferedSink
import okio.Okio
import java.io.File
/**
* An OkHttp RequestBody subclass that counts the outgoing bytes and offers progress callbacks.
*/
class CountingFileRequestBody(val file: File, val contentType: String,
val listenerCallback: (Long) -> Unit) : RequestBody() {
val SEGMENT_SIZE = 4096L
override fun contentLength() = file.length()
override fun contentType() = MediaType.parse(contentType)
override fun writeTo(sink: BufferedSink) {
Okio.source(file).use { source ->
var total = 0L
var read: Long = source.read(sink.buffer(), SEGMENT_SIZE)
while (read != -1L) {
total += read
sink.flush();
listenerCallback(total)
read = source.read(sink.buffer(), SEGMENT_SIZE)
}
}
}
// companion object {
// private val MEDIA_TYPE_BINARY = MediaType.parse("application/octet-stream")
//
// fun progressUpload(file: File, url: String) {
// val totalSize = file.length()
//
// val progressListener = object : ProgressListener {
// override fun transferred(num: Long) {
// val progress: Float = (num.toFloat() * 100) / totalSize
// print("\rProgress: $progress")
// }
// }
//
// val request = Request.Builder()
// .url(url)
// // .post(RequestBody.create(MEDIA_TYPE_BINARY, file))
// .put(CountingFileRequestBody(file, "application/octet-stream", progressListener))
// // .post(requestBody)
// .build();
//
// val response = OkHttpClient().newCall(request).execute()
// if (! response.isSuccessful) {
// println("ERROR")
// } else {
// println("SUCCESS")
// }
// }
// }
}

View file

@ -0,0 +1,148 @@
package com.beust.kobalt.misc
import com.beust.kobalt.KobaltException
import com.beust.kobalt.internal.DocUrl
import com.beust.kobalt.maven.Http
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
import com.squareup.okhttp.Headers
import com.squareup.okhttp.OkHttpClient
import retrofit.RestAdapter
import retrofit.RetrofitError
import retrofit.client.OkClient
import retrofit.http.*
import retrofit.mime.TypedByteArray
import retrofit.mime.TypedFile
import rx.Observable
import java.io.File
import java.util.*
import java.util.concurrent.Callable
import java.util.concurrent.Future
import javax.inject.Inject
/**
* Retrieve Kobalt's latest release version from github.
*/
public class GithubApi @Inject constructor(val executors: KobaltExecutors,
val localProperties: LocalProperties, val http: Http) {
companion object {
const val PROPERTY_ACCESS_TOKEN = "github.accessToken"
const val PROPERTY_USERNAME = "github.username"
}
class RetrofitErrorResponse(val code: String?, val field: String?)
class RetrofitErrorsResponse(val message: String?, val errors: List<RetrofitErrorResponse>)
private val DOC_URL = DocUrl.PUBLISH_PLUGIN_URL
private fun parseRetrofitError(e: Throwable) : RetrofitErrorsResponse {
val re = e as RetrofitError
val json = String((re.response.body as TypedByteArray).bytes)
return Gson().fromJson(json, RetrofitErrorsResponse::class.java)
}
fun uploadRelease(packageName: String, tagName: String, zipFile: File) {
log(1, "Uploading release ${zipFile.name}")
val username = localProperties.get(PROPERTY_USERNAME, DOC_URL)
val accessToken = localProperties.get(PROPERTY_ACCESS_TOKEN, DOC_URL)
try {
service.createRelease(username, accessToken, packageName, CreateRelease(tagName))
.flatMap { response ->
uploadAsset(accessToken, response.uploadUrl!!, TypedFile("application/zip", zipFile),
tagName)
}
.toBlocking()
.forEach { action ->
log(1, "\n${zipFile.name} successfully uploaded")
}
} catch(e: RetrofitError) {
val error = parseRetrofitError(e)
throw KobaltException("Couldn't upload release, ${error.message}: "
+ error.errors[0].code + " field: " + error.errors[0].field)
}
}
private fun uploadAsset(token: String, uploadUrl: String, typedFile: TypedFile, tagName: String)
: Observable<UploadAssetResponse> {
val strippedUrl = uploadUrl.substring(0, uploadUrl.indexOf("{"))
val fileName = typedFile.file().name
val url = "$strippedUrl?name=$fileName&label=$fileName"
val headers = Headers.of("Authorization", "token $token")
val totalSize = typedFile.file().length()
http.uploadFile(url = url, file = typedFile, headers = headers, post = true, // Github requires POST
progressCallback = http.percentProgressCallback(totalSize))
return Observable.just(UploadAssetResponse(tagName, tagName))
}
//
// Read only Api
//
private val service = RestAdapter.Builder()
// .setLogLevel(RestAdapter.LogLevel.FULL)
.setClient(OkClient(OkHttpClient()))
.setEndpoint("https://api.github.com")
.build()
.create(Api::class.java)
//
// JSON mapped classes that get sent up and down
//
class CreateRelease(@SerializedName("tag_name") var tagName: String? = null,
var name: String? = tagName)
class CreateReleaseResponse(var id: String? = null, @SerializedName("upload_url") var uploadUrl: String?)
class UploadAssetResponse(var id: String? = null, val name: String? = null)
class ReleasesResponse(@SerializedName("tag_name") var tagName: String? = null,
var name: String? = tagName)
interface Api {
@POST("/repos/{owner}/{repo}/releases")
fun createRelease(@Path("owner") owner: String,
@Query("access_token") accessToken: String,
@Path("repo") repo: String,
@Body createRelease: CreateRelease): Observable<CreateReleaseResponse>
@GET("/repos/{owner}/{repo}/releases")
fun getReleases(@Path("owner") owner: String,
@Query("access_token") accessToken: String,
@Path("repo") repo: String): List<ReleasesResponse>
@GET("/repos/{owner}/{repo}/releases")
fun getReleasesNoAuth(@Path("owner") owner: String,
@Path("repo") repo: String): List<ReleasesResponse>
}
val latestKobaltVersion: Future<String>
get() {
val callable = Callable<String> {
var result = "0"
val username = localProperties.getNoThrows(PROPERTY_USERNAME, DOC_URL)
val accessToken = localProperties.getNoThrows(PROPERTY_ACCESS_TOKEN, DOC_URL)
try {
val releases =
if (username != null && accessToken != null) {
service.getReleases(username, accessToken, "kobalt")
} else {
service.getReleasesNoAuth("cbeust", "kobalt")
}
releases.firstOrNull()?.let {
try {
result = listOf(it.name, it.tagName).filterNotNull().first { !it.isBlank() }
} catch(ex: NoSuchElementException) {
throw KobaltException("Couldn't find the latest release")
}
}
} catch(e: RetrofitError) {
val error = parseRetrofitError(e)
throw KobaltException("Couldn't retrieve releases, ${error.message}: "
+ error.errors[0].code + " field: " + error.errors[0].field)
}
result
}
return executors.miscExecutor.submit(callable)
}
}

View file

@ -0,0 +1,160 @@
package com.beust.kobalt.misc
import com.beust.kobalt.IFileSpec
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 {
companion object {
val DEFAULT_HANDLER: (Exception) -> Unit = { ex: Exception ->
// Ignore duplicate entry exceptions
if (! ex.message?.contains("duplicate")!!) {
throw ex
}
}
public fun addFiles(directory: String, files: List<IncludedFile>, target: ZipOutputStream,
expandJarFiles: Boolean,
onError: (Exception) -> Unit = DEFAULT_HANDLER) {
files.forEach {
addSingleFile(directory, it, target, expandJarFiles, onError)
}
}
private val DEFAULT_JAR_EXCLUDES = arrayListOf(
IFileSpec.Glob("META-INF/*.SF"),
IFileSpec.Glob("META-INF/*.DSA"),
IFileSpec.Glob("META-INF/*.RSA"))
public fun addSingleFile(directory: String, file: IncludedFile, outputStream: ZipOutputStream,
expandJarFiles: Boolean, onError: (Exception) -> Unit = DEFAULT_HANDLER) {
file.specs.forEach { spec ->
val path = spec.toString()
spec.toFiles(directory + "/" + file.from).forEach { source ->
if (source.isDirectory) {
log(2, "Writing contents of directory ${source}")
// Directory
var name = path
if (!name.isEmpty()) {
if (!name.endsWith("/")) name += "/"
val entry = JarEntry(name)
entry.time = source.lastModified()
try {
outputStream.putNextEntry(entry)
} finally {
outputStream.closeEntry()
}
}
val includedFile = IncludedFile(From(source.path), To(""), listOf(IFileSpec.Glob("**")))
addSingleFile(".", includedFile, outputStream, expandJarFiles)
} else {
if (expandJarFiles and source.name.endsWith(".jar")) {
log(2, "Writing contents of jar file ${source}")
val stream = JarInputStream(FileInputStream(source))
var entry = stream.nextEntry
while (entry != null) {
if (! entry.isDirectory && ! KFiles.isExcluded(entry.name, DEFAULT_JAR_EXCLUDES)) {
val ins = JarFile(source).getInputStream(entry)
addEntry(ins, JarEntry(entry), outputStream, onError)
}
entry = stream.nextEntry
}
} else {
val entry = JarEntry((file.to + source.path).replace("\\", "/"))
entry.time = source.lastModified()
val fromPath = (file.from + "/" + source.path).replace("\\", "/")
val entryFile = File(directory, fromPath)
if (! entryFile.exists()) {
throw AssertionError("File should exist: ${entryFile}")
}
addEntry(FileInputStream(entryFile), entry, outputStream, onError)
}
}
}
}
}
private fun addEntry(inputStream: InputStream, entry: ZipEntry, outputStream: ZipOutputStream,
onError: (Exception) -> Unit = DEFAULT_HANDLER) {
var bis: BufferedInputStream? = null
try {
outputStream.putNextEntry(entry)
bis = BufferedInputStream(inputStream)
val buffer = ByteArray(50 * 1024)
while (true) {
val count = bis.read(buffer)
if (count == -1) break
outputStream.write(buffer, 0, count)
}
outputStream.closeEntry()
} catch(ex: Exception) {
onError(ex)
} finally {
bis?.close()
}
}
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.name}")
zip.getInputStream(file).use { ins ->
return CharStreams.toString(InputStreamReader(ins, "UTF-8"))
}
}
}
return null
}
fun extractJarFile(jarFile: File, destDir: File) {
val jar = JarFile(jarFile)
val enumEntries = jar.entries()
while (enumEntries.hasMoreElements()) {
val file = enumEntries.nextElement()
val f = File(destDir.path + File.separator + file.name)
if (file.isDirectory) {
f.mkdir()
continue
}
jar.getInputStream(file).use { ins ->
f.parentFile.mkdirs()
FileOutputStream(f).use { fos ->
while (ins.available() > 0) {
fos.write(ins.read())
}
}
}
}
}
}
}
open class Direction(open val p: String) {
override public fun toString() = path
public val path: String get() = if (p.isEmpty() or p.endsWith("/")) p else p + "/"
}
class IncludedFile(val fromOriginal: From, val toOriginal: To, val specs: List<IFileSpec>) {
constructor(specs: List<IFileSpec>) : this(From(""), To(""), specs)
public val from: String get() = fromOriginal.path.replace("\\", "/")
public val to: String get() = toOriginal.path.replace("\\", "/")
override public fun toString() = toString("IncludedFile",
"files", specs.map { it.toString() }.joinToString(", "),
"from", from,
"to", to)
}
class From(override val p: String) : Direction(p)
class To(override val p: String) : Direction(p)

View file

@ -0,0 +1,300 @@
package com.beust.kobalt.misc
import com.beust.kobalt.IFileSpec
import com.beust.kobalt.SystemProperties
import com.beust.kobalt.Variant
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.api.Project
import com.beust.kobalt.homeDir
import com.beust.kobalt.internal.build.BuildFile
import java.io.File
import java.io.IOException
import java.nio.file.*
import kotlin.io.FileAlreadyExistsException
import kotlin.io.FileSystemException
import kotlin.io.NoSuchFileException
class KFiles {
/**
* This actually returns a list of strings because in development mode, we are not pointing to a single
* jar file but to a set of /classes directories.
*/
val kobaltJar : List<String>
get() {
val envJar = System.getenv("KOBALT_JAR")
if (envJar != null) {
debug("Using kobalt jar $envJar")
return listOf(File(envJar).absolutePath)
} else {
val jar = joinDir(distributionsDir, Kobalt.version, "kobalt/wrapper/kobalt-" + Kobalt.version + ".jar")
val jarFile = File(jar)
if (jarFile.exists()) {
return listOf(jarFile.absolutePath)
} else {
// 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
val result = listOf("kobalt", "kobalt-plugin-api", "kobalt-wrapper").map {
File(homeDir(KFiles.joinDir("kotlin", "kobalt", "out", "production", it))).absolutePath
}
debug("Couldn't find ${jarFile.absolutePath}, using\n " + result.joinToString(" "))
return result
}
}
}
init {
File(KOBALT_DOT_DIR).mkdirs()
}
companion object {
private const val KOBALT_DOT_DIR : String = ".kobalt"
const val KOBALT_DIR : String = "kobalt"
const val KOBALT_BUILD_DIR = "kobaltBuild"
// Directories under ~/.kobalt
val localRepo = homeDir(KOBALT_DOT_DIR, "repository")
/** Where all the .zip files are extracted */
val distributionsDir = homeDir(KOBALT_DOT_DIR, "wrapper", "dist")
// Directories under ./.kobalt
val SCRIPT_BUILD_DIR : String = "build"
val CLASSES_DIR : String = "classes"
/** Where build file and support source files go, under KOBALT_DIR */
private val SRC = "src"
val TEST_CLASSES_DIR : String = "test-classes"
private fun generatedDir(project: Project, variant: Variant)
= KFiles.joinDir(project.directory, project.buildDirectory, "generated", variant.toIntermediateDir())
fun generatedSourceDir(project: Project, variant: Variant, name: String) =
KFiles.joinDir(project.directory, project.buildDirectory, "generated", "source", name,
variant.toIntermediateDir())
fun buildDir(project: Project) = KFiles.makeDir(project.directory, project.buildDirectory)
/**
* Join the paths elements with the file separator.
*/
fun joinDir(paths: List<String>): String = paths.joinToString(File.separator)
/**
* Join the paths elements with the file separator.
*/
fun joinDir(vararg ts: String): String = ts.toArrayList().joinToString(File.separator)
/**
* The paths elements are expected to be a directory. Make that directory and join the
* elements with the file separator.
*/
fun joinAndMakeDir(paths: List<String>) = joinDir(paths).apply { File(this).mkdirs() }
/**
* The paths elements are expected to be a directory. Make that directory and join the
* elements with the file separator.
*/
fun joinAndMakeDir(vararg ts: String) = joinAndMakeDir(ts.toList())
/**
* The paths elements are expected to be a file. Make that parent directory of that file and join the
* elements with the file separator.
*/
fun joinFileAndMakeDir(vararg ts: String) = joinDir(joinAndMakeDir(ts.slice(0..ts.size - 2)), ts[ts.size - 1])
fun makeDir(dir: String, s: String? = null) =
(if (s != null) File(dir, s) else File(dir)).apply { mkdirs() }
fun findRecursively(rootDir: File) : List<String> =
findRecursively(rootDir, arrayListOf(), { s -> true })
fun findRecursively(rootDir: File, directories: List<File>,
function: Function1<String, Boolean>): List<String> {
var result = arrayListOf<String>()
val allDirs = arrayListOf<File>()
if (directories.isEmpty()) {
allDirs.add(rootDir)
} else {
allDirs.addAll(directories.map { File(rootDir, it.path) })
}
val seen = hashSetOf<java.nio.file.Path>()
allDirs.forEach { dir ->
if (! dir.exists()) {
log(2, "Couldn't find directory $dir")
} else {
val files = findRecursively(dir, function)
files.map { Paths.get(it) }.forEach {
val rel = Paths.get(dir.path).relativize(it)
if (! seen.contains(rel)) {
result.add(File(dir, rel.toFile().path).path)
seen.add(rel)
} else {
log(2, "Skipped file already seen in previous flavor: $rel")
}
}
}
}
// Return files relative to rootDir
val r = result.map { it.substring(rootDir.path.length + 1)}
return r
}
fun findRecursively(directory: File, function: Function1<String, Boolean>): List<String> {
var result = arrayListOf<String>()
directory.listFiles().forEach {
if (it.isFile && function(it.path)) {
result.add(it.path)
} else if (it.isDirectory) {
result.addAll(findRecursively(it, function))
}
}
return result
}
fun copyRecursively(from: File, to: File, replaceExisting: Boolean = true, deleteFirst: Boolean = false,
onError: (File, IOException) -> OnErrorAction = { file, exception -> throw exception }) {
// Need to wait until copyRecursively supports an overwrite: Boolean = false parameter
// Until then, wipe everything first
if (deleteFirst) to.deleteRecursively()
// to.mkdirs()
hackCopyRecursively(from, to, replaceExisting, onError)
}
/** Private exception class, used to terminate recursive copying */
private class TerminateException(file: File) : FileSystemException(file) {}
/**
* Copy/pasted from kotlin/io/Utils.kt to add support for overwriting.
*/
private fun hackCopyRecursively(from: File, dst: File,
replaceExisting: Boolean,
onError: (File, IOException) -> OnErrorAction =
{ file, exception -> throw exception }
): Boolean {
if (!from.exists()) {
return onError(from, NoSuchFileException(file = from, reason = "The source file doesn't exist")) !=
OnErrorAction.TERMINATE
}
try {
// We cannot break for loop from inside a lambda, so we have to use an exception here
for (src in from.walkTopDown().fail { f, e -> if (onError(f, e) == OnErrorAction.TERMINATE) throw TerminateException(f) }) {
if (!src.exists()) {
if (onError(src, NoSuchFileException(file = src, reason = "The source file doesn't exist")) ==
OnErrorAction.TERMINATE)
return false
} else {
val relPath = src.relativeTo(from)
val dstFile = File(dst, relPath)
if (dstFile.exists() && !replaceExisting && !(src.isDirectory() && dstFile.isDirectory())) {
if (onError(dstFile, FileAlreadyExistsException(file = src,
other = dstFile,
reason = "The destination file already exists")) == OnErrorAction.TERMINATE)
return false
} else if (src.isDirectory()) {
dstFile.mkdirs()
} else {
if (src.copyTo(dstFile, true) != src.length()) {
if (onError(src, IOException("src.length() != dst.length()")) == OnErrorAction.TERMINATE)
return false
}
}
}
}
return true
} catch (e: TerminateException) {
return false
}
}
fun findDotDir(startDir: File) : File {
var result = startDir
while (result != null && ! File(result, KOBALT_DOT_DIR).exists()) {
result = result.parentFile
}
if (result == null) {
throw IllegalArgumentException("Couldn't locate $KOBALT_DOT_DIR in $startDir")
}
return File(result, KOBALT_DOT_DIR)
}
/**
* The build location for build scripts is .kobalt/build
*/
fun findBuildScriptLocation(buildFile: BuildFile, jarFile: String) : String {
val result = joinDir(findDotDir(buildFile.directory).absolutePath, KFiles.SCRIPT_BUILD_DIR, jarFile)
log(2, "Script jar file: $result")
return result
}
fun saveFile(file: File, text: String) {
file.absoluteFile.parentFile.mkdirs()
file.writeText(text)
log(2, "Wrote $file")
}
private fun isWindows() = System.getProperty("os.name").contains("Windows");
fun copy(from: Path?, to: Path?, option: StandardCopyOption = StandardCopyOption.REPLACE_EXISTING) {
if (isWindows() && to!!.toFile().exists()) {
log(2, "Windows detected, not overwriting ${to}")
} else {
try {
log(2, "Copy from $from to ${to!!}")
Files.copy(from, to, option)
} catch(ex: IOException) {
// Windows is anal about this
log(1, "Couldn't copy $from to $to: ${ex.message}")
}
}
}
fun createTempFile(suffix : String = "", deleteOnExit: Boolean = false) : File =
File.createTempFile("kobalt", suffix, File(SystemProperties.tmpDir)).let {
if (deleteOnExit) it.deleteOnExit()
return it
}
fun src(filePath: String): String = KFiles.joinDir(KOBALT_DIR, SRC, filePath)
fun makeDir(project: Project, suffix: String) : File {
return File(project.directory, project.buildDirectory + File.separator + suffix).apply { mkdirs() }
}
fun makeOutputDir(project: Project) : File = makeDir(project, KFiles.CLASSES_DIR)
fun makeOutputTestDir(project: Project) : File = makeDir(project, KFiles.TEST_CLASSES_DIR)
fun isExcluded(file: File, excludes: List<IFileSpec.Glob>) = isExcluded(file.path, excludes)
fun isExcluded(file: String, excludes: List<IFileSpec.Glob>) : Boolean {
if (excludes.isEmpty()) {
return false
} else {
val ex = arrayListOf<PathMatcher>()
excludes.forEach {
ex.add(FileSystems.getDefault().getPathMatcher("glob:${it.spec}"))
}
ex.forEach {
if (it.matches(Paths.get(file))) {
log(3, "Excluding $file")
return true
}
}
}
return false
}
}
fun findRecursively(directory: File, function: Function1<String, Boolean>): List<String> {
return KFiles.findRecursively(directory, function)
}
fun findRecursively(rootDir: File, directories: List<File>,
function: Function1<String, Boolean>): List<String> {
return KFiles.findRecursively(rootDir, directories, function)
}
}

Some files were not shown because too many files have changed in this diff Show more