@@ -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))
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
val compilerModules: Array<String> 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 {}
|
||||
+26
-9
@@ -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<Callbacks.ConstantAffection> {
|
||||
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<ModuleBuildTarget>
|
||||
|
||||
+521
@@ -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<LookupSymbol>
|
||||
private var isICEnabledBackup: Boolean = false
|
||||
|
||||
protected var mapWorkingToOriginalFile: MutableMap<File, File> = 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<String, List<DependencyDescriptor>>? {
|
||||
val dependenciesTxt = File(testDataDir, "dependencies.txt")
|
||||
if (!dependenciesTxt.exists()) return null
|
||||
|
||||
val result = HashMap<String, List<DependencyDescriptor>>()
|
||||
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<AbstractIncrementalJpsTest.MakeResult>): 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("<target $target>\n")
|
||||
append(project.dataManager.getKotlinCache(target).dump())
|
||||
append("</target $target>\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<String>?): List<MakeResult> {
|
||||
val results = arrayListOf<MakeResult>()
|
||||
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<Modification>) {
|
||||
}
|
||||
|
||||
// null means one module
|
||||
private fun configureModules(): Set<String>? {
|
||||
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<String>?
|
||||
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<File>()
|
||||
private val markedDirtyAfterRound = ArrayList<File>()
|
||||
|
||||
override fun actionsOnCacheVersionChanged(actions: List<CacheVersion.Action>) {
|
||||
if (actions.size > 1 && actions.any { it != CacheVersion.Action.DO_NOTHING }) {
|
||||
logLine("Actions after cache changed: $actions")
|
||||
}
|
||||
}
|
||||
|
||||
override fun markedAsDirtyBeforeRound(files: Iterable<File>) {
|
||||
markedDirtyBeforeRound.addAll(files)
|
||||
}
|
||||
|
||||
override fun markedAsDirtyAfterRound(files: Iterable<File>) {
|
||||
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<File>) {
|
||||
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<File>()
|
||||
|
||||
override fun isEnabled(): Boolean = true
|
||||
|
||||
override fun logCompiledFiles(files: MutableCollection<File>?, 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<Callbacks.ConstantAffection> {
|
||||
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<ModuleBuildTarget>
|
||||
get() = buildTargetIndex.allTargets.filterIsInstance<ModuleBuildTarget>()
|
||||
|
||||
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]"
|
||||
-27
@@ -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<Callbacks.ConstantAffection> {
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -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<Callbacks.ConstantAffection> {
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.jps.build.KotlinJavaBuilderExtension
|
||||
@@ -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<Callbacks.ConstantAffection> {
|
||||
val future = object : BasicFuture<Callbacks.ConstantAffection>() {
|
||||
@Volatile
|
||||
private var result: Callbacks.ConstantAffection = Callbacks.ConstantAffection.EMPTY
|
||||
|
||||
fun result(files: Collection<File>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user