Unverified Commit e110ca72 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

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

parent 39f93d18
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0830" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
...@@ -27,15 +27,6 @@ ...@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Free App.app"
BlueprintName = "Free App"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
</Testables> </Testables>
</TestAction> </TestAction>
......
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0830" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
...@@ -27,15 +27,6 @@ ...@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Free App.app"
BlueprintName = "Free App"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
</Testables> </Testables>
</TestAction> </TestAction>
......
...@@ -119,18 +119,10 @@ Future<XcodeBuildResult> buildXcodeProject({ ...@@ -119,18 +119,10 @@ Future<XcodeBuildResult> buildXcodeProject({
await removeFinderExtendedAttributes(app.project.hostAppRoot, processUtils, globals.logger); 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); final String scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) { if (scheme == null) {
globals.printError(''); projectInfo.reportFlavorNotFoundAndExit();
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);
} }
final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme); final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
if (configuration == null) { if (configuration == null) {
......
...@@ -391,7 +391,7 @@ class XcodeProjectInterpreter { ...@@ -391,7 +391,7 @@ class XcodeProjectInterpreter {
if (result.exitCode == missingProjectExitCode) { if (result.exitCode == missingProjectExitCode) {
throwToolExit('Unable to get Xcode project information:\n ${result.stderr}'); 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 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin ...@@ -435,9 +435,14 @@ String substituteXcodeVariables(String str, Map<String, String> xcodeBuildSettin
/// ///
/// Represents the output of `xcodebuild -list`. /// Represents the output of `xcodebuild -list`.
class XcodeProjectInfo { class XcodeProjectInfo {
XcodeProjectInfo(this.targets, this.buildConfigurations, this.schemes); XcodeProjectInfo(
this.targets,
factory XcodeProjectInfo.fromXcodeBuildOutput(String output) { this.buildConfigurations,
this.schemes,
Logger logger
) : _logger = logger;
factory XcodeProjectInfo.fromXcodeBuildOutput(String output, Logger logger) {
final List<String> targets = <String>[]; final List<String> targets = <String>[];
final List<String> buildConfigurations = <String>[]; final List<String> buildConfigurations = <String>[];
final List<String> schemes = <String>[]; final List<String> schemes = <String>[];
...@@ -461,16 +466,18 @@ class XcodeProjectInfo { ...@@ -461,16 +466,18 @@ class XcodeProjectInfo {
if (schemes.isEmpty) { if (schemes.isEmpty) {
schemes.add('Runner'); schemes.add('Runner');
} }
return XcodeProjectInfo(targets, buildConfigurations, schemes); return XcodeProjectInfo(targets, buildConfigurations, schemes, logger);
} }
final List<String> targets; final List<String> targets;
final List<String> buildConfigurations; final List<String> buildConfigurations;
final List<String> schemes; final List<String> schemes;
final Logger _logger;
bool get definesCustomSchemes => !(schemes.contains('Runner') && schemes.length == 1); bool get definesCustomSchemes => !(schemes.contains('Runner') && schemes.length == 1);
/// The expected scheme for [buildInfo]. /// The expected scheme for [buildInfo].
@visibleForTesting
static String expectedSchemeFor(BuildInfo buildInfo) { static String expectedSchemeFor(BuildInfo buildInfo) {
return toTitleCase(buildInfo?.flavor ?? 'runner'); return toTitleCase(buildInfo?.flavor ?? 'runner');
} }
...@@ -507,6 +514,16 @@ class XcodeProjectInfo { ...@@ -507,6 +514,16 @@ class XcodeProjectInfo {
}); });
} }
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 /// Returns unique build configuration matching [buildInfo] and [scheme], or
/// null, if there is no unique best match. /// null, if there is no unique best match.
String buildConfigurationFor(BuildInfo buildInfo, String scheme) { String buildConfigurationFor(BuildInfo buildInfo, String scheme) {
......
...@@ -61,7 +61,7 @@ Future<void> buildMacOS({ ...@@ -61,7 +61,7 @@ Future<void> buildMacOS({
); );
final String scheme = projectInfo.schemeFor(buildInfo); final String scheme = projectInfo.schemeFor(buildInfo);
if (scheme == null) { if (scheme == null) {
throwToolExit('Unable to find expected scheme in Xcode project.'); projectInfo.reportFlavorNotFoundAndExit();
} }
final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme); final String configuration = projectInfo.buildConfigurationFor(buildInfo, scheme);
if (configuration == null) { if (configuration == null) {
......
...@@ -20,6 +20,7 @@ import 'flutter_manifest.dart'; ...@@ -20,6 +20,7 @@ import 'flutter_manifest.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'ios/plist_parser.dart'; import 'ios/plist_parser.dart';
import 'ios/xcodeproj.dart' as xcode; import 'ios/xcodeproj.dart' as xcode;
import 'ios/xcodeproj.dart';
import 'platform_plugins.dart'; import 'platform_plugins.dart';
import 'plugins.dart'; import 'plugins.dart';
import 'template.dart'; import 'template.dart';
...@@ -105,11 +106,16 @@ class FlutterProject { ...@@ -105,11 +106,16 @@ class FlutterProject {
// Don't require iOS build info, this method is only // Don't require iOS build info, this method is only
// used during create as best-effort, use the // used during create as best-effort, use the
// default target bundle identifier. // default target bundle identifier.
await ios.productBundleIdentifier(null), if (ios.existsSync())
android.applicationId, await ios.productBundleIdentifier(null),
android.group, if (android.existsSync()) ...<String>[
example.android.applicationId, android.applicationId,
await example.ios.productBundleIdentifier(null), android.group,
],
if (example.android.existsSync())
example.android.applicationId,
if (example.ios.existsSync())
await example.ios.productBundleIdentifier(null),
]; ];
return Set<String>.of(candidates return Set<String>.of(candidates
.map<String>(_organizationNameFromPackageName) .map<String>(_organizationNameFromPackageName)
...@@ -434,8 +440,12 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -434,8 +440,12 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
/// The product bundle identifier of the host app, or null if not set or if /// The product bundle identifier of the host app, or null if not set or if
/// iOS tooling needed to read it is not installed. /// iOS tooling needed to read it is not installed.
Future<String> productBundleIdentifier(BuildInfo buildInfo) async => Future<String> productBundleIdentifier(BuildInfo buildInfo) async {
_productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo); if (!existsSync()) {
return null;
}
return _productBundleIdentifier ??= await _parseProductBundleIdentifier(buildInfo);
}
String _productBundleIdentifier; String _productBundleIdentifier;
Future<String> _parseProductBundleIdentifier(BuildInfo buildInfo) async { Future<String> _parseProductBundleIdentifier(BuildInfo buildInfo) async {
...@@ -481,8 +491,12 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -481,8 +491,12 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
} }
/// The bundle name of the host app, `My App.app`. /// The bundle name of the host app, `My App.app`.
Future<String> hostAppBundleName(BuildInfo buildInfo) async => Future<String> hostAppBundleName(BuildInfo buildInfo) async {
_hostAppBundleName ??= await _parseHostAppBundleName(buildInfo); if (!existsSync()) {
return null;
}
return _hostAppBundleName ??= await _parseHostAppBundleName(buildInfo);
}
String _hostAppBundleName; String _hostAppBundleName;
Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async { Future<String> _parseHostAppBundleName(BuildInfo buildInfo) async {
...@@ -507,12 +521,32 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -507,12 +521,32 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
/// ///
/// Returns null, if iOS tooling is unavailable. /// Returns null, if iOS tooling is unavailable.
Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async { Future<Map<String, String>> buildSettingsForBuildInfo(BuildInfo buildInfo) async {
if (!existsSync()) {
return null;
}
_buildSettingsByScheme ??= <String, Map<String, String>>{}; _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); return _buildSettingsByScheme[scheme] ??= await _xcodeProjectBuildSettings(scheme);
} }
Map<String, Map<String, String>> _buildSettingsByScheme; 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 { Future<Map<String, String>> _xcodeProjectBuildSettings(String scheme) async {
if (!globals.xcodeProjectInterpreter.isInstalled) { if (!globals.xcodeProjectInterpreter.isInstalled) {
return null; return null;
...@@ -624,32 +658,6 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -624,32 +658,6 @@ class IosProject extends FlutterProjectPlatform implements XcodeBasedProject {
} }
} }
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 @override
File get generatedXcodePropertiesFile => _flutterLibRoot File get generatedXcodePropertiesFile => _flutterLibRoot
.childDirectory('Flutter') .childDirectory('Flutter')
...@@ -788,20 +796,6 @@ class AndroidProject extends FlutterProjectPlatform { ...@@ -788,20 +796,6 @@ class AndroidProject extends FlutterProjectPlatform {
) || globals.cache.isOlderThanToolsStamp(ephemeralDirectory); ) || 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'); File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties');
Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app'); Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app');
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.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/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
...@@ -26,6 +27,7 @@ class FakeXcodeProjectInterpreterWithProfile extends FakeXcodeProjectInterpreter ...@@ -26,6 +27,7 @@ class FakeXcodeProjectInterpreterWithProfile extends FakeXcodeProjectInterpreter
<String>['Runner'], <String>['Runner'],
<String>['Debug', 'Profile', 'Release'], <String>['Debug', 'Profile', 'Release'],
<String>['Runner'], <String>['Runner'],
BufferLogger.test(),
); );
} }
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.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/base/platform.dart';
import 'package:flutter_tools/src/commands/clean.dart'; import 'package:flutter_tools/src/commands/clean.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
...@@ -140,6 +141,6 @@ class MockXcode extends Mock implements Xcode {} ...@@ -140,6 +141,6 @@ class MockXcode extends Mock implements Xcode {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter { class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {
@override @override
Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async { Future<XcodeProjectInfo> getInfo(String projectPath, {String projectFilename}) async {
return XcodeProjectInfo(null, null, <String>['Runner']); return XcodeProjectInfo(null, null, <String>['Runner'], BufferLogger.test());
} }
} }
...@@ -87,6 +87,7 @@ void main() { ...@@ -87,6 +87,7 @@ void main() {
<String>['Runner'], <String>['Runner'],
<String>['Debug', 'Release'], <String>['Debug', 'Release'],
<String>['Runner'], <String>['Runner'],
logger,
)); ));
} }
); );
......
...@@ -379,7 +379,7 @@ Information about project "Runner": ...@@ -379,7 +379,7 @@ Information about project "Runner":
Runner Runner
'''; ''';
final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output); final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
expect(info.targets, <String>['Runner']); expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Runner']); expect(info.schemes, <String>['Runner']);
expect(info.buildConfigurations, <String>['Debug', 'Release']); expect(info.buildConfigurations, <String>['Debug', 'Release']);
...@@ -404,7 +404,7 @@ Information about project "Runner": ...@@ -404,7 +404,7 @@ Information about project "Runner":
Paid Paid
'''; ''';
final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output); final XcodeProjectInfo info = XcodeProjectInfo.fromXcodeBuildOutput(output, logger);
expect(info.targets, <String>['Runner']); expect(info.targets, <String>['Runner']);
expect(info.schemes, <String>['Free', 'Paid']); expect(info.schemes, <String>['Free', 'Paid']);
expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']); expect(info.buildConfigurations, <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)']);
...@@ -434,7 +434,7 @@ Information about project "Runner": ...@@ -434,7 +434,7 @@ Information about project "Runner":
}); });
testWithoutContext('scheme for default project is Runner', () { 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.debug), 'Runner');
expect(info.schemeFor(BuildInfo.profile), 'Runner'); expect(info.schemeFor(BuildInfo.profile), 'Runner');
...@@ -443,7 +443,7 @@ Information about project "Runner": ...@@ -443,7 +443,7 @@ Information about project "Runner":
}); });
testWithoutContext('build configuration for default project is matched against BuildMode', () { 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.debug, 'Runner'), 'Debug');
expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Profile'); expect(info.buildConfigurationFor(BuildInfo.profile, 'Runner'), 'Profile');
...@@ -455,6 +455,7 @@ Information about project "Runner": ...@@ -455,6 +455,7 @@ Information about project "Runner":
<String>['Runner'], <String>['Runner'],
<String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'], <String>['Debug (Free)', 'Debug (Paid)', 'Release (Free)', 'Release (Paid)'],
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
logger,
); );
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free'); expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false)), 'Free');
...@@ -464,11 +465,44 @@ Information about project "Runner": ...@@ -464,11 +465,44 @@ Information about project "Runner":
expect(info.schemeFor(const BuildInfo(BuildMode.debug, 'unknown', treeShakeIcons: false)), isNull); 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', () { testWithoutContext('build configuration for project with custom schemes is matched against BuildMode and flavor', () {
final XcodeProjectInfo info = XcodeProjectInfo( final XcodeProjectInfo info = XcodeProjectInfo(
<String>['Runner'], <String>['Runner'],
<String>['debug (free)', 'Debug paid', 'profile - Free', 'Profile-Paid', 'release - Free', 'Release-Paid'], <String>['debug (free)', 'Debug paid', 'profile - Free', 'Profile-Paid', 'release - Free', 'Release-Paid'],
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
logger,
); );
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)'); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'free', treeShakeIcons: false), 'Free'), 'debug (free)');
...@@ -482,6 +516,7 @@ Information about project "Runner": ...@@ -482,6 +516,7 @@ Information about project "Runner":
<String>['Runner'], <String>['Runner'],
<String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'], <String>['Debug-F', 'Dbg Paid', 'Rel Free', 'Release Full'],
<String>['Free', 'Paid'], <String>['Free', 'Paid'],
logger,
); );
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.debug, 'Free', treeShakeIcons: false), 'Free'), null);
expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null); expect(info.buildConfigurationFor(const BuildInfo(BuildMode.profile, 'Free', treeShakeIcons: false), 'Free'), null);
......
...@@ -9,6 +9,7 @@ import 'package:file/memory.dart'; ...@@ -9,6 +9,7 @@ import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.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/cache.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/flutter_manifest.dart';
...@@ -109,52 +110,6 @@ void main() { ...@@ -109,52 +110,6 @@ void main() {
}); });
}); });
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', () { group('ensure ready for platform-specific tooling', () {
_testInMemory('does nothing, if project is not created', () async { _testInMemory('does nothing, if project is not created', () async {
final FlutterProject project = FlutterProject( final FlutterProject project = FlutterProject(
...@@ -384,6 +339,9 @@ apply plugin: 'kotlin-android' ...@@ -384,6 +339,9 @@ apply plugin: 'kotlin-android'
}); });
} }
); );
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'); expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
}); });
...@@ -411,6 +369,10 @@ apply plugin: 'kotlin-android' ...@@ -411,6 +369,10 @@ apply plugin: 'kotlin-android'
}); });
} }
); );
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)'); when(mockPlistUtils.getValueFromFile(any, any)).thenReturn(r'$(PRODUCT_BUNDLE_IDENTIFIER)');
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject'); expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject');
}); });
...@@ -426,9 +388,55 @@ apply plugin: 'kotlin-android' ...@@ -426,9 +388,55 @@ apply plugin: 'kotlin-android'
}); });
} }
); );
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)'); when(mockPlistUtils.getValueFromFile(any, any)).thenReturn(r'$(PRODUCT_BUNDLE_IDENTIFIER).$(SUFFIX)');
expect(await project.ios.productBundleIdentifier(null), 'io.flutter.someProject.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 { testWithMocks('empty surrounded by quotes', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
addIosProjectFile(project.directory, projectFileContent: () { addIosProjectFile(project.directory, projectFileContent: () {
...@@ -476,6 +484,9 @@ apply plugin: 'kotlin-android' ...@@ -476,6 +484,9 @@ apply plugin: 'kotlin-android'
'FULL_PRODUCT_NAME': 'My App.app' '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'); expect(await project.ios.hostAppBundleName(null), 'My App.app');
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
...@@ -634,6 +645,9 @@ name: foo_bar ...@@ -634,6 +645,9 @@ name: foo_bar
}); });
} }
); );
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 { testUsingContext('no Info.plist in target', () async {
......
...@@ -414,6 +414,7 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { ...@@ -414,6 +414,7 @@ class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter {
<String>['Runner'], <String>['Runner'],
<String>['Debug', 'Release'], <String>['Debug', 'Release'],
<String>['Runner'], <String>['Runner'],
BufferLogger.test(),
); );
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment