Version 2.13.0-8.0.dev

Merge commit '2e3ac0cf154ab0387fc6b8859480a0a27a67fcb3' into 'dev'
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index bfaf1df..ef388b7 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -213,6 +213,34 @@
     return []
 
 
+def _CheckPackageConfigUpToDate(input_api, output_api):
+    """Checks that .dart_tool/package_config.json is up to date."""
+    # Run only if DEPS file or package_config.json have been modified.
+    if not any(p == 'DEPS' or p == '.dart_tool/package_config.json' or
+               p.endswith('pubspec.yaml') for p in input_api.LocalPaths()):
+        return []
+    local_root = input_api.change.RepositoryRoot()
+    utils = imp.load_source('utils',
+                            os.path.join(local_root, 'tools', 'utils.py'))
+
+    dart = utils.CheckedInSdkExecutable()
+    generate = os.path.join(local_root, 'tools', 'generate_package_config.dart')
+    cmd = [dart, generate, '--check']
+    pipe = subprocess.Popen(cmd,
+                            stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE,
+                            shell=utils.IsWindows())
+    output = pipe.communicate()
+    if pipe.returncode != 0:
+        return [
+            output_api.PresubmitError(
+                'File .dart_tool/package_config.json is out of date.\n'
+                'Fix these issues with:\n'
+                '%s tools/generate_package_config.dart' % (dart))
+        ]
+    return []
+
+
 def _CheckValidHostsInDEPS(input_api, output_api):
     """Checks that DEPS file deps are from allowed_hosts."""
     # Run only if DEPS file has been modified to annoy fewer bystanders.
@@ -333,6 +361,7 @@
     results.extend(_CheckLayering(input_api, output_api))
     results.extend(_CheckClangTidy(input_api, output_api))
     results.extend(_CheckTestMatrixValid(input_api, output_api))
+    results.extend(_CheckPackageConfigUpToDate(input_api, output_api))
     results.extend(
         input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
     return results
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index 327b003..ba9b171 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -1716,7 +1716,7 @@
           info.discardPromotionsAndMarkNotUnassigned();
       if (!identical(info, newInfo)) {
         (newVariableInfo ??=
-            new Map<Variable?, VariableModel<Variable, Type>>.from(
+            new Map<Variable?, VariableModel<Variable, Type>>.of(
                 variableInfo))[variable] = newInfo;
       }
     }
@@ -1725,7 +1725,7 @@
       VariableModel<Variable, Type>? info = variableInfo[variable];
       if (info == null) {
         (newVariableInfo ??=
-            new Map<Variable, VariableModel<Variable, Type>>.from(
+            new Map<Variable?, VariableModel<Variable, Type>>.of(
                 variableInfo))[variable] = new VariableModel<Variable, Type>(
             promotedTypes: null,
             tested: const [],
@@ -1734,7 +1734,7 @@
             ssaNode: null);
       } else if (!info.writeCaptured) {
         (newVariableInfo ??=
-            new Map<Variable, VariableModel<Variable, Type>>.from(
+            new Map<Variable?, VariableModel<Variable, Type>>.of(
                 variableInfo))[variable] = info.writeCapture();
       }
     }
@@ -2194,7 +2194,7 @@
       {Reachability? reachable}) {
     reachable ??= this.reachable;
     Map<Variable?, VariableModel<Variable, Type>> newVariableInfo =
-        new Map<Variable?, VariableModel<Variable, Type>>.from(variableInfo);
+        new Map<Variable?, VariableModel<Variable, Type>>.of(variableInfo);
     reference.storeInfo(newVariableInfo, model);
     return new FlowModel<Variable, Type>.withInfo(reachable, newVariableInfo);
   }
@@ -3274,7 +3274,7 @@
           Type newType,
           TypeOperations<Variable, Type> typeOperations) {
     if (_typeListContains(typeOperations, types, newType)) return types;
-    return new List<Type>.from(types)..add(newType);
+    return new List<Type>.of(types)..add(newType);
   }
 
   /// Creates a new [VariableModel] object, unless it is equivalent to either
