diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/klib/KlibABITestUtils.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/klib/KlibABITestUtils.kt index e38bbaa8e90..0ac24a0b305 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/klib/KlibABITestUtils.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/klib/KlibABITestUtils.kt @@ -18,8 +18,8 @@ object KlibABITestUtils { val buildDir: File val stdlibFile: File - fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection, klibFile: File) - fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection) + fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: Dependencies, klibFile: File) + fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: Dependencies) fun onNonEmptyBuildDirectory(directory: File) @@ -27,6 +27,15 @@ object KlibABITestUtils { fun onIgnoredTest() } + class Dependencies(val regularDependencies: Set, val friendDependencies: Set) { + fun mergeWith(other: Dependencies): Dependencies = + Dependencies(regularDependencies + other.regularDependencies, friendDependencies + other.friendDependencies) + + companion object { + val EMPTY = Dependencies(emptySet(), emptySet()) + } + } + fun runTest(testConfiguration: TestConfiguration) = with(testConfiguration) { val projectName = testDir.name @@ -70,6 +79,9 @@ object KlibABITestUtils { } } + // Collect all dependencies for building the final binary file. + var binaryDependencies = Dependencies.EMPTY + projectInfo.steps.forEach { projectStep -> projectStep.order.forEach { moduleName -> val (moduleInfo, moduleTestDir, moduleBuildDirs, klibFile) = modulesMap[moduleName] @@ -84,24 +96,31 @@ object KlibABITestUtils { if (!moduleBuildDirs.outputDir.list().isNullOrEmpty()) onNonEmptyBuildDirectory(moduleBuildDirs.outputDir) - val moduleDependencies = moduleStep.dependencies.map { dependencyName -> - if (dependencyName == "stdlib") - stdlibFile - else - modulesMap[dependencyName]?.klibFile ?: fail { "No module $dependencyName found on step ${projectStep.id}" } + val regularDependencies = hashSetOf() + val friendDependencies = hashSetOf() + + moduleStep.dependencies.forEach { dependency -> + if (dependency.moduleName == "stdlib") + regularDependencies += stdlibFile + else { + val moduleFile = modulesMap[dependency.moduleName]?.klibFile + ?: fail { "No module ${dependency.moduleName} found on step ${projectStep.id}" } + regularDependencies += moduleFile + if (dependency.isFriend) friendDependencies += moduleFile + } } - buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, moduleDependencies, klibFile) + val dependencies = Dependencies(regularDependencies, friendDependencies) + binaryDependencies = binaryDependencies.mergeWith(dependencies) + + buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, dependencies, klibFile) } } val mainModuleKlibFile = modulesMap[MAIN_MODULE_NAME]?.klibFile ?: fail { "No main module $MAIN_MODULE_NAME found" } - val allKlibs = buildSet { - this += stdlibFile - modulesMap.mapTo(this) { (_, module) -> module.klibFile } - } + binaryDependencies = binaryDependencies.mergeWith(Dependencies(setOf(mainModuleKlibFile), emptySet())) - buildBinaryAndRun(mainModuleKlibFile, allKlibs) + buildBinaryAndRun(mainModuleKlibFile, binaryDependencies) } private fun copySources(from: File, to: File) { diff --git a/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/TestModel.kt b/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/TestModel.kt index 8def6f0bd8f..9d8fb305723 100644 --- a/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/TestModel.kt +++ b/compiler/tests-compiler-utils/tests/org/jetbrains/kotlin/codegen/TestModel.kt @@ -41,9 +41,11 @@ class ModuleInfo(val moduleName: String) { abstract fun execute(testDirectory: File, sourceDirectory: File, deletedFilesCollector: (File) -> Unit = {}) } + class Dependency(val moduleName: String, val isFriend: Boolean) + class ModuleStep( val id: Int, - val dependencies: Collection, + val dependencies: Collection, val modifications: List, val expectedFileStats: Map> ) @@ -51,9 +53,18 @@ class ModuleInfo(val moduleName: String) { val steps = mutableListOf() } -const val MODULES_LIST = "MODULES" const val PROJECT_INFO_FILE = "project.info" +private const val MODULES_LIST = "MODULES" +private const val LIBS_LIST = "libs" +private const val DIRTY_JS_MODULES_LIST = "dirty js" +private const val LANGUAGE = "language" + const val MODULE_INFO_FILE = "module.info" +private const val DEPENDENCIES = "dependencies" +private const val FRIENDS = "friends" +private const val MODIFICATIONS = "modifications" +private const val MODIFICATION_UPDATE = "U" +private const val MODIFICATION_DELETE = "D" private val STEP_PATTERN = Pattern.compile("^\\s*STEP\\s+(\\d+)\\.*(\\d+)?\\s*:?$") @@ -104,8 +115,8 @@ class ProjectInfoParser(infoFile: File) : InfoParser(infoFile) { val splitIndex = line.indexOf(':') if (splitIndex < 0) throwSyntaxError(line) - val splitted = line.split(":") - val op = splitted[0] + val split = line.split(":") + val op = split[0] if (op.matches(STEP_PATTERN.toRegex())) { return@loop true // break the loop @@ -115,15 +126,9 @@ class ProjectInfoParser(infoFile: File) : InfoParser(infoFile) { when (op) { - "libs" -> { - order += splitted[1].splitAndTrim() - } - "dirty js" -> { - dirtyJS += splitted[1].splitAndTrim() - } - "language" -> { - language += splitted[1].splitAndTrim() - } + LIBS_LIST -> order += split[1].splitAndTrim() + DIRTY_JS_MODULES_LIST -> dirtyJS += split[1].splitAndTrim() + LANGUAGE -> language += split[1].splitAndTrim() else -> println(diagnosticMessage("Unknown op $op", line)) } @@ -149,13 +154,11 @@ class ProjectInfoParser(infoFile: File) : InfoParser(infoFile) { val splitIndex = line.indexOf(':') if (splitIndex < 0) throwSyntaxError(line) - val splitted = line.split(":") - val op = splitted[0] + val split = line.split(":") + val op = split[0] when { - op == MODULES_LIST -> { - libraries += splitted[1].splitAndTrim() - } + op == MODULES_LIST -> libraries += split[1].splitAndTrim() op.matches(STEP_PATTERN.toRegex()) -> { val m = STEP_PATTERN.matcher(op) if (!m.matches()) throwSyntaxError(line) @@ -164,7 +167,7 @@ class ProjectInfoParser(infoFile: File) : InfoParser(infoFile) { val lastId = m.group(2)?.let { Integer.parseInt(it) } ?: firstId steps += parseSteps(firstId, lastId) } - else -> println(diagnosticMessage("Unknown op $op", line)) + else -> error(diagnosticMessage("Unknown op $op", line)) } false @@ -186,11 +189,11 @@ class ModuleInfoParser(infoFile: File) : InfoParser(infoFile) { val mop = matcher3.group(1) val cmd = matcher3.group(2) when (mop) { - "U" -> { + MODIFICATION_UPDATE -> { val (from, to) = cmd.split("->") modifications.add(ModuleInfo.Modification.Update(from.trim(), to.trim())) } - "D" -> modifications.add(ModuleInfo.Modification.Delete(cmd.trim())) + MODIFICATION_DELETE -> modifications.add(ModuleInfo.Modification.Delete(cmd.trim())) else -> error("Unknown modification $line") } false @@ -204,7 +207,8 @@ class ModuleInfoParser(infoFile: File) : InfoParser(infoFile) { private fun parseSteps(firstId: Int, lastId: Int): List { val expectedFileStats = mutableMapOf>() - val dependencies = mutableSetOf() + val regularDependencies = mutableSetOf() + val friendDependencies = mutableSetOf() val modifications = mutableListOf() loop { line -> @@ -223,15 +227,24 @@ class ModuleInfoParser(infoFile: File) : InfoParser(infoFile) { expectedFileStats[expectedState.str] = getOpArgs().toSet() } else { when (op) { - "dependencies" -> getOpArgs().forEach { dependencies.add(it) } - "modifications" -> modifications.addAll(parseModifications()) - else -> println(diagnosticMessage("Unknown op $op", line)) + DEPENDENCIES -> getOpArgs().forEach { regularDependencies += it } + FRIENDS -> getOpArgs().forEach { friendDependencies += it } + MODIFICATIONS -> modifications += parseModifications() + else -> error(diagnosticMessage("Unknown op $op", line)) } } false } + (friendDependencies - regularDependencies) + .takeIf(Set::isNotEmpty) + ?.let { error("Misconfiguration: There are friend modules that are not listed as regular dependencies: $it") } + + val dependencies = regularDependencies.map { regularDependency -> + ModuleInfo.Dependency(regularDependency, regularDependency in friendDependencies) + } + return (firstId..lastId).map { ModuleInfo.ModuleStep( id = it, diff --git a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt index 493bd2244f7..ad1f34ff837 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/incremental/AbstractInvalidationTest.kt @@ -43,7 +43,7 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() { private const val BOX_FUNCTION_NAME = "box" private const val STDLIB_ALIAS = "stdlib" - private val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib-js-ir" + private const val STDLIB_MODULE_NAME = "kotlin-kotlin-stdlib-js-ir" private val STDLIB_KLIB = File(System.getProperty("kotlin.js.stdlib.klib.path") ?: error("Please set stdlib path")).canonicalPath private val KT_FILE_IGNORE_PATTERN = Regex("^.*\\..+\\.kt$") @@ -139,7 +139,9 @@ abstract class AbstractInvalidationTest : KotlinTestWithEnvironment() { modification.execute(moduleTestDir, moduleSourceDir) { deletedFiles.add(it.name) } } - val dependencies = moduleStep.dependencies.mapTo(mutableListOf(File(STDLIB_KLIB))) { resolveModuleArtifact(it, buildDir) } + val dependencies = moduleStep.dependencies.mapTo(mutableListOf(File(STDLIB_KLIB))) { + resolveModuleArtifact(it.moduleName, buildDir) + } val outputKlibFile = resolveModuleArtifact(module, buildDir) val configuration = createConfiguration(module, projStep.language) buildArtifact(configuration, module, moduleSourceDir, dependencies, outputKlibFile) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt index fc57197fff0..1cf81effff2 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/AbstractJsKLibABITestCase.kt @@ -71,11 +71,11 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { override val buildDir: File get() = this@AbstractJsKLibABITestCase.buildDir override val stdlibFile: File get() = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile - override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection, klibFile: File) = - this@AbstractJsKLibABITestCase.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile) + override fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) = + this@AbstractJsKLibABITestCase.buildKlib(moduleName, moduleSourceDir, dependencies, klibFile) - override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection) = - this@AbstractJsKLibABITestCase.buildBinaryAndRun(mainModuleKlibFile, allDependencies) + override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: KlibABITestUtils.Dependencies) = + this@AbstractJsKLibABITestCase.buildBinaryAndRun(mainModuleKlibFile, dependencies) override fun onNonEmptyBuildDirectory(directory: File) { directory.listFiles()?.forEach(File::deleteRecursively) @@ -103,7 +103,7 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { // The entry point to generated test classes. fun doTest(testPath: String) = KlibABITestUtils.runTest(JsTestConfiguration(testPath)) - private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection, klibFile: File) { + private fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) { val ktFiles = environment.createPsiFiles(moduleSourceDir) val config = environment.configuration.copy() @@ -113,15 +113,15 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { environment.project, ktFiles, config, - moduleDependencies.map { it.path }, - emptyList(), // TODO + dependencies.regularDependencies.map { it.path }, + dependencies.friendDependencies.map { it.path }, AnalyzerWithCompilerReport(config) ) generateKLib(sourceModule, IrFactoryImpl, klibFile.path, nopack = false, jsOutputName = moduleName) } - private fun buildBinaryAndRun(mainModuleKlibFile: File, libraries: Collection) { + private fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: KlibABITestUtils.Dependencies) { val configuration = environment.configuration.copy() configuration.put(JSConfigurationKeys.PARTIAL_LINKAGE, true) @@ -130,12 +130,12 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { configuration.put(CommonConfigurationKeys.MODULE_NAME, MAIN_MODULE_NAME) val compilationOutputs = if (useIncrementalCompiler) - buildBinaryWithIC(configuration, mainModuleKlibFile, libraries) + buildBinaryWithIC(configuration, mainModuleKlibFile, allDependencies) else - buildBinaryNoIC(configuration, mainModuleKlibFile, libraries) + buildBinaryNoIC(configuration, mainModuleKlibFile, allDependencies) val binariesDir = File(buildDir, BIN_DIR_NAME).also { it.mkdirs() } - val binaries = ArrayList(libraries.size) + val binaries = ArrayList(allDependencies.regularDependencies.size) for ((name, code) in compilationOutputs.dependencies) { val depBinary = binariesDir.binJsFile(name) @@ -154,14 +154,15 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { private fun buildBinaryWithIC( configuration: CompilerConfiguration, mainModuleKlibFile: File, - libraries: Collection + allDependencies: KlibABITestUtils.Dependencies ): CompilationOutputs { fun cacheDir(library: File): File = buildDir.resolve("libs-cache").resolve(library.name).apply { mkdirs() } + // TODO: what about friend dependencies? val cacheUpdater = CacheUpdater( mainModule = mainModuleKlibFile.absolutePath, - allModules = libraries.map { it.absolutePath }, - icCachePaths = libraries.map { cacheDir(it).absolutePath }, + allModules = allDependencies.regularDependencies.map { it.path }, + icCachePaths = allDependencies.regularDependencies.map { cacheDir(it).path }, compilerConfiguration = configuration, irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) }, mainArguments = null, @@ -186,10 +187,16 @@ abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() { private fun buildBinaryNoIC( configuration: CompilerConfiguration, mainModuleKlibFile: File, - libraries: Collection + allDependencies: KlibABITestUtils.Dependencies ): CompilationOutputs { val klib = MainModule.Klib(mainModuleKlibFile.path) - val moduleStructure = ModulesStructure(environment.project, klib, configuration, libraries.map { it.path }, emptyList()) + val moduleStructure = ModulesStructure( + environment.project, + klib, + configuration, + allDependencies.regularDependencies.map { it.path }, + allDependencies.friendDependencies.map { it.path } + ) val ir = compile( moduleStructure, diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt index 9ce30055e59..38c7c2d019e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt @@ -23,18 +23,18 @@ import java.io.File @Tag("klib-abi") abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() { - private val producedKlibs = linkedMapOf>() // IMPORTANT: The order makes sense! + private val producedKlibs = linkedMapOf() // IMPORTANT: The order makes sense! private inner class NativeTestConfiguration(testPath: String) : KlibABITestUtils.TestConfiguration { override val testDir = getAbsoluteFile(testPath) override val buildDir get() = this@AbstractNativeKlibABITest.buildDir override val stdlibFile get() = this@AbstractNativeKlibABITest.stdlibFile - override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection, klibFile: File) = - this@AbstractNativeKlibABITest.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile) + override fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) = + this@AbstractNativeKlibABITest.buildKlib(moduleName, moduleSourceDir, dependencies, klibFile) - override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection) = - this@AbstractNativeKlibABITest.buildBinaryAndRun(allDependencies) + override fun buildBinaryAndRun(mainModuleKlibFile: File, dependencies: KlibABITestUtils.Dependencies) = + this@AbstractNativeKlibABITest.buildBinaryAndRun(dependencies) override fun onNonEmptyBuildDirectory(directory: File) = backupDirectoryContents(directory) @@ -44,7 +44,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() { // The entry point to generated test classes. protected fun runTest(@TestDataFile testPath: String) = KlibABITestUtils.runTest(NativeTestConfiguration(testPath)) - private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection, klibFile: File) { + private fun buildKlib(moduleName: String, moduleSourceDir: File, dependencies: KlibABITestUtils.Dependencies, klibFile: File) { val module = createModule(moduleName) moduleSourceDir.walk() .filter { file -> file.isFile && file.extension == "kt" } @@ -57,16 +57,16 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() { settings = testRunSettings, freeCompilerArgs = testCase.freeCompilerArgs, sourceModules = testCase.modules, - dependencies = createLibraryDependencies(moduleDependencies), + dependencies = createLibraryDependencies(dependencies), expectedArtifact = klibArtifact ) compilation.result.assertSuccess() // <-- trigger compilation - producedKlibs[klibArtifact] = moduleDependencies // Remember the artifact with its dependencies. + producedKlibs[klibArtifact] = dependencies // Remember the artifact with its dependencies. } - private fun buildBinaryAndRun(allDependencies: Collection) { + private fun buildBinaryAndRun(allDependencies: KlibABITestUtils.Dependencies) { val cacheDependencies = if (staticCacheRequiredForEveryLibrary) { producedKlibs.map { (klibArtifact, moduleDependencies) -> buildCacheForKlib(moduleDependencies, klibArtifact) @@ -102,7 +102,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() { runExecutableAndVerify(testCase, executable) // <-- run executable and verify } - private fun buildCacheForKlib(moduleDependencies: Collection, klibArtifact: KLIB) { + private fun buildCacheForKlib(moduleDependencies: KlibABITestUtils.Dependencies, klibArtifact: KLIB) { val compilation = StaticCacheCompilation( settings = testRunSettings, freeCompilerArgs = COMPILER_ARGS_FOR_STATIC_CACHE_AND_EXECUTABLE, @@ -133,13 +133,20 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() { initialize(null) } - private fun createLibraryDependencies(klibFiles: Iterable): Iterable> = - klibFiles.map { klibFile -> KLIB(klibFile).toDependency() } + private fun createLibraryDependencies(dependencies: KlibABITestUtils.Dependencies): Iterable> = + with(dependencies) { + regularDependencies.map { KLIB(it).toDependency() } + friendDependencies.map { KLIB(it).toFriendDependency() } + } - private fun createLibraryCacheDependencies(klibFiles: Iterable): Iterable> = - klibFiles.mapNotNull { klibFile -> if (klibFile != stdlibFile) KLIB(klibFile).toStaticCacheArtifact().toDependency() else null } + private fun createLibraryCacheDependencies(dependencies: KlibABITestUtils.Dependencies): Iterable> = + with(dependencies) { + regularDependencies.mapNotNull { klibFile -> + if (klibFile != stdlibFile) KLIB(klibFile).toStaticCacheArtifact().toDependency() else null + } + } private fun KLIB.toDependency() = ExistingDependency(this, Library) + private fun KLIB.toFriendDependency() = ExistingDependency(this, TestCompilationDependencyType.FriendLibrary) private fun KLIBStaticCache.toDependency() = ExistingDependency(this, TestCompilationDependencyType.LibraryStaticCache) private fun KLIB.toStaticCacheArtifact() = KLIBStaticCache(