Skip to content

Commit b7f8f4b

Browse files
authored
[jnigen] logging (#48)
1 parent e022064 commit b7f8f4b

12 files changed

+137
-63
lines changed

pkgs/jnigen/lib/src/bindings/dart_bindings.dart

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:io';
6-
75
import 'package:jnigen/src/elements/elements.dart';
86
import 'package:jnigen/src/config/config.dart';
7+
import 'package:jnigen/src/logging/logging.dart';
98
import 'package:jnigen/src/util/rename_conflict.dart';
109

1110
import 'symbol_resolver.dart';
@@ -41,7 +40,9 @@ class DartBindingsGenerator {
4140
if (!decl.isIncluded) {
4241
return '';
4342
}
44-
return _class(decl);
43+
final bindings = _class(decl);
44+
log.finest('generated bindings for class ${decl.binaryName}');
45+
return bindings;
4546
}
4647

4748
String _class(ClassDecl decl) {
@@ -72,7 +73,7 @@ class DartBindingsGenerator {
7273
s.write(_field(decl, field));
7374
s.writeln();
7475
} on SkipException catch (e) {
75-
stderr.writeln('skip field ${decl.binaryName}#${field.name}: '
76+
log.info('skip field ${decl.binaryName}#${field.name}: '
7677
'${e.message}');
7778
}
7879
}
@@ -85,7 +86,7 @@ class DartBindingsGenerator {
8586
s.write(_method(decl, method));
8687
s.writeln();
8788
} on SkipException catch (e) {
88-
stderr.writeln('skip field ${decl.binaryName}#${method.name}: '
89+
log.info('skip field ${decl.binaryName}#${method.name}: '
8990
'${e.message}');
9091
}
9192
}

pkgs/jnigen/lib/src/bindings/preprocessor.dart

+8-6
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'dart:io';
6-
75
import 'package:jnigen/src/elements/elements.dart';
86
import 'package:jnigen/src/config/config.dart';
7+
import 'package:jnigen/src/logging/logging.dart';
98
import 'package:jnigen/src/util/rename_conflict.dart';
9+
1010
import 'common.dart';
1111

1212
/// Preprocessor which fills information needed by both Dart and C generators.
@@ -22,7 +22,7 @@ class ApiPreprocessor {
2222
if (decl.isPreprocessed) return;
2323
if (!_isClassIncluded(decl, config)) {
2424
decl.isIncluded = false;
25-
stdout.writeln('exclude class ${decl.binaryName}');
25+
log.info('exclude class ${decl.binaryName}');
2626
decl.isPreprocessed = true;
2727
return;
2828
}
@@ -37,11 +37,12 @@ class ApiPreprocessor {
3737
decl.nameCounts.addAll(superclass.nameCounts);
3838
}
3939
}
40-
40+
log.finest('Superclass of ${decl.binaryName} resolved to '
41+
'${superclass?.binaryName}');
4142
for (var field in decl.fields) {
4243
if (!_isFieldIncluded(decl, field, config)) {
4344
field.isIncluded = false;
44-
stderr.writeln('exclude ${decl.binaryName}#${field.name}');
45+
log.info('exclude ${decl.binaryName}#${field.name}');
4546
continue;
4647
}
4748
field.finalName = renameConflict(decl.nameCounts, field.name);
@@ -50,7 +51,7 @@ class ApiPreprocessor {
5051
for (var method in decl.methods) {
5152
if (!_isMethodIncluded(decl, method, config)) {
5253
method.isIncluded = false;
53-
stderr.writeln('exclude method ${decl.binaryName}#${method.name}');
54+
log.info('exclude method ${decl.binaryName}#${method.name}');
5455
continue;
5556
}
5657
var realName = method.name;
@@ -76,6 +77,7 @@ class ApiPreprocessor {
7677
}
7778
}
7879
decl.isPreprocessed = true;
80+
log.finest('preprocessed ${decl.binaryName}');
7981
}
8082

8183
static bool _isFieldIncluded(ClassDecl decl, Field field, Config config) =>

pkgs/jnigen/lib/src/bindings/symbol_resolver.dart

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
// a locally meaningful name, when creating dart bindings
77

88
import 'dart:math';
9+
10+
import 'package:jnigen/src/logging/logging.dart';
11+
912
import 'package:jnigen/src/util/name_utils.dart';
1013

1114
abstract class SymbolResolver {
@@ -29,8 +32,9 @@ class PackagePathResolver implements SymbolResolver {
2932

3033
final Map<String, String> _importedNameToPackage = {};
3134
final Map<String, String> _packageToImportedName = {};
32-
// return null if type's package cannot be resolved
33-
// else return the fully qualified name of type
35+
36+
/// Returns the dart name of the [binaryName] in current translation context,
37+
/// or `null` if the name cannot be resolved.
3438
@override
3539
String? resolve(String binaryName) {
3640
if (predefined.containsKey(binaryName)) {
@@ -57,6 +61,7 @@ class PackagePathResolver implements SymbolResolver {
5761
}
5862

5963
final packageImport = getImport(package, binaryName);
64+
log.finest('$package resolved to $packageImport for $binaryName');
6065
if (packageImport == null) {
6166
return null;
6267
}
@@ -67,6 +72,8 @@ class PackagePathResolver implements SymbolResolver {
6772
'qualified binaryName');
6873
}
6974

75+
// We always name imports with an underscore suffix, so that they can be
76+
// never shadowed by a parameter or local variable.
7077
var importedName = '${pkgName}_';
7178
int suffix = 0;
7279
while (_importedNameToPackage.containsKey(importedName)) {

pkgs/jnigen/lib/src/config/config.dart

+27-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import 'package:jnigen/src/elements/elements.dart';
99
import 'yaml_reader.dart';
1010
import 'filters.dart';
1111

12+
import 'package:logging/logging.dart';
13+
1214
/// Configuration for dependencies to be downloaded using maven.
1315
///
1416
/// Dependency names should be listed in groupId:artifactId:version format.
@@ -142,6 +144,7 @@ class Config {
142144
this.androidSdkConfig,
143145
this.mavenDownloads,
144146
this.summarizerOptions,
147+
this.logLevel = Level.INFO,
145148
this.dumpJsonTo,
146149
});
147150

@@ -205,8 +208,15 @@ class Config {
205208
/// Additional options for the summarizer component
206209
SummarizerOptions? summarizerOptions;
207210

211+
/// Log verbosity. The possible values in decreasing order of verbosity
212+
/// are verbose > debug > info > warning > error. Defaults to [LogLevel.info]
213+
Level logLevel = Level.INFO;
214+
215+
/// File to which JSON summary is written before binding generation.
208216
String? dumpJsonTo;
209217

218+
static final _levels = Map.fromEntries(
219+
Level.LEVELS.map((l) => MapEntry(l.name.toLowerCase(), l)));
210220
static Uri? _toDirUri(String? path) =>
211221
path != null ? Uri.directory(path) : null;
212222
static List<Uri>? _toUris(List<String>? paths) =>
@@ -231,7 +241,7 @@ class Config {
231241
for (var exclusion in exclusions) {
232242
final split = exclusion.split('#');
233243
if (split.length != 2) {
234-
throw FormatException('Error parsing exclusion: "$exclusion"; '
244+
throw FormatException('Error parsing exclusion: "$exclusion": '
235245
'expected to be in binaryName#member format.');
236246
}
237247
filters.add(MemberNameFilter<T>.exclude(
@@ -252,6 +262,15 @@ class Config {
252262
return root;
253263
}
254264

265+
Level logLevelFromString(String? levelName) {
266+
if (levelName == null) return Level.INFO;
267+
final level = _levels[levelName.toLowerCase()];
268+
if (level == null) {
269+
throw ConfigError('Not a valid logging level: $levelName');
270+
}
271+
return level;
272+
}
273+
255274
final config = Config(
256275
sourcePath: _toUris(prov.getStringList(_Props.sourcePath)),
257276
classPath: _toUris(prov.getStringList(_Props.classPath)),
@@ -295,6 +314,12 @@ class Config {
295314
androidExample: prov.getString(_Props.androidExample),
296315
)
297316
: null,
317+
logLevel: logLevelFromString(
318+
prov.getOneOf(
319+
_Props.logLevel,
320+
{'error', 'warning', 'info', 'debug', 'verbose'},
321+
),
322+
),
298323
);
299324
if (missingValues.isNotEmpty) {
300325
stderr.write('Following config values are required but not provided\n'
@@ -333,6 +358,7 @@ class _Props {
333358
static const dartRoot = 'dart_root';
334359
static const preamble = 'preamble';
335360
static const libraryName = 'library_name';
361+
static const logLevel = 'log_level';
336362

337363
static const mavenDownloads = 'maven_downloads';
338364
static const sourceDeps = '$mavenDownloads.source_deps';

pkgs/jnigen/lib/src/generate_bindings.dart

+7-4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ import 'summary/summary.dart';
1010
import 'config/config.dart';
1111
import 'tools/tools.dart';
1212
import 'writers/writers.dart';
13+
import 'logging/logging.dart';
1314

1415
Future<void> generateJniBindings(Config config) async {
16+
setLoggingLevel(config.logLevel);
17+
1518
await buildSummarizerIfNotExists();
1619

1720
final summarizer = SummarizerCommand(
@@ -72,19 +75,19 @@ Future<void> generateJniBindings(Config config) async {
7275
try {
7376
input = await summarizer.getInputStream();
7477
} on Exception catch (e) {
75-
stderr.writeln('error obtaining API summary: $e');
78+
log.fatal('Cannot obtain API summary: $e');
7679
return;
7780
}
7881
final stream = JsonDecoder().bind(Utf8Decoder().bind(input));
7982
dynamic json;
8083
try {
8184
json = await stream.single;
8285
} on Exception catch (e) {
83-
stderr.writeln('error while parsing summary: $e');
86+
log.fatal('Cannot parse summary: $e');
8487
return;
8588
}
8689
if (json == null) {
87-
stderr.writeln('error: expected JSON element from summarizer.');
90+
log.fatal('Expected JSON element from summarizer.');
8891
return;
8992
}
9093
final list = json as List;
@@ -93,6 +96,6 @@ Future<void> generateJniBindings(Config config) async {
9396
await outputWriter.writeBindings(list.map((c) => ClassDecl.fromJson(c)));
9497
} on Exception catch (e, trace) {
9598
stderr.writeln(trace);
96-
stderr.writeln('error writing bindings: $e');
99+
log.fatal('Error while writing bindings: $e');
97100
}
98101
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
import 'package:logging/logging.dart';
7+
8+
const _ansiRed = '\x1b[31m';
9+
const _ansiDefault = '\x1b[39;49m';
10+
11+
Logger log = Logger('jnigen');
12+
13+
void setLoggingLevel(Level level) {
14+
Logger.root.level = level;
15+
Logger.root.onRecord.listen((r) {
16+
var message = '(${r.loggerName}) ${r.level.name}: ${r.message}';
17+
if (level == Level.SHOUT || level == Level.SEVERE) {
18+
message = '$_ansiRed$message$_ansiDefault';
19+
}
20+
stderr.writeln(message);
21+
});
22+
}
23+
24+
extension FatalErrors on Logger {
25+
void fatal(Object? message, {int exitCode = 2}) {
26+
message = '${_ansiRed}Fatal: $message$_ansiDefault';
27+
stderr.writeln(message);
28+
exit(exitCode);
29+
}
30+
}

pkgs/jnigen/lib/src/summary/summary.dart

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:io';
6+
import 'package:jnigen/src/logging/logging.dart';
67
import 'package:jnigen/src/util/command_output.dart';
78

89
/// A command based summary source which calls the ApiSummarizer command.
@@ -79,7 +80,7 @@ class SummarizerCommand {
7980
args.addAll(extraArgs);
8081
args.addAll(classes);
8182

82-
stderr.writeln('[exec] $exec ${args.join(' ')}');
83+
log.info('execute $exec ${args.join(' ')}');
8384
final proc = await Process.start(exec, args,
8485
workingDirectory: workingDirectory?.toFilePath() ?? '.');
8586
prefixedCommandOutputStream('[ApiSummarizer]', proc.stderr)

pkgs/jnigen/lib/src/tools/android_sdk_tools.dart

+16-8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import 'dart:io';
66
import 'package:path/path.dart';
77

8+
import 'package:jnigen/src/logging/logging.dart';
9+
810
class AndroidSdkTools {
911
/// get path for android API sources
1012
static Future<String?> _getVersionDir(
@@ -25,7 +27,9 @@ class AndroidSdkTools {
2527

2628
static Future<String?> getAndroidSourcesPath(
2729
{String? sdkRoot, required List<int> versionOrder}) async {
28-
return _getVersionDir('sources', sdkRoot, versionOrder);
30+
final dir = _getVersionDir('sources', sdkRoot, versionOrder);
31+
log.info('Found sources at $dir');
32+
return dir;
2933
}
3034

3135
static Future<String?> _getFile(String relative, String file, String? sdkRoot,
@@ -34,6 +38,7 @@ class AndroidSdkTools {
3438
if (platform == null) return null;
3539
final filePath = join(platform, file);
3640
if (await File(filePath).exists()) {
41+
log.info('Found $filePath');
3742
return filePath;
3843
}
3944
return null;
@@ -68,28 +73,31 @@ task listDependencies(type: Copy) {
6873
/// If current project is not directly buildable by gradle, eg: a plugin,
6974
/// a relative path to other project can be specified using [androidProject].
7075
static List<String> getGradleClasspaths([String androidProject = '.']) {
71-
stderr.writeln('trying to obtain gradle classpaths...');
76+
log.info('trying to obtain gradle classpaths...');
7277
final android = join(androidProject, 'android');
7378
final buildGradle = join(android, 'build.gradle');
7479
final buildGradleOld = join(android, 'build.gradle.old');
7580
final origBuild = File(buildGradle);
7681
final script = origBuild.readAsStringSync();
7782
origBuild.renameSync(buildGradleOld);
7883
origBuild.createSync();
84+
log.finer('Writing temporary gradle script with stub function...');
7985
origBuild.writeAsStringSync('$script\n$_gradleListDepsFunction\n');
86+
log.finer('Running gradle wrapper...');
8087
final procRes = Process.runSync('./gradlew', ['-q', 'listDependencies'],
8188
workingDirectory: android);
89+
log.finer('Restoring build scripts');
8290
origBuild.writeAsStringSync(script);
8391
File(buildGradleOld).deleteSync();
8492
if (procRes.exitCode != 0) {
93+
final inAndroidProject =
94+
(androidProject == '.') ? '' : ' in $androidProject';
8595
throw Exception('\n\ngradle exited with exit code ${procRes.exitCode}\n'
8696
'This can be related to a known issue with gradle. Please run '
87-
'`flutter build apk` in $androidProject and try again\n');
88-
}
89-
final gradleClassPaths = (procRes.stdout as String).split('\n');
90-
if (gradleClassPaths.last.isEmpty) {
91-
gradleClassPaths.removeLast();
97+
'`flutter build apk`$inAndroidProject and try again\n');
9298
}
93-
return gradleClassPaths;
99+
final classpaths = (procRes.stdout as String).trim().split('\n');
100+
log.info('Found release build classpath with ${classpaths.length} entries');
101+
return classpaths;
94102
}
95103
}

0 commit comments

Comments
 (0)