Version 2.10.0-47.0.dev

Merge commit '7ec29a9b6d8bf015b92a4b28dc24494648ec2a39' into 'dev'
diff --git a/DEPS b/DEPS
index 39db623..8ddee13 100644
--- a/DEPS
+++ b/DEPS
@@ -132,7 +132,7 @@
   "pub_semver_tag": "v1.4.4",
   "quiver-dart_tag": "246e754fe45cecb6aa5f3f13b4ed61037ff0d784",
   "resource_rev": "f8e37558a1c4f54550aa463b88a6a831e3e33cd6",
-  "root_certificates_rev": "16ef64be64c7dfdff2b9f4b910726e635ccc519e",
+  "root_certificates_rev": "7e5ec82c99677a2e5b95ce296c4d68b0d3378ed8",
   "rust_revision": "60960a260f7b5c695fd0717311d72ce62dd4eb43",
   "shelf_static_rev": "v0.2.8",
   "shelf_packages_handler_tag": "2.0.0",
diff --git a/pkg/_js_interop_checks/pubspec.yaml b/pkg/_js_interop_checks/pubspec.yaml
index 89f71c4..1463e1d 100644
--- a/pkg/_js_interop_checks/pubspec.yaml
+++ b/pkg/_js_interop_checks/pubspec.yaml
@@ -6,5 +6,7 @@
   sdk: '>=2.7.0 <3.0.0'
 
 dependencies:
-  _fe_analyzer_shared: ../_fe_analyzer_shared
-  kernel: ../kernel
+  _fe_analyzer_shared:
+    path: ../_fe_analyzer_shared
+  kernel:
+    path: ../kernel
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
index c486755..92e70c1 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart
@@ -24,19 +24,17 @@
   /// there is a `return` statement at the end of the function body block.
   final List<FunctionBody> functionBodiesThatDontComplete = [];
 
-  /// The list of [SimpleIdentifier]s that were checked if they are definitely
-  /// assigned, but were not.
-  final List<AstNode> notDefinitelyAssignedNodes = [];
+  /// The list of references to variables, where a variable is read, and
+  /// is not definitely assigned.
+  final List<SimpleIdentifier> notDefinitelyAssigned = [];
 
-  /// The list of [SimpleIdentifier]s representing variable references (reads,
-  /// writes, or both) that occur when the corresponding variable has been
-  /// definitely assigned.
-  final List<AstNode> definitelyAssignedNodes = [];
+  /// The list of references to variables, where a variable is read, and
+  /// is definitely assigned.
+  final List<SimpleIdentifier> definitelyAssigned = [];
 
-  /// The list of [SimpleIdentifier]s representing variable references (reads,
-  /// writes, or both) that occur when the corresponding variable has been
-  /// definitely unassigned.
-  final List<AstNode> definitelyUnassignedNodes = [];
+  /// The list of references to variables, where a variable is written, and
+  /// is definitely unassigned.
+  final List<SimpleIdentifier> definitelyUnassigned = [];
 
   /// For each top level or class level declaration, the assigned variables
   /// information that was computed for it.
@@ -158,9 +156,9 @@
 
     if (dataForTesting != null) {
       if (isAssigned) {
-        dataForTesting.definitelyAssignedNodes.add(node);
+        dataForTesting.definitelyAssigned.add(node);
       } else {
-        dataForTesting.notDefinitelyAssignedNodes.add(node);
+        dataForTesting.notDefinitelyAssigned.add(node);
       }
     }
 
@@ -174,7 +172,7 @@
     var isUnassigned = flow.isUnassigned(element);
 
     if (dataForTesting != null && isUnassigned) {
-      dataForTesting.definitelyUnassignedNodes.add(node);
+      dataForTesting.definitelyUnassigned.add(node);
     }
 
     return isUnassigned;
