Case insensitive check flavor names against Xcode schemes (#61140)

diff --git a/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Free.xcscheme b/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/free.xcscheme
similarity index 88%
rename from dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Free.xcscheme
rename to dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/free.xcscheme
index 4cd4df6..a606d2b 100644
--- a/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Free.xcscheme
+++ b/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/free.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0830"
+   LastUpgradeVersion = "1200"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -27,15 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Free App.app"
-            BlueprintName = "Free App"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
       <Testables>
       </Testables>
    </TestAction>
diff --git a/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Paid.xcscheme b/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/paid.xcscheme
similarity index 87%
rename from dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Paid.xcscheme
rename to dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/paid.xcscheme
index 4fbd7e1..43f1ee85 100644
--- a/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/Paid.xcscheme
+++ b/dev/integration_tests/flavors/ios/Runner.xcodeproj/xcshareddata/xcschemes/paid.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0830"
+   LastUpgradeVersion = "1200"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -27,15 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <MacroExpansion>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
-            BuildableName = "Free App.app"
-            BlueprintName = "Free App"
-            ReferencedContainer = "container:Runner.xcodeproj">
-         </BuildableReference>
-      </MacroExpansion>
       <Testables>
       </Testables>
    </TestAction>
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 4398459..1223b2e 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -119,18 +119,10 @@
 
   await removeFinderExtendedAttributes(app.project.hostAppRoot, processUtils, globals.logger);
 
-  final XcodeProjectInfo projectInfo = await globals.xcodeProjectInterpreter.getInfo(app.project.hostAppRoot.path);
+  final XcodeProjectInfo projectInfo = await app.project.projectInfo();
   final String scheme = projectInfo.schemeFor(buildInfo);
   if (scheme == null) {
-    globals.printError('');
-    if (projectInfo.definesCustomSchemes) {
-      globals.printError('The Xcode project defines schemes: ${projectInfo.schemes.join(', ')}');
-      globals.printError('You must specify a --flavor option to select one of them.');
-    } else {
-      globals.printError('The Xcode project does not define custom schemes.');
-      globals.printError('You cannot use the --flavor option.');
-    }
-    return XcodeBuildResult(success: false);
+    projectInfo.reportFlavorNotFoundAndExit();
   }
   final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
   if (configuration == null) {
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 873c6af..8b09b35 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -391,7 +391,7 @@
     if (result.exitCode == missingProjectExitCode) {
       throwToolExit('Unable to get Xcode project information:\n ${result.stderr}');
     }
-    return XcodeProjectInfo.fromXcodeBuildOutput(result.toString());
+    return XcodeProjectInfo.fromXcodeBuildOutput(result.toString(), _logger);
   }
 }
 
@@ -435,9 +435,14 @@
 ///
 /// Represents the output of `xcodebuild -list`.
 class XcodeProjectInfo {
-  XcodeProjectInfo(this.targets, this.buildConfigurations, this.schemes);
+  XcodeProjectInfo(
+    this.targets,
+    this.buildConfigurations,
+    this.schemes,
+    Logger logger
+  ) : _logger = logger;
 
-  factory XcodeProjectInfo.fromXcodeBuildOutput(String output) {
+  factory XcodeProjectInfo.fromXcodeBuildOutput(String output, Logger logger) {
     final List<String> targets = <String>[];
     final List<String> buildConfigurations = <String>[];
     final List<String> schemes = <String>[];
@@ -461,16 +466,18 @@
     if (schemes.isEmpty) {
       schemes.add('Runner');
     }
-    return XcodeProjectInfo(targets, buildConfigurations, schemes);
+    return XcodeProjectInfo(targets, buildConfigurations, schemes, logger);
   }
 
   final List<String> targets;
   final List<String> buildConfigurations;
   final List<String> schemes;
+  final Logger _logger;
 
   bool get definesCustomSchemes => !(schemes.contains('Runner') && schemes.length == 1);
 
   /// The expected scheme for [buildInfo].
+  @visibleForTesting
   static String expectedSchemeFor(BuildInfo buildInfo) {
     return toTitleCase(buildInfo?.flavor ?? 'runner');
   }
@@ -507,6 +514,16 @@
     });
   }
 
