blob: e762fee59b3a970be1a7e87b08bc38edccd2fabb [file]
// 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:jnigen/src/bindings/bindings.dart';
import 'package:jnigen/src/elements/elements.dart';
import 'package:jnigen/src/config/config.dart';
import 'package:jnigen/src/logging/logging.dart';
import 'package:jnigen/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();
log.info('Creating dart init file ...');
final initFileUri = dartRoot.resolve(_initFileName);
final initFile = await File.fromUri(initFileUri).create(recursive: true);
var initCode = DartPreludes.initFile(config.libraryName);
if (preamble != null) {
initCode = '$preamble\n$initCode';
}
await initFile.writeAsString(initCode, flush: true);
final subdir = config.cSubdir ?? '.';
final cFileRelativePath = '$subdir/$libraryName.c';
final cFile = await File.fromUri(cRoot.resolve(cFileRelativePath))
.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);
log.info('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.JniString'});
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 jniLookup;\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();
log.info('Running dart format...');
final formatRes =
await Process.run('dart', ['format', dartRoot.toFilePath()]);
// if negative exit code, likely due to an interrupt.
if (formatRes.exitCode > 0) {
log.fatal('Dart format completed with exit code ${formatRes.exitCode} '
'This usually means there\'s a syntax error in bindings.\n'
'Please look at the generated files and report a bug.');
}
log.info('Copying auxiliary files...');
await _copyFileFromPackage(
'jni', 'src/dartjni.h', cRoot.resolve('$subdir/dartjni.h'));
await _copyFileFromPackage(
'jnigen', 'cmake/CMakeLists.txt.tmpl', cRoot.resolve('CMakeLists.txt'),
transform: (s) {
return s
.replaceAll('{{LIBRARY_NAME}}', libraryName)
.replaceAll('{{SUBDIR}}', subdir);
});
log.info('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 {
log.warning('package $package not found! '
'skipped copying ${target.toFilePath()}');
}
}
}