diff --git a/pkg/analyzer/test/id_tests/definite_assignment_test.dart b/pkg/analyzer/test/id_tests/definite_assignment_test.dart
index 4d75189..03d7c45 100644
--- a/pkg/analyzer/test/id_tests/definite_assignment_test.dart
+++ b/pkg/analyzer/test/id_tests/definite_assignment_test.dart
@@ -73,7 +73,7 @@
     if (node is SimpleIdentifier && node.inGetterContext()) {
       var element = node.staticElement;
       if (element is LocalVariableElement || element is ParameterElement) {
-        if (_flowResult.notDefinitelyAssignedNodes.contains(node)) {
+        if (_flowResult.notDefinitelyAssigned.contains(node)) {
           return 'unassigned';
         }
       }
diff --git a/pkg/analyzer/test/id_tests/definite_unassignment_test.dart b/pkg/analyzer/test/id_tests/definite_unassignment_test.dart
index 359b481..c0e1153 100644
--- a/pkg/analyzer/test/id_tests/definite_unassignment_test.dart
+++ b/pkg/analyzer/test/id_tests/definite_unassignment_test.dart
@@ -73,7 +73,7 @@
     if (node is SimpleIdentifier && node.inGetterContext()) {
       var element = node.staticElement;
       if (element is LocalVariableElement || element is ParameterElement) {
-        if (_flowResult.definitelyUnassignedNodes.contains(node)) {
+        if (_flowResult.definitelyUnassigned.contains(node)) {
           return 'unassigned';
         }
       }
diff --git a/pkg/analyzer/test/src/diagnostics/null_safety_read_write_test.dart b/pkg/analyzer/test/src/diagnostics/null_safety_read_write_test.dart
index 3ab33ad..eaac79c 100644
--- a/pkg/analyzer/test/src/diagnostics/null_safety_read_write_test.dart
+++ b/pkg/analyzer/test/src/diagnostics/null_safety_read_write_test.dart
@@ -438,15 +438,16 @@
     var unitData = testingData.uriToFlowAnalysisData[result.uri];
 
     if (assigned) {
-      expect(unitData.definitelyAssignedNodes, contains(node));
+      expect(unitData.definitelyAssigned, contains(node));
     } else {
-      expect(unitData.definitelyAssignedNodes, isNot(contains(node)));
+      expect(unitData.notDefinitelyAssigned, contains(node));
+      expect(unitData.definitelyAssigned, isNot(contains(node)));
     }
 
     if (unassigned) {
-      expect(unitData.definitelyUnassignedNodes, contains(node));
+      expect(unitData.definitelyUnassigned, contains(node));
     } else {
-      expect(unitData.definitelyUnassignedNodes, isNot(contains(node)));
+      expect(unitData.definitelyUnassigned, isNot(contains(node)));
     }
   }
 }
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 33d1bed..2827fd4 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -1432,11 +1432,12 @@
     return thisType;
   }
 
-  void handleCondition(ir.Node node) {
+  TypeInformation handleCondition(ir.Node node) {
     bool oldAccumulateIsChecks = _accumulateIsChecks;
     _accumulateIsChecks = true;
-    visit(node, conditionContext: true);
+    TypeInformation result = visit(node, conditionContext: true);
     _accumulateIsChecks = oldAccumulateIsChecks;
+    return result;
   }
 
   void _potentiallyAddIsCheck(ir.IsExpression node) {
@@ -1500,6 +1501,8 @@
     LocalState stateAfterOperandWhenFalse = _stateAfterWhenFalse;
     _setStateAfter(
         _state, stateAfterOperandWhenFalse, stateAfterOperandWhenTrue);
+    // TODO(sra): Improve precision on constant and bool-conversion-to-constant
+    // inputs.
     return _types.boolType;
   }
 
@@ -1508,11 +1511,11 @@
     if (node.operator == '&&') {
       LocalState stateBefore = _state;
       _state = new LocalState.childPath(stateBefore);
-      handleCondition(node.left);
+      TypeInformation leftInfo = handleCondition(node.left);
       LocalState stateAfterLeftWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterLeftWhenFalse = _stateAfterWhenFalse;
       _state = new LocalState.childPath(stateAfterLeftWhenTrue);
-      handleCondition(node.right);
+      TypeInformation rightInfo = handleCondition(node.right);
       LocalState stateAfterRightWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterRightWhenFalse = _stateAfterWhenFalse;
       LocalState stateAfterWhenTrue = stateAfterRightWhenTrue;
@@ -1522,15 +1525,22 @@
       LocalState after = stateBefore.mergeDiamondFlow(
           _inferrer, stateAfterWhenTrue, stateAfterWhenFalse);
       _setStateAfter(after, stateAfterWhenTrue, stateAfterWhenFalse);
+      // Constant-fold result.
+      if (_types.isLiteralFalse(leftInfo)) return leftInfo;
+      if (_types.isLiteralTrue(leftInfo)) {
+        if (_types.isLiteralFalse(rightInfo)) return rightInfo;
+        if (_types.isLiteralTrue(rightInfo)) return rightInfo;
+      }
+      // TODO(sra): Add a selector/mux node to improve precision.
       return _types.boolType;
     } else if (node.operator == '||') {
       LocalState stateBefore = _state;
       _state = new LocalState.childPath(stateBefore);
-      handleCondition(node.left);
+      TypeInformation leftInfo = handleCondition(node.left);
       LocalState stateAfterLeftWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterLeftWhenFalse = _stateAfterWhenFalse;
       _state = new LocalState.childPath(stateAfterLeftWhenFalse);
-      handleCondition(node.right);
+      TypeInformation rightInfo = handleCondition(node.right);
       LocalState stateAfterRightWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterRightWhenFalse = _stateAfterWhenFalse;
       LocalState stateAfterWhenTrue = new LocalState.childPath(stateBefore)
@@ -1540,6 +1550,13 @@
       LocalState stateAfter = stateBefore.mergeDiamondFlow(
           _inferrer, stateAfterWhenTrue, stateAfterWhenFalse);
       _setStateAfter(stateAfter, stateAfterWhenTrue, stateAfterWhenFalse);
+      // Constant-fold result.
+      if (_types.isLiteralTrue(leftInfo)) return leftInfo;
+      if (_types.isLiteralFalse(leftInfo)) {
+        if (_types.isLiteralTrue(rightInfo)) return rightInfo;
+        if (_types.isLiteralFalse(rightInfo)) return rightInfo;
+      }
+      // TODO(sra): Add a selector/mux node to improve precision.
       return _types.boolType;
     }
     failedAt(CURRENT_ELEMENT_SPANNABLE,
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index 3042432..129b30b 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -508,7 +508,9 @@
       // it is in the graph.
       types.withMember(member, () => analyze(member));
     });
+    metrics.elementsInGraph.add(_addedInGraph);
     _reporter.log('Added $_addedInGraph elements in inferencing graph.');
+    metrics.allTypesCount.add(types.allTypes.length);
   }
 
   /// Returns the body node for [member].
@@ -1201,6 +1203,8 @@
   final analyze = DurationMetric('time.analyze');
   final refine1 = DurationMetric('time.refine1');
   final refine2 = DurationMetric('time.refine2');
+  final elementsInGraph = CountMetric('count.elementsInGraph');
+  final allTypesCount = CountMetric('count.allTypes');
   final exceededMaxChangeCount = CountMetric('count.exceededMaxChange');
   final overallRefineCount = CountMetric('count.overallRefines');
 
@@ -1210,6 +1214,8 @@
       analyze,
       refine1,
       refine2,
+      elementsInGraph,
+      allTypesCount,
       exceededMaxChangeCount,
       overallRefineCount
     ];
diff --git a/pkg/compiler/lib/src/inferrer/type_system.dart b/pkg/compiler/lib/src/inferrer/type_system.dart
index 1234917..865fbcc 100644
--- a/pkg/compiler/lib/src/inferrer/type_system.dart
+++ b/pkg/compiler/lib/src/inferrer/type_system.dart
@@ -4,6 +4,7 @@
 
 import 'package:kernel/ast.dart' as ir;
 import '../common.dart';
