diff --git a/.travis.yml b/.travis.yml index 46fdae3d..0c1d3b1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,4 +10,4 @@ jdk: install: true -script: ./kobaltw assemble test --log 1 +script: ./build-travis.sh diff --git a/build-travis.sh b/build-travis.sh new file mode 100755 index 00000000..467849b9 --- /dev/null +++ b/build-travis.sh @@ -0,0 +1,4 @@ +java -Xmx2048m -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $* assemble + +java -Xmx2048m -jar $(dirname $0)/kobalt/wrapper/kobalt-wrapper.jar $* test + diff --git a/dist/kobaltw.bat b/dist/kobaltw.bat index 52e2d636..8ce6a621 100644 --- a/dist/kobaltw.bat +++ b/dist/kobaltw.bat @@ -1,2 +1,2 @@ @echo off -java -jar "%~dp0/kobalt/wrapper/kobalt-wrapper.jar" %* +java -jar "%~dp0/../kobalt/wrapper/kobalt-wrapper.jar" %* diff --git a/kobalt.iml b/kobalt.iml index 384e6859..e9146d07 100644 --- a/kobalt.iml +++ b/kobalt.iml @@ -3,9 +3,7 @@ - - @@ -16,7 +14,7 @@ - + @@ -25,7 +23,7 @@ - + @@ -34,7 +32,7 @@ - + @@ -43,7 +41,7 @@ - + @@ -52,7 +50,7 @@ - + @@ -61,7 +59,7 @@ - + @@ -70,7 +68,7 @@ - + @@ -79,16 +77,16 @@ - + - + - + @@ -97,7 +95,7 @@ - + @@ -106,7 +104,7 @@ - + @@ -115,7 +113,7 @@ - + @@ -124,7 +122,7 @@ - + @@ -133,7 +131,7 @@ - + @@ -142,7 +140,7 @@ - + @@ -151,7 +149,7 @@ - + @@ -160,7 +158,7 @@ - + @@ -169,7 +167,7 @@ - + @@ -178,7 +176,7 @@ - + @@ -187,7 +185,7 @@ - + @@ -196,7 +194,7 @@ - + @@ -205,7 +203,7 @@ - + @@ -214,7 +212,7 @@ - + @@ -223,7 +221,7 @@ - + @@ -232,7 +230,7 @@ - + @@ -241,7 +239,7 @@ - + @@ -250,7 +248,7 @@ - + @@ -259,7 +257,7 @@ - + @@ -268,7 +266,7 @@ - + @@ -277,7 +275,7 @@ - + @@ -286,7 +284,7 @@ - + @@ -295,7 +293,7 @@ - + @@ -304,7 +302,7 @@ - + @@ -313,7 +311,7 @@ - + @@ -322,7 +320,7 @@ - + @@ -331,7 +329,7 @@ - + @@ -340,7 +338,7 @@ - + @@ -349,7 +347,7 @@ - + @@ -358,7 +356,7 @@ - + @@ -367,12 +365,13 @@ - + + - \ No newline at end of file + diff --git a/kobalt/src/Build.kt b/kobalt/src/Build.kt index afd4efe7..af5b0b66 100644 --- a/kobalt/src/Build.kt +++ b/kobalt/src/Build.kt @@ -21,13 +21,11 @@ import java.nio.file.StandardCopyOption object Versions { val okhttp = "3.2.0" val okio = "1.6.0" - val retrofit = "2.0.0" + val retrofit = "2.0.2" val gson = "2.6.2" val aether = "1.1.0" val sonatypeAether = "1.13.1" val maven = "3.3.9" - val jersey = "2.22.2" - val jetty = "8.1.19.v20160209" // "9.3.9.M1" } val wrapper = project { @@ -69,16 +67,16 @@ val kobaltPluginApi = project { developerConnection = "git@github.com:cbeust/kobalt.git") dependencies { - compile("org.jetbrains.kotlinx:kotlinx.dom:0.0.10", - + compile( "com.google.inject:guice:4.0", "com.google.inject.extensions:guice-assistedinject:4.0", "javax.inject:javax.inject:1", - "com.google.guava:guava:19.0-rc2", + "com.google.guava:guava:19.0", "org.apache.maven:maven-model:${Versions.maven}", - "io.reactivex:rxjava:1.0.16", - "com.google.code.gson:gson:${Versions.gson}", + "io.reactivex:rxjava:1.1.5", "com.squareup.okio:okio:${Versions.okio}", + "com.google.code.gson:gson:${Versions.gson}", + "com.squareup.okhttp3:okhttp:${Versions.okhttp}", "com.squareup.retrofit2:retrofit:${Versions.retrofit}", "com.squareup.retrofit2:converter-gson:${Versions.retrofit}", "com.beust:jcommander:1.48", @@ -126,7 +124,7 @@ val kobaltApp = project(kobaltPluginApi, wrapper) { dependencies { // Used by the plugins - compile("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.0") + compile("org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.2") // Used by the main app compile("com.github.spullara.mustache.java:compiler:0.9.1", @@ -137,7 +135,6 @@ val kobaltApp = project(kobaltPluginApi, wrapper) { "org.apache.maven:maven-model:${Versions.maven}", "com.google.code.findbugs:jsr305:3.0.1", "com.google.code.gson:gson:${Versions.gson}", - "com.squareup.okhttp3:okhttp:${Versions.okhttp}", "com.squareup.retrofit2:retrofit:${Versions.retrofit}", "com.squareup.retrofit2:converter-gson:${Versions.retrofit}", "org.codehaus.plexus:plexus-utils:3.0.22", @@ -159,7 +156,7 @@ val kobaltApp = project(kobaltPluginApi, wrapper) { } dependenciesTest { - compile("org.testng:testng:6.9.10", + compile("org.testng:testng:6.9.11", "org.assertj:assertj-core:3.4.1") } diff --git a/kobalt/wrapper/kobalt-wrapper.properties b/kobalt/wrapper/kobalt-wrapper.properties index 7fe637c5..25ce6fa8 100644 --- a/kobalt/wrapper/kobalt-wrapper.properties +++ b/kobalt/wrapper/kobalt-wrapper.properties @@ -1 +1 @@ -kobalt.version=0.789 \ No newline at end of file +kobalt.version=0.814 \ No newline at end of file diff --git a/kobaltw.bat b/kobaltw.bat index 8ce6a621..52e2d636 100644 --- a/kobaltw.bat +++ b/kobaltw.bat @@ -1,2 +1,2 @@ @echo off -java -jar "%~dp0/../kobalt/wrapper/kobalt-wrapper.jar" %* +java -jar "%~dp0/kobalt/wrapper/kobalt-wrapper.jar" %* diff --git a/modules/kobalt-plugin-api/kobalt-plugin-api.iml b/modules/kobalt-plugin-api/kobalt-plugin-api.iml index 7e463927..40595e33 100644 --- a/modules/kobalt-plugin-api/kobalt-plugin-api.iml +++ b/modules/kobalt-plugin-api/kobalt-plugin-api.iml @@ -12,9 +12,9 @@ - + - + @@ -23,7 +23,7 @@ - + @@ -32,7 +32,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -59,7 +59,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -104,7 +104,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -131,7 +131,7 @@ - + @@ -140,7 +140,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -167,7 +167,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -203,7 +203,7 @@ - + @@ -212,7 +212,7 @@ - + @@ -221,7 +221,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -239,7 +239,7 @@ - + @@ -248,7 +248,7 @@ - + @@ -257,7 +257,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -275,7 +275,7 @@ - + @@ -284,7 +284,7 @@ - + @@ -293,7 +293,7 @@ - + @@ -302,7 +302,7 @@ - + @@ -311,7 +311,7 @@ - + @@ -320,7 +320,7 @@ - + @@ -329,7 +329,7 @@ - + @@ -338,7 +338,7 @@ - + @@ -347,7 +347,7 @@ - + @@ -356,7 +356,7 @@ - + @@ -365,7 +365,7 @@ - + @@ -374,7 +374,7 @@ - + @@ -383,16 +383,16 @@ - + - + - + @@ -401,7 +401,7 @@ - + @@ -410,7 +410,7 @@ - + @@ -419,7 +419,7 @@ - + @@ -428,7 +428,7 @@ - + @@ -437,11 +437,12 @@ - + + - \ No newline at end of file + diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt index 7501cec5..03c5e134 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Args.kt @@ -45,6 +45,10 @@ class Args { @Parameter(names = arrayOf("--log"), description = "Define the log level (1-3)") var log: Int = 1 + @Parameter(names = arrayOf("--forceIncremental"), + description = "Force the build to be incremental even if the build file was modified") + var forceIncremental: Boolean = false + @Parameter(names = arrayOf("--noIncremental"), description = "Turn off incremental builds") var noIncremental: Boolean = false diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt index d7197526..d99c59f5 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt @@ -2,10 +2,8 @@ package com.beust.kobalt import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project -import com.beust.kobalt.api.ProjectDescription import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.Jar -import com.beust.kobalt.internal.JvmCompilerPlugin import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.* import com.google.inject.Inject @@ -93,26 +91,23 @@ class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) // If fatJar is true, add all the transitive dependencies as well: compile, runtime and dependent projects // if (jar.fatJar) { - log(2, "Creating fat jar") + log(2, "Finding included files for fat jar") val seen = hashSetOf() @Suppress("UNCHECKED_CAST") - val dependentProjects = project.projectProperties.get(JvmCompilerPlugin.DEPENDENT_PROJECTS) - as List val allDependencies = project.compileDependencies + project.compileRuntimeDependencies + context.variant.buildType.compileDependencies + context.variant.buildType.compileRuntimeDependencies + context.variant.productFlavor.compileDependencies + context.variant.productFlavor.compileRuntimeDependencies - val transitiveDependencies = dependencyManager.calculateDependencies(project, context, dependentProjects, - allDependencies) + val transitiveDependencies = dependencyManager.calculateDependencies(project, context, allDependencies) transitiveDependencies.map { it.jarFile.get() }.forEach { file : File -> if (! seen.contains(file.path)) { seen.add(file.path) if (! KFiles.Companion.isExcluded(file, jar.excludes)) { - result.add(IncludedFile(specs = arrayListOf(IFileSpec.FileSpec(file.path)), + result.add(IncludedFile(specs = arrayListOf(IFileSpec.FileSpec(file.absolutePath)), expandJarFiles = true)) } } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt index c31837b0..29e902e1 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/Plugins.kt @@ -12,6 +12,7 @@ import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.KobaltExecutors import com.beust.kobalt.misc.log import com.google.inject.Provider +import java.io.File import java.lang.reflect.Method import java.lang.reflect.Modifier import java.net.URLClassLoader @@ -78,7 +79,9 @@ class Plugins @Inject constructor (val taskManagerProvider : Provider + taskManager.dynamicTasks.addAll(it.tasksFor(project, context)) + } } // Now that we have collected all static and dynamic tasks, turn them all into plug-in tasks @@ -160,9 +163,17 @@ class Plugins @Inject constructor (val taskManagerProvider : Provider = emptyList(), vararg allDependencies: List): List } \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt index aa39712b..33ec6e7c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ISourceDirectoriesInterceptor.kt @@ -5,7 +5,7 @@ import java.io.File /** * Plug-ins can alter the source directories by implementing this interface. */ -interface ISourceDirectoryIncerceptor : IInterceptor { +interface ISourceDirectoryInterceptor : IInterceptor { fun intercept(project: Project, context: KobaltContext, sourceDirectories: List) : List } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt index 55eed61a..83621451 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/ITaskContributor.kt @@ -8,7 +8,7 @@ import com.beust.kobalt.internal.TaskResult2 * to implement this interface. */ interface ITaskContributor : IContributor { - fun tasksFor(context: KobaltContext) : List + fun tasksFor(project: Project, context: KobaltContext) : List } class DynamicTask(override val plugin: IPlugin, override val name: String, override val doc: String, diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt index a004071a..2e2b5146 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/Project.kt @@ -123,6 +123,8 @@ open class Project( }) return result } + + override fun toString() = "[Project $name]" } class Sources(val project: Project, val sources: HashSet) { diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt index 7fa33775..65eb529d 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/api/TaskContributor.kt @@ -53,6 +53,7 @@ class TaskContributor @Inject constructor(val incrementalManagerFactory: Increme runTask: (Project) -> IncrementalTaskInfo) { Variant.allVariants(project).forEach { variant -> val variantTaskName = variant.toTask(taskName) + context.variant = variant dynamicTasks.add(DynamicTask(plugin, variantTaskName, variantTaskName, group, project, dependsOn = dependsOn.map { variant.toTask(it) }, reverseDependsOn = reverseDependsOn.map { variant.toTask(it) }, @@ -62,5 +63,5 @@ class TaskContributor @Inject constructor(val incrementalManagerFactory: Increme } } - override fun tasksFor(context: KobaltContext) : List = dynamicTasks + override fun tasksFor(project: Project, context: KobaltContext) : List = dynamicTasks } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt index ca808318..66fe50e1 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt @@ -33,12 +33,19 @@ class Archives { val result = File(archiveDir.path, fullArchiveName) log(3, "Creating $result") if (! Features.USE_TIMESTAMPS || isOutdated(project.directory, includedFiles, result)) { - val outStream = outputStreamFactory(FileOutputStream(result)) - JarUtils.addFiles(project.directory, includedFiles, outStream, expandJarFiles) - log(2, text = "Added ${includedFiles.size} files to $result") - outStream.flush() - outStream.close() - log(1, " Created $result") + try { + outputStreamFactory(FileOutputStream(result)).use { + JarUtils.addFiles(project.directory, includedFiles, it, expandJarFiles) + log(2, text = "Added ${includedFiles.size} files to $result") + log(1, " Created $result") + } + } catch (e: Throwable) { + // make sure that incomplete archive is deleted + // otherwise incremental build does not work on next run + result.delete() + throw e + } + } else { log(3, " $result is up to date") } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/CompilerUtils.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/CompilerUtils.kt new file mode 100644 index 00000000..fb551133 --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/CompilerUtils.kt @@ -0,0 +1,192 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.TaskResult +import com.beust.kobalt.api.* +import com.beust.kobalt.maven.DependencyManager +import com.beust.kobalt.maven.dependency.FileDependency +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.log +import com.google.inject.Inject +import java.io.File +import java.util.* + +/** + * Central place to compile files, used by plug-ins and non plug-ins. + */ +class CompilerUtils @Inject constructor(val files: KFiles, + val dependencyManager: DependencyManager) { + + class CompilerResult(val successResults: List, val failedResult: TaskResult?) + + fun invokeCompiler(project: Project, context: KobaltContext, compiler: ICompiler, + sourceDirectories: List, isTest: Boolean): CompilerResult { + val results = arrayListOf() + var failedResult: TaskResult? = null + val contributedSourceDirs = + if (isTest) { + context.testSourceDirectories(project) + } else { + context.sourceDirectories(project) + } + val sourceFiles = KFiles.findSourceFiles(project.directory, + contributedSourceDirs.map { it.path }, compiler.sourceSuffixes) + if (sourceFiles.size > 0) { + // TODO: createCompilerActionInfo recalculates the source files, only compute them + // once and pass them + val info = createCompilerActionInfo(project, context, compiler, isTest, + sourceDirectories, sourceSuffixes = compiler.sourceSuffixes) + val thisResult = invokeCompiler(project, context, compiler, info) + results.addAll(thisResult.successResults) + if (failedResult == null) { + failedResult = thisResult.failedResult + } + } else { + log(2, "Compiler $compiler not running on ${project.name} since no source files were found") + } + + return CompilerResult(results, failedResult) + } + + fun invokeCompiler(project: Project, context: KobaltContext, compiler: ICompiler, info: CompilerActionInfo) + : CompilerResult { + val results = arrayListOf() + var failedResult: TaskResult? = null + val thisResult = compiler.compile(project, context, info) + results.add(thisResult) + if (!thisResult.success && failedResult == null) { + failedResult = thisResult + } + return CompilerResult(results, failedResult) + } + + /** + * 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. + */ + fun createCompilerActionInfo(project: Project, context: KobaltContext, compiler: ICompiler, + isTest: Boolean, sourceDirectories: List, sourceSuffixes: List): CompilerActionInfo { + copyResources(project, context, SourceSet.of(isTest)) + + val fullClasspath = if (isTest) dependencyManager.testDependencies(project, context) + else dependencyManager.dependencies(project, context) + + // The directory where the classes get compiled + val buildDirectory = if (isTest) File(project.buildDirectory, KFiles.TEST_CLASSES_DIR) + else File(project.classesDir(context)) + File(project.directory, buildDirectory.path).mkdirs() + + // Remove all the excluded dependencies from the classpath + var classpath = fullClasspath.filter { + ! isDependencyExcluded(it, project.excludedDependencies) + } + + // The classpath needs to contain $buildDirectory/classes as well so that projects that contain + // multiple languages can use classes compiled by the compiler run before them. + if (buildDirectory.exists()) { + classpath += FileDependency(buildDirectory.path) + } + + val initialSourceDirectories = ArrayList(sourceDirectories) + // Source directories from the contributors + val contributedSourceDirs = + if (isTest) { + context.pluginInfo.testSourceDirContributors.flatMap { it.testSourceDirectoriesFor(project, context) } + } else { + context.pluginInfo.sourceDirContributors.flatMap { it.sourceDirectoriesFor(project, context) } + } + + initialSourceDirectories.addAll(contributedSourceDirs) + + // Transform them with the interceptors, if any + val allSourceDirectories = + if (isTest) { + initialSourceDirectories + } else { + context.pluginInfo.sourceDirectoriesInterceptors.fold(initialSourceDirectories.toList(), + { sd, interceptor -> interceptor.intercept(project, context, sd) }) + }.filter { + File(project.directory, it.path).exists() + }.filter { + ! KFiles.isResource(it.path) + }.distinct() + + // Now that we have all the source directories, find all the source files in them + val projectDirectory = File(project.directory) + val sourceFiles = if (compiler.canCompileDirectories) { + allSourceDirectories.map { File(projectDirectory, it.path).path } + } else { + files.findRecursively(projectDirectory, allSourceDirectories, + { file -> sourceSuffixes.any { file.endsWith(it) } }) + .map { File(projectDirectory, it).path } + } + + // Special treatment if we are compiling Kotlin files and the project also has a java source + // directory. In this case, also pass that java source directory to the Kotlin compiler as is + // so that it can parse its symbols + // Note: this should actually be queried on the compiler object so that this method, which + // is compiler agnostic, doesn't hardcode Kotlin specific stuff + val extraSourceFiles = arrayListOf() + if (sourceSuffixes.any { it.contains("kt")}) { + project.sourceDirectories.forEach { + val javaDir = KFiles.joinDir(project.directory, it) + if (File(javaDir).exists()) { + if (it.contains("java")) { + extraSourceFiles.add(javaDir) + // Add all the source directories contributed as potential Java directories too + // (except our own) + context.pluginInfo.sourceDirContributors +// .filter { it != this } + .forEach { + extraSourceFiles.addAll(it.sourceDirectoriesFor(project, context).map { it.path }) + } + + } + } + } + } + + val allSources = (sourceFiles + extraSourceFiles) + .map { File(it).canonicalPath } + .distinct() + .filter { File(it).exists() } + + // Finally, alter the info with the compiler interceptors before returning it + val initialActionInfo = CompilerActionInfo(projectDirectory.path, classpath, allSources, + sourceSuffixes, buildDirectory, emptyList() /* the flags will be provided by flag contributors */) + val result = context.pluginInfo.compilerInterceptors.fold(initialActionInfo, { ai, interceptor -> + interceptor.intercept(project, context, ai) + }) + return result + } + + /** + * Copy the resources from a source directory to the build one + */ + private fun copyResources(project: Project, context: KobaltContext, sourceSet: SourceSet) { + val outputDir = sourceSet.outputDir + + val variantSourceDirs = context.variant.resourceDirectories(project, sourceSet) + if (variantSourceDirs.size > 0) { + JvmCompilerPlugin.lp(project, "Copying $sourceSet resources") + val absOutputDir = File(KFiles.joinDir(project.directory, project.buildDirectory, outputDir)) + variantSourceDirs.map { File(project.directory, it.path) }.filter { + it.exists() + }.forEach { + log(2, "Copying from $it to $absOutputDir") + KFiles.copyRecursively(it, absOutputDir, deleteFirst = false) + } + } else { + JvmCompilerPlugin.lp(project, "No resources to copy for $sourceSet") + } + } + + + /** + * Naïve implementation: just exclude all dependencies that start with one of the excluded dependencies. + * Should probably make exclusion more generic (full on string) or allow exclusion to be specified + * formally by groupId or artifactId. + */ + private fun isDependencyExcluded(id: IClasspathDependency, excluded: List) + = excluded.any { id.id.startsWith(it.id) } + +} diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt index bc53592e..f69e7107 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt @@ -19,16 +19,17 @@ import java.nio.file.Files import java.nio.file.Paths import java.util.* -data class TaskInfo(val taskName: String, var inputChecksum: String? = null, var outputChecksum: String? = null) - -class BuildInfo(var tasks: List) - /** * Manage the file .kobalt/buildInfo.json, which keeps track of input and output checksums to manage * incremental builds. */ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileName : String) { + private data class TaskInfo(val taskName: String, var inputChecksum: String? = null, + var outputChecksum: String? = null) + + private class BuildInfo(var tasks: List) + interface IFactory { fun create(@Assisted fileName: String = IncrementalManager.BUILD_INFO_FILE) : IncrementalManager } @@ -79,6 +80,7 @@ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileN fun outputChecksumFor(taskName: String) : String? = taskInfoFor(taskInfos(), taskName).outputChecksum + /** * @param method is assumed to return an IncrementalTaskInfo. * @return a closure that invokes that method and decide whether to run the task or not based @@ -93,7 +95,8 @@ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileN var upToDate = false var taskOutputChecksum : String? = null - if (args.noIncremental || (Kobalt.context?.internalContext?.buildFileOutOfDate as Boolean)) { + if (! args.forceIncremental && + (args.noIncremental || (Kobalt.context?.internalContext?.buildFileOutOfDate as Boolean))) { // // If the user turned off incremental builds or if the build file was modified, always run this task // @@ -124,7 +127,8 @@ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileN if (outputChecksum == taskOutputChecksum) { upToDate = true } else { - logIncremental(LEVEL, "Incremental task $taskName output is out of date, running it") + logIncremental(LEVEL, "Incremental task $taskName output is out of date" + + " (different output checksums), running it") } } } else { @@ -132,7 +136,7 @@ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileN logIncremental(LEVEL, "Project ${project.name} depends on dirty project, running $taskName") } else { logIncremental(LEVEL, "Incremental task $taskName input is out of date, running it" - + " old: $inputChecksum new: ${iti.inputChecksum()}") + + " (different input checksums old: $inputChecksum new: ${iti.inputChecksum()})") } project.projectExtra.isDirty = true } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt index 41de8408..7dbde4c4 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/JvmCompilerPlugin.kt @@ -30,7 +30,8 @@ open class JvmCompilerPlugin @Inject constructor( open val files: KFiles, open val dependencyManager: DependencyManager, open val executors: KobaltExecutors, - open val taskContributor : TaskContributor) + open val taskContributor : TaskContributor, + val compilerUtils: CompilerUtils) : BasePlugin(), ISourceDirectoryContributor, IProjectContributor, ITaskContributor by taskContributor { companion object { @@ -52,19 +53,19 @@ open class JvmCompilerPlugin @Inject constructor( const val GROUP_TEST = "test" const val GROUP_BUILD = "build" const val GROUP_DOCUMENTATION = "documentation" + + /** + * Log with a project. + */ + fun lp(project: Project, s: String) { + log(2, "${project.name}: $s") + } } override val name: String = PLUGIN_NAME override fun accept(project: Project) = true - /** - * 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) // cleanUpActors() @@ -112,41 +113,9 @@ open class JvmCompilerPlugin @Inject constructor( return TaskResult() } - /** - * Copy the resources from a source directory to the build one - */ - protected fun copyResources(project: Project, sourceSet: SourceSet) { - var outputDir = sourceSet.outputDir - - val variantSourceDirs = context.variant.resourceDirectories(project, sourceSet) - if (variantSourceDirs.size > 0) { - lp(project, "Copying $sourceSet resources") - val absOutputDir = File(KFiles.joinDir(project.directory, project.buildDirectory, outputDir)) - variantSourceDirs.map { File(project.directory, it.path) }.filter { - it.exists() - }.forEach { - log(2, "Copying from $it to $absOutputDir") - KFiles.copyRecursively(it, absOutputDir, deleteFirst = false) - } - } else { - lp(project, "No resources to copy for $sourceSet") - } - } - - protected fun compilerArgsFor(project: Project): List { - val result = project.projectProperties.get(COMPILER_ARGS) - if (result != null) { - @Suppress("UNCHECKED_CAST") - return result as List - } else { - return emptyList() - } - } - @IncrementalTask(name = TASK_COMPILE_TEST, description = "Compile the tests", group = GROUP_BUILD, dependsOn = arrayOf(TASK_COMPILE)) fun taskCompileTest(project: Project): IncrementalTaskInfo { - sourceTestDirectories.addAll(context.variant.sourceDirectories(project, context, SourceSet.of(isTest = true))) return IncrementalTaskInfo( inputChecksum = { Md5.toMd5Directories(context.testSourceDirectories(project).map { File(project.directory, it.path)}) @@ -159,12 +128,12 @@ open class JvmCompilerPlugin @Inject constructor( ) } + private fun sourceDirectories(project: Project, context: KobaltContext) + = context.variant.sourceDirectories(project, context, SourceSet.of(isTest = false)) + @IncrementalTask(name = JvmCompilerPlugin.TASK_COMPILE, description = "Compile the project", group = GROUP_BUILD, runAfter = arrayOf(TASK_CLEAN)) fun taskCompile(project: Project): IncrementalTaskInfo { - // Set up the source files now that we have the variant - sourceDirectories.addAll(context.variant.sourceDirectories(project, context, SourceSet.of(isTest = false))) - return IncrementalTaskInfo( inputChecksum = { Md5.toMd5Directories(context.sourceDirectories(project).map { File(project.directory, it.path) }) @@ -193,29 +162,32 @@ open class JvmCompilerPlugin @Inject constructor( throw KobaltException("Couldn't find any compiler for project ${project.name}") } else { val allCompilers = compilerContributors.flatMap { it.compilersFor(project, context)}.sorted() - allCompilers.forEach { compiler -> - val contributedSourceDirs = - if (isTest) { - context.testSourceDirectories(project) - } else { - context.sourceDirectories(project) - } - val sourceFiles = KFiles.findSourceFiles(project.directory, - contributedSourceDirs.map { it.path }, compiler.sourceSuffixes) - if (sourceFiles.size > 0) { - // TODO: createCompilerActionInfo recalculates the source files, only compute them - // once and pass them - val info = createCompilerActionInfo(project, context, compiler, isTest, sourceDirectories, - sourceSuffixes = compiler.sourceSuffixes) - val thisResult = compiler.compile(project, context, info) - results.add(thisResult) - if (!thisResult.success && failedResult == null) { - failedResult = thisResult - } - } else { - log(2, "Compiler $compiler not running on ${project.name} since no source files were found") + + /** + * Swap the Java and Kotlin compilers from the list. + */ + fun swapJavaAndKotlin(allCompilers: List): List { + val result = ArrayList(allCompilers) + var ik = -1 + var ij = -1 + allCompilers.withIndex().forEach { wi -> + if (wi.value.sourceSuffixes.contains("java")) ij = wi.index + if (wi.value.sourceSuffixes.contains("kt")) ik = wi.index } + Collections.swap(result, ik, ij) + return result } + + // If this project has a kapt{} directive, we want to run the Java compiler first + val hasKapt = project.projectProperties.get("kaptConfig") != null + val finalAllCompilers = if (hasKapt) swapJavaAndKotlin(allCompilers) else allCompilers + finalAllCompilers.forEach { compiler -> + val compilerResults = compilerUtils.invokeCompiler(project, context, compiler, + sourceDirectories(project, context), isTest) + results.addAll(compilerResults.successResults) + if (failedResult == null) failedResult = compilerResults.failedResult + } + return if (failedResult != null) failedResult!! else if (results.size > 0) results[0] else TaskResult(true) @@ -249,9 +221,9 @@ open class JvmCompilerPlugin @Inject constructor( var result: TaskResult? = null contributors.forEach { it.compilersFor(project, context).forEach { compiler -> - result = docGenerator.generateDoc(project, context, createCompilerActionInfo(project, context, - compiler, - isTest = false, sourceDirectories = sourceDirectories, + result = docGenerator.generateDoc(project, context, + compilerUtils.createCompilerActionInfo(project, context, compiler, + isTest = false, sourceDirectories = sourceDirectories(project, context), sourceSuffixes = compiler.sourceSuffixes)) } } @@ -262,110 +234,10 @@ open class JvmCompilerPlugin @Inject constructor( } } - /** - * Naïve implementation: just exclude all dependencies that start with one of the excluded dependencies. - * Should probably make exclusion more generic (full on string) or allow exclusion to be specified - * formally by groupId or artifactId. - */ - private fun isDependencyExcluded(id: IClasspathDependency, excluded: List) - = excluded.any { id.id.startsWith(it.id) } - - /** - * 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, compiler: ICompiler, - isTest: Boolean, sourceDirectories: List, sourceSuffixes: List): CompilerActionInfo { - copyResources(project, SourceSet.of(isTest)) - - val fullClasspath = if (isTest) dependencyManager.testDependencies(project, context) - else dependencyManager.dependencies(project, context) - - // Remove all the excluded dependencies from the classpath - val classpath = fullClasspath.filter { - ! isDependencyExcluded(it, project.excludedDependencies) - } - - val buildDirectory = if (isTest) File(project.buildDirectory, KFiles.TEST_CLASSES_DIR) - else File(project.classesDir(context)) - buildDirectory.mkdirs() - - - val initialSourceDirectories = ArrayList(sourceDirectories) - // Source directories from the contributors - val contributedSourceDirs = - if (isTest) { - context.pluginInfo.testSourceDirContributors.flatMap { it.testSourceDirectoriesFor(project, context) } - } else { - context.pluginInfo.sourceDirContributors.flatMap { it.sourceDirectoriesFor(project, context) } - } - - initialSourceDirectories.addAll(contributedSourceDirs) - - // Transform them with the interceptors, if any - val allSourceDirectories = - if (isTest) { - initialSourceDirectories - } else { - context.pluginInfo.sourceDirectoriesInterceptors.fold(initialSourceDirectories.toList(), - { sd, interceptor -> interceptor.intercept(project, context, sd) }) - }.filter { - File(project.directory, it.path).exists() - }.filter { - ! KFiles.isResource(it.path) - }.distinct() - - // Now that we have all the source directories, find all the source files in them - val projectDirectory = File(project.directory) - val sourceFiles = if (compiler.canCompileDirectories) { - allSourceDirectories.map { File(projectDirectory, it.path).path } - } else { - files.findRecursively(projectDirectory, allSourceDirectories, - { file -> sourceSuffixes.any { file.endsWith(it) } }) - .map { File(projectDirectory, it).path } - } - - // Special treatment if we are compiling Kotlin files and the project also has a java source - // directory. In this case, also pass that java source directory to the Kotlin compiler as is - // so that it can parse its symbols - // Note: this should actually be queried on the compiler object so that this method, which - // is compiler agnostic, doesn't hardcode Kotlin specific stuff - val extraSourceFiles = arrayListOf() - if (sourceSuffixes.any { it.contains("kt")}) { - project.sourceDirectories.forEach { - val javaDir = KFiles.joinDir(project.directory, it) - if (File(javaDir).exists()) { - if (it.contains("java")) { - extraSourceFiles.add(javaDir) - // Add all the source directories contributed as potential Java directories too - // (except our own) - context.pluginInfo.sourceDirContributors.filter { it != this }.forEach { - extraSourceFiles.addAll(it.sourceDirectoriesFor(project, context).map { it.path }) - } - - } - } - } - } - - val allSources = (sourceFiles + extraSourceFiles).distinct().filter { File(it).exists() } - - // Finally, alter the info with the compiler interceptors before returning it - val initialActionInfo = CompilerActionInfo(projectDirectory.path, classpath, allSources, - sourceSuffixes, buildDirectory, emptyList() /* the flags will be provided by flag contributors */) - val result = context.pluginInfo.compilerInterceptors.fold(initialActionInfo, { ai, interceptor -> - interceptor.intercept(project, context, ai) - }) - return result - } - - val sourceDirectories = arrayListOf() - val sourceTestDirectories = arrayListOf() - // ISourceDirectoryContributor override fun sourceDirectoriesFor(project: Project, context: KobaltContext) = if (accept(project)) { - sourceDirectories.toList() + sourceDirectories(project, context) } else { arrayListOf() } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltPluginXml.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltPluginXml.kt index 4df2409b..33c0176e 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltPluginXml.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltPluginXml.kt @@ -76,8 +76,8 @@ class PluginInfo(val xml: KobaltPluginXml, val pluginClassLoader: ClassLoader?, val repoContributors = arrayListOf() val compilerFlagContributors = arrayListOf() val compilerInterceptors = arrayListOf() - val sourceDirectoriesInterceptors = arrayListOf() - val buildDirectoryInterceptors = arrayListOf() + val sourceDirectoriesInterceptors = arrayListOf() + val buildDirectoryInterceptors = arrayListOf() val runnerContributors = arrayListOf() val testRunnerContributors = arrayListOf() val classpathInterceptors = arrayListOf() @@ -156,10 +156,22 @@ class PluginInfo(val xml: KobaltPluginXml, val pluginClassLoader: ClassLoader?, GuiceFactory() } - fun forName(className: String) = - if (pluginClassLoader != null) pluginClassLoader.loadClass(className) - else if (classLoader != null) classLoader.loadClass(className) - else Class.forName(className) + fun forName(className: String) : Class<*> { + fun loadClass(className: String, classLoader: ClassLoader?) : Class<*>? { + try { + return classLoader?.loadClass(className) + } catch(ex: ClassNotFoundException) { + return null + } + } + + val result = loadClass(className, classLoader) + ?: Class.forName(className) + ?: loadClass(className, pluginClassLoader) + ?: throw ClassNotFoundException(className) + + return result + } // // Populate pluginInfo with what was found in Kobalt's own kobalt-plugin.xml @@ -169,7 +181,7 @@ class PluginInfo(val xml: KobaltPluginXml, val pluginClassLoader: ClassLoader?, 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 IBuildDirectoryInterceptor) buildDirectoryInterceptors.add(this) if (this is IClasspathContributor) classpathContributors.add(this) if (this is IClasspathInterceptor) classpathInterceptors.add(this) if (this is ICompilerContributor) compilerContributors.add(this) @@ -182,7 +194,7 @@ class PluginInfo(val xml: KobaltPluginXml, val pluginClassLoader: ClassLoader?, 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 ISourceDirectoryInterceptor) sourceDirectoriesInterceptors.add(this) if (this is ITaskContributor) taskContributors.add(this) if (this is ITestRunnerContributor) testRunnerContributors.add(this) if (this is IMavenIdInterceptor) mavenIdInterceptors.add(this) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltSettingsXml.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltSettingsXml.kt index b803393e..fe9ffc41 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltSettingsXml.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltSettingsXml.kt @@ -27,7 +27,7 @@ class KobaltSettingsXml { var proxies: ProxiesXml? = null @XmlElement(name = "kobalt-compiler-version") @JvmField - var kobaltCompilerVersion: String = "1.0.0" + var kobaltCompilerVersion: String = "1.0.2" @XmlElement(name = "kobalt-compiler-repo") @JvmField var kobaltCompilerRepo: String? = null diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinJarFiles.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinJarFiles.kt new file mode 100644 index 00000000..3f29427a --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KotlinJarFiles.kt @@ -0,0 +1,21 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.maven.DependencyManager +import com.google.inject.Inject +import java.io.File + +/** + * The jar files that Kotlin needs to run. + */ +class KotlinJarFiles @Inject constructor(val dependencyManager: DependencyManager, + val settings: KobaltSettings){ + private fun getKotlinCompilerJar(name: String): File { + val id = "org.jetbrains.kotlin:kotlin-$name:${settings.kobaltCompilerVersion}" + val dep = dependencyManager.create(id) + return dep.jarFile.get().absoluteFile + } + + val stdlib: File get() = getKotlinCompilerJar("stdlib") + val runtime: File get() = getKotlinCompilerJar("runtime") + val compiler: File get() = getKotlinCompilerJar("compiler-embeddable") +} diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt index 2039c9e1..6d8e478c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt @@ -4,10 +4,7 @@ import com.beust.kobalt.* import com.beust.kobalt.api.* import com.beust.kobalt.api.annotation.IncrementalTask import com.beust.kobalt.api.annotation.Task -import com.beust.kobalt.misc.Strings -import com.beust.kobalt.misc.benchmarkMillis -import com.beust.kobalt.misc.kobaltError -import com.beust.kobalt.misc.log +import com.beust.kobalt.misc.* import com.google.common.annotations.VisibleForTesting import com.google.common.collect.ArrayListMultimap import com.google.common.collect.ListMultimap @@ -81,7 +78,41 @@ class TaskManager @Inject constructor(val args: Args, } } - fun runTargets(taskNames: List, projects: List): RunTargetResult { + fun runTargets(passedTaskNames: List, allProjects: List): RunTargetResult { + val taskInfos = calculateDependentTaskNames(passedTaskNames, allProjects) + val projectsToRun = findProjectsToRun(taskInfos, allProjects) + return runProjects(taskInfos, projectsToRun) + } + + /** + * Determine which projects to run based on the request tasks. Also make sure that all the requested projects + * exist. + */ + fun findProjectsToRun(taskInfos: List, projects: List) : List { + + // Validate projects + val result = arrayListOf() + val projectMap = HashMap().apply { + projects.forEach { put(it.name, it)} + } + + // Extract all the projects we need to run from the tasks +// val orderedTaskInfos = calculateDependentTaskNames(taskInfos.map { it.id }, projects) + taskInfos.forEach { + val p = it.project + if (p != null) { + if (! projectMap.containsKey(p)) { + throw KobaltException("Unknown project: ${it.project}") + } + result.add(projectMap[it.project]!!) + } + } + + // If at least one task didn't specify a project, run them all + return if (result.any()) result else projects + } + + private fun runProjects(taskInfos: List, projects: List) : RunTargetResult { var result = 0 val failedProjects = hashSetOf() val messages = Collections.synchronizedList(arrayListOf()) @@ -111,7 +142,7 @@ class TaskManager @Inject constructor(val args: Args, log(3, " $it: " + tasksByNames.get(it)) } - val graph = createGraph2(project.name, taskNames, tasksByNames, + val graph = createGraph2(project.name, taskInfos, tasksByNames, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, { task: ITask -> task.name }, { task: ITask -> task.plugin.accept(project) }) @@ -137,9 +168,65 @@ class TaskManager @Inject constructor(val args: Args, } } } + return RunTargetResult(result, messages) } + /** + * If the user wants to run a single task on a single project (e.g. "kobalt:assemble"), we need to + * see if that project depends on others and if it does, invoke these tasks on all of them. This + * function returns all these task names (including dependent). + */ + fun calculateDependentTaskNames(taskNames: List, projects: List): List { + val projectMap = hashMapOf().apply { + projects.forEach { project -> put(project.name, project)} + } + + val allTaskInfos = HashSet(taskNames.map { TaskInfo(it) }) + with(Topological()) { + val toProcess = ArrayList(allTaskInfos) + val seen = HashSet(allTaskInfos) + val newTasks = hashSetOf() + + fun maybeAdd(taskInfo: TaskInfo) { + if (!seen.contains(taskInfo)) { + newTasks.add(taskInfo) + seen.add(taskInfo) + } + } + + while (toProcess.any()) { + toProcess.forEach { ti -> + val project = projectMap[ti.project] + if (project != null) { + val dependents = project.projectExtra.dependsOn + if (dependents.any()) { + dependents.forEach { depProject -> + val tiDep = TaskInfo(depProject.name, ti.taskName) + allTaskInfos.add(tiDep) + addEdge(ti, tiDep) + maybeAdd(tiDep) + } + } else { + allTaskInfos.add(ti) + addNode(ti) + } + } else { + // No project specified for this task, run that task in all the projects + projects.forEach { + maybeAdd(TaskInfo(it.name, ti.taskName)) + } + } + } + toProcess.clear() + toProcess.addAll(newTasks) + newTasks.clear() + } + val result = sort() + return result + } + } + val LOG_LEVEL = 3 /** @@ -150,7 +237,8 @@ class TaskManager @Inject constructor(val args: Args, * we'll be adding to the graph while @toName extracts the name of a node. */ @VisibleForTesting - fun createGraph2(projectName: String, taskNames: List, nodeMap: Multimap, + fun createGraph2(projectName: String, passedTasks: List, + nodeMap: Multimap, dependsOn: Multimap, reverseDependsOn: Multimap, runBefore: Multimap, @@ -176,9 +264,9 @@ class TaskManager @Inject constructor(val args: Args, } // - // Turn the task names into the more useful TaskInfo and do some sanity checking on the way + // Keep only the tasks we need to run. // - val taskInfos = taskNames.map { TaskInfo(it) }.filter { + val taskInfos = passedTasks.filter { if (!nodeMap.keys().contains(it.taskName)) { throw KobaltException("Unknown task: $it") } @@ -262,14 +350,16 @@ class TaskManager @Inject constructor(val args: Args, // runBefore and runAfter (task ordering) are only considered for explicit tasks (tasks that were // explicitly requested by the user) // - runBefore[taskName].forEach { from -> - if (taskNames.contains(from)) { - addEdge(result, from, taskName, newToProcess, "runBefore") + passedTasks.map { it.id }.let { taskNames -> + runBefore[taskName].forEach { from -> + if (taskNames.contains(from)) { + addEdge(result, from, taskName, newToProcess, "runBefore") + } } - } - runAfter[taskName].forEach { to -> - if (taskNames.contains(to)) { - addEdge(result, taskName, to, newToProcess, "runAfter") + runAfter[taskName].forEach { to -> + if (taskNames.contains(to)) { + addEdge(result, taskName, to, newToProcess, "runAfter") + } } } seen.add(taskName) @@ -339,16 +429,14 @@ class TaskManager @Inject constructor(val args: Args, */ fun computePluginTasks(projects: List) { installAnnotationTasks(projects) - installDynamicTasks(projects) + installDynamicTasks() } - private fun installDynamicTasks(projects: List) { + private fun installDynamicTasks() { dynamicTasks.forEach { task -> - projects.filter { task.plugin.accept(it) }.forEach { project -> - addTask(task.plugin, project, task.name, task.doc, task.group, - task.dependsOn, task.reverseDependsOn, task.runBefore, task.runAfter, task.alwaysRunAfter, - task.closure) - } + addTask(task.plugin, task.project, task.name, task.doc, task.group, + task.dependsOn, task.reverseDependsOn, task.runBefore, task.runAfter, task.alwaysRunAfter, + task.closure) } } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt index 93d997ed..93d4c705 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/DependencyManager.kt @@ -85,14 +85,13 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * allDependencies is typically either compileDependencies or testDependencies */ override fun calculateDependencies(project: Project?, context: KobaltContext, - dependentProjects: List, vararg allDependencies: List): List { val result = arrayListOf() allDependencies.forEach { dependencies -> result.addAll(transitiveClosure(dependencies)) } result.addAll(runClasspathContributors(project, context)) - result.addAll(dependentProjectDependencies(dependentProjects, project, context)) + result.addAll(dependentProjectDependencies(project, context)) return result } @@ -112,9 +111,9 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val */ fun transitiveClosure(dependencies : List, indent : String = " "): List { - var executor = executors.newExecutor("JvmCompiler}", 10) + val executor = executors.newExecutor("JvmCompiler}", 10) - var result = hashSetOf() + val result = hashSetOf() dependencies.forEach { projectDependency -> log(ConsoleRepositoryListener.LOG_LEVEL, "$indent Resolving $projectDependency") @@ -169,27 +168,25 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val * If this project depends on other projects, we need to include their jar file and also * their own dependencies */ - private fun dependentProjectDependencies(projectDescriptions: List, + private fun dependentProjectDependencies( project: Project?, context: KobaltContext) : List { - val result = arrayListOf() - projectDescriptions.filter { - it.project.name == project?.name - }.forEach { pd -> - pd.dependsOn.forEach { p -> + if (project == null) { + return emptyList() + } else { + val result = arrayListOf() + project.projectExtra.dependsOn.forEach { p -> result.add(FileDependency(KFiles.joinDir(p.directory, p.classesDir(context)))) - val otherDependencies = calculateDependencies(p, context, projectDescriptions, - p.compileDependencies) + val otherDependencies = calculateDependencies(p, context, p.compileDependencies) result.addAll(otherDependencies) - } - } - return result + } + return result + } } private fun dependencies(project: Project, context: KobaltContext, isTest: Boolean) : List { val transitive = hashSetOf() - val projects = listOf(ProjectDescription(project, project.projectExtra.dependsOn)) with(project) { val deps = arrayListOf(compileDependencies, compileProvidedDependencies, context.variant.buildType.compileDependencies, @@ -202,15 +199,20 @@ class DependencyManager @Inject constructor(val executors: KobaltExecutors, val deps.add(testProvidedDependencies) } deps.filter { it.any() }.forEach { - transitive.addAll(calculateDependencies(project, context, projects, it)) + transitive.addAll(calculateDependencies(project, context, it)) } } // Make sure that classes/ and test-classes/ are always at the top of this classpath, // so that older versions of that project on the classpath don't shadow them - val result = listOf(FileDependency(KFiles.makeOutputDir(project).absolutePath), - FileDependency(KFiles.makeOutputTestDir(project).absolutePath)) + - reorderDependencies(transitive) + val result = arrayListOf().apply { + if (isTest) { + add(FileDependency(KFiles.makeOutputDir(project).absolutePath)) + add(FileDependency(KFiles.makeOutputTestDir(project).absolutePath)) + } + addAll(reorderDependencies(transitive)) + } + return result } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Md5.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Md5.kt index 339efaa1..6ed088a4 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Md5.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Md5.kt @@ -18,12 +18,18 @@ public class Md5 { // return DatatypeConverter.printHexBinary(md5.digest()).toLowerCase() // } - fun toMd5Directories(directories: List) : String? { - val ds = directories.filter { it.exists() } + /** + * Calculate a checksum for all the files/directories. The conversion from File to + * bytes can be customized by the @param{toBytes} parameter. The default implementation calculates + * a checksum of the last modified timestamp. + */ + fun toMd5Directories(filesOrDirectories: List, + toBytes: (File) -> ByteArray = { it.lastModified().toString().toByteArray() } ): String? { + val ds = filesOrDirectories.filter { it.exists() } if (ds.size > 0) { MessageDigest.getInstance("MD5").let { md5 -> var fileCount = 0 - directories.filter { it.exists() }.forEach { file -> + filesOrDirectories.filter { it.exists() }.forEach { file -> if (file.isFile) { val bytes = file.readBytes() md5.update(bytes, 0, bytes.size) @@ -37,7 +43,7 @@ public class Md5 { it.isFile }.forEach { fileCount++ - val bytes = it.readBytes() + val bytes = toBytes(it) md5.update(bytes, 0, bytes.size) } } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Pom.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Pom.kt index 48f6965e..a6263e22 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Pom.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/maven/Pom.kt @@ -3,11 +3,9 @@ package com.beust.kobalt.maven import com.beust.kobalt.misc.toString import com.beust.kobalt.misc.warn 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.parsers.DocumentBuilderFactory import javax.xml.xpath.XPathConstants class Pom @javax.inject.Inject constructor(@Assisted val id: String, @@ -66,8 +64,8 @@ class Pom @javax.inject.Inject constructor(@Assisted val id: String, init { val DEPENDENCIES = XPATH.compile("/project/dependencies/dependency") + val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(documentFile) - 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) @@ -75,11 +73,12 @@ class Pom @javax.inject.Inject constructor(@Assisted val id: String, 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 }) + repositories = childElements(repoElem).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 { + childElements(propsElem).forEach { properties.put(it.nodeName, it.textContent) } @@ -111,5 +110,18 @@ class Pom @javax.inject.Inject constructor(@Assisted val id: String, } } + private fun childElements(repoElem: Element?): List { + val result = arrayListOf() + if (repoElem != null) { + for (i in 0..repoElem.childNodes.length - 1) { + val elem = repoElem.childNodes.item(i) + if (elem is Element) { + result.add(elem) + } + } + } + return result + } + override fun toString() = toString("Pom", "id", id) } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Io.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Io.kt index 6f8694a9..a354043e 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Io.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Io.kt @@ -73,7 +73,7 @@ class Io(val dryRun: Boolean = false) { } fun mkdir(dir: File) { - log("rm -rf $dir") + log("mkdir $dir") if (! dryRun) { dir.mkdirs() } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt index 90b8aacc..a17c2196 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/JarUtils.kt @@ -9,7 +9,6 @@ import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarInputStream import java.util.zip.ZipEntry -import java.util.zip.ZipException import java.util.zip.ZipFile import java.util.zip.ZipOutputStream @@ -48,27 +47,14 @@ public class JarUtils { } if (foundFile.isDirectory) { - log(2, "Writing contents of directory $foundFile") + log(2, " Writing contents of directory $foundFile") // Directory - var name = foundFile.name - if (!name.isEmpty()) { - if (!name.endsWith("/")) name += "/" - val entry = JarEntry(name) - entry.time = foundFile.lastModified() - try { - outputStream.putNextEntry(entry) - } catch(ex: ZipException) { - log(2, "Can't add $name: ${ex.message}") - } finally { - outputStream.closeEntry() - } - } - val includedFile = IncludedFile(From(foundFile.path), To(""), listOf(IFileSpec.GlobSpec("**"))) - addSingleFile(".", includedFile, outputStream, expandJarFiles) + val includedFile = IncludedFile(From(""), To(""), listOf(IFileSpec.GlobSpec("**"))) + addSingleFile(localFile.path, includedFile, outputStream, expandJarFiles) } else { if (file.expandJarFiles && foundFile.name.endsWith(".jar") && ! file.from.contains("resources")) { - log(2, "Writing contents of jar file $foundFile") + log(2, " Writing contents of jar file $foundFile") val stream = JarInputStream(FileInputStream(localFile)) var entry = stream.nextEntry while (entry != null) { @@ -81,7 +67,7 @@ public class JarUtils { } else { val entryFileName = file.to(foundFile.path).path.replace("\\", "/") val entry = JarEntry(entryFileName) - entry.time = foundFile.lastModified() + entry.time = localFile.lastModified() addEntry(FileInputStream(localFile), entry, outputStream, onError) } } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Topological.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Topological.kt index b0a92315..38c4c503 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Topological.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/Topological.kt @@ -10,22 +10,25 @@ import java.util.* */ class Topological { private val dependingOn = ArrayListMultimap.create() + private val nodes = hashSetOf() + + fun addNode(t: T) = nodes.add(t) fun addEdge(t: T, other: T) { + addNode(t) + addNode(other) dependingOn.put(t, other) } - fun addEdge(t: T, others: Array) { - dependingOn.putAll(t, others.toMutableList()) - } - /** * @return the Ts sorted topologically. */ - fun sort(all: ArrayList) : List { + fun sort() : List { + val all = ArrayList(nodes) val result = arrayListOf() var dependMap = HashMultimap.create() dependingOn.keySet().forEach { dependMap.putAll(it, dependingOn.get(it))} + nodes.forEach { dependMap.putAll(it, emptyList())} while (all.size > 0) { val freeNodes = all.filter { dependMap.get(it).isEmpty() diff --git a/src/main/kotlin/com/beust/kobalt/Main.kt b/src/main/kotlin/com/beust/kobalt/Main.kt index 576ec211..6222b686 100644 --- a/src/main/kotlin/com/beust/kobalt/Main.kt +++ b/src/main/kotlin/com/beust/kobalt/Main.kt @@ -115,7 +115,7 @@ private class Main @Inject constructor( val seconds = benchmarkSeconds { try { result = runWithArgs(jc, args, argv, pluginClassLoader) - } catch(ex: KobaltException) { + } catch(ex: Throwable) { error("", ex.cause ?: ex) result = 1 } diff --git a/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt b/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt index fbeaef40..a077a9e6 100644 --- a/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt +++ b/src/main/kotlin/com/beust/kobalt/app/BuildScriptUtil.kt @@ -20,7 +20,6 @@ import java.io.InputStream import java.lang.reflect.Modifier import java.net.URL import java.net.URLClassLoader -import java.util.* import java.util.jar.JarInputStream class BuildScriptUtil @Inject constructor(val plugins: Plugins, val files: KFiles, @@ -113,14 +112,14 @@ class BuildScriptUtil @Inject constructor(val plugins: Plugins, val files: KFile context.pluginInfo.projectContributors.forEach { contributor -> val descriptions = contributor.projects() descriptions.forEach { pd -> - all.add(pd.project) + topologicalProjects.addNode(pd.project) pd.dependsOn.forEach { dependsOn -> topologicalProjects.addEdge(pd.project, dependsOn) all.add(dependsOn) } } } - val result = topologicalProjects.sort(ArrayList(all)) + val result = topologicalProjects.sort() return result } diff --git a/src/main/kotlin/com/beust/kobalt/app/LanguageTemplateGenerator.kt b/src/main/kotlin/com/beust/kobalt/app/LanguageTemplateGenerator.kt index a47a4d1a..403c8f5c 100644 --- a/src/main/kotlin/com/beust/kobalt/app/LanguageTemplateGenerator.kt +++ b/src/main/kotlin/com/beust/kobalt/app/LanguageTemplateGenerator.kt @@ -3,7 +3,6 @@ package com.beust.kobalt.app import com.beust.kobalt.Args import com.beust.kobalt.api.ITemplate import com.beust.kobalt.api.ITemplateContributor -import com.beust.kobalt.app.Mustache import com.beust.kobalt.maven.Pom import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.log @@ -85,7 +84,7 @@ abstract class LanguageTemplateGenerator : ITemplate { private fun importPom(pomFile: File, mainDeps: ArrayList, testDeps: ArrayList, map: HashMap) { - var pom = Pom("imported", pomFile.absoluteFile) + val pom = Pom("imported", pomFile.absoluteFile) with(map) { put("group", pom.groupId ?: PACKAGE_NAME) put("artifactId", pom.artifactId ?: PACKAGE_NAME) diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt b/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt index 7a4f4eec..7f612aa9 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt @@ -36,6 +36,8 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep val pluginDependencies = projectResult.pluginUrls.map { File(it.toURI()) }.map { FileDependency(it.absolutePath) } + + val allTasks = hashSetOf() projectResult.projects.forEach { project -> val compileDependencies = pluginDependencies.map { toDependencyData(it, "compile") } + allDeps(project.compileDependencies).map { toDependencyData(it, "compile") } + @@ -55,15 +57,16 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep // Separate resource from source directories val sources = project.sourceDirectories.partition { KFiles.isResource(it) } val tests = project.sourceDirectoriesTest.partition { KFiles.isResource(it) } - val allTasks = taskManager.tasksByNames(project).values().map { + val projectTasks = taskManager.tasksByNames(project).values().map { TaskData(it.name, it.doc, it.group) } + allTasks.addAll(projectTasks) projectDatas.add(ProjectData(project.name, project.directory, dependentProjects, compileDependencies, testDependencies, sources.second.toSet(), tests.second.toSet(), sources.first.toSet(), tests.first.toSet(), - allTasks)) + projectTasks)) } - return GetDependenciesData(projectDatas, projectResult.taskResult.errorMessage) + return GetDependenciesData(projectDatas, allTasks, projectResult.taskResult.errorMessage) } ///// @@ -72,7 +75,9 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep // class DependencyData(val id: String, val scope: String, val path: String) - class TaskData(val name: String, val description: String, val group: String) + data class TaskData(val name: String, val description: String, val group: String) { + override fun toString() = name + } class ProjectData(val name: String, val directory: String, val dependentProjects: List, @@ -81,5 +86,7 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep val sourceResourceDirs: Set, val testResourceDirs: Set, val tasks: Collection) - class GetDependenciesData(val projects: List, val errorMessage: String?) + class GetDependenciesData(val projects: List = emptyList(), + val allTasks: Collection = emptySet(), + val errorMessage: String?) } diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt index 2b0cb10b..237c0ccb 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt @@ -17,6 +17,7 @@ import okhttp3.OkHttpClient import retrofit2.Call import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Query import java.io.* @@ -31,7 +32,10 @@ fun main(argv: Array) { } interface Api { - @POST("/getDependencies") + @GET("/ping") + fun ping() : Call + + @POST("/v0/getDependencies") fun getDependencies(@Query("buildFile") buildFile: String) : Call> } @@ -40,16 +44,25 @@ class KobaltClient : Runnable { private val service = Retrofit.Builder() .client(OkHttpClient.Builder().build()) - .baseUrl("http://localhost:1252") + .baseUrl("http://localhost:1238") .addConverterFactory(GsonConverterFactory.create()) .build() .create(Api::class.java) override fun run() { + +// val pong = service.ping().execute() +// println("Result from ping: " + pong) + val buildFile = Paths.get(SystemProperties.homeDir, "kotlin/klaxon/kobalt/src/Build.kt").toString() val dependencies = service.getDependencies(buildFile) - val results = dependencies.execute() - println("Dependencies: $results") + val response = dependencies.execute() + if (response.isSuccessful) { + println("Dependencies: $response") + } else { + println("Error calling getDependencies: " + response.errorBody().string()) + } + println("") } } diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/SparkServer.kt b/src/main/kotlin/com/beust/kobalt/app/remote/SparkServer.kt index 71e34391..7913852b 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/SparkServer.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/SparkServer.kt @@ -36,7 +36,7 @@ class SparkServer(val initCallback: (String) -> List, val cleanUpCallba override fun run(port: Int) { Spark.port(port) - Spark.get("/ping", { req, res -> KobaltServer.OK }) + Spark.get("/ping", { req, res -> """ { "result" : "ok" } """ }) Spark.get("/quit", { req, res -> Executors.newFixedThreadPool(1).let { executor -> executor.submit { @@ -58,13 +58,13 @@ class SparkServer(val initCallback: (String) -> List, val cleanUpCallba dependencyData.dependenciesDataFor(buildFile, args) } catch(ex: Exception) { - DependencyData.GetDependenciesData(emptyList(), ex.message) + DependencyData.GetDependenciesData(errorMessage = ex.message) } finally { cleanUpCallback() } } else { - DependencyData.GetDependenciesData(emptyList(), - "buildFile wasn't passed in the query parameter") + DependencyData.GetDependenciesData( + errorMessage = "buildFile wasn't passed in the query parameter") } cleanUpCallback() result diff --git a/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt index 3b871016..483a9f3f 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/application/ApplicationPlugin.kt @@ -7,12 +7,11 @@ import com.beust.kobalt.api.annotation.Task import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.Jar import com.beust.kobalt.internal.ActorUtils -import com.beust.kobalt.internal.JvmCompilerPlugin import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.KobaltExecutors import com.beust.kobalt.misc.RunCommand -import com.beust.kobalt.misc.warn +import com.beust.kobalt.misc.log import com.beust.kobalt.plugin.packaging.PackageConfig import com.beust.kobalt.plugin.packaging.PackagingPlugin import com.google.inject.Inject @@ -60,7 +59,7 @@ class ApplicationPlugin @Inject constructor(val configActor: ConfigActor 0) { return runContributor.run(project, context, dependencyManager.dependencies(project, context)) } else { - warn("Couldn't find a runner for project ${project.name}. Please make sure your build file contains " + + log(2, "Couldn't find a runner for project ${project.name}. Please make sure your build file contains " + "an application{} directive with a mainClass=... in it") return TaskResult() } @@ -106,12 +105,10 @@ class ApplicationPlugin @Inject constructor(val configActor: ConfigActor // If the jar file is not fat, we need to add the transitive closure of all dependencies // on the classpath val allTheDependencies = - dependencyManager.calculateDependencies(project, context, projDeps, + dependencyManager.calculateDependencies(project, context, allDependencies = project.compileDependencies).map { it.jarFile.get().path } allDeps.addAll(allTheDependencies) } @@ -130,6 +127,6 @@ class ApplicationPlugin @Inject constructor(val configActor: ConfigActor = taskContributor.dynamicTasks + override fun tasksFor(project: Project, context: KobaltContext): List = taskContributor.dynamicTasks } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt index 4c7f5e7c..0aede4b9 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt @@ -2,12 +2,16 @@ package com.beust.kobalt.plugin.apt import com.beust.kobalt.api.* import com.beust.kobalt.api.annotation.Directive +import com.beust.kobalt.internal.ActorUtils +import com.beust.kobalt.internal.CompilerUtils import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.log import com.google.common.collect.ArrayListMultimap import com.google.inject.Inject import java.io.File +import java.nio.file.Files +import java.util.* import javax.inject.Singleton /** @@ -18,30 +22,82 @@ import javax.inject.Singleton * (outputDir, etc...). */ @Singleton -class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, val configActor: ConfigActor) - : BasePlugin(), ICompilerFlagContributor, ISourceDirectoryContributor, IConfigActor by configActor { +class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, val compilerUtils: CompilerUtils) + : BasePlugin(), ICompilerFlagContributor, ISourceDirectoryContributor, ITaskContributor { // ISourceDirectoryContributor override fun sourceDirectoriesFor(project: Project, context: KobaltContext): List { - val config = configurationFor(project) - val result = - if (config != null) { - listOf(File( - KFiles.joinDir(KFiles.KOBALT_BUILD_DIR, config.outputDir, context.variant.toIntermediateDir()))) - } else { - emptyList() - } + val result = arrayListOf() + aptConfigs[project.name]?.let { config -> + result.add(File( + KFiles.joinDir(project.directory, + KFiles.KOBALT_BUILD_DIR, + config.outputDir))) + } + + kaptConfigs[project.name]?.let { config -> + result.add(File( + KFiles.joinDir(project.directory, + KFiles.KOBALT_BUILD_DIR, + config.outputDir))) + } return result } companion object { const val PLUGIN_NAME = "Apt" + const val KAPT_CONFIG = "kaptConfig" + const val APT_CONFIG = "aptConfig" } override val name = PLUGIN_NAME + override fun apply(project: Project, context: KobaltContext) { + } + + // ITaskContributor + override fun tasksFor(project: Project, context: KobaltContext) : List { +// val kapt = kaptConfigs[project.name] +// if (kapt != null) { +// return listOf(DynamicTask(this, "kapt", "Run kapt", project = project, +// reverseDependsOn = listOf(JvmCompilerPlugin.TASK_COMPILE), +// group = AnnotationDefault.GROUP, +// closure = { project -> +// runApt(project, context) +// TaskResult() +// })) +// } else { + return emptyList() +// } +// + } + + private fun runApt(project: Project, context: KobaltContext) { + val kapt = kaptConfigs[project.name] + if (kapt != null) { + + val sourceDir = Files.createTempDirectory("kobalt").toFile() + val javaFile = File(sourceDir, "A.java").apply { + appendText("class A {}") + } + val compilerContributors = context.pluginInfo.compilerContributors + ActorUtils.selectAffinityActors(project, context, + context.pluginInfo.compilerContributors) + + val allCompilers = compilerContributors.flatMap { it.compilersFor(project, context) }.sorted() + val javaCompiler = allCompilers.filter { it.sourceSuffixes.contains("java") }[0] + + val dependencies = dependencyManager.calculateDependencies(project, context) + val info = CompilerActionInfo(sourceDir.absolutePath, dependencies, + listOf(javaFile.absolutePath), listOf("java"), File(sourceDir, "generated"), + listOf()) + + val results = compilerUtils.invokeCompiler(project, context, javaCompiler, info) + } + } + private fun generated(project: Project, context: KobaltContext, outputDir: String) = KFiles.joinAndMakeDir(project.directory, project.buildDirectory, outputDir, context.variant.toIntermediateDir()) @@ -52,20 +108,23 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va if (!suffixesBeingCompiled.contains("java")) return emptyList() val result = arrayListOf() - configurationFor(project)?.let { config -> + + fun addFlags(outputDir: String) { aptDependencies[project.name]?.let { aptDependencies -> - val deps = aptDependencies.map { dependencyManager.create(it) } - - val dependencies = context.dependencyManager.calculateDependencies(null, context, emptyList(), deps) - .map { it.jarFile.get().path } - - result.add("-processorpath") - result.add((dependencies).joinToString(":")) result.add("-s") - result.add(generated(project, context, config.outputDir)) + result.add(generated(project, context, outputDir)) } - log(2, "New flags from apt: " + result.joinToString(" ")) } + + aptConfigs[project.name]?.let { config -> + addFlags(config.outputDir) + } + + kaptConfigs[project.name]?.let { config -> + addFlags(config.outputDir) + } + + log(2, "New flags from apt: " + result.joinToString(" ")) return result } @@ -74,15 +133,28 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va fun addAptDependency(dependencies: Dependencies, it: String) { aptDependencies.put(dependencies.project.name, it) } + + private val aptConfigs: HashMap = hashMapOf() + private val kaptConfigs: HashMap = hashMapOf() + + fun addAptConfig(project: Project, kapt: AptConfig) { + project.projectProperties.put(APT_CONFIG, kapt) + aptConfigs.put(project.name, kapt) + } + + fun addKaptConfig(project: Project, kapt: KaptConfig) { + project.projectProperties.put(KAPT_CONFIG, kapt) + kaptConfigs.put(project.name, kapt) + } } class AptConfig(var outputDir: String = "generated/source/apt") @Directive -public fun Project.apt(init: AptConfig.() -> Unit) { +fun Project.apt(init: AptConfig.() -> Unit) { AptConfig().let { it.init() - (Kobalt.findPlugin(AptPlugin.PLUGIN_NAME) as AptPlugin).addConfiguration(this, it) + (Kobalt.findPlugin(AptPlugin.PLUGIN_NAME) as AptPlugin).addAptConfig(this, it) } } @@ -92,3 +164,13 @@ fun Dependencies.apt(vararg dep: String) { (Kobalt.findPlugin(AptPlugin.PLUGIN_NAME) as AptPlugin).addAptDependency(this, it) } } + +class KaptConfig(var outputDir: String = "generated/source/apt") + +@Directive +fun Project.kapt(init: KaptConfig.() -> Unit) { + KaptConfig().let { + it.init() + (Kobalt.findPlugin(AptPlugin.PLUGIN_NAME) as AptPlugin).addKaptConfig(this, it) + } +} diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt index 0bbca05f..cad94b0f 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt @@ -5,6 +5,7 @@ import com.beust.kobalt.api.* import com.beust.kobalt.internal.ICompilerAction import com.beust.kobalt.internal.JvmCompiler import com.beust.kobalt.internal.KobaltSettings +import com.beust.kobalt.internal.KotlinJarFiles import com.beust.kobalt.kotlin.ParentLastClassLoader import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.dependency.FileDependency @@ -34,7 +35,8 @@ class KotlinCompiler @Inject constructor( val dependencyManager: DependencyManager, val executors: KobaltExecutors, val settings: KobaltSettings, - val jvmCompiler: JvmCompiler) { + val jvmCompiler: JvmCompiler, + val kotlinJarFiles: KotlinJarFiles) { val compilerAction = object: ICompilerAction { override fun compile(projectName: String?, info: CompilerActionInfo): TaskResult { @@ -88,29 +90,30 @@ class KotlinCompiler @Inject constructor( log(2, "Calling kotlinc " + allArgs.joinToString(" ")) val result : TaskResult = if (true) { - val classLoader = ParentLastClassLoader(cp.map { it.toURI().toURL() }) - val compiler = classLoader.loadClass("org.jetbrains.kotlin.cli.common.CLICompiler") - val compilerMain = compiler.declaredMethods.filter { - it.name == "doMainNoExit" && it.parameterTypes.size == 2 - }[0] - val kCompiler = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") - // // In order to capture the error stream, I need to invoke CLICompiler.exec(), which // is the first method that accepts a PrintStream for the errors in parameter // - val baos = ByteArrayOutputStream() - val ps = PrintStream(baos) - val execMethod = compiler.declaredMethods.filter { - it.name == "exec" && it.parameterTypes.size == 2 - }[0] - val exitCode = execMethod.invoke(kCompiler.newInstance(), ps, allArgs.toTypedArray()) - val errorString = baos.toString(Charset.defaultCharset().toString()) + ByteArrayOutputStream().use { baos -> + val compilerJar = listOf(kotlinJarFiles.compiler.toURI().toURL()) - // The return value is an enum - val nameMethod = exitCode.javaClass.getMethod("name") - val success = "OK" == nameMethod.invoke(exitCode).toString() - TaskResult(success, errorString) + val classLoader = ParentLastClassLoader(compilerJar) + val compiler = classLoader.loadClass("org.jetbrains.kotlin.cli.common.CLICompiler") + val kCompiler = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") + + PrintStream(baos).use { ps -> + val execMethod = compiler.declaredMethods.filter { + it.name == "exec" && it.parameterTypes.size == 2 + }[0] + val exitCode = execMethod.invoke(kCompiler.newInstance(), ps, allArgs.toTypedArray()) + val errorString = baos.toString(Charset.defaultCharset().toString()) + + // The return value is an enum + val nameMethod = exitCode.javaClass.getMethod("name") + val success = "OK" == nameMethod.invoke(exitCode).toString() + TaskResult(success, errorString) + } + } } else { val exitCode = CLICompiler.doMainNoExit(K2JVMCompiler(), allArgs.toTypedArray()) TaskResult(exitCode == ExitCode.OK) @@ -161,7 +164,7 @@ class KotlinCompiler @Inject constructor( } class KConfiguration @Inject constructor(val compiler: KotlinCompiler){ - val classpath = arrayListOf() + private val classpath = arrayListOf() val dependencies = arrayListOf() var source = arrayListOf() var output: File by Delegates.notNull() diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt index 4d210fa4..c0d975f3 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinPlugin.kt @@ -7,6 +7,7 @@ import com.beust.kobalt.api.annotation.Directive import com.beust.kobalt.internal.BaseJvmPlugin import com.beust.kobalt.internal.JvmCompilerPlugin import com.beust.kobalt.internal.KobaltSettings +import com.beust.kobalt.internal.KotlinJarFiles import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.dependency.FileDependency import com.beust.kobalt.misc.KFiles @@ -19,7 +20,8 @@ import javax.inject.Singleton @Singleton class KotlinPlugin @Inject constructor(val executors: KobaltExecutors, val dependencyManager: DependencyManager, - val settings: KobaltSettings, override val configActor: ConfigActor) + val settings: KobaltSettings, override val configActor: ConfigActor, + val kotlinJarFiles: KotlinJarFiles) : BaseJvmPlugin(configActor), IDocContributor, IClasspathContributor, ICompilerContributor, IBuildConfigContributor { companion object { @@ -105,8 +107,8 @@ class KotlinPlugin @Inject constructor(val executors: KobaltExecutors, val depen override fun classpathEntriesFor(project: Project?, context: KobaltContext): List = if (project == null || accept(project)) { // All Kotlin projects automatically get the Kotlin runtime added to their class path - listOf(getKotlinCompilerJar("kotlin-stdlib"), getKotlinCompilerJar("kotlin-runtime")) - .map { FileDependency(it) } + listOf(kotlinJarFiles.stdlib, kotlinJarFiles.runtime) + .map { FileDependency(it.absolutePath) } } else { emptyList() } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt index 16dea24c..3b1dd5b9 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt @@ -67,17 +67,20 @@ class PackagingPlugin @Inject constructor(val dependencyManager : DependencyMana * skipped. */ override fun assemble(project: Project, context: KobaltContext) : IncrementalTaskInfo { - return IncrementalTaskInfo({ null }, { null }, { project -> - try { - packages.filter { it.project.name == project.name }.forEach { pkg -> - pkg.jars.forEach { jarGenerator.generateJar(pkg.project, context, it) } - pkg.wars.forEach { warGenerator.generateWar(pkg.project, context, it) } - pkg.zips.forEach { zipGenerator.generateZip(pkg.project, context, it) } - if (pkg.generatePom) { - pomFactory.create(project).generate() - } - } - TaskResult() + return IncrementalTaskInfo( + { null }, + { null }, + { project -> + try { + packages.filter { it.project.name == project.name }.forEach { packageConfig -> + packageConfig.jars.forEach { jarGenerator.generateJar(packageConfig.project, context, it) } + packageConfig.wars.forEach { warGenerator.generateWar(packageConfig.project, context, it) } + packageConfig.zips.forEach { zipGenerator.generateZip(packageConfig.project, context, it) } + if (packageConfig.generatePom) { + pomFactory.create(project).generate() + } + } + TaskResult() } catch(ex: Exception) { throw KobaltException(ex) }}, context) @@ -118,8 +121,7 @@ class PackagingPlugin @Inject constructor(val dependencyManager : DependencyMana val analyzer = Analyzer().apply { jar = aQute.bnd.osgi.Jar(project.projectProperties.get(Archives.JAR_NAME) as String) val dependencies = project.compileDependencies + project.compileRuntimeDependencies - val dependentProjects = project.dependentProjects - dependencyManager.calculateDependencies(project, context, dependentProjects, dependencies).forEach { + dependencyManager.calculateDependencies(project, context, dependencies).forEach { addClasspath(it.jarFile.get()) } setProperty(Analyzer.BUNDLE_VERSION, project.version) @@ -152,7 +154,7 @@ class PackagingPlugin @Inject constructor(val dependencyManager : DependencyMana } //ITaskContributor - override fun tasksFor(context: KobaltContext): List = taskContributor.dynamicTasks + override fun tasksFor(project: Project, context: KobaltContext): List = taskContributor.dynamicTasks } @Directive diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt index ad4b4f8d..a4826813 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt @@ -1,13 +1,12 @@ package com.beust.kobalt.plugin.packaging -import com.beust.kobalt.archive.Archives import com.beust.kobalt.IFileSpec import com.beust.kobalt.JarGenerator -import com.beust.kobalt.archive.War import com.beust.kobalt.api.IClasspathDependency import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project -import com.beust.kobalt.api.ProjectDescription +import com.beust.kobalt.archive.Archives +import com.beust.kobalt.archive.War import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.From import com.beust.kobalt.misc.IncludedFile @@ -40,9 +39,7 @@ class WarGenerator @Inject constructor(val dependencyManager: DependencyManager) // The transitive closure of libraries goes into WEB-INF/libs. // Copy them all in kobaltBuild/war/WEB-INF/libs and create one IncludedFile out of that directory // - val dependentProjects = listOf(ProjectDescription(project, project.projectExtra.dependsOn)) - val allDependencies = dependencyManager.calculateDependencies(project, context, dependentProjects, - project.compileDependencies) + val allDependencies = dependencyManager.calculateDependencies(project, context, project.compileDependencies) val outDir = project.buildDirectory + "/war" val fullDir = outDir + "/" + LIB diff --git a/src/main/kotlin/com/beust/kobalt/plugin/publish/BintrayApi.kt b/src/main/kotlin/com/beust/kobalt/plugin/publish/BintrayApi.kt index d93da574..8bbd7058 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/publish/BintrayApi.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/publish/BintrayApi.kt @@ -6,26 +6,35 @@ import com.beust.kobalt.api.Project import com.beust.kobalt.maven.Gpg import com.beust.kobalt.maven.Http import com.beust.kobalt.maven.Md5 +import com.beust.kobalt.misc.CountingFileRequestBody import com.beust.kobalt.misc.KobaltExecutors import com.beust.kobalt.misc.error import com.beust.kobalt.misc.log import com.beust.kobalt.misc.warn +import com.google.gson.Gson import com.google.gson.JsonArray import com.google.gson.JsonObject -import com.google.gson.JsonParser +import com.google.gson.TypeAdapter +import com.google.gson.reflect.TypeToken import com.google.inject.assistedinject.Assisted -import okhttp3.* +import okhttp3.Credentials +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import okhttp3.ResponseBody import retrofit2.Call +import retrofit2.Converter import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import retrofit2.http.* -import retrofit2.http.Headers +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.PUT +import retrofit2.http.Path import java.io.File +import java.lang.reflect.Type import javax.annotation.Nullable import javax.inject.Inject -class BintrayResponse() - class BintrayApi @Inject constructor(val http: Http, @Nullable @Assisted("username") val username: String?, @Nullable @Assisted("password") val password: String?, @@ -34,7 +43,6 @@ class BintrayApi @Inject constructor(val http: Http, companion object { const val BINTRAY_URL_API = "https://api.bintray.com" - const val BINTRAY_URL_API_CONTENT = BINTRAY_URL_API + "/content" } interface IFactory { @@ -52,28 +60,15 @@ class BintrayApi @Inject constructor(val http: Http, fun createPackage(@Path("owner") owner: String, @Body content: JsonObject): Call - @Multipart - @Headers("Content-Type: application/xml") - @PUT("/content/{owner}/maven/{repo}/{version}/{group}/{artifact}/{version}/{name}") - fun uploadPom(@Path("owner") owner: String, - @Path("repo") repo: String, - @Path("group", encoded = true) group: String, - @Path("artifact") artifact: String, - @Path("version") version: String, - @Path("name") name: String, - @Part file: MultipartBody.Part): Call - - @Multipart - @PUT("/content/{owner}/maven/{repo}/{version}/{group}/{artifact}/{version}/{name}") + @PUT("/content/{owner}/maven/{repo}/{version}/{group}/{artifact}/{version}/{name};publish={publish}") fun uploadArtifact(@Path("owner") owner: String, @Path("repo") repo: String, @Path("group", encoded = true) group: String, @Path("artifact") artifact: String, @Path("version") version: String, @Path("name") name: String, - @Part file: MultipartBody.Part): Call - - + @Path("publish") publish: Int, + @Body file: File): Call } private val service: Api @@ -84,7 +79,7 @@ class BintrayApi @Inject constructor(val http: Http, // level = HttpLoggingInterceptor.Level.BASIC // }) builder.interceptors().add(Interceptor { chain -> - var original = chain.request(); + val original = chain.request(); chain.proceed(original.newBuilder() .header("Authorization", Credentials.basic(username, password)) @@ -96,7 +91,7 @@ class BintrayApi @Inject constructor(val http: Http, service = Retrofit.Builder() .client(okHttpClient) .baseUrl(BintrayApi.BINTRAY_URL_API) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(ConverterFactory()) .build() .create(Api::class.java) } @@ -128,18 +123,18 @@ class BintrayApi @Inject constructor(val http: Http, return jsonObject } - fun uploadMaven(project: Project, files: List, config: BintrayConfig?): TaskResult { + fun uploadMaven(project: Project, files: List, config: BintrayConfig): TaskResult { validatePackage(project) return upload(project, files, config, generateMd5 = true) } - fun uploadFile(project: Project, file: File, config: BintrayConfig?, generateMd5: Boolean = false) = + fun uploadFile(project: Project, file: File, config: BintrayConfig, generateMd5: Boolean = false) = upload(project, arrayListOf(file), config, generateMd5) - private fun upload(project: Project, files: List, config: BintrayConfig?, generateMd5: Boolean = false): TaskResult { + private fun upload(project: Project, files: List, config: BintrayConfig, generateMd5: Boolean): TaskResult { val filesToUpload = arrayListOf() - if (config != null && config.sign) { + if (config.sign) { // Create the .asc files filesToUpload.addAll(gpg.runGpg(files)) } @@ -156,17 +151,6 @@ class BintrayApi @Inject constructor(val http: Http, } } - // - // If any configuration was given, apply them so the URL reflects them, e.g. ?publish=1 - // - val options = arrayListOf() - if (config?.publish == true) options.add("publish=1") - - val optionPath = StringBuffer() - if (options.size > 0) { - optionPath.append("?" + options.joinToString("&")) - } - val fileCount = filesToUpload.size if (fileCount > 0) { log(1, " Found $fileCount artifacts to upload") @@ -180,33 +164,21 @@ class BintrayApi @Inject constructor(val http: Http, val results = arrayListOf() filesToUpload.forEachIndexed { i, file -> - val type = MediaType.parse("multipart/form-data") + val owner = org ?: username!! + val repo = project.name + val group = project.group!!.replace('.', '/') + val artifact = project.artifactId!! + val version = project.version!! - val body = MultipartBody.Part.createFormData("artifact", file.name, RequestBody.create(type, file)); - - if (file.extension != "pom") { - val upload = service.uploadArtifact(org ?: username!!, project.name, - project.group!!.replace('.', '/'), project.artifactId!!, project.version!!, file.name, body) - val result = upload.execute() - val error = result.errorBody()?.string() - if (result.errorBody() != null) { - errorMessages.add(error!!) - results.add(false) - } else { - results.add(true) - } + val result = service.uploadArtifact(owner, repo, group, artifact, version, file.name, + if (config.publish) 1 else 0, file) + .execute() + val error = result.errorBody()?.string() + if (result.errorBody() != null) { + errorMessages.add(error!!) + results.add(false) } else { - http.uploadFile(username, password, fileToPath(project, file) + optionPath, - Http.TypedFile(com.google.common.net.MediaType.ANY_APPLICATION_TYPE.toString(), file), - post = false, // Bintray requires PUT - success = { r: Response -> results.add(true) }, - error = { r: Response -> - results.add(false) - val jcResponse = parseResponse(r) - errorMessages.add(jcResponse.errorMessage!!) - }) -// service.uploadPom(org ?: username!!, project.name, project.group!!.replace('.', '/'), -// project.artifactId!!, project.version!!, file.name, body) + results.add(true) } log(1, " Uploading ${i + 1} / $fileCount " + dots(fileCount, results, file), false) @@ -228,37 +200,8 @@ class BintrayApi @Inject constructor(val http: Http, } } - fun fileToPath(project: Project, f: File) : String { - return listOf( - BINTRAY_URL_API_CONTENT, - org ?: username!!, - "maven", - project.name, - project.version!!, - project.group!!.replace(".", "/"), - project.artifactId!!, - project.version!!, - f.name) - .joinToString("/") - } - class BintrayResponse(val jo: JsonObject?, val errorMessage: String?) - fun parseResponse(r: Response): BintrayResponse { - val networkResponse = r.networkResponse() - if (networkResponse.code() != 200) { - val message = networkResponse.message() - try { - val errorObject = JsonParser().parse(r.body().string()).asJsonObject - return BintrayResponse(null, message + ": " + errorObject.get("message").asString) - } catch(ex: Exception) { - return BintrayResponse(null, message) - } - } else { - return BintrayResponse(JsonParser().parse(r.body().string()).asJsonObject, null) - } - } - fun JsonObject.addNonNull(name: String, value: String?) { if (value != null) { addProperty(name, value); @@ -266,3 +209,31 @@ class BintrayApi @Inject constructor(val http: Http, } } + +class ConverterFactory : Converter.Factory() { + override fun responseBodyConverter(type: Type, annotations: Array, retrofit: Retrofit): Converter? { + return GsonResponseBodyConverter(Gson(), Gson().getAdapter(TypeToken.get(type))) + } + + override fun requestBodyConverter(type: Type, parameterAnnotations: Array, methodAnnotations: Array, + retrofit: Retrofit?): Converter<*, RequestBody>? { + return RequestBodyConverter() + } +} + +class GsonResponseBodyConverter(private val gson: Gson, private val adapter: TypeAdapter) : Converter { + override fun convert(value: ResponseBody): Any { + val jsonReader = gson.newJsonReader(value.charStream()) + try { + return adapter.read(jsonReader) + } finally { + value.close() + } + } +} + +class RequestBodyConverter : Converter { + override fun convert(value: File): RequestBody { + return CountingFileRequestBody(value, "application/*", { }) + } +} \ No newline at end of file diff --git a/src/main/resources/kobalt.properties b/src/main/resources/kobalt.properties index 08c3d2ee..c6f13ab1 100644 --- a/src/main/resources/kobalt.properties +++ b/src/main/resources/kobalt.properties @@ -1 +1 @@ -kobalt.version=0.789 +kobalt.version=0.814 diff --git a/src/test/kotlin/com/beust/kobalt/internal/BuildOrderTest.kt b/src/test/kotlin/com/beust/kobalt/internal/BuildOrderTest.kt new file mode 100644 index 00000000..835d342c --- /dev/null +++ b/src/test/kotlin/com/beust/kobalt/internal/BuildOrderTest.kt @@ -0,0 +1,104 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.TestModule +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.project +import org.assertj.core.api.Assertions.assertThat +import org.testng.annotations.BeforeClass +import org.testng.annotations.DataProvider +import org.testng.annotations.Guice +import org.testng.annotations.Test +import javax.inject.Inject + +@Guice(modules = arrayOf(TestModule::class)) +class BuildOrderTest @Inject constructor(val taskManager: TaskManager) { + @BeforeClass + fun beforeClass() { + Kobalt.init(TestModule()) + } + + private fun toExpectedList(vararg projectNames: Int) = projectNames.map { "p$it:assemble"}.toList() + + @DataProvider + fun tasks() = arrayOf( + arrayOf(listOf("assemble"), toExpectedList(1, 2, 3)), + arrayOf(listOf("p1:assemble"), toExpectedList(1)), + arrayOf(listOf("p2:assemble"), toExpectedList(1, 2)), + arrayOf(listOf("p3:assemble"), toExpectedList(1, 2, 3))) + + @Test(dataProvider = "tasks") + fun shouldBuildInRightOrder(tasks: List, expectedTasks: List) { + val p1 = project { name = "p1" } + val p2 = project(p1) { name = "p2" } + val p3 = project(p2) { name = "p3" } + + val allProjects = listOf(p1, p2, p3) + with(taskManager) { + val taskInfos = calculateDependentTaskNames(tasks, allProjects) + assertThat(taskInfos.map { it.id }).isEqualTo(expectedTasks) + } + } + + @DataProvider + fun tasks2(): Array> { + return arrayOf( + arrayOf(listOf("p14:assemble"), toExpectedList(1, 2, 3, 4, 5, 8, 11, 6, 7, 9, 10, 12, 13, 14)) + ) + } + + @Test(dataProvider = "tasks2") + fun shouldBuildInRightOrder2(tasks: List, expectedTasks: List) { + val p1 = project { name ="p1" } + val p2 = project { name ="p2" } + val p3 = project { name ="p3" } + val p4 = project { name ="p4" } + val p5 = project { name ="p5" } + val p6 = project(p5) { name ="p6" } + val p7 = project(p5) { name ="p7" } + val p8 = project { name ="p8" } + val p9 = project(p6, p5, p2, p3) { name ="p9" } + val p10 = project(p9) { name ="p10" } + val p11 = project { name ="p11" } + val p12 = project(p1, p7, p9, p10, p11) { name ="p12" } + val p13 = project(p4, p8, p9, p12) { name ="p13" } + val p14 = project(p12, p13) { name ="p14" } + + fun Collection.appearsBefore(first: String, second: String) { + var sawFirst = false + var sawSecond = false + forEach { ti -> + if (ti.project == first) { + sawFirst = true + } + if (ti.project == second) { + assertThat(sawFirst) + .`as`("Expected to see $first before $second in ${map {it.project}}") + .isTrue() + sawSecond = true + } + } + assertThat(sawFirst).`as`("Didn't see $first").isTrue() + assertThat(sawSecond).`as`("Didn't see $second").isTrue() + } + + fun Collection.appearsBefore(firsts: List, second: String) { + firsts.forEach { first -> + appearsBefore(first, second) + } + } + + val allProjects = listOf(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14) + with(taskManager) { + with(calculateDependentTaskNames(tasks, allProjects)) { + assertThat(size).isEqualTo(expectedTasks.size) + appearsBefore("p5", "p6") + appearsBefore("p5", "p7") + appearsBefore("p9", "p10") + appearsBefore(listOf("p1", "p7", "p9", "p10", "p11"), "p12") + appearsBefore(listOf("p4", "p8", "p9", "p12"), "p13") + appearsBefore(listOf("p12", "p13"), "p14") + } + } + } + +} diff --git a/src/test/kotlin/com/beust/kobalt/internal/GenericRunnerTest.kt b/src/test/kotlin/com/beust/kobalt/internal/DependencyTest.kt similarity index 90% rename from src/test/kotlin/com/beust/kobalt/internal/GenericRunnerTest.kt rename to src/test/kotlin/com/beust/kobalt/internal/DependencyTest.kt index fcf04494..a510cd66 100644 --- a/src/test/kotlin/com/beust/kobalt/internal/GenericRunnerTest.kt +++ b/src/test/kotlin/com/beust/kobalt/internal/DependencyTest.kt @@ -16,8 +16,9 @@ import javax.inject.Inject * Test ITestJvmFlagContributor and ITestJvmFlagInterceptor. */ class DependencyTest @Inject constructor() { - private val A_JAR = "/tmp/a.jar" - private val B_JAR = "/tmp/b.jar" + private fun isWindows() = System.getProperty("os.name").toLowerCase().contains("ndows") + private val A_JAR = if (isWindows()) "c:\\tmp\\a.jar" else "/tmp/a.jar" + private val B_JAR = if (isWindows()) "c:\\tmp\\b.jar" else "/tmp/b.jar" private val project : Project get() = project { name = "dummy" } private val classpath = listOf(FileDependency(A_JAR)) diff --git a/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt b/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt index 07b2ffd2..6298c994 100644 --- a/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt +++ b/src/test/kotlin/com/beust/kobalt/internal/DynamicGraphTest.kt @@ -125,8 +125,10 @@ class DynamicGraphTest { addEdge("b2", "a2") addEdge("c1", "b1") addEdge("c1", "b2") - val sorted = sort(arrayListOf("a1", "a2", "b1", "b2", "c1", "x", "y")) - Assert.assertEquals(sorted, arrayListOf("a1", "a2", "x", "y", "b1", "b2", "c1")) + addNode("x") + addNode("y") + val sorted = sort() + Assert.assertEquals(sorted, arrayListOf("a1", "a2", "x", "y", "b2", "b1", "c1")) } } diff --git a/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt b/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt index ee1caa50..252e1228 100644 --- a/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt +++ b/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt @@ -80,7 +80,7 @@ class TaskManagerTest @Inject constructor(val taskManager: TaskManager) { } } - val graph = taskManager.createGraph2("", tasks, dependencies, + val graph = taskManager.createGraph2("", tasks.map { TaskManager.TaskInfo(it) }, dependencies, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, { it }, { t -> true }) val result = DryRunGraphExecutor(graph).run() diff --git a/src/test/kotlin/com/beust/kobalt/maven/PomTest.kt b/src/test/kotlin/com/beust/kobalt/maven/PomTest.kt index 27fa07c2..2d52413d 100644 --- a/src/test/kotlin/com/beust/kobalt/maven/PomTest.kt +++ b/src/test/kotlin/com/beust/kobalt/maven/PomTest.kt @@ -10,42 +10,60 @@ import com.google.inject.Inject import org.testng.Assert import org.testng.annotations.Test import java.io.File +import java.io.FileOutputStream +import java.nio.file.Files -class PomTest @Inject constructor() : KobaltTest() { +/** + * If a user calls --init on a project with a pom.xml in it, make sure that pom.xml is correctly reflected in the + * generated Build.kt. + */ +class PomImportTest @Inject constructor() : KobaltTest() { @Test fun importPom() { - val pomSrc = File("src/test/resources/pom.xml") - val pom = Pom("testing", pomSrc); + resourceToFile("PomTest/pom.xml").let { pomSrc -> + with(Pom("testing", pomSrc)) { + Assert.assertEquals(groupId, "com.foo.bob") + Assert.assertEquals(artifactId, "rawr") + Assert.assertEquals(name, "rawr") + Assert.assertEquals(version, "1.2.3") + Assert.assertEquals(properties["commons.version"], "2.1.1") + Assert.assertEquals(properties["guice.version"], "4.0") - Assert.assertEquals(pom.groupId, "com.foo.bob") - Assert.assertEquals(pom.artifactId, "rawr") - Assert.assertEquals(pom.name, "rawr") - Assert.assertEquals(pom.version, "1.2.3") - Assert.assertEquals(pom.properties.get("commons.version"), "2.1.1") - Assert.assertEquals(pom.properties.get("guice.version"), "4.0") - - validateGeneratedFile(pom, pomSrc) + validateGeneratedFile(this, pomSrc) + } + } } @Test fun importBasicPom() { - val pomSrc = File("src/test/resources/pom-norepositories-properties.xml") - val pom = Pom("testing", pomSrc); + resourceToFile("PomTest/pom-norepositories-properties.xml").let { pomSrc -> + with(Pom("testing", pomSrc)) { + Assert.assertEquals(groupId, "com.foo.bob") + Assert.assertEquals(artifactId, "rawr") + Assert.assertEquals(name, "rawr") + Assert.assertEquals(version, "1.2.3") + Assert.assertTrue(properties.isEmpty()) + Assert.assertTrue(repositories.isEmpty()) - Assert.assertEquals(pom.groupId, "com.foo.bob") - Assert.assertEquals(pom.artifactId, "rawr") - Assert.assertEquals(pom.name, "rawr") - Assert.assertEquals(pom.version, "1.2.3") - Assert.assertTrue(pom.properties.isEmpty()) - Assert.assertTrue(pom.repositories.isEmpty()) + validateGeneratedFile(this, pomSrc) + } + } + } - validateGeneratedFile(pom, pomSrc) + private fun resourceToFile(fileName: String) : File { + val ins = javaClass.classLoader.getResourceAsStream(fileName) + val result = Files.createTempFile("kobaltTest", "").toFile() + FileOutputStream(result).use { + ins.copyTo(it) + } + return result } private fun validateGeneratedFile(pom: Pom, pomSrc: File) { val temp = File(System.getProperty("java.io.tmpdir")) val original = System.getProperty("user.dir") System.setProperty("user.dir", temp.absolutePath) + val pomFile = File(temp, "pom.xml") pomFile.deleteOnExit() pomSrc.copyTo(pomFile, true) @@ -60,7 +78,7 @@ class PomTest @Inject constructor() : KobaltTest() { ProjectGenerator(Kobalt.INJECTOR.getInstance(PluginInfo::class.java)).run(args, javaClass.classLoader) - var contents = file.readText() + val contents = file.readText() Assert.assertTrue(contents.contains("group = \"${pom.groupId}\""), "Should find the group defined") Assert.assertTrue(contents.contains("name = \"${pom.name}\""), "Should find the name defined") Assert.assertTrue(contents.contains("version = \"${pom.version}\""), "Should find the version defined") diff --git a/src/test/resources/pom-norepositories-properties.xml b/src/test/resources/PomTest/pom-norepositories-properties.xml similarity index 100% rename from src/test/resources/pom-norepositories-properties.xml rename to src/test/resources/PomTest/pom-norepositories-properties.xml diff --git a/src/test/resources/pom.xml b/src/test/resources/PomTest/pom.xml similarity index 100% rename from src/test/resources/pom.xml rename to src/test/resources/PomTest/pom.xml