Case insensitive check flavor names against Xcode schemes (#61140)
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(), ); } }