+import '../constants/values.dart' show BoolConstantValue;
 import '../elements/entities.dart';
 import '../elements/types.dart';
 import '../world.dart';
@@ -81,6 +82,9 @@
   final Map<AbstractValue, TypeInformation> concreteTypes =
       new Map<AbstractValue, TypeInformation>();
 
+  /// Cache of some primitive constant types.
+  final Map<Object, TypeInformation> primitiveConstantTypes = {};
+
   /// List of [TypeInformation]s for calls inside method bodies.
   final List<CallSiteTypeInformation> allocatedCalls =
       <CallSiteTypeInformation>[];
@@ -102,8 +106,9 @@
         allocatedMaps.values,
         allocatedClosures,
         concreteTypes.values,
+        primitiveConstantTypes.values,
         allocatedCalls,
-        allocatedTypes
+        allocatedTypes,
       ].expand((x) => x);
 
   TypeSystem(this._closedWorld, this.strategy) {
@@ -283,8 +288,22 @@
   }
 
   TypeInformation boolLiteralType(bool value) {
-    return new BoolLiteralTypeInformation(
-        _abstractValueDomain, value, _abstractValueDomain.boolType);
+    return primitiveConstantTypes[value] ??= _boolLiteralType(value);
+  }
+
+  TypeInformation _boolLiteralType(bool value) {
+    AbstractValue abstractValue = _abstractValueDomain
+        .computeAbstractValueForConstant(BoolConstantValue(value));
+    return BoolLiteralTypeInformation(
+        _abstractValueDomain, value, abstractValue);
+  }
+
+  bool isLiteralTrue(TypeInformation info) {
+    return info is BoolLiteralTypeInformation && info.value == true;
+  }
+
+  bool isLiteralFalse(TypeInformation info) {
+    return info is BoolLiteralTypeInformation && info.value == false;
   }
 
   /// Returns the least upper bound between [firstType] and
diff --git a/pkg/compiler/test/inference/data/logical.dart b/pkg/compiler/test/inference/data/logical.dart
index 6503fd9f0..6f0c127 100644
--- a/pkg/compiler/test/inference/data/logical.dart
+++ b/pkg/compiler/test/inference/data/logical.dart
@@ -154,14 +154,14 @@
 /// Return logical and of `true` && `true`.
 ////////////////////////////////////////////////////////////////////////////////
 
-/*member: returnLogicalAndTrueTrue:[exact=JSBool]*/
+/*member: returnLogicalAndTrueTrue:Value([exact=JSBool], value: true)*/
 returnLogicalAndTrueTrue() => true && true;
 
 ////////////////////////////////////////////////////////////////////////////////
 /// Return logical and of `false` && `true`.
 ////////////////////////////////////////////////////////////////////////////////
 
-/*member: returnLogicalAndFalseTrue:[exact=JSBool]*/
+/*member: returnLogicalAndFalseTrue:Value([exact=JSBool], value: false)*/
 /// ignore: dead_code
 returnLogicalAndFalseTrue() => false && true;
 
@@ -275,14 +275,14 @@
 /// Return logical or of `false` || `true`.
 ////////////////////////////////////////////////////////////////////////////////
 
-/*member: returnLogicalOrFalseTrue:[exact=JSBool]*/
+/*member: returnLogicalOrFalseTrue:Value([exact=JSBool], value: true)*/
 returnLogicalOrFalseTrue() => false || true;
 
 ////////////////////////////////////////////////////////////////////////////////
 /// Return logical or of `false` || `false`.
 ////////////////////////////////////////////////////////////////////////////////
 
-/*member: returnLogicalOrFalseFalse:[exact=JSBool]*/
+/*member: returnLogicalOrFalseFalse:Value([exact=JSBool], value: false)*/
 returnLogicalOrFalseFalse() => false || false;
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/pkg/dart2native/pubspec.yaml b/pkg/dart2native/pubspec.yaml
index 3bdd2c5..85a9eac 100644
--- a/pkg/dart2native/pubspec.yaml
+++ b/pkg/dart2native/pubspec.yaml
@@ -11,6 +11,6 @@
 
 dependencies:
    args: ^1.4.0
+   path: any
 
 dev_dependencies:
-
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 8768762..31bd2ba 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -209,9 +209,8 @@
     // service intermediary which implements the VM service protocol and
     // provides non-VM specific extensions (e.g., log caching, client
     // synchronization).