@@ -4732,7 +4732,7 @@
           _assignedVariables._anywhere._written.contains(entry.key)) {
         continue;
       }
-      (newKnownTypes ??= new Map<Variable, Type>.from(_knownTypes))[entry.key] =
+      (newKnownTypes ??= new Map<Variable, Type>.of(_knownTypes))[entry.key] =
           entry.value;
     }
     if (newKnownTypes != null) _knownTypes = newKnownTypes;
@@ -4877,8 +4877,8 @@
             _assignedVariables._anywhere._written.contains(entry.key)) {
           continue;
         }
-        (newKnownTypes ??=
-            new Map<Variable, Type>.from(_knownTypes))[entry.key] = entry.value;
+        (newKnownTypes ??= new Map<Variable, Type>.of(_knownTypes))[entry.key] =
+            entry.value;
       }
       if (newKnownTypes != null) _knownTypes = newKnownTypes;
     }
@@ -5012,7 +5012,7 @@
       VariableModel<Variable, Type> variableModel) {
     VariableModel<Variable, Type> targetInfo = target.getInfo(variableInfo);
     Map<String, VariableModel<Variable, Type>> newProperties =
-        new Map<String, VariableModel<Variable, Type>>.from(
+        new Map<String, VariableModel<Variable, Type>>.of(
             targetInfo.properties);
     newProperties[propertyName] = variableModel;
     target.storeInfo(variableInfo, targetInfo.setProperties(newProperties));
diff --git a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
index f205135..ecac3e6 100644
--- a/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
+++ b/pkg/analysis_server/lib/src/services/completion/dart/feature_computer.dart
@@ -517,7 +517,7 @@
           if (offset <= argument.offset) {
             return typeOfIndexPositionalParameter();
           }
-          if (argument.contains(offset)) {
+          if (argument.contains(offset) && offset >= argument.name.end) {
             return argument.staticParameterElement?.type;
           }
           return null;
diff --git a/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart b/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart
index 03c9c87..d6746b3 100644
--- a/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart
+++ b/pkg/analysis_server/test/src/services/completion/dart/feature_computer_test.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analysis_server/src/services/completion/dart/feature_computer.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
+import 'package:analyzer_plugin/src/utilities/completion/completion_target.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
@@ -11,29 +11,18 @@
 
 void main() {
   defineReflectiveSuite(() {
-    defineReflectiveTests(FeatureComputerTest);
+    defineReflectiveTests(ContextTypeTest);
   });
 }
 
 @reflectiveTest
-class FeatureComputerTest extends AbstractSingleUnitTest {
-  @override
-  bool verifyNoTestUnitErrors = false;
-
+class ContextTypeTest extends FeatureComputerTest {
   Future<void> assertContextType(String content, String expectedType) async {
-    var index = content.indexOf('^');
-    if (index < 0) {
-      fail('Missing node offset marker (^) in content');
-    }
-    content = content.substring(0, index) + content.substring(index + 1);
-    await resolveTestCode(content);
-    // TODO(jwren) Consider changing this from the NodeLocator to the optype
-    // node finding logic to be more consistent with what the user behavior
-    // here will be.
-    var node = NodeLocator(index).searchWithin(testUnit);
+    await completeIn(content);
     var computer = FeatureComputer(
         testAnalysisResult.typeSystem, testAnalysisResult.typeProvider);
-    var type = computer.computeContextType(node, index);
+    var type = computer.computeContextType(
+        completionTarget.containingNode, cursorIndex);
 
     if (expectedType == null) {
       expect(type, null);
@@ -687,3 +676,23 @@
 ''', null);
   }
 }
+
+abstract class FeatureComputerTest extends AbstractSingleUnitTest {
+  int cursorIndex = 0;
+
+  CompletionTarget completionTarget;
+
+  @override
+  bool verifyNoTestUnitErrors = false;
+
+  Future<void> completeIn(String content) async {
+    cursorIndex = content.indexOf('^');
+    if (cursorIndex < 0) {
+      fail('Missing node offset marker (^) in content');
+    }
+    content =
+        content.substring(0, cursorIndex) + content.substring(cursorIndex + 1);
+    await resolveTestCode(content);
+    completionTarget = CompletionTarget.forOffset(testUnit, cursorIndex);
+  }
+}
diff --git a/pkg/analyzer/lib/dart/ast/ast.dart b/pkg/analyzer/lib/dart/ast/ast.dart
index 80c8339..4219495 100644
--- a/pkg/analyzer/lib/dart/ast/ast.dart
+++ b/pkg/analyzer/lib/dart/ast/ast.dart
@@ -4684,7 +4684,7 @@
 ///    rawStringLiteral ::=
 ///        'r' basicStringLiteral
 ///
-///    simpleStringLiteral ::=
+///    basicStringLiteral ::=
 ///        multiLineStringLiteral
 ///      | singleLineStringLiteral
 ///
diff --git a/pkg/analyzer/lib/src/dart/element/type_system.dart b/pkg/analyzer/lib/src/dart/element/type_system.dart
index b79502d..3fa2aa1 100644
--- a/pkg/analyzer/lib/src/dart/element/type_system.dart
+++ b/pkg/analyzer/lib/src/dart/element/type_system.dart
@@ -1795,8 +1795,6 @@
         var numTypeQuestion = typeProvider.numTypeQuestion;
         if (isSubtypeOf(t1, numTypeQuestion) && !t2.isBottom && !t3.isBottom) {
           assert(!t1.isBottom);
-          assert(isSubtypeOf(t2, numTypeQuestion));
-          assert(isSubtypeOf(t3, numTypeQuestion));
           // Then:
           // - If T1, T2 and T3 are all subtypes of int, the static type of e is
           //   int.
diff --git a/pkg/analyzer/lib/src/error/best_practices_verifier.dart b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
index 9f75b54..1c32c5c 100644
--- a/pkg/analyzer/lib/src/error/best_practices_verifier.dart
+++ b/pkg/analyzer/lib/src/error/best_practices_verifier.dart
@@ -35,7 +35,6 @@
 import 'package:analyzer/src/workspace/workspace.dart';
 import 'package:meta/meta.dart';
 import 'package:meta/meta_meta.dart';
-import 'package:path/path.dart' as path;
 
 /// Instances of the class `BestPracticesVerifier` traverse an AST structure
 /// looking for violations of Dart best practices.
@@ -128,6 +127,7 @@
       analysisOptions,
       _workspacePackage,
     );
+    _invalidAccessVerifier._inTestDirectory = _linterContext.inTestDir(unit);
   }
 
   bool get _inPublicPackageApi {
@@ -1684,12 +1684,6 @@
 
 class _InvalidAccessVerifier {
   static final _templateExtension = '.template';
-  static final _testDirectories = [
-    '${path.separator}test${path.separator}',
-    '${path.separator}integration_test${path.separator}',
-    '${path.separator}test_driver${path.separator}',
-    '${path.separator}testing${path.separator}',
-  ];
 
   final ErrorReporter _errorReporter;
   final LibraryElement _library;
@@ -1704,7 +1698,6 @@
       this._errorReporter, this._library, this._workspacePackage) {
     var path = _library.source.fullName;
     _inTemplateSource = path.contains(_templateExtension);
-    _inTestDirectory = _testDirectories.any(path.contains);
   }
 
   /// Produces a hint if [identifier] is accessed from an invalid location.
diff --git a/pkg/analyzer/lib/src/lint/linter.dart b/pkg/analyzer/lib/src/lint/linter.dart
index 59a0a74..fd04724 100644
--- a/pkg/analyzer/lib/src/lint/linter.dart
+++ b/pkg/analyzer/lib/src/lint/linter.dart
@@ -269,6 +269,9 @@
   /// Return the result of evaluating the given expression.
   LinterConstantEvaluationResult evaluateConstant(Expression node);
 
+  /// Return `true` if the given [unit] is in a test directory.
+  bool inTestDir(CompilationUnit unit);
+
   /// Return `true` if the [feature] is enabled in the library being linted.
   bool isEnabled(Feature feature);
 
@@ -281,6 +284,13 @@
 
 /// Implementation of [LinterContext]
 class LinterContextImpl implements LinterContext {
+  static final _testDirectories = [
+    '${p.separator}test${p.separator}',
+    '${p.separator}integration_test${p.separator}',
+    '${p.separator}test_driver${p.separator}',
+    '${p.separator}testing${p.separator}',
+  ];
+
   @override
   final List<LinterContextUnit> allUnits;
 
@@ -369,6 +379,12 @@
   }
 
   @override
+  bool inTestDir(CompilationUnit unit) {
+    var path = unit.declaredElement?.source.fullName;
+    return path != null && _testDirectories.any(path.contains);
+  }
+
+  @override
   bool isEnabled(Feature feature) {
     var unitElement = currentUnit.unit.declaredElement!;
     return unitElement.library.featureSet.isEnabled(feature);
diff --git a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
index 9393416..e493bde8 100644
--- a/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/method_invocation_test.dart
@@ -189,6 +189,21 @@
         expectedType: 'num');
   }
 
+  test_clamp_int_double_dynamic() async {
+    await assertNoErrorsInCode('''
+f(int a, double b, dynamic c) {
+  a.clamp(b, c);
+}
+''');
+
+    assertMethodInvocation(
+        findNode.methodInvocation('clamp'),
+        elementMatcher(numElement.getMethod('clamp'),
+            isLegacy: isNullSafetySdkAndLegacyLibrary),
+        'num Function(num, num)',
+        expectedType: 'num');
+  }
+
   test_clamp_int_double_int() async {
     await assertNoErrorsInCode('''
 f(int a, double b, int c) {
@@ -204,6 +219,36 @@
         expectedType: 'num');
   }
 
+  test_clamp_int_dynamic_double() async {
+    await assertNoErrorsInCode('''
+f(int a, dynamic b, double c) {
+  a.clamp(b, c);
+}
+''');
+
+    assertMethodInvocation(
+        findNode.methodInvocation('clamp'),
+        elementMatcher(numElement.getMethod('clamp'),
+            isLegacy: isNullSafetySdkAndLegacyLibrary),
+        'num Function(num, num)',
+        expectedType: 'num');
+  }
+
+  test_clamp_int_dynamic_int() async {
+    await assertNoErrorsInCode('''
+f(int a, dynamic b, int c) {
+  a.clamp(b, c);
+}
+''');
+
+    assertMethodInvocation(
+        findNode.methodInvocation('clamp'),
+        elementMatcher(numElement.getMethod('clamp'),
+            isLegacy: isNullSafetySdkAndLegacyLibrary),
+        'num Function(num, num)',
+        expectedType: 'num');
+  }
+
   test_clamp_int_int_double() async {
     await assertNoErrorsInCode('''
 f(int a, int b, double c) {
@@ -219,6 +264,21 @@
         expectedType: 'num');
   }
 
+  test_clamp_int_int_dynamic() async {
+    await assertNoErrorsInCode('''
+f(int a, int b, dynamic c) {
+  a.clamp(b, c);
+}
+''');
+
+    assertMethodInvocation(
+        findNode.methodInvocation('clamp'),
+        elementMatcher(numElement.getMethod('clamp'),
+            isLegacy: isNullSafetySdkAndLegacyLibrary),
+        'num Function(num, num)',
+        expectedType: 'num');
+  }
+
   test_clamp_int_int_int() async {
     await assertNoErrorsInCode('''
 f(int a, int b, int c) {
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index b1d2000..d4a9558 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -234,9 +234,22 @@
     passThrough(argument);
   }
 
-  void addInEnvironment(String argument) {
+  void addInEnvironment(Iterator<String> arguments) {
+    final isDefine = arguments.current.startsWith('--define');
+    String argument;
+    if (arguments.current == '--define') {
+      arguments.moveNext();
+      argument = arguments.current;
+    } else {
+      argument = arguments.current.substring(isDefine ? '--define='.length : 2);
+    }
+    // Allow for ' ' or '=' after --define
     int eqIndex = argument.indexOf('=');
-    String name = argument.substring(2, eqIndex);
+    if (eqIndex <= 0) {
+      helpAndFail('Invalid value for --define: $argument');
+      return;
+    }
+    String name = argument.substring(0, eqIndex);
     String value = argument.substring(eqIndex + 1);
     environment[name] = value;
   }
@@ -564,7 +577,8 @@
     new OptionHandler('${Flags.mergeFragmentsThreshold}=.+', passThrough),
 
     // The following three options must come last.
-    new OptionHandler('-D.+=.*', addInEnvironment),
+    new OptionHandler('-D.+=.*|--define=.+=.*|--define', addInEnvironment,
+        multipleArguments: true),
     new OptionHandler('-.*', (String argument) {
       helpAndFail("Unknown option '$argument'.");
     }),
@@ -1030,7 +1044,7 @@
   -v, --verbose
     Display verbose information.
 
-  -D<name>=<value>
+  -D<name>=<value>, --define=<name>=<value>
     Define an environment declaration.
 
   --version
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index affa10d..2e6da52 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -88,7 +88,11 @@
         help: 'Generate minified output.',
         abbr: 'm',
         negatable: false,
-      );
+      )
+      ..addMultiOption('define', abbr: 'D', valueHelp: 'key=value', help: '''
+Define an environment declaration. To specify multiple declarations, use multiple options or use commas to separate key-value pairs.
+For example: dart compile $cmdName -Da=1,b=2 main.dart''');
+
     addExperimentalFlags(argParser, verbose);
   }
 
@@ -120,6 +124,24 @@
     if (!checkFile(sourcePath)) {
       return 1;
     }
+    final args = <String>[
+      '--libraries-spec=$librariesPath',
+      if (argResults.enabledExperiments.isNotEmpty)
+        "--enable-experiment=${argResults.enabledExperiments.join(',')}",
+      if (argResults.wasParsed(commonOptions['outputFile'].flag))
+        "-o${argResults[commonOptions['outputFile'].flag]}",
+      if (argResults.wasParsed('minified')) '-m',
+    ];
+
+    if (argResults.wasParsed('define')) {
+      for (final define in argResults['define']) {
+        args.add('-D$define');
+      }
+    }
+
+    // Add any args that weren't parsed to the end. This will likely only ever
+    // be the script name.
+    args.addAll(argResults.rest);
 
     VmInteropHandler.run(
         sdk.dart2jsSnapshot,
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 2825b49..45bfd1e 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -109,8 +109,12 @@
         );
     }
     argParser
-      ..addMultiOption('define',
-          abbr: 'D', help: 'Defines an environment variable', hide: true)
+      ..addMultiOption(
+        'define',
+        abbr: 'D',
+        valueHelp: 'key=value',
+        help: 'Define an environment declaration.',
+      )
       ..addFlag(
         'disable-service-auth-codes',
         hide: !verbose,
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index 458fb39..17cc1a8 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -187,7 +187,11 @@
   });
 
   test('Compile JS', () {
-    final p = project(mainSrc: "void main() { print('Hello from JS'); }");
+    final p = project(mainSrc: '''
+        void main() {
+          print('1: ' + const String.fromEnvironment('foo'));
+          print('2: ' + const String.fromEnvironment('bar'));
+        }''');
     final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
     final outFile = path.canonicalize(path.join(p.dirPath, 'main.js'));
 
@@ -195,6 +199,8 @@
       'compile',
       'js',
       '-m',
+      '-Dfoo=bar',
+      '--define=bar=foo',
       '-o',
       outFile,
       '-v',
@@ -202,8 +208,14 @@
     ]);
     expect(result.stderr, isEmpty);
     expect(result.exitCode, 0);
-    expect(File(outFile).existsSync(), true,
-        reason: 'File not found: $outFile');
+    final file = File(outFile);
+    expect(file.existsSync(), true, reason: 'File not found: $outFile');
+
+    // Ensure the -D and --define arguments were processed correctly.
+    final contents = file.readAsStringSync();
+    print(contents);
+    expect(contents.contains('1: bar'), true);
+    expect(contents.contains('2: foo'), true);
   });
 
   test('Compile exe with error', () {
diff --git a/pkg/dartdev/test/commands/run_test.dart b/pkg/dartdev/test/commands/run_test.dart
index 12c07d3..b8cc5e8 100644
--- a/pkg/dartdev/test/commands/run_test.dart
+++ b/pkg/dartdev/test/commands/run_test.dart
@@ -181,6 +181,7 @@
       '--no-pause-isolates-on-exit',
       '--no-pause-isolates-on-unhandled-exceptions',
       '-Dfoo=bar',
+      '--define=bar=foo',
       p.relativeFilePath,
     ]);
     expect(
@@ -202,6 +203,7 @@
       '--no-pause-isolates-on-unhandled-exceptions',
       '--disable-service-auth-codes',
       '-Dfoo=bar',
+      '--define=bar=foo',
       p.relativeFilePath,
     ]);
 
diff --git a/pkg/dev_compiler/lib/src/kernel/command.dart b/pkg/dev_compiler/lib/src/kernel/command.dart
index 8085f69..550c6e1 100644
--- a/pkg/dev_compiler/lib/src/kernel/command.dart
+++ b/pkg/dev_compiler/lib/src/kernel/command.dart
@@ -730,8 +730,19 @@
   var declaredVariables = <String, String>{};
   for (var i = 0; i < args.length;) {
     var arg = args[i];
+    String rest;
+    const defineFlag = '--define';
     if (arg.startsWith('-D') && arg.length > 2) {
-      var rest = arg.substring(2);
+      rest = arg.substring(2);
+    } else if (arg.startsWith('$defineFlag=') &&
+        arg.length > defineFlag.length + 1) {
+      rest = arg.substring(defineFlag.length + 1);
+    } else if (arg == defineFlag) {
+      i++;
+      rest = args[i];
+    }
+
+    if (rest != null) {
       var eq = rest.indexOf('=');
       if (eq <= 0) {
         var kind = eq == 0 ? 'name' : 'value';
diff --git a/pkg/front_end/tool/_fasta/command_line.dart b/pkg/front_end/tool/_fasta/command_line.dart
index bfa8f4b..433c88a 100644
--- a/pkg/front_end/tool/_fasta/command_line.dart
+++ b/pkg/front_end/tool/_fasta/command_line.dart
@@ -203,6 +203,7 @@
   Flags.noDeps: const BoolValue(false),
   Flags.invocationModes: const StringValue(),
   "-D": const DefineValue(),
+  "--define": const AliasValue("-D"),
   "-h": const AliasValue(Flags.help),
   "--out": const AliasValue(Flags.output),
   "-o": const AliasValue(Flags.output),
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 0ec7acd..9ef37d0 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -1197,6 +1197,7 @@
       arguments.where((name) => name.endsWith('.dart')).single,
       ...arguments.where((name) =>
           name.startsWith('-D') ||
+          name.startsWith('--define') ||
           name.startsWith('--packages=') ||
           name.startsWith('--enable-experiment=')),
       '-Ddart.vm.product=$isProductMode',
diff --git a/runtime/bin/main_options.cc b/runtime/bin/main_options.cc
index 517029d..1bc116d 100644
--- a/runtime/bin/main_options.cc
+++ b/runtime/bin/main_options.cc
@@ -147,6 +147,9 @@
 "  all VM options).\n"
 "--packages=<path>\n"
 "  Where to find a package spec file.\n"
+"--define=<key>=<value> or -D<key>=<value>\n"
+"  Define an environment declaration. To specify multiple declarations,\n"
+"  use multiple instances of this option.\n"
 "--observe[=<port>[/<bind-address>]]\n"
 "  The observe flag is a convenience flag used to run a program with a\n"
 "  set of options which are often useful for debugging under Observatory.\n"
@@ -180,6 +183,9 @@
 "  all VM options).\n"
 "--packages=<path>\n"
 "  Where to find a package spec file.\n"
+"--define=<key>=<value> or -D<key>=<value>\n"
+"  Define an environment declaration. To specify multiple declarations,\n"
+"  use multiple instances of this option.\n"
 "--observe[=<port>[/<bind-address>]]\n"
 "  The observe flag is a convenience flag used to run a program with a\n"
 "  set of options which are often useful for debugging under Observatory.\n"
@@ -546,7 +552,8 @@
     while (tmp_i < argc) {
       // Check if this flag is a potentially valid VM flag. If not, we've likely
       // hit a script name and are done parsing VM flags.
-      if (!OptionProcessor::IsValidFlag(argv[tmp_i])) {
+      if (!OptionProcessor::IsValidFlag(argv[tmp_i]) &&
+          !OptionProcessor::IsValidShortFlag(argv[tmp_i])) {
         break;
       }
       OptionProcessor::TryProcess(argv[tmp_i], vm_options);
diff --git a/runtime/bin/options.cc b/runtime/bin/options.cc
index 7ad5d20..d32ca5f 100644
--- a/runtime/bin/options.cc
+++ b/runtime/bin/options.cc
@@ -41,22 +41,35 @@
   return false;
 }
 
+static bool IsPrefix(const char* prefix, size_t prefix_len, const char* str) {
+  ASSERT(prefix != nullptr);
+  ASSERT(str != nullptr);
+  const size_t str_len = strlen(str);
+  if (str_len < prefix_len) {
+    return false;
+  }
+  return strncmp(prefix, str, prefix_len) == 0;
+}
+
 bool OptionProcessor::ProcessEnvironmentOption(
     const char* arg,
     CommandLineOptions* vm_options,
     dart::SimpleHashMap** environment) {
   ASSERT(arg != NULL);
   ASSERT(environment != NULL);
-  if (*arg == '\0') {
+  const char* kShortPrefix = "-D";
+  const char* kLongPrefix = "--define=";
+  const int kShortPrefixLen = strlen(kShortPrefix);
+  const int kLongPrefixLen = strlen(kLongPrefix);
+  const bool is_short_form = IsPrefix(kShortPrefix, kShortPrefixLen, arg);
+  const bool is_long_form = IsPrefix(kLongPrefix, kLongPrefixLen, arg);
+  if (is_short_form) {
+    arg = arg + kShortPrefixLen;
+  } else if (is_long_form) {
+    arg = arg + kLongPrefixLen;
+  } else {
     return false;
   }
-  if (*arg != '-') {
-    return false;
-  }
-  if (*(arg + 1) != 'D') {
-    return false;
-  }
-  arg = arg + 2;
   if (*arg == '\0') {
     return true;
   }
@@ -69,12 +82,20 @@
   const char* equals_pos = strchr(arg, '=');
   if (equals_pos == NULL) {
     // No equal sign (name without value) currently not supported.
-    Syslog::PrintErr("No value given to -D option\n");
+    if (is_short_form) {
+      Syslog::PrintErr("No value given to -D option\n");
+    } else {
+      Syslog::PrintErr("No value given to --define option\n");
+    }
     return true;
   }
   int name_len = equals_pos - arg;
   if (name_len == 0) {
-    Syslog::PrintErr("No name given to -D option\n");
+    if (is_short_form) {
+      Syslog::PrintErr("No name given to -D option\n");
+    } else {
+      Syslog::PrintErr("No name given to --define option\n");
+    }
     return true;
   }
   // Split name=value into name and value.
diff --git a/tests/corelib/string_from_environment_test.dart b/tests/corelib/string_from_environment_test.dart
index 2134e44..974e89e 100644
--- a/tests/corelib/string_from_environment_test.dart
+++ b/tests/corelib/string_from_environment_test.dart
@@ -1,7 +1,7 @@
 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-// SharedOptions=-Da=a -Db=bb -Dc=ccc -Dd=
+// SharedOptions=-Da=a -Db=bb -Dc=ccc -Dd= --define=e=eeee
 
 import "package:expect/expect.dart";
 
@@ -10,4 +10,5 @@
   Expect.equals('bb', const String.fromEnvironment('b'));
   Expect.equals('ccc', const String.fromEnvironment('c'));
   Expect.equals('', const String.fromEnvironment('d'));
+  Expect.equals('eeee', const String.fromEnvironment('e'));
 }
diff --git a/tests/corelib_2/string_from_environment_test.dart b/tests/corelib_2/string_from_environment_test.dart
index 2134e44..974e89e 100644
--- a/tests/corelib_2/string_from_environment_test.dart
+++ b/tests/corelib_2/string_from_environment_test.dart
@@ -1,7 +1,7 @@
 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-// SharedOptions=-Da=a -Db=bb -Dc=ccc -Dd=
+// SharedOptions=-Da=a -Db=bb -Dc=ccc -Dd= --define=e=eeee
 
 import "package:expect/expect.dart";
 
@@ -10,4 +10,5 @@
   Expect.equals('bb', const String.fromEnvironment('b'));
   Expect.equals('ccc', const String.fromEnvironment('c'));
   Expect.equals('', const String.fromEnvironment('d'));
+  Expect.equals('eeee', const String.fromEnvironment('e'));
 }
diff --git a/tools/VERSION b/tools/VERSION
index 5f9771b..7ebd6a0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 7
+PRERELEASE 8
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/generate_package_config.dart b/tools/generate_package_config.dart
index 516dcf7..f5868ff 100644
--- a/tools/generate_package_config.dart
+++ b/tools/generate_package_config.dart
@@ -7,14 +7,43 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:args/args.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 
+bool _parseOptions(List<String> args) {
+  const usage = "Usage: dart generate_package_config.dart [flags...]";
+
+  var parser = ArgParser();
+
+  parser.addFlag("help", abbr: "h");
+
+  parser.addFlag("check",
+      abbr: "c",
+      help: "Return with a non-zero exit code if not up-to-date",
+      negatable: false);
+
+  var results = parser.parse(args);
+
+  if (results["help"] as bool) {
+    print("Regenerate the .dart_tool/package_config.json file.");
+    print("");
+    print(usage);
+    print("");
+    print(parser.usage);
+    exit(0);
+  }
+
+  return results["check"] as bool;
+}
+
 final repoRoot = p.dirname(p.dirname(p.fromUri(Platform.script)));
 final configFilePath = p.join(repoRoot, '.dart_tool/package_config.json');
 
 void main(List<String> args) {
+  bool checkOnly = _parseOptions(args);
+
   var packageDirs = [
     ...listSubdirectories('pkg'),
     ...listSubdirectories('third_party/pkg'),
@@ -60,6 +89,24 @@
   ];
   packages.sort((a, b) => a["name"].compareTo(b["name"]));
 
+  var configFile = File(p.join(repoRoot, '.dart_tool', 'package_config.json'));
+
+  // Validate the packages entry only, to avoid spurious failures from changes
+  // in the dates embedded in the other entries.
+  if (checkOnly) {
+    var json =
+        jsonDecode(configFile.readAsStringSync()) as Map<dynamic, dynamic>;
+    var oldPackages = json['packages'] as List<dynamic>;
+    if (jsonEncode(packages) == jsonEncode(oldPackages)) {
+      print("Package config up to date");
+      exit(0);
+    } else {
+      print("Package config out of date");
+      print("Run `dart tools/generate_package_config.dart` to update");
+      exit(1);
+    }
+  }
+
   var year = DateTime.now().year;
   var config = <String, dynamic>{
     'copyright': [
@@ -81,8 +128,7 @@
 
   // TODO(rnystrom): Consider using package_config_v2 to generate this instead.
   var json = JsonEncoder.withIndent('  ').convert(config);
-  File(p.join(repoRoot, '.dart_tool', 'package_config.json'))
-      .writeAsStringSync(json);
+  configFile.writeAsStringSync(json);
   print('Generated .dart_tool/package_config.dart containing '
       '${packages.length} packages.');
 }