|
|
|
@@ -0,0 +1,204 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright 2010-2017 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.cli.metadata
|
|
|
|
|
|
|
|
|
|
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
|
|
|
|
import org.jetbrains.kotlin.analyzer.common.DefaultAnalyzerFacade
|
|
|
|
|
import org.jetbrains.kotlin.builtins.BuiltInsBinaryVersion
|
|
|
|
|
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
|
|
|
|
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
|
|
|
|
|
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
|
|
|
|
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
|
|
|
|
|
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
|
|
|
|
import org.jetbrains.kotlin.codegen.JvmCodegenUtil
|
|
|
|
|
import org.jetbrains.kotlin.codegen.serializeToByteArray
|
|
|
|
|
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils
|
|
|
|
|
import org.jetbrains.kotlin.load.kotlin.PackageParts
|
|
|
|
|
import org.jetbrains.kotlin.name.ClassId
|
|
|
|
|
import org.jetbrains.kotlin.name.FqName
|
|
|
|
|
import org.jetbrains.kotlin.name.Name
|
|
|
|
|
import org.jetbrains.kotlin.psi.*
|
|
|
|
|
import org.jetbrains.kotlin.resolve.BindingContext
|
|
|
|
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
|
|
|
|
import org.jetbrains.kotlin.serialization.DescriptorSerializer
|
|
|
|
|
import org.jetbrains.kotlin.serialization.KotlinSerializerExtensionBase
|
|
|
|
|
import org.jetbrains.kotlin.serialization.ProtoBuf
|
|
|
|
|
import org.jetbrains.kotlin.serialization.deserialization.MetadataPackageFragment.Companion.DOT_METADATA_FILE_EXTENSION
|
|
|
|
|
import org.jetbrains.kotlin.serialization.jvm.JvmPackageTable
|
|
|
|
|
import java.io.ByteArrayOutputStream
|
|
|
|
|
import java.io.DataOutputStream
|
|
|
|
|
import java.io.File
|
|
|
|
|
|
|
|
|
|
open class MetadataSerializer(private val dependOnOldBuiltIns: Boolean) {
|
|
|
|
|
protected var totalSize = 0
|
|
|
|
|
protected var totalFiles = 0
|
|
|
|
|
|
|
|
|
|
fun serialize(environment: KotlinCoreEnvironment) {
|
|
|
|
|
val configuration = environment.configuration
|
|
|
|
|
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
|
|
|
|
val files = environment.getSourceFiles()
|
|
|
|
|
val moduleName = Name.special("<${configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME)}>")
|
|
|
|
|
|
|
|
|
|
val destDir = configuration.get(CLIConfigurationKeys.METADATA_DESTINATION_DIRECTORY) ?: run {
|
|
|
|
|
messageCollector.report(CompilerMessageSeverity.ERROR, "Specify destination via -d")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val analyzer = AnalyzerWithCompilerReport(messageCollector)
|
|
|
|
|
analyzer.analyzeAndReport(files, object : AnalyzerWithCompilerReport.Analyzer {
|
|
|
|
|
override fun analyze(): AnalysisResult = DefaultAnalyzerFacade.analyzeFiles(files, moduleName, dependOnOldBuiltIns) {
|
|
|
|
|
_, content -> JvmPackagePartProvider(environment, content.moduleContentScope)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (analyzer.hasErrors()) return
|
|
|
|
|
|
|
|
|
|
val (bindingContext, moduleDescriptor) = analyzer.analysisResult
|
|
|
|
|
|
|
|
|
|
performSerialization(files, bindingContext, moduleDescriptor, destDir)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected open fun performSerialization(
|
|
|
|
|
files: Collection<KtFile>, bindingContext: BindingContext, module: ModuleDescriptor, destDir: File
|
|
|
|
|
) {
|
|
|
|
|
val packageTable = hashMapOf<FqName, PackageParts>()
|
|
|
|
|
|
|
|
|
|
for (file in files) {
|
|
|
|
|
val packageFqName = file.packageFqName
|
|
|
|
|
val members = arrayListOf<DeclarationDescriptor>()
|
|
|
|
|
for (declaration in file.declarations) {
|
|
|
|
|
declaration.accept(object : KtVisitorVoid() {
|
|
|
|
|
override fun visitNamedFunction(function: KtNamedFunction) {
|
|
|
|
|
members.add(bindingContext.get(BindingContext.FUNCTION, function)
|
|
|
|
|
?: error("No descriptor found for function ${function.fqName}"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun visitProperty(property: KtProperty) {
|
|
|
|
|
members.add(bindingContext.get(BindingContext.VARIABLE, property)
|
|
|
|
|
?: error("No descriptor found for property ${property.fqName}"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun visitTypeAlias(typeAlias: KtTypeAlias) {
|
|
|
|
|
members.add(bindingContext.get(BindingContext.TYPE_ALIAS, typeAlias)
|
|
|
|
|
?: error("No descriptor found for type alias ${typeAlias.fqName}"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
|
|
|
|
|
val classDescriptor = bindingContext.get(BindingContext.CLASS, classOrObject)
|
|
|
|
|
?: error("No descriptor found for class ${classOrObject.fqName}")
|
|
|
|
|
val destFile = File(destDir, getClassFilePath(ClassId(packageFqName, classDescriptor.name)))
|
|
|
|
|
PackageSerializer(listOf(classDescriptor), emptyList(), packageFqName, destFile).run()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (members.isNotEmpty()) {
|
|
|
|
|
val destFile = File(destDir, getPackageFilePath(packageFqName, file.name))
|
|
|
|
|
PackageSerializer(emptyList(), members, packageFqName, destFile).run()
|
|
|
|
|
|
|
|
|
|
packageTable.getOrPut(packageFqName) {
|
|
|
|
|
PackageParts(packageFqName.asString())
|
|
|
|
|
}.addMetadataPart(destFile.nameWithoutExtension)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val kotlinModuleFile = File(destDir, JvmCodegenUtil.getMappingFileName(JvmCodegenUtil.getModuleName(module)))
|
|
|
|
|
val packageTableBytes = JvmPackageTable.PackageTable.newBuilder().apply {
|
|
|
|
|
for (table in packageTable.values) {
|
|
|
|
|
table.addTo(this)
|
|
|
|
|
}
|
|
|
|
|
}.serializeToByteArray()
|
|
|
|
|
|
|
|
|
|
kotlinModuleFile.parentFile.mkdirs()
|
|
|
|
|
kotlinModuleFile.writeBytes(packageTableBytes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected open fun createSerializerExtension(): KotlinSerializerExtensionBase = MetadataSerializerExtension()
|
|
|
|
|
|
|
|
|
|
private fun getPackageFilePath(packageFqName: FqName, fileName: String): String =
|
|
|
|
|
packageFqName.asString().replace('.', '/') + "/" +
|
|
|
|
|
PackagePartClassUtils.getFilePartShortName(fileName) + DOT_METADATA_FILE_EXTENSION
|
|
|
|
|
|
|
|
|
|
private fun getClassFilePath(classId: ClassId): String =
|
|
|
|
|
classId.asSingleFqName().asString().replace('.', '/') + DOT_METADATA_FILE_EXTENSION
|
|
|
|
|
|
|
|
|
|
protected inner class PackageSerializer(
|
|
|
|
|
private val classes: Collection<DeclarationDescriptor>,
|
|
|
|
|
private val members: Collection<DeclarationDescriptor>,
|
|
|
|
|
private val packageFqName: FqName,
|
|
|
|
|
private val destFile: File
|
|
|
|
|
) {
|
|
|
|
|
private val proto = ProtoBuf.PackageFragment.newBuilder()
|
|
|
|
|
private val extension = createSerializerExtension()
|
|
|
|
|
|
|
|
|
|
fun run() {
|
|
|
|
|
serializeClasses(classes)
|
|
|
|
|
serializeMembers(members)
|
|
|
|
|
serializeStringTable()
|
|
|
|
|
serializeBuiltInsFile()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun serializeClass(classDescriptor: ClassDescriptor) {
|
|
|
|
|
val classProto = DescriptorSerializer.createTopLevel(extension).classProto(classDescriptor).build()
|
|
|
|
|
proto.addClass_(classProto)
|
|
|
|
|
|
|
|
|
|
serializeClasses(classDescriptor.unsubstitutedInnerClassesScope.getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun serializeClasses(classes: Collection<DeclarationDescriptor>) {
|
|
|
|
|
for (descriptor in DescriptorSerializer.sort(classes)) {
|
|
|
|
|
if (descriptor is ClassDescriptor && descriptor.kind != ClassKind.ENUM_ENTRY) {
|
|
|
|
|
serializeClass(descriptor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun serializeMembers(members: Collection<DeclarationDescriptor>) {
|
|
|
|
|
proto.`package` = DescriptorSerializer.createTopLevel(extension).packagePartProto(packageFqName, members).build()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun serializeStringTable() {
|
|
|
|
|
val (strings, qualifiedNames) = extension.stringTable.buildProto()
|
|
|
|
|
proto.strings = strings
|
|
|
|
|
proto.qualifiedNames = qualifiedNames
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun serializeBuiltInsFile() {
|
|
|
|
|
val stream = ByteArrayOutputStream()
|
|
|
|
|
with(DataOutputStream(stream)) {
|
|
|
|
|
val version = BuiltInsBinaryVersion.INSTANCE.toArray()
|
|
|
|
|
writeInt(version.size)
|
|
|
|
|
version.forEach { writeInt(it) }
|
|
|
|
|
}
|
|
|
|
|
proto.build().writeTo(stream)
|
|
|
|
|
write(stream)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun write(stream: ByteArrayOutputStream) {
|
|
|
|
|
totalSize += stream.size()
|
|
|
|
|
totalFiles++
|
|
|
|
|
assert(!destFile.isDirectory) { "Cannot write because output destination is a directory: $destFile" }
|
|
|
|
|
destFile.parentFile.mkdirs()
|
|
|
|
|
destFile.writeBytes(stream.toByteArray())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|