-    // TODO(bkonyi): Handle race condition made possible by Observatory
-    // listening message being printed to console before DDS is started.
-    // See https://github.com/dart-lang/sdk/issues/42727
+    // TODO(bkonyi): Remove once DevTools supports DDS.
+    // See https://github.com/flutter/flutter/issues/62507
     launchDds = false;
     _DebuggingSession debugSession;
     if (launchDds) {
diff --git a/pkg/kernel/pubspec.yaml b/pkg/kernel/pubspec.yaml
index 4b308e1..6713b64 100644
--- a/pkg/kernel/pubspec.yaml
+++ b/pkg/kernel/pubspec.yaml
@@ -11,6 +11,8 @@
   args: '>=0.13.4 <2.0.0'
   meta: ^1.0.0
 dev_dependencies:
-  expect: any
+  expect:
+    path: ../expect
+  front_end:
+    path: ../front_end
   test: any
-  testing: any
diff --git a/pkg/test_runner/lib/src/options.dart b/pkg/test_runner/lib/src/options.dart
index 79222af..14eb334 100644
--- a/pkg/test_runner/lib/src/options.dart
+++ b/pkg/test_runner/lib/src/options.dart
@@ -155,7 +155,7 @@
         hide: true),
     _Option('system', 'The operating system to run tests on.',
         abbr: 's',
-        values: System.names,
+        values: ['all', ...System.names],
         defaultsTo: Platform.operatingSystem,
         hide: true),
     _Option('sanitizer', 'Sanitizer in which to run the tests.',
@@ -228,6 +228,7 @@
     _Option.bool('no-tree-shake', 'Disable kernel IR tree shaking.',
         hide: true),
     _Option.bool('list', 'List tests only, do not run them.'),
+    _Option.bool('find-configurations', 'Find matching configurations.'),
     _Option.bool('list-configurations', 'Output list of configurations.'),
     _Option.bool('list_status_files',
         'List status files for test-suites. Do not run any test suites.',
@@ -406,21 +407,8 @@
       return null;
     }
 
-    if (arguments.contains("--list-configurations")) {
-      var testMatrixFile = "tools/bots/test_matrix.json";
-      var testMatrix = TestMatrix.fromPath(testMatrixFile);
-      for (var configuration in testMatrix.configurations
-          .map((configuration) => configuration.name)
-          .toList()
-            ..sort()) {
-        print(configuration);
-      }
-      return null;
-    }
-
-    var configuration = <String, dynamic>{};
-
-    // Fill in configuration with arguments passed to the test script.
+    // Parse the command line arguments to a map.
+    var options = <String, dynamic>{};
     for (var i = 0; i < arguments.length; i++) {
       var arg = arguments[i];
 
@@ -458,7 +446,7 @@
       } else {
         // The argument does not start with "-" or "--" and is therefore not an
         // option. Use it as a test selector pattern.
-        var patterns = configuration.putIfAbsent("selectors", () => <String>[]);
+        var patterns = options.putIfAbsent("selectors", () => <String>[]);
 
         // Allow passing in the full relative path to a test or directory and
         // infer the selector from it. This lets users use tab completion on
@@ -492,7 +480,7 @@
 
       // Multiple uses of a flag are an error, because there is no naturally
       // correct way to handle conflicting options.
-      if (configuration.containsKey(option.name)) {
+      if (options.containsKey(option.name)) {
         _fail('Already have value for command line option "$command".');
       }
 
@@ -503,12 +491,12 @@
             _fail('Boolean flag "$command" does not take a value.');
           }
 
-          configuration[option.name] = true;
+          options[option.name] = true;
           break;
 
         case _OptionValueType.int:
           try {
-            configuration[option.name] = int.parse(value);
+            options[option.name] = int.parse(value);
           } on FormatException {
             _fail('Integer value expected for option "$command".');
           }
@@ -535,17 +523,27 @@
 
           // TODO(rnystrom): Store as a list instead of a comma-delimited
           // string.
-          configuration[option.name] = value;
+          options[option.name] = value;
           break;
       }
     }
 
+    if (options.containsKey('find-configurations')) {
+      findConfigurations(options);
+      return null;
+    }
+
+    if (options.containsKey('list-configurations')) {
+      listConfigurations(options);
+      return null;
+    }
+
     // If a named configuration was specified ensure no other options, which are
     // implied by the named configuration, were specified.
