• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import 'dart:convert';
2import 'dart:io';
3
4import 'package:path/path.dart' as p;
5import 'package:collection/collection.dart' show MapEquality;
6
7// This script verifies that the release binaries only export the expected
8// symbols.
9//
10// Android binaries (libflutter.so) should only export one symbol "JNI_OnLoad"
11// of type "T".
12//
13// iOS binaries (Flutter.framework/Flutter) should only export Objective-C
14// Symbols from the Flutter namespace. These are either of type
15// "(__DATA,__common)" or "(__DATA,__objc_data)".
16
17/// Takes the path to the out directory as the first argument, and the path to
18/// the buildtools directory as the second argument.
19///
20/// If the second argument is not specified, it is assumed that it is the parent
21/// of the out directory (for backwards compatibility).
22void main(List<String> arguments) {
23  assert(arguments.length == 2 || arguments.length == 1);
24  final String outPath = arguments.first;
25  final String buildToolsPath = arguments.length == 1
26      ? p.join(p.dirname(outPath), 'buildtools')
27      : arguments[1];
28
29  String platform;
30  if (Platform.isLinux) {
31    platform = 'linux-x64';
32  } else if (Platform.isMacOS) {
33    platform = 'mac-x64';
34  } else {
35    throw UnimplementedError('Script only support running on Linux or MacOS.');
36  }
37  final String nmPath = p.join(buildToolsPath, platform, 'clang', 'bin', 'llvm-nm');
38  assert(new Directory(outPath).existsSync());
39
40  final Iterable<String> releaseBuilds = Directory(outPath).listSync()
41      .where((FileSystemEntity entity) => entity is Directory)
42      .map<String>((FileSystemEntity dir) => p.basename(dir.path))
43      .where((String s) => s.contains('_release'));
44
45  final Iterable<String> iosReleaseBuilds = releaseBuilds
46      .where((String s) => s.startsWith('ios_'));
47  final Iterable<String> androidReleaseBuilds = releaseBuilds
48      .where((String s) => s.startsWith('android_'));
49
50  int failures = 0;
51  failures += _checkIos(outPath, nmPath, iosReleaseBuilds);
52  failures += _checkAndroid(outPath, nmPath, androidReleaseBuilds);
53  print('Failing checks: $failures');
54  exit(failures);
55}
56
57int _checkIos(String outPath, String nmPath, Iterable<String> builds) {
58  int failures = 0;
59  for (String build in builds) {
60    final String libFlutter = p.join(outPath, build, 'Flutter.framework', 'Flutter');
61    if (!new File(libFlutter).existsSync()) {
62      print('SKIPPING: $libFlutter does not exist.');
63      continue;
64    }
65    final ProcessResult nmResult = Process.runSync(nmPath, <String>['-gUm', libFlutter]);
66    if (nmResult.exitCode != 0) {
67      print('ERROR: failed to execute "nm -gUm $libFlutter":\n${nmResult.stderr}');
68      failures++;
69      continue;
70    }
71    final Iterable<NmEntry> unexpectedEntries = NmEntry.parse(nmResult.stdout).where((NmEntry entry) {
72      return !(((entry.type == '(__DATA,__common)' || entry.type == '(__DATA,__const)') && entry.name.startsWith('_Flutter'))
73          || (entry.type == '(__DATA,__objc_data)'
74              && (entry.name.startsWith('_OBJC_METACLASS_\$_Flutter') || entry.name.startsWith('_OBJC_CLASS_\$_Flutter'))));
75    });
76    if (unexpectedEntries.isNotEmpty) {
77      print('ERROR: $libFlutter exports unexpected symbols:');
78      print(unexpectedEntries.fold<String>('', (String previous, NmEntry entry) {
79        return '${previous == '' ? '' : '$previous\n'}     ${entry.type} ${entry.name}';
80      }));
81      failures++;
82    } else {
83      print('OK: $libFlutter');
84    }
85  }
86  return failures;
87}
88
89int _checkAndroid(String outPath, String nmPath, Iterable<String> builds) {
90  int failures = 0;
91  for (String build in builds) {
92    final String libFlutter = p.join(outPath, build, 'libflutter.so');
93    if (!new File(libFlutter).existsSync()) {
94      print('SKIPPING: $libFlutter does not exist.');
95      continue;
96    }
97    final ProcessResult nmResult = Process.runSync(nmPath, <String>['-gU', libFlutter]);
98    if (nmResult.exitCode != 0) {
99      print('ERROR: failed to execute "nm -gU $libFlutter":\n${nmResult.stderr}');
100      failures++;
101      continue;
102    }
103    final Iterable<NmEntry> entries = NmEntry.parse(nmResult.stdout);
104    final Map<String, String> entryMap = Map<String, String>.fromIterable(
105        entries,
106        key: (dynamic entry) => entry.name,
107        value: (dynamic entry) => entry.type);
108    final Map<String, String> expectedSymbols = <String, String>{
109      'JNI_OnLoad': 'T',
110      '_binary_icudtl_dat_size': 'A',
111      '_binary_icudtl_dat_start': 'D',
112    };
113    if (!const MapEquality<String, String>().equals(entryMap, expectedSymbols)) {
114      print('ERROR: $libFlutter exports the wrong symbols');
115      print(' Expected $expectedSymbols');
116      print(' Library has $entryMap.');
117      failures++;
118    } else {
119      print('OK: $libFlutter');
120    }
121  }
122  return failures;
123}
124
125class NmEntry {
126  NmEntry._(this.address, this.type, this.name);
127
128  final String address;
129  final String type;
130  final String name;
131
132  static Iterable<NmEntry> parse(String stdout) {
133    return LineSplitter.split(stdout).map((String line) {
134      final List<String> parts = line.split(' ');
135      return NmEntry._(parts[0], parts[1], parts.last);
136    });
137  }
138}
139