diff --git a/dev/bots/allowlist.dart b/dev/bots/allowlist.dart new file mode 100644 index 00000000000..64dfedfd607 --- /dev/null +++ b/dev/bots/allowlist.dart @@ -0,0 +1,76 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/// The SDK package allowlist for the flutter, flutter_test, flutter_driver, flutter_localizations, +/// and integration_test packages. +/// +/// The goal of the allowlist is to make it more difficult to accidentally add new dependencies +/// to the core SDK packages that users depend on. Any dependencies added to this set can have a +/// large impact on the allowed version solving of a given flutter application because of how +/// the SDK pins to an exact version. +/// +/// Before adding a new Dart Team owned dependency to this set, please clear with natebosch@ +/// or jakemac53@. For other packages please contact hixie@ or jonahwilliams@ . +const Set kCorePackageAllowList = { + 'characters', + 'clock', + 'collection', + 'fake_async', + 'file', + 'intl', + 'meta', + 'path', + 'stack_trace', + 'test', + 'test_api', + 'typed_data', + 'vector_math', + 'vm_service', + 'webdriver', + '_fe_analyzer_shared', + 'analyzer', + 'archive', + 'args', + 'async', + 'boolean_selector', + 'charcode', + 'cli_util', + 'convert', + 'coverage', + 'crypto', + 'glob', + 'http_multi_server', + 'http_parser', + 'io', + 'js', + 'logging', + 'matcher', + 'mime', + 'node_preamble', + 'package_config', + 'pedantic', + 'pool', + 'pub_semver', + 'shelf', + 'shelf_packages_handler', + 'shelf_static', + 'shelf_web_socket', + 'source_map_stack_trace', + 'source_maps', + 'source_span', + 'stream_channel', + 'string_scanner', + 'sync_http', + 'term_glyph', + 'test_core', + 'watcher', + 'web_socket_channel', + 'webkit_inspection_protocol', + 'yaml', + 'flutter', + 'flutter_driver', + 'flutter_localizations', + 'flutter_test', + 'integration_test' +}; diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 7cf2571d079..b39c24daa95 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:convert'; import 'dart:core' hide print; import 'dart:io' hide exit; @@ -11,6 +12,7 @@ import 'package:crypto/crypto.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +import 'allowlist.dart'; import 'run_command.dart'; import 'utils.dart'; @@ -76,6 +78,11 @@ Future run(List arguments) async { workingDirectory: flutterRoot, ); + /// Ensure that no new dependencies have been accidentally + /// added to core packages. + print('$clock Package Allowlist...'); + await _checkConsumerDependencies(); + // Analyze all the Dart code in the repo. print('$clock Dart analysis...'); await _runFlutterAnalyze(flutterRoot, options: [ @@ -1129,6 +1136,62 @@ Future _evalCommand(String executable, List arguments, { return result; } +Future _checkConsumerDependencies() async { + final ProcessResult result = await Process.run(flutter, [ + 'update-packages', + '--transitive-closure', + '--consumer-only', + ]); + if (result.exitCode != 0) { + print(result.stdout); + print(result.stderr); + exit(result.exitCode); + } + final Set dependencySet = {}; + for (final String line in result.stdout.toString().split('\n')) { + if (!line.contains('->')) { + continue; + } + final List parts = line.split('->'); + final String name = parts[0].trim(); + dependencySet.add(name); + } + final List dependencies = dependencySet.toList() + ..sort(); + final List disallowed = []; + final StreamController controller = StreamController(); + final ByteConversionSink hasher = sha256.startChunkedConversion(controller.sink); + for (final String dependency in dependencies) { + hasher.add(utf8.encode(dependency)); + if (!kCorePackageAllowList.contains(dependency)) { + disallowed.add(dependency); + } + } + hasher.close(); + final Digest digest = await controller.stream.last; + final String signature = base64.encode(digest.bytes); + + // Do not change this signature without following the directions in + // dev/bots/allowlist.dart + const String kExpected = '3S20q38QbN+dDAp+jApXiTRaDgVGGBZ0t4bMJgD3AUY='; + + if (disallowed.isNotEmpty) { + exitWithError([ + 'Warning: transitive closure contained non-allowlisted packages:', + '${disallowed..join(', ')}', + 'See dev/bots/allowlist.dart for instructions on how to update the package allowlist.', + ]); + } + + if (signature != kExpected) { + exitWithError([ + 'Warning: transitive closure sha256 does not match expected signature.', + 'See dev/bots/allowlist.dart for instructions on how to update the package allowlist.', + '$signature != $kExpected', + ]); + } +} + Future _runFlutterAnalyze(String workingDirectory, { List options = const [], }) async {