[jnigen] Refactor C bindings generation (https://github.com/dart-lang/jnigen/issues/282)
diff --git a/pkgs/jnigen/lib/src/bindings/c_bindings.dart b/pkgs/jnigen/lib/src/bindings/c_bindings.dart deleted file mode 100644 index 4f1cad5..0000000 --- a/pkgs/jnigen/lib/src/bindings/c_bindings.dart +++ /dev/null
@@ -1,307 +0,0 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import '../config/config.dart'; -import '../elements/elements.dart'; -import 'c_generator.dart'; - -class CBindingGenerator { - static const classVarPrefix = '_c'; - static const methodVarPrefix = '_m'; - static const fieldVarPrefix = '_f'; - static final indent = ' ' * 4; - static const jniResultType = 'JniResult'; - static const ifError = - '(JniResult){.value = {.j = 0}, .exception = check_exception()}'; - - // These should be avoided in parameter names. - static const _cTypeKeywords = { - 'short', - 'char', - 'int', - 'long', - 'float', - 'double', - }; - - String _renameCParam(String paramName) => - _cTypeKeywords.contains(paramName) ? '${paramName}0' : paramName; - - CBindingGenerator(this.config); - Config config; - - String generateBinding(ClassDecl c) => _class(c); - - String _class(ClassDecl c) { - final s = StringBuffer(); - final classNameInC = c.uniqueName; - // global variable in C that holds the reference to class - final classVar = '${classVarPrefix}_$classNameInC'; - s.write('// ${c.binaryName}\n' - 'jclass $classVar = NULL;\n\n'); - - for (var m in c.methods) { - s.write(_method(c, m)); - s.writeln(); - } - - for (var f in c.fields) { - final fieldBinding = _field(c, f); - s.write(fieldBinding); - // Fields are skipped if they're static final. In that case - // do not write too much whitespace. - if (fieldBinding.isNotEmpty) s.writeln(); - } - return s.toString(); - } - - String getCType(String binaryName) { - switch (binaryName) { - case "void": - return "void"; - case "byte": - return "int8_t"; - case "char": - return "uint16_t"; - case "double": - return "double"; - case "float": - return "float"; - case "int": - return "int32_t"; - case "long": - return "int64_t"; - case "short": - return "int16_t"; - case "boolean": - return "uint8_t"; - default: - return "jobject"; - } - } - - String _method(ClassDecl c, Method m) { - final classNameInC = c.uniqueName; - final isACtor = m.isCtor; - final isStatic = m.isStatic; - - final s = StringBuffer(); - final cMethodName = m.accept(const CMethodName()); - final classRef = '${classVarPrefix}_$classNameInC'; - final methodID = '${methodVarPrefix}_$cMethodName'; - final cMethodParams = _formalArgs(m); - final jniSignature = m.accept(const MethodSignature()); - final ifStaticMethodID = isStatic ? 'static_' : ''; - - var javaReturnType = m.returnType.name; - if (isACtor) { - javaReturnType = c.binaryName; - } - final callType = _typeNameAtCallSite(m.returnType); - final callArgs = _callArgs(m, classRef, methodID); - - var ifAssignResult = ''; - if (javaReturnType != 'void') { - ifAssignResult = '${getCType(javaReturnType)} _result = '; - } - - final ifStaticCall = isStatic ? 'Static' : ''; - final envMethod = - isACtor ? 'NewObject' : 'Call$ifStaticCall${callType}Method'; - final returnResultIfAny = _result(m); - s.write(''' -jmethodID $methodID = NULL; -FFI_PLUGIN_EXPORT -$jniResultType $cMethodName($cMethodParams) { - $_loadEnvCall - ${_loadClassCall(classRef, c.internalName)} - load_${ifStaticMethodID}method($classRef, - &$methodID, "${m.name}", "$jniSignature"); - if ($methodID == NULL) return $ifError; - $ifAssignResult(*jniEnv)->$envMethod($callArgs); - $returnResultIfAny -}\n'''); - return s.toString(); - } - - String _field(ClassDecl c, Field f) { - final cClassName = c.uniqueName; - final isStatic = f.isStatic; - - final fieldName = f.finalName; - final fieldNameInC = f.accept(const CFieldName()); - final fieldVar = "${fieldVarPrefix}_$fieldNameInC"; - - // If the field is final and default is assigned, then no need to wrap - // this field. It should then be a constant in dart code. - if (isStatic && f.isFinal && f.defaultValue != null) { - return ""; - } - - final s = StringBuffer(); - - s.write('jfieldID $fieldVar = NULL;\n'); - - final classVar = '${classVarPrefix}_$cClassName'; - void writeAccessor({bool isSetter = false}) { - const cReturnType = jniResultType; - final cMethodPrefix = isSetter ? 'set' : 'get'; - final formalArgs = <String>[ - if (!isStatic) 'jobject self_', - if (isSetter) '${getCType(f.type.name)} value', - ].join(', '); - final ifStaticField = isStatic ? 'static_' : ''; - final ifStaticCall = isStatic ? 'Static' : ''; - final callType = _typeNameAtCallSite(f.type); - final objectArgument = isStatic ? classVar : 'self_'; - - String accessorStatements; - if (isSetter) { - accessorStatements = - '$indent(*jniEnv)->Set$ifStaticCall${callType}Field(jniEnv, ' - '$objectArgument, $fieldVar, value);\n' - '${indent}return $ifError;'; - } else { - final getterExpr = - '(*jniEnv)->Get$ifStaticCall${callType}Field(jniEnv, ' - '$objectArgument, $fieldVar)'; - final cResultType = getCType(f.type.name); - final unionField = getJValueField(f.type); - final String returnExpr; - if (f.type.kind != Kind.primitive) { - returnExpr = 'to_global_ref_result(_result)'; - } else { - returnExpr = '(JniResult){.value = ' - '{.$unionField = _result}, .exception = check_exception()}'; - } - accessorStatements = '$indent$cResultType _result = $getterExpr;\n' - '${indent}return $returnExpr;'; - } - - s.write(''' -FFI_PLUGIN_EXPORT -$cReturnType ${cMethodPrefix}_$fieldNameInC($formalArgs) { - $_loadEnvCall - ${_loadClassCall(classVar, c.internalName)} - load_${ifStaticField}field($classVar, &$fieldVar, "$fieldName", - "${f.type.accept(const Descriptor())}"); -$accessorStatements -}\n\n'''); - } - - writeAccessor(isSetter: false); - if (f.isFinal) { - return s.toString(); - } - writeAccessor(isSetter: true); - return s.toString(); - } - - final String _loadEnvCall = '${indent}load_env();'; - - String _loadClassCall(String classVar, String internalName) { - return '${indent}load_class_global_ref(&$classVar, "$internalName");\n' - '${indent}if ($classVar == NULL) return $ifError;'; - } - - String _formalArgs(Method m) { - final args = <String>[]; - if (!m.isCtor && !m.isStatic) { - // The underscore-suffixed name prevents accidental collision with - // parameter named self, if any. - args.add('jobject self_'); - } - - for (var param in m.params) { - final paramName = _renameCParam(param.name); - args.add('${getCType(param.type.name)} $paramName'); - } - - return args.join(", "); - } - - String getJValueField(TypeUsage type) { - const primitives = { - 'boolean': 'z', - 'byte': 'b', - 'short': 's', - 'char': 'c', - 'int': 'i', - 'long': 'j', - 'float': 'f', - 'double': 'd', - 'void': 'j', // in case of void return, just write 0 to largest field. - }; - if (type.kind == Kind.primitive) { - return primitives[type.name]!; - } - return 'l'; - } - - // Returns arguments at call site, concatenated by `,`. - String _callArgs(Method m, String classVar, String methodVar) { - final args = ['jniEnv']; - if (!m.isCtor && !m.isStatic) { - args.add('self_'); - } else { - args.add(classVar); - } - args.add(methodVar); - for (var param in m.params) { - final paramName = _renameCParam(param.name); - args.add(paramName); - } - return args.join(', '); - } - - String _result(Method m) { - final cReturnType = getCType(m.returnType.name); - String valuePart; - String unionField; - if (cReturnType == 'jobject' || m.isCtor) { - return '${indent}return to_global_ref_result(_result);'; - } else if (cReturnType == 'void') { - // in case of void return, just write 0 in result part of JniResult - unionField = 'j'; - valuePart = '0'; - } else { - unionField = getJValueField(m.returnType); - valuePart = '_result'; - } - const exceptionPart = 'check_exception()'; - return '${indent}return (JniResult){.value = {.$unionField = $valuePart}, ' - '.exception = $exceptionPart};'; - } - - /// Returns capitalized java type name to be used as in call${type}Method - /// or get${type}Field etc.. - String _typeNameAtCallSite(TypeUsage type) { - if (type.kind == Kind.primitive) { - return type.name.substring(0, 1).toUpperCase() + type.name.substring(1); - } - return "Object"; - } -} - -class CPreludes { - static const autoGeneratedNotice = '// Autogenerated by jnigen. ' - 'DO NOT EDIT!\n\n'; - static const includes = '#include <stdint.h>\n' - '#include "jni.h"\n' - '#include "dartjni.h"\n' - '\n'; - static const defines = 'thread_local JNIEnv *jniEnv;\n' - 'JniContext *jni;\n\n' - 'JniContext *(*context_getter)(void);\n' - 'JNIEnv *(*env_getter)(void);\n' - '\n'; - static const initializers = 'void setJniGetters(JniContext *(*cg)(void),\n' - ' JNIEnv *(*eg)(void)) {\n' - ' context_getter = cg;\n' - ' env_getter = eg;\n' - '}\n' - '\n'; - static const prelude = - autoGeneratedNotice + includes + defines + initializers; -}
diff --git a/pkgs/jnigen/lib/src/bindings/c_generator.dart b/pkgs/jnigen/lib/src/bindings/c_generator.dart index 97b2604..ee30572 100644 --- a/pkgs/jnigen/lib/src/bindings/c_generator.dart +++ b/pkgs/jnigen/lib/src/bindings/c_generator.dart
@@ -2,7 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io'; + +import '../config/config.dart'; import '../elements/elements.dart'; +import '../logging/logging.dart'; +import '../util/find_package.dart'; +import '../util/string_util.dart'; import 'visitor.dart'; /// JVM representation of type signatures. @@ -92,3 +98,350 @@ return '${className}__$methodName'; } } + +class CGenerator extends Visitor<Classes, Future<void>> { + static const _prelude = '''// Autogenerated by jnigen. DO NOT EDIT! + +#include <stdint.h> +#include "jni.h" +#include "dartjni.h" + +thread_local JNIEnv *jniEnv; +JniContext *jni; + +JniContext *(*context_getter)(void); +JNIEnv *(*env_getter)(void); + +void setJniGetters(JniContext *(*cg)(void), + JNIEnv *(*eg)(void)) { + context_getter = cg; + env_getter = eg; +} + +'''; + + final Config config; + + CGenerator(this.config); + + Future<void> _copyFileFromPackage(String package, String relPath, Uri target, + {String Function(String)? transform}) async { + final packagePath = await findPackageRoot(package); + if (packagePath != null) { + final sourceFile = File.fromUri(packagePath.resolve(relPath)); + final targetFile = await File.fromUri(target).create(recursive: true); + var source = await sourceFile.readAsString(); + if (transform != null) { + source = transform(source); + } + await targetFile.writeAsString(source); + } else { + log.warning('package $package not found! ' + 'skipped copying ${target.toFilePath()}'); + } + } + + @override + Future<void> visit(Classes node) async { + // Write C file and init file. + final cConfig = config.outputConfig.cConfig!; + final cRoot = cConfig.path; + final preamble = config.preamble; + log.info("Using c root = $cRoot"); + final libraryName = cConfig.libraryName; + log.info('Creating dart init file ...'); + // Create C file. + final subdir = cConfig.subdir ?? '.'; + final cFileRelativePath = '$subdir/$libraryName.c'; + final cFile = await File.fromUri(cRoot.resolve(cFileRelativePath)) + .create(recursive: true); + final cFileStream = cFile.openWrite(); + // Write C Bindings. + if (preamble != null) { + cFileStream.writeln(preamble); + } + cFileStream.write(_prelude); + final classGenerator = _CClassGenerator(config, cFileStream); + for (final classDecl in node.decls.values) { + classDecl.accept(classGenerator); + } + await cFileStream.close(); + log.info('Copying auxiliary files...'); + for (final file in ['dartjni.h', '.clang-format']) { + await _copyFileFromPackage( + 'jni', 'src/$file', cRoot.resolve('$subdir/$file')); + } + await _copyFileFromPackage( + 'jnigen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'), + transform: (s) { + return s + .replaceAll('{{LIBRARY_NAME}}', libraryName) + .replaceAll('{{SUBDIR}}', subdir); + }); + log.info('Running clang-format on C bindings'); + try { + final clangFormat = Process.runSync('clang-format', ['-i', cFile.path]); + if (clangFormat.exitCode != 0) { + printError(clangFormat.stderr); + log.warning('clang-format exited with ${clangFormat.exitCode}'); + } + } on ProcessException catch (e) { + log.warning('cannot run clang-format: $e'); + } + } +} + +const _classVarPrefix = '_c_'; +const _jniResultType = 'JniResult'; +const _loadEnvCall = 'load_env();'; +const _ifError = + '(JniResult){.value = {.j = 0}, .exception = check_exception()}'; + +class _CClassGenerator extends Visitor<ClassDecl, void> { + final Config config; + final StringSink s; + + _CClassGenerator(this.config, this.s); + + @override + void visit(ClassDecl node) { + final classNameInC = node.uniqueName; + final classVar = '$_classVarPrefix$classNameInC'; + // Global variable in C that holds the reference to class. + s.write('''// ${node.binaryName} +jclass $classVar = NULL; + +'''); + + final methodGenerator = _CMethodGenerator(config, s); + for (final method in node.methods) { + method.accept(methodGenerator); + } + + final fieldGenerator = _CFieldGenerator(config, s); + for (final field in node.fields) { + field.accept(fieldGenerator); + } + } +} + +class _CLoadClassGenerator extends Visitor<ClassDecl, String> { + _CLoadClassGenerator(); + + @override + String visit(ClassDecl node) { + final classVar = '$_classVarPrefix${node.uniqueName}'; + return ''' load_class_global_ref(&$classVar, "${node.internalName}"); + if ($classVar == NULL) return $_ifError;'''; + } +} + +class _CMethodGenerator extends Visitor<Method, void> { + static const _methodVarPrefix = '_m_'; + + final Config config; + final StringSink s; + + _CMethodGenerator(this.config, this.s); + + @override + void visit(Method node) { + final classNameInC = node.classDecl.uniqueName; + + final cMethodName = node.accept(const CMethodName()); + final classRef = '$_classVarPrefix$classNameInC'; + final methodId = '$_methodVarPrefix$cMethodName'; + final cMethodParams = [ + if (!node.isCtor && !node.isStatic) 'jobject self_', + ...node.params.accept(const _CParamGenerator(addReturnType: true)), + ].join(','); + final jniSignature = node.accept(const MethodSignature()); + final ifStaticMethodID = node.isStatic ? 'static_' : ''; + + var javaReturnType = node.returnType.type; + if (node.isCtor) { + javaReturnType = DeclaredType( + binaryName: node.classDecl.binaryName, + simpleName: node.classDecl.simpleName, + ); + } + final callType = node.returnType.accept(const _CTypeCallSite()); + final callArgs = [ + 'jniEnv', + if (!node.isCtor && !node.isStatic) 'self_' else classRef, + methodId, + ...node.params.accept(const _CParamGenerator(addReturnType: false)) + ].join(', '); + + var ifAssignResult = ''; + if (javaReturnType.name != 'void') { + ifAssignResult = + '${javaReturnType.accept(const _CReturnType())} _result = '; + } + + final ifStaticCall = node.isStatic ? 'Static' : ''; + final envMethod = + node.isCtor ? 'NewObject' : 'Call$ifStaticCall${callType}Method'; + final returnResultIfAny = javaReturnType.accept(const _CResult()); + s.write(''' +jmethodID $methodId = NULL; +FFI_PLUGIN_EXPORT +$_jniResultType $cMethodName($cMethodParams) { + $_loadEnvCall + ${node.classDecl.accept(_CLoadClassGenerator())} + load_${ifStaticMethodID}method($classRef, + &$methodId, "${node.name}", "$jniSignature"); + if ($methodId == NULL) return $_ifError; + $ifAssignResult(*jniEnv)->$envMethod($callArgs); + $returnResultIfAny +} + +'''); + } +} + +class _CFieldGenerator extends Visitor<Field, void> { + static const _fieldVarPrefix = '_f_'; + + final Config config; + final StringSink s; + + _CFieldGenerator(this.config, this.s); + + @override + void visit(Field node) { + final cClassName = node.classDecl.uniqueName; + + final fieldName = node.finalName; + final fieldNameInC = node.accept(const CFieldName()); + final fieldVar = "$_fieldVarPrefix$fieldNameInC"; + + // If the field is final and default is assigned, then no need to wrap + // this field. It should then be a constant in dart code. + if (node.isStatic && node.isFinal && node.defaultValue != null) { + return; + } + + s.write('jfieldID $fieldVar = NULL;\n'); + + final classVar = '$_classVarPrefix$cClassName'; + void writeAccessor({bool isSetter = false}) { + const cReturnType = _jniResultType; + final cMethodPrefix = isSetter ? 'set' : 'get'; + final formalArgs = [ + if (!node.isStatic) 'jobject self_', + if (isSetter) '${node.type.accept(const _CReturnType())} value', + ].join(', '); + final ifStaticField = node.isStatic ? 'static_' : ''; + final ifStaticCall = node.isStatic ? 'Static' : ''; + final callType = node.type.accept(const _CTypeCallSite()); + final objectArgument = node.isStatic ? classVar : 'self_'; + + String accessorStatements; + if (isSetter) { + accessorStatements = + ' (*jniEnv)->Set$ifStaticCall${callType}Field(jniEnv, ' + '$objectArgument, $fieldVar, value);\n' + ' return $_ifError;'; + } else { + final getterExpr = + '(*jniEnv)->Get$ifStaticCall${callType}Field(jniEnv, ' + '$objectArgument, $fieldVar)'; + final cResultType = node.type.accept(const _CReturnType()); + final result = node.type.accept(const _CResult()); + accessorStatements = ''' $cResultType _result = $getterExpr; + $result'''; + } + s.write(''' +FFI_PLUGIN_EXPORT +$cReturnType ${cMethodPrefix}_$fieldNameInC($formalArgs) { + $_loadEnvCall + ${node.classDecl.accept(_CLoadClassGenerator())} + load_${ifStaticField}field($classVar, &$fieldVar, "$fieldName", + "${node.type.accept(const Descriptor())}"); +$accessorStatements +} + +'''); + } + + writeAccessor(isSetter: false); + if (node.isFinal) { + return; + } + writeAccessor(isSetter: true); + } +} + +class _CParamGenerator extends Visitor<Param, String> { + /// These should be avoided in parameter names. + static const _cTypeKeywords = { + 'short', + 'char', + 'int', + 'long', + 'float', + 'double', + }; + + const _CParamGenerator({required this.addReturnType}); + + final bool addReturnType; + + @override + String visit(Param node) { + final paramName = + _cTypeKeywords.contains(node.name) ? '${node.name}0' : node.name; + final type = node.type.accept(const _CReturnType()); + if (addReturnType) return '$type $paramName'; + return paramName; + } +} + +class _CReturnType extends TypeVisitor<String> { + const _CReturnType(); + + @override + String visitNonPrimitiveType(ReferredType node) { + return 'jobject'; + } + + @override + String visitPrimitiveType(PrimitiveType node) { + return node.cType; + } +} + +class _CTypeCallSite extends TypeVisitor<String> { + const _CTypeCallSite(); + + @override + String visitNonPrimitiveType(ReferredType node) { + return 'Object'; + } + + @override + String visitPrimitiveType(PrimitiveType node) { + return node.name.capitalize(); + } +} + +class _CResult extends TypeVisitor<String> { + const _CResult(); + + @override + String visitNonPrimitiveType(ReferredType node) { + return 'return to_global_ref_result(_result);'; + } + + @override + String visitPrimitiveType(PrimitiveType node) { + if (node.name == 'void') { + return 'return $_ifError;'; + } + // The union field is the same as the type's signature, but in lowercase. + final unionField = node.signature.toLowerCase(); + return 'return (JniResult){.value = {.$unionField = _result}, ' + '.exception = check_exception()};'; + } +}
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart index 326069a..1cd3c2b 100644 --- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart +++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -9,7 +9,7 @@ import '../config/config.dart'; import '../elements/elements.dart'; import '../logging/logging.dart'; -import '../writers/bindings_writer.dart'; +import '../util/string_util.dart'; import 'c_generator.dart'; import 'resolver.dart'; import 'visitor.dart'; @@ -47,15 +47,6 @@ ' /// The returned object must be deleted after use, ' 'by calling the `delete` method.'; -extension on String { - String capitalize() { - return '${this[0].toUpperCase()}${substring(1)}'; - } - - /// Reverses an ASCII string. - String get reversed => split('').reversed.join(); -} - extension on Iterable<String> { /// Similar to [join] but adds the [separator] to the end as well. String delimited([String separator = '']) { @@ -173,6 +164,19 @@ static const preImportBoilerplate = autoGeneratedNotice + defaultLintSuppressions + defaultImports; + /// Run dart format command on [path]. + Future<void> _runDartFormat(String path) async { + log.info('Running dart format...'); + final formatRes = await Process.run('dart', ['format', path]); + // if negative exit code, likely due to an interrupt. + if (formatRes.exitCode > 0) { + log.fatal('Dart format completed with exit code ${formatRes.exitCode} ' + 'This usually means there\'s a syntax error in bindings.\n' + 'Please look at the generated files and report a bug: \n' + 'https://github.com/dart-lang/jnigen/issues/new\n'); + } + } + @override Future<void> visit(Classes node) async { final cBased = config.outputConfig.bindingsType == BindingsType.cBased; @@ -192,9 +196,11 @@ s.writeln(cInitCode); } final classGenerator = _ClassGenerator(config, s); - node.decls.values.accept(classGenerator).toList(); + for (final classDecl in node.decls.values) { + classDecl.accept(classGenerator); + } await s.close(); - await runDartFormat(file.path); + await _runDartFormat(file.path); return; } final files = <String, List<ClassDecl>>{}; @@ -247,9 +253,10 @@ currentClass: fileClassName, inputClassNames: node.decls.keys.toSet(), ); - classesInFile - .accept(_ClassGenerator(config, s, resolver: resolver)) - .toList(); + final classGenerator = _ClassGenerator(config, s, resolver: resolver); + for (final classDecl in classesInFile) { + classDecl.accept(classGenerator); + } dartFileStream.writeAll(resolver.getImportStrings(), '\n'); dartFileStream.writeln(s.toString()); await dartFileStream.close(); @@ -265,7 +272,7 @@ packages[package]!.map((cls) => 'export "$cls.dart";').join('\n'); exportFile.writeAsStringSync(exports); } - await runDartFormat(root.toFilePath()); + await _runDartFormat(root.toFilePath()); log.info('Completed.'); } }
diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index 2e4a9f5..7793c1f 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart
@@ -278,7 +278,7 @@ signature: 'C', dartType: 'int', jniType: 'jchar', - cType: 'char', + cType: 'uint16_t', ffiType: 'Uint16', ), 'int': PrimitiveType._(
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart index d553657..ab184da 100644 --- a/pkgs/jnigen/lib/src/generate_bindings.dart +++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -5,6 +5,7 @@ import 'dart:io'; import 'dart:convert'; +import 'bindings/c_generator.dart'; import 'bindings/dart_generator.dart'; import 'bindings/excluder.dart'; import 'bindings/linker.dart'; @@ -13,7 +14,6 @@ import 'summary/summary.dart'; import 'config/config.dart'; import 'tools/tools.dart'; -import 'writers/bindings_writer.dart'; import 'logging/logging.dart'; void collectOutputStream(Stream<List<int>> stream, StringBuffer buffer) => @@ -34,14 +34,14 @@ log.fatal(e.message); } - final cBased = config.outputConfig.bindingsType == BindingsType.cBased; classes ..accept(Excluder(config)) ..accept(Linker(config)) ..accept(Renamer(config)); + final cBased = config.outputConfig.bindingsType == BindingsType.cBased; if (cBased) { - await writeCBindings(config, classes.decls.values.toList()); + await classes.accept(CGenerator(config)); } try {
diff --git a/pkgs/jnigen/lib/src/util/string_util.dart b/pkgs/jnigen/lib/src/util/string_util.dart new file mode 100644 index 0000000..5a65bfb --- /dev/null +++ b/pkgs/jnigen/lib/src/util/string_util.dart
@@ -0,0 +1,13 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +extension StringUtil on String { + /// Makes the first letter uppercase. + String capitalize() { + return '${this[0].toUpperCase()}${substring(1)}'; + } + + /// Reverses an ASCII string. + String get reversed => split('').reversed.join(); +}
diff --git a/pkgs/jnigen/lib/src/writers/bindings_writer.dart b/pkgs/jnigen/lib/src/writers/bindings_writer.dart deleted file mode 100644 index 190a90c..0000000 --- a/pkgs/jnigen/lib/src/writers/bindings_writer.dart +++ /dev/null
@@ -1,88 +0,0 @@ -// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'dart:io'; - -import '../bindings/c_bindings.dart'; -import '../config/config.dart'; -import '../elements/elements.dart'; -import '../logging/logging.dart'; -import '../util/find_package.dart'; - -/// Run dart format command on [path]. -Future<void> runDartFormat(String path) async { - log.info('Running dart format...'); - final formatRes = await Process.run('dart', ['format', path]); - // if negative exit code, likely due to an interrupt. - if (formatRes.exitCode > 0) { - log.fatal('Dart format completed with exit code ${formatRes.exitCode} ' - 'This usually means there\'s a syntax error in bindings.\n' - 'Please look at the generated files and report a bug.'); - } -} - -Future<void> _copyFileFromPackage(String package, String relPath, Uri target, - {String Function(String)? transform}) async { - final packagePath = await findPackageRoot(package); - if (packagePath != null) { - final sourceFile = File.fromUri(packagePath.resolve(relPath)); - final targetFile = await File.fromUri(target).create(recursive: true); - var source = await sourceFile.readAsString(); - if (transform != null) { - source = transform(source); - } - await targetFile.writeAsString(source); - } else { - log.warning('package $package not found! ' - 'skipped copying ${target.toFilePath()}'); - } -} - -Future<void> writeCBindings(Config config, List<ClassDecl> classes) async { - // write C file and init file - final cConfig = config.outputConfig.cConfig!; - final cRoot = cConfig.path; - final preamble = config.preamble; - log.info("Using c root = $cRoot"); - final libraryName = cConfig.libraryName; - log.info('Creating dart init file ...'); - // Create C file - final subdir = cConfig.subdir ?? '.'; - final cFileRelativePath = '$subdir/$libraryName.c'; - final cFile = await File.fromUri(cRoot.resolve(cFileRelativePath)) - .create(recursive: true); - final cFileStream = cFile.openWrite(); - // Write C Bindings - if (preamble != null) { - cFileStream.writeln(preamble); - } - cFileStream.write(CPreludes.prelude); - final cgen = CBindingGenerator(config); - final cBindings = classes.map(cgen.generateBinding).toList(); - log.info('writing c bindings to $cFile'); - cBindings.forEach(cFileStream.write); - await cFileStream.close(); - log.info('Copying auxiliary files...'); - await _copyFileFromPackage( - 'jni', 'src/dartjni.h', cRoot.resolve('$subdir/dartjni.h')); - await _copyFileFromPackage( - 'jni', 'src/.clang-format', cRoot.resolve('$subdir/.clang-format')); - await _copyFileFromPackage( - 'jnigen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'), - transform: (s) { - return s - .replaceAll('{{LIBRARY_NAME}}', libraryName) - .replaceAll('{{SUBDIR}}', subdir); - }); - log.info('Running clang-format on C bindings'); - try { - final clangFormat = Process.runSync('clang-format', ['-i', cFile.path]); - if (clangFormat.exitCode != 0) { - printError(clangFormat.stderr); - log.warning('clang-format exited with ${clangFormat.exitCode}'); - } - } on ProcessException catch (e) { - log.warning('cannot run clang-format: $e'); - } -}