[jnigen] Add basic YAML config support (https://github.com/dart-lang/jnigen/issues/32)
diff --git a/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java index 589ba85..ffda30a 100644 --- a/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java +++ b/pkgs/jni/android/src/main/java/dev/dart/jni/JniPlugin.java
@@ -1,3 +1,7 @@ +// 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. + package dev.dart.jni; import android.app.Activity;
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart index 67d9f96..c4befc6 100644 --- a/pkgs/jni/bin/setup.dart +++ b/pkgs/jni/bin/setup.dart
@@ -1,3 +1,7 @@ +// 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 'package:args/args.dart';
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart index c21802b..0303900 100644 --- a/pkgs/jni/example/lib/main.dart +++ b/pkgs/jni/example/lib/main.dart
@@ -1,3 +1,7 @@ +// 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. + // ignore_for_file: library_private_types_in_public_api import 'package:flutter/material.dart';
diff --git a/pkgs/jni/example/test/widget_test.dart b/pkgs/jni/example/test/widget_test.dart index 2607462..ec585ec 100644 --- a/pkgs/jni/example/test/widget_test.dart +++ b/pkgs/jni/example/test/widget_test.dart
@@ -1,3 +1,7 @@ +// 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 'package:flutter/material.dart';
diff --git a/pkgs/jni/lib/jni_object.dart b/pkgs/jni/lib/jni_object.dart index f12a8fd..e0916f0 100644 --- a/pkgs/jni/lib/jni_object.dart +++ b/pkgs/jni/lib/jni_object.dart
@@ -1,3 +1,7 @@ +// 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. + /// jni_object library provides an easier interface to JNI's object references, /// providing various helper methods for one-off uses. ///
diff --git a/pkgs/jni/lib/src/direct_methods_generated.dart b/pkgs/jni/lib/src/direct_methods_generated.dart index fc04c0f..d1f0191 100644 --- a/pkgs/jni/lib/src/direct_methods_generated.dart +++ b/pkgs/jni/lib/src/direct_methods_generated.dart
@@ -1,3 +1,7 @@ +// 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. + // Autogenerated; DO NOT EDIT // Generated by running the script in tool/gen_aux_methods.dart // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/extensions.dart b/pkgs/jni/lib/src/extensions.dart index d749e3e..995eb58 100644 --- a/pkgs/jni/lib/src/extensions.dart +++ b/pkgs/jni/lib/src/extensions.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jl_object.dart b/pkgs/jni/lib/src/jl_object.dart index 36558f4..2778dfc 100644 --- a/pkgs/jni/lib/src/jl_object.dart +++ b/pkgs/jni/lib/src/jl_object.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni.dart b/pkgs/jni/lib/src/jni.dart index b3ce6df..a188d86 100644 --- a/pkgs/jni/lib/src/jni.dart +++ b/pkgs/jni/lib/src/jni.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'dart:io';
diff --git a/pkgs/jni/lib/src/jni_class.dart b/pkgs/jni/lib/src/jni_class.dart index b4a6763..308981a 100644 --- a/pkgs/jni/lib/src/jni_class.dart +++ b/pkgs/jni/lib/src/jni_class.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni_class_methods_generated.dart b/pkgs/jni/lib/src/jni_class_methods_generated.dart index 3e3a7ca..195deef 100644 --- a/pkgs/jni/lib/src/jni_class_methods_generated.dart +++ b/pkgs/jni/lib/src/jni_class_methods_generated.dart
@@ -1,3 +1,7 @@ +// 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. + // Autogenerated; DO NOT EDIT // Generated by running the script in tool/gen_aux_methods.dart // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/jni_exceptions.dart b/pkgs/jni/lib/src/jni_exceptions.dart index c765ec4..a9cb639 100644 --- a/pkgs/jni/lib/src/jni_exceptions.dart +++ b/pkgs/jni/lib/src/jni_exceptions.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'third_party/jni_bindings_generated.dart';
diff --git a/pkgs/jni/lib/src/jni_object.dart b/pkgs/jni/lib/src/jni_object.dart index 217c832..47d480a 100644 --- a/pkgs/jni/lib/src/jni_object.dart +++ b/pkgs/jni/lib/src/jni_object.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/lib/src/jni_object_methods_generated.dart b/pkgs/jni/lib/src/jni_object_methods_generated.dart index 490f8fc..3c41707 100644 --- a/pkgs/jni/lib/src/jni_object_methods_generated.dart +++ b/pkgs/jni/lib/src/jni_object_methods_generated.dart
@@ -1,3 +1,7 @@ +// 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. + // Autogenerated; DO NOT EDIT // Generated by running the script in tool/gen_aux_methods.dart // coverage:ignore-file
diff --git a/pkgs/jni/lib/src/jvalues.dart b/pkgs/jni/lib/src/jvalues.dart index 63945b0..94a9cbe 100644 --- a/pkgs/jni/lib/src/jvalues.dart +++ b/pkgs/jni/lib/src/jvalues.dart
@@ -1,3 +1,7 @@ +// 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:ffi'; import 'package:ffi/ffi.dart';
diff --git a/pkgs/jni/src/dartjni.c b/pkgs/jni/src/dartjni.c index 82f1597..8ba5a89 100644 --- a/pkgs/jni/src/dartjni.c +++ b/pkgs/jni/src/dartjni.c
@@ -1,3 +1,7 @@ +// 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. + #include <jni.h> #include <stdint.h>
diff --git a/pkgs/jni/src/dartjni.h b/pkgs/jni/src/dartjni.h index 407312b..cd94b15 100644 --- a/pkgs/jni/src/dartjni.h +++ b/pkgs/jni/src/dartjni.h
@@ -1,3 +1,7 @@ +// 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. + #include <jni.h> #include <stdint.h> #include <stdio.h>
diff --git a/pkgs/jni/test/exception_test.dart b/pkgs/jni/test/exception_test.dart index 109a7e6..0e86ec9 100644 --- a/pkgs/jni/test/exception_test.dart +++ b/pkgs/jni/test/exception_test.dart
@@ -1,3 +1,7 @@ +// 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 'package:test/test.dart';
diff --git a/pkgs/jni/test/jni_object_test.dart b/pkgs/jni/test/jni_object_test.dart index 6124d1f..d234101 100644 --- a/pkgs/jni/test/jni_object_test.dart +++ b/pkgs/jni/test/jni_object_test.dart
@@ -1,3 +1,7 @@ +// 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 'dart:ffi'; import 'dart:isolate';
diff --git a/pkgs/jni/test/jni_test.dart b/pkgs/jni/test/jni_test.dart index 29f536b..ce31fc4 100644 --- a/pkgs/jni/test/jni_test.dart +++ b/pkgs/jni/test/jni_test.dart
@@ -1,3 +1,7 @@ +// 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 'dart:ffi';
diff --git a/pkgs/jni/tool/gen_aux_methods.dart b/pkgs/jni/tool/gen_aux_methods.dart index 0c7ae58..a30f54db32 100644 --- a/pkgs/jni/tool/gen_aux_methods.dart +++ b/pkgs/jni/tool/gen_aux_methods.dart
@@ -1,8 +1,16 @@ -/// Run from templates directory +// 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' as io; import 'package:path/path.dart'; +const _license = ''' +// 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. + +'''; final targetTypes = { "String": "String", "Object": "JniObject", @@ -53,6 +61,7 @@ sInvoke: join(outputDir, "direct_methods_generated.dart") }; for (final s in [sInst, sStatic, sInvoke]) { + s.write(_license); s.write("// Autogenerated; DO NOT EDIT\n" "// Generated by running the script in tool/gen_aux_methods.dart\n"); s.write("// coverage:ignore-file\n");
diff --git a/pkgs/jni_gen/README.md b/pkgs/jni_gen/README.md index 373127b..1ae735d 100644 --- a/pkgs/jni_gen/README.md +++ b/pkgs/jni_gen/README.md
@@ -5,3 +5,27 @@ This is a GSoC 2022 project. +Currently this package is highly experimental and proof-of-concept. See `test/jackson_core_test` for an example of generating bindings for a library. It is possible to specify some dependencies to be downloaded automatically through `maven`. + +## Basics +### Running `jni_gen` +There are 2 ways to use `jni_gen`: + +* Import `package:jni_gen/jni_gen.dart` from a script in `tool/` directory of your project. +* Run as command line tool with a YAML config. + +Both approaches are almost identical. If using YAML, it's possible to selectively override configuration properties with command line, using `-Dproperty.name=value` syntax. + +### Generated bindings +Generated bindings will consist of 2 parts - C bindings which call JNI, and Dart bindings which call C bindings. The generated bindings will depend on `package:jni` for instantiating / obtaining a JVM instance. + +The following properties must be specified in yaml. + +* `c_root`: root folder to write generated C bindings. +* `dart_root`: root folder to write generated Dart bindings (see below). +* `library_name`: specifies name of the generated library in CMakeFiles.txt. + +The generated C file has to be linked to JNI libraries. Therefore a CMake configuration is always generated which builds the generated code as shared library. The `init.dart` in generated dart code loads the library on first time a method is accessed. On dart standalone, it will be loaded from the same directory specified in `Jni.spawn` call. + +## Examples +See [jackson_core_test](test/jackson_core_test) folder for an example how bindings are generated. Runnable examples will be added soon.
diff --git a/pkgs/jni_gen/bin/jni_gen.dart b/pkgs/jni_gen/bin/jni_gen.dart new file mode 100644 index 0000000..855ae6e --- /dev/null +++ b/pkgs/jni_gen/bin/jni_gen.dart
@@ -0,0 +1,10 @@ +// 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 'package:jni_gen/jni_gen.dart'; + +void main(List<String> args) async { + final config = Config.parseArgs(args); + await generateJniBindings(config); +}
diff --git a/pkgs/jni_gen/bin/setup.dart b/pkgs/jni_gen/bin/setup.dart index 656b5da..18e7a8d 100644 --- a/pkgs/jni_gen/bin/setup.dart +++ b/pkgs/jni_gen/bin/setup.dart
@@ -2,67 +2,20 @@ // 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. -// This script gets the java sources using the copy of this package, and builds -// ApiSummarizer jar using Maven. import 'dart:io'; -import 'package:path/path.dart'; - -import 'package:jni_gen/src/util/find_package.dart'; - -final toolPath = join('.', '.dart_tool', 'jni_gen'); -final mvnTargetDir = join(toolPath, 'target'); -final jarFile = join(toolPath, 'ApiSummarizer.jar'); -final targetJarFile = join(mvnTargetDir, 'ApiSummarizer.jar'); - -Future<void> buildApiSummarizer() async { - final pkg = await findPackageRoot('jni_gen'); - if (pkg == null) { - stderr.writeln('package jni_gen not found!'); - exitCode = 2; - return; - } - final pom = pkg.resolve('java/pom.xml'); - await Directory(toolPath).create(recursive: true); - final mvnProc = await Process.start( - 'mvn', - [ - '--batch-mode', - '--update-snapshots', - '-f', - pom.toFilePath(), - 'assembly:assembly' - ], - workingDirectory: toolPath, - mode: ProcessStartMode.inheritStdio); - await mvnProc.exitCode; - // move ApiSummarizer.jar from target to current directory - File(targetJarFile).renameSync(jarFile); - Directory(mvnTargetDir).deleteSync(recursive: true); -} +import 'package:jni_gen/src/tools/tools.dart'; void main(List<String> args) async { bool force = false; if (args.isNotEmpty) { if (args.length != 1 || args[0] != '-f') { stderr.writeln('usage: dart run jni_gen:setup [-f]'); - stderr.writeln('use -f option to rebuild ApiSummarizer jar ' - 'even if it already exists.'); + stderr.writeln('* -f\trebuild ApiSummarizer jar even if it already ' + 'exists.'); } else { force = true; } } - final jarExists = await File(jarFile).exists(); - final isJarStale = jarExists && - await isPackageModifiedAfter( - 'jni_gen', await File(jarFile).lastModified(), 'java/'); - if (isJarStale) { - stderr.writeln('Rebuilding ApiSummarizer component since sources ' - 'have changed. This might take some time.'); - } - if (!jarExists || isJarStale || force) { - await buildApiSummarizer(); - } else { - stderr.writeln('ApiSummarizer.jar exists. Skipping build..'); - } + buildSummarizerIfNotExists(force: force); }
diff --git a/pkgs/jni_gen/lib/jni_gen.dart b/pkgs/jni_gen/lib/jni_gen.dart index 0bc7895..33068f4 100644 --- a/pkgs/jni_gen/lib/jni_gen.dart +++ b/pkgs/jni_gen/lib/jni_gen.dart
@@ -9,4 +9,5 @@ export 'src/elements/elements.dart'; export 'src/config/config.dart'; -export 'src/writers/writers.dart'; +export 'src/config/filters.dart'; +export 'src/generate_bindings.dart';
diff --git a/pkgs/jni_gen/lib/src/bindings/c_bindings.dart b/pkgs/jni_gen/lib/src/bindings/c_bindings.dart index 71be9c1..f6b7ddf 100644 --- a/pkgs/jni_gen/lib/src/bindings/c_bindings.dart +++ b/pkgs/jni_gen/lib/src/bindings/c_bindings.dart
@@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; +import 'package:jni_gen/src/config/config.dart'; import 'common.dart'; @@ -28,8 +28,8 @@ String _cParamRename(String paramName) => _cTypeKeywords.contains(paramName) ? '${paramName}0' : paramName; - CBindingGenerator(this.options); - WrapperOptions options; + CBindingGenerator(this.config); + Config config; String generateBinding(ClassDecl c) { return _class(c);
diff --git a/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart b/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart index b994441..7a27303 100644 --- a/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart +++ b/pkgs/jni_gen/lib/src/bindings/dart_bindings.dart
@@ -5,7 +5,7 @@ import 'dart:io'; import 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; +import 'package:jni_gen/src/config/config.dart'; import 'package:jni_gen/src/util/rename_conflict.dart'; import 'symbol_resolver.dart'; @@ -29,8 +29,8 @@ static const String _jlObject = '${jni}JlObject'; - DartBindingsGenerator(this.options, this.resolver); - WrapperOptions options; + DartBindingsGenerator(this.config, this.resolver); + Config config; SymbolResolver resolver; String generateBinding(ClassDecl decl) { @@ -176,7 +176,7 @@ void writeAccessor({bool isSetter = false}) { final symPrefix = isSetter ? 'set' : 'get'; - final sym = '_$symPrefix$name'; + final sym = '_${symPrefix}_$name'; final ffiSig = dartSigForField(f, isSetter: isSetter, isFfiSig: true); final dartSig = dartSigForField(f, isSetter: isSetter, isFfiSig: false); s.write('${_indent}static final $sym = $_jlookup' @@ -300,6 +300,7 @@ String _literal(dynamic value) { if (value is String) { + // TODO(#31): escape string literal. return '"$value"'; } if (value is int || value is double || value is bool) {
diff --git a/pkgs/jni_gen/lib/src/bindings/preprocessor.dart b/pkgs/jni_gen/lib/src/bindings/preprocessor.dart index 6fd1ee3..2177123 100644 --- a/pkgs/jni_gen/lib/src/bindings/preprocessor.dart +++ b/pkgs/jni_gen/lib/src/bindings/preprocessor.dart
@@ -5,25 +5,22 @@ import 'dart:io'; import 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; +import 'package:jni_gen/src/config/config.dart'; import 'package:jni_gen/src/util/rename_conflict.dart'; import 'common.dart'; /// Preprocessor which fills information needed by both Dart and C generators. class ApiPreprocessor { - ApiPreprocessor(this.classes, this.options); - final Map<String, ClassDecl> classes; - final WrapperOptions options; - - void preprocessAll() { + static void preprocessAll(Map<String, ClassDecl> classes, Config config) { for (var c in classes.values) { - _preprocess(c); + _preprocess(c, classes, config); } } - void _preprocess(ClassDecl decl) { + static void _preprocess( + ClassDecl decl, Map<String, ClassDecl> classes, Config config) { if (decl.isPreprocessed) return; - if (!_isClassIncluded(decl)) { + if (!_isClassIncluded(decl, config)) { decl.isIncluded = false; stdout.writeln('exclude class ${decl.binaryName}'); decl.isPreprocessed = true; @@ -32,7 +29,7 @@ ClassDecl? superclass; if (decl.superclass != null && classes.containsKey(decl.superclass?.name)) { superclass = classes[decl.superclass!.name]!; - _preprocess(superclass); + _preprocess(superclass, classes, config); // again, un-consider superclass if it was excluded through config if (!superclass.isIncluded) { superclass = null; @@ -42,7 +39,7 @@ } for (var field in decl.fields) { - if (!_isFieldIncluded(decl, field)) { + if (!_isFieldIncluded(decl, field, config)) { field.isIncluded = false; stderr.writeln('exclude ${decl.binaryName}#${field.name}'); continue; @@ -51,7 +48,7 @@ } for (var method in decl.methods) { - if (!_isMethodIncluded(decl, method)) { + if (!_isMethodIncluded(decl, method, config)) { method.isIncluded = false; stderr.writeln('exclude method ${decl.binaryName}#${method.name}'); continue; @@ -81,10 +78,12 @@ decl.isPreprocessed = true; } - bool _isFieldIncluded(ClassDecl decl, Field field) => - options.fieldFilter?.included(decl, field) != false; - bool _isMethodIncluded(ClassDecl decl, Method method) => - options.methodFilter?.included(decl, method) != false; - bool _isClassIncluded(ClassDecl decl) => - options.classFilter?.included(decl) != false; + static bool _isFieldIncluded(ClassDecl decl, Field field, Config config) => + !field.name.startsWith('_') && + config.exclude?.fields?.included(decl, field) != false; + static bool _isMethodIncluded(ClassDecl decl, Method method, Config config) => + !method.name.startsWith('_') && + config.exclude?.methods?.included(decl, method) != false; + static bool _isClassIncluded(ClassDecl decl, Config config) => + config.exclude?.classes?.included(decl) != false; }
diff --git a/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart b/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart index bfaaab4..43b928f 100644 --- a/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart +++ b/pkgs/jni_gen/lib/src/bindings/symbol_resolver.dart
@@ -14,8 +14,6 @@ List<String> getImportStrings(); } -// TODO(#24): resolve all included classes without requiring import mappings. - class PackagePathResolver implements SymbolResolver { PackagePathResolver(this.packages, this.currentPackage, this.inputClassNames, {this.predefined = const {}});
diff --git a/pkgs/jni_gen/lib/src/config/config.dart b/pkgs/jni_gen/lib/src/config/config.dart index 03ce6f2..a0272f3 100644 --- a/pkgs/jni_gen/lib/src/config/config.dart +++ b/pkgs/jni_gen/lib/src/config/config.dart
@@ -2,7 +2,288 @@ // 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. -export 'summary_source.dart'; -export 'task.dart'; -export 'wrapper_options.dart'; -export 'errors.dart'; +import 'dart:io'; + +import 'package:jni_gen/src/elements/elements.dart'; + +import 'yaml_reader.dart'; +import 'filters.dart'; + +/// Configuration for dependencies to be downloaded using maven. +/// +/// Dependency names should be listed in groupId:artifactId:version format. +/// For [sourceDeps], sources will be unpacked to [sourceDir] root and JAR files +/// will also be downloaded. For the packages in jarOnlyDeps, only JAR files +/// will be downloaded. +/// +/// When passed as a parameter to [Config], the downloaded sources and +/// JAR files will be automatically added to source path and class path +/// respectively. +class MavenDownloads { + static const defaultMavenSourceDir = 'mvn_java'; + static const defaultMavenJarDir = 'mvn_jar'; + + MavenDownloads({ + this.sourceDeps = const [], + // ASK: Should this be changed to a gitignore'd directory like build ? + this.sourceDir = defaultMavenSourceDir, + this.jarOnlyDeps = const [], + this.jarDir = defaultMavenJarDir, + }); + List<String> sourceDeps; + String sourceDir; + List<String> jarOnlyDeps; + String jarDir; +} + +/// Configuration for Android SDK sources and stub JAR files. +/// +/// The SDK directories for platform stub JARs and sources are searched in the +/// same order in which [versions] are specified. +/// +/// If [sdkRoot] is not provided, an attempt is made to discover it +/// using the environment variable `ANDROID_SDK_ROOT`, which will fail if the +/// environment variable is not set. +/// +/// If [includeSources] is true, `jni_gen` searches for Android SDK sources +/// as well in the SDK directory and adds them to the source path. +class AndroidSdkConfig { + AndroidSdkConfig( + {required this.versions, this.sdkRoot, this.includeSources = false}); + List<int> versions; + String? sdkRoot; + bool includeSources; +} + +/// Additional options to pass to the summary generator component. +class SummarizerOptions { + SummarizerOptions( + {this.extraArgs = const [], this.workingDirectory, this.backend}); + List<String> extraArgs; + Uri? workingDirectory; + String? backend; +} + +/// Backend for reading summary of Java libraries +enum SummarizerBackend { + /// Generate Java API summaries using JARs in provided `classPath`s. + asm, + + /// Generate Java API summaries using source files in provided `sourcePath`s. + doclet, +} + +class BindingExclusions { + BindingExclusions({this.methods, this.fields, this.classes}); + MethodFilter? methods; + FieldFilter? fields; + ClassFilter? classes; +} + +/// Configuration for jni_gen binding generation. +class Config { + Config({ + required this.classes, + required this.libraryName, + required this.cRoot, + required this.dartRoot, + this.exclude, + this.sourcePath, + this.classPath, + this.preamble, + this.importMap, + this.androidSdkConfig, + this.mavenDownloads, + this.summarizerOptions, + this.dumpJsonTo, + }); + + /// List of classes or packages for which bindings have to be generated. + /// + /// The names must be fully qualified, and it's assumed that the directory + /// structure corresponds to package naming. For example, com.abc.MyClass + /// should be resolvable as `com/abc/MyClass.java` from one of the provided + /// source paths. Same applies if ASM backend is used, except that the file + /// name suffix is `.class`. + List<String> classes; + + /// Name of generated library in CMakeLists.txt configuration. + /// + /// This will also determine the name of shared object file. + String libraryName; + + /// Directory to write JNI C Bindings. + Uri cRoot; + + /// Directory to write Dart bindings. + Uri dartRoot; + + /// Methods and fields to be excluded from generated bindings. + BindingExclusions? exclude; + + /// Paths to search for java source files. + /// + /// If a source package is downloaded through [mavenDownloads] option, + /// the corresponding source folder is automatically added and does not + /// need to be explicitly specified. + List<Uri>? sourcePath; + + /// class path for scanning java libraries. If [backend] is `asm`, the + /// specified classpath is used to search for [classes], otherwise it's + /// merely used by the doclet API to find transitively referenced classes, + /// but not the specified classes / packages themselves. + List<Uri>? classPath; + + /// Common text to be pasted on top of generated C and Dart files. + String? preamble; + + /// Additional java package -> dart package mappings (Experimental). + Map<String, String>? importMap; + + /// Configuration to search for Android SDK libraries (Experimental). + AndroidSdkConfig? androidSdkConfig; + + /// Configuration for auto-downloading JAR / source packages using maven, + /// along with their transitive dependencies. + MavenDownloads? mavenDownloads; + + /// Additional options for the summarizer component + SummarizerOptions? summarizerOptions; + + String? dumpJsonTo; + + static Uri? _toDirUri(String? path) => + path != null ? Uri.directory(path) : null; + static List<Uri>? _toUris(List<String>? paths) => + paths?.map(Uri.file).toList(); + + static Config parseArgs(List<String> args) { + final prov = YamlReader.parseArgs(args); + + final List<String> missingValues = []; + T must<T>(T? Function(String) f, T ifNull, String property) { + final res = f(property); + if (res == null) { + missingValues.add(property); + return ifNull; + } + return res; + } + + MemberFilter<T>? regexFilter<T extends ClassMember>(String property) { + final exclusions = prov.getStringList(property); + if (exclusions == null) return null; + final List<MemberFilter<T>> filters = []; + for (var exclusion in exclusions) { + final split = exclusion.split('#'); + if (split.length != 2) { + throw FormatException('Error parsing exclusion: "$exclusion"; ' + 'expected to be in binaryName#member format.'); + } + filters.add(MemberNameFilter<T>.exclude( + RegExp(split[0]), + RegExp(split[1]), + )); + } + return CombinedMemberFilter<T>(filters); + } + + String getSdkRoot() { + final root = prov.getString(_Props.androidSdkRoot) ?? + Platform.environment['ANDROID_SDK_ROOT']; + if (root == null) { + missingValues.add(_Props.androidSdkRoot); + return '?'; + } + return root; + } + + final config = Config( + sourcePath: _toUris(prov.getStringList(_Props.sourcePath)), + classPath: _toUris(prov.getStringList(_Props.classPath)), + classes: must(prov.getStringList, [], _Props.classes), + summarizerOptions: SummarizerOptions( + extraArgs: prov.getStringList(_Props.summarizerArgs) ?? const [], + backend: prov.getString(_Props.backend), + workingDirectory: + _toDirUri(prov.getString(_Props.summarizerWorkingDir)), + ), + exclude: BindingExclusions( + methods: regexFilter<Method>(_Props.excludeMethods), + fields: regexFilter<Field>(_Props.excludeFields), + ), + cRoot: Uri.directory(must(prov.getString, '', _Props.cRoot)), + dartRoot: Uri.directory(must(prov.getString, '', _Props.dartRoot)), + preamble: prov.getString(_Props.preamble), + libraryName: must(prov.getString, '', _Props.libraryName), + importMap: prov.getStringMap(_Props.importMap), + mavenDownloads: prov.hasValue(_Props.mavenDownloads) + ? MavenDownloads( + sourceDeps: prov.getStringList(_Props.sourceDeps) ?? const [], + sourceDir: prov.getString(_Props.mavenSourceDir) ?? + MavenDownloads.defaultMavenSourceDir, + jarOnlyDeps: prov.getStringList(_Props.jarOnlyDeps) ?? const [], + jarDir: prov.getString(_Props.mavenJarDir) ?? + MavenDownloads.defaultMavenJarDir, + ) + : null, + androidSdkConfig: prov.hasValue(_Props.androidSdkConfig) + ? AndroidSdkConfig( + versions: must<List<String>>( + prov.getStringList, [], _Props.androidSdkVersions) + .map(int.parse) + .toList(), + sdkRoot: getSdkRoot(), + includeSources: + prov.getBool(_Props.includeAndroidSources) ?? false, + ) + : null, + ); + if (missingValues.isNotEmpty) { + stderr.write('Following config values are required but not provided\n' + 'Please provide these properties through YAML ' + 'or use the command line switch -D<property_name>=<value>.\n'); + for (var missing in missingValues) { + stderr.writeln('* $missing'); + } + if (missingValues.contains(_Props.androidSdkRoot)) { + stderr.writeln('Please specify ${_Props.androidSdkRoot} through ' + 'command line or ensure that the ANDROID_SDK_ROOT environment ' + 'variable is set.'); + } + exit(1); + } + return config; + } +} + +class _Props { + static const summarizer = 'summarizer'; + static const summarizerArgs = '$summarizer.extra_args'; + static const summarizerWorkingDir = '$summarizer.working_dir'; + static const backend = '$summarizer.backend'; + + static const sourcePath = 'source_path'; + static const classPath = 'class_path'; + static const classes = 'classes'; + static const exclude = 'exclude'; + static const excludeMethods = '$exclude.methods'; + static const excludeFields = '$exclude.fields'; + + static const importMap = 'import_map'; + static const cRoot = 'c_root'; + static const dartRoot = 'dart_root'; + static const preamble = 'preamble'; + static const libraryName = 'library_name'; + + static const mavenDownloads = 'maven_downloads'; + static const sourceDeps = '$mavenDownloads.source_deps'; + static const mavenSourceDir = '$mavenDownloads.source_dir'; + static const jarOnlyDeps = '$mavenDownloads.jar_only_deps'; + static const mavenJarDir = '$mavenDownloads.jar_dir'; + + static const androidSdkConfig = 'android_sdk_config'; + static const androidSdkRoot = '$androidSdkConfig.sdk_root'; + static const androidSdkVersions = '$androidSdkConfig.versions'; + static const includeAndroidSources = '$androidSdkConfig.include_sources'; +}
diff --git a/pkgs/jni_gen/lib/src/config/wrapper_options.dart b/pkgs/jni_gen/lib/src/config/filters.dart similarity index 76% rename from pkgs/jni_gen/lib/src/config/wrapper_options.dart rename to pkgs/jni_gen/lib/src/config/filters.dart index 7f1101d..66f1612 100644 --- a/pkgs/jni_gen/lib/src/config/wrapper_options.dart +++ b/pkgs/jni_gen/lib/src/config/filters.dart
@@ -4,6 +4,11 @@ import 'package:jni_gen/src/elements/elements.dart'; +bool _matchesCompletely(String string, Pattern pattern) { + final match = pattern.matchAsPrefix(string); + return match != null && match.group(0) == string; +} + /// A filter which tells if bindings for given [ClassDecl] are generated. abstract class ClassFilter { bool included(ClassDecl decl); @@ -19,11 +24,6 @@ } } -bool _matchesCompletely(String string, Pattern pattern) { - final match = pattern.matchAsPrefix(string); - return match != null && match.group(0) == string; -} - /// Filter to include / exclude classes by matching on the binary name. /// A binary name is like qualified name but with a `$` used to indicate nested /// class instead of `.`, guaranteeing a unique name. @@ -45,6 +45,7 @@ bool included(ClassDecl classDecl, T member); } +/// Filter that excludes or includes members based on class and member name. class MemberNameFilter<T extends ClassMember> implements MemberFilter<T> { MemberNameFilter.include(this.classPattern, this.namePattern) : onMatch = true; @@ -60,6 +61,7 @@ } } +/// Filter that includes or excludes a member based on a custom callback. class CustomMemberFilter<T extends ClassMember> implements MemberFilter<T> { CustomMemberFilter(this.predicate); bool Function(ClassDecl, T) predicate; @@ -67,6 +69,7 @@ bool included(ClassDecl classDecl, T member) => predicate(classDecl, member); } +/// Filter which excludes classes excluded by any one filter in [filters]. class CombinedClassFilter implements ClassFilter { CombinedClassFilter.all(this.filters); final List<ClassFilter> filters; @@ -74,6 +77,7 @@ bool included(ClassDecl decl) => filters.every((f) => f.included(decl)); } +/// Filter which excludes members excluded by any one filter in [filters]. class CombinedMemberFilter<T extends ClassMember> implements MemberFilter<T> { CombinedMemberFilter(this.filters); @@ -112,37 +116,3 @@ return CombinedMemberFilter<T>( names.map((p) => MemberNameFilter<T>.exclude(p[0], p[1])).toList()); } - -/// Options that affect the semantics of the generated code. -class WrapperOptions { - const WrapperOptions({ - this.classFilter, - this.fieldFilter, - this.methodFilter, - this.classTransformer, - this.methodTransformer, - this.fieldTransformer, - this.importPaths = const {}, - }); - - /// Mapping from java package names to dart packages. - /// A mapping `a.b` -> `package:a_b/' means that - /// any import `a.b.C` will be resolved as `package:a_b/a/b.dart` in dart. - /// Note that dart bindings use the same hierarchy as the java packages. - final Map<String, String> importPaths; - - /// [ClassFilter] to decide if bindings for a class should be generated. - final ClassFilter? classFilter; - - /// [FieldFilter] to decide if bindings for a field should be generated. - final FieldFilter? fieldFilter; - - /// [MethodFilter] to decide if bindings for a method should be generated. - final MethodFilter? methodFilter; - - // TODO(#26): This allows us to implement flexible renaming and more customization - // via the dart API. - final ClassDecl? Function(ClassDecl decl)? classTransformer; - final Method? Function(Method method)? methodTransformer; - final Field? Function(Field field)? fieldTransformer; -}
diff --git a/pkgs/jni_gen/lib/src/config/task.dart b/pkgs/jni_gen/lib/src/config/task.dart deleted file mode 100644 index a5e015d..0000000 --- a/pkgs/jni_gen/lib/src/config/task.dart +++ /dev/null
@@ -1,59 +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 'dart:convert'; - -import 'package:jni_gen/src/writers/bindings_writer.dart'; -import 'package:jni_gen/src/config/config.dart'; -import 'package:jni_gen/src/elements/elements.dart'; - -/// Represents a complete jni_gen binding generation configuration. -/// * [summarySource] handles the API summary generation. -/// * [options] specify any semantic options regarding generated code. -/// * [outputWriter] handles the output configuration. -class JniGenTask { - JniGenTask({ - required this.summarySource, - this.options = const WrapperOptions(), - required this.outputWriter, - }); - BindingsWriter outputWriter; - SummarySource summarySource; - WrapperOptions options; - - // execute this task - Future<void> run({bool dumpJson = false}) async { - Stream<List<int>> input; - try { - input = await summarySource.getInputStream(); - } on Exception catch (e) { - stderr.writeln('error obtaining API summary: $e'); - return; - } - final stream = JsonDecoder().bind(Utf8Decoder().bind(input)); - dynamic json; - try { - json = await stream.single; - } on Exception catch (e) { - stderr.writeln('error while parsing summary: $e'); - return; - } - if (json == null) { - stderr.writeln('error: expected JSON element from summarizer.'); - return; - } - if (dumpJson) { - stderr.writeln(json); - } - final list = json as List; - try { - await outputWriter.writeBindings( - list.map((c) => ClassDecl.fromJson(c)), options); - } on Exception catch (e, trace) { - stderr.writeln(trace); - stderr.writeln('error writing bindings: $e'); - } - } -}
diff --git a/pkgs/jni_gen/lib/src/config/yaml_reader.dart b/pkgs/jni_gen/lib/src/config/yaml_reader.dart new file mode 100644 index 0000000..3d12dae --- /dev/null +++ b/pkgs/jni_gen/lib/src/config/yaml_reader.dart
@@ -0,0 +1,141 @@ +// 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 'package:args/args.dart'; +import 'package:yaml/yaml.dart'; + +class ConfigError extends Error { + ConfigError(this.message); + final String message; + @override + String toString() => message; +} + +/// YAML Reader which enables to override specific values from command line. +class YamlReader { + YamlReader.of(this.cli, this.yaml); + YamlReader.fromYaml(this.yaml) : cli = const {}; + YamlReader.fromMap(this.cli) : yaml = const {}; + Map<String, String> cli; + Map<dynamic, dynamic> yaml; + + /// Parses the provided command line arguments and returns a [YamlReader]. + /// + /// This is a utility function which does all things a program would do when + /// parsing command line arguments, including exiting from the program when + /// arguments are invalid. + static YamlReader parseArgs(List<String> args, + {bool allowYamlConfig = true}) { + final parser = ArgParser(); + parser.addFlag('help', abbr: 'h', help: 'Show this help.'); + + // Sometimes it's required to change a config value for a single invocation, + // then this option can be used. Conventionally in -D switch is used in + // C to set preprocessor variable & in java to override a config property. + + parser.addMultiOption('override', + abbr: 'D', + help: 'Override or assign a config property from command line.'); + if (allowYamlConfig) { + parser.addOption('config', abbr: 'c', help: 'Path to YAML config.'); + } + + final results = parser.parse(args); + if (results['help']) { + stderr.writeln(parser.usage); + exit(1); + } + final configFile = results['config'] as String?; + Map<dynamic, dynamic> yamlMap = {}; + if (configFile != null) { + try { + final yamlInput = loadYaml(File(configFile).readAsStringSync(), + sourceUrl: Uri.file(configFile)); + if (yamlInput is Map) { + yamlMap = yamlInput; + } else { + throw ConfigError('YAML config must be set of key value pairs'); + } + } on Exception catch (e) { + stderr.writeln('cannot read $configFile: $e'); + } + } + final regex = RegExp('([a-z-_.]+)=(.*)'); + final properties = <String, String>{}; + for (var prop in results['override']) { + final match = regex.matchAsPrefix(prop as String); + if (match != null && match.group(0) == prop) { + final propertyName = match.group(1); + final propertyValue = match.group(2); + properties[propertyName!] = propertyValue!; + } else { + throw ConfigError('override does not match expected pattern'); + } + } + return YamlReader.of(properties, yamlMap); + } + + bool? getBool(String property) { + if (cli.containsKey(property)) { + final v = cli[property]!; + if (v == 'true') { + return true; + } + if (v == 'false') { + return false; + } + throw ConfigError('expected boolean value for $property, got $v'); + } + return null; + } + + String? getString(String property) { + final configValue = cli[property] ?? getYamlValue<String>(property); + return configValue; + } + + List<String>? getStringList(String property) { + final configValue = cli[property]?.split(',') ?? + getYamlValue<YamlList>(property)?.cast<String>(); + return configValue; + } + + String? getOneOf(String property, Set<String> values) { + final value = cli[property] ?? getYamlValue<String>(property); + if (value == null || values.contains(value)) { + return value; + } + throw ConfigError('expected one of $values for $property'); + } + + Map<String, String>? getStringMap(String property) { + final value = getYamlValue<YamlMap>(property); + return value?.cast<String, String>(); + } + + bool hasValue(String property) => getYamlValue<dynamic>(property) != null; + + T? getYamlValue<T>(String property) { + final path = property.split('.'); + dynamic cursor = yaml; + String current = ''; + for (var i in path) { + if (cursor is YamlMap || cursor is Map) { + cursor = cursor[i]; + } else { + throw ConfigError('expected $current to be a YAML map'); + } + current = [if (current != '') current, i].join('.'); + if (cursor == null) { + return null; + } + } + if (cursor is! T) { + throw ConfigError('expected $T for $property, got ${cursor.runtimeType}'); + } + return cursor; + } +}
diff --git a/pkgs/jni_gen/lib/src/generate_bindings.dart b/pkgs/jni_gen/lib/src/generate_bindings.dart new file mode 100644 index 0000000..b939f9c --- /dev/null +++ b/pkgs/jni_gen/lib/src/generate_bindings.dart
@@ -0,0 +1,92 @@ +// 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 'dart:convert'; + +import 'elements/elements.dart'; +import 'summary/summary.dart'; +import 'config/config.dart'; +import 'tools/tools.dart'; +import 'writers/writers.dart'; + +Future<void> generateJniBindings(Config config) async { + await buildSummarizerIfNotExists(); + + final summarizer = SummarizerCommand( + sourcePath: config.sourcePath, + classPath: config.classPath, + classes: config.classes, + workingDirectory: config.summarizerOptions?.workingDirectory, + extraArgs: config.summarizerOptions?.extraArgs ?? const [], + backend: config.summarizerOptions?.backend, + ); + + final extraSources = <Uri>[]; + final extraJars = <Uri>[]; + final mavenDl = config.mavenDownloads; + if (mavenDl != null) { + final sourcePath = mavenDl.sourceDir; + Directory(sourcePath).create(recursive: true); + await MavenTools.downloadMavenSources( + MavenTools.deps(mavenDl.sourceDeps), sourcePath); + extraSources.add(Uri.directory(sourcePath)); + final jarPath = mavenDl.jarDir; + Directory(jarPath).create(recursive: true); + await MavenTools.downloadMavenJars( + MavenTools.deps(mavenDl.sourceDeps + mavenDl.jarOnlyDeps), jarPath); + extraJars.addAll(await Directory(jarPath) + .list() + .where((entry) => entry.path.endsWith('.jar')) + .map((entry) => entry.uri) + .toList()); + } + + final androidConfig = config.androidSdkConfig; + if (androidConfig != null) { + final androidJar = await AndroidSdkTools.getAndroidJarPath( + sdkRoot: androidConfig.sdkRoot, versionOrder: androidConfig.versions); + if (androidJar != null) { + extraJars.add(Uri.directory(androidJar)); + } + if (androidConfig.includeSources) { + final androidSources = await AndroidSdkTools.getAndroidSourcesPath( + sdkRoot: androidConfig.sdkRoot, versionOrder: androidConfig.versions); + if (androidSources != null) { + extraSources.add(Uri.directory(androidSources)); + } + } + } + + summarizer.addSourcePaths(extraSources); + summarizer.addClassPaths(extraJars); + + Stream<List<int>> input; + try { + input = await summarizer.getInputStream(); + } on Exception catch (e) { + stderr.writeln('error obtaining API summary: $e'); + return; + } + final stream = JsonDecoder().bind(Utf8Decoder().bind(input)); + dynamic json; + try { + json = await stream.single; + } on Exception catch (e) { + stderr.writeln('error while parsing summary: $e'); + return; + } + if (json == null) { + stderr.writeln('error: expected JSON element from summarizer.'); + return; + } + final list = json as List; + final outputWriter = FilesWriter(config); + try { + await outputWriter.writeBindings(list.map((c) => ClassDecl.fromJson(c))); + } on Exception catch (e, trace) { + stderr.writeln(trace); + stderr.writeln('error writing bindings: $e'); + } +}
diff --git a/pkgs/jni_gen/lib/src/config/summary_source.dart b/pkgs/jni_gen/lib/src/summary/summary.dart similarity index 80% rename from pkgs/jni_gen/lib/src/config/summary_source.dart rename to pkgs/jni_gen/lib/src/summary/summary.dart index 622c36b..a259f27 100644 --- a/pkgs/jni_gen/lib/src/config/summary_source.dart +++ b/pkgs/jni_gen/lib/src/summary/summary.dart
@@ -5,10 +5,6 @@ import 'dart:io'; import 'package:jni_gen/src/util/command_output.dart'; -abstract class SummarySource { - Future<Stream<List<int>>> getInputStream(); -} - /// A command based summary source which calls the ApiSummarizer command. /// [sourcePaths] and [classPaths] can be provided for the summarizer to find /// required dependencies. The [classes] argument specifies the fully qualified @@ -21,25 +17,41 @@ /// /// The default summarizer needs to be built with `jni_gen:setup` /// script before this API is used. -class SummarizerCommand extends SummarySource { +class SummarizerCommand { SummarizerCommand({ this.command = "java -jar .dart_tool/jni_gen/ApiSummarizer.jar", - required this.sourcePaths, - this.classPaths = const [], + List<Uri>? sourcePath, + List<Uri>? classPath, this.extraArgs = const [], required this.classes, this.workingDirectory, - }); + this.backend, + }) : sourcePaths = sourcePath ?? [], + classPaths = classPath ?? [] { + if (backend != null && !{'asm', 'doclet'}.contains(backend)) { + throw ArgumentError('Supported backends: asm, doclet'); + } + } static const sourcePathsOption = '-s'; static const classPathsOption = '-c'; String command; List<Uri> sourcePaths, classPaths; + List<String> extraArgs; List<String> classes; Uri? workingDirectory; + String? backend; + + void addSourcePaths(List<Uri> paths) { + sourcePaths.addAll(paths); + } + + void addClassPaths(List<Uri> paths) { + classPaths.addAll(paths); + } void _addPathParam(List<String> args, String option, List<Uri> paths) { if (paths.isNotEmpty) { @@ -54,7 +66,6 @@ } } - @override Future<Stream<List<int>>> getInputStream() async { final commandSplit = command.split(" "); final exec = commandSplit[0]; @@ -62,6 +73,9 @@ _addPathParam(args, sourcePathsOption, sourcePaths); _addPathParam(args, classPathsOption, classPaths); + if (backend != null) { + args.addAll(['--backend', backend!]); + } args.addAll(extraArgs); args.addAll(classes); @@ -73,15 +87,3 @@ return proc.stdout; } } - -/// A JSON file based summary source. -// (Did not test it yet) -class SummaryFile extends SummarySource { - Uri path; - SummaryFile(this.path); - SummaryFile.fromPath(String path) : path = Uri.file(path); - - @override - Future<Stream<List<int>>> getInputStream() async => - File.fromUri(path).openRead(); -}
diff --git a/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart new file mode 100644 index 0000000..e1092b2 --- /dev/null +++ b/pkgs/jni_gen/lib/src/tools/android_sdk_tools.dart
@@ -0,0 +1,45 @@ +// 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 'package:path/path.dart'; + +class AndroidSdkTools { + /// get path for android API sources + static Future<String?> _getVersionDir( + String relative, String? sdkRoot, List<int> versionOrder) async { + sdkRoot ??= Platform.environment['ANDROID_SDK_ROOT']; + if (sdkRoot == null) { + throw ArgumentError('SDK Root not provided and ANDROID_SDK_ROOT not set'); + } + final parent = join(sdkRoot, relative); + for (var version in versionOrder) { + final dir = Directory(join(parent, 'android-$version')); + if (await dir.exists()) { + return dir.path; + } + } + return null; + } + + static Future<String?> getAndroidSourcesPath( + {String? sdkRoot, required List<int> versionOrder}) async { + return _getVersionDir('sources', sdkRoot, versionOrder); + } + + static Future<String?> _getFile(String relative, String file, String? sdkRoot, + List<int> versionOrder) async { + final platform = await _getVersionDir(relative, sdkRoot, versionOrder); + if (platform == null) return null; + final filePath = join(platform, file); + if (await File(filePath).exists()) { + return filePath; + } + return null; + } + + static Future<String?> getAndroidJarPath( + {String? sdkRoot, required List<int> versionOrder}) async => + await _getFile('platforms', 'android.jar', sdkRoot, versionOrder); +}
diff --git a/pkgs/jni_gen/lib/src/tools/build_summarizer.dart b/pkgs/jni_gen/lib/src/tools/build_summarizer.dart new file mode 100644 index 0000000..46847fb --- /dev/null +++ b/pkgs/jni_gen/lib/src/tools/build_summarizer.dart
@@ -0,0 +1,60 @@ +// 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 'package:path/path.dart'; + +import 'package:jni_gen/src/util/find_package.dart'; + +final toolPath = join('.', '.dart_tool', 'jni_gen'); +final mvnTargetDir = join(toolPath, 'target'); +final jarFile = join(toolPath, 'ApiSummarizer.jar'); +final targetJarFile = join(mvnTargetDir, 'ApiSummarizer.jar'); + +Future<void> buildApiSummarizer() async { + final pkg = await findPackageRoot('jni_gen'); + if (pkg == null) { + stderr.writeln('package jni_gen not found!'); + exitCode = 2; + return; + } + final pom = pkg.resolve('java/pom.xml'); + await Directory(toolPath).create(recursive: true); + final mvnProc = await Process.start( + 'mvn', + [ + '--batch-mode', + '--update-snapshots', + '-f', + pom.toFilePath(), + 'assembly:assembly' + ], + workingDirectory: toolPath, + mode: ProcessStartMode.inheritStdio); + await mvnProc.exitCode; + File(targetJarFile).renameSync(jarFile); + Directory(mvnTargetDir).deleteSync(recursive: true); +} + +Future<void> buildSummarizerIfNotExists({bool force = false}) async { + final jarExists = await File(jarFile).exists(); + final isJarStale = jarExists && + await isPackageModifiedAfter( + 'jni_gen', await File(jarFile).lastModified(), 'java/'); + if (isJarStale) { + stderr.writeln('Rebuilding ApiSummarizer component since sources ' + 'have changed. This might take some time.'); + } + if (!jarExists) { + stderr.write('Building ApiSummarizer component. ' + 'This might take some time. \n' + 'The build will be cached for subsequent runs\n'); + } + if (!jarExists || isJarStale || force) { + await buildApiSummarizer(); + } else { + stderr.writeln('ApiSummarizer.jar exists. Skipping build..'); + } +}
diff --git a/pkgs/jni_gen/lib/src/tools/maven_tools.dart b/pkgs/jni_gen/lib/src/tools/maven_tools.dart new file mode 100644 index 0000000..52835d9 --- /dev/null +++ b/pkgs/jni_gen/lib/src/tools/maven_tools.dart
@@ -0,0 +1,137 @@ +// 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'; + +/// This class provides some utility methods to download a sources / jars +/// using maven along with transitive dependencies. +class MavenTools { + static const _tempPom = '__temp_pom.xml'; + static const _tempClassPath = '__temp_classpath.xml'; + static const _tempTarget = '__mvn_target'; + + static bool _verbose = false; + static void setVerbose(bool enabled) => _verbose = enabled; + + static void _verboseLog(Object? value) { + if (_verbose) { + stderr.writeln(value); + } + } + + /// Helper method since we can't pass inheritStdio option to [Process.run]. + static Future<int> _runCmd(String exec, List<String> args, + [String? workingDirectory]) async { + _verboseLog('[exec] $exec ${args.join(" ")}'); + final proc = await Process.start(exec, args, + workingDirectory: workingDirectory, + mode: ProcessStartMode.inheritStdio); + return proc.exitCode; + } + + static Future<void> _runMavenCommand( + List<MavenDependency> deps, List<String> mvnArgs) async { + final pom = _getStubPom(deps); + _verboseLog('using POM stub:\n$pom'); + await File(_tempPom).writeAsString(pom); + await Directory(_tempTarget).create(); + await _runCmd('mvn', ['-f', _tempPom, ...mvnArgs]); + await File(_tempPom).delete(); + await Directory(_tempTarget).delete(recursive: true); + } + + /// Create a list of [MavenDependency] objects from maven coordinates in string form. + static List<MavenDependency> deps(List<String> depNames) => + depNames.map(MavenDependency.fromString).toList(); + + /// Downloads and unpacks source files of [deps] into [targetDir]. + static Future<void> downloadMavenSources( + List<MavenDependency> deps, String targetDir) async { + await _runMavenCommand(deps, [ + 'dependency:unpack-dependencies', + '-DexcludeTransitive=true', + '-DoutputDirectory=$targetDir', + '-Dclassifier=sources', + ]); + } + + /// Downloads JAR files of all [deps] transitively into [targetDir]. + static Future<void> downloadMavenJars( + List<MavenDependency> deps, String targetDir) async { + await _runMavenCommand(deps, [ + 'dependency:copy-dependencies', + '-DoutputDirectory=$targetDir', + ]); + } + + /// Get classpath string using JARs in maven's local repository. + static Future<String> getMavenClassPath(List<MavenDependency> deps) async { + await _runMavenCommand(deps, [ + 'dependency:build-classpath', + '-Dmdep.outputFile=$_tempClassPath', + ]); + final classPathFile = File(_tempClassPath); + final classpath = await classPathFile.readAsString(); + await classPathFile.delete(); + return classpath; + } + + static String _getStubPom(List<MavenDependency> deps, + {String javaVersion = '11'}) { + final depDecls = <String>[]; + for (var dep in deps) { + final otherTags = StringBuffer(); + for (var entry in dep.otherTags.entries) { + otherTags.write(''' + <${entry.key}> + ${entry.value} + </${entry.key}> + '''); + } + depDecls.add(''' + <dependency> + <groupId>${dep.groupID}</groupId> + <artifactId>${dep.artifactID}</artifactId> + <version>${dep.version}</version> + ${otherTags.toString()} + </dependency>'''); + } + + return ''' +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>com.mycompany.app</groupId> + <artifactId>jnigen_maven_stub</artifactId> + <version>1.0-SNAPSHOT</version> + <properties> + <maven.compiler.source>$javaVersion</maven.compiler.source> + <maven.compiler.target>$javaVersion</maven.compiler.target> + </properties> + <dependencies> +${depDecls.join("\n")} + </dependencies> + <build> + <directory>$_tempTarget</directory> + </build> +</project>'''; + } +} + +/// Maven dependency with group ID, artifact ID, and version. +class MavenDependency { + MavenDependency(this.groupID, this.artifactID, this.version, + {this.otherTags = const {}}); + factory MavenDependency.fromString(String fullName) { + final components = fullName.split(':'); + if (components.length != 3) { + throw ArgumentError('invalid name for maven dependency: $fullName'); + } + return MavenDependency(components[0], components[1], components[2]); + } + String groupID, artifactID, version; + Map<String, String> otherTags; +}
diff --git a/pkgs/jni_gen/lib/src/tools/maven_utils.dart b/pkgs/jni_gen/lib/src/tools/maven_utils.dart deleted file mode 100644 index 2c28657..0000000 --- a/pkgs/jni_gen/lib/src/tools/maven_utils.dart +++ /dev/null
@@ -1,134 +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'; - -/// This class provides some utility methods to download a sources / jars -/// using maven along with transitive dependencies. -class MvnTools { - static const _tempPom = '__temp_pom.xml'; - static const _tempClassPath = '__temp_classpath.xml'; - static const _tempTarget = '__mvn_target'; - - static bool _verbose = false; - static void setVerbose(bool enabled) => _verbose = enabled; - - static void _verboseLog(Object? value) { - if (_verbose) { - stderr.writeln(value); - } - } - - /// Helper method since we can't pass inheritStdio option to [Process.run]. - static Future<int> _runCmd(String exec, List<String> args, - [String? workingDirectory]) async { - _verboseLog('[exec] $exec ${args.join(" ")}'); - final proc = await Process.start(exec, args, - workingDirectory: workingDirectory, - mode: ProcessStartMode.inheritStdio); - return proc.exitCode; - } - - static Future<void> _runMavenCommand( - List<MvnDep> deps, List<String> mvnArgs) async { - final pom = _getStubPom(deps); - _verboseLog('using POM stub:\n$pom'); - await File(_tempPom).writeAsString(pom); - await Directory(_tempTarget).create(); - await _runCmd( - 'mvn', ['-f', _tempPom, '-DbuildDirectory=$_tempTarget', ...mvnArgs]); - await File(_tempPom).delete(); - await Directory(_tempTarget).delete(recursive: true); - } - - /// Create a list of [MvnDep] objects from maven coordinates in string form. - static List<MvnDep> makeDependencyList(List<String> depNames) => - depNames.map(MvnDep.fromString).toList(); - - /// Downloads and unpacks source files of [deps] into [targetDir]. - static Future<void> downloadMavenSources( - List<MvnDep> deps, String targetDir) async { - await _runMavenCommand(deps, [ - 'dependency:unpack-dependencies', - '-DoutputDirectory=$targetDir', - '-Dclassifier=sources' - ]); - } - - /// Downloads JAR files of all [deps] transitively into [targetDir]. - static Future<void> downloadMavenJars( - List<MvnDep> deps, String targetDir) async { - await _runMavenCommand(deps, [ - 'dependency:copy-dependencies', - '-DoutputDirectory=$targetDir', - ]); - } - - /// Get classpath string using JARs in maven's local repository. - static Future<String> getMavenClassPath(List<MvnDep> deps) async { - await _runMavenCommand(deps, [ - 'dependency:build-classpath', - '-Dmdep.outputFile=$_tempClassPath', - ]); - final classPathFile = File(_tempClassPath); - final classpath = await classPathFile.readAsString(); - await classPathFile.delete(); - return classpath; - } - - static String _getStubPom(List<MvnDep> deps, {String javaVersion = '11'}) { - final i2 = ' ' * 2; - final i4 = ' ' * 4; - final i6 = ' ' * 6; - final i8 = ' ' * 8; - final depDecls = <String>[]; - - for (var dep in deps) { - final otherTags = StringBuffer(); - for (var entry in dep.otherTags.entries) { - otherTags.write('$i6<${entry.key}>\n' - '$i8${entry.value}\n' - '$i6</${entry.key}>\n'); - } - depDecls.add('$i4<dependency>\n' - '$i6<groupId>${dep.groupID}</groupId>\n' - '$i6<artifactId>${dep.artifactID}</artifactId>\n' - '$i6<version>${dep.version}</version>\n' - '${otherTags.toString()}\n' - '$i4</dependency>\n'); - } - - return '<project xmlns="http://maven.apache.org/POM/4.0.0" ' - 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' - 'xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 ' - 'http://maven.apache.org/xsd/maven-4.0.0.xsd">\n' - '$i2<modelVersion>4.0.0</modelVersion>\n' - '$i2<groupId>com.mycompany.app</groupId>\n' - '$i2<artifactId>my-app</artifactId>\n' - '$i2<version>1.0-SNAPSHOT</version>\n' - '$i2<properties>\n' - '$i4<maven.compiler.source>$javaVersion</maven.compiler.source>\n' - '$i4<maven.compiler.target>$javaVersion</maven.compiler.target>\n' - '$i2</properties>\n' - '$i4<dependencies>\n' - '${depDecls.join("\n")}' - '$i2</dependencies>\n' - '</project>'; - } -} - -/// Maven dependency with group ID, artifact ID, and version. -class MvnDep { - MvnDep(this.groupID, this.artifactID, this.version, - {this.otherTags = const {}}); - factory MvnDep.fromString(String fullName) { - final components = fullName.split(':'); - if (components.length != 3) { - throw ArgumentError('invalid name for maven dependency: $fullName'); - } - return MvnDep(components[0], components[1], components[2]); - } - String groupID, artifactID, version; - Map<String, String> otherTags; -}
diff --git a/pkgs/jni_gen/lib/src/config/errors.dart b/pkgs/jni_gen/lib/src/tools/tools.dart similarity index 64% rename from pkgs/jni_gen/lib/src/config/errors.dart rename to pkgs/jni_gen/lib/src/tools/tools.dart index 516e8f8..9677a3e 100644 --- a/pkgs/jni_gen/lib/src/config/errors.dart +++ b/pkgs/jni_gen/lib/src/tools/tools.dart
@@ -2,5 +2,6 @@ // 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. -/// Base class for all unexpected errors in JniGen (Except Skip) -abstract class JniGenException implements Exception {} +export 'android_sdk_tools.dart'; +export 'maven_tools.dart'; +export 'build_summarizer.dart';
diff --git a/pkgs/jni_gen/lib/src/writers/bindings_writer.dart b/pkgs/jni_gen/lib/src/writers/bindings_writer.dart deleted file mode 100644 index ed50a41..0000000 --- a/pkgs/jni_gen/lib/src/writers/bindings_writer.dart +++ /dev/null
@@ -1,11 +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 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; - -abstract class BindingsWriter { - Future<void> writeBindings( - Iterable<ClassDecl> classes, WrapperOptions options); -}
diff --git a/pkgs/jni_gen/lib/src/writers/callback_writer.dart b/pkgs/jni_gen/lib/src/writers/callback_writer.dart deleted file mode 100644 index 0ab7ed0..0000000 --- a/pkgs/jni_gen/lib/src/writers/callback_writer.dart +++ /dev/null
@@ -1,20 +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 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; - -import 'bindings_writer.dart'; - -/// A writer for debugging purpose. -class CallbackWriter extends BindingsWriter { - CallbackWriter(this.callback); - Future<void> Function(Iterable<ClassDecl>, WrapperOptions) callback; - - @override - Future<void> writeBindings( - Iterable<ClassDecl> classes, WrapperOptions options) async { - callback(classes, options); - } -}
diff --git a/pkgs/jni_gen/lib/src/writers/files_writer.dart b/pkgs/jni_gen/lib/src/writers/files_writer.dart deleted file mode 100644 index 0e8a6ad..0000000 --- a/pkgs/jni_gen/lib/src/writers/files_writer.dart +++ /dev/null
@@ -1,132 +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 'package:jni_gen/src/bindings/bindings.dart'; - -import 'package:jni_gen/src/elements/elements.dart'; -import 'package:jni_gen/src/config/wrapper_options.dart'; -import 'package:jni_gen/src/util/find_package.dart'; - -import 'bindings_writer.dart'; - -/// Writer which takes writes C and Dart bindings to specified directories. -/// -/// The structure of dart files is determined by package structure of java. -/// One dart file corresponds to one java package, and it's path is decided by -/// fully qualified name of the package. -/// -/// Example: -/// `android.os` -> `$dartWrappersRoot`/`android/os.dart` -class FilesWriter extends BindingsWriter { - static const _initFileName = 'init.dart'; - - FilesWriter( - {required this.cWrapperDir, - required this.dartWrappersRoot, - this.javaWrappersRoot, - this.preamble, - required this.libraryName}); - Uri cWrapperDir, dartWrappersRoot; - Uri? javaWrappersRoot; - String? preamble; - String libraryName; - @override - Future<void> writeBindings( - Iterable<ClassDecl> classes, WrapperOptions options) async { - // If the file already exists, show warning. - // sort classes so that all classes get written at once. - final Map<String, List<ClassDecl>> packages = {}; - final Map<String, ClassDecl> classesByName = {}; - for (var c in classes) { - classesByName.putIfAbsent(c.binaryName, () => c); - packages.putIfAbsent(c.packageName!, () => <ClassDecl>[]); - packages[c.packageName!]!.add(c); - } - final classNames = classesByName.keys.toSet(); - - stderr.writeln('Creating dart init file ...'); - final initFileUri = dartWrappersRoot.resolve(_initFileName); - final initFile = await File.fromUri(initFileUri).create(recursive: true); - await initFile.writeAsString(DartPreludes.initFile(libraryName), - flush: true); - - final cFile = await File.fromUri(cWrapperDir.resolve('$libraryName.c')) - .create(recursive: true); - final cFileStream = cFile.openWrite(); - if (preamble != null) { - cFileStream.writeln(preamble); - } - cFileStream.write(CPreludes.prelude); - final preprocessor = ApiPreprocessor(classesByName, options); - preprocessor.preprocessAll(); - for (var packageName in packages.keys) { - final relativeFileName = '${packageName.replaceAll('.', '/')}.dart'; - final dartFileUri = dartWrappersRoot.resolve(relativeFileName); - stderr.writeln('Writing bindings for $packageName...'); - final dartFile = await File.fromUri(dartFileUri).create(recursive: true); - final resolver = PackagePathResolver( - options.importPaths, packageName, classNames, - predefined: {'java.lang.String': 'jni.JlString'}); - final cgen = CBindingGenerator(options); - final dgen = DartBindingsGenerator(options, resolver); - - final package = packages[packageName]!; - final cBindings = package.map(cgen.generateBinding).toList(); - final dartBindings = package.map(dgen.generateBinding).toList(); - // write imports from bindings - final dartFileStream = dartFile.openWrite(); - final initImportPath = ('../' * - relativeFileName.codeUnits - .where((cu) => '/'.codeUnitAt(0) == cu) - .length) + - _initFileName; - if (preamble != null) { - dartFileStream.writeln(preamble); - } - dartFileStream - ..write(DartPreludes.bindingFileHeaders) - ..write(resolver.getImportStrings().join('\n')) - ..write('import "$initImportPath" show jlookup;\n\n'); - // write dart bindings only after all imports are figured out - dartBindings.forEach(dartFileStream.write); - cBindings.forEach(cFileStream.write); - await dartFileStream.close(); - } - await cFileStream.close(); - stderr.writeln('Running dart format...'); - final formatRes = - await Process.run('dart', ['format', dartWrappersRoot.toFilePath()]); - if (formatRes.exitCode != 0) { - stderr.writeln('ERROR: dart format completed with ' - 'exit code ${formatRes.exitCode}'); - } - - stderr.writeln('Copying auxiliary files...'); - await _copyFileFromPackage( - 'jni', 'src/dartjni.h', cWrapperDir.resolve('dartjni.h')); - await _copyFileFromPackage('jni_gen', 'cmake/CMakeLists.txt.tmpl', - cWrapperDir.resolve('CMakeLists.txt'), - transform: (s) => s.replaceAll('{{LIBRARY_NAME}}', libraryName)); - stderr.writeln('Completed.'); - } - - 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(); - var source = await sourceFile.readAsString(); - if (transform != null) { - source = transform(source); - } - await targetFile.writeAsString(source); - } else { - stderr.writeln('package $package not found! ' - 'skipped copying ${target.toFilePath()}'); - } - } -}
diff --git a/pkgs/jni_gen/lib/src/writers/writers.dart b/pkgs/jni_gen/lib/src/writers/writers.dart index 887e6a9..38b202e 100644 --- a/pkgs/jni_gen/lib/src/writers/writers.dart +++ b/pkgs/jni_gen/lib/src/writers/writers.dart
@@ -2,6 +2,141 @@ // 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. -export 'bindings_writer.dart'; -export 'files_writer.dart'; -export 'callback_writer.dart'; +import 'dart:io'; + +import 'package:jni_gen/src/bindings/bindings.dart'; + +import 'package:jni_gen/src/elements/elements.dart'; +import 'package:jni_gen/src/config/config.dart'; +import 'package:jni_gen/src/util/find_package.dart'; + +abstract class BindingsWriter { + Future<void> writeBindings(Iterable<ClassDecl> classes); +} + +/// Writer which executes custom callback on passed class elements. +/// +/// This class is provided for debugging purposes. +class CallbackWriter implements BindingsWriter { + CallbackWriter(this.callback); + Future<void> Function(Iterable<ClassDecl>) callback; + @override + Future<void> writeBindings(Iterable<ClassDecl> classes) async { + await callback(classes); + } +} + +/// Writer which takes writes C and Dart bindings to specified directories. +/// +/// The structure of dart files is determined by package structure of java. +/// One dart file corresponds to one java package, and it's path is decided by +/// fully qualified name of the package. +/// +/// Example: +/// `android.os` -> `$dartWrappersRoot`/`android/os.dart` +class FilesWriter extends BindingsWriter { + static const _initFileName = 'init.dart'; + + FilesWriter(this.config); + Config config; + + @override + Future<void> writeBindings(Iterable<ClassDecl> classes) async { + // If the file already exists, show warning. + // sort classes so that all classes get written at once. + final cRoot = config.cRoot; + final dartRoot = config.dartRoot; + final libraryName = config.libraryName; + final preamble = config.preamble; + + final Map<String, List<ClassDecl>> packages = {}; + final Map<String, ClassDecl> classesByName = {}; + for (var c in classes) { + classesByName.putIfAbsent(c.binaryName, () => c); + packages.putIfAbsent(c.packageName!, () => <ClassDecl>[]); + packages[c.packageName!]!.add(c); + } + final classNames = classesByName.keys.toSet(); + + stderr.writeln('Creating dart init file ...'); + final initFileUri = dartRoot.resolve(_initFileName); + final initFile = await File.fromUri(initFileUri).create(recursive: true); + await initFile.writeAsString(DartPreludes.initFile(config.libraryName), + flush: true); + + final cFile = await File.fromUri(cRoot.resolve('$libraryName.c')) + .create(recursive: true); + final cFileStream = cFile.openWrite(); + if (preamble != null) { + cFileStream.writeln(preamble); + } + cFileStream.write(CPreludes.prelude); + ApiPreprocessor.preprocessAll(classesByName, config); + for (var packageName in packages.keys) { + final relativeFileName = '${packageName.replaceAll('.', '/')}.dart'; + final dartFileUri = dartRoot.resolve(relativeFileName); + stderr.writeln('Writing bindings for $packageName...'); + final dartFile = await File.fromUri(dartFileUri).create(recursive: true); + final resolver = PackagePathResolver( + config.importMap ?? const {}, packageName, classNames, + predefined: {'java.lang.String': 'jni.JlString'}); + final cgen = CBindingGenerator(config); + final dgen = DartBindingsGenerator(config, resolver); + + final package = packages[packageName]!; + final cBindings = package.map(cgen.generateBinding).toList(); + final dartBindings = package.map(dgen.generateBinding).toList(); + // write imports from bindings + final dartFileStream = dartFile.openWrite(); + final initImportPath = ('../' * + relativeFileName.codeUnits + .where((cu) => '/'.codeUnitAt(0) == cu) + .length) + + _initFileName; + if (preamble != null) { + dartFileStream.writeln(preamble); + } + dartFileStream + ..write(DartPreludes.bindingFileHeaders) + ..write(resolver.getImportStrings().join('\n')) + ..write('import "$initImportPath" show jlookup;\n\n'); + // write dart bindings only after all imports are figured out + dartBindings.forEach(dartFileStream.write); + cBindings.forEach(cFileStream.write); + await dartFileStream.close(); + } + await cFileStream.close(); + stderr.writeln('Running dart format...'); + final formatRes = + await Process.run('dart', ['format', dartRoot.toFilePath()]); + if (formatRes.exitCode != 0) { + stderr.writeln('ERROR: dart format completed with ' + 'exit code ${formatRes.exitCode}'); + } + + stderr.writeln('Copying auxiliary files...'); + await _copyFileFromPackage( + 'jni', 'src/dartjni.h', cRoot.resolve('dartjni.h')); + await _copyFileFromPackage( + 'jni_gen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'), + transform: (s) => s.replaceAll('{{LIBRARY_NAME}}', libraryName)); + stderr.writeln('Completed.'); + } + + 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(); + var source = await sourceFile.readAsString(); + if (transform != null) { + source = transform(source); + } + await targetFile.writeAsString(source); + } else { + stderr.writeln('package $package not found! ' + 'skipped copying ${target.toFilePath()}'); + } + } +}
diff --git a/pkgs/jni_gen/lib/tools.dart b/pkgs/jni_gen/lib/tools.dart index 19e0eab..91934d3 100644 --- a/pkgs/jni_gen/lib/tools.dart +++ b/pkgs/jni_gen/lib/tools.dart
@@ -4,4 +4,4 @@ library jni_gen_tools; -export 'src/tools/maven_utils.dart'; +export 'src/tools/tools.dart';
diff --git a/pkgs/jni_gen/pubspec.yaml b/pkgs/jni_gen/pubspec.yaml index c75369b..f347657 100644 --- a/pkgs/jni_gen/pubspec.yaml +++ b/pkgs/jni_gen/pubspec.yaml
@@ -13,7 +13,9 @@ dependencies: json_annotation: ^4.6.0 package_config: ^2.1.0 - path: + path: ^1.8.0 + args: ^2.3.0 + yaml: ^3.1.0 dev_dependencies: lints: ^2.0.0
diff --git a/pkgs/jni_gen/test/config_test.dart b/pkgs/jni_gen/test/config_test.dart new file mode 100644 index 0000000..e7b8c25 --- /dev/null +++ b/pkgs/jni_gen/test/config_test.dart
@@ -0,0 +1,74 @@ +// 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 'package:jni_gen/src/config/config.dart'; +import 'package:test/test.dart'; +import 'package:path/path.dart' hide equals; + +import 'jackson_core_test/generate.dart'; + +const packageTests = 'test'; +final jacksonCoreTests = join(packageTests, 'jackson_core_test'); +final thirdParty = join(jacksonCoreTests, 'third_party'); +final lib = join(thirdParty, 'lib'); +final src = join(thirdParty, 'src'); +final testLib = join(thirdParty, 'test_lib'); +final testSrc = join(thirdParty, 'test_src'); + +/// Compares 2 [Config] objects using [expect] to give useful errors when +/// two fields are not equal. +void expectConfigsAreEqual(Config a, Config b) { + expect(a.classes, equals(b.classes)); + expect(a.libraryName, equals(b.libraryName)); + expect(a.cRoot, equals(b.cRoot)); + expect(a.dartRoot, equals(b.dartRoot)); + expect(a.sourcePath, equals(b.sourcePath)); + expect(a.classPath, equals(b.classPath)); + expect(a.preamble, equals(b.preamble)); + expect(a.importMap, equals(b.importMap)); + final am = a.mavenDownloads; + final bm = b.mavenDownloads; + if (am != null) { + expect(bm, isNotNull); + expect(am.sourceDeps, bm!.sourceDeps); + expect(am.sourceDir, bm.sourceDir); + expect(am.jarOnlyDeps, bm.jarOnlyDeps); + expect(am.jarDir, bm.jarDir); + } else { + expect(bm, isNull); + } + final aa = a.androidSdkConfig; + final ba = b.androidSdkConfig; + if (aa != null) { + expect(ba, isNotNull); + expect(aa.versions, ba!.versions); + expect(aa.sdkRoot, ba.sdkRoot); + expect(aa.includeSources, ba.includeSources); + } else { + expect(ba, isNull); + } + final aso = a.summarizerOptions; + final bso = b.summarizerOptions; + if (aso != null) { + expect(bso, isNotNull); + expect(aso.extraArgs, bso!.extraArgs); + expect(aso.workingDirectory, bso.workingDirectory); + expect(aso.backend, bso.backend); + } else { + expect(bso, isNull); + } +} + +void main() { + final config = Config.parseArgs([ + '--config', + join(jacksonCoreTests, 'jnigen.yaml'), + '-Dc_root=$testSrc', + '-Ddart_root=$testLib', + ]); + + test('compare configuration values', () { + expectConfigsAreEqual(config, getConfig(isTest: true)); + }); +}
diff --git a/pkgs/jni_gen/test/jackson_core_test/generate.dart b/pkgs/jni_gen/test/jackson_core_test/generate.dart index 5d62592..919fadb 100644 --- a/pkgs/jni_gen/test/jackson_core_test/generate.dart +++ b/pkgs/jni_gen/test/jackson_core_test/generate.dart
@@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:jni_gen/jni_gen.dart'; -import '../test_util/test_util.dart'; +import 'package:path/path.dart' hide equals; const jacksonPreamble = '// Generated from jackson-core which is licensed under' ' the Apache License 2.0.\n' @@ -23,18 +23,27 @@ '// See the License for the specific language governing permissions and\n' '// limitations under the License.\n'; -Future<void> generate( +const testName = 'jackson_core_test'; +final thirdParty = join('test', testName, 'third_party'); +const deps = ['com.fasterxml.jackson.core:jackson-core:2.13.3']; + +Config getConfig( {bool isTest = false, bool generateFullVersion = false, - bool useAsm = false}) async { - final deps = ['com.fasterxml.jackson.core:jackson-core:2.13.3']; - await generateBindings( - testName: 'jackson_core_test', - sourceDepNames: deps, - jarDepNames: deps, - useAsmBackend: useAsm, + bool useAsm = false}) { + final config = Config( + mavenDownloads: MavenDownloads( + sourceDeps: deps, + sourceDir: join(thirdParty, 'java'), + jarDir: join(thirdParty, 'jar'), + ), + summarizerOptions: SummarizerOptions( + backend: useAsm ? 'asm' : null, + ), preamble: jacksonPreamble, - isThirdParty: true, + libraryName: testName, + cRoot: Uri.directory(join(thirdParty, isTest ? 'test_src' : 'src')), + dartRoot: Uri.directory(join(thirdParty, isTest ? 'test_lib' : 'lib')), classes: (generateFullVersion) ? ['com.fasterxml.jackson.core'] : [ @@ -42,20 +51,25 @@ 'com.fasterxml.jackson.core.JsonParser', 'com.fasterxml.jackson.core.JsonToken', ], - isGeneratedFileTest: isTest, - options: WrapperOptions( - fieldFilter: CombinedFieldFilter([ - excludeAll<Field>([ - ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'], - ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'], - ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'], - ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'], - ]), - CustomFieldFilter((decl, field) => !field.name.startsWith("_")), - ]), - methodFilter: - CustomMethodFilter((decl, method) => !method.name.startsWith('_'))), + exclude: BindingExclusions( + fields: excludeAll<Field>([ + ['com.fasterxml.jackson.core.JsonFactory', 'DEFAULT_QUOTE_CHAR'], + ['com.fasterxml.jackson.core.Base64Variant', 'PADDING_CHAR_NONE'], + ['com.fasterxml.jackson.core.base.ParserMinimalBase', 'CHAR_NULL'], + ['com.fasterxml.jackson.core.io.UTF32Reader', 'NC'], + ]), + ), ); + return config; +} + +Future<void> generate( + {bool isTest = false, + bool generateFullVersion = false, + bool useAsm = false}) async { + final config = getConfig( + isTest: isTest, generateFullVersion: generateFullVersion, useAsm: useAsm); + await generateJniBindings(config); } void main() => generate(isTest: false);
diff --git a/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml b/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml new file mode 100644 index 0000000..96988bd --- /dev/null +++ b/pkgs/jni_gen/test/jackson_core_test/jnigen.yaml
@@ -0,0 +1,36 @@ +maven_downloads: + source_deps: + - 'com.fasterxml.jackson.core:jackson-core:2.13.3' + source_dir: test/jackson_core_test/third_party/java + jar_dir: test/jackson_core_test/third_party/jar + +dart_root: test/jackson_core_test/third_party/lib +c_root: test/jackson_core_test/third_party/src +library_name: jackson_core_test +classes: + - 'com.fasterxml.jackson.core.JsonFactory' + - 'com.fasterxml.jackson.core.JsonParser' + - 'com.fasterxml.jackson.core.JsonToken' + +exclude: + fields: + - 'com.fasterxml.jackson.core.JsonFactory#DEFAULT_QUOTE_CHAR' + +preamble: | + // Generated from jackson-core which is licensed under the Apache License 2.0. + // The following copyright from the original authors applies. + // See https://github.com/FasterXML/jackson-core/blob/2.14/LICENSE + // + // Copyright (c) 2007 - The Jackson Project Authors + // 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. +
diff --git a/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart b/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart index ee2de71..6ecc806 100644 --- a/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart +++ b/pkgs/jni_gen/test/jackson_core_test/third_party/lib/com/fasterxml/jackson/core.dart
@@ -61,7 +61,7 @@ /// (and returned by \#getFormatName() static const FORMAT_NAME_JSON = "JSON"; - static final _getDEFAULT_FACTORY_FEATURE_FLAGS = jlookup< + static final _get_DEFAULT_FACTORY_FEATURE_FLAGS = jlookup< ffi.NativeFunction<ffi.Int32 Function()>>( "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_FACTORY_FEATURE_FLAGS") .asFunction<int Function()>(); @@ -70,9 +70,9 @@ /// /// Bitfield (set of flags) of all factory features that are enabled by default. static int get DEFAULT_FACTORY_FEATURE_FLAGS => - _getDEFAULT_FACTORY_FEATURE_FLAGS(); + _get_DEFAULT_FACTORY_FEATURE_FLAGS(); - static final _getDEFAULT_PARSER_FEATURE_FLAGS = jlookup< + static final _get_DEFAULT_PARSER_FEATURE_FLAGS = jlookup< ffi.NativeFunction<ffi.Int32 Function()>>( "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_PARSER_FEATURE_FLAGS") .asFunction<int Function()>(); @@ -82,9 +82,9 @@ /// Bitfield (set of flags) of all parser features that are enabled /// by default. static int get DEFAULT_PARSER_FEATURE_FLAGS => - _getDEFAULT_PARSER_FEATURE_FLAGS(); + _get_DEFAULT_PARSER_FEATURE_FLAGS(); - static final _getDEFAULT_GENERATOR_FEATURE_FLAGS = jlookup< + static final _get_DEFAULT_GENERATOR_FEATURE_FLAGS = jlookup< ffi.NativeFunction<ffi.Int32 Function()>>( "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_GENERATOR_FEATURE_FLAGS") .asFunction<int Function()>(); @@ -94,9 +94,9 @@ /// Bitfield (set of flags) of all generator features that are enabled /// by default. static int get DEFAULT_GENERATOR_FEATURE_FLAGS => - _getDEFAULT_GENERATOR_FEATURE_FLAGS(); + _get_DEFAULT_GENERATOR_FEATURE_FLAGS(); - static final _getDEFAULT_ROOT_VALUE_SEPARATOR = jlookup< + static final _get_DEFAULT_ROOT_VALUE_SEPARATOR = jlookup< ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( "get_com_fasterxml_jackson_core_JsonFactory_DEFAULT_ROOT_VALUE_SEPARATOR") .asFunction<ffi.Pointer<ffi.Void> Function()>(); @@ -104,7 +104,7 @@ /// from: static public final com.fasterxml.jackson.core.SerializableString DEFAULT_ROOT_VALUE_SEPARATOR /// The returned object must be deleted after use, by calling the `delete` method. static jni.JlObject get DEFAULT_ROOT_VALUE_SEPARATOR => - jni.JlObject.fromRef(_getDEFAULT_ROOT_VALUE_SEPARATOR()); + jni.JlObject.fromRef(_get_DEFAULT_ROOT_VALUE_SEPARATOR()); static final _ctor = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( @@ -1682,7 +1682,7 @@ /// from: private static final int MAX_SHORT_I static const MAX_SHORT_I = 32767; - static final _getDEFAULT_READ_CAPABILITIES = jlookup< + static final _get_DEFAULT_READ_CAPABILITIES = jlookup< ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( "get_com_fasterxml_jackson_core_JsonParser_DEFAULT_READ_CAPABILITIES") .asFunction<ffi.Pointer<ffi.Void> Function()>(); @@ -1695,7 +1695,7 @@ /// set needs to be passed). ///@since 2.12 static jni.JlObject get DEFAULT_READ_CAPABILITIES => - jni.JlObject.fromRef(_getDEFAULT_READ_CAPABILITIES()); + jni.JlObject.fromRef(_get_DEFAULT_READ_CAPABILITIES()); static final _ctor = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
diff --git a/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h b/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h index 407312b..cd94b15 100644 --- a/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h +++ b/pkgs/jni_gen/test/jackson_core_test/third_party/src/dartjni.h
@@ -1,3 +1,7 @@ +// 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. + #include <jni.h> #include <stdint.h> #include <stdio.h>
diff --git a/pkgs/jni_gen/test/simple_package_test/generate.dart b/pkgs/jni_gen/test/simple_package_test/generate.dart index 427098d..dd6eeca 100644 --- a/pkgs/jni_gen/test/simple_package_test/generate.dart +++ b/pkgs/jni_gen/test/simple_package_test/generate.dart
@@ -20,7 +20,6 @@ } Future<void> generateSources(String lib, String src) async { - await runCmd('dart', ['run', 'jni_gen:setup']); await compileJavaSources(javaPath, javaFiles); final cWrapperDir = Uri.directory(join(testRoot, src)); final dartWrappersRoot = Uri.directory(join(testRoot, lib)); @@ -31,17 +30,15 @@ await dir.delete(recursive: true); } } - await JniGenTask( - summarySource: SummarizerCommand( - sourcePaths: [Uri.directory(javaPath)], - classPaths: [Uri.directory(javaPath)], - classes: ['dev.dart.simple_package', 'dev.dart.pkg2'], - ), - outputWriter: FilesWriter( - cWrapperDir: cWrapperDir, - dartWrappersRoot: dartWrappersRoot, - libraryName: 'simple_package'), - ).run(); + final config = Config( + sourcePath: [Uri.directory(javaPath)], + classPath: [Uri.directory(javaPath)], + classes: ['dev.dart.simple_package', 'dev.dart.pkg2'], + cRoot: cWrapperDir, + dartRoot: dartWrappersRoot, + libraryName: 'simple_package', + ); + await generateJniBindings(config); } void main() async => await generateSources('lib', 'src');
diff --git a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart index 618b175..0fa32cd 100644 --- a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart +++ b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/pkg2.dart
@@ -17,19 +17,20 @@ class C2 extends jni.JlObject { C2.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref); - static final _getCONSTANT = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>( - "get_dev_dart_pkg2_C2_CONSTANT") - .asFunction<int Function()>(); + static final _get_CONSTANT = + jlookup<ffi.NativeFunction<ffi.Int32 Function()>>( + "get_dev_dart_pkg2_C2_CONSTANT") + .asFunction<int Function()>(); /// from: static public int CONSTANT - static int get CONSTANT => _getCONSTANT(); - static final _setCONSTANT = + static int get CONSTANT => _get_CONSTANT(); + static final _set_CONSTANT = jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Int32)>>( "set_dev_dart_pkg2_C2_CONSTANT") .asFunction<void Function(int)>(); /// from: static public int CONSTANT - static set CONSTANT(int value) => _setCONSTANT(value); + static set CONSTANT(int value) => _set_CONSTANT(value); static final _ctor = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>(
diff --git a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart index da6ec32..173c493 100644 --- a/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart +++ b/pkgs/jni_gen/test/simple_package_test/lib/dev/dart/simple_package.dart
@@ -23,36 +23,36 @@ /// from: static public final int OFF static const OFF = 0; - static final _getaux = + static final _get_aux = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( "get_dev_dart_simple_package_Example_aux") .asFunction<ffi.Pointer<ffi.Void> Function()>(); /// from: static public dev.dart.simple_package.Example.Aux aux /// The returned object must be deleted after use, by calling the `delete` method. - static Example_Aux get aux => Example_Aux.fromRef(_getaux()); - static final _setaux = + static Example_Aux get aux => Example_Aux.fromRef(_get_aux()); + static final _set_aux = jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>( "set_dev_dart_simple_package_Example_aux") .asFunction<void Function(ffi.Pointer<ffi.Void>)>(); /// from: static public dev.dart.simple_package.Example.Aux aux /// The returned object must be deleted after use, by calling the `delete` method. - static set aux(Example_Aux value) => _setaux(value.reference); + static set aux(Example_Aux value) => _set_aux(value.reference); - static final _getnum = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>( + static final _get_num = jlookup<ffi.NativeFunction<ffi.Int32 Function()>>( "get_dev_dart_simple_package_Example_num") .asFunction<int Function()>(); /// from: static public int num - static int get num => _getnum(); - static final _setnum = + static int get num => _get_num(); + static final _set_num = jlookup<ffi.NativeFunction<ffi.Void Function(ffi.Int32)>>( "set_dev_dart_simple_package_Example_num") .asFunction<void Function(int)>(); /// from: static public int num - static set num(int value) => _setnum(value); + static set num(int value) => _set_num(value); static final _ctor = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function()>>( @@ -111,7 +111,7 @@ class Example_Aux extends jni.JlObject { Example_Aux.fromRef(ffi.Pointer<ffi.Void> ref) : super.fromRef(ref); - static final _getvalue = jlookup< + static final _get_value = jlookup< ffi.NativeFunction< ffi.Uint8 Function( ffi.Pointer<ffi.Void>, @@ -122,15 +122,15 @@ )>(); /// from: public boolean value - bool get value => _getvalue(reference) != 0; - static final _setvalue = jlookup< + bool get value => _get_value(reference) != 0; + static final _set_value = jlookup< ffi.NativeFunction< ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Uint8)>>("set_dev_dart_simple_package_Example__Aux_value") .asFunction<void Function(ffi.Pointer<ffi.Void>, int)>(); /// from: public boolean value - set value(bool value) => _setvalue(reference, value ? 1 : 0); + set value(bool value) => _set_value(reference, value ? 1 : 0); static final _ctor = jlookup<ffi.NativeFunction<ffi.Pointer<ffi.Void> Function(ffi.Uint8)>>(
diff --git a/pkgs/jni_gen/test/simple_package_test/src/dartjni.h b/pkgs/jni_gen/test/simple_package_test/src/dartjni.h index 407312b..cd94b15 100644 --- a/pkgs/jni_gen/test/simple_package_test/src/dartjni.h +++ b/pkgs/jni_gen/test/simple_package_test/src/dartjni.h
@@ -1,3 +1,7 @@ +// 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. + #include <jni.h> #include <stdint.h> #include <stdio.h>
diff --git a/pkgs/jni_gen/test/test_util/test_util.dart b/pkgs/jni_gen/test/test_util/test_util.dart index c0a1681..49bc5ff 100644 --- a/pkgs/jni_gen/test/test_util/test_util.dart +++ b/pkgs/jni_gen/test/test_util/test_util.dart
@@ -1,13 +1,9 @@ import 'dart:io'; import 'package:path/path.dart' hide equals; -import 'package:jni_gen/jni_gen.dart'; -import 'package:jni_gen/tools.dart'; import 'package:test/test.dart'; -const packageTestsDir = 'test'; - -Future<bool> isEmptyDir(String path) async { +Future<bool> hasNoFilesInDir(String path) async { final dir = Directory(path); return (!await dir.exists()) || (await dir.list().length == 0); } @@ -22,72 +18,15 @@ return proc.exitCode; } -Future<void> buildNativeLibs(String testName) async { - final testRoot = join(packageTestsDir, testName); - await runCmd('dart', ['run', 'jni:setup']); - await runCmd('dart', ['run', 'jni:setup', '-S', join(testRoot, 'src')]); -} - Future<List<String>> getJarPaths(String testRoot) { final jarPath = join(testRoot, 'jar'); return Directory(jarPath) .list() .map((entry) => entry.path) - .where((path) => path.endsWith('jar')) + .where((path) => path.endsWith('.jar')) .toList(); } -/// Download dependencies using maven and generate bindings. -Future<void> generateBindings({ - required String testName, - required List<String> sourceDepNames, - required List<String> jarDepNames, - required List<String> classes, - required WrapperOptions options, - required bool isGeneratedFileTest, - bool useAsmBackend = false, - bool isThirdParty = false, - String? preamble, -}) async { - final testRoot = - join(packageTestsDir, testName, isThirdParty ? 'third_party' : ''); - final jarPath = join(testRoot, 'jar'); - final javaPath = join(testRoot, 'java'); - final src = join(testRoot, isGeneratedFileTest ? 'test_src' : 'src'); - final lib = join(testRoot, isGeneratedFileTest ? 'test_lib' : 'lib'); - - final sourceDeps = MvnTools.makeDependencyList(sourceDepNames); - final jarDeps = MvnTools.makeDependencyList(jarDepNames); - - await runCmd('dart', ['run', 'jni_gen:setup']); - - MvnTools.setVerbose(true); - if (await isEmptyDir(jarPath)) { - await Directory(jarPath).create(recursive: true); - await MvnTools.downloadMavenJars(jarDeps, jarPath); - } - if (await isEmptyDir(javaPath)) { - await Directory(javaPath).create(recursive: true); - await MvnTools.downloadMavenSources(sourceDeps, javaPath); - } - final jars = await getJarPaths(testRoot); - stderr.writeln('using classpath: $jars'); - await JniGenTask( - summarySource: SummarizerCommand( - sourcePaths: [Uri.directory(javaPath)], - classPaths: jars.map(Uri.file).toList(), - classes: classes, - extraArgs: useAsmBackend ? ['--backend', 'asm'] : [], - ), - options: options, - outputWriter: FilesWriter( - cWrapperDir: Uri.directory(src), - dartWrappersRoot: Uri.directory(lib), - preamble: preamble, - libraryName: testName)) - .run(); -} - /// compares 2 hierarchies, with and without prefix 'test_' void compareDirs(String path1, String path2) { final list1 = Directory(path1).listSync(recursive: true);
diff --git a/pkgs/jni_gen/test/yaml_config_test.dart b/pkgs/jni_gen/test/yaml_config_test.dart new file mode 100644 index 0000000..2f180a6 --- /dev/null +++ b/pkgs/jni_gen/test/yaml_config_test.dart
@@ -0,0 +1,42 @@ +// 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. + +// End-to-end test confirming yaml config works as expected. + +import 'dart:io'; + +import 'package:path/path.dart' hide equals; +import 'package:test/test.dart'; + +import 'test_util/test_util.dart'; + +void main() { + final thirdParty = join('test', 'jackson_core_test', 'third_party'); + final testLib = join(thirdParty, 'test_lib'); + final testSrc = join(thirdParty, 'test_src'); + final lib = join(thirdParty, 'lib'); + final src = join(thirdParty, 'src'); + final config = join('test', 'jackson_core_test', 'jnigen.yaml'); + test('generate and compare bindings using YAML config', () { + final jnigenProc = Process.runSync('dart', [ + 'run', + 'jni_gen', + '--config', + config, + '-Dc_root=$testSrc', + '-Ddart_root=$testLib' + ]); + expect(jnigenProc.exitCode, equals(0)); + + final analyzeProc = Process.runSync('dart', ['analyze', testLib]); + expect(analyzeProc.exitCode, equals(0)); + + compareDirs(lib, testLib); + compareDirs(src, testSrc); + + for (var dir in [testLib, testSrc]) { + Directory(dir).deleteSync(recursive: true); + } + }, timeout: Timeout.factor(4)); +}