Split serialized package fragments metadata (#2051)
This commit is contained in:
committed by
Nikolay Igotti
parent
7c21913990
commit
c40396bf19
+7
-5
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.backend.konan
|
package org.jetbrains.kotlin.backend.konan
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.findPackage
|
import org.jetbrains.kotlin.backend.konan.descriptors.findPackage
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.findPackageView
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||||
import org.jetbrains.kotlin.backend.konan.irasdescriptors.constructedClass
|
import org.jetbrains.kotlin.backend.konan.irasdescriptors.constructedClass
|
||||||
import org.jetbrains.kotlin.backend.konan.irasdescriptors.getExternalObjCMethodInfo
|
import org.jetbrains.kotlin.backend.konan.irasdescriptors.getExternalObjCMethodInfo
|
||||||
@@ -21,6 +22,7 @@ import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
|
|||||||
import org.jetbrains.kotlin.resolve.constants.BooleanValue
|
import org.jetbrains.kotlin.resolve.constants.BooleanValue
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
|
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
@@ -75,15 +77,15 @@ private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
|
|||||||
assert (this.kind.isReal)
|
assert (this.kind.isReal)
|
||||||
|
|
||||||
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
||||||
val packageFragment = this.findPackage()
|
val packageView = this.findPackageView()
|
||||||
|
|
||||||
val bridgeName = methodAnnotation.getStringValue("bridge")
|
val bridgeName = methodAnnotation.getStringValue("bridge")
|
||||||
|
|
||||||
return objCMethodInfoByBridge(packageFragment, bridgeName)
|
return objCMethodInfoByBridge(packageView, bridgeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun objCMethodInfoByBridge(packageFragment: PackageFragmentDescriptor, bridgeName: String): ObjCMethodInfo {
|
private fun objCMethodInfoByBridge(packageView: PackageViewDescriptor, bridgeName: String): ObjCMethodInfo {
|
||||||
val bridge = packageFragment.getMemberScope()
|
val bridge = packageView.memberScope
|
||||||
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
|
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
|
||||||
.single()
|
.single()
|
||||||
|
|
||||||
@@ -213,7 +215,7 @@ fun FunctionDescriptor.getObjCFactoryInitMethodInfo(): ObjCMethodInfo? {
|
|||||||
val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null
|
val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null
|
||||||
val bridgeName = factoryAnnotation.getStringValue("bridge")
|
val bridgeName = factoryAnnotation.getStringValue("bridge")
|
||||||
|
|
||||||
return objCMethodInfoByBridge(this.findPackage(), bridgeName)
|
return objCMethodInfoByBridge(this.findPackageView(), bridgeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
|
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
|
||||||
|
|||||||
+5
@@ -114,6 +114,11 @@ tailrec internal fun DeclarationDescriptor.findPackage(): PackageFragmentDescrip
|
|||||||
else this.containingDeclaration!!.findPackage()
|
else this.containingDeclaration!!.findPackage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun DeclarationDescriptor.findPackageView(): PackageViewDescriptor {
|
||||||
|
val packageFragment = this.findPackage()
|
||||||
|
return packageFragment.module.getPackage(packageFragment.fqName)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun DeclarationDescriptor.allContainingDeclarations(): List<DeclarationDescriptor> {
|
internal fun DeclarationDescriptor.allContainingDeclarations(): List<DeclarationDescriptor> {
|
||||||
var list = mutableListOf<DeclarationDescriptor>()
|
var list = mutableListOf<DeclarationDescriptor>()
|
||||||
var current = this.containingDeclaration
|
var current = this.containingDeclaration
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ interface KonanLibraryWriter {
|
|||||||
|
|
||||||
class LinkData(
|
class LinkData(
|
||||||
val module: ByteArray,
|
val module: ByteArray,
|
||||||
val fragments: List<ByteArray>,
|
val fragments: List<List<ByteArray>>,
|
||||||
val fragmentNames: List<String>
|
val fragmentNames: List<String>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -13,8 +13,16 @@ internal class MetadataWriterImpl(libraryLayout: KonanLibraryLayout): KonanLibra
|
|||||||
fun addLinkData(linkData: LinkData) {
|
fun addLinkData(linkData: LinkData) {
|
||||||
moduleHeaderFile.writeBytes(linkData.module)
|
moduleHeaderFile.writeBytes(linkData.module)
|
||||||
linkData.fragments.forEachIndexed { index, it ->
|
linkData.fragments.forEachIndexed { index, it ->
|
||||||
val name = linkData.fragmentNames[index]
|
val packageFqName = linkData.fragmentNames[index]
|
||||||
packageFile(name).writeBytes(it)
|
val shortName = packageFqName.substringAfterLast(".")
|
||||||
|
val dir = packageFragmentsDir(packageFqName)
|
||||||
|
dir.deleteRecursively()
|
||||||
|
dir.mkdirs()
|
||||||
|
val numCount = it.size.toString().length
|
||||||
|
fun withLeadingZeros(i: Int) = String.format("%0${numCount}d", i)
|
||||||
|
for ((i, fragment) in it.withIndex()) {
|
||||||
|
packageFragmentFile(packageFqName, "${withLeadingZeros(i)}_$shortName").writeBytes(fragment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -615,7 +615,7 @@ class KonanDescriptorSerializer private constructor(
|
|||||||
fun packagePartProto(packageFqName: FqName, members: Collection<DeclarationDescriptor>): ProtoBuf.Package.Builder {
|
fun packagePartProto(packageFqName: FqName, members: Collection<DeclarationDescriptor>): ProtoBuf.Package.Builder {
|
||||||
val builder = ProtoBuf.Package.newBuilder()
|
val builder = ProtoBuf.Package.newBuilder()
|
||||||
|
|
||||||
for (declaration in sort(members)) {
|
for (declaration in members) {
|
||||||
when (declaration) {
|
when (declaration) {
|
||||||
is PropertyDescriptor -> builder.addProperty(propertyProto(declaration))
|
is PropertyDescriptor -> builder.addProperty(propertyProto(declaration))
|
||||||
is FunctionDescriptor -> builder.addFunction(functionProto(declaration))
|
is FunctionDescriptor -> builder.addFunction(functionProto(declaration))
|
||||||
|
|||||||
+145
-65
@@ -8,9 +8,8 @@ package org.jetbrains.kotlin.backend.konan.serialization
|
|||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.isExpectMember
|
import org.jetbrains.kotlin.backend.konan.descriptors.isExpectMember
|
||||||
import org.jetbrains.kotlin.backend.konan.library.LinkData
|
import org.jetbrains.kotlin.backend.konan.library.LinkData
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||||
import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf
|
import org.jetbrains.kotlin.metadata.konan.KonanProtoBuf
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
@@ -18,6 +17,9 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|||||||
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
|
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
|
||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.CALLABLES
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter.Companion.CLASSIFIERS
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.serialization.KonanDescriptorSerializer
|
import org.jetbrains.kotlin.serialization.KonanDescriptorSerializer
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,39 +33,57 @@ import org.jetbrains.kotlin.serialization.KonanDescriptorSerializer
|
|||||||
* with MemberDeserializer class.
|
* with MemberDeserializer class.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
internal class KonanSerializationUtil(val context: Context, metadataVersion: BinaryVersion) {
|
internal class KonanSerializationUtil(val context: Context, private val metadataVersion: BinaryVersion) {
|
||||||
|
|
||||||
val serializerExtension = KonanSerializerExtension(context, metadataVersion)
|
lateinit var serializerContext: SerializerContext
|
||||||
val topSerializer = KonanDescriptorSerializer.createTopLevel(context, serializerExtension)
|
|
||||||
var classSerializer: KonanDescriptorSerializer = topSerializer
|
|
||||||
|
|
||||||
fun serializeClass(packageName: FqName,
|
data class SerializerContext(
|
||||||
builder: KonanProtoBuf.LinkDataClasses.Builder,
|
val serializerExtension: KonanSerializerExtension,
|
||||||
classDescriptor: ClassDescriptor) {
|
val topSerializer: KonanDescriptorSerializer,
|
||||||
|
var classSerializer: KonanDescriptorSerializer = topSerializer
|
||||||
|
)
|
||||||
|
|
||||||
val previousSerializer = classSerializer
|
private fun createNewContext(): SerializerContext {
|
||||||
|
val extension = KonanSerializerExtension(context, metadataVersion)
|
||||||
// TODO: this is to filter out object{}. Change me.
|
return SerializerContext(
|
||||||
if (classDescriptor.isExported())
|
extension,
|
||||||
classSerializer = KonanDescriptorSerializer.create(context, classDescriptor, serializerExtension)
|
KonanDescriptorSerializer.createTopLevel(context, extension)
|
||||||
|
)
|
||||||
val classProto = classSerializer.classProto(classDescriptor).build()
|
|
||||||
?: error("Class not serialized: $classDescriptor")
|
|
||||||
|
|
||||||
builder.addClasses(classProto)
|
|
||||||
val index = classSerializer.stringTable.getFqNameIndex(classDescriptor)
|
|
||||||
builder.addClassName(index)
|
|
||||||
|
|
||||||
serializeClasses(packageName, builder,
|
|
||||||
classDescriptor.unsubstitutedInnerClassesScope
|
|
||||||
.getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS))
|
|
||||||
|
|
||||||
classSerializer = previousSerializer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun serializeClasses(packageName: FqName,
|
private inline fun <T> withNewContext(crossinline block: SerializerContext.() -> T): T {
|
||||||
builder: KonanProtoBuf.LinkDataClasses.Builder,
|
serializerContext = createNewContext()
|
||||||
descriptors: Collection<DeclarationDescriptor>) {
|
return with(serializerContext, block)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun serializeClass(packageName: FqName,
|
||||||
|
builder: KonanProtoBuf.LinkDataClasses.Builder,
|
||||||
|
classDescriptor: ClassDescriptor) {
|
||||||
|
with(serializerContext) {
|
||||||
|
val previousSerializer = classSerializer
|
||||||
|
|
||||||
|
// TODO: this is to filter out object{}. Change me.
|
||||||
|
if (classDescriptor.isExported())
|
||||||
|
classSerializer = KonanDescriptorSerializer.create(context, classDescriptor, serializerExtension)
|
||||||
|
|
||||||
|
val classProto = classSerializer.classProto(classDescriptor).build()
|
||||||
|
?: error("Class not serialized: $classDescriptor")
|
||||||
|
|
||||||
|
builder.addClasses(classProto)
|
||||||
|
val index = classSerializer.stringTable.getFqNameIndex(classDescriptor)
|
||||||
|
builder.addClassName(index)
|
||||||
|
|
||||||
|
serializeClasses(packageName, builder,
|
||||||
|
classDescriptor.unsubstitutedInnerClassesScope
|
||||||
|
.getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS))
|
||||||
|
|
||||||
|
classSerializer = previousSerializer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun serializeClasses(packageName: FqName,
|
||||||
|
builder: KonanProtoBuf.LinkDataClasses.Builder,
|
||||||
|
descriptors: Collection<DeclarationDescriptor>) {
|
||||||
|
|
||||||
for (descriptor in descriptors) {
|
for (descriptor in descriptors) {
|
||||||
if (descriptor is ClassDescriptor) {
|
if (descriptor is ClassDescriptor) {
|
||||||
@@ -72,51 +92,89 @@ internal class KonanSerializationUtil(val context: Context, metadataVersion: Bin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun serializePackage(fqName: FqName, module: ModuleDescriptor) :
|
private fun serializePackage(fqName: FqName, module: ModuleDescriptor):
|
||||||
KonanProtoBuf.LinkDataPackageFragment? {
|
List<KonanProtoBuf.LinkDataPackageFragment> {
|
||||||
|
|
||||||
// TODO: ModuleDescriptor should be able to return
|
// TODO: ModuleDescriptor should be able to return
|
||||||
// the package only with the contents of that module, without dependencies
|
// the package only with the contents of that module, without dependencies
|
||||||
|
|
||||||
val fragments = module.getPackage(fqName).fragments.filter { it.module == module }
|
val fragments = module.getPackage(fqName).fragments.filter { it.module == module }
|
||||||
if (fragments.isEmpty()) return null
|
if (fragments.isEmpty()) return emptyList()
|
||||||
|
|
||||||
val classifierDescriptors = KonanDescriptorSerializer.sort(
|
val classifierDescriptors = KonanDescriptorSerializer.sort(
|
||||||
fragments.flatMap {
|
fragments.flatMap {
|
||||||
it.getMemberScope().getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS)
|
it.getMemberScope().getDescriptorsFiltered(CLASSIFIERS)
|
||||||
}.filter { !it.isExpectMember || it.isSerializableExpectClass }
|
}.filter { !it.isExpectMember || it.isSerializableExpectClass }
|
||||||
)
|
)
|
||||||
|
|
||||||
val members = fragments.flatMap { fragment ->
|
val topLevelDescriptors = KonanDescriptorSerializer.sort(
|
||||||
DescriptorUtils.getAllDescriptors(fragment.getMemberScope())
|
fragments.flatMap { fragment ->
|
||||||
}.filter { !it.isExpectMember }
|
fragment.getMemberScope().getDescriptorsFiltered(CALLABLES)
|
||||||
|
}.filter { !it.isExpectMember }
|
||||||
|
)
|
||||||
|
|
||||||
val classesBuilder = KonanProtoBuf.LinkDataClasses.newBuilder()
|
val result = mutableListOf<KonanProtoBuf.LinkDataPackageFragment>()
|
||||||
|
|
||||||
serializeClasses(fqName, classesBuilder, classifierDescriptors)
|
result += classifierDescriptors.chunked(TOP_LEVEL_CLASS_DECLARATION_COUNT_PER_FILE) { descriptors ->
|
||||||
val classesProto = classesBuilder.build()
|
|
||||||
|
|
||||||
val packageProto = topSerializer.packagePartProto(fqName, members).build()
|
withNewContext {
|
||||||
?: error("Package fragments not serialized: $fragments")
|
|
||||||
|
|
||||||
val strings = serializerExtension.stringTable
|
val classesProto = buildClassesProto { classesBuilder ->
|
||||||
val (stringTableProto, nameTableProto) = strings.buildProto()
|
serializeClasses(fqName, classesBuilder, descriptors)
|
||||||
|
}
|
||||||
|
|
||||||
val isEmpty = members.isEmpty() && classifierDescriptors.isEmpty()
|
val typeAliases = descriptors.filterIsInstance<TypeAliasDescriptor>()
|
||||||
val fragmentBuilder = KonanProtoBuf.LinkDataPackageFragment.newBuilder()
|
val packageProto =
|
||||||
|
if (typeAliases.isNotEmpty()) buildPackageProto(fqName, typeAliases, fragments)
|
||||||
|
else emptyPackageProto()
|
||||||
|
|
||||||
val fragmentProto = fragmentBuilder
|
buildFragment(
|
||||||
.setPackage(packageProto)
|
packageProto,
|
||||||
.setFqName(fqName.asString())
|
classesProto,
|
||||||
.setClasses(classesProto)
|
fqName,
|
||||||
.setStringTable(stringTableProto)
|
descriptors.isEmpty()
|
||||||
.setNameTable(nameTableProto)
|
)
|
||||||
.setIsEmpty(isEmpty)
|
}
|
||||||
.build()
|
}
|
||||||
|
|
||||||
return fragmentProto
|
result += topLevelDescriptors.chunked(TOP_LEVEL_DECLARATION_COUNT_PER_FILE) { descriptors ->
|
||||||
|
withNewContext {
|
||||||
|
buildFragment(
|
||||||
|
buildPackageProto(fqName, descriptors, fragments),
|
||||||
|
buildClassesProto {},
|
||||||
|
fqName,
|
||||||
|
descriptors.isEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
result += withNewContext {
|
||||||
|
buildFragment(
|
||||||
|
emptyPackageProto(),
|
||||||
|
buildClassesProto {},
|
||||||
|
fqName,
|
||||||
|
true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun buildClassesProto(action: (KonanProtoBuf.LinkDataClasses.Builder) -> Unit): KonanProtoBuf.LinkDataClasses {
|
||||||
|
val classesBuilder = KonanProtoBuf.LinkDataClasses.newBuilder()
|
||||||
|
action(classesBuilder)
|
||||||
|
return classesBuilder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emptyPackageProto(): ProtoBuf.Package = ProtoBuf.Package.newBuilder().build()
|
||||||
|
|
||||||
|
private fun SerializerContext.buildPackageProto(
|
||||||
|
fqName: FqName,
|
||||||
|
descriptors: List<DeclarationDescriptor>,
|
||||||
|
fragments: List<PackageFragmentDescriptor>) = topSerializer.packagePartProto(fqName, descriptors).build()
|
||||||
|
?: error("Package fragments not serialized: $fragments")
|
||||||
|
|
||||||
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
|
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
|
||||||
val result = mutableSetOf<FqName>()
|
val result = mutableSetOf<FqName>()
|
||||||
|
|
||||||
@@ -129,25 +187,47 @@ internal class KonanSerializationUtil(val context: Context, metadataVersion: Bin
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SerializerContext.buildFragment(
|
||||||
|
packageProto: ProtoBuf.Package,
|
||||||
|
classesProto: KonanProtoBuf.LinkDataClasses,
|
||||||
|
fqName: FqName,
|
||||||
|
isEmpty: Boolean
|
||||||
|
): KonanProtoBuf.LinkDataPackageFragment {
|
||||||
|
|
||||||
|
val (stringTableProto, nameTableProto) = serializerExtension.stringTable.buildProto()
|
||||||
|
|
||||||
|
return KonanProtoBuf.LinkDataPackageFragment.newBuilder()
|
||||||
|
.setPackage(packageProto)
|
||||||
|
.setClasses(classesProto)
|
||||||
|
.setIsEmpty(isEmpty)
|
||||||
|
.setFqName(fqName.asString())
|
||||||
|
.setStringTable(stringTableProto)
|
||||||
|
.setNameTable(nameTableProto)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun serializeModule(moduleDescriptor: ModuleDescriptor): LinkData {
|
internal fun serializeModule(moduleDescriptor: ModuleDescriptor): LinkData {
|
||||||
val libraryProto = KonanProtoBuf.LinkDataLibrary.newBuilder()
|
val libraryProto = KonanProtoBuf.LinkDataLibrary.newBuilder()
|
||||||
libraryProto.moduleName = moduleDescriptor.name.asString()
|
libraryProto.moduleName = moduleDescriptor.name.asString()
|
||||||
val fragments = mutableListOf<ByteArray>()
|
val fragments = mutableListOf<List<ByteArray>>()
|
||||||
val fragmentNames = mutableListOf<String>()
|
val fragmentNames = mutableListOf<String>()
|
||||||
|
|
||||||
getPackagesFqNames(moduleDescriptor).forEach iteration@ {
|
getPackagesFqNames(moduleDescriptor).forEach iteration@{ packageFqName ->
|
||||||
val packageProto = serializePackage(it, moduleDescriptor)
|
val packageProtos = serializePackage(packageFqName, moduleDescriptor)
|
||||||
if (packageProto == null) return@iteration
|
if (packageProtos.isEmpty()) return@iteration
|
||||||
|
|
||||||
libraryProto.addPackageFragmentName(it.asString())
|
val packageFqNameStr = packageFqName.asString()
|
||||||
if (packageProto.isEmpty) {
|
libraryProto.addPackageFragmentName(packageFqNameStr)
|
||||||
libraryProto.addEmptyPackage(it.asString())
|
if (packageProtos.all { it.isEmpty }) {
|
||||||
|
libraryProto.addEmptyPackage(packageFqNameStr)
|
||||||
}
|
}
|
||||||
fragments.add(packageProto.toByteArray())
|
fragments.add(packageProtos.map { it.toByteArray() })
|
||||||
fragmentNames.add(it.asString())
|
fragmentNames.add(packageFqNameStr)
|
||||||
}
|
}
|
||||||
val libraryAsByteArray = libraryProto.build().toByteArray()
|
val libraryAsByteArray = libraryProto.build().toByteArray()
|
||||||
return LinkData(libraryAsByteArray, fragments, fragmentNames)
|
return LinkData(libraryAsByteArray, fragments, fragmentNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val TOP_LEVEL_DECLARATION_COUNT_PER_FILE = 128
|
||||||
|
private const val TOP_LEVEL_CLASS_DECLARATION_COUNT_PER_FILE = 64
|
||||||
|
|||||||
+3
-2
@@ -18,8 +18,9 @@ class KonanClassDataFinder(
|
|||||||
val nameList = proto.classNameList
|
val nameList = proto.classNameList
|
||||||
|
|
||||||
val index = nameList.indexOfFirst { nameResolver.getClassId(it) == classId }
|
val index = nameList.indexOfFirst { nameResolver.getClassId(it) == classId }
|
||||||
if (index == -1)
|
if (index == -1) {
|
||||||
error("Could not find serialized class $classId")
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val foundClass = proto.getClasses(index) ?: error("Could not find data for serialized class $classId")
|
val foundClass = proto.getClasses(index) ?: error("Could not find data for serialized class $classId")
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -19,7 +19,8 @@ class KonanPackageFragment(
|
|||||||
private val library: KonanLibrary,
|
private val library: KonanLibrary,
|
||||||
private val packageAccessedHandler: PackageAccessedHandler?,
|
private val packageAccessedHandler: PackageAccessedHandler?,
|
||||||
storageManager: StorageManager,
|
storageManager: StorageManager,
|
||||||
module: ModuleDescriptor
|
module: ModuleDescriptor,
|
||||||
|
partName: String
|
||||||
) : DeserializedPackageFragment(fqName, storageManager, module) {
|
) : DeserializedPackageFragment(fqName, storageManager, module) {
|
||||||
|
|
||||||
lateinit var components: DeserializationComponents
|
lateinit var components: DeserializationComponents
|
||||||
@@ -31,7 +32,7 @@ class KonanPackageFragment(
|
|||||||
// The proto field is lazy so that we can load only needed
|
// The proto field is lazy so that we can load only needed
|
||||||
// packages from the library.
|
// packages from the library.
|
||||||
private val protoForNames: KonanProtoBuf.LinkDataPackageFragment by lazy {
|
private val protoForNames: KonanProtoBuf.LinkDataPackageFragment by lazy {
|
||||||
parsePackageFragment(library.packageMetadata(fqName.asString()))
|
parsePackageFragment(library.packageMetadata(fqName.asString(), partName))
|
||||||
}
|
}
|
||||||
|
|
||||||
val proto: KonanProtoBuf.LinkDataPackageFragment
|
val proto: KonanProtoBuf.LinkDataPackageFragment
|
||||||
|
|||||||
+6
-2
@@ -29,8 +29,12 @@ internal object KonanDeserializedPackageFragmentsFactoryImpl: KonanDeserializedP
|
|||||||
moduleDescriptor: ModuleDescriptor,
|
moduleDescriptor: ModuleDescriptor,
|
||||||
packageAccessedHandler: PackageAccessedHandler?,
|
packageAccessedHandler: PackageAccessedHandler?,
|
||||||
storageManager: StorageManager
|
storageManager: StorageManager
|
||||||
) = packageFragmentNames.map {
|
) = packageFragmentNames.flatMap {
|
||||||
KonanPackageFragment(FqName(it), library, packageAccessedHandler, storageManager, moduleDescriptor)
|
val fqName = FqName(it)
|
||||||
|
val parts = library.packageMetadataParts(fqName.asString())
|
||||||
|
parts.map { partName ->
|
||||||
|
KonanPackageFragment(fqName, library, packageAccessedHandler, storageManager, moduleDescriptor, partName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createSyntheticPackageFragments(
|
override fun createSyntheticPackageFragments(
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ class KlibPrinter(out: Appendable) {
|
|||||||
get() = kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|
get() = kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE
|
||||||
|
|
||||||
val DeclarationDescriptor.shouldBePrinted: Boolean
|
val DeclarationDescriptor.shouldBePrinted: Boolean
|
||||||
get() = this is ClassDescriptor && isPublicOrProtected || this is CallableMemberDescriptor && isPublicOrProtected && !isFakeOverride
|
get() = this is ClassifierDescriptorWithTypeParameters && isPublicOrProtected
|
||||||
|
|| this is CallableMemberDescriptor && isPublicOrProtected && !isFakeOverride
|
||||||
|
|
||||||
private fun Printer.printBody(header: CharSequence, block: () -> Unit) {
|
private fun Printer.printBody(header: CharSequence, block: () -> Unit) {
|
||||||
println()
|
println()
|
||||||
@@ -35,7 +36,7 @@ class KlibPrinter(out: Appendable) {
|
|||||||
println()
|
println()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ClassDescriptor.render(): String {
|
private fun ClassifierDescriptorWithTypeParameters.render(): String {
|
||||||
val renderer = when (modality) {
|
val renderer = when (modality) {
|
||||||
// Don't render 'final' modality
|
// Don't render 'final' modality
|
||||||
Modality.FINAL -> Renderers.WITHOUT_MODALITY
|
Modality.FINAL -> Renderers.WITHOUT_MODALITY
|
||||||
@@ -131,6 +132,10 @@ class KlibPrinter(out: Appendable) {
|
|||||||
override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, data: Unit) {
|
override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, data: Unit) {
|
||||||
printer.println(descriptor.render())
|
printer.println(descriptor.render())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visitTypeAliasDescriptor(descriptor: TypeAliasDescriptor, data: Unit) {
|
||||||
|
printer.println(descriptor.render())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Renderers {
|
object Renderers {
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ class ContentsTest {
|
|||||||
@Test
|
@Test
|
||||||
fun topLevelFunctions() = klibContents(testLibrary("TopLevelFunctions")) {
|
fun topLevelFunctions() = klibContents(testLibrary("TopLevelFunctions")) {
|
||||||
"""
|
"""
|
||||||
|
package <root> {
|
||||||
|
annotation class A constructor() : Annotation
|
||||||
|
annotation class B constructor() : Annotation
|
||||||
|
class Foo constructor()
|
||||||
|
}
|
||||||
|
|
||||||
package <root> {
|
package <root> {
|
||||||
@A @B fun a()
|
@A @B fun a()
|
||||||
fun f1(x: Foo)
|
fun f1(x: Foo)
|
||||||
@@ -56,9 +62,6 @@ class ContentsTest {
|
|||||||
inline fun <reified T> t4(x: T)
|
inline fun <reified T> t4(x: T)
|
||||||
fun <T : Number> t5(x: T)
|
fun <T : Number> t5(x: T)
|
||||||
fun Foo.e()
|
fun Foo.e()
|
||||||
annotation class A constructor() : Annotation
|
|
||||||
annotation class B constructor() : Annotation
|
|
||||||
class Foo constructor()
|
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
@@ -285,6 +288,74 @@ class ContentsTest {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun topLevelPropertiesCustomPackage() = klibContents(testLibrary("TopLevelPropertiesCustomPackage")) {
|
||||||
|
"""
|
||||||
|
package custom.pkg {
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
}
|
||||||
|
|
||||||
|
package custom.pkg {
|
||||||
|
val v1: Int = 1
|
||||||
|
val v2: String = "hello"
|
||||||
|
val v3: (String) -> Int
|
||||||
|
val v4: MyTransformer /* = (String) -> Int */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun topLevelPropertiesRootPackage() = klibContents(testLibrary("TopLevelPropertiesRootPackage")) {
|
||||||
|
"""
|
||||||
|
package <root> {
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
}
|
||||||
|
|
||||||
|
package <root> {
|
||||||
|
val v1: Int = 1
|
||||||
|
val v2: String = "hello"
|
||||||
|
val v3: (String) -> Int
|
||||||
|
val v4: MyTransformer /* = (String) -> Int */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun topLevelPropertiesWithClassesCustomPackage() = klibContents(testLibrary("TopLevelPropertiesWithClassesCustomPackage")) {
|
||||||
|
"""
|
||||||
|
package custom.pkg {
|
||||||
|
object Bar
|
||||||
|
class Foo constructor()
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
}
|
||||||
|
|
||||||
|
package custom.pkg {
|
||||||
|
val v1: Int = 1
|
||||||
|
val v2: String = "hello"
|
||||||
|
val v3: (String) -> Int
|
||||||
|
val v4: MyTransformer /* = (String) -> Int */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun topLevelPropertiesWithClassesRootPackage() = klibContents(testLibrary("TopLevelPropertiesWithClassesRootPackage")) {
|
||||||
|
"""
|
||||||
|
package <root> {
|
||||||
|
object Bar
|
||||||
|
class Foo constructor()
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
}
|
||||||
|
|
||||||
|
package <root> {
|
||||||
|
val v1: Int = 1
|
||||||
|
val v2: String = "hello"
|
||||||
|
val v3: (String) -> Int
|
||||||
|
val v4: MyTransformer /* = (String) -> Int */
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val LIBRARY_DIRECTORY = Paths.get("build/konan/libs").resolve( HostManager.hostName)
|
val LIBRARY_DIRECTORY = Paths.get("build/konan/libs").resolve( HostManager.hostName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
package custom.pkg
|
||||||
|
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
|
||||||
|
// top-level properties
|
||||||
|
val v1 = 1
|
||||||
|
val v2 = "hello"
|
||||||
|
val v3: (String) -> Int = { it.length }
|
||||||
|
val v4: MyTransformer = v3
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
|
||||||
|
// top-level properties
|
||||||
|
val v1 = 1
|
||||||
|
val v2 = "hello"
|
||||||
|
val v3: (String) -> Int = { it.length }
|
||||||
|
val v4: MyTransformer = v3
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
package custom.pkg
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
|
||||||
|
// top-level properties
|
||||||
|
val v1 = 1
|
||||||
|
val v2 = "hello"
|
||||||
|
val v3: (String) -> Int = { it.length }
|
||||||
|
val v4: MyTransformer = v3
|
||||||
|
|
||||||
|
object Bar
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UNUSED_PARAMETER")
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
|
||||||
|
typealias MyTransformer = (String) -> Int
|
||||||
|
|
||||||
|
// top-level properties
|
||||||
|
val v1 = 1
|
||||||
|
val v2 = "hello"
|
||||||
|
val v3: (String) -> Int = { it.length }
|
||||||
|
val v4: MyTransformer = v3
|
||||||
|
|
||||||
|
object Bar
|
||||||
@@ -40,7 +40,8 @@ interface KonanLibrary {
|
|||||||
|
|
||||||
val dataFlowGraph: ByteArray?
|
val dataFlowGraph: ByteArray?
|
||||||
val moduleHeaderData: ByteArray
|
val moduleHeaderData: ByteArray
|
||||||
fun packageMetadata(fqName: String): ByteArray
|
fun packageMetadataParts(fqName: String): Set<String>
|
||||||
|
fun packageMetadata(fqName: String, partName: String): ByteArray
|
||||||
}
|
}
|
||||||
|
|
||||||
val KonanLibrary.uniqueName
|
val KonanLibrary.uniqueName
|
||||||
@@ -63,7 +64,6 @@ val KonanLibrary.exportForwardDeclarations
|
|||||||
.split(' ').asSequence()
|
.split(' ').asSequence()
|
||||||
.map { it.trim() }
|
.map { it.trim() }
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.map { it }
|
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
val KonanLibrary.includedHeaders
|
val KonanLibrary.includedHeaders
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ interface KonanLibraryLayout {
|
|||||||
val dataFlowGraphFile
|
val dataFlowGraphFile
|
||||||
get() = File(linkdataDir, "module_data_flow_graph")
|
get() = File(linkdataDir, "module_data_flow_graph")
|
||||||
|
|
||||||
fun packageFile(packageName: String)
|
fun packageFragmentsDir(packageName: String)
|
||||||
= File(linkdataDir, if (packageName == "") "root_package.knm" else "package_$packageName.knm")
|
= File(linkdataDir, if (packageName == "") "root_package" else "package_$packageName")
|
||||||
|
|
||||||
|
fun packageFragmentFile(packageFqName: String, partName: String) =
|
||||||
|
File(packageFragmentsDir(packageFqName), "$partName$KLIB_METADATA_FILE_EXTENSION_WITH_DOT")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ import org.jetbrains.kotlin.konan.library.impl.KonanLibraryImpl
|
|||||||
import org.jetbrains.kotlin.konan.library.impl.zippedKonanLibraryChecks
|
import org.jetbrains.kotlin.konan.library.impl.zippedKonanLibraryChecks
|
||||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||||
|
|
||||||
val KLIB_FILE_EXTENSION = "klib"
|
const val KLIB_FILE_EXTENSION = "klib"
|
||||||
val KLIB_FILE_EXTENSION_WITH_DOT = ".$KLIB_FILE_EXTENSION"
|
const val KLIB_FILE_EXTENSION_WITH_DOT = ".$KLIB_FILE_EXTENSION"
|
||||||
|
|
||||||
|
const val KLIB_METADATA_FILE_EXTENSION = "knm"
|
||||||
|
const val KLIB_METADATA_FILE_EXTENSION_WITH_DOT = ".$KLIB_METADATA_FILE_EXTENSION"
|
||||||
|
|
||||||
fun File.unpackZippedKonanLibraryTo(newDir: File) {
|
fun File.unpackZippedKonanLibraryTo(newDir: File) {
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ package org.jetbrains.kotlin.konan.library
|
|||||||
|
|
||||||
interface MetadataReader {
|
interface MetadataReader {
|
||||||
fun loadSerializedModule(libraryLayout: KonanLibraryLayout): ByteArray
|
fun loadSerializedModule(libraryLayout: KonanLibraryLayout): ByteArray
|
||||||
fun loadSerializedPackageFragment(libraryLayout: KonanLibraryLayout, fqName: String): ByteArray
|
fun loadSerializedPackageFragment(libraryLayout: KonanLibraryLayout, fqName: String, partName: String): ByteArray
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,6 +8,6 @@ internal object DefaultMetadataReaderImpl : MetadataReader {
|
|||||||
override fun loadSerializedModule(libraryLayout: KonanLibraryLayout): ByteArray =
|
override fun loadSerializedModule(libraryLayout: KonanLibraryLayout): ByteArray =
|
||||||
libraryLayout.moduleHeaderFile.readBytes()
|
libraryLayout.moduleHeaderFile.readBytes()
|
||||||
|
|
||||||
override fun loadSerializedPackageFragment(libraryLayout: KonanLibraryLayout, fqName: String): ByteArray =
|
override fun loadSerializedPackageFragment(libraryLayout: KonanLibraryLayout, fqName: String, partName: String): ByteArray =
|
||||||
libraryLayout.packageFile(fqName).readBytes()
|
libraryLayout.packageFragmentFile(fqName, partName).readBytes()
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-1
@@ -50,7 +50,24 @@ class KonanLibraryImpl(
|
|||||||
|
|
||||||
override val moduleHeaderData: ByteArray by lazy { layout.inPlace { metadataReader.loadSerializedModule(it) } }
|
override val moduleHeaderData: ByteArray by lazy { layout.inPlace { metadataReader.loadSerializedModule(it) } }
|
||||||
|
|
||||||
override fun packageMetadata(fqName: String) = layout.inPlace { metadataReader.loadSerializedPackageFragment(it, fqName) }
|
override fun packageMetadata(packageFqName: String, partName: String) =
|
||||||
|
layout.inPlace { metadataReader.loadSerializedPackageFragment(it, packageFqName, partName) }
|
||||||
|
|
||||||
|
override fun packageMetadataParts(fqName: String): Set<String> =
|
||||||
|
layout.inPlace { inPlaceLayout ->
|
||||||
|
val fileList =
|
||||||
|
inPlaceLayout.packageFragmentsDir(fqName)
|
||||||
|
.listFiles
|
||||||
|
.mapNotNull {
|
||||||
|
it.name
|
||||||
|
.substringBeforeLast(KLIB_METADATA_FILE_EXTENSION_WITH_DOT, missingDelimiterValue = "")
|
||||||
|
.takeIf { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fileList.toSortedSet().also {
|
||||||
|
require(it.size == fileList.size) { "Duplicated names: ${fileList.groupingBy { it }.eachCount().filter { (_, count) -> count > 1 }}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "$libraryName[default=$isDefault]"
|
override fun toString() = "$libraryName[default=$isDefault]"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user