Make Flutter tooling work on Android without Xcode being installed (#15161)
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart index ea4e9dc3..f8fd765 100644 --- a/packages/flutter_tools/lib/src/application_package.dart +++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -178,7 +178,7 @@ final String plistPath = fs.path.join('ios', 'Runner', 'Info.plist'); String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey); - if (id == null) + if (id == null || !xcodeProjectInterpreter.isInstalled) return null; final String projectPath = fs.path.join('ios', 'Runner.xcodeproj'); final Map<String, String> buildSettings = xcodeProjectInterpreter.getBuildSettings(projectPath, 'Runner');
diff --git a/packages/flutter_tools/lib/src/ios/cocoapods.dart b/packages/flutter_tools/lib/src/ios/cocoapods.dart index f9eebbb..e020c17 100644 --- a/packages/flutter_tools/lib/src/ios/cocoapods.dart +++ b/packages/flutter_tools/lib/src/ios/cocoapods.dart
@@ -105,7 +105,7 @@ /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files /// include pods configuration. void setupPodfile(String appDirectory) { - if (!xcodeProjectInterpreter.canInterpretXcodeProjects) { + if (!xcodeProjectInterpreter.isInstalled) { // Don't do anything for iOS when host platform doesn't support it. return; }
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart index d407bd2..55b5ed3 100644 --- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart +++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -68,10 +68,10 @@ messages.add(new ValidationMessage('Xcode at ${xcode.xcodeSelectPath}')); - xcodeVersionInfo = xcode.xcodeVersionText; + xcodeVersionInfo = xcode.versionText; if (xcodeVersionInfo.contains(',')) xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(',')); - messages.add(new ValidationMessage(xcode.xcodeVersionText)); + messages.add(new ValidationMessage(xcode.versionText)); if (!xcode.isInstalledAndMeetsVersionCheck) { xcodeStatus = ValidationType.partial;
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index 0d48146..49eb677 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -104,7 +104,7 @@ } class Xcode { - bool get isInstalledAndMeetsVersionCheck => isInstalled && xcodeVersionSatisfactory; + bool get isInstalledAndMeetsVersionCheck => isInstalled && isVersionSatisfactory; String _xcodeSelectPath; String get xcodeSelectPath { @@ -121,11 +121,15 @@ bool get isInstalled { if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) return false; - if (xcodeVersionText == null || !xcodeVersionRegex.hasMatch(xcodeVersionText)) - return false; - return true; + return xcodeProjectInterpreter.isInstalled; } + int get majorVersion => xcodeProjectInterpreter.majorVersion; + + int get minorVersion => xcodeProjectInterpreter.minorVersion; + + String get versionText => xcodeProjectInterpreter.versionText; + bool _eulaSigned; /// Has the EULA been signed? bool get eulaSigned { @@ -145,61 +149,17 @@ return _eulaSigned; } - final RegExp xcodeVersionRegex = new RegExp(r'Xcode ([0-9.]+)'); - void _updateXcodeVersion() { - try { - _xcodeVersionText = processManager.runSync(<String>['/usr/bin/xcodebuild', '-version']).stdout.trim().replaceAll('\n', ', '); - final Match match = xcodeVersionRegex.firstMatch(xcodeVersionText); - if (match == null) - return; - - final String version = match.group(1); - final List<String> components = version.split('.'); - _xcodeMajorVersion = int.parse(components[0]); - _xcodeMinorVersion = components.length == 1 ? 0 : int.parse(components[1]); - } on ProcessException { - // Ignore: leave values null. - } - } - - String _xcodeVersionText; - String get xcodeVersionText { - if (_xcodeVersionText == null) - _updateXcodeVersion(); - return _xcodeVersionText; - } - - int _xcodeMajorVersion; - int get xcodeMajorVersion { - if (_xcodeMajorVersion == null) - _updateXcodeVersion(); - return _xcodeMajorVersion; - } - - int _xcodeMinorVersion; - int get xcodeMinorVersion { - if (_xcodeMinorVersion == null) - _updateXcodeVersion(); - return _xcodeMinorVersion; - } - - bool get xcodeVersionSatisfactory { - if (xcodeVersionText == null || !xcodeVersionRegex.hasMatch(xcodeVersionText)) + bool get isVersionSatisfactory { + if (!xcodeProjectInterpreter.isInstalled) return false; - return _xcodeVersionCheckValid(xcodeMajorVersion, xcodeMinorVersion); + if (majorVersion > kXcodeRequiredVersionMajor) + return true; + if (majorVersion == kXcodeRequiredVersionMajor) + return minorVersion >= kXcodeRequiredVersionMinor; + return false; } } -bool _xcodeVersionCheckValid(int major, int minor) { - if (major > kXcodeRequiredVersionMajor) - return true; - - if (major == kXcodeRequiredVersionMajor) - return minor >= kXcodeRequiredVersionMinor; - - return false; -} - Future<XcodeBuildResult> buildXcodeProject({ BuildableIOSApp app, BuildInfo buildInfo, @@ -547,23 +507,19 @@ final Map<String, String> buildSettings; } -final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*'); final String _xcodeRequirement = 'Xcode $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor or greater is required to develop for iOS.'; bool _checkXcodeVersion() { if (!platform.isMacOS) return false; - try { - final String version = runCheckedSync(<String>['xcodebuild', '-version']); - final Match match = _xcodeVersionRegExp.firstMatch(version); - if (int.parse(match[1]) < kXcodeRequiredVersionMajor) { - printError('Found "${match[0]}". $_xcodeRequirement'); - return false; - } - } catch (e) { + if (!xcodeProjectInterpreter.isInstalled) { printError('Cannot find "xcodebuild". $_xcodeRequirement'); return false; } + if (!xcode.isVersionSatisfactory) { + printError('Found "${xcodeProjectInterpreter.versionText}". $_xcodeRequirement'); + return false; + } return true; }
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index f9d5b22..c2222df 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -477,7 +477,7 @@ } bool get _xcodeVersionSupportsScreenshot { - return xcode.xcodeMajorVersion > 8 || (xcode.xcodeMajorVersion == 8 && xcode.xcodeMinorVersion >= 2); + return xcode.majorVersion > 8 || (xcode.majorVersion == 8 && xcode.minorVersion >= 2); } @override
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart index 9a50d30..e35d6aa 100644 --- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart +++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -7,7 +7,10 @@ import '../artifacts.dart'; import '../base/context.dart'; import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/platform.dart'; import '../base/process.dart'; +import '../base/process_manager.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -84,16 +87,58 @@ XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent( XcodeProjectInterpreter, - () => const XcodeProjectInterpreter(), + () => new XcodeProjectInterpreter(), ); -/// Interpreter of Xcode projects settings. +/// Interpreter of Xcode projects. class XcodeProjectInterpreter { static const String _executable = '/usr/bin/xcodebuild'; + static final RegExp _versionRegex = new RegExp(r'Xcode ([0-9.]+)'); - const XcodeProjectInterpreter(); + void _updateVersion() { + if (!platform.isMacOS || !fs.file(_executable).existsSync()) { + return; + } + try { + final ProcessResult result = processManager.runSync(<String>[_executable, '-version']); + if (result.exitCode != 0) { + return; + } + _versionText = result.stdout.trim().replaceAll('\n', ', '); + final Match match = _versionRegex.firstMatch(versionText); + if (match == null) + return; + final String version = match.group(1); + final List<String> components = version.split('.'); + _majorVersion = int.parse(components[0]); + _minorVersion = components.length == 1 ? 0 : int.parse(components[1]); + } on ProcessException { + // Ignore: leave values null. + } + } - bool get canInterpretXcodeProjects => fs.isFileSync(_executable); + bool get isInstalled => majorVersion != null; + + String _versionText; + String get versionText { + if (_versionText == null) + _updateVersion(); + return _versionText; + } + + int _majorVersion; + int get majorVersion { + if (_majorVersion == null) + _updateVersion(); + return _majorVersion; + } + + int _minorVersion; + int get minorVersion { + if (_minorVersion == null) + _updateVersion(); + return _minorVersion; + } Map<String, String> getBuildSettings(String projectPath, String target) { final String out = runCheckedSync(<String>[