From b1723668ce2957700109f1cda4dd650abfad0d65 Mon Sep 17 00:00:00 2001 From: Zoltan Nagy Date: Sat, 15 Nov 2014 23:25:54 +0100 Subject: [PATCH] Kotlin2Js Gradle plugin implementation --- .../kotlin/gradle/plugin/KotlinPlugin.kt | 267 ++++++++++++++---- .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 192 ++++++++++--- libraries/tools/kotlin-gradle-plugin/pom.xml | 1 + .../gradle/plugin/KotlinPluginWrapper.kt | 9 +- .../gradle-plugins/kotlin2js.properties | 1 + 5 files changed, 373 insertions(+), 97 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin2js.properties diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 447528bb82d..70f79b96988 100644 --- a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -35,10 +35,187 @@ import org.gradle.api.UnknownDomainObjectException import org.gradle.api.initialization.dsl.ScriptHandler import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper import javax.inject.Inject +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.gradle.api.tasks.Copy +import org.gradle.api.file.SourceDirectorySet +import kotlin.properties.Delegates +import org.gradle.api.tasks.Delete +import org.codehaus.groovy.runtime.MethodClosure +import groovy.lang.Closure +import org.gradle.api.artifacts.ProjectDependency val DEFAULT_ANNOTATIONS = "org.jebrains.kotlin.gradle.defaultAnnotations" -open class KotlinPlugin [Inject] (val scriptHandler: ScriptHandler): Plugin { + +abstract class KotlinSourceSetProcessor(val project: ProjectInternal, + val javaBasePlugin: JavaBasePlugin, + val sourceSet: SourceSet, + val pluginName: String, + val compileTaskNameSuffix: String, + val taskDescription: String, + val compilerClass: Class) { + abstract protected fun doTargetSpecificProcessing() + val logger = Logging.getLogger(this.javaClass) + + protected val sourceSetName: String = sourceSet.getName() + protected val sourceRootDir: String = "src/${sourceSetName}/kotlin" + protected val absoluteSourceRootDir: String = project.getProjectDir().getPath() + "/" + sourceRootDir + protected val kotlinSourceSet: KotlinSourceSet? by Delegates.lazy { createKotlinSourceSet() } + protected val kotlinDirSet: SourceDirectorySet? by Delegates.lazy { createKotlinDirSet() } + protected val kotlinTask: T by Delegates.lazy { createKotlinCompileTask() } + protected val kotlinTaskName: String by Delegates.lazy { kotlinTask.getName() } + + public fun run() { + if (kotlinSourceSet == null || kotlinDirSet == null) { + return + } + addSourcesToKotlinDirSet() + commonTaskConfiguration() + doTargetSpecificProcessing() + } + + open protected fun createKotlinSourceSet(): KotlinSourceSet? = + if (sourceSet is HasConvention) { + logger.debug("Creating KotlinSourceSet for source set ${sourceSet}") + val kotlinSourceSet = KotlinSourceSetImpl(sourceSet.getName(), project.getFileResolver()) + sourceSet.getConvention().getPlugins().put(pluginName, kotlinSourceSet) + kotlinSourceSet + } else { + null + } + + open protected fun createKotlinDirSet(): SourceDirectorySet? { + val srcDir = project.file(sourceRootDir) + logger.debug("Creating Kotlin SourceDirectorySet for source set ${kotlinSourceSet} with src dir ${srcDir}") + val kotlinDirSet = kotlinSourceSet?.getKotlin() + kotlinDirSet?.srcDir(srcDir) + return kotlinDirSet + } + + open protected fun addSourcesToKotlinDirSet() { + logger.debug("Adding Kotlin SourceDirectorySet ${kotlinDirSet} to source set ${sourceSet}") + sourceSet.getAllJava()?.source(kotlinDirSet) + sourceSet.getAllSource()?.source(kotlinDirSet) + sourceSet.getResources()?.getFilter()?.exclude { kotlinDirSet!!.contains(it.getFile()) } + } + + open protected fun createKotlinCompileTask(): T { + val name = sourceSet.getCompileTaskName(compileTaskNameSuffix) + logger.debug("Creating kotlin compile task $name with class $compilerClass") + return project.getTasks().create(name, compilerClass) + } + + open protected fun commonTaskConfiguration() { + javaBasePlugin.configureForSourceSet(sourceSet, kotlinTask) + kotlinTask.setDescription(taskDescription) + kotlinTask.source(kotlinDirSet) + } +} + +class Kotlin2JvmSourceSetProcessor( + project: ProjectInternal, + javaBasePlugin: JavaBasePlugin, + sourceSet: SourceSet, + val scriptHandler: ScriptHandler) +: KotlinSourceSetProcessor( + project, javaBasePlugin, sourceSet, + pluginName = "kotlin", + compileTaskNameSuffix = "kotlin", + taskDescription = "Compiles the $sourceSet.kotlin.", + compilerClass = javaClass()) { + + override fun doTargetSpecificProcessing() { + // store kotlin classes in separate directory. They will serve as class-path to java compiler + kotlinTask.kotlinDestinationDir = File(project.getBuildDir(), "kotlin-classes/${sourceSetName}") + + val javaTask = project.getTasks().findByName(sourceSet.getCompileJavaTaskName()) as AbstractCompile? + + if (javaTask != null) { + javaTask.dependsOn(kotlinTaskName) + val javacClassPath = javaTask.getClasspath() + project.files(kotlinTask.kotlinDestinationDir); + javaTask.setClasspath(javacClassPath) + } + } +} + + +class Kotlin2JsSourceSetProcessor( + project: ProjectInternal, + javaBasePlugin: JavaBasePlugin, + sourceSet: SourceSet, + val scriptHandler: ScriptHandler +) : KotlinSourceSetProcessor( + project, javaBasePlugin, sourceSet, + pluginName = "kotlin2js", + taskDescription = "Compiles the kotlin sources in $sourceSet to JavaScript.", + compileTaskNameSuffix = "kotlin2Js", + compilerClass = javaClass()) { + val copyKotlinJsTaskName = sourceSet.getTaskName("copy", "kotlinJs") + val clean = project.getTasks().findByName("clean") + val build = project.getTasks().findByName("build") + + val defaultKotlinDestinationDir = File(project.getBuildDir(), "kotlin2js/${sourceSetName}") + private fun kotlinTaskDestinationDir(): File? = kotlinTask.kotlinDestinationDir + private fun kotlinJsDestinationDir(): File? = if (kotlinTask.outputFile() == null) { + null + } else { + File(kotlinTask.outputFile()).directory + } + + private fun copyKotlinJsTaskOutput(): String? = if (kotlinJsDestinationDir() == null) { + null + } else { + val copyTask = project.getTasks().getByName(copyKotlinJsTaskName) as Copy + "${copyTask.getDestinationDir()}/kotlin.js" + } + + private fun kotlinSourcePathsForSourceMap() = sourceSet.getAllSource() + .map { it.path } + .filter { it.endsWith(".kt") } + .map { it.replace(absoluteSourceRootDir, kotlinTask.sourceMapDestinationDir().getPath()) } + + private fun shouldGenerateSourceMap() = kotlinTask.kotlinOptions.sourceMap + + override fun doTargetSpecificProcessing() { + kotlinTask.kotlinDestinationDir = defaultKotlinDestinationDir + build?.dependsOn(kotlinTaskName) + clean?.dependsOn("clean" + kotlinTaskName.capitalize()) + + createCopyKotlinJsTask(GradleUtils(scriptHandler, project).resolveJsLibrary().getAbsolutePath()) + createCleanSourceMapTask() + } + + private fun createCopyKotlinJsTask(jsLibraryJar: String) { + val copyKotlinJsTaskName = sourceSet.getTaskName("copy", "kotlinJs") + + val copyKotlinJsTask = project.getTasks().create(copyKotlinJsTaskName, javaClass()) + copyKotlinJsTask.from(project.zipTree(jsLibraryJar)) + copyKotlinJsTask.into(MethodClosure(this, "kotlinJsDestinationDir")) + copyKotlinJsTask.include("kotlin.js") + copyKotlinJsTask.onlyIf { kotlinJsDestinationDir() != null } + build?.dependsOn(copyKotlinJsTaskName) + + val cleanTaskName = "clean" + copyKotlinJsTaskName.capitalize() + val cleanTask = project.getTasks().create(cleanTaskName, javaClass()) + cleanTask.delete(MethodClosure(this, "copyKotlinJsTaskOutput")) + copyKotlinJsTask.onlyIf { copyKotlinJsTaskOutput() != null } + clean?.dependsOn(cleanTaskName) + } + + private fun createCleanSourceMapTask() { + val taskName = sourceSet.getTaskName("clean", "sourceMap") + val task = project.getTasks().create(taskName, javaClass()) + task.onlyIf { kotlinTask.kotlinOptions.sourceMap } + task.delete(object : Closure(this) { + override fun call(): String? = kotlinTask.outputFile() + ".map" + }) + clean?.dependsOn(taskName) + } +} + + +abstract class AbstractKotlinPlugin [Inject] (val scriptHandler: ScriptHandler) : Plugin { + abstract fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet): KotlinSourceSetProcessor<*> public override fun apply(project: Project) { val javaBasePlugin = project.getPlugins().apply(javaClass()) @@ -49,49 +226,23 @@ open class KotlinPlugin [Inject] (val scriptHandler: ScriptHandler): Plugin { sourceSet -> - if (sourceSet is HasConvention) { - val sourceSetName = sourceSet.getName() - val kotlinSourceSet = KotlinSourceSetImpl( sourceSetName, project.getFileResolver()) - sourceSet.getConvention().getPlugins().put("kotlin", kotlinSourceSet) - - val kotlinDirSet = kotlinSourceSet.getKotlin() - kotlinDirSet.srcDir(project.file("src/${sourceSetName}/kotlin")) - - sourceSet.getAllJava()?.source(kotlinDirSet) - sourceSet.getAllSource()?.source(kotlinDirSet) - sourceSet.getResources()?.getFilter()?.exclude({ kotlinDirSet.contains(it.getFile()) }) - - val kotlinTaskName = sourceSet.getCompileTaskName("kotlin") - val kotlinTask: KotlinCompile = project.getTasks().create(kotlinTaskName, javaClass()) - - javaBasePlugin.configureForSourceSet(sourceSet, kotlinTask) - // store kotlin classes in separate directory. They will serve as class-path to java compiler - kotlinTask.kotlinDestinationDir = File(project.getBuildDir(), "kotlin-classes/${sourceSetName}") - - kotlinTask.setDescription("Compiles the $sourceSet.kotlin.") - kotlinTask.source(kotlinDirSet) - - val javaTask = project.getTasks().findByName(sourceSet.getCompileJavaTaskName()) as AbstractCompile? - - if (javaTask != null) { - javaTask.dependsOn(kotlinTaskName) - val javacClassPath = javaTask.getClasspath() + project.files(kotlinTask.kotlinDestinationDir) - javaTask.setClasspath(javacClassPath) + open protected fun configureSourceSetDefaults(project: ProjectInternal, + javaBasePlugin: JavaBasePlugin, + javaPluginConvention: JavaPluginConvention) { + javaPluginConvention.getSourceSets()?.all(object : Action { + override fun execute(sourceSet: SourceSet?) { + if (sourceSet != null) { + buildSourceSetProcessor(project, javaBasePlugin, sourceSet).run() } } }) } - private fun configureKDoc(project: Project, javaPluginConvention: JavaPluginConvention) { + open protected fun configureKDoc(project: Project, javaPluginConvention: JavaPluginConvention) { val mainSourceSet = javaPluginConvention.getSourceSets()?.findByName(SourceSet.MAIN_SOURCE_SET_NAME) as HasConvention? if (mainSourceSet != null) { @@ -113,7 +264,19 @@ open class KotlinPlugin [Inject] (val scriptHandler: ScriptHandler): Plugin { +open class KotlinPlugin [Inject] (scriptHandler: ScriptHandler) : AbstractKotlinPlugin(scriptHandler) { + override fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet): KotlinSourceSetProcessor = + Kotlin2JvmSourceSetProcessor(project, javaBasePlugin, sourceSet, scriptHandler) +} + + +open class Kotlin2JsPlugin [Inject] (scriptHandler: ScriptHandler) : AbstractKotlinPlugin(scriptHandler) { + override fun buildSourceSetProcessor(project: ProjectInternal, javaBasePlugin: JavaBasePlugin, sourceSet: SourceSet) = + Kotlin2JsSourceSetProcessor(project, javaBasePlugin, sourceSet, scriptHandler) +} + + +open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler) : Plugin { val log = Logging.getLogger(this.javaClass) @@ -125,7 +288,7 @@ open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler): Plug ext.getSourceSets().all(Action { sourceSet -> if (sourceSet is HasConvention) { val sourceSetName = sourceSet.getName() - val kotlinSourceSet = KotlinSourceSetImpl( sourceSetName, project.getFileResolver()) + val kotlinSourceSet = KotlinSourceSetImpl(sourceSetName, project.getFileResolver()) sourceSet.getConvention().getPlugins().put("kotlin", kotlinSourceSet) val kotlinDirSet = kotlinSourceSet.getKotlin() kotlinDirSet.srcDir(project.file("src/${sourceSetName}/kotlin")) @@ -161,8 +324,7 @@ open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler): Plug } } - val version = project.getProperties()["kotlin.gradle.plugin.version"] as String - project.getExtensions().add(DEFAULT_ANNOTATIONS, GradleUtils(scriptHandler!!).resolveDependencies("org.jetbrains.kotlin:kotlin-android-sdk-annotations:$version")) + project.getExtensions().add(DEFAULT_ANNOTATIONS, GradleUtils(scriptHandler, project).resolveKotlinPluginDependency("kotlin-android-sdk-annotations")) } private fun processVariants(variants: DefaultDomainObjectSet, project: Project, androidExt: BaseExtension): Unit { @@ -182,13 +344,13 @@ open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler): Plug val buildTypeSourceSetName = AndroidGradleWrapper.getVariantName(variant) logger.debug("Variant build type is [$buildTypeSourceSetName]") - val buildTypeSourceSet : AndroidSourceSet? = sourceSets.findByName(buildTypeSourceSetName) + val buildTypeSourceSet: AndroidSourceSet? = sourceSets.findByName(buildTypeSourceSetName) val javaTask = variant.getJavaCompile()!! val variantName = variant.getName() val kotlinTaskName = "compile${variantName.capitalize()}Kotlin" - val kotlinTask: KotlinCompile = project.getTasks().create(kotlinTaskName, javaClass()) + val kotlinTask = project.getTasks().create(kotlinTaskName, javaClass()) kotlinTask.kotlinOptions = kotlinOptions @@ -246,7 +408,7 @@ open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler): Plug if (null == plugin) { plugin = project.getPlugins().findPlugin("android-library") } - val basePlugin : BasePlugin = plugin as BasePlugin + val basePlugin: BasePlugin = plugin as BasePlugin val javaSources = project.files(javaSourceList) val androidRT = project.files(AndroidGradleWrapper.getRuntimeJars(basePlugin)) val fullClasspath = (javaTask.getClasspath() + (javaSources + androidRT)) - project.files(kotlinTask.kotlinDestinationDir) @@ -273,16 +435,23 @@ open class KotlinAndroidPlugin [Inject] (val scriptHandler: ScriptHandler): Plug } - -open class GradleUtils(val scriptHandler: ScriptHandler) { + +open class GradleUtils(val scriptHandler: ScriptHandler, val project: ProjectInternal) { public fun resolveDependencies(vararg coordinates: String): Collection { - val dependencyHandler : DependencyHandler = scriptHandler.getDependencies() - val configurationsContainer : ConfigurationContainer = scriptHandler.getConfigurations() + val dependencyHandler: DependencyHandler = scriptHandler.getDependencies() + val configurationsContainer: ConfigurationContainer = scriptHandler.getConfigurations() val deps = coordinates map { dependencyHandler.create(it) } val configuration = configurationsContainer.detachedConfiguration(*deps.copyToArray()) return configuration.getResolvedConfiguration().getFiles({true}) } -} + public fun kotlinPluginVersion(): String = project.getProperties()["kotlin.gradle.plugin.version"] as String + public fun kotlinPluginArtifactCoordinates(artifact: String): String = "org.jetbrains.kotlin:${artifact}:${kotlinPluginVersion()}" + public fun kotlinJsLibraryCoordinates(): String = kotlinPluginArtifactCoordinates("kotlin-js-library") + + public fun resolveKotlinPluginDependency(artifact: String): Collection = + resolveDependencies(kotlinPluginArtifactCoordinates(artifact)) + public fun resolveJsLibrary(): File = resolveDependencies(kotlinJsLibraryCoordinates()).first() +} diff --git a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index f949acd4bd2..e25d0a5b8cc 100644 --- a/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin-core/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -24,19 +24,44 @@ import org.apache.commons.io.FileUtils import org.jetbrains.kotlin.gradle.plugin.* import org.gradle.api.Project import org.jetbrains.jet.config.Services +import org.jetbrains.jet.cli.js.K2JSCompiler +import org.jetbrains.jet.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.k2js.config.MetaInfServices +import org.jetbrains.k2js.config.ClassPathLibraryDefintionsConfig +import org.jetbrains.jet.cli.common.CLIConfigurationKeys +import org.jetbrains.jet.config.CompilerConfiguration +import org.jetbrains.jet.cli.jvm.compiler.CompileEnvironmentUtil +import org.jetbrains.jet.cli.jvm.compiler.JetCoreEnvironment +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import org.jetbrains.k2js.config.EcmaVersion +import org.gradle.api.tasks.Copy +import org.gradle.api.Action +import org.gradle.api.internal.project.ProjectInternal +import groovy.lang.Closure +import org.codehaus.groovy.runtime.MethodClosure +import org.gradle.api.DefaultTask +import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.jet.cli.common.CLICompiler +import com.intellij.ide.highlighter.JavaFileType +import org.jetbrains.jet.plugin.JetFileType -public open class KotlinCompile(): AbstractCompile() { +abstract class AbstractKotlinCompile() : AbstractCompile() { + abstract protected val compiler: CLICompiler + abstract protected fun createBlankArgs(): T + open protected fun afterCompileHook(args: T) { + } + abstract protected fun populateTargetSpecificArgs(args: T) + + public var kotlinOptions: T = createBlankArgs() val srcDirsSources = HashSet() - val compiler = K2JVMCompiler() + + public var kotlinDestinationDir: File? = getDestinationDir() private val logger = Logging.getLogger(this.javaClass) override fun getLogger() = logger - public var kotlinOptions: K2JVMCompilerArguments = K2JVMCompilerArguments() - - public var kotlinDestinationDir : File? = getDestinationDir() - // override setSource to track source directory sets override fun setSource(source: Any?) { srcDirsSources.clear() @@ -71,48 +96,69 @@ public open class KotlinCompile(): AbstractCompile() { [TaskAction] override fun compile() { - - getLogger().debug("Starting Kotlin compilation task") - - val args = K2JVMCompilerArguments() - - val javaSrcRoots = HashSet() - val sources = ArrayList() - - // collect source directory roots for all java files to allow cross compilation - for (file in getSource()) { - if (FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("java")) { - val javaRoot = findSrcDirRoot(file) - if (javaRoot != null) { - javaSrcRoots.add(javaRoot) - } - } else { - sources.add(file) - } - } - + getLogger().debug("Starting ${javaClass} task") + val args = createBlankArgs() + val sources = getKotlinSources() if (sources.empty) { getLogger().warn("No Kotlin files found, skipping Kotlin compiler task") return } - args.suppressWarnings = kotlinOptions.suppressWarnings - args.version = kotlinOptions.version - args.verbose = logger.isDebugEnabled() + populateTargetSpecificArgs(args) + populateCommonArgs(args, sources) + callCompiler(args) + afterCompileHook(args) + } + protected fun isJava(it: File): Boolean = it.extension.equalsIgnoreCase(JavaFileType.INSTANCE.getDefaultExtension()) + protected fun isKotlin(it: File): Boolean = it.extension.equalsIgnoreCase(JetFileType.INSTANCE.getDefaultExtension()) + + private fun getKotlinSources(): List = getSource().filter{ isKotlin(it) } + + private fun populateCommonArgs(args: T, sources: List) { args.freeArgs = sources.map { it.getAbsolutePath() } + args.suppressWarnings = kotlinOptions.suppressWarnings + args.verbose = kotlinOptions.verbose + args.version = kotlinOptions.version + args.noInline = kotlinOptions.noInline + } + + private fun callCompiler(args: T) { + val messageCollector = GradleMessageCollector(getLogger()) + getLogger().debug("Calling compiler") + val exitCode = compiler.exec(messageCollector, Services.EMPTY, args) + + when (exitCode) { + ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details") + ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details") + } + } + +} + + +public open class KotlinCompile() : AbstractKotlinCompile() { + override val compiler = K2JVMCompiler() + + override fun createBlankArgs(): K2JVMCompilerArguments = K2JVMCompilerArguments() + + override fun populateTargetSpecificArgs(args: K2JVMCompilerArguments) { if (StringUtils.isEmpty(kotlinOptions.classpath)) { val existingClasspathEntries = getClasspath().filter({ it != null && it.exists() }) - val effectiveClassPath = (javaSrcRoots + existingClasspathEntries).makeString(File.pathSeparator) + val effectiveClassPath = (getJavaSourceRoots() + existingClasspathEntries).makeString(File.pathSeparator) args.classpath = effectiveClassPath } - args.destination = if (StringUtils.isEmpty(kotlinOptions.destination)) { kotlinDestinationDir?.getPath() } else { kotlinOptions.destination } + args.destination = if (StringUtils.isEmpty(kotlinOptions.destination)) { + kotlinDestinationDir?.getPath() + } else { + kotlinOptions.destination + } val embeddedAnnotations = getAnnotations(getProject(), getLogger()) val userAnnotations = (kotlinOptions.annotations ?: "").split(File.pathSeparatorChar).toList() - val allAnnotations = if (kotlinOptions.noJdkAnnotations) userAnnotations else userAnnotations.plus(embeddedAnnotations.map {it.getPath()}) + val allAnnotations = if (kotlinOptions.noJdkAnnotations) userAnnotations else userAnnotations.plus(embeddedAnnotations.map { it.getPath() }) args.annotations = allAnnotations.makeString(File.pathSeparator) args.noStdlib = true @@ -121,17 +167,15 @@ public open class KotlinCompile(): AbstractCompile() { args.noOptimize = kotlinOptions.noOptimize args.noCallAssertions = kotlinOptions.noCallAssertions args.noParamAssertions = kotlinOptions.noParamAssertions + } - val messageCollector = GradleMessageCollector(getLogger()) - getLogger().debug("Calling compiler") - val exitCode = compiler.exec(messageCollector, Services.EMPTY, args) - - when (exitCode) { - ExitCode.COMPILATION_ERROR -> throw GradleException("Compilation error. See log for more details") - ExitCode.INTERNAL_ERROR -> throw GradleException("Internal compiler error. See log for more details") - else -> {} - } + private fun getJavaSourceRoots(): Set = getSource() + .filter { isJava(it) } + .map { findSrcDirRoot(it) } + .filterNotNull() + .toSet() + override fun afterCompileHook(args: K2JVMCompilerArguments) { getLogger().debug("Copying resulting files to classes") // Copy kotlin classes to all classes directory val outputDirFile = File(args.destination!!) @@ -141,7 +185,58 @@ public open class KotlinCompile(): AbstractCompile() { } } -public open class KDoc(): SourceTask() { +public open class Kotlin2JsCompile() : AbstractKotlinCompile() { + override val compiler = K2JSCompiler() + + override fun createBlankArgs(): K2JSCompilerArguments { + val args = K2JSCompilerArguments() + args.libraryFiles = array() // defaults to null + return args + } + + public fun addLibraryFiles(vararg fs: String) { + kotlinOptions.libraryFiles = (kotlinOptions.libraryFiles + fs).copyToArray() + } + + public fun addLibraryFiles(vararg fs: File) { + val strs = fs.map { it.getPath() }.copyToArray() + addLibraryFiles(*strs) + } + + fun outputFile(): String? = kotlinOptions.outputFile + + public fun sourceMapDestinationDir(): File = File(outputFile()).directory + + { + getOutputs().file(MethodClosure(this, "outputFile")) + } + + override fun populateTargetSpecificArgs(args: K2JSCompilerArguments) { + args.outputFile = outputFile() + args.outputPrefix = kotlinOptions.outputPrefix + args.outputPostfix = kotlinOptions.outputPostfix + args.libraryFiles = (kotlinOptions.libraryFiles + getProject().getConfigurations().getByName("compile").map{ it.canonicalPath }).copyToArray() + args.target = kotlinOptions.target + args.sourceMap = kotlinOptions.sourceMap + + if (args.outputFile == null) { + throw GradleException("${getName()}.kotlinOptions.outputFile must be set to a string.") + } + + val outputDir = File(args.outputFile).directory + if (!outputDir.exists()) { + if (!outputDir.mkdirs()) { + throw GradleException("Failed to create output directory ${outputDir} or one of its ancestors") + } + } + + getLogger().debug("${getName()} set libraryFiles to ${args.libraryFiles.join(",")}") + getLogger().debug("${getName()} set outputFile to ${args.outputFile}") + + } +} + +public open class KDoc() : SourceTask() { private val logger = Logging.getLogger(this.javaClass) override fun getLogger() = logger @@ -161,7 +256,11 @@ public open class KDoc(): SourceTask() { val kdocOptions = kdocArgs.docConfig - cfg.docOutputDir = if ((kdocOptions.docOutputDir.length == 0) && (destinationDir != null)) { destinationDir!!.path } else { kdocOptions.docOutputDir } + cfg.docOutputDir = if ((kdocOptions.docOutputDir.length == 0) && (destinationDir != null)) { + destinationDir!!.path + } else { + kdocOptions.docOutputDir + } cfg.title = kdocOptions.title cfg.sourceRootHref = kdocOptions.sourceRootHref cfg.projectRootDir = kdocOptions.projectRootDir @@ -180,7 +279,7 @@ public open class KDoc(): SourceTask() { getLogger().warn(args.freeArgs.toString()) val embeddedAnnotations = getAnnotations(getProject(), getLogger()) val userAnnotations = (kdocArgs.annotations ?: "").split(File.pathSeparatorChar).toList() - val allAnnotations = if (kdocArgs.noJdkAnnotations) userAnnotations else userAnnotations.plus(embeddedAnnotations.map {it.getPath()}) + val allAnnotations = if (kdocArgs.noJdkAnnotations) userAnnotations else userAnnotations.plus(embeddedAnnotations.map { it.getPath() }) args.annotations = allAnnotations.makeString(File.pathSeparator) args.noStdlib = true @@ -195,7 +294,6 @@ public open class KDoc(): SourceTask() { when (exitCode) { ExitCode.COMPILATION_ERROR -> throw GradleException("Failed to generate kdoc. See log for more details") ExitCode.INTERNAL_ERROR -> throw GradleException("Internal generation error. See log for more details") - else -> {} } } @@ -205,14 +303,14 @@ fun getAnnotations(project: Project, logger: Logger): Collection { val annotations = project.getExtensions().getByName(DEFAULT_ANNOTATIONS) as Collection if (!annotations.isEmpty()) { - logger.info("using default annontations from [${annotations.map {it.getPath()}}]") + logger.info("using default annontations from [${annotations.map { it.getPath() }}]") return annotations } else { throw GradleException("Default annotations not found in Kotlin gradle plugin classpath") } } -class GradleMessageCollector(val logger : Logger): MessageCollector { +class GradleMessageCollector(val logger: Logger) : MessageCollector { public override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) { val text = with(StringBuilder()) { append(when (severity) { diff --git a/libraries/tools/kotlin-gradle-plugin/pom.xml b/libraries/tools/kotlin-gradle-plugin/pom.xml index 5156587b931..e6621f8e54f 100644 --- a/libraries/tools/kotlin-gradle-plugin/pom.xml +++ b/libraries/tools/kotlin-gradle-plugin/pom.xml @@ -93,6 +93,7 @@ org.jetbrains.kotlin:kotlin-gradle-plugin-core:${project.version} org.jetbrains.kotlin:kotlin-jdk-annotations:${project.version} + org.jetbrains.kotlin:kotlin-js-library:${project.version} org.jetbrains.kotlin:kotlin-android-sdk-annotations:${project.version} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 6eadc4ee21a..757619ca73c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -111,4 +111,11 @@ open class KotlinAndriodPluginWrapper: KotlinBasePluginWrapper() { public override fun getPluginClassName():String { return "org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPlugin" } -} \ No newline at end of file +} + +open class Kotlin2JsPluginWrapper : KotlinBasePluginWrapper() { + public override fun getPluginClassName():String { + return "org.jetbrains.kotlin.gradle.plugin.Kotlin2JsPlugin" + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin2js.properties b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin2js.properties new file mode 100644 index 00000000000..e599d2e654b --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/resources/META-INF/gradle-plugins/kotlin2js.properties @@ -0,0 +1 @@ +implementation-class=org.jetbrains.kotlin.gradle.plugin.Kotlin2JsPluginWrapper \ No newline at end of file