From 440d218436b0e09f1b7a1b0031e53064a8606bcc Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Fri, 27 Apr 2018 18:25:17 +0300 Subject: [PATCH] Switch to 181 platform Original commit: bc403ce744f0611dc8a27ea31541fe47b5adbb9d --- jps/jps-plugin/build.gradle.kts | 4 +- jps/jps-plugin/build.gradle.kts.173 | 52 ++ .../jps/build/AbstractIncrementalJpsTest.kt | 35 +- .../build/AbstractIncrementalJpsTest.kt.173 | 521 ++++++++++++++++++ .../build/IncrementalConstantSearchTest.kt | 27 - .../IncrementalConstantSearchTest.kt.173 | 70 +++ ...ins.jps.builders.java.JavaBuilderExtension | 1 + .../jps/build/KotlinJavaBuilderExtension.kt | 65 +++ 8 files changed, 737 insertions(+), 38 deletions(-) create mode 100644 jps/jps-plugin/build.gradle.kts.173 create mode 100644 jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 create mode 100644 jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt.173 create mode 100644 jps/jps-plugin/src/META-INF/services/org.jetbrains.jps.builders.java.JavaBuilderExtension create mode 100644 jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinJavaBuilderExtension.kt diff --git a/jps/jps-plugin/build.gradle.kts b/jps/jps-plugin/build.gradle.kts index 01d8ffabb1d..c1b73d11bfc 100644 --- a/jps/jps-plugin/build.gradle.kts +++ b/jps/jps-plugin/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { compile(projectRuntimeJar(":kotlin-preloader")) compile(project(":idea:idea-jps-common")) compileOnly(group = "org.jetbrains", name = "annotations", version = "13.0") - compileOnly(intellijDep()) { includeJars("jdom", "trove4j", "jps-model", "openapi", "util", "asm-all") } + compileOnly(intellijDep()) { includeJars("jdom", "trove4j", "jps-model", "openapi", "platform-api", "util", "asm-all") } compileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") } testCompileOnly(project(":kotlin-reflect-api")) testCompile(project(":compiler:incremental-compilation-impl")) @@ -26,7 +26,7 @@ dependencies { testCompile(projectDist(":kotlin-test:kotlin-test-jvm")) testCompile(projectTests(":kotlin-build-common")) testCompileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") } - testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "log4j") } + testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "platform-api", "log4j") } testCompile(intellijDep("jps-build-test")) compilerModules.forEach { testRuntime(project(it)) diff --git a/jps/jps-plugin/build.gradle.kts.173 b/jps/jps-plugin/build.gradle.kts.173 new file mode 100644 index 00000000000..01d8ffabb1d --- /dev/null +++ b/jps/jps-plugin/build.gradle.kts.173 @@ -0,0 +1,52 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +val compilerModules: Array by rootProject.extra + +dependencies { + compile(project(":kotlin-build-common")) + compile(project(":core:descriptors")) + compile(project(":core:descriptors.jvm")) + compile(project(":kotlin-compiler-runner")) + compile(project(":compiler:daemon-common")) + compile(projectRuntimeJar(":kotlin-daemon-client")) + compile(project(":compiler:frontend.java")) + compile(projectRuntimeJar(":kotlin-preloader")) + compile(project(":idea:idea-jps-common")) + compileOnly(group = "org.jetbrains", name = "annotations", version = "13.0") + compileOnly(intellijDep()) { includeJars("jdom", "trove4j", "jps-model", "openapi", "util", "asm-all") } + compileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") } + testCompileOnly(project(":kotlin-reflect-api")) + testCompile(project(":compiler:incremental-compilation-impl")) + testCompile(projectTests(":compiler:tests-common")) + testCompile(projectTests(":compiler:incremental-compilation-impl")) + testCompile(commonDep("junit:junit")) + testCompile(projectDist(":kotlin-test:kotlin-test-jvm")) + testCompile(projectTests(":kotlin-build-common")) + testCompileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") } + testCompileOnly(intellijDep()) { includeJars("openapi", "idea", "log4j") } + testCompile(intellijDep("jps-build-test")) + compilerModules.forEach { + testRuntime(project(it)) + } + testRuntime(intellijDep()) + testRuntime(projectDist(":kotlin-reflect")) +} + +sourceSets { + "main" { projectDefault() } + "test" { + java.srcDirs("jps-tests/test" + /*, "kannotator-jps-plugin-test/test"*/ // Obsolete + ) + } +} + +projectTest { + dependsOn(":kotlin-compiler:dist") + workingDir = rootDir +} + +testsJar {} diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt index 435a13ace89..9af8ba59fd8 100644 --- a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt @@ -46,6 +46,7 @@ import org.jetbrains.jps.util.JpsPathUtil import org.jetbrains.kotlin.config.IncrementalCompilation import org.jetbrains.kotlin.incremental.CacheVersion import org.jetbrains.kotlin.incremental.LookupSymbol +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 @@ -132,8 +133,9 @@ 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 - protected open val mockConstantSearch: Callbacks.ConstantAffectionResolver? - get() = MockConstantSearch(workDir) + // Usages in Kotlin files are expected to be found by KotlinLookupConstantSearch + private val mockConstantSearch: Callbacks.ConstantAffectionResolver? + get() = MockJavaConstantSearch(workDir) private fun build(scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()): MakeResult { val workDirPath = FileUtil.toSystemIndependentName(workDir.absolutePath) @@ -493,7 +495,16 @@ abstract class AbstractIncrementalJpsTest( } } -private class MockConstantSearch(private val workDir: File) : Callbacks.ConstantAffectionResolver { +/** + * 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, @@ -501,13 +512,19 @@ private class MockConstantSearch(private val workDir: File) : Callbacks.Constant fieldRemoved: Boolean, accessChanged: Boolean ): Future { - val affectedFiles = workDir.walk().filter { it.isFile && it.isNameUsage() } - return FixedFuture(Callbacks.ConstantAffection(affectedFiles.toList())) - } + fun File.isAffected(): Boolean { + if (!isJavaFile()) return false - private fun File.isNameUsage(): Boolean = - name.equals("usage.kt", ignoreCase = true) - || name.equals("usage.java", ignoreCase = true) + 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 diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 new file mode 100644 index 00000000000..435a13ace89 --- /dev/null +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/AbstractIncrementalJpsTest.kt.173 @@ -0,0 +1,521 @@ +/* + * 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.CacheVersion +import org.jetbrains.kotlin.incremental.LookupSymbol +import org.jetbrains.kotlin.incremental.testingUtils.* +import org.jetbrains.kotlin.jps.incremental.getKotlinCache +import org.jetbrains.kotlin.jps.incremental.withLookupStorage +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() + + 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.isEnabled() + IncrementalCompilation.setIsEnabled(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.setIsEnabled(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() + projectDescriptor.project.setTestingContext(TestingContext(lookupTracker, logger)) + + try { + val builder = IncProjectBuilder(projectDescriptor, BuilderRegistry.getInstance(), myBuildParams, CanceledStatus.NULL, mockConstantSearch, true) + val buildResult = BuildResult() + builder.addMessageHandler(buildResult) + builder.build(scope.build(), false) + + lookupTracker.lookups.mapTo(lookupsDuringTest) { LookupSymbol(it.name, it.scopeFqName) } + + 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 }) { + append("\n") + append(project.dataManager.getKotlinCache(target).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 + } + AbstractKotlinJpsBuildTestCase.addKotlinStdlibDependency(myProject) + AbstractKotlinJpsBuildTestCase.addKotlinTestDependency(myProject) + return moduleNames + } + + + protected open fun preProcessSources(srcDir: File) { + } + + override fun doGetProjectDir(): File? = workDir + + private class MyLogger(val rootPath: String) : ProjectBuilderLoggerBase(), BuildLogger { + private val markedDirtyBeforeRound = ArrayList() + private val markedDirtyAfterRound = ArrayList() + + override fun actionsOnCacheVersionChanged(actions: List) { + if (actions.size > 1 && actions.any { it != CacheVersion.Action.DO_NOTHING }) { + logLine("Actions after cache changed: $actions") + } + } + + override fun markedAsDirtyBeforeRound(files: Iterable) { + markedDirtyBeforeRound.addAll(files) + } + + override fun markedAsDirtyAfterRound(files: Iterable) { + markedDirtyAfterRound.addAll(files) + } + + override fun buildStarted(context: CompileContext, chunk: ModuleChunk) { + if (!chunk.isDummy(context) && context.projectDescriptor.project.modules.size > 1) { + logLine("Building ${chunk.modules.sortedBy { it.name }.joinToString { it.name }}") + } + } + + override fun afterBuildStarted(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/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt index 7a05d9e9c08..fbb7ce3022b 100644 --- a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt @@ -16,11 +16,6 @@ package org.jetbrains.kotlin.jps.build -import org.jetbrains.jps.builders.java.dependencyView.Callbacks -import com.intellij.util.concurrency.FixedFuture -import java.io.File -import java.util.concurrent.Future - class IncrementalConstantSearchTest : AbstractIncrementalJpsTest() { fun testJavaConstantChangedUsedInKotlin() { doTest("jps-plugin/testData/incremental/custom/javaConstantChangedUsedInKotlin/") @@ -45,26 +40,4 @@ class IncrementalConstantSearchTest : AbstractIncrementalJpsTest() { fun testKotlinJvmFieldUnchangedUsedInJava() { doTest("jps-plugin/testData/incremental/custom/kotlinJvmFieldUnchangedUsedInJava/") } - - override val mockConstantSearch: Callbacks.ConstantAffectionResolver? - get() = object : Callbacks.ConstantAffectionResolver { - override fun request( - ownerClassName: String?, - fieldName: String?, - accessFlags: Int, - fieldRemoved: Boolean, - accessChanged: Boolean - ): Future { - // We emulate how constant affection service works in IDEA: - // it is able to find Kotlin usages of Java constant, but can't find Java usages of Kotlin constant - val affectedFiles = - when { - ownerClassName == "JavaClass" && fieldName == "CONST" -> listOf(File(workDir, "src/usage.kt")) - ownerClassName == "test.Klass" && fieldName == "CONST" -> listOf(File(workDir, "src/Usage.java")) - else -> emptyList() - } - - return FixedFuture(Callbacks.ConstantAffection(affectedFiles)) - } - } } \ No newline at end of file diff --git a/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt.173 b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt.173 new file mode 100644 index 00000000000..7a05d9e9c08 --- /dev/null +++ b/jps/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/IncrementalConstantSearchTest.kt.173 @@ -0,0 +1,70 @@ +/* + * 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 org.jetbrains.jps.builders.java.dependencyView.Callbacks +import com.intellij.util.concurrency.FixedFuture +import java.io.File +import java.util.concurrent.Future + +class IncrementalConstantSearchTest : AbstractIncrementalJpsTest() { + fun testJavaConstantChangedUsedInKotlin() { + doTest("jps-plugin/testData/incremental/custom/javaConstantChangedUsedInKotlin/") + } + + fun testJavaConstantUnchangedUsedInKotlin() { + doTest("jps-plugin/testData/incremental/custom/javaConstantUnchangedUsedInKotlin/") + } + + fun testKotlinConstantChangedUsedInJava() { + doTest("jps-plugin/testData/incremental/custom/kotlinConstantChangedUsedInJava/") + } + + fun testKotlinJvmFieldChangedUsedInJava() { + doTest("jps-plugin/testData/incremental/custom/kotlinJvmFieldChangedUsedInJava/") + } + + fun testKotlinConstantUnchangedUsedInJava() { + doTest("jps-plugin/testData/incremental/custom/kotlinConstantUnchangedUsedInJava/") + } + + fun testKotlinJvmFieldUnchangedUsedInJava() { + doTest("jps-plugin/testData/incremental/custom/kotlinJvmFieldUnchangedUsedInJava/") + } + + override val mockConstantSearch: Callbacks.ConstantAffectionResolver? + get() = object : Callbacks.ConstantAffectionResolver { + override fun request( + ownerClassName: String?, + fieldName: String?, + accessFlags: Int, + fieldRemoved: Boolean, + accessChanged: Boolean + ): Future { + // We emulate how constant affection service works in IDEA: + // it is able to find Kotlin usages of Java constant, but can't find Java usages of Kotlin constant + val affectedFiles = + when { + ownerClassName == "JavaClass" && fieldName == "CONST" -> listOf(File(workDir, "src/usage.kt")) + ownerClassName == "test.Klass" && fieldName == "CONST" -> listOf(File(workDir, "src/Usage.java")) + else -> emptyList() + } + + return FixedFuture(Callbacks.ConstantAffection(affectedFiles)) + } + } +} \ No newline at end of file diff --git a/jps/jps-plugin/src/META-INF/services/org.jetbrains.jps.builders.java.JavaBuilderExtension b/jps/jps-plugin/src/META-INF/services/org.jetbrains.jps.builders.java.JavaBuilderExtension new file mode 100644 index 00000000000..206b2c9a36d --- /dev/null +++ b/jps/jps-plugin/src/META-INF/services/org.jetbrains.jps.builders.java.JavaBuilderExtension @@ -0,0 +1 @@ +org.jetbrains.kotlin.jps.build.KotlinJavaBuilderExtension \ No newline at end of file diff --git a/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinJavaBuilderExtension.kt b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinJavaBuilderExtension.kt new file mode 100644 index 00000000000..63c9e0ba471 --- /dev/null +++ b/jps/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinJavaBuilderExtension.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2000-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 org.jetbrains.jps.api.BasicFuture +import org.jetbrains.jps.builders.java.JavaBuilderExtension +import org.jetbrains.jps.builders.java.dependencyView.Callbacks +import org.jetbrains.jps.incremental.CompileContext +import org.jetbrains.kotlin.incremental.LookupSymbol +import org.jetbrains.kotlin.jps.incremental.withLookupStorage +import java.io.File +import java.util.concurrent.Executors +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit + +class KotlinJavaBuilderExtension : JavaBuilderExtension() { + override fun getConstantSearch(context: CompileContext): Callbacks.ConstantAffectionResolver { + return KotlinLookupConstantSearch(context) + } +} + +private class KotlinLookupConstantSearch(context: CompileContext) : Callbacks.ConstantAffectionResolver { + private val pool = Executors.newSingleThreadExecutor() + private val dataManager = context.projectDescriptor.dataManager + + override fun request( + ownerClassName: String, + fieldName: String, + accessFlags: Int, + fieldRemoved: Boolean, + accessChanged: Boolean + ): Future { + val future = object : BasicFuture() { + @Volatile + private var result: Callbacks.ConstantAffection = Callbacks.ConstantAffection.EMPTY + + fun result(files: Collection) { + result = Callbacks.ConstantAffection(files) + setDone() + } + + override fun get(): Callbacks.ConstantAffection { + super.get() + return result + } + + override fun get(timeout: Long, unit: TimeUnit): Callbacks.ConstantAffection { + super.get(timeout, unit) + return result + } + } + pool.submit { + if (!future.isCancelled) { + dataManager.withLookupStorage { storage -> + val paths = storage.get(LookupSymbol(name = fieldName, scope = ownerClassName)) + future.result(paths.map { File(it) }) + } + } + } + return future + } +} \ No newline at end of file