Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package com.tencent.tmm.knoi.sample

import com.tencent.tmm.knoi.annotation.KNExport
import com.tencent.tmm.knoi.annotation.KNExportRetPromise
import com.tencent.tmm.knoi.logger.info
import com.tencent.tmm.knoi.type.ArrayBuffer
import com.tencent.tmm.knoi.type.JSValue
Expand All @@ -11,6 +12,12 @@ import kotlinx.cinterop.allocArray
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.reinterpret
import kotlinx.cinterop.set
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import platform.ohos.LOG_APP
import platform.ohos.LOG_INFO
import platform.ohos.OH_LOG_Print
Expand Down Expand Up @@ -63,6 +70,36 @@ fun testDoubleReturnDouble(result: Double): Double {
return result + 1
}

@KNExportRetPromise
fun testAsyncIntReturnInt(number: Int): Int {
return number + 100
}

@KNExportRetPromise
fun testAsyncIntReturnIntOnIO(number: Int): Int = runBlocking {
val ioScope = CoroutineScope(Dispatchers.IO)
ioScope.async {
info("testAsyncIntReturnIntOnIO run on IO dispatcher")
delay(100)
number + 300
}.await()
}

@KNExportRetPromise
fun testAsyncVoidReturnVoid() {
info("testAsyncVoidReturnVoid")
}

@KNExportRetPromise
fun testAsyncJSCallbackReturnString(function: (args: Array<out Any?>) -> Any): String {
val result = function.invoke(arrayOf("async callback from KMM"))
return if (result is JSValue) {
result.toKString().orEmpty()
} else {
result.toString()
}
}

@KNExport
fun testJSValueReturnJSValue(value: JSValue): JSValue {
info("testJSValueReturnJSValue")
Expand Down Expand Up @@ -182,4 +219,4 @@ fun testCustomClassWrap(): JSValue {
return@createJSFunction nativeRect?.l!! + param1
}
return rectJSValue
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const val VERSION = "0.0.4"
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
annotation class KNExport(val name: String = "")

@Target(AnnotationTarget.FUNCTION)
annotation class KNExportRetPromise(val name: String = "")

@Target(AnnotationTarget.CLASS)
annotation class ServiceConsumer(val name: String = "")

Expand All @@ -24,4 +27,4 @@ annotation class Hidden
annotation class KNCallback

@Target(AnnotationTarget.FUNCTION)
annotation class ModuleInitializer()
annotation class ModuleInitializer()
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.tencent.tmm.knoi.callback.processCallbackList
import com.tencent.tmm.knoi.declare.processDeclareList
import com.tencent.tmm.knoi.function.processAsyncExportFunction
import com.tencent.tmm.knoi.function.processExportFunction
import com.tencent.tmm.knoi.module.genModuleInitializer
import com.tencent.tmm.knoi.module.processAutoRegister
Expand Down Expand Up @@ -57,6 +58,7 @@ class KnoiProcessor(val environment: SymbolProcessorEnvironment) :
}
fillOptionsFromConfigFile(options)
val functions = processExportFunction(codeGenerator, resolver, options)
val asyncFunctions = processAsyncExportFunction(codeGenerator, resolver, options)
val consumers = processServiceConsumer(codeGenerator, resolver, options)
val providers = processServiceProvider(codeGenerator, resolver, options)
val declares = processDeclareList(codeGenerator, resolver, options)
Expand All @@ -65,7 +67,7 @@ class KnoiProcessor(val environment: SymbolProcessorEnvironment) :


