[jnigen] logging (https://github.com/dart-lang/jnigen/issues/48)
diff --git a/pkgs/jnigen/lib/src/bindings/dart_bindings.dart b/pkgs/jnigen/lib/src/bindings/dart_bindings.dart index 0f205c2..8367161 100644 --- a/pkgs/jnigen/lib/src/bindings/dart_bindings.dart +++ b/pkgs/jnigen/lib/src/bindings/dart_bindings.dart
@@ -2,10 +2,9 @@ // 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/elements/elements.dart'; import 'package:jnigen/src/config/config.dart'; +import 'package:jnigen/src/logging/logging.dart'; import 'package:jnigen/src/util/rename_conflict.dart'; import 'symbol_resolver.dart'; @@ -41,7 +40,9 @@ if (!decl.isIncluded) { return ''; } - return _class(decl); + final bindings = _class(decl); + log.finest('generated bindings for class ${decl.binaryName}'); + return bindings; } String _class(ClassDecl decl) { @@ -72,7 +73,7 @@ s.write(_field(decl, field)); s.writeln(); } on SkipException catch (e) { - stderr.writeln('skip field ${decl.binaryName}#${field.name}: ' + log.info('skip field ${decl.binaryName}#${field.name}: ' '${e.message}'); } } @@ -85,7 +86,7 @@ s.write(_method(decl, method)); s.writeln(); } on SkipException catch (e) { - stderr.writeln('skip field ${decl.binaryName}#${method.name}: ' + log.info('skip field ${decl.binaryName}#${method.name}: ' '${e.message}'); } }
diff --git a/pkgs/jnigen/lib/src/bindings/preprocessor.dart b/pkgs/jnigen/lib/src/bindings/preprocessor.dart index dabe584..61c2141 100644 --- a/pkgs/jnigen/lib/src/bindings/preprocessor.dart +++ b/pkgs/jnigen/lib/src/bindings/preprocessor.dart
@@ -2,11 +2,11 @@ // 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/elements/elements.dart'; import 'package:jnigen/src/config/config.dart'; +import 'package:jnigen/src/logging/logging.dart'; import 'package:jnigen/src/util/rename_conflict.dart'; + import 'common.dart'; /// Preprocessor which fills information needed by both Dart and C generators. @@ -22,7 +22,7 @@ if (decl.isPreprocessed) return; if (!_isClassIncluded(decl, config)) { decl.isIncluded = false; - stdout.writeln('exclude class ${decl.binaryName}'); + log.info('exclude class ${decl.binaryName}'); decl.isPreprocessed = true; return; } @@ -37,11 +37,12 @@ decl.nameCounts.addAll(superclass.nameCounts); } } - + log.finest('Superclass of ${decl.binaryName} resolved to ' + '${superclass?.binaryName}'); for (var field in decl.fields) { if (!_isFieldIncluded(decl, field, config)) { field.isIncluded = false; - stderr.writeln('exclude ${decl.binaryName}#${field.name}'); + log.info('exclude ${decl.binaryName}#${field.name}'); continue; } field.finalName = renameConflict(decl.nameCounts, field.name); @@ -50,7 +51,7 @@ for (var method in decl.methods) { if (!_isMethodIncluded(decl, method, config)) { method.isIncluded = false; - stderr.writeln('exclude method ${decl.binaryName}#${method.name}'); + log.info('exclude method ${decl.binaryName}#${method.name}'); continue; } var realName = method.name; @@ -76,6 +77,7 @@ } } decl.isPreprocessed = true; + log.finest('preprocessed ${decl.binaryName}'); } static bool _isFieldIncluded(ClassDecl decl, Field field, Config config) =>
diff --git a/pkgs/jnigen/lib/src/bindings/symbol_resolver.dart b/pkgs/jnigen/lib/src/bindings/symbol_resolver.dart index 530e570..45b9e1a 100644 --- a/pkgs/jnigen/lib/src/bindings/symbol_resolver.dart +++ b/pkgs/jnigen/lib/src/bindings/symbol_resolver.dart
@@ -6,6 +6,9 @@ // a locally meaningful name, when creating dart bindings import 'dart:math'; + +import 'package:jnigen/src/logging/logging.dart'; + import 'package:jnigen/src/util/name_utils.dart'; abstract class SymbolResolver { @@ -29,8 +32,9 @@ final Map<String, String> _importedNameToPackage = {}; final Map<String, String> _packageToImportedName = {}; - // return null if type's package cannot be resolved - // else return the fully qualified name of type + + /// Returns the dart name of the [binaryName] in current translation context, + /// or `null` if the name cannot be resolved. @override String? resolve(String binaryName) { if (predefined.containsKey(binaryName)) { @@ -57,6 +61,7 @@ } final packageImport = getImport(package, binaryName); + log.finest('$package resolved to $packageImport for $binaryName'); if (packageImport == null) { return null; } @@ -67,6 +72,8 @@ 'qualified binaryName'); } + // We always name imports with an underscore suffix, so that they can be + // never shadowed by a parameter or local variable. var importedName = '${pkgName}_'; int suffix = 0; while (_importedNameToPackage.containsKey(importedName)) {
diff --git a/pkgs/jnigen/lib/src/config/config.dart b/pkgs/jnigen/lib/src/config/config.dart index 4094e3c..136845f 100644 --- a/pkgs/jnigen/lib/src/config/config.dart +++ b/pkgs/jnigen/lib/src/config/config.dart
@@ -9,6 +9,8 @@ import 'yaml_reader.dart'; import 'filters.dart'; +import 'package:logging/logging.dart'; + /// Configuration for dependencies to be downloaded using maven. /// /// Dependency names should be listed in groupId:artifactId:version format. @@ -142,6 +144,7 @@ this.androidSdkConfig, this.mavenDownloads, this.summarizerOptions, + this.logLevel = Level.INFO, this.dumpJsonTo, }); @@ -205,8 +208,15 @@ /// Additional options for the summarizer component SummarizerOptions? summarizerOptions; + /// Log verbosity. The possible values in decreasing order of verbosity + /// are verbose > debug > info > warning > error. Defaults to [LogLevel.info] + Level logLevel = Level.INFO; + + /// File to which JSON summary is written before binding generation. String? dumpJsonTo; + static final _levels = Map.fromEntries( + Level.LEVELS.map((l) => MapEntry(l.name.toLowerCase(), l))); static Uri? _toDirUri(String? path) => path != null ? Uri.directory(path) : null; static List<Uri>? _toUris(List<String>? paths) => @@ -231,7 +241,7 @@ for (var exclusion in exclusions) { final split = exclusion.split('#'); if (split.length != 2) { - throw FormatException('Error parsing exclusion: "$exclusion"; ' + throw FormatException('Error parsing exclusion: "$exclusion": ' 'expected to be in binaryName#member format.'); } filters.add(MemberNameFilter<T>.exclude( @@ -252,6 +262,15 @@ return root; } + Level logLevelFromString(String? levelName) { + if (levelName == null) return Level.INFO; + final level = _levels[levelName.toLowerCase()]; + if (level == null) { + throw ConfigError('Not a valid logging level: $levelName'); + } + return level; + } + final config = Config( sourcePath: _toUris(prov.getStringList(_Props.sourcePath)), classPath: _toUris(prov.getStringList(_Props.classPath)), @@ -295,6 +314,12 @@ androidExample: prov.getString(_Props.androidExample), ) : null, + logLevel: logLevelFromString( + prov.getOneOf( + _Props.logLevel, + {'error', 'warning', 'info', 'debug', 'verbose'}, + ), + ), ); if (missingValues.isNotEmpty) { stderr.write('Following config values are required but not provided\n' @@ -333,6 +358,7 @@ static const dartRoot = 'dart_root'; static const preamble = 'preamble'; static const libraryName = 'library_name'; + static const logLevel = 'log_level'; static const mavenDownloads = 'maven_downloads'; static const sourceDeps = '$mavenDownloads.source_deps';
diff --git a/pkgs/jnigen/lib/src/generate_bindings.dart b/pkgs/jnigen/lib/src/generate_bindings.dart index c6f410e..b6a64f5 100644 --- a/pkgs/jnigen/lib/src/generate_bindings.dart +++ b/pkgs/jnigen/lib/src/generate_bindings.dart
@@ -10,8 +10,11 @@ import 'config/config.dart'; import 'tools/tools.dart'; import 'writers/writers.dart'; +import 'logging/logging.dart'; Future<void> generateJniBindings(Config config) async { + setLoggingLevel(config.logLevel); + await buildSummarizerIfNotExists(); final summarizer = SummarizerCommand( @@ -72,7 +75,7 @@ try { input = await summarizer.getInputStream(); } on Exception catch (e) { - stderr.writeln('error obtaining API summary: $e'); + log.fatal('Cannot obtain API summary: $e'); return; } final stream = JsonDecoder().bind(Utf8Decoder().bind(input)); @@ -80,11 +83,11 @@ try { json = await stream.single; } on Exception catch (e) { - stderr.writeln('error while parsing summary: $e'); + log.fatal('Cannot parse summary: $e'); return; } if (json == null) { - stderr.writeln('error: expected JSON element from summarizer.'); + log.fatal('Expected JSON element from summarizer.'); return; } final list = json as List; @@ -93,6 +96,6 @@ await outputWriter.writeBindings(list.map((c) => ClassDecl.fromJson(c))); } on Exception catch (e, trace) { stderr.writeln(trace); - stderr.writeln('error writing bindings: $e'); + log.fatal('Error while writing bindings: $e'); } }
diff --git a/pkgs/jnigen/lib/src/logging/logging.dart b/pkgs/jnigen/lib/src/logging/logging.dart new file mode 100644 index 0000000..526981e --- /dev/null +++ b/pkgs/jnigen/lib/src/logging/logging.dart
@@ -0,0 +1,30 @@ +// 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:logging/logging.dart'; + +const _ansiRed = '\x1b[31m'; +const _ansiDefault = '\x1b[39;49m'; + +Logger log = Logger('jnigen'); + +void setLoggingLevel(Level level) { + Logger.root.level = level; + Logger.root.onRecord.listen((r) { + var message = '(${r.loggerName}) ${r.level.name}: ${r.message}'; + if (level == Level.SHOUT || level == Level.SEVERE) { + message = '$_ansiRed$message$_ansiDefault'; + } + stderr.writeln(message); + }); +} + +extension FatalErrors on Logger { + void fatal(Object? message, {int exitCode = 2}) { + message = '${_ansiRed}Fatal: $message$_ansiDefault'; + stderr.writeln(message); + exit(exitCode); + } +}
diff --git a/pkgs/jnigen/lib/src/summary/summary.dart b/pkgs/jnigen/lib/src/summary/summary.dart index 5a7d714..1282ee2 100644 --- a/pkgs/jnigen/lib/src/summary/summary.dart +++ b/pkgs/jnigen/lib/src/summary/summary.dart
@@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:io'; +import 'package:jnigen/src/logging/logging.dart'; import 'package:jnigen/src/util/command_output.dart'; /// A command based summary source which calls the ApiSummarizer command. @@ -79,7 +80,7 @@ args.addAll(extraArgs); args.addAll(classes); - stderr.writeln('[exec] $exec ${args.join(' ')}'); + log.info('execute $exec ${args.join(' ')}'); final proc = await Process.start(exec, args, workingDirectory: workingDirectory?.toFilePath() ?? '.'); prefixedCommandOutputStream('[ApiSummarizer]', proc.stderr)
diff --git a/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart b/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart index ed78566..b903bd6 100644 --- a/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart +++ b/pkgs/jnigen/lib/src/tools/android_sdk_tools.dart
@@ -5,6 +5,8 @@ import 'dart:io'; import 'package:path/path.dart'; +import 'package:jnigen/src/logging/logging.dart'; + class AndroidSdkTools { /// get path for android API sources static Future<String?> _getVersionDir( @@ -25,7 +27,9 @@ static Future<String?> getAndroidSourcesPath( {String? sdkRoot, required List<int> versionOrder}) async { - return _getVersionDir('sources', sdkRoot, versionOrder); + final dir = _getVersionDir('sources', sdkRoot, versionOrder); + log.info('Found sources at $dir'); + return dir; } static Future<String?> _getFile(String relative, String file, String? sdkRoot, @@ -34,6 +38,7 @@ if (platform == null) return null; final filePath = join(platform, file); if (await File(filePath).exists()) { + log.info('Found $filePath'); return filePath; } return null; @@ -68,7 +73,7 @@ /// If current project is not directly buildable by gradle, eg: a plugin, /// a relative path to other project can be specified using [androidProject]. static List<String> getGradleClasspaths([String androidProject = '.']) { - stderr.writeln('trying to obtain gradle classpaths...'); + log.info('trying to obtain gradle classpaths...'); final android = join(androidProject, 'android'); final buildGradle = join(android, 'build.gradle'); final buildGradleOld = join(android, 'build.gradle.old'); @@ -76,20 +81,23 @@ final script = origBuild.readAsStringSync(); origBuild.renameSync(buildGradleOld); origBuild.createSync(); + log.finer('Writing temporary gradle script with stub function...'); origBuild.writeAsStringSync('$script\n$_gradleListDepsFunction\n'); + log.finer('Running gradle wrapper...'); final procRes = Process.runSync('./gradlew', ['-q', 'listDependencies'], workingDirectory: android); + log.finer('Restoring build scripts'); origBuild.writeAsStringSync(script); File(buildGradleOld).deleteSync(); if (procRes.exitCode != 0) { + final inAndroidProject = + (androidProject == '.') ? '' : ' in $androidProject'; throw Exception('\n\ngradle exited with exit code ${procRes.exitCode}\n' 'This can be related to a known issue with gradle. Please run ' - '`flutter build apk` in $androidProject and try again\n'); + '`flutter build apk`$inAndroidProject and try again\n'); } - final gradleClassPaths = (procRes.stdout as String).split('\n'); - if (gradleClassPaths.last.isEmpty) { - gradleClassPaths.removeLast(); - } - return gradleClassPaths; + final classpaths = (procRes.stdout as String).trim().split('\n'); + log.info('Found release build classpath with ${classpaths.length} entries'); + return classpaths; } }
diff --git a/pkgs/jnigen/lib/src/tools/build_summarizer.dart b/pkgs/jnigen/lib/src/tools/build_summarizer.dart index b018327..d092549 100644 --- a/pkgs/jnigen/lib/src/tools/build_summarizer.dart +++ b/pkgs/jnigen/lib/src/tools/build_summarizer.dart
@@ -7,6 +7,7 @@ import 'package:path/path.dart'; import 'package:jnigen/src/util/find_package.dart'; +import 'package:jnigen/src/logging/logging.dart'; final toolPath = join('.', '.dart_tool', 'jnigen'); final mvnTargetDir = join(toolPath, 'target'); @@ -16,23 +17,21 @@ Future<void> buildApiSummarizer() async { final pkg = await findPackageRoot('jnigen'); if (pkg == null) { - stderr.writeln('package jnigen not found!'); - exitCode = 2; + log.fatal('package jnigen not found!'); 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); + final mvnArgs = [ + '--batch-mode', + '--update-snapshots', + '-f', + pom.toFilePath(), + 'assembly:assembly' + ]; + log.info('execute mvn $mvnArgs'); + final mvnProc = await Process.start('mvn', mvnArgs, + workingDirectory: toolPath, mode: ProcessStartMode.inheritStdio); await mvnProc.exitCode; File(targetJarFile).renameSync(jarFile); Directory(mvnTargetDir).deleteSync(recursive: true); @@ -44,17 +43,17 @@ await isPackageModifiedAfter( 'jnigen', await File(jarFile).lastModified(), 'java/'); if (isJarStale) { - stderr.writeln('Rebuilding ApiSummarizer component since sources ' + log.info('Rebuilding ApiSummarizer component since sources ' 'have changed. This might take some time.'); } if (!jarExists) { - stderr.write('Building ApiSummarizer component. ' + log.info('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..'); + log.info('ApiSummarizer.jar exists. Skipping build..'); } }
diff --git a/pkgs/jnigen/lib/src/tools/maven_tools.dart b/pkgs/jnigen/lib/src/tools/maven_tools.dart index 52835d9..b121e39 100644 --- a/pkgs/jnigen/lib/src/tools/maven_tools.dart +++ b/pkgs/jnigen/lib/src/tools/maven_tools.dart
@@ -4,6 +4,8 @@ import 'dart:io'; +import 'package:jnigen/src/logging/logging.dart'; + /// This class provides some utility methods to download a sources / jars /// using maven along with transitive dependencies. class MavenTools { @@ -11,19 +13,10 @@ 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(" ")}'); + log.info('execute $exec ${args.join(" ")}'); final proc = await Process.start(exec, args, workingDirectory: workingDirectory, mode: ProcessStartMode.inheritStdio); @@ -33,7 +26,7 @@ static Future<void> _runMavenCommand( List<MavenDependency> deps, List<String> mvnArgs) async { final pom = _getStubPom(deps); - _verboseLog('using POM stub:\n$pom'); + log.finer('using POM stub:\n$pom'); await File(_tempPom).writeAsString(pom); await Directory(_tempTarget).create(); await _runCmd('mvn', ['-f', _tempPom, ...mvnArgs]);
diff --git a/pkgs/jnigen/lib/src/writers/writers.dart b/pkgs/jnigen/lib/src/writers/writers.dart index c1caeb7..c4f3843 100644 --- a/pkgs/jnigen/lib/src/writers/writers.dart +++ b/pkgs/jnigen/lib/src/writers/writers.dart
@@ -8,6 +8,7 @@ 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 { @@ -58,7 +59,7 @@ } final classNames = classesByName.keys.toSet(); - stderr.writeln('Creating dart init file ...'); + log.info('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), @@ -76,7 +77,7 @@ for (var packageName in packages.keys) { final relativeFileName = '${packageName.replaceAll('.', '/')}.dart'; final dartFileUri = dartRoot.resolve(relativeFileName); - stderr.writeln('Writing bindings for $packageName...'); + log.info('Writing bindings for $packageName...'); final dartFile = await File.fromUri(dartFileUri).create(recursive: true); final resolver = PackagePathResolver( config.importMap ?? const {}, packageName, classNames, @@ -107,15 +108,17 @@ await dartFileStream.close(); } await cFileStream.close(); - stderr.writeln('Running dart format...'); + log.info('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}'); + // 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.'); } - stderr.writeln('Copying auxiliary files...'); + log.info('Copying auxiliary files...'); await _copyFileFromPackage( 'jni', 'src/dartjni.h', cRoot.resolve('$subdir/dartjni.h')); await _copyFileFromPackage( @@ -125,7 +128,7 @@ .replaceAll('{{LIBRARY_NAME}}', libraryName) .replaceAll('{{SUBDIR}}', subdir); }); - stderr.writeln('Completed.'); + log.info('Completed.'); } Future<void> _copyFileFromPackage(String package, String relPath, Uri target, @@ -140,7 +143,7 @@ } await targetFile.writeAsString(source); } else { - stderr.writeln('package $package not found! ' + log.warning('package $package not found! ' 'skipped copying ${target.toFilePath()}'); } }
diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml index fd538fc..fe8defa 100644 --- a/pkgs/jnigen/pubspec.yaml +++ b/pkgs/jnigen/pubspec.yaml
@@ -16,6 +16,7 @@ path: ^1.8.0 args: ^2.3.0 yaml: ^3.1.0 + logging: ^1.0.2 dev_dependencies: lints: ^2.0.0