+  void reportFlavorNotFoundAndExit() {
+    _logger.printError('');
+    if (definesCustomSchemes) {
+      _logger.printError('The Xcode project defines schemes: ${schemes.join(', ')}');
+      throwToolExit('You must specify a --flavor option to select one of the available schemes.');
+    } else {
+      throwToolExit('The Xcode project does not define custom schemes. You cannot use the --flavor option.');
+    }
+  }
+
   /// Returns unique build configuration matching [buildInfo] and [scheme], or
   /// null, if there is no unique best match.
   String buildConfigurationFor(BuildInfo buildInfo, String scheme) {
diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart
index 83c9849..b885195 100644
--- a/packages/flutter_tools/lib/src/macos/build_macos.dart
+++ b/packages/flutter_tools/lib/src/macos/build_macos.dart
@@ -61,7 +61,7 @@
   );
   final String scheme = projectInfo.schemeFor(buildInfo);
   if (scheme == null) {
-    throwToolExit('Unable to find expected scheme in Xcode project.');
+    projectInfo.reportFlavorNotFoundAndExit();
   }
   final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
   if (configuration == null) {
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 5b8f070..690877d 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -20,6 +20,7 @@
 import 'globals.dart' as globals;
 import 'ios/plist_parser.dart';
 import 'ios/xcodeproj.dart' as xcode;
+import 'ios/xcodeproj.dart';
 import 'platform_plugins.dart';
 import 'plugins.dart';
 import 'template.dart';
@@ -105,11 +106,16 @@
       // Don't require iOS build info, this method is only
       // used during create as best-effort, use the
       // default target bundle identifier.
-      await ios.productBundleIdentifier(null),
-      android.applicationId,
-      android.group,
-      example.android.applicationId,
-      await example.ios.productBundleIdentifier(null),
+      if (ios.existsSync())
+        await ios.productBundleIdentifier(null),
+      if (android.existsSync()) ...<String>[
+        android.applicationId,
+        android.group,
+      ],
+      if (example.android.existsSync())
+        example.android.applicationId,
+      if (example.ios.existsSync())
+        await example.ios.productBundleIdentifier(null),
     ];
     return Set<String>.of(candidates
         .map<String>(_organizationNameFromPackageName)
@@ -434,8 +440,12 @@
 
   /// The product bundle identifier of the host app, or null if not set or if
   /// iOS tooling needed to read it is not installed.
-  Future<String> productBundleIdentifier(BuildInfo buildInfo) async =>
-    _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
+  Future<String> productBundleIdentifier(BuildInfo buildInfo) async {
+    if (!existsSync()) {
+      return null;
+    }
+    return _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
+  }
   String _productBundleIdentifier;
 
   Future<String> _parseProductBundleIdentifier(BuildInfo buildInfo) async {
@@ -481,8 +491,12 @@
   }
 
   /// The bundle name of the host app, `My App.app`.
-  Future<String> hostAppBundleName(BuildInfo buildInfo) async =>
-    _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
+  Future<String> hostAppBundleName(BuildInfo buildInfo) async {
+    if (!existsSync()) {
+      return null;
+    }
+    return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
+  }
   String _hostAppBundleName;
 
   Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async {
@@ -507,12 +521,32 @@
   ///
   /// Returns null, if iOS tooling is unavailable.
   Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async {
+    if (!existsSync()) {
+      return null;
+    }
     _buildSettingsByScheme ??= <String, Map<String, String>>{};
-    final String scheme = xcode.XcodeProjectInfo.expectedSchemeFor(buildInfo);
+    final XcodeProjectInfo info = await projectInfo();
+    if (info == null) {
+      return null;
+    }
+
+    final String scheme = info.schemeFor(buildInfo);
+    if (scheme == null) {
+      info.reportFlavorNotFoundAndExit();
+    }
+
     return _buildSettingsByScheme[scheme] ??= await _xcodeProjectBuildSettings(scheme);
   }
   Map<String, Map<String, String>> _buildSettingsByScheme;
 
+  Future<XcodeProjectInfo> projectInfo() async {
+    if (!existsSync() || !globals.xcodeProjectInterpreter.isInstalled) {
+      return null;
+    }
+    return _projectInfo ??= await globals.xcodeProjectInterpreter.getInfo(hostAppRoot.path);
+  }
+  XcodeProjectInfo _projectInfo;
+
   Future<Map<String, String>> _xcodeProjectBuildSettings(String scheme) async {
     if (!globals.xcodeProjectInterpreter.isInstalled) {
       return null;
@@ -624,32 +658,6 @@
     }
   }
 
-  Future<void> makeHostAppEditable() async {
-    assert(isModule);
-    if (_editableDirectory.existsSync()) {
-      throwToolExit('iOS host app is already editable. To start fresh, delete the ios/ folder.');
-    }
-    _deleteIfExistsSync(ephemeralDirectory);
-    await _overwriteFromTemplate(
-      globals.fs.path.join('module', 'ios', 'library'),
-      ephemeralDirectory,
-    );
-    await _overwriteFromTemplate(
-      globals.fs.path.join('module', 'ios', 'host_app_ephemeral'),
-      _editableDirectory,
-    );
-    await _overwriteFromTemplate(
-      globals.fs.path.join('module', 'ios', 'host_app_ephemeral_cocoapods'),
-      _editableDirectory,
-    );
-    await _overwriteFromTemplate(
-      globals.fs.path.join('module', 'ios', 'host_app_editable_cocoapods'),
-      _editableDirectory,
-    );
-    await _updateGeneratedXcodeConfigIfNeeded();
-    await injectPlugins(parent);
-  }
-
   @override
   File get generatedXcodePropertiesFile => _flutterLibRoot
     .childDirectory('Flutter')
@@ -788,20 +796,6 @@
     ) || globals.cache.isOlderThanToolsStamp(ephemeralDirectory);
   }
 
