Revert "[flutter_tools] Remove --no-sound-null-safety #3" (#123969)

Revert "[flutter_tools] Remove --no-sound-null-safety #3"
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index 68dae76..f188ae7 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -573,7 +573,9 @@
   // Only verify caching with flutter gallery.
   final bool verifyCaching = exampleDirectory.path.contains('flutter_gallery');
   final String examplePath = path.relative(exampleDirectory.path, from: Directory.current.path);
+  final bool hasNullSafety = File(path.join(examplePath, 'null_safety')).existsSync();
   final List<String> additionalArgs = <String>[
+    if (hasNullSafety) '--no-sound-null-safety',
     if (mainFile != null) path.relative(mainFile.path, from: exampleDirectory.absolute.path),
   ];
   if (Directory(path.join(examplePath, 'android')).existsSync()) {
@@ -769,6 +771,8 @@
 }
 
 Future<void> _runFrameworkTests() async {
+  final List<String> soundNullSafetyOptions     = <String>['--null-assertions', '--sound-null-safety'];
+  final List<String> mixedModeNullSafetyOptions = <String>['--null-assertions', '--no-sound-null-safety'];
   final List<String> trackWidgetCreationAlternatives = <String>['--track-widget-creation', '--no-track-widget-creation'];
 
   Future<void> runWidgets() async {
@@ -776,7 +780,7 @@
     for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
       await _runFlutterTest(
         path.join(flutterRoot, 'packages', 'flutter'),
-        options: <String>[trackWidgetCreationOption],
+        options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
         tests: <String>[ path.join('test', 'widgets') + path.separator ],
       );
     }
@@ -791,13 +795,13 @@
     // Run release mode tests (see packages/flutter/test_release/README.md)
     await _runFlutterTest(
       path.join(flutterRoot, 'packages', 'flutter'),
-      options: <String>['--dart-define=dart.vm.product=true'],
+      options: <String>['--dart-define=dart.vm.product=true', ...soundNullSafetyOptions],
       tests: <String>['test_release${path.separator}'],
     );
     // Run profile mode tests (see packages/flutter/test_profile/README.md)
     await _runFlutterTest(
       path.join(flutterRoot, 'packages', 'flutter'),
-      options: <String>['--dart-define=dart.vm.product=false', '--dart-define=dart.vm.profile=true'],
+      options: <String>['--dart-define=dart.vm.product=false', '--dart-define=dart.vm.profile=true', ...soundNullSafetyOptions],
       tests: <String>['test_profile${path.separator}'],
     );
   }
@@ -813,7 +817,7 @@
     for (final String trackWidgetCreationOption in trackWidgetCreationAlternatives) {
       await _runFlutterTest(
         path.join(flutterRoot, 'packages', 'flutter'),
-        options: <String>[trackWidgetCreationOption],
+        options: <String>[trackWidgetCreationOption, ...soundNullSafetyOptions],
         tests: tests,
       );
     }
@@ -833,9 +837,9 @@
         workingDirectory: path.join(flutterRoot, 'examples', 'api'),
       );
     }
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'api'));
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'));
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'));
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'api'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), options: soundNullSafetyOptions);
   }
 
   Future<void> runTracingTests() async {
@@ -941,6 +945,7 @@
 
   Future<void> runPrivateTests() async {
     final List<String> args = <String>[
+      '--sound-null-safety',
       'run',
       'bin/test_private.dart',
     ];
@@ -984,17 +989,17 @@
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_defaults'));
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes'));
     await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'));
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')]);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')], options: soundNullSafetyOptions);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: <String>[
       '--enable-vmservice',
       // Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard.
       '--exclude-tags=web',
     ]);
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'));
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'));
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'));
-    await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'));
-    await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'));
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), options: soundNullSafetyOptions);
+    await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'non_nullable'), options: mixedModeNullSafetyOptions);
     const String httpClientWarning =
       'Warning: At least one test in this suite creates an HttpClient. When\n'
       'running a test suite that uses TestWidgetsFlutterBinding, all HTTP\n'
