jps(refactor): Extract platform related code

#KT-23656 Fixed

(cherry picked from commit 18a751d)

Original commit: 75e6be7802
This commit is contained in:
Sergey Rostov
2018-04-04 14:59:17 +03:00
parent c7fe4c10bf
commit ef5d8435b6
13 changed files with 698 additions and 517 deletions
@@ -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
@@ -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()
}
}
@@ -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<String> {
val list = arrayListOf<String>()
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<String>()
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<Action>, pathsToCompile: Array<String>?, pathsToDelete: Array<String>?) {
clearKotlinModuleBuildTargetDataBindings()
for (action in actions) {
action.apply()
}
@@ -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<String> {
val result = ArrayList<String>()
getLibraryFiles(target, result)
getDependencyModulesAndSources(target, result)
return result
}
fun getLibraryFiles(target: ModuleBuildTarget, result: MutableList<String>) {
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<String>) {
JpsUtils.getAllDependencies(target).processModules(object : Consumer<JpsModule> {
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 }
@@ -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<File, ModuleBuildTarget>()
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<String>()
JpsJsModuleUtils.getLibraryFiles(representativeTarget, libraryFilesToCopy)
JsLibraryUtils.copyJsFilesFromLibraries(
libraryFilesToCopy, outputLibraryRuntimeDirectory,
copySourceMap = k2jsCompilerSettings.sourceMap
)
}
}
// if null is returned, nothing was done
private fun compileToJvm(
allCompiledFiles: MutableSet<File>,
chunk: ModuleChunk,
commonArguments: CommonCompilerArguments,
context: CompileContext,
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: JpsCompilerEnvironment,
filesToCompile: MultiMap<ModuleBuildTarget, File>
): 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<MutableSet<File>>("_all_kotlin_compiled_files_")
private fun getAllCompiledFilesContainer(context: CompileContext): MutableSet<File> {
fun getAllCompiledFilesContainer(context: CompileContext): MutableSet<File> {
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<Fi
// TODO: investigate thread safety
private val PROCESSED_TARGETS_WITH_REMOVED_FILES = Key.create<MutableSet<ModuleBuildTarget>>("_processed_targets_with_removed_files_")
private fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet<ModuleBuildTarget> {
fun getProcessedTargetsWithRemovedFilesContainer(context: CompileContext): MutableSet<ModuleBuildTarget> {
var set = PROCESSED_TARGETS_WITH_REMOVED_FILES.get(context)
if (set == null) {
set = HashSet()
@@ -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<ModuleBuildTarget, File>, // ignored for non-incremental compilation
hasRemovedFiles: Boolean
): File? {
val builder = KotlinModuleXmlBuilder()
var noSources = true
val outputDirs = HashSet<File>()
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<JpsModule> {
if (!from.isTests) return emptyList()
val result = SmartList<JpsModule>(from.module)
result.addIfNotNull(getRelatedProductionModule(from.module))
return result.filter { it.hasProductionSourceRoot }
}
fun getAdditionalOutputDirsWhereInternalsAreVisible(target: ModuleBuildTarget): List<File> {
return getProductionModulesWhichInternalsAreVisible(target).mapNotNullTo(SmartList<File>()) {
JpsJavaExtensionService.getInstance().getOutputDirectory(it, false)
}
}
private fun findClassPathRoots(target: ModuleBuildTarget): Collection<File> {
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<JvmSourceRoot> {
val roots = context.projectDescriptor.buildRootIndex.getTargetRoots(target, context)
val result = ContainerUtil.newArrayList<JvmSourceRoot>()
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
}
}
@@ -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<File> {
val moduleExcludes = target.module.excludeRootsList.urls.mapTo(HashSet(), JpsPathUtil::urlToFile)
val compilerExcludes = JpsJavaExtensionService.getInstance()
.getOrCreateCompilerConfiguration(target.module.project)
.compilerExcludes
val allKotlinFiles = ArrayList<File>()
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<JpsModuleSourceRoot> {
val sourceRootType = if (target.isTests) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE
return target.module.getSourceRoots<JavaSourceRootProperties>(sourceRootType)
@@ -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<File>,
chunk: ModuleChunk,
commonArguments: CommonCompilerArguments,
context: CompileContext,
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: JpsCompilerEnvironment,
filesToCompile: MultiMap<ModuleBuildTarget, File>
): Boolean {
require(chunk.representativeTarget() == jpsModuleBuildTarget)
if (reportAndSkipCircular(chunk, environment)) return false
// TODO: Compile metadata
return false
}
}
@@ -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<String>
get() = mutableListOf<String>().also { result ->
for (library in allDependencies.libraries) {
for (root in library.getRoots(JpsOrderRootType.COMPILED)) {
result.add(JpsPathUtil.urlToPath(root.url))
}
}
}
val dependenciesMetaFiles: List<String>
get() = mutableListOf<String>().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<String>,
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<File>,
chunk: ModuleChunk,
commonArguments: CommonCompilerArguments,
context: CompileContext,
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: JpsCompilerEnvironment,
filesToCompile: MultiMap<ModuleBuildTarget, File>
): 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
)
}
}
}
@@ -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<File>,
chunk: ModuleChunk,
commonArguments: CommonCompilerArguments,
context: CompileContext,
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: JpsCompilerEnvironment,
filesToCompile: MultiMap<ModuleBuildTarget, File>
): 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<ModuleBuildTarget, File>, // 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<File> {
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<JvmSourceRoot> {
val roots = context.projectDescriptor.buildRootIndex.getTargetRoots(jpsModuleBuildTarget, context)
val result = ContainerUtil.newArrayList<JvmSourceRoot>()
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
}
}
@@ -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<KotlinModuleBuilderTarget>
get() {
val result = mutableListOf<KotlinModuleBuilderTarget>()
if (isTests) {
result.addIfNotNull(module.productionBuildTarget.kotlinData)
result.addIfNotNull(relatedProductionModule?.productionBuildTarget?.kotlinData)
}
return result.filter { it.sources.isNotEmpty() }
}
val friendOutputDirs: List<File>
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<KotlinModuleBuilderTarget> {
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<JavaSourceRootDescriptor, ModuleBuildTarget>): List<File> =
dirtyFilesHolder
.getRemovedFiles(jpsModuleBuildTarget)
.mapNotNull { if (FileUtilRt.extensionEquals(it, "kt")) File(it) else null }
val sources by lazy {
mutableListOf<File>().also { result ->
// add all common libs sources
expectedBy.forEach {
it.collectSources(result)
}
collectSources(result)
}
}
private fun collectSources(receiver: MutableList<File>) {
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<File>,
chunk: ModuleChunk,
commonArguments: CommonCompilerArguments,
context: CompileContext,
dirtyFilesHolder: DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget>,
environment: JpsCompilerEnvironment,
filesToCompile: MultiMap<ModuleBuildTarget, File>
): 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() {
}
}
@@ -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<ModuleBuildTarget, KotlinModuleBuilderTarget>()
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<String, Boolean>()
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()
}
@@ -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)
}