-  Future<void> makeHostAppEditable() async {
-    assert(isModule);
-    if (_editableHostAppDirectory.existsSync()) {
-      throwToolExit('Android host app is already editable. To start fresh, delete the android/ folder.');
-    }
-    await _regenerateLibrary();
-    await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), _editableHostAppDirectory);
-    await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_editable'), _editableHostAppDirectory);
-    await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'gradle'), _editableHostAppDirectory);
-    gradle.gradleUtils.injectGradleWrapperIfNeeded(_editableHostAppDirectory);
-    gradle.writeLocalProperties(_editableHostAppDirectory.childFile('local.properties'));
-    await injectPlugins(parent);
-  }
-
   File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
 
   Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart
index df2da20..e7017d0 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart
@@ -5,6 +5,7 @@
 import 'package:args/command_runner.dart';
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/cache.dart';
@@ -26,6 +27,7 @@
       <String>['Runner'],
       <String>['Debug', 'Profile', 'Release'],
       <String>['Runner'],
+      BufferLogger.test(),
     );
   }
 }
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
index 191665f..80df539 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/clean_test.dart
@@ -5,6 +5,7 @@
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/commands/clean.dart';
 import 'package:flutter_tools/src/ios/xcodeproj.dart';
@@ -140,6 +141,6 @@
 class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {
   @override
   Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
-    return XcodeProjectInfo(null, null, <String>['Runner']);
+    return XcodeProjectInfo(null, null, <String>['Runner'], BufferLogger.test());
   }
 }
diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
index 4b961dc..9a6887a 100644
--- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart
@@ -87,6 +87,7 @@
             <String>['Runner'],
             <String>['Debug', 'Release'],
             <String>['Runner'],
+            logger,
           ));
         }
       );
diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
index 262482f..7ea5155 100644
--- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -379,7 +379,7 @@
         Runner
 
 ''';
-    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output);
+    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
     expect(info.targets, <String>['Runner']);
     expect(info.schemes, <String>['Runner']);
     expect(info.buildConfigurations, <String>['Debug', 'Release']);
@@ -404,7 +404,7 @@
         Paid
 
 ''';