-    if (configuration['named_configuration'] is String) {
+    if (options['named_configuration'] is String) {
       for (var optionName in _namedConfigurationOptions) {
-        if (configuration.containsKey(optionName)) {
-          var namedConfig = configuration['named_configuration'];
+        if (options.containsKey(optionName)) {
+          var namedConfig = options['named_configuration'];
           _fail("The named configuration '$namedConfig' implies "
               "'$optionName'. Try removing '$optionName'.");
         }
@@ -554,26 +552,26 @@
 
     // Apply default values for unspecified options.
     for (var option in _options) {
-      if (!configuration.containsKey(option.name)) {
-        configuration[option.name] = option.defaultValue;
+      if (!options.containsKey(option.name)) {
+        options[option.name] = option.defaultValue;
       }
     }
 
     // Fetch list of tests to run, if option is present.
-    var testList = configuration['test_list'];
+    var testList = options['test_list'];
     if (testList is String) {
-      configuration['test_list_contents'] = File(testList).readAsLinesSync();
+      options['test_list_contents'] = File(testList).readAsLinesSync();
     }
 
-    var tests = configuration['tests'];
+    var tests = options['tests'];
     if (tests is String) {
-      if (configuration.containsKey('test_list_contents')) {
+      if (options.containsKey('test_list_contents')) {
         _fail('--tests and --test-list cannot be used together');
       }
-      configuration['test_list_contents'] = LineSplitter.split(tests).toList();
+      options['test_list_contents'] = LineSplitter.split(tests).toList();
     }
 
-    return _createConfigurations(configuration);
+    return _createConfigurations(options);
   }
 
   /// Given a set of parsed option values, returns the list of command line
@@ -682,6 +680,12 @@
       data['progress'] = 'verbose';
     }
 
+    var systemName = data["system"] as String;
+    if (systemName == "all") {
+      _fail("Can only use '--system=all' with '--find-configurations'.");
+    }
+    var system = System.find(systemName);
+
     var runtimeNames = data["runtime"] as String;
     var runtimes = [
       if (runtimeNames != null) ...runtimeNames.split(",").map(Runtime.find)
@@ -813,7 +817,6 @@
             }
             for (var sanitizerName in sanitizers.split(",")) {
               var sanitizer = Sanitizer.find(sanitizerName);
-              var system = System.find(data["system"] as String);
               var configuration = Configuration("custom configuration",
                   architecture, compiler, mode, runtime, system,
                   nnbdMode: nnbdMode,
@@ -996,6 +999,101 @@
   OptionParseException(this.message);
 }
 
+/// Prints the names of the configurations in the test matrix that match the
+/// given filter options.
+///
+/// If any of the options `--system`, `--arch`, `--mode`, `--compiler`,
+/// `--nnbd`, or `--runtime` (or their abbreviations) are passed, then only
+/// configurations matching those are shown.
+void findConfigurations(Map<String, dynamic> options) {
+  var testMatrix = TestMatrix.fromPath('tools/bots/test_matrix.json');
+
+  // Default to only showing configurations for the current machine.
+  var systemOption = options['system'] as String;
+  var system = System.host;
+  if (systemOption == 'all') {
+    system = null;
+  } else if (systemOption != null) {
+    system = System.find(systemOption);
+  }
+
+  var architectureOption = options['arch'] as String;
+  var architectures = const [Architecture.x64];
+  if (architectureOption == 'all') {
+    architectures = null;
+  } else if (architectureOption != null) {
+    architectures =
+        architectureOption.split(',').map(Architecture.find).toList();
+  }
+
+  var mode = Mode.release;
+  if (options.containsKey('mode')) {
+    mode = Mode.find(options['mode'] as String);
+  }
+
+  Compiler compiler;
+  if (options.containsKey('compiler')) {
+    compiler = Compiler.find(options['compiler'] as String);
+  }
+
+  Runtime runtime;
+  if (options.containsKey('runtime')) {
+    runtime = Runtime.find(options['runtime'] as String);
+  }
+
+  NnbdMode nnbdMode;
+  if (options.containsKey('nnbd')) {
+    nnbdMode = NnbdMode.find(options['nnbd'] as String);
+  }
+
+  var names = <String>[];
+  for (var configuration in testMatrix.configurations) {
+    if (system != null && configuration.system != system) continue;
+    if (architectures != null &&
+        !architectures.contains(configuration.architecture)) {
+      continue;
+    }
+    if (mode != null && configuration.mode != mode) continue;
+    if (compiler != null && configuration.compiler != compiler) continue;
+    if (runtime != null && configuration.runtime != runtime) continue;
+    if (nnbdMode != null && configuration.nnbdMode != nnbdMode) continue;
+
+    names.add(configuration.name);
+  }
+
+  names.sort();
+
+  var filters = [
+    if (system != null) "system=$system",
+    if (architectures != null) "arch=${architectures.join(',')}",
+    if (mode != null) "mode=$mode",
+    if (compiler != null) "compiler=$compiler",
+    if (runtime != null) "runtime=$runtime",
+    if (nnbdMode != null) "nnbd=$nnbdMode",
+  ];
+
+  if (filters.isEmpty) {
+    print("All configurations:");
+  } else {
+    print("Configurations where ${filters.join(', ')}:");
+  }
+
+  for (var name in names) {
+    print("- $name");
+  }
+}
+
+/// Prints the names of the configurations in the test matrix.
+void listConfigurations(Map<String, dynamic> options) {
+  var testMatrix = TestMatrix.fromPath('tools/bots/test_matrix.json');
+
+  var names = testMatrix.configurations
+      .map((configuration) => configuration.name)
+      .toList();
+  names.sort();
+  names.forEach(print);
+}
+
 /// Throws an [OptionParseException] with [message].
 void _fail(String message) {
   throw OptionParseException(message);
diff --git a/pkg/test_runner/lib/test_runner.dart b/pkg/test_runner/lib/test_runner.dart
index 065b840..e0843da 100644
--- a/pkg/test_runner/lib/test_runner.dart
+++ b/pkg/test_runner/lib/test_runner.dart
@@ -12,6 +12,7 @@
 import 'package:smith/smith.dart';
 
 import 'bot_results.dart';
+import 'src/options.dart';
 
 const int deflakingCount = 5;
 
@@ -343,10 +344,7 @@
   }
 
   if (options["list-configurations"] as bool) {
-    var process = await Process.start(
-        "python", ["tools/test.py", "--list-configurations"],
-        mode: ProcessStartMode.inheritStdio, runInShell: Platform.isWindows);
-    exitCode = await process.exitCode;
+    listConfigurations({"system": "all"});
     return;
   }
 
diff --git a/runtime/bin/dart_embedder_api_impl.cc b/runtime/bin/dart_embedder_api_impl.cc
index ab0cbe0..88c641f 100644
--- a/runtime/bin/dart_embedder_api_impl.cc
+++ b/runtime/bin/dart_embedder_api_impl.cc
@@ -107,7 +107,8 @@
                              config.disable_auth_codes,
                              config.write_service_info_filename,
                              /*trace_loading=*/false, config.deterministic,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service=*/false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
@@ -142,7 +143,8 @@
                              config.disable_auth_codes,
                              config.write_service_info_filename,
                              /*trace_loading=*/false, config.deterministic,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service=*/false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 9457dcd..000855d 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -549,7 +549,10 @@
           Options::vm_service_server_ip(), Options::vm_service_server_port(),
           Options::vm_service_dev_mode(), Options::vm_service_auth_disabled(),
           Options::vm_write_service_info_filename(), Options::trace_loading(),
-          Options::deterministic(), Options::enable_service_port_fallback())) {
+          Options::deterministic(), Options::enable_service_port_fallback(),
+          // TODO(bkonyi): uncomment when DDS is re-enabled.
+          // See https://github.com/flutter/flutter/issues/62507
+          /*!Options::disable_dart_dev()*/ false)) {
     *error = Utils::StrDup(VmService::GetErrorMessage());
     return NULL;
   }
diff --git a/runtime/bin/run_vm_tests.cc b/runtime/bin/run_vm_tests.cc
index c288359..9513513 100644
--- a/runtime/bin/run_vm_tests.cc
+++ b/runtime/bin/run_vm_tests.cc
@@ -149,7 +149,8 @@
                              /*dev_mode=*/false, /*auth_disabled=*/true,
                              /*write_service_info_filename*/ "",
                              /*trace_loading=*/false, /*deterministic=*/true,
-                             /*enable_service_port_fallback=*/false)) {
+                             /*enable_service_port_fallback=*/false,
+                             /*wait_for_dds_to_advertise_service*/ false)) {
     *error = Utils::StrDup(bin::VmService::GetErrorMessage());
     return nullptr;
   }
diff --git a/runtime/bin/vmservice_impl.cc b/runtime/bin/vmservice_impl.cc
index 0a1896a..47b7992 100644
--- a/runtime/bin/vmservice_impl.cc
+++ b/runtime/bin/vmservice_impl.cc
@@ -118,7 +118,8 @@
                       const char* write_service_info_filename,
                       bool trace_loading,
                       bool deterministic,
