[jnigen] Using a yaml symbols file instead of hard coding package:jni exported classes (https://github.com/dart-lang/jnigen/issues/289)
* Removes the support for importMap in config.
* Adds the support for importing a yaml symbols file.
diff --git a/pkgs/jni/lib/jni_symbols.yaml b/pkgs/jni/lib/jni_symbols.yaml
new file mode 100644
index 0000000..199df29
--- /dev/null
+++ b/pkgs/jni/lib/jni_symbols.yaml
@@ -0,0 +1,12 @@
+version: 1.0.0
+files:
+ 'jni.dart':
+ 'java.lang.Object':
+ name: JObject
+ type_class: JObjectType
+ super_count: 0
+ 'java.lang.String':
+ name: JString
+ type_class: JStringType
+ super_count: 1
+
diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md
index 23c1e26..58fb5be 100644
--- a/pkgs/jnigen/CHANGELOG.md
+++ b/pkgs/jnigen/CHANGELOG.md
@@ -1,5 +1,6 @@
## 0.5.0-dev.0
-* No JNIGen changes (yet), but keeping version in lockstep with `package:jni`.
+* **Breaking Change** ([#72](https://github.com/dart-lang/jnigen/issues/72)): Removed support for `importMap` in favor of the newly added interop mechanism with importing yaml files.
+* Strings now use UTF16.
## 0.4.0
* **Breaking Change** ([#145](https://github.com/dart-lang/jnigen/issues/145)): Type arguments are now named instead of positional.
diff --git a/pkgs/jnigen/lib/src/bindings/dart_generator.dart b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
index 1cd3c2b..a1559cf 100644
--- a/pkgs/jnigen/lib/src/bindings/dart_generator.dart
+++ b/pkgs/jnigen/lib/src/bindings/dart_generator.dart
@@ -31,9 +31,6 @@
// Prefixes and suffixes
const _typeParamPrefix = '\$';
-// TODO(#143): this is a temporary fix for the name collision.
-const _typeClassPrefix = '\$';
-const _typeClassSuffix = 'Type';
// Misc.
const _classRef = '_class.reference';
@@ -195,7 +192,12 @@
if (cBased) {
s.writeln(cInitCode);
}
- final classGenerator = _ClassGenerator(config, s);
+ final resolver = Resolver(
+ importedClasses: config.importedClasses,
+ currentClass: null, // Single file mode.
+ inputClassNames: node.decls.keys.toSet(),
+ );
+ final classGenerator = _ClassGenerator(config, s, resolver);
for (final classDecl in node.decls.values) {
classDecl.accept(classGenerator);
}
@@ -249,11 +251,11 @@
s.write('import "$initFilePath";');
}
final resolver = Resolver(
- importMap: config.importMap ?? {},
+ importedClasses: config.importedClasses,
currentClass: fileClassName,
inputClassNames: node.decls.keys.toSet(),
);
- final classGenerator = _ClassGenerator(config, s, resolver: resolver);
+ final classGenerator = _ClassGenerator(config, s, resolver);
for (final classDecl in classesInFile) {
classDecl.accept(classGenerator);
}
@@ -281,13 +283,13 @@
class _ClassGenerator extends Visitor<ClassDecl, void> {
final Config config;
final StringSink s;
- final Resolver? resolver;
+ final Resolver resolver;
_ClassGenerator(
this.config,
- this.s, {
+ this.s,
this.resolver,
- });
+ );
static const staticTypeGetter = 'type';
static const instanceTypeGetter = '\$$staticTypeGetter';
@@ -331,7 +333,7 @@
.map((typeParam) => 'this.$typeParam,')
.join(_newLine(depth: 2));
final superClass = (node.classDecl.superclass!.type as DeclaredType);
- final superTypeClassesCall = superClass.classDecl == ClassDecl.object
+ final superTypeClassesCall = superClass.classDecl.isObject()
? ''
: superClass.params
.accept(_TypeClassGenerator(resolver))
@@ -365,7 +367,7 @@
// Static TypeClass getter
s.writeln(
' /// The type which includes information such as the signature of this class.');
- final typeClassName = '$_typeClassPrefix$name$_typeClassSuffix';
+ final typeClassName = node.typeClassName;
if (typeParams.isEmpty) {
s.writeln('static const $staticTypeGetter = $typeClassName();');
} else {
@@ -527,7 +529,7 @@
}
final typeParams = _encloseIfNotEmpty('<', allTypeParams.join(', '), '>');
- final prefix = resolver?.resolvePrefix(node.classDecl.binaryName) ?? '';
+ final prefix = resolver?.resolvePrefix(node.classDecl) ?? '';
return '$prefix${node.classDecl.finalName}$typeParams';
}
@@ -563,7 +565,7 @@
/// Generates the type class.
class _TypeClassGenerator extends TypeVisitor<_TypeClass> {
final bool isConst;
- final Resolver? resolver;
+ final Resolver resolver;
_TypeClassGenerator(this.resolver, {this.isConst = true});
@@ -573,19 +575,13 @@
node.type.accept(_TypeClassGenerator(resolver, isConst: false));
final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : '';
return _TypeClass(
- '$ifConst$_jArray$_typeClassSuffix(${innerTypeClass.name})',
+ '$ifConst${_jArray}Type(${innerTypeClass.name})',
innerTypeClass.canBeConst,
);
}
@override
_TypeClass visitDeclaredType(DeclaredType node) {
- if (node.classDecl.binaryName == 'java.lang.Object' ||
- node.classDecl.binaryName == 'java.lang.String') {
- final ifConst = isConst ? 'const ' : '';
- return _TypeClass(
- '$ifConst$_jni.${node.classDecl.finalName}$_typeClassSuffix()', true);
- }
final allTypeParams = node.classDecl.allTypeParams
.accept(const _TypeParamGenerator(withExtends: false))
.toList();
@@ -596,7 +592,8 @@
// Can be const if all the type parameters are defined and each of them are
// also const.
- final canBeConst = definedTypeClasses.every((e) => e.canBeConst);
+ final canBeConst =
+ allTypeParams.isEmpty || definedTypeClasses.every((e) => e.canBeConst);
// Replacing the declared ones. They come at the end.
// The rest will be `JObjectType`.
@@ -607,7 +604,7 @@
List.filled(
allTypeParams.length - node.params.length,
// Adding const to subexpressions if the entire expression is not const.
- '${canBeConst ? '' : 'const '}$_jObject$_typeClassSuffix()',
+ '${canBeConst ? '' : 'const '}${_jObject}Type()',
),
);
allTypeParams.replaceRange(
@@ -621,9 +618,9 @@
final args = allTypeParams.join(', ');
final ifConst = isConst && canBeConst ? 'const ' : '';
- final prefix = resolver?.resolvePrefix(node.classDecl.binaryName) ?? '';
+ final prefix = resolver.resolvePrefix(node.classDecl);
return _TypeClass(
- '$ifConst$prefix$_typeClassPrefix${node.classDecl.finalName}$_typeClassSuffix($args)',
+ '$ifConst$prefix${node.classDecl.typeClassName}($args)',
canBeConst,
);
}
@@ -631,7 +628,7 @@
@override
_TypeClass visitPrimitiveType(PrimitiveType node) {
final ifConst = isConst ? 'const ' : '';
- return _TypeClass('$ifConst$_jni.${node.jniType}$_typeClassSuffix()', true);
+ return _TypeClass('$ifConst$_jni.${node.jniType}Type()', true);
}
@override
@@ -648,7 +645,7 @@
@override
_TypeClass visitNonPrimitiveType(ReferredType node) {
final ifConst = isConst ? 'const ' : '';
- return _TypeClass('$ifConst$_jObject$_typeClassSuffix()', true);
+ return _TypeClass('$ifConst${_jObject}Type()', true);
}
}
@@ -759,7 +756,7 @@
}
class _FromNative extends TypeVisitor<String> {
- final Resolver? resolver;
+ final Resolver resolver;
final String value;
const _FromNative(this.resolver, this.value);
@@ -778,7 +775,7 @@
class _FieldGenerator extends Visitor<Field, void> {
final Config config;
- final Resolver? resolver;
+ final Resolver resolver;
final StringSink s;
const _FieldGenerator(this.config, this.resolver, this.s);
@@ -932,7 +929,7 @@
/// Generates Dart bindings for Java methods.
class _MethodGenerator extends Visitor<Method, void> {
final Config config;
- final Resolver? resolver;
+ final Resolver resolver;
final StringSink s;
const _MethodGenerator(this.config, this.resolver, this.s);
@@ -1181,7 +1178,7 @@
/// void bar(Foo foo) => ...
/// ```
class _ParamDef extends Visitor<Param, String> {
- final Resolver? resolver;
+ final Resolver resolver;
const _ParamDef(this.resolver);
@@ -1289,7 +1286,7 @@
/// ((((a.$type as jni.JArrayType).elementType) as $JMapType).V) as jni.JObjType<$T>
/// ```
class _ParamTypeLocator extends Visitor<Param, Map<String, List<String>>> {
- final Resolver? resolver;
+ final Resolver resolver;
_ParamTypeLocator({required this.resolver});
@@ -1308,7 +1305,7 @@
}
class _TypeVarLocator extends TypeVisitor<Map<String, List<OutsideInBuffer>>> {
- final Resolver? resolver;
+ final Resolver resolver;
_TypeVarLocator({required this.resolver});
@@ -1334,14 +1331,13 @@
@override
Map<String, List<OutsideInBuffer>> visitDeclaredType(DeclaredType node) {
- if (node.classDecl == ClassDecl.object) {
+ if (node.classDecl.isObject()) {
return {};
}
final offset = node.classDecl.allTypeParams.length - node.params.length;
final result = <String, List<OutsideInBuffer>>{};
- final prefix = resolver?.resolvePrefix(node.binaryName) ?? '';
- final typeClass =
- '$prefix$_typeClassPrefix${node.classDecl.finalName}$_typeClassSuffix';
+ final prefix = resolver.resolvePrefix(node.classDecl);
+ final typeClass = '$prefix${node.classDecl.typeClassName}';
for (var i = 0; i < node.params.length; ++i) {
final typeParam = node.classDecl.allTypeParams[i + offset].name;
final exprs = node.params[i].accept(this);
@@ -1361,7 +1357,7 @@
final exprs = node.type.accept(this);
for (final e in exprs.values.expand((i) => i)) {
e.appendLeft('((');
- e.prependRight(' as $_jArray$_typeClassSuffix).elementType as $_jType)');
+ e.prependRight(' as ${_jArray}Type).elementType as $_jType)');
}
return exprs;
}
diff --git a/pkgs/jnigen/lib/src/bindings/linker.dart b/pkgs/jnigen/lib/src/bindings/linker.dart
index e50d437..74c14e7 100644
--- a/pkgs/jnigen/lib/src/bindings/linker.dart
+++ b/pkgs/jnigen/lib/src/bindings/linker.dart
@@ -2,26 +2,74 @@
// 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 'visitor.dart';
import '../config/config.dart';
import '../elements/elements.dart';
+import '../logging/logging.dart';
+import 'visitor.dart';
typedef _Resolver = ClassDecl Function(String? binaryName);
-/// Adds references from child elements back to their parent elements.
-/// Resolves Kotlin specific `asyncReturnType` for methods.
-class Linker extends Visitor<Classes, void> {
+/// A [Visitor] that adds the correct [ClassDecl] references from the
+/// string binary names.
+///
+/// It adds the following references:
+/// * Links [ClassDecl] objects from imported dependencies.
+/// * Adds references from child elements back to their parent elements.
+/// * Resolves Kotlin specific `asyncReturnType` for methods.
+class Linker extends Visitor<Classes, Future<void>> {
Linker(this.config);
final Config config;
@override
- void visit(Classes node) {
- final classLinker = _ClassLinker(config, (binaryName) {
- return ClassDecl.predefined[binaryName] ??
+ Future<void> visit(Classes node) async {
+ // Specify paths for this package's classes.
+ final root = config.outputConfig.dartConfig.path;
+ if (config.outputConfig.dartConfig.structure ==
+ OutputStructure.singleFile) {
+ // Connect all to the root if the output is in single file mode.
+ final path = root.toFilePath();
+ for (final decl in node.decls.values) {
+ decl.path = path;
+ }
+ } else {
+ for (final decl in node.decls.values) {
+ final dollarSign = decl.binaryName.indexOf('\$');
+ final className = dollarSign != -1
+ ? decl.binaryName.substring(0, dollarSign)
+ : decl.binaryName;
+ final path = className.replaceAll('.', '/');
+ decl.path = root.resolve(path).toFilePath();
+ }
+ }
+
+ // Find all the imported classes.
+ await config.importClasses();
+
+ if (config.importedClasses.keys
+ .toSet()
+ .intersection(node.decls.keys.toSet())
+ .isNotEmpty) {
+ log.fatal(
+ 'Trying to re-import the generated classes.\n'
+ 'Try hiding the class(es) in import.',
+ );
+ }
+
+ for (final className in config.importedClasses.keys) {
+ log.finest('Imported $className successfully.');
+ }
+
+ ClassDecl resolve(String? binaryName) {
+ return config.importedClasses[binaryName] ??
node.decls[binaryName] ??
- ClassDecl.object;
- });
+ resolve(TypeUsage.object.name);
+ }
+
+ final classLinker = _ClassLinker(
+ config,
+ resolve,
+ );
for (final classDecl in node.decls.values) {
classDecl.accept(classLinker);
}
@@ -29,20 +77,24 @@
}
class _ClassLinker extends Visitor<ClassDecl, void> {
- _ClassLinker(this.config, this.resolve);
-
final Config config;
final _Resolver resolve;
- final Set<ClassDecl> _linked = {...ClassDecl.predefined.values};
+ final Set<ClassDecl> _linked;
+
+ _ClassLinker(
+ this.config,
+ this.resolve,
+ ) : _linked = {...config.importedClasses.values};
@override
void visit(ClassDecl node) {
if (_linked.contains(node)) return;
+ log.finest('Linking ${node.binaryName}.');
_linked.add(node);
node.parent = resolve(node.parentName);
node.parent!.accept(this);
- // Adding type params of outer classes to the nested classes
+ // Add type params of outer classes to the nested classes
final allTypeParams = <TypeParam>[];
if (!node.modifiers.contains('static')) {
for (final typeParam in node.parent!.allTypeParams) {
diff --git a/pkgs/jnigen/lib/src/bindings/renamer.dart b/pkgs/jnigen/lib/src/bindings/renamer.dart
index 4f1c440..d5f9a73 100644
--- a/pkgs/jnigen/lib/src/bindings/renamer.dart
+++ b/pkgs/jnigen/lib/src/bindings/renamer.dart
@@ -135,18 +135,13 @@
class _ClassRenamer implements Visitor<ClassDecl, void> {
final Config config;
+ final Set<ClassDecl> renamed;
final Map<String, int> classNameCounts = {};
- final Set<ClassDecl> renamed = {...ClassDecl.predefined.values};
- final Map<ClassDecl, Map<String, int>> nameCounts = {
- for (final predefined in ClassDecl.predefined.values) ...{
- predefined: {..._definedSyms},
- }
- };
- final Map<ClassDecl, Map<String, int>> methodNumsAfterRenaming = {};
+ final Map<ClassDecl, Map<String, int>> nameCounts = {};
_ClassRenamer(
this.config,
- );
+ ) : renamed = {...config.importedClasses.values};
/// Returns class name as useful in dart.
///
@@ -157,10 +152,11 @@
@override
void visit(ClassDecl node) {
if (renamed.contains(node)) return;
+ log.finest('Renaming ${node.binaryName}.');
renamed.add(node);
nameCounts[node] = {..._definedSyms};
- methodNumsAfterRenaming[node] = {};
+ node.methodNumsAfterRenaming = {};
final className = _getSimplifiedClassName(node.binaryName);
node.uniqueName = _renameConflict(classNameCounts, className);
@@ -170,15 +166,16 @@
final uniquifyName =
config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
node.finalName = uniquifyName ? node.uniqueName : className;
+ // TODO(#143): $ at the beginning is a temporary fix for the name collision.
+ node.typeClassName = '\$${node.finalName}Type';
log.fine('Class ${node.binaryName} is named ${node.finalName}');
final superClass = (node.superclass!.type as DeclaredType).classDecl;
superClass.accept(this);
- nameCounts[node]!.addAll(nameCounts[superClass]!);
+ nameCounts[node]!.addAll(nameCounts[superClass] ?? {});
final methodRenamer = _MethodRenamer(
config,
nameCounts[node]!,
- methodNumsAfterRenaming,
);
for (final method in node.methods) {
method.accept(methodRenamer);
@@ -192,11 +189,10 @@
}
class _MethodRenamer implements Visitor<Method, void> {
- _MethodRenamer(this.config, this.nameCounts, this.methodNumsAfterRenaming);
+ _MethodRenamer(this.config, this.nameCounts);
final Config config;
final Map<String, int> nameCounts;
- final Map<ClassDecl, Map<String, int>> methodNumsAfterRenaming;
@override
void visit(Method node) {
@@ -205,17 +201,17 @@
// If node is in super class, assign its number, overriding it.
final superClass =
(node.classDecl.superclass!.type as DeclaredType).classDecl;
- final superNum = methodNumsAfterRenaming[superClass]?[sig];
+ final superNum = superClass.methodNumsAfterRenaming[sig];
if (superNum != null) {
// Don't rename if superNum == 0
// Unless the node name is a keyword.
final superNumText = superNum == 0 ? '' : '$superNum';
final methodName = superNum == 0 ? _keywordRename(name) : name;
node.finalName = '$methodName$superNumText';
- methodNumsAfterRenaming[node.classDecl]?[sig] = superNum;
+ node.classDecl.methodNumsAfterRenaming[sig] = superNum;
} else {
node.finalName = _renameConflict(nameCounts, name);
- methodNumsAfterRenaming[node.classDecl]?[sig] = nameCounts[name]! - 1;
+ node.classDecl.methodNumsAfterRenaming[sig] = nameCounts[name]! - 1;
}
log.fine(
'Method ${node.classDecl.binaryName}#${node.name} is named ${node.finalName}');
diff --git a/pkgs/jnigen/lib/src/bindings/resolver.dart b/pkgs/jnigen/lib/src/bindings/resolver.dart
index b0c8230..68b250b 100644
--- a/pkgs/jnigen/lib/src/bindings/resolver.dart
+++ b/pkgs/jnigen/lib/src/bindings/resolver.dart
@@ -4,18 +4,17 @@
import 'dart:math';
+import '../elements/elements.dart';
import '../logging/logging.dart';
class Resolver {
- static const Map<String, String> predefined = {
- 'java.lang.String': 'jni.',
- };
-
/// Class corresponding to currently writing file.
- final String currentClass;
+ ///
+ /// Is [null] when in single file mode.
+ final String? currentClass;
/// Explicit import mappings.
- final Map<String, String> importMap;
+ final Map<String, ClassDecl> importedClasses;
/// Names of all classes in input.
final Set<String> inputClassNames;
@@ -27,7 +26,7 @@
final Map<String, String> _classToImportedName = {};
Resolver({
- required this.importMap,
+ required this.importedClasses,
required this.currentClass,
this.inputClassNames = const {},
});
@@ -50,13 +49,20 @@
}
/// Get the prefix for the class
- String resolvePrefix(String binaryName) {
- if (predefined.containsKey(binaryName)) {
- return predefined[binaryName]!;
+ String resolvePrefix(ClassDecl classDecl) {
+ if (classDecl.path == 'package:jni/jni.dart') {
+ // For package:jni we don't use a leading underscore.
+ return 'jni.';
}
+ final binaryName = classDecl.binaryName;
final target = getFileClassName(binaryName);
- if (target == currentClass && inputClassNames.contains(binaryName)) {
+ // For classes we generate (inside [inputClassNames]) no import
+ // (and therefore no prefix) is necessary when:
+ // * Never necessary in single file mode
+ // * In multi file mode if the target is the same as the current class
+ if ((currentClass == null || target == currentClass) &&
+ inputClassNames.contains(binaryName)) {
return '';
}
@@ -105,11 +111,11 @@
/// requested so that classes included in current bindings can be resolved
/// using relative path.
String? getImport(String classToResolve, String binaryName) {
- var prefix = classToResolve;
+ final prefix = classToResolve;
// short circuit if the requested class is specified directly in import map.
- if (importMap.containsKey(binaryName)) {
- return importMap[binaryName]!;
+ if (importedClasses.containsKey(binaryName)) {
+ return importedClasses[binaryName]!.path;
}
if (prefix.isEmpty) {
@@ -117,7 +123,7 @@
}
final dest = classToResolve.split('.');
- final src = currentClass.split('.');
+ final src = currentClass!.split('.');
// Use relative import when the required class is included in current set
// of bindings.
if (inputClassNames.contains(binaryName)) {
@@ -138,14 +144,6 @@
return '$pathToCommon$pathToClass.dart';
}
- while (prefix.isNotEmpty) {
- final split = cutFromLast(prefix, '.');
- final left = split[0];
- if (importMap.containsKey(prefix)) {
- return importMap[prefix]!;
- }
- prefix = left;
- }
return null;
}
diff --git a/pkgs/jnigen/lib/src/config/config_types.dart b/pkgs/jnigen/lib/src/config/config_types.dart
index 6030436..e74718c 100644
--- a/pkgs/jnigen/lib/src/config/config_types.dart
+++ b/pkgs/jnigen/lib/src/config/config_types.dart
@@ -5,12 +5,20 @@
import 'dart:io';
import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
import '../elements/elements.dart';
+import '../logging/logging.dart';
+import '../util/find_package.dart';
import 'config_exception.dart';
import 'filters.dart';
import 'yaml_reader.dart';
+/// Modify this when symbols file format changes according to pub_semver.
+final _currentVersion = Version(1, 0, 0);
+
/// Configuration for dependencies to be downloaded using maven.
///
/// Dependency names should be listed in groupId:artifactId:version format.
@@ -209,9 +217,9 @@
this.structure = OutputStructure.packageStructure,
}) {
if (structure == OutputStructure.singleFile) {
- if (!path.toFilePath().endsWith('.dart')) {
+ if (p.extension(path.toFilePath()) != '.dart') {
throw ConfigException(
- 'output path must end with ".dart" in single file mode');
+ 'Dart\'s output path must end with ".dart" in single file mode.');
}
} else {
_ensureIsDirectory('Dart output path', path);
@@ -225,19 +233,32 @@
OutputStructure structure;
}
+class SymbolsOutputConfig {
+ /// Path to write generated Dart bindings.
+ final Uri path;
+
+ SymbolsOutputConfig(this.path) {
+ if (p.extension(path.toFilePath()) != '.yaml') {
+ throw ConfigException('Symbol\'s output path must end with ".yaml".');
+ }
+ }
+}
+
class OutputConfig {
OutputConfig({
+ required this.dartConfig,
this.bindingsType = BindingsType.cBased,
this.cConfig,
- required this.dartConfig,
+ this.symbolsConfig,
}) {
if (bindingsType == BindingsType.cBased && cConfig == null) {
- throw ConfigException("C output config must be provided!");
+ throw ConfigException('C output config must be provided!');
}
}
BindingsType bindingsType;
DartCodeOutputConfig dartConfig;
CCodeOutputConfig? cConfig;
+ SymbolsOutputConfig? symbolsConfig;
}
class BindingExclusions {
@@ -257,12 +278,12 @@
this.sourcePath,
this.classPath,
this.preamble,
- this.importMap,
this.androidSdkConfig,
this.mavenDownloads,
this.summarizerOptions,
this.logLevel = Level.INFO,
this.dumpJsonTo,
+ this.imports,
});
/// Output configuration for generated bindings
@@ -296,18 +317,6 @@
/// Common text to be pasted on top of generated C and Dart files.
final String? preamble;
- /// Additional java package -> dart package mappings (Experimental).
- ///
- /// a mapping com.abc.package -> 'package:my_package.dart/my_import.dart'
- /// in this configuration suggests that any reference to a type from
- /// com.abc.package shall resolve to an import of 'package:my_package.dart'.
- ///
- /// This can be as granular
- /// `com.abc.package.Class -> 'package:abc/abc.dart'`
- /// or coarse
- /// `com.abc.package` -> 'package:abc/abc.dart'`
- final Map<String, String>? importMap;
-
/// Whether or not to change Kotlin's suspend functions to Dart async ones.
///
/// This will remove the final Continuation argument.
@@ -321,9 +330,119 @@
/// along with their transitive dependencies.
final MavenDownloads? mavenDownloads;
- /// Additional options for the summarizer component
+ /// Additional options for the summarizer component.
final SummarizerOptions? summarizerOptions;
+ /// List of dependencies.
+ final List<Uri>? imports;
+
+ /// Call [importClasses] before using this.
+ late final Map<String, ClassDecl> importedClasses;
+
+ Future<void> importClasses() async {
+ importedClasses = {};
+ for (final import in [
+ // Implicitly importing package:jni symbols.
+ Uri.parse('package:jni/jni_symbols.yaml'),
+ ...?imports,
+ ]) {
+ // Getting the actual uri in case of package uris.
+ final Uri yamlUri;
+ final String importPath;
+ if (import.scheme == 'package') {
+ final packageName = import.pathSegments.first;
+ final packageRoot = await findPackageRoot(packageName);
+ if (packageRoot == null) {
+ log.fatal('package:$packageName was not found.');
+ }
+ yamlUri = packageRoot
+ .resolve('lib/')
+ .resolve(import.pathSegments.sublist(1).join('/'));
+ importPath = 'package:$packageName';
+ } else {
+ yamlUri = import;
+ importPath = ([...import.pathSegments]..removeLast()).join('/');
+ }
+ log.finest('Parsing yaml file in url $yamlUri.');
+ final YamlMap yaml;
+ try {
+ final symbolsFile = File.fromUri(yamlUri);
+ final content = symbolsFile.readAsStringSync();
+ yaml = loadYaml(content, sourceUrl: yamlUri);
+ } on UnsupportedError catch (_) {
+ log.fatal('Could not reference "$import".');
+ } catch (e, s) {
+ log.warning(e);
+ log.warning(s);
+ log.fatal('Error while parsing yaml file "$import".');
+ }
+ final version = Version.parse(yaml['version'] as String);
+ if (!VersionConstraint.compatibleWith(_currentVersion).allows(version)) {
+ log.fatal('"$import" is version "$version" which is not compatible with'
+ 'the current JNIgen symbols version $_currentVersion');
+ }
+ final files = yaml['files'] as YamlMap;
+ for (final entry in files.entries) {
+ final filePath = entry.key as String;
+ final classes = entry.value as YamlMap;
+ for (final classEntry in classes.entries) {
+ final binaryName = classEntry.key as String;
+ final decl = classEntry.value as YamlMap;
+ if (importedClasses.containsKey(binaryName)) {
+ log.fatal(
+ 'Re-importing "$binaryName" in "$import".\n'
+ 'Try hiding the class in import.',
+ );
+ }
+ final classDecl = ClassDecl(
+ simpleName: binaryName.split('.').last,
+ packageName: (binaryName.split('.')..removeLast()).join('.'),
+ binaryName: binaryName,
+ )
+ ..path = '$importPath/$filePath'
+ ..finalName = decl['name']
+ ..typeClassName = decl['type_class']
+ ..superCount = decl['super_count'];
+ for (final typeParamEntry
+ in ((decl['type_params'] as YamlMap?)?.entries) ??
+ <MapEntry<dynamic, dynamic>>[]) {
+ final typeParamName = typeParamEntry.key as String;
+ final bounds = (typeParamEntry.value as YamlMap).entries.map((e) {
+ final boundName = e.key as String;
+ // Can only be DECLARED or TYPE_VARIABLE
+ if (!['DECLARED', 'TYPE_VARIABLE'].contains(e.value)) {
+ log.fatal(
+ 'Unsupported bound kind "${e.value}" for bound "$boundName" '
+ 'in type parameter "$typeParamName" '
+ 'of "$binaryName".',
+ );
+ }
+ final boundKind = (e.value as String) == 'DECLARED'
+ ? Kind.declared
+ : Kind.typeVariable;
+ final ReferredType type;
+ if (boundKind == Kind.declared) {
+ type =
+ DeclaredType(binaryName: boundName, simpleName: boundName);
+ } else {
+ type = TypeVar(name: boundName);
+ }
+ return TypeUsage(
+ shorthand: boundName, kind: boundKind, typeJson: {})
+ ..type = type;
+ }).toList();
+ classDecl.allTypeParams.add(
+ TypeParam(name: typeParamName, bounds: bounds),
+ );
+ }
+ classDecl.methodNumsAfterRenaming =
+ (decl['methods'] as YamlMap?)?.cast() ?? {};
+ importedClasses[binaryName] = classDecl;
+ }
+ }
+ }
+ }
+
/// Directory containing the YAML configuration file, if any.
Uri? get configRoot => _configRoot;
Uri? _configRoot;
@@ -337,8 +456,6 @@
static final _levels = Map.fromEntries(
Level.LEVELS.map((l) => MapEntry(l.name.toLowerCase(), l)));
- static List<Uri>? _toUris(List<String>? paths) =>
- paths?.map(Uri.file).toList();
static Config parseArgs(List<String> args) {
final prov = YamlReader.parseArgs(args);
@@ -354,9 +471,6 @@
return res;
}
- Uri? directoryUri(String? path) =>
- path != null ? Uri.directory(path) : null;
-
MemberFilter<T>? regexFilter<T extends ClassMember>(String property) {
final exclusions = prov.getStringList(property);
if (exclusions == null) return null;
@@ -395,14 +509,13 @@
configRoot?.resolve(reference).toFilePath() ?? reference;
final config = Config(
- sourcePath: _toUris(prov.getPathList(_Props.sourcePath)),
- classPath: _toUris(prov.getPathList(_Props.classPath)),
+ sourcePath: prov.getPathList(_Props.sourcePath),
+ classPath: prov.getPathList(_Props.classPath),
classes: must(prov.getStringList, [], _Props.classes),
summarizerOptions: SummarizerOptions(
extraArgs: prov.getStringList(_Props.summarizerArgs) ?? const [],
backend: prov.getString(_Props.backend),
- workingDirectory:
- directoryUri(prov.getPath(_Props.summarizerWorkingDir)),
+ workingDirectory: prov.getPath(_Props.summarizerWorkingDir),
),
exclude: BindingExclusions(
methods: regexFilter<Method>(_Props.excludeMethods),
@@ -417,27 +530,32 @@
cConfig: prov.hasValue(_Props.cCodeOutputConfig)
? CCodeOutputConfig(
libraryName: must(prov.getString, '', _Props.libraryName),
- path: Uri.file(must(prov.getPath, '.', _Props.cRoot)),
+ path: must(prov.getPath, Uri.parse('.'), _Props.cRoot),
subdir: prov.getString(_Props.cSubdir),
)
: null,
dartConfig: DartCodeOutputConfig(
- path: Uri.file(must(prov.getPath, '.', _Props.dartRoot)),
+ path: must(prov.getPath, Uri.parse('.'), _Props.dartRoot),
structure: getOutputStructure(
prov.getString(_Props.outputStructure),
OutputStructure.packageStructure,
),
),
+ symbolsConfig: prov.hasValue(_Props.symbolsOutputConfig)
+ ? SymbolsOutputConfig(
+ must(prov.getPath, Uri.parse('.'), _Props.symbolsOutputConfig),
+ )
+ : null,
),
preamble: prov.getString(_Props.preamble),
- importMap: prov.getStringMap(_Props.importMap),
+ imports: prov.getPathList(_Props.import),
mavenDownloads: prov.hasValue(_Props.mavenDownloads)
? MavenDownloads(
sourceDeps: prov.getStringList(_Props.sourceDeps) ?? const [],
- sourceDir: prov.getPath(_Props.mavenSourceDir) ??
+ sourceDir: prov.getPath(_Props.mavenSourceDir)?.toFilePath() ??
resolveFromConfigRoot(MavenDownloads.defaultMavenSourceDir),
jarOnlyDeps: prov.getStringList(_Props.jarOnlyDeps) ?? const [],
- jarDir: prov.getPath(_Props.mavenJarDir) ??
+ jarDir: prov.getPath(_Props.mavenJarDir)?.toFilePath() ??
resolveFromConfigRoot(MavenDownloads.defaultMavenJarDir),
)
: null,
@@ -496,11 +614,12 @@
static const suspendFunToAsync = 'suspend_fun_to_async';
- static const importMap = 'import_map';
+ static const import = 'import';
static const outputConfig = 'output';
static const bindingsType = '$outputConfig.bindings_type';
static const cCodeOutputConfig = '$outputConfig.c';
static const dartCodeOutputConfig = '$outputConfig.dart';
+ static const symbolsOutputConfig = '$outputConfig.symbols';
static const cRoot = '$cCodeOutputConfig.path';
static const cSubdir = '$cCodeOutputConfig.subdir';
static const dartRoot = '$dartCodeOutputConfig.path';
diff --git a/pkgs/jnigen/lib/src/config/yaml_reader.dart b/pkgs/jnigen/lib/src/config/yaml_reader.dart
index ecae2cb..1cb1f46 100644
--- a/pkgs/jnigen/lib/src/config/yaml_reader.dart
+++ b/pkgs/jnigen/lib/src/config/yaml_reader.dart
@@ -86,8 +86,7 @@
/// Same as [getString] but path is resolved relative to YAML config if it's
/// from YAML config.
- String? getPath(String property) =>
- _config.optionalPath(property)?.toFilePath();
+ Uri? getPath(String property) => _config.optionalPath(property);
List<String>? getStringList(String property) => _config.optionalStringList(
property,
@@ -95,14 +94,11 @@
combineAllConfigs: false,
);
- List<String>? getPathList(String property) {
- final configResult = _config.optionalPathList(
- property,
- combineAllConfigs: false,
- splitCliPattern: ';',
- );
- return configResult?.map((e) => e.path).toList();
- }
+ List<Uri>? getPathList(String property) => _config.optionalPathList(
+ property,
+ combineAllConfigs: false,
+ splitCliPattern: ';',
+ );
String? getOneOf(String property, Set<String> values) =>
_config.optionalString(property, validValues: values);
diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart
index 7793c1f..347c818 100644
--- a/pkgs/jnigen/lib/src/elements/elements.dart
+++ b/pkgs/jnigen/lib/src/elements/elements.dart
@@ -29,7 +29,7 @@
}
class Classes implements Element<Classes> {
- const Classes._(this.decls);
+ const Classes(this.decls);
final Map<String, ClassDecl> decls;
@@ -39,7 +39,7 @@
final classDecl = ClassDecl.fromJson(declJson);
decls[classDecl.binaryName] = classDecl;
}
- return Classes._(decls);
+ return Classes(decls);
}
@override
@@ -115,6 +115,12 @@
@JsonKey(includeFromJson: false)
late final String finalName;
+ /// Name of the type class.
+ ///
+ /// Populated by [Renamer].
+ @JsonKey(includeFromJson: false)
+ late final String typeClassName;
+
/// Unique name obtained by renaming conflicting names with a number.
///
/// This is used by C bindings instead of fully qualified name to reduce
@@ -130,6 +136,18 @@
@JsonKey(includeFromJson: false)
List<TypeParam> allTypeParams = const [];
+ /// The path which this class is generated in.
+ ///
+ /// Populated by [Linker].
+ @JsonKey(includeFromJson: false)
+ late final String path;
+
+ /// The numeric suffix of the methods.
+ ///
+ /// Populated by [Renamer].
+ @JsonKey(includeFromJson: false)
+ late final Map<String, int> methodNumsAfterRenaming;
+
@override
String toString() {
return 'Java class declaration for $binaryName';
@@ -137,28 +155,6 @@
String get signature => 'L$internalName;';
- static final object = ClassDecl(
- binaryName: 'java.lang.Object',
- packageName: 'java.lang',
- simpleName: 'Object',
- )
- ..finalName = 'JObject'
- ..superCount = 0;
-
- static final string = ClassDecl(
- superclass: TypeUsage.object,
- binaryName: 'java.lang.String',
- packageName: 'java.lang',
- simpleName: 'String',
- )
- ..finalName = 'JString'
- ..superCount = 1;
-
- static final predefined = {
- 'java.lang.Object': object,
- 'java.lang.String': string,
- };
-
factory ClassDecl.fromJson(Map<String, dynamic> json) =>
_$ClassDeclFromJson(json);
@@ -172,6 +168,8 @@
@override
String get name => finalName;
+
+ bool isObject() => superCount == 0;
}
@JsonEnum()
@@ -196,15 +194,10 @@
required this.typeJson,
});
- static TypeUsage object = () {
- final typeUsage = TypeUsage.fromJson({
- "shorthand": "java.lang.Object",
- "kind": "DECLARED",
- "type": {"binaryName": "java.lang.Object", "simpleName": "Object"}
- });
- (typeUsage.type as DeclaredType).classDecl = ClassDecl.object;
- return typeUsage;
- }();
+ static TypeUsage object = TypeUsage(
+ kind: Kind.declared, shorthand: 'JObject', typeJson: {})
+ ..type =
+ (DeclaredType(binaryName: 'java.lang.Object', simpleName: 'Object'));
final String shorthand;
final Kind kind;
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart
index ab184da..5c9dc9b 100644
--- a/pkgs/jnigen/lib/src/generate_bindings.dart
+++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -34,10 +34,9 @@
log.fatal(e.message);
}
- classes
- ..accept(Excluder(config))
- ..accept(Linker(config))
- ..accept(Renamer(config));
+ classes.accept(Excluder(config));
+ await classes.accept(Linker(config));
+ classes.accept(Renamer(config));
final cBased = config.outputConfig.bindingsType == BindingsType.cBased;
if (cBased) {
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml
index 5cde347..a76d658 100644
--- a/pkgs/jnigen/pubspec.yaml
+++ b/pkgs/jnigen/pubspec.yaml
@@ -14,6 +14,7 @@
json_annotation: ^4.8.0
package_config: ^2.1.0
path: ^1.8.0
+ pub_semver: ^2.1.4
args: ^2.3.0
yaml: ^3.1.0
logging: ^1.0.2
diff --git a/pkgs/jnigen/test/config_test.dart b/pkgs/jnigen/test/config_test.dart
index 97fbb3d..8538ac1 100644
--- a/pkgs/jnigen/test/config_test.dart
+++ b/pkgs/jnigen/test/config_test.dart
@@ -31,10 +31,12 @@
reason: "cRoot");
expect(a.outputConfig.dartConfig.path, equals(b.outputConfig.dartConfig.path),
reason: "dartRoot");
+ expect(a.outputConfig.symbolsConfig?.path,
+ equals(b.outputConfig.symbolsConfig?.path),
+ reason: "symbolsRoot");
expect(a.sourcePath, equals(b.sourcePath), reason: "sourcePath");
expect(a.classPath, equals(b.classPath), reason: "classPath");
expect(a.preamble, equals(b.preamble), reason: "preamble");
- expect(a.importMap, equals(b.importMap), reason: "importMap");
final am = a.mavenDownloads;
final bm = b.mavenDownloads;
if (am != null) {
diff --git a/pkgs/jnigen/test/package_resolver_test.dart b/pkgs/jnigen/test/package_resolver_test.dart
index 98c0d2b..2f3a2ea 100644
--- a/pkgs/jnigen/test/package_resolver_test.dart
+++ b/pkgs/jnigen/test/package_resolver_test.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:jnigen/src/bindings/resolver.dart';
+import 'package:jnigen/src/elements/elements.dart';
import 'package:test/test.dart';
import 'test_util/test_util.dart';
@@ -17,21 +18,28 @@
void main() async {
await checkLocallyBuiltDependencies();
final resolver = Resolver(
- importMap: {
- 'org.apache.pdfbox': 'package:pdfbox/pdfbox.dart',
- 'android.os.Process': 'package:android/os.dart',
- },
- currentClass: 'a.b.N',
- inputClassNames: {
- 'a.b.C',
- 'a.b.c.D',
- 'a.b.c.d.E',
- 'a.X',
- 'e.f.G',
- 'e.F',
- 'a.g.Y',
- 'a.m.n.P'
- });
+ importedClasses: {
+ 'org.apache.pdfbox.pdmodel.PDDocument': ClassDecl(
+ binaryName: 'org.apache.pdfbox.pdmodel.PDDocument',
+ simpleName: 'PDDocument',
+ )..path = 'package:pdfbox/pdfbox.dart',
+ 'android.os.Process': ClassDecl(
+ binaryName: 'android.os.Process',
+ simpleName: 'Process',
+ )..path = 'package:android/os.dart',
+ },
+ currentClass: 'a.b.N',
+ inputClassNames: {
+ 'a.b.C',
+ 'a.b.c.D',
+ 'a.b.c.d.E',
+ 'a.X',
+ 'e.f.G',
+ 'e.F',
+ 'a.g.Y',
+ 'a.m.n.P'
+ },
+ );
final tests = [
// Absolute imports resolved using import map
@@ -64,6 +72,8 @@
test(
'resolve $binaryName',
() => expect(
- resolver.resolvePrefix(binaryName), equals(testCase.expectedName)));
+ resolver.resolvePrefix(
+ ClassDecl(binaryName: binaryName, simpleName: '')..path = ''),
+ equals(testCase.expectedName)));
}
}