[cfe,dart2js,dartdev] Show null safety mode for `dart compile`

This adds a new messages kind 'info' to the CFE for showing general
information during compilation. A 'configuration' options is added
to `CompilerOptions` for telling the CFE how it is run.

The configuration 'compile' is added for when the CFE is invoked to
produces an "executable" as when running `dart compile`. When
configuration is set, the CFE emits an info message about the
null safety compilation mode.

Support for `dart compile exe` and `dart compile js` is added in this
CL. Support for `dart compile kernel|app-jit|aot` is not included.

In response to https://github.com/dart-lang/sdk/issues/44234

TEST=pkg/dartdev/test/commands/compile_test.dart

Change-Id: I08f51e2a3f5ad4841c4d703bcd266b7afb63c7c6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/178982
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Jens Johansen <jensj@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index d2ba523..31b5e0f 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -988,6 +988,26 @@
 }
 
 // DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeCompilingWithSoundNullSafety =
+    messageCompilingWithSoundNullSafety;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageCompilingWithSoundNullSafety = const MessageCode(
+    "CompilingWithSoundNullSafety",
+    severity: Severity.info,
+    message: r"""Compiling with sound null safety""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Null> codeCompilingWithUnsoundNullSafety =
+    messageCompilingWithUnsoundNullSafety;
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const MessageCode messageCompilingWithUnsoundNullSafety = const MessageCode(
+    "CompilingWithUnsoundNullSafety",
+    severity: Severity.info,
+    message: r"""Compiling with unsound null safety""");
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
 const Template<
     Message Function(
         String string,
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
index 0358608..9818cca 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/severity.dart
@@ -10,6 +10,7 @@
   ignored,
   internalProblem,
   warning,
+  info,
 }
 
 const Map<String, String> severityEnumNames = const <String, String>{
@@ -18,6 +19,7 @@
   'IGNORED': 'ignored',
   'INTERNAL_PROBLEM': 'internalProblem',
   'WARNING': 'warning',
+  'INFO': 'info',
 };
 
 const Map<String, Severity> severityEnumValues = const <String, Severity>{
@@ -26,6 +28,7 @@
   'IGNORED': Severity.ignored,
   'INTERNAL_PROBLEM': Severity.internalProblem,
   'WARNING': Severity.warning,
+  'INFO': Severity.info,
 };
 
 const Map<Severity, String> severityPrefixes = const <Severity, String>{
@@ -33,6 +36,7 @@
   Severity.internalProblem: "Internal problem",
   Severity.warning: "Warning",
   Severity.context: "Context",
+  Severity.info: "Info",
 };
 
 const Map<Severity, String> severityTexts = const <Severity, String>{
@@ -40,4 +44,5 @@
   Severity.internalProblem: "internal problem",
   Severity.warning: "warning",
   Severity.context: "context",
+  Severity.info: "info",
 };
diff --git a/pkg/compiler/lib/src/commandline_options.dart b/pkg/compiler/lib/src/commandline_options.dart
index c93d471..bf9f4a7 100644
--- a/pkg/compiler/lib/src/commandline_options.dart
+++ b/pkg/compiler/lib/src/commandline_options.dart
@@ -127,6 +127,8 @@
 
   static const String conditionalDirectives = '--conditional-directives';
 
+  static const String cfeInvocationModes = '--cfe-invocation-modes';
+
   // The syntax-only level of support for generic methods is included in the
   // 1.50 milestone for Dart. It is not experimental, but also not permanent:
   // a full implementation is expected in the future. Hence, the
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 05bf1ff..6faa855 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -726,9 +726,15 @@
     reportDiagnosticInternal(message, infos, api.Diagnostic.HINT);
   }
 
+  @override
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
+    reportDiagnosticInternal(message, infos, api.Diagnostic.INFO);
+  }
+
   @deprecated
   @override
-  void reportInfo(Spannable node, MessageKind messageKind,
+  void reportInfoMessage(Spannable node, MessageKind messageKind,
       [Map<String, String> arguments = const {}]) {
     reportDiagnosticInternal(createMessage(node, messageKind, arguments),
         const <DiagnosticMessage>[], api.Diagnostic.INFO);
diff --git a/pkg/compiler/lib/src/dart2js.dart b/pkg/compiler/lib/src/dart2js.dart
index 5a69e20..c528dfb 100644
--- a/pkg/compiler/lib/src/dart2js.dart
+++ b/pkg/compiler/lib/src/dart2js.dart
@@ -544,6 +544,7 @@
     new OptionHandler(Flags.useOldRti, passThrough),
     new OptionHandler(Flags.testMode, passThrough),
     new OptionHandler('${Flags.dumpSsa}=.+', passThrough),
+    new OptionHandler('${Flags.cfeInvocationModes}=.+', passThrough),
 
     // Experimental features.
     // We don't provide documentation for these yet.
diff --git a/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart b/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
index 554e809..b009473 100644
--- a/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
+++ b/pkg/compiler/lib/src/diagnostics/diagnostic_listener.dart
@@ -49,8 +49,11 @@
   void reportHint(DiagnosticMessage message,
       [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]);
 
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]);
+
   @deprecated
-  void reportInfo(Spannable node, MessageKind errorCode,
+  void reportInfoMessage(Spannable node, MessageKind errorCode,
       [Map<String, String> arguments = const {}]);
 
   /// Set current element of this reporter to [element]. This is used for
diff --git a/pkg/compiler/lib/src/dump_info.dart b/pkg/compiler/lib/src/dump_info.dart
index 3e82de2..691151c 100644
--- a/pkg/compiler/lib/src/dump_info.dart
+++ b/pkg/compiler/lib/src/dump_info.dart
@@ -581,7 +581,8 @@
         OutputType.dumpInfo)
       ..add(jsonBuffer.toString())
       ..close();
-    compiler.reporter.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
+    compiler.reporter
+        .reportInfoMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
       'text': "View the dumped .info.json file at "
           "https://dart-lang.github.io/dump-info-visualizer"
     });
@@ -592,7 +593,8 @@
     Sink<List<int>> sink = new BinaryOutputSinkAdapter(compiler.outputProvider
         .createBinarySink(compiler.options.outputUri.resolve(name)));
     dump_info.encode(data, sink);
-    compiler.reporter.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
+    compiler.reporter
+        .reportInfoMessage(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, {
       'text': "Use `package:dart2js_info` to parse and process the dumped "
           ".info.data file."
     });
diff --git a/pkg/compiler/lib/src/helpers/helpers.dart b/pkg/compiler/lib/src/helpers/helpers.dart
index bf34252..910485e9 100644
--- a/pkg/compiler/lib/src/helpers/helpers.dart
+++ b/pkg/compiler/lib/src/helpers/helpers.dart
@@ -90,8 +90,8 @@
 
 /// Implementation of [reportHere]
 _reportHere(DiagnosticReporter reporter, Spannable node, String debugMessage) {
-  reporter
-      .reportInfo(node, MessageKind.GENERIC, {'text': 'HERE: $debugMessage'});
+  reporter.reportInfoMessage(
+      node, MessageKind.GENERIC, {'text': 'HERE: $debugMessage'});
 }
 
 /// Set of tracked objects used by [track] and [ifTracked].
diff --git a/pkg/compiler/lib/src/kernel/front_end_adapter.dart b/pkg/compiler/lib/src/kernel/front_end_adapter.dart
index 571f11f..25796fa 100644
--- a/pkg/compiler/lib/src/kernel/front_end_adapter.dart
+++ b/pkg/compiler/lib/src/kernel/front_end_adapter.dart
@@ -116,6 +116,9 @@
     case fe.Severity.warning:
       reporter.reportWarning(mainMessage, infos);
       break;
+    case fe.Severity.info:
+      reporter.reportInfo(mainMessage, infos);
+      break;
     default:
       throw new UnimplementedError('unhandled severity ${message.severity}');
   }
diff --git a/pkg/compiler/lib/src/kernel/loader.dart b/pkg/compiler/lib/src/kernel/loader.dart
index d40a061..a44d5c5 100644
--- a/pkg/compiler/lib/src/kernel/loader.dart
+++ b/pkg/compiler/lib/src/kernel/loader.dart
@@ -170,7 +170,9 @@
             explicitExperimentalFlags: _options.explicitExperimentalFlags,
             nnbdMode: _options.useLegacySubtyping
                 ? fe.NnbdMode.Weak
-                : fe.NnbdMode.Strong);
+                : fe.NnbdMode.Strong,
+            invocationModes:
+                fe.InvocationMode.parseArguments(_options.cfeInvocationModes));
         component = await fe.compile(initializedCompilerState, verbose,
             fileSystem, onDiagnostic, resolvedUri);
         if (component == null) return null;
diff --git a/pkg/compiler/lib/src/options.dart b/pkg/compiler/lib/src/options.dart
index 6e86321..2e6ffe7 100644
--- a/pkg/compiler/lib/src/options.dart
+++ b/pkg/compiler/lib/src/options.dart
@@ -424,6 +424,17 @@
   /// deserialize when using [readCodegenUri].
   int codegenShards;
 
+  /// Arguments passed to the front end about how it is invoked.
+  ///
+  /// This is used to selectively emit certain messages depending on how the
+  /// CFE is invoked. For instance to emit a message about the null safety
+  /// compilation mode when compiling an executable.
+  ///
+  /// See `InvocationMode` in
+  /// `pkg/front_end/lib/src/api_prototype/compiler_options.dart` for all
+  /// possible options.
+  String cfeInvocationModes = '';
+
   // -------------------------------------------------
   // Options for deprecated features
   // -------------------------------------------------
@@ -535,7 +546,9 @@
       .._soundNullSafety = _hasOption(options, Flags.soundNullSafety)
       .._noSoundNullSafety = _hasOption(options, Flags.noSoundNullSafety)
       .._mergeFragmentsThreshold =
-          _extractIntOption(options, '${Flags.mergeFragmentsThreshold}=');
+          _extractIntOption(options, '${Flags.mergeFragmentsThreshold}=')
+      ..cfeInvocationModes =
+          _extractStringOption(options, '${Flags.cfeInvocationModes}=', '');
   }
 
   void validate() {
diff --git a/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart b/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
index 63d8596..0058660 100644
--- a/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
+++ b/pkg/compiler/test/end_to_end/diagnostic_reporter_helper.dart
@@ -48,9 +48,15 @@
   }
 
   @override
-  void reportInfo(Spannable node, MessageKind errorCode,
+  void reportInfo(DiagnosticMessage message,
+      [List<DiagnosticMessage> infos = const <DiagnosticMessage>[]]) {
+    reporter.reportInfo(message, infos);
+  }
+
+  @override
+  void reportInfoMessage(Spannable node, MessageKind errorCode,
       [Map<String, String> arguments = const {}]) {
-    reporter.reportInfo(node, errorCode, arguments);
+    reporter.reportInfoMessage(node, errorCode, arguments);
   }
 
   @override
diff --git a/pkg/dart2native/lib/dart2native.dart b/pkg/dart2native/lib/dart2native.dart
index 0139b24..a6d1deb 100644
--- a/pkg/dart2native/lib/dart2native.dart
+++ b/pkg/dart2native/lib/dart2native.dart
@@ -37,8 +37,14 @@
   return Process.run('chmod', ['+x', outputFile]);
 }
 
-Future generateAotKernel(String dart, String genKernel, String platformDill,
-    String sourceFile, String kernelFile, String packages, List<String> defines,
+Future<ProcessResult> generateAotKernel(
+    String dart,
+    String genKernel,
+    String platformDill,
+    String sourceFile,
+    String kernelFile,
+    String packages,
+    List<String> defines,
     {String enableExperiment = '',
     List<String> extraGenKernelOptions = const []}) {
   return Process.run(dart, [
diff --git a/pkg/dart2native/lib/generate.dart b/pkg/dart2native/lib/generate.dart
index eb70dcf..3579ec8 100644
--- a/pkg/dart2native/lib/generate.dart
+++ b/pkg/dart2native/lib/generate.dart
@@ -57,13 +57,24 @@
     final String kernelFile = path.join(tempDir.path, 'kernel.dill');
     final kernelResult = await generateAotKernel(Platform.executable, genKernel,
         productPlatformDill, sourcePath, kernelFile, packages, defines,
-        enableExperiment: enableExperiment);
+        enableExperiment: enableExperiment,
+        extraGenKernelOptions: ['--invocation-modes=compile']);
     if (kernelResult.exitCode != 0) {
-      stderr.writeln(kernelResult.stdout);
-      stderr.writeln(kernelResult.stderr);
+      // We pipe both stdout and stderr to stderr because the CFE doesn't print
+      // errors to stderr. This unfortunately does emit info-only output in
+      // stderr, though.
+      stderr.write(kernelResult.stdout);
+      stderr.write(kernelResult.stderr);
       await stderr.flush();
       throw 'Generating AOT kernel dill failed!';
     }
+    // Pipe info and warnings from the CFE to stdout since the compilation
+    // succeeded. Stderr should be empty but we pipe it to stderr for
+    // completeness.
+    stdout.write(kernelResult.stdout);
+    await stdout.flush();
+    stderr.write(kernelResult.stderr);
+    await stderr.flush();
 
     if (verbose) {
       print('Generating AOT snapshot.');
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index 6edd82b..29c5b3e 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -98,6 +98,7 @@
           '--libraries-spec=$librariesPath',
           if (argResults.enabledExperiments.isNotEmpty)
             "--enable-experiment=${argResults.enabledExperiments.join(',')}",
+          '--cfe-invocation-modes=compile',
           ...argResults.arguments,
         ],
         packageConfigOverride: null);
diff --git a/pkg/dartdev/test/commands/compile_test.dart b/pkg/dartdev/test/commands/compile_test.dart
index f2f5f31..0921ddd 100644
--- a/pkg/dartdev/test/commands/compile_test.dart
+++ b/pkg/dartdev/test/commands/compile_test.dart
@@ -15,6 +15,10 @@
   group('compile', defineCompileTests, timeout: longTimeout);
 }
 
+const String soundNullSafetyMessage = 'Info: Compiling with sound null safety';
+const String unsoundNullSafetyMessage =
+    'Info: Compiling with unsound null safety';
+
 void defineCompileTests() {
   // *** NOTE ***: These tests *must* be run with the `--use-sdk` option
   // as they depend on a fully built SDK to resolve various snapshot files
@@ -201,4 +205,155 @@
     expect(File(outFile).existsSync(), true,
         reason: 'File not found: $outFile');
   });
+
+  test('Compile exe with error', () {
+    final p = project(mainSrc: '''
+void main() {
+  int? i;
+  i.isEven;
+}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, isEmpty);
+    expect(result.stderr, contains('Error: '));
+    // The CFE doesn't print to stderr, so all output is piped to stderr, even
+    // including info-only output:
+    expect(result.stderr, contains(soundNullSafetyMessage));
+    expect(result.exitCode, compileErrorExitCode);
+    expect(File(outFile).existsSync(), false,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with warning', () {
+    final p = project(mainSrc: '''
+void main() {
+  int i = 0;
+  i?.isEven;
+}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains('Warning: '));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with sound null safety', () {
+    final p = project(mainSrc: '''void main() {}''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(soundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile exe with unsound null safety', () {
+    final p = project(mainSrc: '''
+// @dart=2.9
+void main() {}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'exe',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(unsoundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile JS with sound null safety', () {
+    final p = project(mainSrc: '''void main() {}''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myjs'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'js',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(soundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
+
+  test('Compile JS with unsound null safety', () {
+    final p = project(mainSrc: '''
+// @dart=2.9
+void main() {}
+''');
+    final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+    final outFile = path.canonicalize(path.join(p.dirPath, 'myjs'));
+
+    var result = p.runSync(
+      [
+        'compile',
+        'js',
+        '-o',
+        outFile,
+        inFile,
+      ],
+    );
+
+    expect(result.stdout, contains(unsoundNullSafetyMessage));
+    expect(result.stderr, isEmpty);
+    expect(result.exitCode, 0);
+    expect(File(outFile).existsSync(), true,
+        reason: 'File not found: $outFile');
+  });
 }
diff --git a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
index d90969a..74b352d 100644
--- a/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
+++ b/pkg/dev_compiler/lib/src/kernel/expression_compiler_worker.dart
@@ -159,6 +159,7 @@
 
   static List<String> errors = <String>[];
   static List<String> warnings = <String>[];
+  static List<String> infos = <String>[];
 
   /// Create the worker and load the sdk outlines.
   static Future<ExpressionCompilerWorker> create({
@@ -188,7 +189,7 @@
       ..omitPlatform = true
       ..environmentDefines = environmentDefines
       ..explicitExperimentalFlags = explicitExperimentalFlags
-      ..onDiagnostic = _onDiagnosticHandler(errors, warnings)
+      ..onDiagnostic = _onDiagnosticHandler(errors, warnings, infos)
       ..nnbdMode = soundNullSafety ? NnbdMode.Strong : NnbdMode.Weak
       ..verbose = verbose;
     requestStream ??= stdin
@@ -275,6 +276,7 @@
 
     errors.clear();
     warnings.clear();
+    infos.clear();
 
     var incrementalCompiler = IncrementalCompiler.forExpressionCompilationOnly(
         CompilerContext(_processedOptions), component, /*resetTicker*/ false);
@@ -288,6 +290,7 @@
       return {
         'errors': errors,
         'warnings': warnings,
+        'infos': infos,
         'compiledProcedure': null,
         'succeeded': errors.isEmpty,
       };
@@ -332,6 +335,7 @@
     return {
       'errors': errors,
       'warnings': warnings,
+      'infos': infos,
       'compiledProcedure': compiledProcedure,
       'succeeded': errors.isEmpty,
     };
@@ -461,7 +465,7 @@
 }
 
 void Function(DiagnosticMessage) _onDiagnosticHandler(
-        List<String> errors, List<String> warnings) =>
+        List<String> errors, List<String> warnings, List<String> infos) =>
     (DiagnosticMessage message) {
       switch (message.severity) {
         case Severity.error:
@@ -471,6 +475,9 @@
         case Severity.warning:
           warnings.add(message.plainTextFormatted.join('\n'));
           break;
+        case Severity.info:
+          infos.add(message.plainTextFormatted.join('\n'));
+          break;
         case Severity.context:
         case Severity.ignored:
           throw 'Unexpected severity: ${message.severity}';
diff --git a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
index a8af7506..501fe6d 100644
--- a/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
+++ b/pkg/dev_compiler/test/expression_compiler/expression_compiler_worker_test.dart
@@ -255,6 +255,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return other;'),
             })
           ]));
@@ -288,6 +289,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return formal;'),
             })
           ]));
@@ -320,6 +322,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return count;'),
             })
           ]));
@@ -354,6 +357,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure': contains('return ret;'),
             })
           ]));
@@ -387,6 +391,7 @@
               'succeeded': true,
               'errors': isEmpty,
               'warnings': isEmpty,
+              'infos': isEmpty,
               'compiledProcedure':
                   contains('return new test_library.B.new().c().getNumber()'),
             })
diff --git a/pkg/front_end/lib/src/api_prototype/compiler_options.dart b/pkg/front_end/lib/src/api_prototype/compiler_options.dart
index 4b517ca..7639c95 100644
--- a/pkg/front_end/lib/src/api_prototype/compiler_options.dart
+++ b/pkg/front_end/lib/src/api_prototype/compiler_options.dart
@@ -256,6 +256,13 @@
   /// compiling the platform dill.
   bool emitDeps = true;
 
+  /// Set of invocation modes the describe how the compilation is performed.
+  ///
+  /// This used to selectively emit certain messages depending on how the
+  /// CFE is invoked. For instance to emit a message about the null safety
+  /// compilation mode when the modes includes [InvocationMode.compile].
+  Set<InvocationMode> invocationModes = {};
+
   bool isExperimentEnabledByDefault(ExperimentalFlag flag) {
     return flags.isExperimentEnabled(flag,
         defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting);
@@ -344,6 +351,7 @@
     if (nnbdMode != other.nnbdMode) return false;
     if (currentSdkVersion != other.currentSdkVersion) return false;
     if (emitDeps != other.emitDeps) return false;
+    if (!equalSets(invocationModes, other.invocationModes)) return false;
 
     return true;
   }
@@ -431,3 +439,44 @@
   }
   return flags;
 }
+
+class InvocationMode {
+  /// This mode is used for when the CFE is invoked in order to compile an
+  /// executable.
+  ///
+  /// If used, a message about the null safety compilation mode will be emitted.
+  static const InvocationMode compile = const InvocationMode('compile');
+
+  final String name;
+
+  const InvocationMode(this.name);
+
+  /// Returns the set of information modes from a comma-separated list of
+  /// invocation mode names.
+  static Set<InvocationMode> parseArguments(String arg) {
+    Set<InvocationMode> result = {};
+    for (String name in arg.split(',')) {
+      if (name.isNotEmpty) {
+        InvocationMode mode = fromName(name);
+        if (mode == null) {
+          throw new UnsupportedError("Unknown invocation mode '$name'.");
+        } else {
+          result.add(mode);
+        }
+      }
+    }
+    return result;
+  }
+
+  /// Returns the [InvocationMode] with the given [name].
+  static InvocationMode fromName(String name) {
+    for (InvocationMode invocationMode in values) {
+      if (name == invocationMode.name) {
+        return invocationMode;
+      }
+    }
+    return null;
+  }
+
+  static const List<InvocationMode> values = const [compile];
+}
diff --git a/pkg/front_end/lib/src/api_unstable/dart2js.dart b/pkg/front_end/lib/src/api_unstable/dart2js.dart
index d7fba2e..9a102ac 100644
--- a/pkg/front_end/lib/src/api_unstable/dart2js.dart
+++ b/pkg/front_end/lib/src/api_unstable/dart2js.dart
@@ -18,7 +18,8 @@
 
 import 'package:kernel/target/targets.dart' show Target;
 
-import '../api_prototype/compiler_options.dart' show CompilerOptions;
+import '../api_prototype/compiler_options.dart'
+    show CompilerOptions, InvocationMode;
 
 import '../api_prototype/experimental_flags.dart' show ExperimentalFlag;
 
@@ -40,7 +41,7 @@
 
 import 'compiler_state.dart' show InitializedCompilerState;
 
-import 'util.dart' show equalLists, equalMaps;
+import 'util.dart' show equalLists, equalMaps, equalSets;
 
 export 'package:_fe_analyzer_shared/src/messages/codes.dart'
     show LocatedMessage;
@@ -97,7 +98,11 @@
     show relativizeUri;
 
 export '../api_prototype/compiler_options.dart'
-    show CompilerOptions, parseExperimentalFlags, parseExperimentalArguments;
+    show
+        CompilerOptions,
+        InvocationMode,
+        parseExperimentalFlags,
+        parseExperimentalArguments;
 
 export '../api_prototype/experimental_flags.dart'
     show defaultExperimentalFlags, ExperimentalFlag, isExperimentEnabled;
@@ -138,7 +143,8 @@
     Uri packagesFileUri,
     {Map<ExperimentalFlag, bool> explicitExperimentalFlags,
     bool verify: false,
-    NnbdMode nnbdMode}) {
+    NnbdMode nnbdMode,
+    Set<InvocationMode> invocationModes: const <InvocationMode>{}}) {
   additionalDills.sort((a, b) => a.toString().compareTo(b.toString()));
 
   // We don't check `target` because it doesn't support '==' and each
@@ -151,7 +157,8 @@
       equalMaps(oldState.options.explicitExperimentalFlags,
           explicitExperimentalFlags) &&
       oldState.options.verify == verify &&
-      oldState.options.nnbdMode == nnbdMode) {
+      oldState.options.nnbdMode == nnbdMode &&
+      equalSets(oldState.options.invocationModes, invocationModes)) {
     return oldState;
   }
 
@@ -161,7 +168,8 @@
     ..librariesSpecificationUri = librariesSpecificationUri
     ..packagesFileUri = packagesFileUri
     ..explicitExperimentalFlags = explicitExperimentalFlags
-    ..verify = verify;
+    ..verify = verify
+    ..invocationModes = invocationModes;
   if (nnbdMode != null) options.nnbdMode = nnbdMode;
 
   ProcessedOptions processedOpts = new ProcessedOptions(options: options);
diff --git a/pkg/front_end/lib/src/api_unstable/vm.dart b/pkg/front_end/lib/src/api_unstable/vm.dart
index 6e45bfc..65963f9 100644
--- a/pkg/front_end/lib/src/api_unstable/vm.dart
+++ b/pkg/front_end/lib/src/api_unstable/vm.dart
@@ -8,7 +8,11 @@
 export 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity;
 
 export '../api_prototype/compiler_options.dart'
-    show CompilerOptions, parseExperimentalArguments, parseExperimentalFlags;
+    show
+        CompilerOptions,
+        InvocationMode,
+        parseExperimentalArguments,
+        parseExperimentalFlags;
 
 export '../api_prototype/experimental_flags.dart'
     show defaultExperimentalFlags, ExperimentalFlag;
diff --git a/pkg/front_end/lib/src/base/command_line_options.dart b/pkg/front_end/lib/src/base/command_line_options.dart
index a87824f..862187a 100644
--- a/pkg/front_end/lib/src/base/command_line_options.dart
+++ b/pkg/front_end/lib/src/base/command_line_options.dart
@@ -41,4 +41,6 @@
   static const String verify = "--verify";
   static const String verifySkipPlatform = "--verify-skip-platform";
   static const String warnOnReachabilityCheck = "--warn-on-reachability-check";
+
+  static const String invocationModes = "--invocation-modes";
 }
diff --git a/pkg/front_end/lib/src/base/processed_options.dart b/pkg/front_end/lib/src/base/processed_options.dart
index 97f49f0..ec366d0 100644
--- a/pkg/front_end/lib/src/base/processed_options.dart
+++ b/pkg/front_end/lib/src/base/processed_options.dart
@@ -24,7 +24,7 @@
 import 'package:package_config/package_config.dart';
 
 import '../api_prototype/compiler_options.dart'
-    show CompilerOptions, DiagnosticMessage;
+    show CompilerOptions, InvocationMode, DiagnosticMessage;
 
 import '../api_prototype/experimental_flags.dart' as flags;
 
@@ -45,6 +45,8 @@
         Message,
         messageCantInferPackagesFromManyInputs,
         messageCantInferPackagesFromPackageUri,
+        messageCompilingWithSoundNullSafety,
+        messageCompilingWithUnsoundNullSafety,
         messageInternalProblemProvidedBothCompileSdkAndSdkSummary,
         messageMissingInput,
         noLength,
@@ -267,6 +269,25 @@
     report(message.withoutLocation(), severity);
   }
 
+  /// If `CompilerOptions.invocationModes` contains `InvocationMode.compile`, an
+  /// info message about the null safety compilation mode is emitted.
+  void reportNullSafetyCompilationModeInfo() {
+    if (_raw.invocationModes.contains(InvocationMode.compile)) {
+      switch (nnbdMode) {
+        case NnbdMode.Weak:
+          reportWithoutLocation(messageCompilingWithUnsoundNullSafety,
+              messageCompilingWithUnsoundNullSafety.severity);
+          break;
+        case NnbdMode.Strong:
+          reportWithoutLocation(messageCompilingWithSoundNullSafety,
+              messageCompilingWithSoundNullSafety.severity);
+          break;
+        case NnbdMode.Agnostic:
+          break;
+      }
+    }
+  }
+
   /// Runs various validations checks on the input options. For instance,
   /// if an option is a path to a file, it checks that the file exists.
   Future<bool> validateOptions({bool errorOnMissingInput: true}) async {
diff --git a/pkg/front_end/lib/src/fasta/command_line_reporting.dart b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
index 1247aea..d8f6c95 100644
--- a/pkg/front_end/lib/src/fasta/command_line_reporting.dart
+++ b/pkg/front_end/lib/src/fasta/command_line_reporting.dart
@@ -19,7 +19,7 @@
     show $CARET, $SPACE, $TAB;
 
 import 'package:_fe_analyzer_shared/src/util/colors.dart'
-    show enableColors, green, magenta, red;
+    show enableColors, green, magenta, red, yellow;
 
 import 'package:_fe_analyzer_shared/src/util/relativize.dart'
     show isWindows, relativizeUri;
@@ -73,6 +73,11 @@
           messageText = green(messageText);
           break;
 
+        case Severity.info:
+          messageText = yellow(messageText);
+          break;
+
+        case Severity.ignored:
         default:
           return unhandled("$severity", "format", -1, null);
       }
@@ -145,14 +150,15 @@
     case Severity.error:
     case Severity.internalProblem:
     case Severity.context:
+    case Severity.info:
       return false;
 
     case Severity.warning:
       return hideWarnings;
-
-    default:
-      return unhandled("$severity", "isHidden", -1, null);
+    case Severity.ignored:
+      return true;
   }
+  return unhandled("$severity", "isHidden", -1, null);
 }
 
 /// Are problems of [severity] fatal? That is, should the compiler terminate
@@ -168,12 +174,12 @@
     case Severity.warning:
       return CompilerContext.current.options.throwOnWarningsForDebugging;
 
+    case Severity.info:
+    case Severity.ignored:
     case Severity.context:
       return false;
-
-    default:
-      return unhandled("$severity", "shouldThrowOn", -1, null);
   }
+  return unhandled("$severity", "shouldThrowOn", -1, null);
 }
 
 bool isCompileTimeError(Severity severity) {
@@ -184,6 +190,7 @@
 
     case Severity.warning:
     case Severity.context:
+    case Severity.info:
       return false;
 
     case Severity.ignored:
diff --git a/pkg/front_end/lib/src/kernel_generator_impl.dart b/pkg/front_end/lib/src/kernel_generator_impl.dart
index ae668cb..d4ec55d 100644
--- a/pkg/front_end/lib/src/kernel_generator_impl.dart
+++ b/pkg/front_end/lib/src/kernel_generator_impl.dart
@@ -65,6 +65,7 @@
     bool retainDataForTesting: false,
     bool includeHierarchyAndCoreTypes: false}) async {
   ProcessedOptions options = CompilerContext.current.options;
+  options.reportNullSafetyCompilationModeInfo();
   FileSystem fs = options.fileSystem;
 
   Loader sourceLoader;
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index afaa87e..794f307 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -69,6 +69,8 @@
 CantUseSuperBoundedTypeForInstanceCreation/example: Fail
 ClassInNullAwareReceiver/analyzerCode: Fail
 ColonInPlaceOfIn/example: Fail
+CompilingWithSoundNullSafety/analyzerCode: Fail
+CompilingWithUnsoundNullSafety/analyzerCode: Fail
 ConflictingModifiers/part_wrapped_script1: Fail
 ConflictingModifiers/script1: Fail
 ConflictsWithConstructor/example: Fail
@@ -172,6 +174,8 @@
 DirectiveAfterDeclaration/part_wrapped_script2: Fail
 DirectiveAfterDeclaration/script1: Fail
 DirectiveAfterDeclaration/script2: Fail
+DuplicateDeferred/example: Fail
+DuplicatePrefix/example: Fail
 DuplicatedDeclarationUse/analyzerCode: Fail # No corresponding analyzer code.
 DuplicatedDeclarationUse/part_wrapped_script1: Fail
 DuplicatedDeclarationUse/part_wrapped_script2: Fail
@@ -179,7 +183,6 @@
 DuplicatedDeclarationUse/script2: Fail # Wrong error.
 DuplicatedDefinition/analyzerCode: Fail
 DuplicatedDefinition/example: Fail
-DuplicateDeferred/example: Fail
 DuplicatedExport/part_wrapped_script: Fail # Exporting file in the (now) part.
 DuplicatedExportInType/analyzerCode: Fail
 DuplicatedExportInType/example: Fail
@@ -191,7 +194,6 @@
 DuplicatedName/example: Fail
 DuplicatedNamedArgument/example: Fail
 DuplicatedParameterName/example: Fail
-DuplicatePrefix/example: Fail
 Encoding/analyzerCode: Fail
 EnumConstantSameNameAsEnclosing/example: Fail
 EnumInstantiation/example: Fail
@@ -271,7 +273,6 @@
 ExternalFactoryWithBody/script1: Fail
 ExternalFieldConstructorInitializer/analyzerCode: Fail
 ExternalFieldInitializer/analyzerCode: Fail
-ExtraneousModifier/part_wrapped_script1: Fail
 ExtraneousModifier/part_wrapped_script10: Fail
 ExtraneousModifier/part_wrapped_script11: Fail
 ExtraneousModifier/part_wrapped_script12: Fail
@@ -280,8 +281,9 @@
 ExtraneousModifier/part_wrapped_script17: Fail
 ExtraneousModifier/part_wrapped_script18: Fail
 ExtraneousModifier/part_wrapped_script19: Fail
-ExtraneousModifier/part_wrapped_script2: Fail
+ExtraneousModifier/part_wrapped_script1: Fail
 ExtraneousModifier/part_wrapped_script20: Fail
+ExtraneousModifier/part_wrapped_script2: Fail
 ExtraneousModifier/part_wrapped_script3: Fail
 ExtraneousModifier/part_wrapped_script4: Fail
 ExtraneousModifier/part_wrapped_script5: Fail
@@ -289,7 +291,6 @@
 ExtraneousModifier/part_wrapped_script7: Fail
 ExtraneousModifier/part_wrapped_script8: Fail
 ExtraneousModifier/part_wrapped_script9: Fail
-ExtraneousModifier/script1: Fail
 ExtraneousModifier/script10: Fail
 ExtraneousModifier/script11: Fail
 ExtraneousModifier/script12: Fail
@@ -298,8 +299,9 @@
 ExtraneousModifier/script17: Fail
 ExtraneousModifier/script18: Fail
 ExtraneousModifier/script19: Fail
-ExtraneousModifier/script2: Fail
+ExtraneousModifier/script1: Fail
 ExtraneousModifier/script20: Fail
+ExtraneousModifier/script2: Fail
 ExtraneousModifier/script3: Fail
 ExtraneousModifier/script4: Fail
 ExtraneousModifier/script5: Fail
@@ -396,10 +398,10 @@
 IncompatibleRedirecteeFunctionType/part_wrapped_script6: Fail
 IncompatibleRedirecteeFunctionType/script6: Fail # Triggers multiple errors.
 IncompatibleRedirecteeFunctionTypeWarning/example: Fail
-IncorrectTypeArgumentInferredWarning/example: Fail
 IncorrectTypeArgumentInReturnTypeWarning/example: Fail
 IncorrectTypeArgumentInSupertypeInferredWarning/example: Fail
 IncorrectTypeArgumentInSupertypeWarning/example: Fail
+IncorrectTypeArgumentInferredWarning/example: Fail
 IncorrectTypeArgumentQualifiedInferredWarning/example: Fail
 IncorrectTypeArgumentQualifiedWarning/example: Fail
 IncorrectTypeArgumentWarning/example: Fail
@@ -564,6 +566,9 @@
 NeverValueWarning/analyzerCode: Fail
 NeverValueWarning/example: Fail
 NoFormals/example: Fail
+NoSuchNamedParameter/example: Fail
+NoUnnamedConstructorInObject/analyzerCode: Fail
+NoUnnamedConstructorInObject/example: Fail
 NonAgnosticConstant/analyzerCode: Fail
 NonAgnosticConstant/example: Fail
 NonAsciiIdentifier/expression: Fail
@@ -571,6 +576,7 @@
 NonConstConstructor/example: Fail
 NonConstFactory/example: Fail
 NonInstanceTypeVariableUse/example: Fail
+NonNullAwareSpreadIsNull/analyzerCode: Fail # There's no analyzer code for that error yet.
 NonNullableInNullAware/analyzerCode: Fail
 NonNullableInNullAware/example: Fail
 NonNullableNotAssignedError/analyzerCode: Fail
@@ -580,17 +586,14 @@
 NonNullableOptOutExplicit/example: Fail
 NonNullableOptOutImplicit/analyzerCode: Fail
 NonNullableOptOutImplicit/example: Fail
-NonNullAwareSpreadIsNull/analyzerCode: Fail # There's no analyzer code for that error yet.
 NonPartOfDirectiveInPart/part_wrapped_script1: Fail
 NonPartOfDirectiveInPart/script1: Fail
-NoSuchNamedParameter/example: Fail
 NotAConstantExpression/example: Fail
-NotAnLvalue/example: Fail
 NotAType/example: Fail
+NotAnLvalue/example: Fail
 NotBinaryOperator/analyzerCode: Fail
 NotConstantExpression/example: Fail
-NoUnnamedConstructorInObject/analyzerCode: Fail
-NoUnnamedConstructorInObject/example: Fail
+NullAwareCascadeOutOfOrder/example: Fail
 NullableExpressionCallError/analyzerCode: Fail
 NullableExpressionCallError/example: Fail
 NullableExpressionCallWarning/analyzerCode: Fail
@@ -625,7 +628,6 @@
 NullableTearoffError/example: Fail
 NullableTearoffWarning/analyzerCode: Fail
 NullableTearoffWarning/example: Fail
-NullAwareCascadeOutOfOrder/example: Fail
 OperatorMinusParameterMismatch/example: Fail
 OperatorParameterMismatch0/analyzerCode: Fail
 OperatorParameterMismatch0/example: Fail
@@ -723,13 +725,13 @@
 StrongWithWeakDillLibrary/spelling: Fail
 SuperAsExpression/example: Fail
 SuperAsIdentifier/example: Fail
+SuperNullAware/example: Fail
 SuperclassHasNoDefaultConstructor/example: Fail
 SuperclassHasNoGetter/example: Fail
 SuperclassHasNoMethod/example: Fail
 SuperclassHasNoSetter/example: Fail
 SuperclassMethodArgumentMismatch/analyzerCode: Fail
 SuperclassMethodArgumentMismatch/example: Fail
-SuperNullAware/example: Fail
 SupertypeIsFunction/analyzerCode: Fail
 SupertypeIsFunction/example: Fail
 SupertypeIsIllegal/example: Fail
@@ -754,14 +756,14 @@
 TypeArgumentMismatch/example: Fail
 TypeArgumentsOnTypeVariable/part_wrapped_script1: Fail
 TypeArgumentsOnTypeVariable/script1: Fail
+TypeNotFound/example: Fail
+TypeVariableDuplicatedName/example: Fail
+TypeVariableSameNameAsEnclosing/example: Fail
 TypedefNotFunction/example: Fail
 TypedefNotType/example: Fail # Feature not yet enabled by default.
 TypedefNullableType/analyzerCode: Fail
 TypedefTypeVariableNotConstructor/analyzerCode: Fail # Feature not yet enabled by default.
 TypedefTypeVariableNotConstructor/example: Fail # Feature not yet enabled by default.
-TypeNotFound/example: Fail
-TypeVariableDuplicatedName/example: Fail
-TypeVariableSameNameAsEnclosing/example: Fail
 UnexpectedToken/part_wrapped_script1: Fail
 UnexpectedToken/script1: Fail
 UnmatchedToken/part_wrapped_script1: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 390424e..9bb52f9 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -4936,3 +4936,18 @@
   analyzerCode: UNEXPECTED_TOKEN
   script:
     - "late int x;"
+
+CompilingWithSoundNullSafety:
+  template: "Compiling with sound null safety"
+  configuration: nnbd-strong,compile
+  severity: INFO
+  script: |
+    main() {}
+
+CompilingWithUnsoundNullSafety:
+  template: "Compiling with unsound null safety"
+  configuration: nnbd-weak,compile
+  severity: INFO
+  script: |
+    // @dart=2.9
+    main() {}
diff --git a/pkg/front_end/test/fasta/messages_suite.dart b/pkg/front_end/test/fasta/messages_suite.dart
index 9a370f4..8e04b81 100644
--- a/pkg/front_end/test/fasta/messages_suite.dart
+++ b/pkg/front_end/test/fasta/messages_suite.dart
@@ -26,7 +26,7 @@
 import "package:yaml/yaml.dart" show YamlList, YamlMap, YamlNode, loadYamlNode;
 
 import 'package:front_end/src/api_prototype/compiler_options.dart'
-    show CompilerOptions;
+    show CompilerOptions, InvocationMode;
 
 import 'package:front_end/src/api_prototype/experimental_flags.dart'
     show ExperimentalFlag;
@@ -70,8 +70,9 @@
 
 class Configuration {
   final NnbdMode nnbdMode;
+  final Set<InvocationMode> invocationModes;
 
-  const Configuration(this.nnbdMode);
+  const Configuration(this.nnbdMode, this.invocationModes);
 
   CompilerOptions apply(CompilerOptions options) {
     if (nnbdMode != null) {
@@ -80,10 +81,12 @@
     } else {
       options.explicitExperimentalFlags[ExperimentalFlag.nonNullable] = false;
     }
+    options.invocationModes = invocationModes;
     return options;
   }
 
-  static const Configuration defaultConfiguration = const Configuration(null);
+  static const Configuration defaultConfiguration =
+      const Configuration(null, const {});
 }
 
 class MessageTestSuite extends ChainContext {
@@ -345,12 +348,25 @@
             break;
 
           case "configuration":
-            if (value == "nnbd-weak") {
-              configuration = const Configuration(NnbdMode.Weak);
-            } else if (value == "nnbd-strong") {
-              configuration = const Configuration(NnbdMode.Strong);
-            } else {
-              throw new ArgumentError("Unknown configuration '$value'.");
+            if (value is String) {
+              NnbdMode nnbdMode;
+              Set<InvocationMode> invocationModes = {};
+              for (String part in value.split(',')) {
+                if (part.isEmpty) continue;
+                if (part == "nnbd-weak") {
+                  nnbdMode = NnbdMode.Weak;
+                } else if (part == "nnbd-strong") {
+                  nnbdMode = NnbdMode.Strong;
+                } else {
+                  InvocationMode invocationMode = InvocationMode.fromName(part);
+                  if (invocationMode != null) {
+                    invocationModes.add(invocationMode);
+                  } else {
+                    throw new ArgumentError("Unknown configuration '$part'.");
+                  }
+                }
+              }
+              configuration = new Configuration(nnbdMode, invocationModes);
             }
             break;
 
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index cfb9146..f719e36e 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1014,6 +1014,7 @@
 sdk's
 seconds
 sections
+selectively
 selectors
 semantically
 semver
diff --git a/pkg/front_end/tool/_fasta/command_line.dart b/pkg/front_end/tool/_fasta/command_line.dart
index ed79435..9c275d4 100644
--- a/pkg/front_end/tool/_fasta/command_line.dart
+++ b/pkg/front_end/tool/_fasta/command_line.dart
@@ -198,6 +198,7 @@
   Flags.warnOnReachabilityCheck: const BoolValue(false),
   Flags.linkDependencies: const UriListValue(),
   Flags.noDeps: const BoolValue(false),
+  Flags.invocationModes: const StringValue(),
   "-D": const DefineValue(),
   "-h": const AliasValue(Flags.help),
   "--out": const AliasValue(Flags.output),
@@ -303,6 +304,8 @@
 
   final List<Uri> linkDependencies = options[Flags.linkDependencies] ?? [];
 
+  final String invocationModes = options[Flags.invocationModes] ?? '';
+
   if (nnbdStrongMode && nnbdWeakMode) {
     return throw new CommandLineProblem.deprecated(
         "Can't specify both '${Flags.nnbdStrongMode}' and "
@@ -353,7 +356,8 @@
     ..nnbdMode = nnbdMode
     ..additionalDills = linkDependencies
     ..emitDeps = !noDeps
-    ..warnOnReachabilityCheck = warnOnReachabilityCheck;
+    ..warnOnReachabilityCheck = warnOnReachabilityCheck
+    ..invocationModes = InvocationMode.parseArguments(invocationModes);
 
   if (programName == "compile_platform") {
     if (arguments.length != 5) {
diff --git a/pkg/front_end/tool/_fasta/entry_points.dart b/pkg/front_end/tool/_fasta/entry_points.dart
index 1aa2265..b05374d 100644
--- a/pkg/front_end/tool/_fasta/entry_points.dart
+++ b/pkg/front_end/tool/_fasta/entry_points.dart
@@ -357,6 +357,7 @@
 
   Future<Uri> compile(
       {bool omitPlatform: false, bool supportAdditionalDills: true}) async {
+    c.options.reportNullSafetyCompilationModeInfo();
     KernelTarget kernelTarget =
         await buildOutline(supportAdditionalDills: supportAdditionalDills);
     Uri uri = c.options.output;
diff --git a/pkg/frontend_server/lib/frontend_server.dart b/pkg/frontend_server/lib/frontend_server.dart
index 5b8a4eb..21d82f5 100644
--- a/pkg/frontend_server/lib/frontend_server.dart
+++ b/pkg/frontend_server/lib/frontend_server.dart
@@ -359,6 +359,7 @@
         errors.addAll(message.plainTextFormatted);
         break;
       case Severity.warning:
+      case Severity.info:
         printMessage = true;
         break;
       case Severity.context:
diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart
index 3a7d5f4..a0fc1de 100644
--- a/pkg/vm/bin/kernel_service.dart
+++ b/pkg/vm/bin/kernel_service.dart
@@ -126,6 +126,7 @@
           errors.addAll(message.plainTextFormatted);
           break;
         case Severity.warning:
+        case Severity.info:
           printMessage = !suppressWarnings;
           break;
         case Severity.context:
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index bd60735..a1fc45e 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -23,6 +23,7 @@
         CompilerContext,
         CompilerOptions,
         CompilerResult,
+        InvocationMode,
         DiagnosticMessage,
         DiagnosticMessageHandler,
         ExperimentalFlag,
@@ -127,6 +128,9 @@
   args.addFlag('track-widget-creation',
       help: 'Run a kernel transformer to track creation locations for widgets.',
       defaultsTo: false);
+  args.addOption('invocation-modes',
+      help: 'Provides information to the front end about how it is invoked.',
+      defaultsTo: '');
 }
 
 /// Create ArgParser and populate it with options consumed by [runCompiler].
@@ -226,7 +230,9 @@
     ..onDiagnostic = (DiagnosticMessage m) {
       errorDetector(m);
     }
-    ..embedSourceText = embedSources;
+    ..embedSourceText = embedSources
+    ..invocationModes =
+        InvocationMode.parseArguments(options['invocation-modes']);
 
   if (nullSafety == null &&
       compilerOptions.isExperimentEnabled(ExperimentalFlag.nonNullable)) {
@@ -496,7 +502,18 @@
 
   void printCompilationMessages() {
     final sortedUris = compilationMessages.keys.toList()
-      ..sort((a, b) => '$a'.compareTo('$b'));
+      ..sort((a, b) {
+        // Sort messages without a corresponding uri before the location based
+        // messages, since these related to the whole compilation.
+        if (a != null && b != null) {
+          return '$a'.compareTo('$b');
+        } else if (a != null) {
+          return 1;
+        } else if (b != null) {
+          return -1;
+        }
+        return 0;
+      });
     for (final Uri sourceUri in sortedUris) {
       for (final DiagnosticMessage message in compilationMessages[sourceUri]) {
         printDiagnosticMessage(message, print);