-                      bool enable_service_port_fallback) {
+                      bool enable_service_port_fallback,
+                      bool wait_for_dds_to_advertise_service) {
   Dart_Isolate isolate = Dart_CurrentIsolate();
   ASSERT(isolate != NULL);
   SetServerAddress("");
@@ -189,6 +190,12 @@
                                        write_service_info_filename);
     SHUTDOWN_ON_ERROR(result);
   }
+
+  result = Dart_SetField(library,
+                         DartUtils::NewString("_waitForDdsToAdvertiseService"),
+                         Dart_NewBoolean(wait_for_dds_to_advertise_service));
+  SHUTDOWN_ON_ERROR(result);
+
 // Are we running on Windows?
 #if defined(HOST_OS_WINDOWS)
   Dart_Handle is_windows = Dart_True();
diff --git a/runtime/bin/vmservice_impl.h b/runtime/bin/vmservice_impl.h
index 4b5838b..3371f27 100644
--- a/runtime/bin/vmservice_impl.h
+++ b/runtime/bin/vmservice_impl.h
@@ -21,7 +21,8 @@
                     const char* write_service_info_filename,
                     bool trace_loading,
                     bool deterministic,
-                    bool enable_service_port_fallback);
+                    bool enable_service_port_fallback,
+                    bool wait_for_dds_to_advertise_service);
 
   static void SetNativeResolver();
 
diff --git a/sdk/lib/_internal/vm/bin/vmservice_io.dart b/sdk/lib/_internal/vm/bin/vmservice_io.dart
index e84c8f8..5718d46 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_io.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_io.dart
@@ -37,6 +37,8 @@
 var _signalSubscription;
 @pragma("vm:entry-point")
 bool _enableServicePortFallback = false;
+@pragma("vm:entry-point")
+bool _waitForDdsToAdvertiseService = false;
 
 // HTTP server.
 Server? server;
@@ -78,6 +80,12 @@
   _shutdown();
 }
 
+Future<void> ddsConnectedCallback() async {
+  if (_waitForDdsToAdvertiseService) {
+    await server!.outputConnectionInformation();
+  }
+}
+
 Future<Uri> createTempDirCallback(String base) async {
   final temp = await Directory.systemTemp.createTemp(base);
   // Underneath the temporary directory, create a directory with the
@@ -245,6 +253,7 @@
   // Set embedder hooks.
   VMServiceEmbedderHooks.cleanup = cleanupCallback;
   VMServiceEmbedderHooks.createTempDir = createTempDirCallback;
+  VMServiceEmbedderHooks.ddsConnected = ddsConnectedCallback;
   VMServiceEmbedderHooks.deleteDir = deleteDirCallback;
   VMServiceEmbedderHooks.writeFile = writeFileCallback;
   VMServiceEmbedderHooks.writeStreamFile = writeStreamFileCallback;
diff --git a/sdk/lib/_internal/vm/bin/vmservice_server.dart b/sdk/lib/_internal/vm/bin/vmservice_server.dart
index 68e18a3..09ec391 100644
--- a/sdk/lib/_internal/vm/bin/vmservice_server.dart
+++ b/sdk/lib/_internal/vm/bin/vmservice_server.dart
@@ -477,11 +477,21 @@
     }
     final server = _server!;
     server.listen(_requestHandler, cancelOnError: true);
+    if (!_waitForDdsToAdvertiseService) {
+      await outputConnectionInformation();
+    }
+    // Server is up and running.
+    _notifyServerState(serverAddress.toString());
+    onServerAddressChange('$serverAddress');
+    return this;
+  }
+
+  Future<void> outputConnectionInformation() async {
     serverPrint('Observatory listening on $serverAddress');
     if (Platform.isFuchsia) {
       // Create a file with the port number.
       final tmp = Directory.systemTemp.path;
-      final path = '$tmp/dart.services/${server.port}';
+      final path = '$tmp/dart.services/${_server!.port}';
       serverPrint('Creating $path');
       File(path)..createSync(recursive: true);
     }
@@ -490,10 +500,6 @@
         serviceInfoFilenameLocal.isNotEmpty) {
       await _dumpServiceInfoToFile(serviceInfoFilenameLocal);
     }
-    // Server is up and running.
-    _notifyServerState(serverAddress.toString());
-    onServerAddressChange('$serverAddress');
-    return this;
   }
 
   Future<void> cleanup(bool force) {
diff --git a/sdk/lib/vmservice/vmservice.dart b/sdk/lib/vmservice/vmservice.dart
index 840a6e0..c404a78 100644
--- a/sdk/lib/vmservice/vmservice.dart
+++ b/sdk/lib/vmservice/vmservice.dart
@@ -139,6 +139,9 @@
 /// Called when the server should be stopped.
 typedef Future ServerStopCallback();
 
+/// Called when DDS has connected.
+typedef Future<void> DdsConnectedCallback();
+
 /// Called when the service is exiting.
 typedef Future CleanupCallback();
 
@@ -177,6 +180,7 @@
 class VMServiceEmbedderHooks {
   static ServerStartCallback? serverStart;
   static ServerStopCallback? serverStop;
+  static DdsConnectedCallback? ddsConnected;
   static CleanupCallback? cleanup;
   static CreateTempDirCallback? createTempDir;
   static DeleteDirCallback? deleteDir;
@@ -245,6 +249,7 @@
     }
     acceptNewWebSocketConnections(false);
     _ddsUri = Uri.parse(uri);
+    await VMServiceEmbedderHooks?.ddsConnected!();
     return encodeSuccess(message);
   }
 
diff --git a/tools/VERSION b/tools/VERSION
index 85b59ea..8f56a69 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 10
 PATCH 0
