From 75e6be78024ff447a97b0e42a3c3805992a7558b Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Wed, 4 Apr 2018 14:59:17 +0300 Subject: [PATCH] jps(refactor): Extract platform related code #KT-23656 Fixed (cherry picked from commit 18a751d) --- .../jps/build/AbstractIncrementalJpsTest.kt | 3 + .../jps/build/BaseKotlinJpsBuildTestCase.kt | 2 + .../kotlin/jps/build/KotlinJpsBuildTest.kt | 27 ++- .../kotlin/jps/build/JpsJsModuleUtils.kt | 94 -------- .../kotlin/jps/build/KotlinBuilder.kt | 186 ++------------- .../KotlinBuilderModuleScriptGenerator.kt | 197 ---------------- .../jps/build/KotlinSourceFileCollector.kt | 18 -- .../KotlinCommonModuleBuildTarget.kt | 35 +++ .../platforms/KotlinJsModuleBuildTarget.kt | 154 ++++++++++++ .../platforms/KotlinJvmModuleBuildTarget.kt | 219 ++++++++++++++++++ .../platforms/KotlinModuleBuilderTarget.kt | 172 ++++++++++++++ .../kotlin/jps/platforms/TargetPlatform.kt | 75 ++++++ .../jetbrains/kotlin/modules/modulesUtil.kt | 33 --- 13 files changed, 698 insertions(+), 517 deletions(-) delete mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/build/JpsJsModuleUtils.kt delete mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilderModuleScriptGenerator.kt create mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinCommonModuleBuildTarget.kt create mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJsModuleBuildTarget.kt create mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJvmModuleBuildTarget.kt create mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinModuleBuilderTarget.kt create mode 100644 jps-plugin/src/org/jetbrains/kotlin/jps/platforms/TargetPlatform.kt delete mode 100644 jps-plugin/src/org/jetbrains/kotlin/modules/modulesUtil.kt diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt index 9af8ba59fd8..e0aee4d9a99 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt @@ -50,6 +50,9 @@ import org.jetbrains.kotlin.incremental.isJavaFile import org.jetbrains.kotlin.incremental.testingUtils.* import org.jetbrains.kotlin.jps.incremental.getKotlinCache import org.jetbrains.kotlin.jps.incremental.withLookupStorage +import org.jetbrains.kotlin.jps.model.JpsKotlinFacetModuleExtension +import org.jetbrains.kotlin.jps.model.kotlinFacetExtension +import org.jetbrains.kotlin.jps.platforms.clearKotlinModuleBuildTargetDataBindings import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.keysToMap diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/BaseKotlinJpsBuildTestCase.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/BaseKotlinJpsBuildTestCase.kt index cda9bfa406c..7f663bf5827 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/BaseKotlinJpsBuildTestCase.kt +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/BaseKotlinJpsBuildTestCase.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.jps.build import org.jetbrains.jps.builders.JpsBuildTestCase +import org.jetbrains.kotlin.jps.platforms.clearKotlinModuleBuildTargetDataBindings abstract class BaseKotlinJpsBuildTestCase : JpsBuildTestCase() { @Throws(Exception::class) @@ -33,5 +34,6 @@ abstract class BaseKotlinJpsBuildTestCase : JpsBuildTestCase() { super.tearDown() myModel = null myBuildParams.clear() + clearKotlinModuleBuildTargetDataBindings() } } \ No newline at end of file diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt index d4be2bf9517..a0bfa595162 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt @@ -62,6 +62,10 @@ import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.withIC import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings import org.jetbrains.kotlin.jps.build.KotlinJpsBuildTest.LibraryDependency.* +import org.jetbrains.kotlin.jps.platforms.KotlinJsModuleBuildTarget +import org.jetbrains.kotlin.jps.platforms.clearKotlinModuleBuildTargetDataBindings +import org.jetbrains.kotlin.jps.platforms.kotlinData +import org.jetbrains.kotlin.jps.platforms.productionBuildTarget import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.test.KotlinTestUtils @@ -248,17 +252,22 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() { } private fun k2jsOutput(vararg moduleNames: String): Array { - val list = arrayListOf() - for (moduleName in moduleNames) { - val outputDir = File("out/production/$moduleName") - list.add(toSystemIndependentName(JpsJsModuleUtils.getOutputFile(outputDir, moduleName, false).path)) - list.add(toSystemIndependentName(JpsJsModuleUtils.getOutputMetaFile(outputDir, moduleName, false).path)) + val moduleNamesSet = moduleNames.toSet() + val list = mutableListOf() - val kjsmFiles = File(workDir, outputDir.path).walk() - .filter { it.isFile && it.extension.equals("kjsm", ignoreCase = true) } - list.addAll(kjsmFiles.map { toSystemIndependentName(it.relativeTo(workDir).path) }) + myProject.modules.forEach { + if (it.name in moduleNamesSet) { + val productionTarget = it.productionBuildTarget.kotlinData as KotlinJsModuleBuildTarget + list.add(toSystemIndependentName(productionTarget.outputFile.relativeTo(workDir).path)) + list.add(toSystemIndependentName(productionTarget.outputMetaFile.relativeTo(workDir).path)) + val kjsmFiles = productionTarget.outputDir.walk() + .filter { it.isFile && it.extension.equals("kjsm", ignoreCase = true) } + + list.addAll(kjsmFiles.map { toSystemIndependentName(it.relativeTo(workDir).path) }) + } } + return list.toTypedArray() } @@ -1057,6 +1066,8 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() { } protected fun checkWhen(actions: Array, pathsToCompile: Array?, pathsToDelete: Array?) { + clearKotlinModuleBuildTargetDataBindings() + for (action in actions) { action.apply() } diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/JpsJsModuleUtils.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/JpsJsModuleUtils.kt deleted file mode 100644 index 02f8738c384..00000000000 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/JpsJsModuleUtils.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.jps.build - -import com.intellij.util.Consumer -import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType -import org.jetbrains.jps.incremental.ModuleBuildTarget -import org.jetbrains.jps.model.java.JavaSourceRootType -import org.jetbrains.jps.model.java.JpsJavaModuleType -import org.jetbrains.jps.model.library.JpsOrderRootType -import org.jetbrains.jps.model.module.JpsModule -import org.jetbrains.jps.util.JpsPathUtil -import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils -import java.io.File -import java.util.* - -object JpsJsModuleUtils { - fun getLibraryFilesAndDependencies(target: ModuleBuildTarget): List { - val result = ArrayList() - getLibraryFiles(target, result) - getDependencyModulesAndSources(target, result) - return result - } - - fun getLibraryFiles(target: ModuleBuildTarget, result: MutableList) { - val libraries = JpsUtils.getAllDependencies(target).libraries - for (library in libraries) { - for (root in library.getRoots(JpsOrderRootType.COMPILED)) { - result.add(JpsPathUtil.urlToPath(root.url)) - } - } - } - - fun getDependencyModulesAndSources(target: ModuleBuildTarget, result: MutableList) { - JpsUtils.getAllDependencies(target).processModules(object : Consumer { - override fun consume(module: JpsModule) { - if (module.moduleType != JpsJavaModuleType.INSTANCE) return - - if ((module != target.module || target.isTests) && module.hasProductionSourceRoot) { - addTarget(module, isTests = false) - } - - if (module != target.module && target.isTests && module.hasTestSourceRoot) { - addTarget(module, isTests = true) - } - } - - fun addTarget(module: JpsModule, isTests: Boolean) { - val metaInfoFile = getOutputMetaFile(module, isTests) - if (metaInfoFile.exists()) { - result.add(metaInfoFile.absolutePath) - } - } - }) - } - - @JvmStatic - fun getOutputMetaFile(module: JpsModule, isTests: Boolean): File { - val moduleBuildTarget = - ModuleBuildTarget(module, if (isTests) JavaModuleBuildTargetType.TEST else JavaModuleBuildTargetType.PRODUCTION) - val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(moduleBuildTarget) - return getOutputMetaFile(outputDir, module.name, isTests) - } - - @JvmStatic - fun getOutputFile(outputDir: File, moduleName: String, isTests: Boolean) = - File(outputDir, moduleName + suffix(isTests) + KotlinJavascriptMetadataUtils.JS_EXT) - - @JvmStatic - fun getOutputMetaFile(outputDir: File, moduleName: String, isTests: Boolean) = - File(outputDir, moduleName + suffix(isTests) + KotlinJavascriptMetadataUtils.META_JS_SUFFIX) - - private fun suffix(isTests: Boolean) = if (isTests) "_test" else "" -} - -val JpsModule.hasProductionSourceRoot - get() = sourceRoots.any { it.rootType == JavaSourceRootType.SOURCE } - -val JpsModule.hasTestSourceRoot - get() = sourceRoots.any { it.rootType == JavaSourceRootType.TEST_SOURCE } \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt index a8c3179dec2..81b6e20bf0f 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.jps.build import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.util.Key import com.intellij.openapi.util.io.FileUtil import com.intellij.util.containers.ContainerUtil @@ -32,8 +31,6 @@ import org.jetbrains.jps.builders.java.JavaBuilderUtil import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor import org.jetbrains.jps.incremental.* import org.jetbrains.jps.incremental.ModuleLevelBuilder.ExitCode.* -import org.jetbrains.jps.incremental.Utils.* -import org.jetbrains.jps.incremental.fs.CompilationRound import org.jetbrains.jps.incremental.java.JavaBuilder import org.jetbrains.jps.incremental.messages.BuildMessage import org.jetbrains.jps.incremental.messages.CompilerMessage @@ -58,23 +55,21 @@ import org.jetbrains.kotlin.daemon.common.isDaemonEnabled import org.jetbrains.kotlin.incremental.* import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings -import org.jetbrains.kotlin.jps.build.JpsJsModuleUtils.getOutputMetaFile import org.jetbrains.kotlin.jps.incremental.* -import org.jetbrains.kotlin.jps.productionOutputFilePath -import org.jetbrains.kotlin.jps.testOutputFilePath +import org.jetbrains.kotlin.jps.platforms.KotlinJsModuleBuildTarget +import org.jetbrains.kotlin.jps.platforms.kotlinData import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCompilationComponents -import org.jetbrains.kotlin.modules.TargetId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.preloading.ClassCondition import org.jetbrains.kotlin.progress.CompilationCanceledException import org.jetbrains.kotlin.progress.CompilationCanceledStatus -import org.jetbrains.kotlin.utils.* -import org.jetbrains.kotlin.utils.addToStdlib.flattenTo +import org.jetbrains.kotlin.utils.KotlinPaths +import org.jetbrains.kotlin.utils.KotlinPathsFromHomeDir +import org.jetbrains.kotlin.utils.PathUtil +import org.jetbrains.kotlin.utils.keysToMap import org.jetbrains.org.objectweb.asm.ClassReader import java.io.File -import java.io.IOException -import java.net.URI import java.util.* import kotlin.collections.HashSet @@ -218,7 +213,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { val allVersions = cacheVersionsProvider.allVersions(targets) val actions = allVersions.map { it.checkVersion() }.toMutableSet() - if (!JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { + if (chunk.representativeTarget().kotlinData !is KotlinJsModuleBuildTarget) { val args = compilerArgumentsForChunk(chunk) val currentBuildMetaInfo = JvmBuildMetaInfo(args) @@ -395,10 +390,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { rebuildAfterCacheVersionChanged.clean(target) } - if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { - copyJsLibraryFilesIfNeeded(chunk) - return OK - } + chunk.representativeTarget().kotlinData?.doAfterBuild() updateJavaMappings(chunk, context, dirtyFilesHolder, filesToCompile, generatedFiles, incrementalCaches) @@ -543,11 +535,6 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { LOG.debug("Plugin loaded: ${argumentProvider::class.java.simpleName}") } - if (JpsUtils.isJsKotlinModule(chunk.representativeTarget())) { - LOG.debug("Compiling to JS ${filesToCompile.values().size} files in ${filesToCompile.keySet().joinToString { it.presentableName }}") - return compileToJs(chunk, commonArguments, environment, project) - } - if (IncrementalCompilation.isEnabled()) { for (target in chunk.targets) { val cache = incrementalCaches[target]!! @@ -556,7 +543,12 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { } } - return compileToJvm(allCompiledFiles, chunk, commonArguments, context, dirtyFilesHolder, environment, filesToCompile) + val isDoneSomething = representativeTarget.kotlinData?.compileModuleChunk( + allCompiledFiles, chunk, commonArguments, context, + dirtyFilesHolder, environment, filesToCompile + ) ?: false + + return if (isDoneSomething) environment.outputItemsCollector else null } private fun createCompileEnvironment( @@ -569,7 +561,7 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { register(LookupTracker::class.java, lookupTracker) register( IncrementalCompilationComponents::class.java, - IncrementalCompilationComponentsImpl(incrementalCaches.mapKeys { TargetId(it.key) }) + IncrementalCompilationComponentsImpl(incrementalCaches.mapKeys { it.key.kotlinData!!.moduleId }) ) register(CompilationCanceledStatus::class.java, object : CompilationCanceledStatus { override fun checkCanceled() { @@ -626,8 +618,8 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { val sourceToTarget = HashMap() if (chunk.targets.size > 1) { for (target in chunk.targets) { - for (file in KotlinSourceFileCollector.getAllKotlinSourceFiles(target)) { - sourceToTarget[file] = target + target.kotlinData?.sources?.forEach { + sourceToTarget[it] = target } } } @@ -720,146 +712,6 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { } } - // if null is returned, nothing was done - private fun compileToJs( - chunk: ModuleChunk, - commonArguments: CommonCompilerArguments, - environment: JpsCompilerEnvironment, - project: JpsProject - ): OutputItemsCollector? { - val representativeTarget = chunk.representativeTarget() - if (chunk.modules.size > 1) { - // We do not support circular dependencies, but if they are present, we do our best should not break the build, - // so we simply yield a warning and report NOTHING_DONE - environment.messageCollector.report( - STRONG_WARNING, - "Circular dependencies are not supported. The following JS modules depend on each other: " - + chunk.modules.joinToString(", ") { it.name } + ". " - + "Kotlin is not compiled for these modules" - ) - return null - } - - val sourceFiles = KotlinSourceFileCollector.getAllKotlinSourceFiles(representativeTarget) - if (sourceFiles.isEmpty()) { - return null - } - - val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(representativeTarget) - - val representativeModule = representativeTarget.module - val moduleName = representativeModule.name - val isTests = representativeTarget.isTests - val explicitOutputPath = if (isTests) representativeModule.testOutputFilePath else representativeModule.productionOutputFilePath - val outputFile = explicitOutputPath?.let { File(it) } ?: JpsJsModuleUtils.getOutputFile(outputDir, moduleName, isTests) - val libraries = JpsJsModuleUtils.getLibraryFilesAndDependencies(representativeTarget) - val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeModule) - val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(representativeModule) - - // Compiler starts to produce path relative to base dirs in source maps if at least one statement is true: - // 1) base dirs are specified; - // 2) prefix is specified (i.e. non-empty) - // Otherwise compiler produces paths relative to source maps location. - // We don't have UI to configure base dirs, but we have UI to configure prefix. - // If prefix is not specified (empty) in UI, we want to produce paths relative to source maps location - val sourceRoots = if (k2JsArguments.sourceMapPrefix.isNullOrBlank()) { - emptyList() - } else { - representativeModule.contentRootsList.urls - .map { URI.create(it) } - .filter { it.scheme == "file" } - .map { File(it.path) } - } - - val friendPaths = KotlinBuilderModuleScriptGenerator.getProductionModulesWhichInternalsAreVisible(representativeTarget).mapNotNull { - val file = getOutputMetaFile(it, false) - if (file.exists()) file.absolutePath.toString() else null - } - - val compilerRunner = JpsKotlinCompilerRunner() - compilerRunner.runK2JsCompiler( - commonArguments, k2JsArguments, compilerSettings, environment, sourceFiles, sourceRoots, - libraries, friendPaths, outputFile - ) - return environment.outputItemsCollector - } - - private fun copyJsLibraryFilesIfNeeded(chunk: ModuleChunk) { - val representativeTarget = chunk.representativeTarget() - val outputDir = KotlinBuilderModuleScriptGenerator.getOutputDirSafe(representativeTarget) - val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeTarget.module) - val k2jsCompilerSettings = JpsKotlinCompilerSettings.getK2JsCompilerArguments(representativeTarget.module) - if (compilerSettings.copyJsLibraryFiles) { - val outputLibraryRuntimeDirectory = File(outputDir, compilerSettings.outputDirectoryForJsLibraryFiles).absolutePath - val libraryFilesToCopy = arrayListOf() - JpsJsModuleUtils.getLibraryFiles(representativeTarget, libraryFilesToCopy) - JsLibraryUtils.copyJsFilesFromLibraries( - libraryFilesToCopy, outputLibraryRuntimeDirectory, - copySourceMap = k2jsCompilerSettings.sourceMap - ) - } - } - - // if null is returned, nothing was done - private fun compileToJvm( - allCompiledFiles: MutableSet, - chunk: ModuleChunk, - commonArguments: CommonCompilerArguments, - context: CompileContext, - dirtyFilesHolder: DirtyFilesHolder, - environment: JpsCompilerEnvironment, - filesToCompile: MultiMap - ): OutputItemsCollector? { - if (chunk.modules.size > 1) { - environment.messageCollector.report( - STRONG_WARNING, - "Circular dependencies are only partially supported. The following modules depend on each other: " - + chunk.modules.joinToString(", ") { it.name } + ". " - + "Kotlin will compile them, but some strange effect may happen" - ) - } - - allCompiledFiles.addAll(filesToCompile.values()) - - val processedTargetsWithRemoved = getProcessedTargetsWithRemovedFilesContainer(context) - - var totalRemovedFiles = 0 - for (target in chunk.targets) { - val removedFilesInTarget = KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target) - if (!removedFilesInTarget.isEmpty()) { - if (processedTargetsWithRemoved.add(target)) { - totalRemovedFiles += removedFilesInTarget.size - } - } - } - - val moduleFile = - KotlinBuilderModuleScriptGenerator.generateModuleDescription(context, chunk, filesToCompile, totalRemovedFiles != 0) - if (moduleFile == null) { - KotlinBuilder.LOG.debug("Not compiling, because no files affected: " + filesToCompile.keySet().joinToString { it.presentableName }) - // No Kotlin sources found - return null - } - - val module = chunk.representativeTarget().module - val k2JvmArguments = JpsKotlinCompilerSettings.getK2JvmCompilerArguments(module) - val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(module) - - KotlinBuilder.LOG.debug("Compiling to JVM ${filesToCompile.values().size} files" - + (if (totalRemovedFiles == 0) "" else " ($totalRemovedFiles removed files)") - + " in " + filesToCompile.keySet().joinToString { it.presentableName }) - - try { - val compilerRunner = JpsKotlinCompilerRunner() - compilerRunner.runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, environment, moduleFile) - } finally { - if (System.getProperty("kotlin.jps.delete.module.file.after.build") != "false") { - moduleFile.delete() - } - } - - return environment.outputItemsCollector - } class MessageCollectorAdapter(private val context: CompileContext) : MessageCollector { private var hasErrors = false @@ -1006,7 +858,7 @@ private fun getDependenciesRecursively(module: JpsModule, kind: JpsJavaClasspath // TODO: investigate thread safety private val ALL_COMPILED_FILES_KEY = Key.create>("_all_kotlin_compiled_files_") -private fun getAllCompiledFilesContainer(context: CompileContext): MutableSet { +fun getAllCompiledFilesContainer(context: CompileContext): MutableSet { var allCompiledFiles = ALL_COMPILED_FILES_KEY.get(context) if (allCompiledFiles == null) { allCompiledFiles = THashSet(FileUtil.FILE_HASHING_STRATEGY) @@ -1018,7 +870,7 @@ private fun getAllCompiledFilesContainer(context: CompileContext): MutableSet>("_processed_targets_with_removed_files_") -private fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet { +fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet { var set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context) if (set == null) { set = HashSet() diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilderModuleScriptGenerator.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilderModuleScriptGenerator.kt deleted file mode 100644 index 5a339d9bda7..00000000000 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilderModuleScriptGenerator.kt +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.jps.build - -import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.util.SmartList -import com.intellij.util.containers.ContainerUtil -import com.intellij.util.containers.MultiMap -import com.intellij.util.io.URLUtil -import org.jetbrains.jps.ModuleChunk -import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType -import org.jetbrains.jps.incremental.CompileContext -import org.jetbrains.jps.incremental.ModuleBuildTarget -import org.jetbrains.jps.incremental.ProjectBuildException -import org.jetbrains.jps.model.java.JpsJavaExtensionService -import org.jetbrains.jps.model.module.JpsModule -import org.jetbrains.jps.model.module.JpsSdkDependency -import org.jetbrains.kotlin.build.JvmSourceRoot -import org.jetbrains.kotlin.config.IncrementalCompilation -import org.jetbrains.kotlin.jps.build.JpsUtils.getAllDependencies -import org.jetbrains.kotlin.jps.productionOutputFilePath -import org.jetbrains.kotlin.jps.testOutputFilePath -import org.jetbrains.kotlin.modules.KotlinModuleXmlBuilder -import org.jetbrains.kotlin.modules.TargetId -import org.jetbrains.kotlin.utils.addIfNotNull -import java.io.File -import java.io.IOException -import java.util.* - -object KotlinBuilderModuleScriptGenerator { - fun getRelatedProductionModule(module: JpsModule): JpsModule? = - JpsJavaExtensionService.getInstance().getTestModuleProperties(module)?.productionModule - - fun generateModuleDescription( - context: CompileContext, - chunk: ModuleChunk, - sourceFiles: MultiMap, // ignored for non-incremental compilation - hasRemovedFiles: Boolean - ): File? { - val builder = KotlinModuleXmlBuilder() - - var noSources = true - - val outputDirs = HashSet() - for (target in chunk.targets) { - outputDirs.add(getOutputDirSafe(target)) - } - - val logger = context.loggingManager.projectBuilderLogger - for (target in chunk.targets) { - val outputDir = getOutputDirSafe(target) - val friendDirs = getAdditionalOutputDirsWhereInternalsAreVisible(target) - - val moduleSources = ArrayList( - if (IncrementalCompilation.isEnabled()) - sourceFiles.get(target) - else - KotlinSourceFileCollector.getAllKotlinSourceFiles(target) - ) - - if (moduleSources.size > 0 || hasRemovedFiles) { - noSources = false - - if (logger.isEnabled) { - logger.logCompiledFiles(moduleSources, KotlinBuilder.KOTLIN_BUILDER_NAME, "Compiling files:") - } - } - - val targetType = target.targetType - assert(targetType is JavaModuleBuildTargetType) - val targetId = TargetId(target) - builder.addModule( - targetId.name, - outputDir.absolutePath, - moduleSources, - findSourceRoots(context, target), - findClassPathRoots(target), - findModularJdkRoot(target), - targetId.type, - (targetType as JavaModuleBuildTargetType).isTests, - // this excludes the output directories from the class path, to be removed for true incremental compilation - outputDirs, - friendDirs - ) - } - - if (noSources) return null - - val scriptFile = createTempFileForModuleDesc(chunk) - FileUtil.writeToFile(scriptFile, builder.asText().toString()) - return scriptFile - } - - private fun createTempFileForModuleDesc(chunk: ModuleChunk): File { - val readableSuffix = buildString { - append(StringUtil.sanitizeJavaIdentifier(chunk.representativeTarget().module.name)) - if (chunk.containsTests()) { - append("-test") - } - } - val dir = System.getProperty("kotlin.jps.dir.for.module.files")?.let { File(it) }?.takeIf { it.isDirectory } - return try { - File.createTempFile("kjps", readableSuffix + ".script.xml", dir) - } catch (e: IOException) { - // sometimes files cannot be created, because file name is too long (Windows, Mac OS) - // see https://bugs.openjdk.java.net/browse/JDK-8148023 - try { - File.createTempFile("kjps", ".script.xml", dir) - } catch (e: IOException) { - val message = buildString { - append("Could not create module file when building chunk $chunk") - if (dir != null) { - append(" in dir $dir") - } - } - throw RuntimeException(message, e) - } - } - } - - fun getOutputDirSafe(target: ModuleBuildTarget): File { - val explicitOutputPath = if (target.isTests) target.module.testOutputFilePath else target.module.productionOutputFilePath - val explicitOutputDir = explicitOutputPath?.let { File(it).absoluteFile.parentFile } - return explicitOutputDir ?: target.outputDir ?: throw ProjectBuildException("No output directory found for " + target) - } - - fun getProductionModulesWhichInternalsAreVisible(from: ModuleBuildTarget): List { - if (!from.isTests) return emptyList() - - val result = SmartList(from.module) - result.addIfNotNull(getRelatedProductionModule(from.module)) - - return result.filter { it.hasProductionSourceRoot } - } - - fun getAdditionalOutputDirsWhereInternalsAreVisible(target: ModuleBuildTarget): List { - return getProductionModulesWhichInternalsAreVisible(target).mapNotNullTo(SmartList()) { - JpsJavaExtensionService.getInstance().getOutputDirectory(it, false) - } - } - - private fun findClassPathRoots(target: ModuleBuildTarget): Collection { - return getAllDependencies(target).classes().roots.filter { file -> - if (!file.exists()) { - val extension = file.extension - - // Don't filter out files, we want to report warnings about absence through the common place - if (!(extension == "class" || extension == "jar")) { - return@filter false - } - } - - true - } - } - - private fun findModularJdkRoot(target: ModuleBuildTarget): File? { - // List of paths to JRE modules in the following format: - // jrt:///Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home!/java.base - val urls = JpsJavaExtensionService.dependencies(target.module) - .satisfying { dependency -> dependency is JpsSdkDependency } - .classes().urls - - val url = urls.firstOrNull { it.startsWith(StandardFileSystems.JRT_PROTOCOL_PREFIX) } ?: return null - - return File(url.substringAfter(StandardFileSystems.JRT_PROTOCOL_PREFIX).substringBeforeLast(URLUtil.JAR_SEPARATOR)) - } - - private fun findSourceRoots(context: CompileContext, target: ModuleBuildTarget): List { - val roots = context.projectDescriptor.buildRootIndex.getTargetRoots(target, context) - val result = ContainerUtil.newArrayList() - for (root in roots) { - val file = root.rootFile - val prefix = root.packagePrefix - if (file.exists()) { - result.add(JvmSourceRoot(file, if (prefix.isEmpty()) null else prefix)) - } - } - return result - } -} diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinSourceFileCollector.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinSourceFileCollector.kt index 9a32e094267..3ea374dfb86 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinSourceFileCollector.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinSourceFileCollector.kt @@ -23,9 +23,7 @@ import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor import org.jetbrains.jps.incremental.ModuleBuildTarget import org.jetbrains.jps.model.java.JavaSourceRootProperties import org.jetbrains.jps.model.java.JavaSourceRootType -import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.model.module.JpsModuleSourceRoot -import org.jetbrains.jps.util.JpsPathUtil import java.io.File @@ -52,22 +50,6 @@ object KotlinSourceFileCollector { .getRemovedFiles(target) .mapNotNull { if (FileUtilRt.extensionEquals(it, "kt")) File(it) else null } - fun getAllKotlinSourceFiles(target: ModuleBuildTarget): List { - val moduleExcludes = target.module.excludeRootsList.urls.mapTo(HashSet(), JpsPathUtil::urlToFile) - - val compilerExcludes = JpsJavaExtensionService.getInstance() - .getOrCreateCompilerConfiguration(target.module.project) - .compilerExcludes - - val allKotlinFiles = ArrayList() - for (sourceRoot in getRelevantSourceRoots(target)) { - sourceRoot.file.walkTopDown() - .onEnter { it !in moduleExcludes } - .filterTo(allKotlinFiles) { !compilerExcludes.isExcluded(it) && it.isFile && isKotlinSourceFile(it) } - } - return allKotlinFiles - } - private fun getRelevantSourceRoots(target: ModuleBuildTarget): Iterable { val sourceRootType = if (target.isTests) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE return target.module.getSourceRoots(sourceRootType) diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinCommonModuleBuildTarget.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinCommonModuleBuildTarget.kt new file mode 100644 index 00000000000..4f1e3681714 --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinCommonModuleBuildTarget.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jps.platforms + +import com.intellij.util.containers.MultiMap +import org.jetbrains.jps.ModuleChunk +import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.incremental.CompileContext +import org.jetbrains.jps.incremental.ModuleBuildTarget +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.compilerRunner.JpsCompilerEnvironment +import java.io.File + +class KotlinCommonModuleBuildTarget(jpsModuleBuildTarget: ModuleBuildTarget) : + KotlinModuleBuilderTarget(jpsModuleBuildTarget) { + override fun compileModuleChunk( + allCompiledFiles: MutableSet, + chunk: ModuleChunk, + commonArguments: CommonCompilerArguments, + context: CompileContext, + dirtyFilesHolder: DirtyFilesHolder, + environment: JpsCompilerEnvironment, + filesToCompile: MultiMap + ): Boolean { + require(chunk.representativeTarget() == jpsModuleBuildTarget) + if (reportAndSkipCircular(chunk, environment)) return false + + // TODO: Compile metadata + return false + } +} \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJsModuleBuildTarget.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJsModuleBuildTarget.kt new file mode 100644 index 00000000000..a0a1eef3557 --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJsModuleBuildTarget.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jps.platforms + +import com.intellij.util.containers.MultiMap +import org.jetbrains.jps.ModuleChunk +import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.incremental.CompileContext +import org.jetbrains.jps.incremental.ModuleBuildTarget +import org.jetbrains.jps.model.library.JpsOrderRootType +import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.util.JpsPathUtil +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.compilerRunner.JpsCompilerEnvironment +import org.jetbrains.kotlin.compilerRunner.JpsKotlinCompilerRunner +import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings +import org.jetbrains.kotlin.jps.productionOutputFilePath +import org.jetbrains.kotlin.jps.testOutputFilePath +import org.jetbrains.kotlin.utils.JsLibraryUtils +import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils.JS_EXT +import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils.META_JS_SUFFIX +import java.io.File +import java.net.URI + +class KotlinJsModuleBuildTarget(jpsModuleBuildTarget: ModuleBuildTarget) : KotlinModuleBuilderTarget(jpsModuleBuildTarget) { + val jsCompilerArguments + get() = JpsKotlinCompilerSettings.getK2JsCompilerArguments(module) + + val outputFile + get() = explicitOutputPath?.let { File(it) } ?: implicitOutputFile + + val explicitOutputPath + get() = if (isTests) module.testOutputFilePath else module.productionOutputFilePath + + val implicitOutputFile: File + get() { + val suffix = if (isTests) "_test" else "" + + return File(outputDir, module.name + suffix + JS_EXT) + } + + val outputFileBaseName: String + get() = outputFile.path.substringBeforeLast(".") + + val outputMetaFile: File + get() = File(outputFileBaseName + META_JS_SUFFIX) + + val libraryFiles: List + get() = mutableListOf().also { result -> + for (library in allDependencies.libraries) { + for (root in library.getRoots(JpsOrderRootType.COMPILED)) { + result.add(JpsPathUtil.urlToPath(root.url)) + } + } + } + + val dependenciesMetaFiles: List + get() = mutableListOf().also { result -> + allDependencies.processModules { module -> + if (isTests) addDependencyMetaFile(module, result, isTests = true) + + // production targets should be also added as dependency to test targets + addDependencyMetaFile(module, result, isTests = false) + } + } + + private fun addDependencyMetaFile( + module: JpsModule, + result: MutableList, + isTests: Boolean + ) { + val dependencyBuildTarget = ModuleBuildTarget(module, isTests).kotlinData + + if (dependencyBuildTarget != this@KotlinJsModuleBuildTarget && + dependencyBuildTarget is KotlinJsModuleBuildTarget && + dependencyBuildTarget.sources.isNotEmpty() + ) { + val metaFile = dependencyBuildTarget.outputMetaFile + if (metaFile.exists()) { + result.add(metaFile.absolutePath) + } + } + } + + override fun compileModuleChunk( + allCompiledFiles: MutableSet, + chunk: ModuleChunk, + commonArguments: CommonCompilerArguments, + context: CompileContext, + dirtyFilesHolder: DirtyFilesHolder, + environment: JpsCompilerEnvironment, + filesToCompile: MultiMap + ): Boolean { + require(chunk.representativeTarget() == jpsModuleBuildTarget) + if (reportAndSkipCircular(chunk, environment)) return false + + val sources = sources + if (sources.isEmpty()) return false + + // Compiler starts to produce path relative to base dirs in source maps if at least one statement is true: + // 1) base dirs are specified; + // 2) prefix is specified (i.e. non-empty) + // Otherwise compiler produces paths relative to source maps location. + // We don't have UI to configure base dirs, but we have UI to configure prefix. + // If prefix is not specified (empty) in UI, we want to produce paths relative to source maps location + val sourceRoots = if (jsCompilerArguments.sourceMapPrefix.isNullOrBlank()) { + emptyList() + } else { + module.contentRootsList.urls + .map { URI.create(it) } + .filter { it.scheme == "file" } + .map { File(it.path) } + } + + val friendPaths = friendBuildTargets.mapNotNull { + (it as? KotlinJsModuleBuildTarget)?.outputMetaFile?.absoluteFile?.toString() + } + + val libraries = libraryFiles + dependenciesMetaFiles + + val compilerRunner = JpsKotlinCompilerRunner() + compilerRunner.runK2JsCompiler( + commonArguments, + jsCompilerArguments, + compilerSettings, + environment, + sources, + sourceRoots, + libraries, + friendPaths, + outputFile + ) + + return true + } + + override fun doAfterBuild() { + copyJsLibraryFilesIfNeeded() + } + + private fun copyJsLibraryFilesIfNeeded() { + if (compilerSettings.copyJsLibraryFiles) { + val outputLibraryRuntimeDirectory = File(outputDir, compilerSettings.outputDirectoryForJsLibraryFiles).absolutePath + JsLibraryUtils.copyJsFilesFromLibraries( + libraryFiles, outputLibraryRuntimeDirectory, + copySourceMap = jsCompilerArguments.sourceMap + ) + } + } +} \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJvmModuleBuildTarget.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJvmModuleBuildTarget.kt new file mode 100644 index 00000000000..83a2dacaf21 --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinJvmModuleBuildTarget.kt @@ -0,0 +1,219 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jps.platforms + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.containers.MultiMap +import com.intellij.util.io.URLUtil +import org.jetbrains.jps.ModuleChunk +import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.incremental.CompileContext +import org.jetbrains.jps.incremental.ModuleBuildTarget +import org.jetbrains.jps.model.java.JpsJavaExtensionService +import org.jetbrains.jps.model.module.JpsSdkDependency +import org.jetbrains.kotlin.build.JvmSourceRoot +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.compilerRunner.JpsCompilerEnvironment +import org.jetbrains.kotlin.compilerRunner.JpsKotlinCompilerRunner +import org.jetbrains.kotlin.config.IncrementalCompilation +import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings +import org.jetbrains.kotlin.jps.build.* +import org.jetbrains.kotlin.modules.KotlinModuleXmlBuilder +import java.io.File +import java.io.IOException + +class KotlinJvmModuleBuildTarget(jpsModuleBuildTarget: ModuleBuildTarget) : KotlinModuleBuilderTarget(jpsModuleBuildTarget) { + override fun compileModuleChunk( + allCompiledFiles: MutableSet, + chunk: ModuleChunk, + commonArguments: CommonCompilerArguments, + context: CompileContext, + dirtyFilesHolder: DirtyFilesHolder, + environment: JpsCompilerEnvironment, + filesToCompile: MultiMap + ): Boolean { + if (chunk.modules.size > 1) { + environment.messageCollector.report( + CompilerMessageSeverity.STRONG_WARNING, + "Circular dependencies are only partially supported. The following modules depend on each other: " + + chunk.modules.joinToString(", ") { it.name } + " " + + "Kotlin will compile them, but some strange effect may happen" + ) + } + + allCompiledFiles.addAll(filesToCompile.values()) + + val processedTargetsWithRemoved = getProcessedTargetsWithRemovedFilesContainer(context) + + var totalRemovedFiles = 0 + for (target in chunk.targets) { + val removedFilesInTarget = KotlinSourceFileCollector.getRemovedKotlinFiles(dirtyFilesHolder, target) + if (!removedFilesInTarget.isEmpty()) { + if (processedTargetsWithRemoved.add(target)) { + totalRemovedFiles += removedFilesInTarget.size + } + } + } + + val moduleFile = generateModuleDescription(context, chunk, filesToCompile, totalRemovedFiles != 0) + if (moduleFile == null) { + KotlinBuilder.LOG.debug( + "Not compiling, because no files affected: " + filesToCompile.keySet().joinToString { it.presentableName } + ) + // No Kotlin sources found + return false + } + + val module = chunk.representativeTarget().module + val k2JvmArguments = JpsKotlinCompilerSettings.getK2JvmCompilerArguments(module) + val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(module) + + KotlinBuilder.LOG.debug("Compiling to JVM ${filesToCompile.values().size} files" + + (if (totalRemovedFiles == 0) "" else " ($totalRemovedFiles removed files)") + + " in " + filesToCompile.keySet().joinToString { it.presentableName }) + + try { + val compilerRunner = JpsKotlinCompilerRunner() + compilerRunner.runK2JvmCompiler(commonArguments, k2JvmArguments, compilerSettings, environment, moduleFile) + } finally { + if (System.getProperty("kotlin.jps.delete.module.file.after.build") != "false") { + moduleFile.delete() + } + } + + return true + } + + fun generateModuleDescription( + context: CompileContext, + chunk: ModuleChunk, + sourceFiles: MultiMap, // ignored for non-incremental compilation + hasRemovedFiles: Boolean + ): File? { + val builder = KotlinModuleXmlBuilder() + + var noSources = true + + val targets = chunk.targets.mapNotNull { it.kotlinData as? KotlinJvmModuleBuildTarget } + + val outputDirs = targets.map { it.outputDir }.toSet() + + val logger = context.loggingManager.projectBuilderLogger + for (target in targets) { + val outputDir = target.outputDir + val friendDirs = target.friendOutputDirs + + val moduleSources = + if (IncrementalCompilation.isEnabled() && target.expectedBy.isEmpty()) { + sourceFiles.get(target.jpsModuleBuildTarget) + } else { + target.sources + } + + if (moduleSources.isNotEmpty() || hasRemovedFiles) { + noSources = false + + if (logger.isEnabled) { + logger.logCompiledFiles(moduleSources, KotlinBuilder.KOTLIN_BUILDER_NAME, "Compiling files:") + } + } + + val kotlinModuleId = target.moduleId + builder.addModule( + kotlinModuleId.name, + outputDir.absolutePath, + moduleSources, + target.findSourceRoots(context), + target.findClassPathRoots(), + target.findModularJdkRoot(), + kotlinModuleId.type, + isTests, + // this excludes the output directories from the class path, to be removed for true incremental compilation + outputDirs, + friendDirs + ) + } + + if (noSources) return null + + val scriptFile = createTempFileForModuleDesc(chunk) + FileUtil.writeToFile(scriptFile, builder.asText().toString()) + return scriptFile + } + + private fun createTempFileForModuleDesc(chunk: ModuleChunk): File { + val readableSuffix = buildString { + append(StringUtil.sanitizeJavaIdentifier(chunk.representativeTarget().module.name)) + if (chunk.containsTests()) { + append("-test") + } + } + val dir = System.getProperty("kotlin.jps.dir.for.module.files")?.let { File(it) }?.takeIf { it.isDirectory } + return try { + File.createTempFile("kjps", readableSuffix + ".script.xml", dir) + } catch (e: IOException) { + // sometimes files cannot be created, because file name is too long (Windows, Mac OS) + // see https://bugs.openjdk.java.net/browse/JDK-8148023 + try { + File.createTempFile("kjps", ".script.xml", dir) + } catch (e: IOException) { + val message = buildString { + append("Could not create module file when building chunk $chunk") + if (dir != null) { + append(" in dir $dir") + } + } + throw RuntimeException(message, e) + } + } + } + + fun findClassPathRoots(): Collection { + return allDependencies.classes().roots.filter { file -> + if (!file.exists()) { + val extension = file.extension + + // Don't filter out files, we want to report warnings about absence through the common place + if (!(extension == "class" || extension == "jar")) { + return@filter false + } + } + + true + } + } + + fun findModularJdkRoot(): File? { + // List of paths to JRE modules in the following format: + // jrt:///Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home!/java.base + val urls = JpsJavaExtensionService.dependencies(module) + .satisfying { dependency -> dependency is JpsSdkDependency } + .classes().urls + + val url = urls.firstOrNull { it.startsWith(StandardFileSystems.JRT_PROTOCOL_PREFIX) } ?: return null + + return File(url.substringAfter(StandardFileSystems.JRT_PROTOCOL_PREFIX).substringBeforeLast(URLUtil.JAR_SEPARATOR)) + } + + fun findSourceRoots(context: CompileContext): List { + val roots = context.projectDescriptor.buildRootIndex.getTargetRoots(jpsModuleBuildTarget, context) + val result = ContainerUtil.newArrayList() + for (root in roots) { + val file = root.rootFile + val prefix = root.packagePrefix + if (file.exists()) { + result.add(JvmSourceRoot(file, if (prefix.isEmpty()) null else prefix)) + } + } + return result + } +} \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinModuleBuilderTarget.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinModuleBuilderTarget.kt new file mode 100644 index 00000000000..eb9948d596b --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/KotlinModuleBuilderTarget.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jps.platforms + +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.util.containers.MultiMap +import org.jetbrains.jps.ModuleChunk +import org.jetbrains.jps.builders.DirtyFilesHolder +import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor +import org.jetbrains.jps.incremental.CompileContext +import org.jetbrains.jps.incremental.ModuleBuildTarget +import org.jetbrains.jps.incremental.ProjectBuildException +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.java.JpsJavaClasspathKind +import org.jetbrains.jps.model.java.JpsJavaExtensionService +import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.util.JpsPathUtil +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.compilerRunner.JpsCompilerEnvironment +import org.jetbrains.kotlin.jps.JpsKotlinCompilerSettings +import org.jetbrains.kotlin.jps.build.KotlinSourceFileCollector +import org.jetbrains.kotlin.jps.model.kotlinFacetExtension +import org.jetbrains.kotlin.jps.productionOutputFilePath +import org.jetbrains.kotlin.jps.testOutputFilePath +import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.utils.addIfNotNull +import java.io.File + +abstract class KotlinModuleBuilderTarget(val jpsModuleBuildTarget: ModuleBuildTarget) { + val module: JpsModule + get() = jpsModuleBuildTarget.module + + val isTests: Boolean + get() = jpsModuleBuildTarget.isTests + + val moduleId: TargetId + get() { + // Since IDEA 2016 each gradle source root is imported as a separate module. + // One gradle module X is imported as two JPS modules: + // 1. X-production with one production target; + // 2. X-test with one test target. + // This breaks kotlin code since internal members' names are mangled using module name. + // For example, a declaration of a function 'f' in 'X-production' becomes 'fXProduction', but a call 'f' in 'X-test' becomes 'fXTest()'. + // The workaround is to replace a name of such test target with the name of corresponding production module. + // See KT-11993. + val name = relatedProductionModule?.name ?: jpsModuleBuildTarget.id + return TargetId(name, jpsModuleBuildTarget.targetType.typeId) + } + + val kotlinFacetExtension by lazy { module.kotlinFacetExtension } + + val outputDir by lazy { + val explicitOutputPath = if (isTests) module.testOutputFilePath else module.productionOutputFilePath + val explicitOutputDir = explicitOutputPath?.let { File(it).absoluteFile.parentFile } + return@lazy explicitOutputDir + ?: jpsModuleBuildTarget.outputDir + ?: throw ProjectBuildException("No output directory found for " + this) + } + + val compilerSettings by lazy { JpsKotlinCompilerSettings.getCompilerSettings(module) } + + val friendBuildTargets: List + get() { + val result = mutableListOf() + + if (isTests) { + result.addIfNotNull(module.productionBuildTarget.kotlinData) + result.addIfNotNull(relatedProductionModule?.productionBuildTarget?.kotlinData) + } + + return result.filter { it.sources.isNotEmpty() } + } + + val friendOutputDirs: List + get() = friendBuildTargets.mapNotNull { + JpsJavaExtensionService.getInstance().getOutputDirectory(it.module, false) + } + + val relatedProductionModule: JpsModule? + get() = JpsJavaExtensionService.getInstance().getTestModuleProperties(module)?.productionModule + + val allDependencies by lazy { + JpsJavaExtensionService.dependencies(module).recursively().exportedOnly() + .includedIn(JpsJavaClasspathKind.compile(isTests)) + } + + val expectedBy by lazy(::findExpectedBy) + + private fun findExpectedBy(): List { + val kotlinFacetExtension = module.kotlinFacetExtension + val implementedModuleNames = kotlinFacetExtension?.settings?.implementedModuleNames ?: return listOf() + if (implementedModuleNames.isEmpty()) return listOf() + + return allDependencies.modules + .filter { it.name in implementedModuleNames } + .map { ModuleBuildTarget(it, isTests).kotlinData!! } + } + + fun getRemovedKotlinFiles(dirtyFilesHolder: DirtyFilesHolder): List = + dirtyFilesHolder + .getRemovedFiles(jpsModuleBuildTarget) + .mapNotNull { if (FileUtilRt.extensionEquals(it, "kt")) File(it) else null } + + val sources by lazy { + mutableListOf().also { result -> + // add all common libs sources + expectedBy.forEach { + it.collectSources(result) + } + + collectSources(result) + } + } + + private fun collectSources(receiver: MutableList) { + val moduleExcludes = module.excludeRootsList.urls.mapTo(java.util.HashSet(), JpsPathUtil::urlToFile) + + val compilerExcludes = JpsJavaExtensionService.getInstance() + .getOrCreateCompilerConfiguration(module.project) + .compilerExcludes + + val sourceRootType = if (isTests) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + module.getSourceRoots(sourceRootType).forEach { + it.file.walkTopDown() + .onEnter { it !in moduleExcludes } + .filterTo(receiver) { + !compilerExcludes.isExcluded(it) && + it.isFile && + KotlinSourceFileCollector.isKotlinSourceFile(it) + } + } + } + + override fun toString() = jpsModuleBuildTarget.toString() + + abstract fun compileModuleChunk( + allCompiledFiles: MutableSet, + chunk: ModuleChunk, + commonArguments: CommonCompilerArguments, + context: CompileContext, + dirtyFilesHolder: DirtyFilesHolder, + environment: JpsCompilerEnvironment, + filesToCompile: MultiMap + ): Boolean + + protected fun reportAndSkipCircular( + chunk: ModuleChunk, + environment: JpsCompilerEnvironment + ): Boolean { + if (chunk.modules.size > 1) { + // We do not support circular dependencies, but if they are present, we do our best should not break the build, + // so we simply yield a warning and report NOTHING_DONE + environment.messageCollector.report( + CompilerMessageSeverity.STRONG_WARNING, + "Circular dependencies are not supported. The following modules depend on each other: " + + chunk.modules.joinToString(", ") { it.name } + " " + + "Kotlin is not compiled for these modules" + ) + + return true + } + + return false + } + + open fun doAfterBuild() { + } +} \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/TargetPlatform.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/TargetPlatform.kt new file mode 100644 index 00000000000..8ed20ebf977 --- /dev/null +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/platforms/TargetPlatform.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.jps.platforms + +import org.jetbrains.annotations.TestOnly +import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType +import org.jetbrains.jps.incremental.ModuleBuildTarget +import org.jetbrains.jps.model.java.JpsJavaModuleType +import org.jetbrains.jps.model.library.JpsOrderRootType +import org.jetbrains.jps.model.module.JpsModule +import org.jetbrains.jps.util.JpsPathUtil +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.jps.targetPlatform +import org.jetbrains.kotlin.utils.LibraryUtils +import java.util.concurrent.ConcurrentHashMap + +fun ModuleBuildTarget(module: JpsModule, isTests: Boolean) = + ModuleBuildTarget(module, if (isTests) JavaModuleBuildTargetType.TEST else JavaModuleBuildTargetType.PRODUCTION) + +val JpsModule.productionBuildTarget + get() = ModuleBuildTarget(this, false) + +val JpsModule.testBuildTarget + get() = ModuleBuildTarget(this, true) + +private val kotlinBuildTargetsData = ConcurrentHashMap() + +val ModuleBuildTarget.kotlinData: KotlinModuleBuilderTarget? + get() { + if (module.moduleType != JpsJavaModuleType.INSTANCE) return null + + return kotlinBuildTargetsData.computeIfAbsent(this) { + when (module.targetPlatform ?: detectTargetPlatform()) { + is TargetPlatformKind.Common -> KotlinCommonModuleBuildTarget(this) + is TargetPlatformKind.JavaScript -> KotlinJsModuleBuildTarget(this) + is TargetPlatformKind.Jvm -> KotlinJvmModuleBuildTarget(this) + } + } + } + +/** + * Compatibility for KT-14082 + * todo: remove when all projects migrated to facets + */ +private fun ModuleBuildTarget.detectTargetPlatform(): TargetPlatformKind<*> { + if (hasJsStdLib()) return TargetPlatformKind.JavaScript + + return TargetPlatformKind.DEFAULT_PLATFORM +} + +private val IS_KOTLIN_JS_STDLIB_JAR_CACHE = ConcurrentHashMap() + +private fun ModuleBuildTarget.hasJsStdLib(): Boolean { + KotlinJvmModuleBuildTarget(this).allDependencies.libraries.forEach { library -> + for (root in library.getRoots(JpsOrderRootType.COMPILED)) { + val url = root.url + + val isKotlinJsLib = IS_KOTLIN_JS_STDLIB_JAR_CACHE.computeIfAbsent(url) { + LibraryUtils.isKotlinJavascriptStdLibrary(JpsPathUtil.urlToFile(url)) + } + + if (isKotlinJsLib) return true + } + } + + return false +} + +@TestOnly +internal fun clearKotlinModuleBuildTargetDataBindings() { + kotlinBuildTargetsData.clear() +} \ No newline at end of file diff --git a/jps-plugin/src/org/jetbrains/kotlin/modules/modulesUtil.kt b/jps-plugin/src/org/jetbrains/kotlin/modules/modulesUtil.kt deleted file mode 100644 index 3ada1c1c624..00000000000 --- a/jps-plugin/src/org/jetbrains/kotlin/modules/modulesUtil.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.modules - -import org.jetbrains.jps.incremental.ModuleBuildTarget -import org.jetbrains.kotlin.jps.build.KotlinBuilderModuleScriptGenerator.getRelatedProductionModule - -fun TargetId(moduleBuildTarget: ModuleBuildTarget): TargetId { - // Since IDEA 2016 each gradle source root is imported as a separate module. - // One gradle module X is imported as two JPS modules: - // 1. X-production with one production target; - // 2. X-test with one test target. - // This breaks kotlin code since internal members' names are mangled using module name. - // For example, a declaration of a function 'f' in 'X-production' becomes 'fXProduction', but a call 'f' in 'X-test' becomes 'fXTest()'. - // The workaround is to replace a name of such test target with the name of corresponding production module. - // See KT-11993. - val name = getRelatedProductionModule(moduleBuildTarget.module)?.name ?: moduleBuildTarget.id - return TargetId(name, moduleBuildTarget.targetType.typeId) -}