-    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output);
+    final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
     expect(info.targets, <String>['Runner']);
     expect(info.schemes, <String>['Free', 'Paid']);
     expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']);
@@ -434,7 +434,7 @@
   });
 
   testWithoutContext('scheme for default project is Runner', () {
-    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner']);
+    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Release'], <String>['Runner'], logger);
 
     expect(info.schemeFor(BuildInfo.debug), 'Runner');
     expect(info.schemeFor(BuildInfo.profile), 'Runner');
@@ -443,7 +443,7 @@
   });
 
   testWithoutContext('build configuration for default project is matched against BuildMode', () {
-    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Profile', 'Release'], <String>['Runner']);
+    final XcodeProjectInfo info = XcodeProjectInfo(<String>['Runner'], <String>['Debug', 'Profile', 'Release'], <String>['Runner'], logger);
 
     expect(info.buildConfigurationFor(BuildInfo.debug, 'Runner'), 'Debug');
     expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Profile');
@@ -455,6 +455,7 @@
       <String>['Runner'],
       <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'],
       <String>['Free', 'Paid'],
+      logger,
     );
 
     expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free');
@@ -464,11 +465,44 @@
     expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull);
   });
 
+  testWithoutContext('reports default scheme error and exit', () {
+    final XcodeProjectInfo defaultInfo = XcodeProjectInfo(
+      <String>[],
+      <String>[],
+      <String>['Runner'],
+      logger,
+    );
+
+    expect(
+      () => defaultInfo.reportFlavorNotFoundAndExit(),
+      throwsToolExit(
+        message: 'The Xcode project does not define custom schemes. You cannot use the --flavor option.'
+      ),
+    );
+  });
+
+  testWithoutContext('reports custom scheme error and exit', () {
+    final XcodeProjectInfo info = XcodeProjectInfo(
+      <String>[],
+      <String>[],
+      <String>['Free', 'Paid'],
+      logger,
+    );
+
+    expect(
+     () => info.reportFlavorNotFoundAndExit(),
+      throwsToolExit(
+        message: 'You must specify a --flavor option to select one of the available schemes.'
+      ),
+    );
+  });
+
   testWithoutContext('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
     final XcodeProjectInfo info = XcodeProjectInfo(
       <String>['Runner'],
       <String>['debug (free)', 'Debug paid', 'profile - Free', 'Profile-Paid', 'release - Free', 'Release-Paid'],
       <String>['Free', 'Paid'],
+      logger,
     );
 
     expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)');
@@ -482,6 +516,7 @@
       <String>['Runner'],
       <String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
       <String>['Free', 'Paid'],
+      logger,
     );
     expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null);
     expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null);
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart
index 52bc3d6..ee2bb0c 100644
--- a/packages/flutter_tools/test/general.shard/project_test.dart
+++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -9,6 +9,7 @@
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/features.dart';
 import 'package:flutter_tools/src/flutter_manifest.dart';
@@ -109,52 +110,6 @@
       });
     });
 
