* Copyright (c) 2025 Huawei Device Co., Ltd.
* 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.
*/
import * as idl from "@idlizer/core/idl"
import { ImportsCollector, isInExternalModule, Language, linearizeNamespaceMembers, PeerLibrary } from "@idlizer/core"
import {
LWDeclaration,
OutputFile,
isManaged,
processNPrintArkTS,
processNPrintCXX,
processNPrintTS,
mapFileName,
TargetFile,
readLangTemplate,
peerGeneratorConfiguration,
readTemplate,
libraryDeclaration,
C_API_PREFIX,
BRIDGE_PREFIX,
IMPL_PREFIX,
readInteropTypesHeader,
OhosSeed,
registerDefaultProducers,
MakeSelector,
moduleLike as libohosModuleLike,
lowLevelLike,
OhosEffect,
createOhosEffect,
managedName,
MANAGED_PREFIX,
} from "@idlizer/libohos"
import { continueWith, moduleLike, onlyFor } from '@idlizer/kit'
import { ArkUIRole, registerArkUIProducers } from "./arkui/index.js"
type Feature = {
name: string
init: () => MakeSelector
seeds: (files: idl.IDLFile[]) => OhosSeed<idl.IDLNode>[]
importHook?: moduleLike.OnUnknownImport
}
const OSTFeature: Feature = {
name: 'ost',
init: () => {
const selector = new MakeSelector()
registerDefaultProducers(selector)
return selector
},
seeds: (files: idl.IDLFile[]) => linearizeNamespaceMembers(files.flatMap(f => f.entries))
.filter(e =>
!isInExternalModule(e) &&
!idl.isImport(e) &&
!idl.isCallback(e))
.map(e => new OhosSeed(e, 'managed')),
importHook: name => {
const parts = name.split('.')
if (parts.length > 2 && parts[0] === 'managed') {
if (parts[1].startsWith('#')) {
return {
result: parts.slice(2).join('.'),
name: parts[2],
source: parts[1]
}
} else {
const trimmedName = parts.slice(1).join('.')
for (const [module, moduleData] of peerGeneratorConfiguration().modules) {
if (moduleData.external && trimmedName.startsWith(module)) {
const moduleParts = module.split('.')
return {
result: parts.slice(moduleParts.length + 1).join('.'),
name: parts[moduleParts.length + 1],
source: '@' + module
}
}
}
}
}
}
}
const ArkUIFeature: Feature = {
name: 'arkui',
init: () => {
const selector = new MakeSelector()
registerArkUIProducers(selector)
registerDefaultProducers(selector)
return selector
},
seeds: (files: idl.IDLFile[]) => linearizeNamespaceMembers(files.flatMap(f => f.entries))
.filter(e =>
idl.hasExtAttribute(e, idl.IDLExtendedAttributes.Component) ||
idl.hasExtAttribute(e, idl.IDLExtendedAttributes.ComponentInterface))
.map(e => new OhosSeed(e, 'managed')),
importHook: (name: string) => {
if (name.startsWith(MANAGED_PREFIX + '.#')) {
const parts = name.split('.')
const baseName = parts.pop()!
return {result: baseName, name: baseName, source: parts[1]}
}
switch (name) {
case 'managed.arkui.component.common.AttributeModifier':
return {result: 'AttributeModifier', name: 'AttributeModifier', source: '#handwritten'}
case 'PeerNode':
case 'ComponentBase': return {result: name, name, source: '@arkui.base'}
case 'memo':
case 'memo_stable':
case 'memo_skip': return {result: name, name, source: 'arkui.incremental.annotation'}
case 'remember': return {result: name, name, source: 'arkui.incremental.runtime.memo.remember'}
case 'NodeAttach': return {result: name, name, source: 'arkui.incremental.runtime.memo.node'}
}
return undefined
}
}
const Features = new Map([
['ost', OSTFeature],
['arkui', ArkUIFeature]]
)
function defaultImports(): ImportsCollector {
const imports = new ImportsCollector()
imports.addFeatures([
'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64'
], '@koalaui/compat')
imports.addFeatures([
'KInt', 'KPointer', 'KInteropReturnBuffer', 'KSerializerBuffer',
'SerializerBase', 'DeserializerBase', 'MaterializedBase', 'MaterializedBaseTag',
'Finalizable', 'toPeerPtr',
'RuntimeType', 'ResourceHolder',
'loadNativeModuleLibrary', 'registerApiEventHandler',
'InteropNativeModule', 'resourceFinalizerRegister',
], '@koalaui/interop')
return imports
}
export function printOstFiles(library: PeerLibrary, featureName: string): [Map<string, OutputFile>, Map<TargetFile, string>] {
const feature = Features.get(featureName)
if (!feature)
throw new Error(`Unknown feature: ${featureName}`)
const selector = feature.init()
const files = library.files.filter(file =>
file.packageClause.length &&
!['idlize', 'synthetic'].includes(file.packageClause[0]))
const {effect, declarations } = continueWith<PeerLibrary, OhosEffect>({
createEffect: createOhosEffect,
library,
roots: { seeds: feature.seeds(files) }},
onlyFor(OhosSeed<idl.IDLNode, ArkUIRole<idl.IDLNode>>, (seed, ctx) => selector.select(seed)(seed.node, ctx, seed.role, seed.data)))
const knownPackages = [
...files.map(file => file.packageClause.length ? file.packageClause.join('.') : library.name.toLowerCase()),
'engine',
'synthetic',
].map(managedName)
const [managed, native] = declarations.reduce<[LWDeclaration[], LWDeclaration[]]>(([m, n], decl) => {
(isManaged(decl.name) ? m : n).push(decl)
return [m, n]
}, [[], []])
return [
dumpTsLike(managed, effect, library.language, new Set(knownPackages), feature.importHook),
dumpCLike(native, effect, library.name)
]
}
function dumpTsLike(decls: LWDeclaration[], effect: OhosEffect, language: Language,
packages: Set<string>, onUnknownImport?: moduleLike.OnUnknownImport
): Map<string, OutputFile> {
decls = libohosModuleLike.postprocess(decls, effect.nativeModuleName, effect.callbacks, language)
const files = moduleLike.formFiles(packages, decls, {knownReference: new Map(), defaultNamespaces: effect.defaultNamespaces, knownImports: new Map(), defaultImports, onUnknownImport})
const result: Map<string, OutputFile> = new Map()
const printer = language === Language.ARKTS ? processNPrintArkTS : processNPrintTS
files.forEach((content, fileName) => {
const mappedName = mapFileName(fileName)
if (!mappedName)
return
const printed = content.body.map(it => printer(it, fileName, packages))
result.set(mappedName, {
imports: content.moduleLikeImports,
content: printed,
extension: ".ts",
exported: true,
})
})
return result
}
function dumpCLike(decls: LWDeclaration[], effect: OhosEffect, moduleName: string): Map<TargetFile, string> {
const files: Map<string, LWDeclaration[]> = lowLevelLike.postprocess(decls, effect.modifiers, effect.callbacks)
const interopTypesContent = readInteropTypesHeader()
const h = [
readLangTemplate('ohos_api_prologue.h', Language.CPP),
readTemplate('any_api.h'),
readTemplate('generic_service_api.h'),
processNPrintCXX(files.get(C_API_PREFIX)!),
readLangTemplate('ohos_api_epilogue.h', Language.CPP)
].join('\n')
.replaceAll("%INTEROP_TYPES_HEADER", interopTypesContent)
.replaceAll("%INCLUDE_GUARD_DEFINE%", `OH_${moduleName.toUpperCase()}_H`)
.replaceAll("%LIBRARY_NAME%", moduleName.toUpperCase())
.replaceAll("%API_KIND%", peerGeneratorConfiguration().ApiKind.toString())
const bridgeDecls = files.get(BRIDGE_PREFIX)!
const callbackKindEnum = bridgeDecls.find(it => it.name === 'CallbackKind')
const cpp = [
readLangTemplate('api_impl_prologue.cpp', Language.CPP),
libraryDeclaration({removeCopyright: true}),
readTemplate("api_getter.cpp"),
processNPrintCXX(bridgeDecls.filter(it => it !== callbackKindEnum)),
].join('\n')
.replaceAll("%INTEROP_MODULE_NAME%", `${moduleName.toUpperCase()}NativeModule`)
.replaceAll("%API_HEADER_PATH%", `${moduleName.toLowerCase()}.h`)
.replaceAll("%API_KIND%", `OH_${moduleName}_APIKind::OH_${moduleName}_API_KIND`)
.replaceAll("%API_NAME%", `OH_${moduleName}_API`)
.replaceAll("%CALLBACK_KINDS%", callbackKindEnum ? processNPrintCXX([callbackKindEnum]) : 'enum CallbackKind {};')
.replaceAll("%LIBRARY_NAME%", moduleName.toUpperCase())
const apiImpl = [
`#include "common-interop.h"`,
`#include "${moduleName.toLowerCase()}.h"`,
processNPrintCXX(files.get(IMPL_PREFIX)!),
readLangTemplate('api_impl_epilogue.cpp', Language.CPP)
].join('\n')
.replaceAll("%LIBRARY_NAME%", moduleName.toUpperCase())
return new Map([
[new TargetFile(`${moduleName.toLowerCase()}.h`), h],
[new TargetFile(`${moduleName.toLowerCase()}.cpp`), cpp],
[new TargetFile(`${moduleName.toLowerCase()}ApiImpl_temp.cpp`), apiImpl],
])
}