diff --git a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt index 3a9e8198..a03cae12 100644 --- a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt @@ -32,7 +32,7 @@ import javax.inject.Inject * 1) Extract the repos() and plugins() statements in a separate .kt and compile it into preBuildScript.jar. * 2) Actually build the whole Build.kt file after adding to the classpath whatever phase 1 found (plugins, repos) */ -class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFiles: List, +public class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFiles: List, @Assisted val pluginInfo: PluginInfo, val files: KFiles, val plugins: Plugins, val dependencyManager: DependencyManager, val pluginProperties: PluginProperties, val executors: KobaltExecutors, val buildScriptUtil: BuildScriptUtil, val settings: KobaltSettings, @@ -71,7 +71,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil return projectResult } - val compiledBuildFiles = arrayListOf() + val parsedBuildFiles = arrayListOf() class FindProjectResult(val context: KobaltContext, val projects: List, val pluginUrls: List, val taskResult: TaskResult) @@ -80,9 +80,9 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil var errorTaskResult: TaskResult? = null val projects = arrayListOf() buildFiles.forEach { buildFile -> - val compiledBuildFile = parseBuildFile(context, buildFile) - compiledBuildFiles.add(compiledBuildFile) - val pluginUrls = compiledBuildFile.pluginUrls + val parsedBuildFile = parseBuildFile(context, buildFile) + parsedBuildFiles.add(parsedBuildFile) + val pluginUrls = parsedBuildFile.pluginUrls val buildScriptJarFile = File(KFiles.findBuildScriptLocation(buildFile, SCRIPT_JAR)) // @@ -102,7 +102,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil // Write the modified Build.kt (e.g. maybe profiles were applied) to a temporary file, // compile it, jar it in buildScript.jar and run it val modifiedBuildFile = KFiles.createTempFile(".kt", deleteOnExit = true) - KFiles.saveFile(modifiedBuildFile, compiledBuildFile.splitFile.buildScriptCode) + KFiles.saveFile(modifiedBuildFile, parsedBuildFile.buildScriptCode) val taskResult = maybeCompileBuildFile(context, BuildFile(Paths.get(modifiedBuildFile.path), "Modified ${Constants.BUILD_FILE_NAME}", buildFile.realPath), buildScriptJarFile, pluginUrls) @@ -118,7 +118,7 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil context.internalContext.absoluteDir = null } - val pluginUrls = compiledBuildFiles.flatMap { it.pluginUrls } + val pluginUrls = parsedBuildFiles.flatMap { it.pluginUrls } return FindProjectResult(context, projects, pluginUrls, if (errorTaskResult != null) errorTaskResult!! else TaskResult()) } @@ -167,5 +167,5 @@ class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val buildFil * - the URL's of all the plug-ins that were found. */ private fun parseBuildFile(context: KobaltContext, buildFile: BuildFile) = - processedBuildFileFactory.create(buildFile, context) + ParsedBuildFile(buildFile, context, buildScriptUtil, dependencyManager, files) } diff --git a/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt b/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt new file mode 100644 index 00000000..9029358b --- /dev/null +++ b/src/main/kotlin/com/beust/kobalt/app/ParsedBuildFile.kt @@ -0,0 +1,162 @@ +package com.beust.kobalt.app + +import com.beust.kobalt.KobaltException +import com.beust.kobalt.Plugins +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.KobaltContext +import com.beust.kobalt.api.Project +import com.beust.kobalt.internal.build.BuildFile +import com.beust.kobalt.internal.build.VersionFile +import com.beust.kobalt.maven.DependencyManager +import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.countChar +import com.beust.kobalt.misc.kobaltLog +import com.beust.kobalt.plugin.kotlin.kotlinCompilePrivate +import java.io.File +import java.net.URL +import java.nio.charset.Charset +import java.nio.file.Paths +import java.util.* + +class ParsedBuildFile(val buildFile: BuildFile, val context: KobaltContext, val buildScriptUtil: BuildScriptUtil, + val dependencyManager: DependencyManager, val files: KFiles) { + val pluginList = arrayListOf() + val repos = arrayListOf() + val buildFileClasspath = arrayListOf() + val profileLines = arrayListOf() + val pluginUrls = arrayListOf() + val projects = arrayListOf() + val activeProfiles = arrayListOf() + + private val preBuildScript = arrayListOf( + "import com.beust.kobalt.*", + "import com.beust.kobalt.api.*") + val preBuildScriptCode : String get() = preBuildScript.joinToString("\n") + + private val buildScript = arrayListOf() + val buildScriptCode : String get() = buildScript.joinToString("\n") + + init { + parseBuildFile() + initPluginUrls() + } + + private fun parseBuildFile() { + var parenCount = 0 + var current: ArrayList? = null + buildFile.path.toFile().forEachLine(Charset.defaultCharset()) { line -> + var index = line.indexOf("plugins(") + if (current == null) { + if (index >= 0) { + current = pluginList + } else { + index = line.indexOf("repos(") + if (index >= 0) { + current = repos + } else { + index = line.indexOf("buildFileClasspath(") + if (index >= 0) { + current = buildFileClasspath + } + } + } + } + + if (parenCount > 0 || current != null) { + if (index == -1) index = 0 + with(line.substring(index)) { + parenCount += line countChar '(' + if (parenCount > 0) { + current!!.add(line) + } + parenCount -= line countChar ')' + } + } + + if (parenCount == 0) { + current = null + } + + /** + * If the current line matches one of the profiles, turn the declaration into + * val profile = true, otherwise return the same line + */ + fun correctProfileLine(line: String) : String { + (context.profiles as List).forEach { + if (line.matches(Regex("[ \\t]*val[ \\t]+$it[ \\t]*=.*"))) { + with("val $it = true") { + kobaltLog(2, "Activating profile $it in build file") + activeProfiles.add(it) + profileLines.add(this) + return this + } + } + } + return line + } + + buildScript.add(correctProfileLine(line)) + } + + repos.forEach { preBuildScript.add(it) } + pluginList.forEach { preBuildScript.add(it) } + buildFileClasspath.forEach { preBuildScript.add(it) } + } + + private fun initPluginUrls() { + // + // Compile and run preBuildScriptCode, which contains all the plugins() calls extracted. This + // will add all the dynamic plugins found in this code to Plugins.dynamicPlugins + // + val pluginSourceFile = KFiles.createTempFile(".kt", deleteOnExit = true) + pluginSourceFile.writeText(preBuildScriptCode, Charset.defaultCharset()) + kobaltLog(2, "Saved ${pluginSourceFile.absolutePath}") + + // + // Compile to preBuildScript.jar + // + val buildScriptJar = KFiles.findBuildScriptLocation(buildFile, "preBuildScript.jar") + val buildScriptJarFile = File(buildScriptJar) + if (! buildScriptUtil.isUpToDate(buildFile, File(buildScriptJar))) { + buildScriptJarFile.parentFile.mkdirs() + generateJarFile(context, BuildFile(Paths.get(pluginSourceFile.path), "Plugins", + Paths.get(buildScriptJar)), buildScriptJarFile, buildFile) + VersionFile.generateVersionFile(buildScriptJarFile.parentFile) + Kobalt.context!!.internalContext.buildFileOutOfDate = true + } + + // + // Run preBuildScript.jar to initialize plugins and repos + // + projects.addAll(buildScriptUtil.runBuildScriptJarFile(buildScriptJarFile, arrayListOf(), context)) + + // + // All the plug-ins are now in Plugins.dynamicPlugins, download them if they're not already + // + Plugins.dynamicPlugins.forEach { + pluginUrls.add(it.jarFile.get().toURI().toURL()) + } + } + + private fun generateJarFile(context: KobaltContext, buildFile: BuildFile, buildScriptJarFile: File, + originalFile: BuildFile) { + + // + // Compile the jar file + // + val kotlintDeps = dependencyManager.calculateDependencies(null, context) + val deps: List = kotlintDeps.map { it.jarFile.get().absolutePath } + val outputJar = File(buildScriptJarFile.absolutePath) + val result = kotlinCompilePrivate { + classpath(files.kobaltJar) + classpath(deps) + sourceFiles(buildFile.path.toFile().absolutePath) + output = outputJar + }.compile(context = context) + if (! result.success) { + throw KobaltException("Couldn't compile ${originalFile.realPath}:\n" + + result.errorMessage) + } + } +} +