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 5b745fb09f0..78b304d0c24 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 @@ -22,7 +22,6 @@ import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtilRt import com.intellij.testFramework.TestLoggerFactory import com.intellij.testFramework.UsefulTestCase -import com.intellij.util.concurrency.FixedFuture import junit.framework.TestCase import org.apache.log4j.ConsoleAppender import org.apache.log4j.Level @@ -45,7 +44,6 @@ import org.jetbrains.jps.model.java.JpsJavaExtensionService import org.jetbrains.jps.util.JpsPathUtil import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.incremental.LookupSymbol -import org.jetbrains.kotlin.incremental.isJavaFile import org.jetbrains.kotlin.incremental.storage.version.CacheAttributesDiff import org.jetbrains.kotlin.incremental.storage.version.CacheVersionManager import org.jetbrains.kotlin.incremental.testingUtils.* @@ -56,9 +54,11 @@ import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.utils.Printer import org.jetbrains.kotlin.utils.keysToMap -import java.io.* +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.PrintStream import java.util.* -import java.util.concurrent.Future import kotlin.reflect.jvm.javaField abstract class AbstractIncrementalJpsTest( @@ -139,7 +139,7 @@ abstract class AbstractIncrementalJpsTest( // JPS forces rebuild of all files when JVM constant has been changed and Callbacks.ConstantAffectionResolver // is not provided, so ConstantAffectionResolver is mocked with empty implementation // Usages in Kotlin files are expected to be found by KotlinLookupConstantSearch - private val mockConstantSearch: Callbacks.ConstantAffectionResolver? + protected open val mockConstantSearch: Callbacks.ConstantAffectionResolver? get() = MockJavaConstantSearch(workDir) private fun build(scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()): MakeResult { @@ -153,7 +153,14 @@ abstract class AbstractIncrementalJpsTest( projectDescriptor.project.setTestingContext(testingContext) try { - val builder = IncProjectBuilder(projectDescriptor, BuilderRegistry.getInstance(), myBuildParams, CanceledStatus.NULL, mockConstantSearch, true) + val builder = IncProjectBuilder( + projectDescriptor, + BuilderRegistry.getInstance(), + myBuildParams, + CanceledStatus.NULL, + mockConstantSearch, + true + ) val buildResult = BuildResult() builder.addMessageHandler(buildResult) val finalScope = scope.build() @@ -173,12 +180,10 @@ abstract class AbstractIncrementalJpsTest( .map { it.replace("^.+:\\d+:\\s+".toRegex(), "").trim() } .joinToString("\n") return MakeResult(logger.log + "$COMPILATION_FAILED\n" + errorMessages + "\n", true, null) - } - else { + } else { return MakeResult(logger.log, false, createMappingsDump(projectDescriptor)) } - } - finally { + } finally { projectDescriptor.dataManager.flush(false) projectDescriptor.release() } @@ -190,8 +195,7 @@ abstract class AbstractIncrementalJpsTest( val initBuildLogFile = File(testDataDir, "init-build.log") if (initBuildLogFile.exists()) { UsefulTestCase.assertSameLinesWithFile(initBuildLogFile.absolutePath, makeResult.log) - } - else { + } else { assertFalse("Initial make failed:\n$makeResult", makeResult.makeFailed) } @@ -215,21 +219,21 @@ abstract class AbstractIncrementalJpsTest( } val rebuildResult = rebuild() - assertEquals("Rebuild failed: ${rebuildResult.makeFailed}, last make failed: ${makeOverallResult.makeFailed}. Rebuild result: $rebuildResult", - rebuildResult.makeFailed, makeOverallResult.makeFailed) + assertEquals( + "Rebuild failed: ${rebuildResult.makeFailed}, last make failed: ${makeOverallResult.makeFailed}. Rebuild result: $rebuildResult", + rebuildResult.makeFailed, makeOverallResult.makeFailed + ) if (!outAfterMake.exists()) { assertFalse(outDir.exists()) - } - else { + } else { assertEqualDirectories(outDir, outAfterMake, makeOverallResult.makeFailed) } if (!makeOverallResult.makeFailed) { if (checkDumpsCaseInsensitively && rebuildResult.mappingsDump?.toLowerCase() == makeOverallResult.mappingsDump?.toLowerCase()) { // do nothing - } - else { + } else { TestCase.assertEquals(rebuildResult.mappingsDump, makeOverallResult.mappingsDump) } } @@ -282,8 +286,7 @@ abstract class AbstractIncrementalJpsTest( if (buildLogFile != null && buildLogFile.exists()) { UsefulTestCase.assertSameLinesWithFile(buildLogFile.absolutePath, logs) - } - else if (!allowNoBuildLogFileInTestData) { + } else if (!allowNoBuildLogFileInTestData) { throw IllegalStateException("No build log file in $testDataDir") } @@ -295,9 +298,9 @@ abstract class AbstractIncrementalJpsTest( private fun createMappingsDump( project: ProjectDescriptor ) = createKotlinIncrementalCacheDump(project) + "\n\n\n" + - createLookupCacheDump(project) + "\n\n\n" + - createCommonMappingsDump(project) + "\n\n\n" + - createJavaMappingsDump(project) + createLookupCacheDump(project) + "\n\n\n" + + createCommonMappingsDump(project) + "\n\n\n" + + createJavaMappingsDump(project) private fun createKotlinIncrementalCacheDump( project: ProjectDescriptor @@ -377,8 +380,7 @@ abstract class AbstractIncrementalJpsTest( performAdditionalModifications(step) if (moduleNames == null) { preProcessSources(File(workDir, "src")) - } - else { + } else { moduleNames.forEach { preProcessSources(File(workDir, "$it/src")) } } @@ -401,7 +403,8 @@ abstract class AbstractIncrementalJpsTest( preProcessSources(sourceDestinationDir) } - JpsJavaExtensionService.getInstance().getOrCreateProjectExtension(myProject).outputUrl = JpsPathUtil.pathToUrl(getAbsolutePath("out")) + JpsJavaExtensionService.getInstance().getOrCreateProjectExtension(myProject).outputUrl = + JpsPathUtil.pathToUrl(getAbsolutePath("out")) val jdk = addJdk("my jdk") val moduleDependencies = readModuleDependencies() @@ -412,8 +415,7 @@ abstract class AbstractIncrementalJpsTest( addModule("module", arrayOf(getAbsolutePath("src")), null, null, jdk) prepareModuleSources(moduleName = null) moduleNames = null - } - else { + } else { val nameToModule = moduleDependencies.keys .keysToMap { addModule(it, arrayOf(getAbsolutePath("$it/src")), null, null, jdk)!! } @@ -421,8 +423,10 @@ abstract class AbstractIncrementalJpsTest( val module = nameToModule[moduleName]!! for (dependency in dependencies) { - JpsModuleRootModificationUtil.addDependency(module, nameToModule[dependency.name], - JpsJavaDependencyScope.COMPILE, dependency.exported) + JpsModuleRootModificationUtil.addDependency( + module, nameToModule[dependency.name], + JpsJavaDependencyScope.COMPILE, dependency.exported + ) } } @@ -527,38 +531,6 @@ abstract class AbstractIncrementalJpsTest( } } -/** - * Mocks Intellij Java constant search. - * When JPS is run from Intellij, it sends find usages request to IDE (it only searches for references inside Java files). - * - * We rely on heuristics instead of precise usages search. - * A Java file is considered affected if: - * 1. It contains changed field name as a content substring. - * 2. Its simple file name is not equal to a field's owner class simple name (to avoid recompiling field's declaration again) - */ -private class MockJavaConstantSearch(private val workDir: File) : Callbacks.ConstantAffectionResolver { - override fun request( - ownerClassName: String, - fieldName: String, - accessFlags: Int, - fieldRemoved: Boolean, - accessChanged: Boolean - ): Future { - fun File.isAffected(): Boolean { - if (!isJavaFile()) return false - - if (nameWithoutExtension == ownerClassName.substringAfterLast(".")) return false - - val code = readText() - return code.contains(fieldName) - } - - - val affectedJavaFiles = workDir.walk().filter(File::isAffected).toList() - return FixedFuture(Callbacks.ConstantAffection(affectedJavaFiles)) - } -} - internal val ProjectDescriptor.allModuleTargets: Collection get() = buildTargetIndex.allTargets.filterIsInstance() diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 deleted file mode 100644 index 47b718e7b29..00000000000 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 +++ /dev/null @@ -1,552 +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.Disposable -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.util.io.FileUtil -import com.intellij.openapi.util.io.FileUtilRt -import com.intellij.testFramework.TestLoggerFactory -import com.intellij.testFramework.UsefulTestCase -import com.intellij.util.concurrency.FixedFuture -import junit.framework.TestCase -import org.apache.log4j.ConsoleAppender -import org.apache.log4j.Level -import org.apache.log4j.Logger -import org.apache.log4j.PatternLayout -import org.jetbrains.jps.ModuleChunk -import org.jetbrains.jps.api.CanceledStatus -import org.jetbrains.jps.builders.BuildResult -import org.jetbrains.jps.builders.CompileScopeTestBuilder -import org.jetbrains.jps.builders.impl.BuildDataPathsImpl -import org.jetbrains.jps.builders.impl.logging.ProjectBuilderLoggerBase -import org.jetbrains.jps.builders.java.dependencyView.Callbacks -import org.jetbrains.jps.builders.logging.BuildLoggingManager -import org.jetbrains.jps.cmdline.ProjectDescriptor -import org.jetbrains.jps.incremental.* -import org.jetbrains.jps.incremental.messages.BuildMessage -import org.jetbrains.jps.model.JpsModuleRootModificationUtil -import org.jetbrains.jps.model.java.JpsJavaDependencyScope -import org.jetbrains.jps.model.java.JpsJavaExtensionService -import org.jetbrains.jps.util.JpsPathUtil -import org.jetbrains.kotlin.config.IncrementalCompilation -import org.jetbrains.kotlin.incremental.LookupSymbol -import org.jetbrains.kotlin.incremental.storage.version.CacheAttributesDiff -import org.jetbrains.kotlin.incremental.storage.version.CacheVersionManager -import org.jetbrains.kotlin.incremental.testingUtils.* -import org.jetbrains.kotlin.jps.incremental.CompositeLookupsCacheAttributesManager -import org.jetbrains.kotlin.jps.incremental.getKotlinCache -import org.jetbrains.kotlin.jps.incremental.withLookupStorage -import org.jetbrains.kotlin.jps.targets.KotlinModuleBuildTarget -import org.jetbrains.kotlin.test.KotlinTestUtils -import org.jetbrains.kotlin.utils.Printer -import org.jetbrains.kotlin.utils.keysToMap -import java.io.* -import java.util.* -import java.util.concurrent.Future -import kotlin.reflect.jvm.javaField - -abstract class AbstractIncrementalJpsTest( - private val allowNoFilesWithSuffixInTestData: Boolean = false, - private val checkDumpsCaseInsensitively: Boolean = false, - private val allowNoBuildLogFileInTestData: Boolean = false -) : BaseKotlinJpsBuildTestCase() { - companion object { - private val COMPILATION_FAILED = "COMPILATION FAILED" - - // change to "/tmp" or anything when default is too long (for easier debugging) - private val TEMP_DIRECTORY_TO_USE = File(FileUtilRt.getTempDirectory()) - - private val DEBUG_LOGGING_ENABLED = System.getProperty("debug.logging.enabled") == "true" - } - - protected lateinit var testDataDir: File - protected lateinit var workDir: File - protected lateinit var projectDescriptor: ProjectDescriptor - // is used to compare lookup dumps in a human readable way (lookup symbols are hashed in an actual lookup storage) - protected lateinit var lookupsDuringTest: MutableSet - private var isICEnabledBackup: Boolean = false - - protected var mapWorkingToOriginalFile: MutableMap = hashMapOf() - - lateinit var kotlinCompileContext: KotlinCompileContext - - protected open val buildLogFinder: BuildLogFinder - get() = BuildLogFinder() - - private fun enableDebugLogging() { - com.intellij.openapi.diagnostic.Logger.setFactory(TestLoggerFactory::class.java) - TestLoggerFactory.dumpLogToStdout("") - TestLoggerFactory.enableDebugLogging(testRootDisposable, "#org") - - val console = ConsoleAppender() - console.layout = PatternLayout("%d [%p|%c|%C{1}] %m%n") - console.threshold = Level.ALL - console.activateOptions() - Logger.getRootLogger().addAppender(console) - } - - private var systemPropertiesBackup = run { - val props = System.getProperties() - val output = ByteArrayOutputStream() - props.store(output, "System properties backup") - output.toByteArray() - } - - private fun restoreSystemProperties() { - val input = ByteArrayInputStream(systemPropertiesBackup) - val props = Properties() - props.load(input) - System.setProperties(props) - } - - override fun setUp() { - super.setUp() - lookupsDuringTest = hashSetOf() - isICEnabledBackup = IncrementalCompilation.isEnabledForJvm() - IncrementalCompilation.setIsEnabledForJvm(true) - - if (DEBUG_LOGGING_ENABLED) { - enableDebugLogging() - } - } - - override fun tearDown() { - restoreSystemProperties() - (AbstractIncrementalJpsTest::myProject).javaField!![this] = null - (AbstractIncrementalJpsTest::projectDescriptor).javaField!![this] = null - (AbstractIncrementalJpsTest::systemPropertiesBackup).javaField!![this] = null - lookupsDuringTest.clear() - IncrementalCompilation.setIsEnabledForJvm(isICEnabledBackup) - super.tearDown() - } - - // JPS forces rebuild of all files when JVM constant has been changed and Callbacks.ConstantAffectionResolver - // is not provided, so ConstantAffectionResolver is mocked with empty implementation - protected open val mockConstantSearch: Callbacks.ConstantAffectionResolver? - get() = MockConstantSearch(workDir) - - private fun build(scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()): MakeResult { - val workDirPath = FileUtil.toSystemIndependentName(workDir.absolutePath) - - val logger = MyLogger(workDirPath) - projectDescriptor = createProjectDescriptor(BuildLoggingManager(logger)) - - val lookupTracker = TestLookupTracker() - val testingContext = TestingContext(lookupTracker, logger) - projectDescriptor.project.setTestingContext(testingContext) - - try { - val builder = IncProjectBuilder(projectDescriptor, BuilderRegistry.getInstance(), myBuildParams, CanceledStatus.NULL, mockConstantSearch, true) - val buildResult = BuildResult() - builder.addMessageHandler(buildResult) - val finalScope = scope.build() - - builder.build(finalScope, false) - - // testingContext.kotlinCompileContext is initialized in KotlinBuilder.initializeKotlinContext - kotlinCompileContext = testingContext.kotlinCompileContext!! - - - if (!buildResult.isSuccessful) { - val errorMessages = - buildResult - .getMessages(BuildMessage.Kind.ERROR) - .map { it.messageText } - .map { it.replace("^.+:\\d+:\\s+".toRegex(), "").trim() } - .joinToString("\n") - return MakeResult(logger.log + "$COMPILATION_FAILED\n" + errorMessages + "\n", true, null) - } - else { - return MakeResult(logger.log, false, createMappingsDump(projectDescriptor)) - } - } - finally { - projectDescriptor.dataManager.flush(false) - projectDescriptor.release() - } - } - - private fun initialMake(): MakeResult { - val makeResult = build() - - val initBuildLogFile = File(testDataDir, "init-build.log") - if (initBuildLogFile.exists()) { - UsefulTestCase.assertSameLinesWithFile(initBuildLogFile.absolutePath, makeResult.log) - } - else { - assertFalse("Initial make failed:\n$makeResult", makeResult.makeFailed) - } - - return makeResult - } - - private fun make(): MakeResult { - return build() - } - - private fun rebuild(): MakeResult { - return build(CompileScopeTestBuilder.rebuild().allModules()) - } - - private fun rebuildAndCheckOutput(makeOverallResult: MakeResult) { - val outDir = File(getAbsolutePath("out")) - val outAfterMake = File(getAbsolutePath("out-after-make")) - - if (outDir.exists()) { - FileUtil.copyDir(outDir, outAfterMake) - } - - val rebuildResult = rebuild() - assertEquals("Rebuild failed: ${rebuildResult.makeFailed}, last make failed: ${makeOverallResult.makeFailed}. Rebuild result: $rebuildResult", - rebuildResult.makeFailed, makeOverallResult.makeFailed) - - if (!outAfterMake.exists()) { - assertFalse(outDir.exists()) - } - else { - assertEqualDirectories(outDir, outAfterMake, makeOverallResult.makeFailed) - } - - if (!makeOverallResult.makeFailed) { - if (checkDumpsCaseInsensitively && rebuildResult.mappingsDump?.toLowerCase() == makeOverallResult.mappingsDump?.toLowerCase()) { - // do nothing - } - else { - TestCase.assertEquals(rebuildResult.mappingsDump, makeOverallResult.mappingsDump) - } - } - - FileUtil.delete(outAfterMake) - } - - private fun clearCachesRebuildAndCheckOutput(makeOverallResult: MakeResult) { - FileUtil.delete(BuildDataPathsImpl(myDataStorageRoot).dataStorageRoot!!) - - rebuildAndCheckOutput(makeOverallResult) - } - - private fun readModuleDependencies(): Map>? { - val dependenciesTxt = File(testDataDir, "dependencies.txt") - if (!dependenciesTxt.exists()) return null - - val result = HashMap>() - for (line in dependenciesTxt.readLines()) { - val split = line.split("->") - val module = split[0] - val dependencies = if (split.size > 1) split[1] else "" - val dependencyList = dependencies.split(",").filterNot { it.isEmpty() } - result[module] = dependencyList.map(::parseDependency) - } - - return result - } - - protected open fun createBuildLog(incrementalMakeResults: List): String = - buildString { - incrementalMakeResults.forEachIndexed { i, makeResult -> - if (i > 0) append("\n") - append("================ Step #${i + 1} =================\n\n") - append(makeResult.log) - } - } - - protected open fun doTest(testDataPath: String) { - testDataDir = File(testDataPath) - workDir = FileUtilRt.createTempDirectory(TEMP_DIRECTORY_TO_USE, "jps-build", null) - Disposer.register(testRootDisposable, Disposable { FileUtilRt.delete(workDir) }) - - val moduleNames = configureModules() - initialMake() - - val otherMakeResults = performModificationsAndMake(moduleNames) - val buildLogFile = buildLogFinder.findBuildLog(testDataDir) - val logs = createBuildLog(otherMakeResults) - - if (buildLogFile != null && buildLogFile.exists()) { - UsefulTestCase.assertSameLinesWithFile(buildLogFile.absolutePath, logs) - } - else if (!allowNoBuildLogFileInTestData) { - throw IllegalStateException("No build log file in $testDataDir") - } - - val lastMakeResult = otherMakeResults.last() - rebuildAndCheckOutput(lastMakeResult) - clearCachesRebuildAndCheckOutput(lastMakeResult) - } - - private fun createMappingsDump( - project: ProjectDescriptor - ) = createKotlinIncrementalCacheDump(project) + "\n\n\n" + - createLookupCacheDump(project) + "\n\n\n" + - createCommonMappingsDump(project) + "\n\n\n" + - createJavaMappingsDump(project) - - private fun createKotlinIncrementalCacheDump( - project: ProjectDescriptor - ): String { - return buildString { - for (target in project.allModuleTargets.sortedBy { it.presentableName }) { - val kotlinCache = project.dataManager.getKotlinCache(kotlinCompileContext.targetsBinding[target]) - if (kotlinCache != null) { - append("\n") - append(kotlinCache.dump()) - append("\n\n\n") - } - } - } - } - - private fun createLookupCacheDump(project: ProjectDescriptor): String { - val sb = StringBuilder() - val p = Printer(sb) - p.println("Begin of Lookup Maps") - p.println() - - project.dataManager.withLookupStorage { lookupStorage -> - lookupStorage.forceGC() - p.print(lookupStorage.dump(lookupsDuringTest)) - } - - p.println() - p.println("End of Lookup Maps") - return sb.toString() - } - - private fun createCommonMappingsDump(project: ProjectDescriptor): String { - val resultBuf = StringBuilder() - val result = Printer(resultBuf) - - result.println("Begin of SourceToOutputMap") - result.pushIndent() - - for (target in project.allModuleTargets) { - result.println(target) - result.pushIndent() - - val mapping = project.dataManager.getSourceToOutputMap(target) - mapping.sources.sorted().forEach { - val outputs = mapping.getOutputs(it)!!.sorted() - if (outputs.isNotEmpty()) { - result.println("source $it -> $outputs") - } - } - - result.popIndent() - } - - result.popIndent() - result.println("End of SourceToOutputMap") - - return resultBuf.toString() - } - - private fun createJavaMappingsDump(project: ProjectDescriptor): String { - val byteArrayOutputStream = ByteArrayOutputStream() - PrintStream(byteArrayOutputStream).use { - project.dataManager.mappings.toStream(it) - } - return byteArrayOutputStream.toString() - } - - protected data class MakeResult(val log: String, val makeFailed: Boolean, val mappingsDump: String?) - - private fun performModificationsAndMake(moduleNames: Set?): List { - val results = arrayListOf() - val modifications = getModificationsToPerform(testDataDir, moduleNames, allowNoFilesWithSuffixInTestData, TouchPolicy.TIMESTAMP) - - for (step in modifications) { - step.forEach { it.perform(workDir, mapWorkingToOriginalFile) } - performAdditionalModifications(step) - if (moduleNames == null) { - preProcessSources(File(workDir, "src")) - } - else { - moduleNames.forEach { preProcessSources(File(workDir, "$it/src")) } - } - - results.add(make()) - } - return results - } - - protected open fun performAdditionalModifications(modifications: List) { - } - - // null means one module - private fun configureModules(): Set? { - fun prepareModuleSources(moduleName: String?) { - val sourceDirName = moduleName?.let { "$it/src" } ?: "src" - val filePrefix = moduleName?.let { "${it}_" } ?: "" - val sourceDestinationDir = File(workDir, sourceDirName) - val sourcesMapping = copyTestSources(testDataDir, sourceDestinationDir, filePrefix) - mapWorkingToOriginalFile.putAll(sourcesMapping) - preProcessSources(sourceDestinationDir) - } - - JpsJavaExtensionService.getInstance().getOrCreateProjectExtension(myProject).outputUrl = JpsPathUtil.pathToUrl(getAbsolutePath("out")) - - val jdk = addJdk("my jdk") - val moduleDependencies = readModuleDependencies() - mapWorkingToOriginalFile = hashMapOf() - - val moduleNames: Set? - if (moduleDependencies == null) { - addModule("module", arrayOf(getAbsolutePath("src")), null, null, jdk) - prepareModuleSources(moduleName = null) - moduleNames = null - } - else { - val nameToModule = moduleDependencies.keys - .keysToMap { addModule(it, arrayOf(getAbsolutePath("$it/src")), null, null, jdk)!! } - - for ((moduleName, dependencies) in moduleDependencies) { - val module = nameToModule[moduleName]!! - - for (dependency in dependencies) { - JpsModuleRootModificationUtil.addDependency(module, nameToModule[dependency.name], - JpsJavaDependencyScope.COMPILE, dependency.exported) - } - } - - for (module in nameToModule.values) { - prepareModuleSources(module.name) - } - - moduleNames = nameToModule.keys - } - - configureDependencies() - return moduleNames - } - - protected open fun configureDependencies() { - AbstractKotlinJpsBuildTestCase.addKotlinStdlibDependency(myProject) - AbstractKotlinJpsBuildTestCase.addKotlinTestDependency(myProject) - } - - protected open fun preProcessSources(srcDir: File) { - } - - override fun doGetProjectDir(): File? = workDir - - private class MyLogger(val rootPath: String) : ProjectBuilderLoggerBase(), TestingBuildLogger { - private val markedDirtyBeforeRound = ArrayList() - private val markedDirtyAfterRound = ArrayList() - - override fun invalidOrUnusedCache( - chunk: KotlinChunk?, - target: KotlinModuleBuildTarget<*>?, - attributesDiff: CacheAttributesDiff<*> - ) { - val cacheManager = attributesDiff.manager - val cacheTitle = when (cacheManager) { - is CacheVersionManager -> "Local cache for ${chunk ?: target}" - is CompositeLookupsCacheAttributesManager -> "Lookups cache" - else -> error("Unknown cache manager $cacheManager") - } - - logLine("$cacheTitle are $attributesDiff") - } - - override fun markedAsDirtyBeforeRound(files: Iterable) { - markedDirtyBeforeRound.addAll(files) - } - - override fun markedAsDirtyAfterRound(files: Iterable) { - markedDirtyAfterRound.addAll(files) - } - - override fun chunkBuildStarted(context: CompileContext, chunk: ModuleChunk) { - logDirtyFiles(markedDirtyBeforeRound) // files can be marked as dirty during build start (KotlinCompileContext initialization) - - if (!chunk.isDummy(context) && context.projectDescriptor.project.modules.size > 1) { - logLine("Building ${chunk.modules.sortedBy { it.name }.joinToString { it.name }}") - } - } - - override fun afterChunkBuildStarted(context: CompileContext, chunk: ModuleChunk) { - logDirtyFiles(markedDirtyBeforeRound) - } - - override fun buildFinished(exitCode: ModuleLevelBuilder.ExitCode) { - logDirtyFiles(markedDirtyAfterRound) - logLine("Exit code: $exitCode") - logLine("------------------------------------------") - } - - private fun logDirtyFiles(files: MutableList) { - if (files.isEmpty()) return - - logLine("Marked as dirty by Kotlin:") - files.apply { - map { FileUtil.toSystemIndependentName(it.path) } - .sorted() - .forEach { logLine(it) } - - clear() - } - } - - private val logBuf = StringBuilder() - val log: String - get() = logBuf.toString() - - val compiledFiles = hashSetOf() - - override fun isEnabled(): Boolean = true - - override fun logCompiledFiles(files: MutableCollection?, builderName: String?, description: String?) { - super.logCompiledFiles(files, builderName, description) - - if (builderName == KotlinBuilder.KOTLIN_BUILDER_NAME) { - compiledFiles.addAll(files!!) - } - } - - override fun logLine(message: String?) { - logBuf.append(KotlinTestUtils.replaceHashWithStar(message!!.replace("^$rootPath/".toRegex(), " "))).append('\n') - } - } -} - -private class MockConstantSearch(private val workDir: File) : Callbacks.ConstantAffectionResolver { - override fun request( - ownerClassName: String, - fieldName: String, - accessFlags: Int, - fieldRemoved: Boolean, - accessChanged: Boolean - ): Future { - val affectedFiles = workDir.walk().filter { it.isFile && it.isNameUsage() } - return FixedFuture(Callbacks.ConstantAffection(affectedFiles.toList())) - } - - private fun File.isNameUsage(): Boolean = - name.equals("usage.kt", ignoreCase = true) - || name.equals("usage.java", ignoreCase = true) -} - -internal val ProjectDescriptor.allModuleTargets: Collection - get() = buildTargetIndex.allTargets.filterIsInstance() - -private class DependencyDescriptor(val name: String, val exported: Boolean) - -private fun parseDependency(dependency: String): DependencyDescriptor = - DependencyDescriptor(dependency.removeSuffix(EXPORTED_SUFFIX), dependency.endsWith(EXPORTED_SUFFIX)) - -private val EXPORTED_SUFFIX = "[exported]" diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt new file mode 100644 index 00000000000..e05bee274e5 --- /dev/null +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt @@ -0,0 +1,49 @@ +/* + * 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.build + +import com.intellij.util.concurrency.FixedFuture +import org.jetbrains.jps.builders.java.dependencyView.Callbacks +import org.jetbrains.kotlin.incremental.isJavaFile +import java.io.File +import java.util.concurrent.Future + +/** + * Mocks Intellij Java constant search. + * When JPS is run from Intellij, it sends find usages request to IDE (it only searches for references inside Java files). + * + * We rely on heuristics instead of precise usages search. + * A Java file is considered affected if: + * 1. It contains changed field name as a content substring. + * 2. Its simple file name is not equal to a field's owner class simple name (to avoid recompiling field's declaration again) + */ +class MockJavaConstantSearch(private val workDir: File) : + Callbacks.ConstantAffectionResolver { + override fun request( + ownerClassName: String, + fieldName: String, + accessFlags: Int, + fieldRemoved: Boolean, + accessChanged: Boolean + ): Future { + fun File.isAffected(): Boolean { + if (!isJavaFile()) return false + + if (nameWithoutExtension == ownerClassName.substringAfterLast(".")) return false + + val code = readText() + return code.contains(fieldName) + } + + + val affectedJavaFiles = workDir.walk().filter(File::isAffected).toList() + return FixedFuture( + Callbacks.ConstantAffection( + affectedJavaFiles + ) + ) + } +} \ No newline at end of file diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt.173 b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt.173 new file mode 100644 index 00000000000..72084d3482e --- /dev/null +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/MockJavaConstantSearch.kt.173 @@ -0,0 +1,28 @@ +/* + * 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.build + +import com.intellij.util.concurrency.FixedFuture +import org.jetbrains.jps.builders.java.dependencyView.Callbacks +import java.io.File +import java.util.concurrent.Future + +class MockJavaConstantSearch(private val workDir: File) : Callbacks.ConstantAffectionResolver { + override fun request( + ownerClassName: String, + fieldName: String, + accessFlags: Int, + fieldRemoved: Boolean, + accessChanged: Boolean + ): Future { + val affectedFiles = workDir.walk().filter { it.isFile && it.isNameUsage() } + return FixedFuture(Callbacks.ConstantAffection(affectedFiles.toList())) + } + + private fun File.isNameUsage(): Boolean = + name.equals("usage.kt", ignoreCase = true) + || name.equals("usage.java", ignoreCase = true) +} \ No newline at end of file