import { NitroConfig } from '../../config/NitroConfig.js' import { createCppHybridObjectRegistration } from '../../syntax/c++/CppHybridObjectRegistration.js' import { includeHeader } from '../../syntax/c++/includeNitroHeader.js' import { createFileMetadataString, isNotDuplicate, } from '../../syntax/helpers.js' import { getJNINativeRegistrations } from '../../syntax/kotlin/JNINativeRegistrations.js' import { createJNIHybridObjectRegistration } from '../../syntax/kotlin/KotlinHybridObjectRegistration.js' import type { SourceFile, SourceImport } from '../../syntax/SourceFile.js' import { indent } from '../../utils.js' import { getBuildingWithGeneratedCmakeDefinition } from './createCMakeExtension.js' export function createHybridObjectIntializer(): SourceFile[] { const cxxNamespace = NitroConfig.current.getCxxNamespace('c++') const cppLibName = NitroConfig.current.getAndroidCxxLibName() const javaNamespace = NitroConfig.current.getAndroidPackage('java/kotlin') const autolinkingClassName = `${NitroConfig.current.getAndroidCxxLibName()}OnLoad` const jniRegistrations = getJNINativeRegistrations() .map((r) => `${r.namespace}::${r.className}::registerNatives();`) .filter(isNotDuplicate) const autolinkedHybridObjects = NitroConfig.current.getAutolinkedHybridObjects() const cppHybridObjectImports: SourceImport[] = [] const cppRegistrations: string[] = [] const cppDefinitions: string[] = [] for (const hybridObjectName of Object.keys(autolinkedHybridObjects)) { const implementation = NitroConfig.current.getAndroidAutolinkedImplementation(hybridObjectName) if (implementation == null) { continue } switch (implementation.language) { case 'c++': { // Autolink a C++ HybridObject! const { cppCode, requiredImports } = createCppHybridObjectRegistration({ hybridObjectName: hybridObjectName, cppClassName: implementation.implementationClassName, }) cppHybridObjectImports.push(...requiredImports) cppRegistrations.push(cppCode) break } case 'kotlin': { // Autolink a Kotlin HybridObject through JNI/C++! const { cppCode, cppDefinition, requiredImports } = createJNIHybridObjectRegistration({ hybridObjectName: hybridObjectName, jniClassName: implementation.implementationClassName, }) cppHybridObjectImports.push(...requiredImports) cppDefinitions.push(cppDefinition) cppRegistrations.push(cppCode) break } default: throw new Error( `The HybridObject "${hybridObjectName}" cannot be autolinked on Android - Language "${implementation.language}" is not supported on Android!` ) } } const buildingWithDefinition = getBuildingWithGeneratedCmakeDefinition() const includes = [ ...getJNINativeRegistrations().map((r) => includeHeader(r.import)), ...cppHybridObjectImports.map((i) => includeHeader(i)), ] .filter(isNotDuplicate) .join('\n') const hppCode = ` ${createFileMetadataString(`${autolinkingClassName}.hpp`)} #include #include #include namespace ${cxxNamespace} { [[deprecated("Use registerNatives() instead.")]] int initialize(JavaVM* vm); /** * Register the native (C++) part of ${cppLibName}, and autolinks all Hybrid Objects. * Call this in your \`JNI_OnLoad\` function (probably inside \`cpp-adapter.cpp\`), * inside a \`facebook::jni::initialize(vm, ...)\` call. * Example: * \`\`\`cpp (cpp-adapter.cpp) * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { * return facebook::jni::initialize(vm, []() { * // register all ${cppLibName} HybridObjects * ${cxxNamespace}::registerNatives(); * // any other custom registrations go here. * }); * } * \`\`\` */ void registerAllNatives(); } // namespace ${cxxNamespace} ` const cppCode = ` ${createFileMetadataString(`${autolinkingClassName}.cpp`)} #ifndef ${buildingWithDefinition} #error ${autolinkingClassName}.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this? #endif #include "${autolinkingClassName}.hpp" #include #include #include ${includes} namespace ${cxxNamespace} { int initialize(JavaVM* vm) { return facebook::jni::initialize(vm, []() { ::${cxxNamespace}::registerAllNatives(); }); } ${cppDefinitions.join('\n')} void registerAllNatives() { using namespace margelo::nitro; using namespace ${cxxNamespace}; // Register native JNI methods ${indent(jniRegistrations.join('\n'), ' ')} // Register Nitro Hybrid Objects ${indent(cppRegistrations.join('\n'), ' ')} } } // namespace ${cxxNamespace} `.trim() const kotlinCode = ` ${createFileMetadataString(`${autolinkingClassName}.kt`)} package ${javaNamespace} import android.util.Log internal class ${autolinkingClassName} { companion object { private const val TAG = "${autolinkingClassName}" private var didLoad = false /** * Initializes the native part of "${cppLibName}". * This method is idempotent and can be called more than once. */ @JvmStatic fun initializeNative() { if (didLoad) return try { Log.i(TAG, "Loading ${cppLibName} C++ library...") System.loadLibrary("${cppLibName}") Log.i(TAG, "Successfully loaded ${cppLibName} C++ library!") didLoad = true } catch (e: Error) { Log.e(TAG, "Failed to load ${cppLibName} C++ library! Is it properly installed and linked? " + "Is the name correct? (see \`CMakeLists.txt\`, at \`add_library(...)\`)", e) throw e } } } } `.trim() return [ { content: hppCode, language: 'c++', name: `${autolinkingClassName}.hpp`, platform: 'android', subdirectory: [], }, { content: cppCode, language: 'c++', name: `${autolinkingClassName}.cpp`, platform: 'android', subdirectory: [], }, { content: kotlinCode, language: 'kotlin', name: `${autolinkingClassName}.kt`, platform: 'android', subdirectory: ['kotlin', ...javaNamespace.split('.')], }, ] }