@@ -1229,7 +1234,7 @@
         '--dart-define=TEST_FLUTTER_ENGINE_VERSION=$engineVersion',
       ]),
     () => _runWebDebugTest('test/test.dart'),
-    () => _runWebDebugTest('lib/null_safe_main.dart'),
+    () => _runWebDebugTest('lib/null_safe_main.dart', enableNullSafety: true),
     () => _runWebDebugTest('lib/web_define_loading.dart',
       additionalArguments: <String>[
         '--dart-define=test.valueA=Example,A',
@@ -1242,8 +1247,12 @@
         '--dart-define=test.valueB=Value',
       ]
     ),
-    () => _runWebDebugTest('lib/sound_mode.dart'),
-    () => _runWebReleaseTest('lib/sound_mode.dart'),
+    () => _runWebDebugTest('lib/sound_mode.dart', additionalArguments: <String>[
+      '--sound-null-safety',
+    ]),
+    () => _runWebReleaseTest('lib/sound_mode.dart', additionalArguments: <String>[
+      '--sound-null-safety',
+    ]),
     () => _runFlutterWebTest(
       'html',
       path.join(flutterRoot, 'packages', 'integration_test'),
@@ -1302,6 +1311,7 @@
       if (driver != null) '--driver=$driver',
       '--target=$target',
       '--browser-name=chrome',
+      '--no-sound-null-safety',
       '-d',
       'web-server',
       '--$buildMode',
@@ -1343,6 +1353,7 @@
       'build',
       'web',
       '--target=$target',
+      '--no-sound-null-safety',
       '--profile',
     ],
     workingDirectory: testAppDirectory,
@@ -1568,6 +1579,7 @@
       '--driver=test_driver/transitions_perf_e2e_test.dart',
       '--target=test_driver/transitions_perf_e2e.dart',
       '--browser-name=chrome',
+      '--no-sound-null-safety',
       '-d',
       'web-server',
       '--$buildMode',
@@ -1674,6 +1686,7 @@
 ///
 /// Instead, we use `flutter run --debug` and sniff out the standard output.
 Future<void> _runWebDebugTest(String target, {
+  bool enableNullSafety = false,
   List<String> additionalArguments = const<String>[],
 }) async {
   final String testAppDirectory = path.join(flutterRoot, 'dev', 'integration_tests', 'web');
@@ -1687,6 +1700,11 @@
     <String>[
       'run',
       '--debug',
+      if (enableNullSafety)
+        ...<String>[
+          '--no-sound-null-safety',
+          '--null-assertions',
+        ],
       '-d',
       'chrome',
       '--web-run-headless',
@@ -1729,6 +1747,7 @@
       '--platform=chrome',
       '--web-renderer=$webRenderer',
       '--dart-define=DART_HHH_BOT=$_runningInDartHHHBot',
+      '--sound-null-safety',
       ...flutterTestArgs,
       ...tests,
     ],
diff --git a/packages/flutter/lib/src/services/clipboard.dart b/packages/flutter/lib/src/services/clipboard.dart
index b490e80..88ede40 100644
--- a/packages/flutter/lib/src/services/clipboard.dart
+++ b/packages/flutter/lib/src/services/clipboard.dart
@@ -56,7 +56,7 @@
     if (result == null) {
       return null;
     }
-    return ClipboardData(text: result['text']! as String);
+    return ClipboardData(text: result['text'] as String);
   }
 
   /// Returns a future that resolves to true iff the clipboard contains string
diff --git a/packages/flutter/test/foundation/isolates_test.dart b/packages/flutter/test/foundation/isolates_test.dart
index 571d9a1..912e9ba 100644
--- a/packages/flutter/test/foundation/isolates_test.dart
+++ b/packages/flutter/test/foundation/isolates_test.dart
@@ -80,7 +80,8 @@
   return compute(test5, value);
 }
 
