[IR][tests] Refactoring: Get rid of AbstractKlibABITestCase
This commit is contained in:
committed by
Space Team
parent
b5655dfaac
commit
f048e3ddf7
@@ -1,171 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.klib
|
||||
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase.Companion.ModuleBuildDirs.Companion.OUTPUT_DIR_NAME
|
||||
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase.Companion.ModuleBuildDirs.Companion.SOURCE_DIR_NAME
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.junit.jupiter.api.fail
|
||||
import java.io.File
|
||||
import kotlin.io.path.createTempDirectory
|
||||
|
||||
abstract class AbstractKlibABITestCase : KtUsefulTestCase() {
|
||||
protected lateinit var buildDir: File
|
||||
protected lateinit var environment: KotlinCoreEnvironment
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
buildDir = createTempDirectory().toFile().also { it.mkdirs() }
|
||||
|
||||
environment = KotlinCoreEnvironment.createForTests(
|
||||
testRootDisposable,
|
||||
CompilerConfiguration(),
|
||||
EnvironmentConfigFiles.JS_CONFIG_FILES
|
||||
)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
buildDir.deleteRecursively()
|
||||
}
|
||||
|
||||
protected abstract fun stdlibFile(): File
|
||||
|
||||
protected abstract fun buildKlib(
|
||||
moduleName: String,
|
||||
moduleSourceDir: File,
|
||||
moduleDependencies: Collection<File>,
|
||||
klibFile: File
|
||||
)
|
||||
|
||||
protected abstract fun buildBinaryAndRun(
|
||||
mainModuleKlibFile: File,
|
||||
libraries: Collection<File>
|
||||
)
|
||||
|
||||
fun doTest(testPath: String) = Companion.doTest(
|
||||
testDir = File(testPath).absoluteFile,
|
||||
buildDir = buildDir,
|
||||
stdlibFile = stdlibFile(),
|
||||
buildKlib = ::buildKlib,
|
||||
buildBinaryAndRun = ::buildBinaryAndRun,
|
||||
onNonEmptyBuildDirectory = { directory -> directory.listFiles()?.forEach(File::deleteRecursively) },
|
||||
onIgnoredTest = { /* Do nothing specific. JUnit 3 does not support programmatic tests muting. */ }
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun doTest(
|
||||
testDir: File,
|
||||
buildDir: File,
|
||||
stdlibFile: File,
|
||||
buildKlib: (moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) -> Unit,
|
||||
buildBinaryAndRun: (mainModuleKlibFile: File, allDependencies: Collection<File>) -> Unit,
|
||||
onNonEmptyBuildDirectory: (directory: File) -> Unit,
|
||||
onIgnoredTest: () -> Unit
|
||||
) {
|
||||
val projectName = testDir.name
|
||||
|
||||
val projectInfoFile = File(testDir, PROJECT_INFO_FILE)
|
||||
val projectInfo: ProjectInfo = ProjectInfoParser(projectInfoFile).parse(projectName)
|
||||
|
||||
if (projectInfo.muted) {
|
||||
onIgnoredTest() // Ignore muted tests.
|
||||
return
|
||||
}
|
||||
|
||||
val modulesMap: Map<String, ModuleUnderTest> = buildMap {
|
||||
projectInfo.modules.forEach { moduleName ->
|
||||
val moduleTestDir = File(testDir, moduleName)
|
||||
assertExists(moduleTestDir)
|
||||
|
||||
val moduleInfoFile = File(moduleTestDir, MODULE_INFO_FILE)
|
||||
val moduleInfo = ModuleInfoParser(moduleInfoFile).parse(moduleName)
|
||||
|
||||
val moduleBuildDirs = createModuleDirs(buildDir, moduleName)
|
||||
|
||||
// Populate the source dir with *.kt files.
|
||||
val moduleSourceDir = moduleBuildDirs.sourceDir.apply { mkdirs() }
|
||||
moduleTestDir.walk().filter { it.isFile && it.extension == "kt" }.forEach { sourceFile ->
|
||||
val destFile = moduleSourceDir.resolve(sourceFile.relativeTo(moduleTestDir))
|
||||
destFile.parentFile.mkdirs()
|
||||
sourceFile.copyTo(destFile)
|
||||
}
|
||||
|
||||
val moduleOutputDir = moduleBuildDirs.outputDir.apply { mkdirs() }
|
||||
val klibFile = moduleOutputDir.resolve("$moduleName.klib")
|
||||
|
||||
this[moduleName] = ModuleUnderTest(
|
||||
info = moduleInfo,
|
||||
testDir = moduleTestDir,
|
||||
buildDirs = moduleBuildDirs,
|
||||
klibFile = klibFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
projectInfo.steps.forEach { projectStep ->
|
||||
projectStep.order.forEach { moduleName ->
|
||||
val (moduleInfo, moduleTestDir, moduleBuildDirs, klibFile) = modulesMap[moduleName]
|
||||
?: fail { "No module $moduleName found on step ${projectStep.id}" }
|
||||
|
||||
val moduleStep = moduleInfo.steps[projectStep.id]
|
||||
|
||||
moduleStep.modifications.forEach { modification ->
|
||||
modification.execute(moduleTestDir, moduleBuildDirs.sourceDir)
|
||||
}
|
||||
|
||||
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}" }
|
||||
}
|
||||
|
||||
buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, moduleDependencies, 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 }
|
||||
}
|
||||
|
||||
buildBinaryAndRun(mainModuleKlibFile, allKlibs)
|
||||
}
|
||||
|
||||
fun createModuleDirs(buildDir: File, moduleName: String): ModuleBuildDirs {
|
||||
val moduleBuildDir = buildDir.resolve(moduleName)
|
||||
|
||||
val moduleSourceDir = moduleBuildDir.resolve(SOURCE_DIR_NAME).apply { mkdirs() }
|
||||
val moduleOutputDir = moduleBuildDir.resolve(OUTPUT_DIR_NAME).apply { mkdirs() }
|
||||
|
||||
return ModuleBuildDirs(moduleSourceDir, moduleOutputDir)
|
||||
}
|
||||
|
||||
data class ModuleBuildDirs(val sourceDir: File, val outputDir: File) {
|
||||
internal companion object {
|
||||
const val SOURCE_DIR_NAME = "sources"
|
||||
const val OUTPUT_DIR_NAME = "outputs"
|
||||
}
|
||||
}
|
||||
|
||||
private data class ModuleUnderTest(
|
||||
val info: ModuleInfo,
|
||||
val testDir: File,
|
||||
val buildDirs: ModuleBuildDirs,
|
||||
val klibFile: File
|
||||
)
|
||||
|
||||
const val MAIN_MODULE_NAME = "main"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.klib
|
||||
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.klib.KlibABITestUtils.ModuleBuildDirs.Companion.OUTPUT_DIR_NAME
|
||||
import org.jetbrains.kotlin.klib.KlibABITestUtils.ModuleBuildDirs.Companion.SOURCE_DIR_NAME
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import org.junit.jupiter.api.fail
|
||||
import java.io.File
|
||||
|
||||
object KlibABITestUtils {
|
||||
interface TestConfiguration {
|
||||
val testDir: File
|
||||
val buildDir: File
|
||||
val stdlibFile: File
|
||||
|
||||
fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File)
|
||||
fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>)
|
||||
|
||||
fun onNonEmptyBuildDirectory(directory: File)
|
||||
|
||||
fun isIgnoredTest(projectInfo: ProjectInfo): Boolean = projectInfo.muted
|
||||
fun onIgnoredTest()
|
||||
}
|
||||
|
||||
fun runTest(testConfiguration: TestConfiguration) = with(testConfiguration) {
|
||||
val projectName = testDir.name
|
||||
|
||||
val projectInfoFile = File(testDir, PROJECT_INFO_FILE)
|
||||
val projectInfo: ProjectInfo = ProjectInfoParser(projectInfoFile).parse(projectName)
|
||||
|
||||
if (isIgnoredTest(projectInfo)) {
|
||||
return onIgnoredTest() // Ignore muted tests.
|
||||
}
|
||||
|
||||
val modulesMap: Map<String, ModuleUnderTest> = buildMap {
|
||||
projectInfo.modules.forEach { moduleName ->
|
||||
val moduleTestDir = File(testDir, moduleName)
|
||||
KtUsefulTestCase.assertExists(moduleTestDir)
|
||||
|
||||
val moduleInfoFile = File(moduleTestDir, MODULE_INFO_FILE)
|
||||
val moduleInfo = ModuleInfoParser(moduleInfoFile).parse(moduleName)
|
||||
|
||||
val moduleBuildDirs = createModuleDirs(buildDir, moduleName)
|
||||
|
||||
// Populate the source dir with *.kt files.
|
||||
val moduleSourceDir = moduleBuildDirs.sourceDir.apply { mkdirs() }
|
||||
moduleTestDir.walk().filter { it.isFile && it.extension == "kt" }.forEach { sourceFile ->
|
||||
val destFile = moduleSourceDir.resolve(sourceFile.relativeTo(moduleTestDir))
|
||||
destFile.parentFile.mkdirs()
|
||||
sourceFile.copyTo(destFile)
|
||||
}
|
||||
|
||||
val moduleOutputDir = moduleBuildDirs.outputDir.apply { mkdirs() }
|
||||
val klibFile = moduleOutputDir.resolve("$moduleName.klib")
|
||||
|
||||
this[moduleName] = ModuleUnderTest(
|
||||
info = moduleInfo,
|
||||
testDir = moduleTestDir,
|
||||
buildDirs = moduleBuildDirs,
|
||||
klibFile = klibFile
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
projectInfo.steps.forEach { projectStep ->
|
||||
projectStep.order.forEach { moduleName ->
|
||||
val (moduleInfo, moduleTestDir, moduleBuildDirs, klibFile) = modulesMap[moduleName]
|
||||
?: fail { "No module $moduleName found on step ${projectStep.id}" }
|
||||
|
||||
val moduleStep = moduleInfo.steps[projectStep.id]
|
||||
|
||||
moduleStep.modifications.forEach { modification ->
|
||||
modification.execute(moduleTestDir, moduleBuildDirs.sourceDir)
|
||||
}
|
||||
|
||||
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}" }
|
||||
}
|
||||
|
||||
buildKlib(moduleInfo.moduleName, moduleBuildDirs.sourceDir, moduleDependencies, 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 }
|
||||
}
|
||||
|
||||
buildBinaryAndRun(mainModuleKlibFile, allKlibs)
|
||||
}
|
||||
|
||||
fun createModuleDirs(buildDir: File, moduleName: String): ModuleBuildDirs {
|
||||
val moduleBuildDir = buildDir.resolve(moduleName)
|
||||
|
||||
val moduleSourceDir = moduleBuildDir.resolve(SOURCE_DIR_NAME).apply { mkdirs() }
|
||||
val moduleOutputDir = moduleBuildDir.resolve(OUTPUT_DIR_NAME).apply { mkdirs() }
|
||||
|
||||
return ModuleBuildDirs(moduleSourceDir, moduleOutputDir)
|
||||
}
|
||||
|
||||
data class ModuleBuildDirs(val sourceDir: File, val outputDir: File) {
|
||||
internal companion object {
|
||||
const val SOURCE_DIR_NAME = "sources"
|
||||
const val OUTPUT_DIR_NAME = "outputs"
|
||||
}
|
||||
}
|
||||
|
||||
private data class ModuleUnderTest(
|
||||
val info: ModuleInfo,
|
||||
val testDir: File,
|
||||
val buildDirs: ModuleBuildDirs,
|
||||
val klibFile: File
|
||||
)
|
||||
|
||||
const val MAIN_MODULE_NAME = "main"
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.SingleRootFileViewProvider
|
||||
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
||||
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
@@ -25,11 +26,14 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.js.testOld.V8IrJsTestChecker
|
||||
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase
|
||||
import org.jetbrains.kotlin.klib.KlibABITestUtils
|
||||
import org.jetbrains.kotlin.klib.KlibABITestUtils.MAIN_MODULE_NAME
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
import java.io.File
|
||||
import kotlin.io.path.createTempDirectory
|
||||
|
||||
abstract class AbstractJsKLibABIWithICTestCase : AbstractJsKLibABITestCase() {
|
||||
override val useIncrementalCompiler get() = true
|
||||
@@ -39,12 +43,51 @@ abstract class AbstractJsKLibABINoICTestCase : AbstractJsKLibABITestCase() {
|
||||
override val useIncrementalCompiler get() = false
|
||||
}
|
||||
|
||||
abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
|
||||
abstract class AbstractJsKLibABITestCase : KtUsefulTestCase() {
|
||||
abstract val useIncrementalCompiler: Boolean
|
||||
|
||||
override fun stdlibFile(): File = File("libraries/stdlib/js-ir/build/classes/kotlin/js/main").absoluteFile
|
||||
private lateinit var buildDir: File
|
||||
private lateinit var environment: KotlinCoreEnvironment
|
||||
|
||||
override fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
buildDir = createTempDirectory().toFile().also { it.mkdirs() }
|
||||
|
||||
environment = KotlinCoreEnvironment.createForTests(
|
||||
testRootDisposable,
|
||||
CompilerConfiguration(),
|
||||
EnvironmentConfigFiles.JS_CONFIG_FILES
|
||||
)
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
buildDir.deleteRecursively()
|
||||
}
|
||||
|
||||
private inner class JsTestConfiguration(testPath: String) : KlibABITestUtils.TestConfiguration {
|
||||
override val testDir: File = File(testPath).absoluteFile
|
||||
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<File>, klibFile: File) =
|
||||
this@AbstractJsKLibABITestCase.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile)
|
||||
|
||||
override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>) =
|
||||
this@AbstractJsKLibABITestCase.buildBinaryAndRun(mainModuleKlibFile, allDependencies)
|
||||
|
||||
override fun onNonEmptyBuildDirectory(directory: File) {
|
||||
directory.listFiles()?.forEach(File::deleteRecursively)
|
||||
}
|
||||
|
||||
override fun onIgnoredTest() {
|
||||
/* Do nothing specific. JUnit 3 does not support programmatic tests muting. */
|
||||
}
|
||||
}
|
||||
|
||||
// The entry point to generated test classes.
|
||||
fun doTest(testPath: String) = KlibABITestUtils.runTest(JsTestConfiguration(testPath))
|
||||
|
||||
private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
|
||||
val ktFiles = environment.createPsiFiles(moduleSourceDir)
|
||||
|
||||
val config = environment.configuration.copy()
|
||||
@@ -62,7 +105,7 @@ abstract class AbstractJsKLibABITestCase : AbstractKlibABITestCase() {
|
||||
generateKLib(sourceModule, IrFactoryImpl, klibFile.path, nopack = false, jsOutputName = moduleName)
|
||||
}
|
||||
|
||||
override fun buildBinaryAndRun(mainModuleKlibFile: File, libraries: Collection<File>) {
|
||||
private fun buildBinaryAndRun(mainModuleKlibFile: File, libraries: Collection<File>) {
|
||||
val configuration = environment.configuration.copy()
|
||||
|
||||
configuration.put(JSConfigurationKeys.PARTIAL_LINKAGE, true)
|
||||
|
||||
+20
-11
@@ -6,7 +6,7 @@
|
||||
package org.jetbrains.kotlin.konan.blackboxtest
|
||||
|
||||
import com.intellij.testFramework.TestDataFile
|
||||
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase
|
||||
import org.jetbrains.kotlin.klib.KlibABITestUtils
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.*
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.*
|
||||
@@ -25,15 +25,24 @@ import java.io.File
|
||||
abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
|
||||
private val producedKlibs = linkedMapOf<KLIB, Collection<File>>() // IMPORTANT: The order makes sense!
|
||||
|
||||
protected fun runTest(@TestDataFile testPath: String): Unit = AbstractKlibABITestCase.doTest(
|
||||
testDir = getAbsoluteFile(testPath),
|
||||
buildDir = buildDir,
|
||||
stdlibFile = stdlibFile,
|
||||
buildKlib = ::buildKlib,
|
||||
buildBinaryAndRun = { _, allDependencies -> buildBinaryAndRun(allDependencies) },
|
||||
onNonEmptyBuildDirectory = ::backupDirectoryContents,
|
||||
onIgnoredTest = { throw TestAbortedException() }
|
||||
)
|
||||
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<File>, klibFile: File) =
|
||||
this@AbstractNativeKlibABITest.buildKlib(moduleName, moduleSourceDir, moduleDependencies, klibFile)
|
||||
|
||||
override fun buildBinaryAndRun(mainModuleKlibFile: File, allDependencies: Collection<File>) =
|
||||
this@AbstractNativeKlibABITest.buildBinaryAndRun(allDependencies)
|
||||
|
||||
override fun onNonEmptyBuildDirectory(directory: File) = backupDirectoryContents(directory)
|
||||
|
||||
override fun onIgnoredTest() = throw TestAbortedException()
|
||||
}
|
||||
|
||||
// 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<File>, klibFile: File) {
|
||||
val module = createModule(moduleName)
|
||||
@@ -66,7 +75,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
|
||||
} else
|
||||
emptyList()
|
||||
|
||||
val (sourceDir, outputDir) = AbstractKlibABITestCase.createModuleDirs(buildDir, LAUNCHER_MODULE_NAME)
|
||||
val (sourceDir, outputDir) = KlibABITestUtils.createModuleDirs(buildDir, LAUNCHER_MODULE_NAME)
|
||||
val executableFile = outputDir.resolve("app." + testRunSettings.get<KotlinNativeTargets>().testTarget.family.exeSuffix)
|
||||
|
||||
val module = createModule(LAUNCHER_MODULE_NAME)
|
||||
|
||||
Reference in New Issue
Block a user