-PRERELEASE 46
+PRERELEASE 47
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/package_deps/.gitignore b/tools/package_deps/.gitignore
new file mode 100644
index 0000000..3d64647
--- /dev/null
+++ b/tools/package_deps/.gitignore
@@ -0,0 +1,9 @@
+# Files and directories created by pub
+.dart_tool/
+.packages
+
+# Conventional directory for build outputs
+build/
+
+# Directory created by dartdoc
+doc/api/
diff --git a/tools/package_deps/README.md b/tools/package_deps/README.md
new file mode 100644
index 0000000..45b49b6
--- /dev/null
+++ b/tools/package_deps/README.md
@@ -0,0 +1 @@
+A tool to validate pubspec files in pkg/.
diff --git a/tools/package_deps/bin/package_deps.dart b/tools/package_deps/bin/package_deps.dart
new file mode 100644
index 0000000..97eb5d1
--- /dev/null
+++ b/tools/package_deps/bin/package_deps.dart
@@ -0,0 +1,353 @@
+import 'dart:io';
+
+import 'package:path/path.dart' as path;
+import 'package:yaml/yaml.dart' as yaml;
+
+// TODO(devoncarew): Use bold ansi chars for emphasis.
+
+// TODO(devoncarew): Validate that publishable packages don't use relative sdk
+// paths in their pubspecs.
+
+// TODO(devoncarew): Find unused entries in the DEPS file.
+const validateDEPS = false;
+
+void main(List<String> arguments) {
+  // validate the cwd
+  if (!FileSystemEntity.isFileSync('DEPS') ||
+      !FileSystemEntity.isDirectorySync('pkg')) {
+    print('Please run this tool from the root of the Dart repo.');
+    exit(1);
+  }
+
+  // TODO(devoncarew): Support manually added directories (outside of pkg/).
+
+  // locate all packages
+  final packages = <Package>[];
+  for (var entity in Directory('pkg').listSync()) {
+    if (entity is Directory) {
+      var package = Package(entity.path);
+      if (package.hasPubspec) {
+        packages.add(package);
+      }
+    }
+  }
+
+  packages.sort();
+
+  var validateFailure = false;
+
+  // For each, validate the pubspec contents.
+  for (var package in packages) {
+    print('validating ${package.dir}'
+        '${package.publishable ? ' [publishable]' : ''}');
+
+    if (!package.validate()) {
+      validateFailure = true;
+    }
+
+    print('');
+  }
+
+  // Read and display info about the sdk DEPS file.
+  if (validateDEPS) {
+    print('SDK DEPS');
+    var sdkDeps = SdkDeps(File('DEPS'));
+    sdkDeps.parse();
+    print('');
+    print('packages:');
+    for (var pkg in sdkDeps.pkgs) {
+      print('  package:$pkg');
+    }
+
+    print('');
+    print('tested packages:');
+    for (var pkg in sdkDeps.testedPkgs) {
+      print('  package:$pkg');
+    }
+  }
+
+  if (validateFailure) {
+    exit(1);
+  }
+}
+
+class Package implements Comparable<Package> {
+  final String dir;
+
+  Package(this.dir) {
+    _parsePubspec();
+  }
+
+  String get dirName => path.basename(dir);
+  final Set<String> _regularDependencies = {};
+  final Set<String> _devDependencies = {};
+  String _packageName;
+
+  String get packageName => _packageName;
+  Set<String> _declaredDependencies;
+  Set<String> _declaredDevDependencies;
+
+  List<String> get regularDependencies => _regularDependencies.toList()..sort();
+
+  List<String> get devDependencies => _devDependencies.toList()..sort();
+
+  bool _publishToNone;
+  bool get publishable => !_publishToNone;
+
+  @override
+  String toString() => 'Package $dirName';
+
+  bool get hasPubspec =>
+      FileSystemEntity.isFileSync(path.join(dir, 'pubspec.yaml'));
+
+  @override
+  int compareTo(Package other) {
+    return dirName.compareTo(other.dirName);
+  }
+
+  bool validate() {
+    _parseImports();
+    return _validatePubspecDeps();
+  }
+
+  void _parseImports() {
+    final files = <File>[];
+
+    _collectDartFiles(Directory(dir), files);
+
+    for (var file in files) {
+      //print('  ${file.path}');
+
+      var importedPackages = <String>{};
+
+      for (var import in _collectImports(file)) {
+        try {
+          var uri = Uri.parse(import);
+          if (uri.hasScheme && uri.scheme == 'package') {
+            var packageName = path.split(uri.path).first;
+            importedPackages.add(packageName);
+          }
+        } on FormatException {
+          // ignore
+        }
+      }
+
+      var topLevelDir = _topLevelDir(file);
+
+      if ({'bin', 'lib'}.contains(topLevelDir)) {
+        _regularDependencies.addAll(importedPackages);
+      } else {
+        _devDependencies.addAll(importedPackages);
+      }
+    }
+  }
+
+  void _parsePubspec() {
+    var pubspec = File(path.join(dir, 'pubspec.yaml'));
+    var doc = yaml.loadYamlDocument(pubspec.readAsStringSync());
+    dynamic docContents = doc.contents.value;
+    _packageName = docContents['name'];
+    _publishToNone = docContents['publish_to'] == 'none';
+
+    if (docContents['dependencies'] != null) {
+      _declaredDependencies =
+          Set<String>.from(docContents['dependencies'].keys);
+    } else {
+      _declaredDependencies = {};
+    }
+    if (docContents['dev_dependencies'] != null) {
+      _declaredDevDependencies =
+          Set<String>.from(docContents['dev_dependencies'].keys);
+    } else {
+      _declaredDevDependencies = {};
+    }
+  }
+
+  bool _validatePubspecDeps() {
+    var fail = false;
+
+    if (dirName != packageName) {
+      print('  Package name is different from the directory name.');
+      fail = true;
+    }
+
+    var deps = regularDependencies;
+    deps.remove(packageName);
+
+    var devdeps = devDependencies;
+    devdeps.remove(packageName);
+
+    // if (deps.isNotEmpty) {
+    //   print('  deps    : ${deps}');
+    // }
+    // if (devdeps.isNotEmpty) {
+    //   print('  dev deps: ${devdeps}');
+    // }
+
+    var undeclaredRegularUses = Set<String>.from(deps)
+      ..removeAll(_declaredDependencies);
+    if (undeclaredRegularUses.isNotEmpty) {
+      print('  ${_printSet(undeclaredRegularUses)} used in lib/ but not '
+          "declared in 'dependencies:'.");
+      fail = true;
+    }
+
+    var undeclaredDevUses = Set<String>.from(devdeps)
+      ..removeAll(_declaredDependencies)
+      ..removeAll(_declaredDevDependencies);
+    if (undeclaredDevUses.isNotEmpty) {
+      print('  ${_printSet(undeclaredDevUses)} used in dev dirs but not '
+          "declared in 'dev_dependencies:'.");
+      fail = true;
+    }
+
+    var extraRegularDeclarations = Set<String>.from(_declaredDependencies)
+      ..removeAll(deps);
+    if (extraRegularDeclarations.isNotEmpty) {
+      print('  ${_printSet(extraRegularDeclarations)} declared in '
+          "'dependencies:' but not used in lib/.");
+      fail = true;
+    }
+
+    var extraDevDeclarations = Set<String>.from(_declaredDevDependencies)
+      ..removeAll(devdeps);
+    // Remove package:pedantic as it is often declared as a dev dependency in
+    // order to bring in its analysis_options.yaml file.
+    extraDevDeclarations.remove('pedantic');
+    if (extraDevDeclarations.isNotEmpty) {
+      print('  ${_printSet(extraDevDeclarations)} declared in '
+          "'dev_dependencies:' but not used in dev dirs.");
+      fail = true;
+    }
+
+    // Look for things declared in deps, not used in lib/, but that are used in
+    // dev dirs.
+    var misplacedDeps =
+        extraRegularDeclarations.intersection(Set.from(devdeps));
+    if (misplacedDeps.isNotEmpty) {
+      print("  ${_printSet(misplacedDeps)} declared in 'dependencies:' but "
+          'only used in dev dirs.');
+      fail = true;
+    }
+
+    if (!fail) {
+      print('  No issues.');
+    }
+
+    return !fail;
+  }
+
+  void _collectDartFiles(Directory dir, List<File> files) {
+    for (var entity in dir.listSync(followLinks: false)) {
+      if (entity is Directory) {
+        var name = path.basename(entity.path);
+
+        // Skip 'pkg/analyzer_cli/test/data'.
+        // Skip 'pkg/front_end/test/id_testing/data/'.
+        // Skip 'pkg/front_end/test/language_versioning/data/'.
+        if (name == 'data' && path.split(entity.parent.path).contains('test')) {
+          continue;
+        }
+
+        // Skip 'pkg/analysis_server/test/mock_packages'.
+        if (name == 'mock_packages') {
+          continue;
+        }
+
+        // Skip 'pkg/front_end/testcases'.
+        if (name == 'testcases') {
+          continue;
+        }
+
+        if (!name.startsWith('.')) {
+          _collectDartFiles(entity, files);
+        }
+      } else if (entity is File && entity.path.endsWith('.dart')) {
+        files.add(entity);
+      }
+    }
+  }
+
+  // look for both kinds of quotes
+  static RegExp importRegex1 = RegExp(r"^(import|export)\s+\'(\S+)\'");
+  static RegExp importRegex2 = RegExp(r'^(import|export)\s+"(\S+)"');
+
+  List<String> _collectImports(File file) {
+    var results = <String>[];
+
+    for (var line in file.readAsLinesSync()) {
+      // Check for a few tokens that should stop our parse.
+      if (line.startsWith('class ') ||
+          line.startsWith('typedef ') ||
+          line.startsWith('mixin ') ||
+          line.startsWith('enum ') ||
+          line.startsWith('extension ') ||
+          line.startsWith('void ') ||
+          line.startsWith('Future ') ||
+          line.startsWith('final ') ||
+          line.startsWith('const ')) {
+        break;
+      }
+
+      var match = importRegex1.firstMatch(line);
+      if (match != null) {
+        results.add(match.group(2));
+        continue;
+      }
+
+      match = importRegex2.firstMatch(line);
+      if (match != null) {
+        results.add(match.group(2));
+        continue;
+      }
+    }
+
+    return results;
+  }
+
+  String _topLevelDir(File file) {
+    var relativePath = path.relative(file.path, from: dir);
+    return path.split(relativePath).first;
+  }
+}
+
+String _printSet(Set<String> value) {
+  var list = value.toList()..sort();
+  list = list.map((item) => 'package:$item').toList();
+  if (list.length > 1) {
+    return list.sublist(0, list.length - 1).join(', ') + ' and ' + list.last;
+  } else {
+    return list.join(', ');
+  }
+}
+
+class SdkDeps {
+  final File file;
+
+  List<String> pkgs = [];
+  List<String> testedPkgs = [];
+
+  SdkDeps(this.file);
+
+  void parse() {
+    // Var("dart_root") + "/third_party/pkg/dart2js_info":
+    final pkgRegExp = RegExp(r'"/third_party/pkg/(\S+)"');
+
+    // Var("dart_root") + "/third_party/pkg_tested/dart_style":
+    final testedPkgRegExp = RegExp(r'"/third_party/pkg_tested/(\S+)"');
+
+    for (var line in file.readAsLinesSync()) {
+      var pkgDep = pkgRegExp.firstMatch(line);
+      var testedPkgDep = testedPkgRegExp.firstMatch(line);
+
+      if (pkgDep != null) {
+        pkgs.add(pkgDep.group(1));
+      } else if (testedPkgDep != null) {
+        testedPkgs.add(testedPkgDep.group(1));
+      }
+    }
+
+    pkgs.sort();
+    testedPkgs.sort();
+  }
+}
diff --git a/tools/package_deps/pubspec.yaml b/tools/package_deps/pubspec.yaml
new file mode 100644
index 0000000..f581f76
--- /dev/null
+++ b/tools/package_deps/pubspec.yaml
@@ -0,0 +1,15 @@
+name: package_deps
+description: A tool to validate pubspec files in pkg/.
+
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+
+environment:
+  sdk: '>=2.8.1 <3.0.0'
+
+dependencies:
+  path: any
+  yaml: any
+
+dev_dependencies:
+  pedantic: ^1.9.0