-Future<void> expectFileSuccessfullyCompletes(String filename) async {
+Future<void> expectFileSuccessfullyCompletes(String filename,
+    [bool unsound = false]) async {
   // Run a Dart script that calls compute().
   // The Dart process will terminate only if the script exits cleanly with
   // all isolate ports closed.
@@ -92,10 +93,12 @@
   final String packageRoot = fs.path.dirname(fs.path.fromUri(platform.script));
   final String scriptPath =
       fs.path.join(packageRoot, 'test', 'foundation', filename);
+  final String nullSafetyArg =
+      unsound ? '--no-sound-null-safety' : '--sound-null-safety';
 
   // Enable asserts to also catch potentially invalid assertions.
   final ProcessResult result = await Process.run(
-      dartPath, <String>['run', '--enable-asserts', scriptPath]);
+      dartPath, <String>[nullSafetyArg, 'run', '--enable-asserts', scriptPath]);
   expect(result.exitCode, 0);
 }
 
diff --git a/packages/flutter/test_private/bin/test_private.dart b/packages/flutter/test_private/bin/test_private.dart
index 25b89f3..7005e34 100644
--- a/packages/flutter/test_private/bin/test_private.dart
+++ b/packages/flutter/test_private/bin/test_private.dart
@@ -225,7 +225,7 @@
     for (final File test in tests) {
       final String testPath = path.join(path.dirname(test.path), 'lib', path.basenameWithoutExtension(test.path));
       final ProcessRunnerResult result = await runner.runProcess(
-        <String>[flutter, 'test', testPath],
+        <String>[flutter, 'test', '--enable-experiment=non-nullable', '--no-sound-null-safety', '--null-assertions', testPath],
         failOk: true,
       );
       if (result.exitCode != 0) {
diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart
index 68904a2..596c5a6 100644
--- a/packages/flutter_tools/lib/src/globals.dart
+++ b/packages/flutter_tools/lib/src/globals.dart
@@ -43,7 +43,6 @@
 import 'project.dart';
 import 'reporting/crash_reporting.dart';
 import 'reporting/reporting.dart';
-import 'runner/flutter_command.dart';
 import 'runner/local_engine.dart';
 import 'version.dart';
 
@@ -286,7 +285,3 @@
 
 // Used to build RegExp instances which can detect the VM service message.
 final RegExp kVMServiceMessageRegExp = RegExp(r'The Dart VM service is listening on ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
-
-// The official tool no longer allows non-null safe builds. This can be
-// overridden in other clients.
-NonNullSafeBuilds get nonNullSafeBuilds => context.get<NonNullSafeBuilds>() ?? NonNullSafeBuilds.notAllowed;
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 059a3d1..95eefe2 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -822,13 +822,20 @@
 
   void addNullSafetyModeOptions({ required bool hide }) {
     argParser.addFlag(FlutterOptions.kNullSafety,
-      help: 'This flag is deprecated as only null-safe code is supported.',
+      help:
+        'Whether to override the inferred null safety mode. This allows null-safe '
+        'libraries to depend on un-migrated (non-null safe) libraries. By default, '
+        'Flutter mobile & desktop applications will attempt to run at the null safety '
+        'level of their entrypoint library (usually lib/main.dart). Flutter web '
+        'applications will default to sound null-safety, unless specifically configured.',
       defaultsTo: true,
-      hide: true,
+      hide: hide,
     );
     argParser.addFlag(FlutterOptions.kNullAssertions,
-      help: 'This flag is deprecated as only null-safe code is supported.',
-      hide: true,
+      help:
+        'Perform additional null assertions on the boundaries of migrated and '
+        'un-migrated code. This setting is not currently supported on desktop '
+        'devices.'
     );
   }
 
@@ -1480,16 +1487,6 @@
   /// rather than calling [runCommand] directly.
   @mustCallSuper
   Future<FlutterCommandResult> verifyThenRunCommand(String? commandPath) async {
-    if (argParser.options.containsKey(FlutterOptions.kNullSafety) &&
-        argResults![FlutterOptions.kNullSafety] == false &&
-        globals.nonNullSafeBuilds == NonNullSafeBuilds.notAllowed) {
-      throwToolExit('''
-Could not find an option named "no-${FlutterOptions.kNullSafety}".
-
-Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.
-''');
-    }
-
     globals.preRunValidator.validate();
 
     if (refreshWirelessDevices) {
@@ -1757,12 +1754,3 @@
 
 /// Returns true if s is either null, empty or is solely made of whitespace characters (as defined by String.trim).
 bool _isBlank(String s) => s.trim().isEmpty;
-
-/// Whether the tool should allow non-null safe builds.
-///
-/// The Dart SDK no longer supports non-null safe builds, so this value in the
-/// tool's context should always be [NonNullSafeBuilds.notAllowed].
-enum NonNullSafeBuilds {
-  allowed,
-  notAllowed,
-}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
index 631341a..c3d7524 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart
@@ -67,52 +67,6 @@
       Logger: () => BufferLogger.test(),
     });
 
-    testUsingContext('does not support --no-sound-null-safety by default', () async {
-      fileSystem.file('lib/main.dart').createSync(recursive: true);
-      fileSystem.file('pubspec.yaml').createSync();
-      fileSystem.file('.packages').createSync();
-
-      final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
-      await expectLater(
-        () => createTestCommandRunner(command).run(<String>[
-          'run',
-          '--use-application-binary=app/bar/faz',
-          '--no-sound-null-safety',
-        ]),
-        throwsA(isException.having(
-          (Exception exception) => exception.toString(),
-          'toString',
-          contains('Could not find an option named "no-sound-null-safety"'),
-        )),
-      );
-    }, overrides: <Type, Generator>{
-      FileSystem: () => fileSystem,
-      ProcessManager: () => FakeProcessManager.any(),
-      Logger: () => BufferLogger.test(),
-    });
-
-    testUsingContext('supports --no-sound-null-safety with an overridden NonNullSafeBuilds', () async {
-      fileSystem.file('lib/main.dart').createSync(recursive: true);
-      fileSystem.file('pubspec.yaml').createSync();
-      fileSystem.file('.packages').createSync();
-
-      final FakeDevice device = FakeDevice(isLocalEmulator: true, platformType: PlatformType.android);
-
-      testDeviceManager.devices = <Device>[device];
-      final TestRunCommandThatOnlyValidates command = TestRunCommandThatOnlyValidates();
-      await createTestCommandRunner(command).run(const <String>[
-        'run',
-        '--use-application-binary=app/bar/faz',
-        '--no-sound-null-safety',
-      ]);
-    }, overrides: <Type, Generator>{
-      DeviceManager: () => testDeviceManager,
-      FileSystem: () => fileSystem,
-      Logger: () => BufferLogger.test(),
-      NonNullSafeBuilds: () => NonNullSafeBuilds.allowed,
-      ProcessManager: () => FakeProcessManager.any(),
-    });
-
     testUsingContext('does not support "--use-application-binary" and "--fast-start"', () async {
       fileSystem.file('lib/main.dart').createSync(recursive: true);
       fileSystem.file('pubspec.yaml').createSync();
diff --git a/packages/flutter_tools/test/general.shard/args_test.dart b/packages/flutter_tools/test/general.shard/args_test.dart
index 6fe315e..0dc1b58 100644
--- a/packages/flutter_tools/test/general.shard/args_test.dart
+++ b/packages/flutter_tools/test/general.shard/args_test.dart
@@ -176,10 +176,7 @@
       expect(option.name, matches(_allowedArgumentNamePattern), reason: '$_header$target--${option.name}" is not a valid name for a command line argument. (Is it all lowercase? Does it use hyphens rather than underscores?)');
     }
     expect(option.name, isNot(matches(_bannedArgumentNamePattern)), reason: '$_header$target--${option.name}" is not a valid name for a command line argument. (We use "--foo-url", not "--foo-uri", for example.)');
-    // The flag --sound-null-safety is deprecated
-    if (option.name != FlutterOptions.kNullSafety && option.name != FlutterOptions.kNullAssertions) {
-      expect(option.hide, isFalse, reason: '${_header}Help for $target--${option.name}" is always hidden. $_needHelp');
-    }
+    expect(option.hide, isFalse, reason: '${_header}Help for $target--${option.name}" is always hidden. $_needHelp');
     expect(option.help, isNotNull, reason: '${_header}Help for $target--${option.name}" has null help. $_needHelp');
     expect(option.help, isNotEmpty, reason: '${_header}Help for $target--${option.name}" has empty help. $_needHelp');
     expect(option.help, isNot(matches(_bannedLeadingPatterns)), reason: '${_header}A line in the help for $target--${option.name}" starts with a lowercase letter. For stylistic consistency, all help messages must start with a capital letter.');
diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_null_unsafe_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_null_unsafe_test.dart
new file mode 100644
index 0000000..b4f6705
--- /dev/null
+++ b/packages/flutter_tools/test/integration.shard/flutter_build_null_unsafe_test.dart
@@ -0,0 +1,83 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:file/file.dart';
+import 'package:flutter_tools/src/base/io.dart';
+
+import '../src/common.dart';
+import 'test_utils.dart';
+
+void main() {
+  late Directory tempDir;
+  late Directory projectRoot;
+  late String flutterBin;
+  final List<String> targetPlatforms = <String>[
+    'apk',
+    'web',
+    if (platform.isWindows) 'windows',
+    if (platform.isMacOS) ...<String>['macos', 'ios'],
+  ];
+
+  setUpAll(() {
+    tempDir = createResolvedTempDirectorySync('build_null_unsafe_test.');
+    flutterBin = fileSystem.path.join(
+      getFlutterRoot(),
+      'bin',
+      'flutter',
+    );
+    processManager.runSync(<String>[
+      flutterBin,
+      'config',
+      '--enable-macos-desktop',
+      '--enable-windows-desktop',
+      '--enable-web',
+    ]);
+
+    processManager.runSync(<String>[
+      flutterBin,
+      ...getLocalEngineArguments(),
+      'create',
+      'hello',
+    ], workingDirectory: tempDir.path);
+
+    projectRoot = tempDir.childDirectory('hello');
+    writeFile(fileSystem.path.join(projectRoot.path, 'pubspec.yaml'), '''
+name: hello
+environment:
+  sdk: '>=2.12.0 <4.0.0'
+''');
+    writeFile(fileSystem.path.join(projectRoot.path, 'lib', 'main.dart'), '''
+import 'unsafe.dart';
+void main() {
+  print(unsafeString);
+}
+''');
+    writeFile(fileSystem.path.join(projectRoot.path, 'lib', 'unsafe.dart'), '''
+// @dart=2.9
+String unsafeString = null;
+''');
+  });
+
+  tearDownAll(() {
+    tryToDelete(tempDir);
+  });
+
+  for (final String targetPlatform in targetPlatforms) {
+    testWithoutContext('flutter build $targetPlatform --no-sound-null-safety', () {
+      final ProcessResult result = processManager.runSync(<String>[
+        flutterBin,
+        ...getLocalEngineArguments(),
+        'build',
+        targetPlatform,
+        '--no-pub',
+        '--no-sound-null-safety',
+        if (targetPlatform == 'ios') '--no-codesign',
+      ], workingDirectory: projectRoot.path);
+
+      if (result.exitCode != 0) {
+        fail('build --no-sound-null-safety failed: ${result.exitCode}\n${result.stderr}\n${result.stdout}');
+      }
+    });
+  }
+}