genModuleInitializer(
codeGenerator, resolver, consumers, providers, functions, declares, options
codeGenerator, resolver, consumers, providers, functions, asyncFunctions, declares, options
)
// 无生成的文件视为最后一轮 process,生成自动注册方法
if (codeGenerator.generatedFile.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.tencent.tmm.knoi.function

data class AsyncExportFunction(
val registerName: String,
val function: FunctionInfo
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.tencent.tmm.knoi.function

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.validate
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.UNIT
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import com.tencent.tmm.knoi.annotation.KNExportRetPromise
import com.tencent.tmm.knoi.service.parseFunctionInfo
import com.tencent.tmm.knoi.utils.OPTION_MODULE_NAME
import com.tencent.tmm.knoi.utils.arrayWithAny
import com.tencent.tmm.knoi.utils.capitalizeName
import com.tencent.tmm.knoi.utils.checkAsyncFunctionSupportType
import com.tencent.tmm.knoi.utils.getAnnotationValueByKey
import com.tencent.tmm.knoi.utils.isOhosArm64

fun processAsyncExportFunction(
codeGenerator: CodeGenerator,
resolver: Resolver,
options: Map<String, String>
): List<AsyncExportFunction> {
if (!isOhosArm64(options)) {
return emptyList()
}
val exportFunctionList = parseAsyncExportFunctionList(resolver, KNExportRetPromise::class.qualifiedName!!)
if (exportFunctionList.isEmpty()) {
return exportFunctionList
}
val errorList = checkAsyncFunctionSupportType(exportFunctionList)
assert(errorList.isEmpty()) {
errorList.toTypedArray().joinToString("\n")
}
val moduleName = options[OPTION_MODULE_NAME]!!
genAsyncExportFunctionList(codeGenerator, exportFunctionList, moduleName)
return exportFunctionList
}

private fun parseAsyncExportFunctionList(
resolver: Resolver,
annotationName: String
): List<AsyncExportFunction> {
val symbols = resolver.getSymbolsWithAnnotation(annotationName)
val ksAnnotatedList: List<KSAnnotated> =
symbols.filter { it is KSFunctionDeclaration && it.validate() }.toList()

val exportFunctionList = mutableListOf<AsyncExportFunction>()
ksAnnotatedList.forEach {
if (it !is KSFunctionDeclaration) {
return@forEach
}
val functionInfo = parseFunctionInfo(it)
val registerName = getAnnotationValueByKey(it, annotationName, "name", functionInfo.functionName)
exportFunctionList.add(AsyncExportFunction(registerName, functionInfo))
}
return exportFunctionList
}

fun getAsyncFunctionBindName(name: String): String {
return "bindAsync" + capitalizeName(name)
}

private fun formatAsyncFunctionWithAnyName(name: String): String {
return "${name}WithAnyAsync"
}

fun genAsyncExportFunctionList(
codeGenerator: CodeGenerator,
exportFunctionList: List<AsyncExportFunction>,
moduleName: String
) {
val packageNameToFileSpec = mutableMapOf<String, FileSpec.Builder>()
val registerFileSpecBuilder =
FileSpec.builder("com.tencent.tmm.knoi.modules.${moduleName}", "AsyncExportFunctionRegister")

registerFileSpecBuilder.addImport("com.tencent.tmm.knoi.definder", "bindAsync")
exportFunctionList.forEach {
registerFileSpecBuilder.addImport(
it.function.packageName,
formatAsyncFunctionWithAnyName(it.function.functionName)
)
if (packageNameToFileSpec[it.function.packageName] == null) {
packageNameToFileSpec[it.function.packageName] =
FileSpec.builder(it.function.packageName, "AsyncExportFunction")
}
val expandFileSpecBuilder = packageNameToFileSpec[it.function.packageName] ?: return@forEach
expandFileSpecBuilder.addFunction(genAsyncExportFunctionWithArrayAny(it))
registerFileSpecBuilder.addFunction(genAsyncBindFunction(it))
}

packageNameToFileSpec.forEach { (_, builder) ->
builder.build().writeTo(codeGenerator = codeGenerator, aggregating = true)
}
registerFileSpecBuilder.build().writeTo(codeGenerator = codeGenerator, aggregating = true)
}

fun genAsyncBindFunction(exportFunction: AsyncExportFunction): FunSpec {
val func = FunSpec.builder(getAsyncFunctionBindName(exportFunction.registerName))
var paramTypeListStr = ""
exportFunction.function.parameters.forEach {
paramTypeListStr += ","
paramTypeListStr += " ${formatSupportTypeClassString(it.type)}"
}
func.addCode(
"""
|bindAsync("${exportFunction.registerName}", ::${formatAsyncFunctionWithAnyName(exportFunction.function.functionName)},
| ${formatSupportTypeClassString(exportFunction.function.returnType)}${paramTypeListStr})
|""".trimMargin()
)
return func.build()
}

fun genAsyncExportFunctionWithArrayAny(exportFunction: AsyncExportFunction): FunSpec {
val func = FunSpec.builder(formatAsyncFunctionWithAnyName(exportFunction.function.functionName))
var paramStr = ""
exportFunction.function.parameters.forEachIndexed { index, param ->
paramStr += "args[${index}] as %T"
if (index != exportFunction.function.parameters.size - 1) {
paramStr += ", "
}
}
val paramTypeArray = exportFunction.function.parameters.map {
it.type.toTypeName()
}.toTypedArray()
func.addParameter("args", arrayWithAny).addCode(
"""
|return ${exportFunction.function.functionName}(${paramStr})
|""".trimMargin(),
*paramTypeArray
)
if (exportFunction.function.returnType != null) {
func.returns(exportFunction.function.returnType.toTypeName())
} else {
func.returns(UNIT)
}
return func.build()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.ksp.writeTo
import com.tencent.tmm.knoi.declare.Declare
import com.tencent.tmm.knoi.declare.getDeclareBindName
import com.tencent.tmm.knoi.function.AsyncExportFunction
import com.tencent.tmm.knoi.function.getAsyncFunctionBindName
import com.tencent.tmm.knoi.function.ExportFunction
import com.tencent.tmm.knoi.function.getFunctionBindName
import com.tencent.tmm.knoi.utils.isOhosArm64
Expand All @@ -33,10 +35,11 @@ fun genModuleInitializer(
consumers: List<ServiceInfo>,
providers: List<ServiceInfo>,
functions: List<ExportFunction>,
asyncFunctions: List<AsyncExportFunction>,
declares: List<Declare>,
options: Map<String, String>
): Boolean {
if (consumers.isEmpty() && providers.isEmpty() && functions.isEmpty() && declares.isEmpty()) {
if (consumers.isEmpty() && providers.isEmpty() && functions.isEmpty() && asyncFunctions.isEmpty() && declares.isEmpty()) {
return false
}
val moduleName = options[OPTION_MODULE_NAME]!!
Expand Down Expand Up @@ -67,6 +70,11 @@ fun genModuleInitializer(
"|${KNOI_MODULE_PACKAGE_NAME}.${moduleName}.${getFunctionBindName(it.registerName)}()"
}.joinToString("\n")
}
${
asyncFunctions.filter { it.registerName.isNotBlank() }.map {
"|${KNOI_MODULE_PACKAGE_NAME}.${moduleName}.${getAsyncFunctionBindName(it.registerName)}()"
}.joinToString("\n")
}
${
declares.filter { it.getDeclareName().isNotBlank() }.map {
"|${KNOI_MODULE_PACKAGE_NAME}.${moduleName}.${getDeclareBindName(it.getDeclareName())}()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.squareup.kotlinpoet.WildcardTypeName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.ksp.toClassName
import com.tencent.tmm.knoi.function.FunctionInfo
import com.tencent.tmm.knoi.function.AsyncExportFunction
import java.util.Locale

const val OPTION_CONFIG_FILE = "CONFIG_FILE"
Expand Down Expand Up @@ -87,6 +88,41 @@ fun checkFunctionSupportType(functionList: List<FunctionInfo>): MutableList<Stri
return result
}

private val asyncBlockedTypes = setOf(
JSVALUE_CLASS_NAME,
"com.tencent.tmm.knoi.type.ArrayBuffer"
)

private fun containsAsyncBlockedType(type: KSType?): Boolean {
if (type == null) {
return false
}
val className = type.toClassName().canonicalName
if (asyncBlockedTypes.contains(className)) {
return true
}
return type.arguments.any { containsAsyncBlockedType(it.type?.resolve()) }
}

fun checkAsyncFunctionSupportType(functionList: List<AsyncExportFunction>): MutableList<String> {
val result = mutableListOf<String>()
functionList.forEach { function ->
if (containsAsyncBlockedType(function.function.returnType)) {
result.add(
"${function.function.packageName}#${function.function.functionName}\n Async export does not support return type containing JSValue or ArrayBuffer in V1."
)
}
function.function.parameters.forEach { param ->
if (containsAsyncBlockedType(param.type)) {
result.add(
"${function.function.packageName}#${function.function.functionName}\n Async export does not support param type ${param.type} because V1 excludes JSValue and ArrayBuffer."
)
}
}
}
return result
}

fun <T> getAnnotationValueByKey(
annotation: KSAnnotated, annotationName: String, key: String, fallback: T
): T {
Expand All @@ -111,4 +147,4 @@ fun getRealKSType(ksTypeRef: KSTypeReference): KSType {
} else {
ksTypeRef.resolve()
}
}
}
Binary file modified knoi/knoi/libs/libnapi_bridge.a
Binary file not shown.
3 changes: 2 additions & 1 deletion knoi/knoi/src/nativeInterop/cinterop/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ include_directories(${NAPI_BRIDGE_ROOT_PATH}/include)

add_library(${TARGET_NAME} STATIC
src/cb_info.cpp
src/async.cpp
include/cb_info.h
include/async.h
src/type.cpp
include/type.h
include/property.h
Expand Down Expand Up @@ -45,4 +47,3 @@ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--emit-relocs --verbos

target_link_libraries(${TARGET_NAME} PUBLIC libace_napi.z.so libhilog_ndk.z.so)
target_compile_features(${TARGET_NAME} PUBLIC cxx_std_17)

Loading