verify_exported.dart 5.0 KB
Newer Older
V
vsmenon 已提交
1
// @dart = 2.6
2 3 4 5
import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as p;
6
import 'package:collection/collection.dart' show MapEquality;
7 8 9 10 11 12 13 14 15 16 17

// This script verifies that the release binaries only export the expected
// symbols.
//
// Android binaries (libflutter.so) should only export one symbol "JNI_OnLoad"
// of type "T".
//
// iOS binaries (Flutter.framework/Flutter) should only export Objective-C
// Symbols from the Flutter namespace. These are either of type
// "(__DATA,__common)" or "(__DATA,__objc_data)".

18 19 20 21 22
/// Takes the path to the out directory as the first argument, and the path to
/// the buildtools directory as the second argument.
///
/// If the second argument is not specified, it is assumed that it is the parent
/// of the out directory (for backwards compatibility).
23
void main(List<String> arguments) {
24
  assert(arguments.length == 2 || arguments.length == 1);
25
  final String outPath = arguments.first;
26 27 28 29
  final String buildToolsPath = arguments.length == 1
      ? p.join(p.dirname(outPath), 'buildtools')
      : arguments[1];

30 31 32 33 34 35
  String platform;
  if (Platform.isLinux) {
    platform = 'linux-x64';
  } else if (Platform.isMacOS) {
    platform = 'mac-x64';
  } else {
D
Dan Field 已提交
36
    throw UnimplementedError('Script only support running on Linux or MacOS.');
37
  }
38
  final String nmPath = p.join(buildToolsPath, platform, 'clang', 'bin', 'llvm-nm');
39
  assert(Directory(outPath).existsSync());
40

D
Dan Field 已提交
41
  final Iterable<String> releaseBuilds = Directory(outPath).listSync()
42
      .where((FileSystemEntity entity) => entity is Directory)
43
      .map<String>((FileSystemEntity dir) => p.basename(dir.path))
44 45 46 47 48 49 50 51
      .where((String s) => s.contains('_release'));

  final Iterable<String> iosReleaseBuilds = releaseBuilds
      .where((String s) => s.startsWith('ios_'));
  final Iterable<String> androidReleaseBuilds = releaseBuilds
      .where((String s) => s.startsWith('android_'));

  int failures = 0;
52 53
  failures += _checkIos(outPath, nmPath, iosReleaseBuilds);
  failures += _checkAndroid(outPath, nmPath, androidReleaseBuilds);
54 55
  print('Failing checks: $failures');
  exit(failures);
56 57
}

58
int _checkIos(String outPath, String nmPath, Iterable<String> builds) {
59 60 61
  int failures = 0;
  for (String build in builds) {
    final String libFlutter = p.join(outPath, build, 'Flutter.framework', 'Flutter');
62
    if (!File(libFlutter).existsSync()) {
63 64 65
      print('SKIPPING: $libFlutter does not exist.');
      continue;
    }
66 67 68 69 70 71 72
    final ProcessResult nmResult = Process.runSync(nmPath, <String>['-gUm', libFlutter]);
    if (nmResult.exitCode != 0) {
      print('ERROR: failed to execute "nm -gUm $libFlutter":\n${nmResult.stderr}');
      failures++;
      continue;
    }
    final Iterable<NmEntry> unexpectedEntries = NmEntry.parse(nmResult.stdout).where((NmEntry entry) {
73
      return !(((entry.type == '(__DATA,__common)' || entry.type == '(__DATA,__const)') && entry.name.startsWith('_Flutter'))
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
          || (entry.type == '(__DATA,__objc_data)'
              && (entry.name.startsWith('_OBJC_METACLASS_\$_Flutter') || entry.name.startsWith('_OBJC_CLASS_\$_Flutter'))));
    });
    if (unexpectedEntries.isNotEmpty) {
      print('ERROR: $libFlutter exports unexpected symbols:');
      print(unexpectedEntries.fold<String>('', (String previous, NmEntry entry) {
        return '${previous == '' ? '' : '$previous\n'}     ${entry.type} ${entry.name}';
      }));
      failures++;
    } else {
      print('OK: $libFlutter');
    }
  }
  return failures;
}

90
int _checkAndroid(String outPath, String nmPath, Iterable<String> builds) {
91 92 93
  int failures = 0;
  for (String build in builds) {
    final String libFlutter = p.join(outPath, build, 'libflutter.so');
94
    if (!File(libFlutter).existsSync()) {
95 96 97
      print('SKIPPING: $libFlutter does not exist.');
      continue;
    }
98 99 100 101 102 103 104
    final ProcessResult nmResult = Process.runSync(nmPath, <String>['-gU', libFlutter]);
    if (nmResult.exitCode != 0) {
      print('ERROR: failed to execute "nm -gU $libFlutter":\n${nmResult.stderr}');
      failures++;
      continue;
    }
    final Iterable<NmEntry> entries = NmEntry.parse(nmResult.stdout);
D
Dan Field 已提交
105
    final Map<String, String> entryMap = Map<String, String>.fromIterable(
106
        entries,
D
Dan Field 已提交
107 108 109
        key: (dynamic entry) => entry.name,
        value: (dynamic entry) => entry.type);
    final Map<String, String> expectedSymbols = <String, String>{
110 111 112 113
      'JNI_OnLoad': 'T',
      '_binary_icudtl_dat_size': 'A',
      '_binary_icudtl_dat_start': 'D',
    };
114
    if (!const MapEquality<String, String>().equals(entryMap, expectedSymbols)) {
115 116 117
      print('ERROR: $libFlutter exports the wrong symbols');
      print(' Expected $expectedSymbols');
      print(' Library has $entryMap.');
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
      failures++;
    } else {
      print('OK: $libFlutter');
    }
  }
  return failures;
}

class NmEntry {
  NmEntry._(this.address, this.type, this.name);

  final String address;
  final String type;
  final String name;

  static Iterable<NmEntry> parse(String stdout) {
    return LineSplitter.split(stdout).map((String line) {
      final List<String> parts = line.split(' ');
D
Dan Field 已提交
136
      return NmEntry._(parts[0], parts[1], parts.last);
137 138 139
    });
  }
}