-    group('editable Android host app', () {
-      _testInMemory('fails on non-module', () async {
-        final FlutterProject project = await someProject();
-        await expectLater(
-          project.android.makeHostAppEditable(),
-          throwsAssertionError,
-        );
-      });
-      _testInMemory('exits on already editable module', () async {
-        final FlutterProject project = await aModuleProject();
-        await project.android.makeHostAppEditable();
-        return expectToolExitLater(project.android.makeHostAppEditable(), contains('already editable'));
-      });
-      _testInMemory('creates android/app folder in place of .android/app', () async {
-        final FlutterProject project = await aModuleProject();
-        await project.android.makeHostAppEditable();
-        expectNotExists(project.directory.childDirectory('.android').childDirectory('app'));
-        expect(
-          project.directory.childDirectory('.android').childFile('settings.gradle').readAsStringSync(),
-          isNot(contains("include ':app'")),
-        );
-        expectExists(project.directory.childDirectory('android').childDirectory('app'));
-        expectExists(project.directory.childDirectory('android').childFile('local.properties'));
-        expect(
-          project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
-          contains("include ':app'"),
-        );
-      });
-      _testInMemory('retains .android/Flutter folder and references it', () async {
-        final FlutterProject project = await aModuleProject();
-        await project.android.makeHostAppEditable();
-        expectExists(project.directory.childDirectory('.android').childDirectory('Flutter'));
-        expect(
-          project.directory.childDirectory('android').childFile('settings.gradle').readAsStringSync(),
-          contains("new File(settingsDir.parentFile, '.android/include_flutter.groovy')"),
-        );
-      });
-      _testInMemory('can be redone after deletion', () async {
-        final FlutterProject project = await aModuleProject();
-        await project.android.makeHostAppEditable();
-        project.directory.childDirectory('android').deleteSync(recursive: true);
-        await project.android.makeHostAppEditable();
-        expectExists(project.directory.childDirectory('android').childDirectory('app'));
-      });
-    });
-
     group('ensure ready for platform-specific tooling', () {
       _testInMemory('does nothing, if project is not created', () async {
         final FlutterProject project = FlutterProject(
@@ -384,6 +339,9 @@
               });
             }
         );
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
         expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
       });
 
@@ -411,6 +369,10 @@
               });
             }
         );
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
+
         when(mockPlistUtils.getValueFromFile(any, any)).thenReturn(r'$(PRODUCT_BUNDLE_IDENTIFIER)');
         expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
       });
@@ -426,9 +388,55 @@
             });
           }
         );
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
+
         when(mockPlistUtils.getValueFromFile(any, any)).thenReturn(r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)');
         expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject.suffix');
       });
+
+      testWithMocks('fails with no flavor and defined schemes', () async {
+        final FlutterProject project = await someProject();
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['free', 'paid'], logger));
+        });
+        await expectToolExitLater(
+          project.ios.productBundleIdentifier(null),
+          contains('You must specify a --flavor option to select one of the available schemes.')
+        );
+      });
+
+      testWithMocks('handles case insensitive flavor', () async {
+        final FlutterProject project = await someProject();
+        when(mockXcodeProjectInterpreter.getBuildSettings(any, scheme: anyNamed('scheme'))).thenAnswer(
+                (_) {
+              return Future<Map<String,String>>.value(<String, String>{
+                'PRODUCT_BUNDLE_IDENTIFIER': 'io.flutter.someProject',
+              });
+            }
+        );
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Free'], logger));
+        });
+
+        const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
+        expect(await project.ios.productBundleIdentifier(buildInfo), 'io.flutter.someProject');
+      });
+
+      testWithMocks('fails with flavor and default schemes', () async {
+        final FlutterProject project = await someProject();
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
+
+        const BuildInfo buildInfo = BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false);
+        await expectToolExitLater(
+          project.ios.productBundleIdentifier(buildInfo),
+          contains('The Xcode project does not define custom schemes. You cannot use the --flavor option.')
+        );
+      });
+
       testWithMocks('empty surrounded by quotes', () async {
         final FlutterProject project = await someProject();
         addIosProjectFile(project.directory, projectFileContent: () {
@@ -476,6 +484,9 @@
             'FULL_PRODUCT_NAME': 'My App.app'
           });
         });
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
 
         expect(await project.ios.hostAppBundleName(null), 'My App.app');
       }, overrides: <Type, Generator>{
@@ -634,6 +645,9 @@
             });
           }
         );
+        when(mockXcodeProjectInterpreter.getInfo(any, projectFilename: anyNamed('projectFilename'))).thenAnswer( (_) {
+          return Future<XcodeProjectInfo>.value(XcodeProjectInfo(<String>[], <String>[], <String>['Runner'], logger));
+        });
       });
 
       testUsingContext('no Info.plist in target', () async {
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index b8d1414..efd0385 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -414,6 +414,7 @@
       <String>['Runner'],
       <String>['Debug', 'Release'],
       <String>['Runner'],
+      BufferLogger.test(),
     );
   }
 }