@@ -16,7 +16,7 @@ dependencies {
|
|||||||
compile(projectRuntimeJar(":kotlin-preloader"))
|
compile(projectRuntimeJar(":kotlin-preloader"))
|
||||||
compile(project(":idea:idea-jps-common"))
|
compile(project(":idea:idea-jps-common"))
|
||||||
compileOnly(group = "org.jetbrains", name = "annotations", version = "13.0")
|
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") }
|
compileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") }
|
||||||
testCompileOnly(project(":kotlin-reflect-api"))
|
testCompileOnly(project(":kotlin-reflect-api"))
|
||||||
testCompile(project(":compiler:incremental-compilation-impl"))
|
testCompile(project(":compiler:incremental-compilation-impl"))
|
||||||
@@ -26,7 +26,7 @@ dependencies {
|
|||||||
testCompile(projectDist(":kotlin-test:kotlin-test-jvm"))
|
testCompile(projectDist(":kotlin-test:kotlin-test-jvm"))
|
||||||
testCompile(projectTests(":kotlin-build-common"))
|
testCompile(projectTests(":kotlin-build-common"))
|
||||||
testCompileOnly(intellijDep("jps-standalone")) { includeJars("jps-builders", "jps-builders-6") }
|
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"))
|
testCompile(intellijDep("jps-build-test"))
|
||||||
compilerModules.forEach {
|
compilerModules.forEach {
|
||||||
testRuntime(project(it))
|
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.config.IncrementalCompilation
|
||||||
import org.jetbrains.kotlin.incremental.CacheVersion
|
import org.jetbrains.kotlin.incremental.CacheVersion
|
||||||
import org.jetbrains.kotlin.incremental.LookupSymbol
|
import org.jetbrains.kotlin.incremental.LookupSymbol
|
||||||
|
import org.jetbrains.kotlin.incremental.isJavaFile
|
||||||
import org.jetbrains.kotlin.incremental.testingUtils.*
|
import org.jetbrains.kotlin.incremental.testingUtils.*
|
||||||
import org.jetbrains.kotlin.jps.incremental.getKotlinCache
|
import org.jetbrains.kotlin.jps.incremental.getKotlinCache
|
||||||
import org.jetbrains.kotlin.jps.incremental.withLookupStorage
|
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
|
// 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
|
// is not provided, so ConstantAffectionResolver is mocked with empty implementation
|
||||||
protected open val mockConstantSearch: Callbacks.ConstantAffectionResolver?
|
// Usages in Kotlin files are expected to be found by KotlinLookupConstantSearch
|
||||||
get() = MockConstantSearch(workDir)
|
private val mockConstantSearch: Callbacks.ConstantAffectionResolver?
|
||||||
|
get() = MockJavaConstantSearch(workDir)
|
||||||
|
|
||||||
private fun build(scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()): MakeResult {
|
private fun build(scope: CompileScopeTestBuilder = CompileScopeTestBuilder.make().allModules()): MakeResult {
|
||||||
val workDirPath = FileUtil.toSystemIndependentName(workDir.absolutePath)
|
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(
|
override fun request(
|
||||||
ownerClassName: String,
|
ownerClassName: String,
|
||||||
fieldName: String,
|
fieldName: String,
|
||||||
@@ -501,13 +512,19 @@ private class MockConstantSearch(private val workDir: File) : Callbacks.Constant
|
|||||||
fieldRemoved: Boolean,
|
fieldRemoved: Boolean,
|
||||||
accessChanged: Boolean
|
accessChanged: Boolean
|
||||||
): Future<Callbacks.ConstantAffection> {
|
): Future<Callbacks.ConstantAffection> {
|
||||||
val affectedFiles = workDir.walk().filter { it.isFile && it.isNameUsage() }
|
fun File.isAffected(): Boolean {
|
||||||
return FixedFuture(Callbacks.ConstantAffection(affectedFiles.toList()))
|
if (!isJavaFile()) return false
|
||||||
}
|
|
||||||
|
|
||||||
private fun File.isNameUsage(): Boolean =
|
if (nameWithoutExtension == ownerClassName.substringAfterLast(".")) return false
|
||||||
name.equals("usage.kt", ignoreCase = true)
|
|
||||||
|| name.equals("usage.java", ignoreCase = true)
|
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>
|
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
|
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() {
|
class IncrementalConstantSearchTest : AbstractIncrementalJpsTest() {
|
||||||
fun testJavaConstantChangedUsedInKotlin() {
|
fun testJavaConstantChangedUsedInKotlin() {
|
||||||
doTest("jps-plugin/testData/incremental/custom/javaConstantChangedUsedInKotlin/")
|
doTest("jps-plugin/testData/incremental/custom/javaConstantChangedUsedInKotlin/")
|
||||||
@@ -45,26 +40,4 @@ class IncrementalConstantSearchTest : AbstractIncrementalJpsTest() {
|
|||||||
fun testKotlinJvmFieldUnchangedUsedInJava() {
|
fun testKotlinJvmFieldUnchangedUsedInJava() {
|
||||||
doTest("jps-plugin/testData/incremental/custom/kotlinJvmFieldUnchangedUsedInJava/")
|
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