Unverified Commit 0d596796 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Make Flutter tooling work on Android without Xcode being installed (#15161)

parent c5a5945e
...@@ -178,7 +178,7 @@ abstract class IOSApp extends ApplicationPackage { ...@@ -178,7 +178,7 @@ abstract class IOSApp extends ApplicationPackage {
final String plistPath = fs.path.join('ios', 'Runner', 'Info.plist'); final String plistPath = fs.path.join('ios', 'Runner', 'Info.plist');
String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey); String id = plist.getValueFromFile(plistPath, plist.kCFBundleIdentifierKey);
if (id == null) if (id == null || !xcodeProjectInterpreter.isInstalled)
return null; return null;
final String projectPath = fs.path.join('ios', 'Runner.xcodeproj'); final String projectPath = fs.path.join('ios', 'Runner.xcodeproj');
final Map<String, String> buildSettings = xcodeProjectInterpreter.getBuildSettings(projectPath, 'Runner'); final Map<String, String> buildSettings = xcodeProjectInterpreter.getBuildSettings(projectPath, 'Runner');
......
...@@ -105,7 +105,7 @@ class CocoaPods { ...@@ -105,7 +105,7 @@ class CocoaPods {
/// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files /// contains a suitable `Podfile` and that its `Flutter/Xxx.xcconfig` files
/// include pods configuration. /// include pods configuration.
void setupPodfile(String appDirectory) { void setupPodfile(String appDirectory) {
if (!xcodeProjectInterpreter.canInterpretXcodeProjects) { if (!xcodeProjectInterpreter.isInstalled) {
// Don't do anything for iOS when host platform doesn't support it. // Don't do anything for iOS when host platform doesn't support it.
return; return;
} }
......
...@@ -68,10 +68,10 @@ class IOSWorkflow extends DoctorValidator implements Workflow { ...@@ -68,10 +68,10 @@ class IOSWorkflow extends DoctorValidator implements Workflow {
messages.add(new ValidationMessage('Xcode at ${xcode.xcodeSelectPath}')); messages.add(new ValidationMessage('Xcode at ${xcode.xcodeSelectPath}'));
xcodeVersionInfo = xcode.xcodeVersionText; xcodeVersionInfo = xcode.versionText;
if (xcodeVersionInfo.contains(',')) if (xcodeVersionInfo.contains(','))
xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(',')); xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
messages.add(new ValidationMessage(xcode.xcodeVersionText)); messages.add(new ValidationMessage(xcode.versionText));
if (!xcode.isInstalledAndMeetsVersionCheck) { if (!xcode.isInstalledAndMeetsVersionCheck) {
xcodeStatus = ValidationType.partial; xcodeStatus = ValidationType.partial;
......
...@@ -104,7 +104,7 @@ class IMobileDevice { ...@@ -104,7 +104,7 @@ class IMobileDevice {
} }
class Xcode { class Xcode {
bool get isInstalledAndMeetsVersionCheck => isInstalled && xcodeVersionSatisfactory; bool get isInstalledAndMeetsVersionCheck => isInstalled && isVersionSatisfactory;
String _xcodeSelectPath; String _xcodeSelectPath;
String get xcodeSelectPath { String get xcodeSelectPath {
...@@ -121,11 +121,15 @@ class Xcode { ...@@ -121,11 +121,15 @@ class Xcode {
bool get isInstalled { bool get isInstalled {
if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) if (xcodeSelectPath == null || xcodeSelectPath.isEmpty)
return false; return false;
if (xcodeVersionText == null || !xcodeVersionRegex.hasMatch(xcodeVersionText)) return xcodeProjectInterpreter.isInstalled;
return false;
return true;
} }
int get majorVersion => xcodeProjectInterpreter.majorVersion;
int get minorVersion => xcodeProjectInterpreter.minorVersion;
String get versionText => xcodeProjectInterpreter.versionText;
bool _eulaSigned; bool _eulaSigned;
/// Has the EULA been signed? /// Has the EULA been signed?
bool get eulaSigned { bool get eulaSigned {
...@@ -145,59 +149,15 @@ class Xcode { ...@@ -145,59 +149,15 @@ class Xcode {
return _eulaSigned; return _eulaSigned;
} }
final RegExp xcodeVersionRegex = new RegExp(r'Xcode ([0-9.]+)'); bool get isVersionSatisfactory {
void _updateXcodeVersion() { if (!xcodeProjectInterpreter.isInstalled)
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))
return false; return false;
return _xcodeVersionCheckValid(xcodeMajorVersion, xcodeMinorVersion); if (majorVersion > kXcodeRequiredVersionMajor)
}
}
bool _xcodeVersionCheckValid(int major, int minor) {
if (major > kXcodeRequiredVersionMajor)
return true; return true;
if (majorVersion == kXcodeRequiredVersionMajor)
if (major == kXcodeRequiredVersionMajor) return minorVersion >= kXcodeRequiredVersionMinor;
return minor >= kXcodeRequiredVersionMinor;
return false; return false;
}
} }
Future<XcodeBuildResult> buildXcodeProject({ Future<XcodeBuildResult> buildXcodeProject({
...@@ -547,21 +507,17 @@ class XcodeBuildExecution { ...@@ -547,21 +507,17 @@ class XcodeBuildExecution {
final Map<String, String> buildSettings; 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.'; final String _xcodeRequirement = 'Xcode $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor or greater is required to develop for iOS.';
bool _checkXcodeVersion() { bool _checkXcodeVersion() {
if (!platform.isMacOS) if (!platform.isMacOS)
return false; return false;
try { if (!xcodeProjectInterpreter.isInstalled) {
final String version = runCheckedSync(<String>['xcodebuild', '-version']); printError('Cannot find "xcodebuild". $_xcodeRequirement');
final Match match = _xcodeVersionRegExp.firstMatch(version);
if (int.parse(match[1]) < kXcodeRequiredVersionMajor) {
printError('Found "${match[0]}". $_xcodeRequirement');
return false; return false;
} }
} catch (e) { if (!xcode.isVersionSatisfactory) {
printError('Cannot find "xcodebuild". $_xcodeRequirement'); printError('Found "${xcodeProjectInterpreter.versionText}". $_xcodeRequirement');
return false; return false;
} }
return true; return true;
......
...@@ -477,7 +477,7 @@ class IOSSimulator extends Device { ...@@ -477,7 +477,7 @@ class IOSSimulator extends Device {
} }
bool get _xcodeVersionSupportsScreenshot { bool get _xcodeVersionSupportsScreenshot {
return xcode.xcodeMajorVersion > 8 || (xcode.xcodeMajorVersion == 8 && xcode.xcodeMinorVersion >= 2); return xcode.majorVersion > 8 || (xcode.majorVersion == 8 && xcode.minorVersion >= 2);
} }
@override @override
......
...@@ -7,7 +7,10 @@ import 'package:meta/meta.dart'; ...@@ -7,7 +7,10 @@ import 'package:meta/meta.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/context.dart'; import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart';
import '../base/platform.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/process_manager.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -84,16 +87,58 @@ void updateGeneratedXcodeProperties({ ...@@ -84,16 +87,58 @@ void updateGeneratedXcodeProperties({
XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent( XcodeProjectInterpreter get xcodeProjectInterpreter => context.putIfAbsent(
XcodeProjectInterpreter, XcodeProjectInterpreter,
() => const XcodeProjectInterpreter(), () => new XcodeProjectInterpreter(),
); );
/// Interpreter of Xcode projects settings. /// Interpreter of Xcode projects.
class XcodeProjectInterpreter { class XcodeProjectInterpreter {
static const String _executable = '/usr/bin/xcodebuild'; 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) { Map<String, String> getBuildSettings(String projectPath, String target) {
final String out = runCheckedSync(<String>[ final String out = runCheckedSync(<String>[
......
...@@ -70,7 +70,7 @@ void main() { ...@@ -70,7 +70,7 @@ void main() {
}); });
testUsingContext('creates swift Podfile if swift', () { testUsingContext('creates swift Podfile if swift', () {
when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(true); when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{ when(mockXcodeProjectInterpreter.getBuildSettings(any, any)).thenReturn(<String, String>{
'SWIFT_VERSION': '4.0', 'SWIFT_VERSION': '4.0',
}); });
...@@ -94,7 +94,7 @@ void main() { ...@@ -94,7 +94,7 @@ void main() {
}); });
testUsingContext('does not create Podfile when we cannot interpret Xcode projects', () { testUsingContext('does not create Podfile when we cannot interpret Xcode projects', () {
when(mockXcodeProjectInterpreter.canInterpretXcodeProjects).thenReturn(false); when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
cocoaPodsUnderTest.setupPodfile('project'); cocoaPodsUnderTest.setupPodfile('project');
......
...@@ -79,7 +79,7 @@ void main() { ...@@ -79,7 +79,7 @@ void main() {
testUsingContext('Emits partial status when Xcode version too low', () async { testUsingContext('Emits partial status when Xcode version too low', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 7.0.1\nBuild version 7C1002\n'); .thenReturn('Xcode 7.0.1\nBuild version 7C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(false); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -94,7 +94,7 @@ void main() { ...@@ -94,7 +94,7 @@ void main() {
testUsingContext('Emits partial status when Xcode EULA not signed', () async { testUsingContext('Emits partial status when Xcode EULA not signed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(false); when(xcode.eulaSigned).thenReturn(false);
...@@ -109,7 +109,7 @@ void main() { ...@@ -109,7 +109,7 @@ void main() {
testUsingContext('Emits partial status when Mac dev mode was never enabled', () async { testUsingContext('Emits partial status when Mac dev mode was never enabled', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -124,7 +124,7 @@ void main() { ...@@ -124,7 +124,7 @@ void main() {
testUsingContext('Emits partial status when python six not installed', () async { testUsingContext('Emits partial status when python six not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -139,7 +139,7 @@ void main() { ...@@ -139,7 +139,7 @@ void main() {
testUsingContext('Emits partial status when homebrew not installed', () async { testUsingContext('Emits partial status when homebrew not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -154,7 +154,7 @@ void main() { ...@@ -154,7 +154,7 @@ void main() {
testUsingContext('Emits partial status when libimobiledevice is not installed', () async { testUsingContext('Emits partial status when libimobiledevice is not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -169,7 +169,7 @@ void main() { ...@@ -169,7 +169,7 @@ void main() {
testUsingContext('Emits partial status when libimobiledevice is installed but not working', () async { testUsingContext('Emits partial status when libimobiledevice is installed but not working', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -184,7 +184,7 @@ void main() { ...@@ -184,7 +184,7 @@ void main() {
testUsingContext('Emits partial status when ios-deploy is not installed', () async { testUsingContext('Emits partial status when ios-deploy is not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -199,7 +199,7 @@ void main() { ...@@ -199,7 +199,7 @@ void main() {
testUsingContext('Emits partial status when ios-deploy version is too low', () async { testUsingContext('Emits partial status when ios-deploy version is too low', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -214,7 +214,7 @@ void main() { ...@@ -214,7 +214,7 @@ void main() {
testUsingContext('Emits partial status when CocoaPods is not installed', () async { testUsingContext('Emits partial status when CocoaPods is not installed', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -231,7 +231,7 @@ void main() { ...@@ -231,7 +231,7 @@ void main() {
testUsingContext('Emits partial status when CocoaPods version is too low', () async { testUsingContext('Emits partial status when CocoaPods version is too low', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -249,7 +249,7 @@ void main() { ...@@ -249,7 +249,7 @@ void main() {
testUsingContext('Emits partial status when CocoaPods is not initialized', () async { testUsingContext('Emits partial status when CocoaPods is not initialized', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
...@@ -269,7 +269,7 @@ void main() { ...@@ -269,7 +269,7 @@ void main() {
testUsingContext('Succeeds when all checks pass', () async { testUsingContext('Succeeds when all checks pass', () async {
when(xcode.isInstalled).thenReturn(true); when(xcode.isInstalled).thenReturn(true);
when(xcode.xcodeVersionText) when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n'); .thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true); when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true); when(xcode.eulaSigned).thenReturn(true);
......
...@@ -8,6 +8,7 @@ import 'package:file/file.dart'; ...@@ -8,6 +8,7 @@ import 'package:file/file.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/io.dart' show ProcessException, ProcessResult; import 'package:flutter_tools/src/base/io.dart' show ProcessException, ProcessResult;
import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/ios/mac.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart'; import 'package:platform/platform.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -18,6 +19,7 @@ import '../src/context.dart'; ...@@ -18,6 +19,7 @@ import '../src/context.dart';
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockFile extends Mock implements File {} class MockFile extends Mock implements File {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
void main() { void main() {
group('IMobileDevice', () { group('IMobileDevice', () {
...@@ -97,9 +99,11 @@ void main() { ...@@ -97,9 +99,11 @@ void main() {
group('Xcode', () { group('Xcode', () {
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
Xcode xcode; Xcode xcode;
MockXcodeProjectInterpreter mockXcodeProjectInterpreter;
setUp(() { setUp(() {
mockProcessManager = new MockProcessManager(); mockProcessManager = new MockProcessManager();
mockXcodeProjectInterpreter = new MockXcodeProjectInterpreter();
xcode = new Xcode(); xcode = new Xcode();
}); });
...@@ -120,100 +124,47 @@ void main() { ...@@ -120,100 +124,47 @@ void main() {
ProcessManager: () => mockProcessManager, ProcessManager: () => mockProcessManager,
}); });
testUsingContext('xcodeVersionText returns null when xcodebuild is not installed', () { testUsingContext('xcodeVersionSatisfactory is false when version is less than minimum', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version'])) when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
.thenThrow(const ProcessException('/usr/bin/xcodebuild', const <String>['-version'])); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(8);
expect(xcode.xcodeVersionText, isNull); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(17);
}, overrides: <Type, Generator>{ expect(xcode.isVersionSatisfactory, isFalse);
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeVersionText returns formatted version text', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcode.xcodeVersionText, 'Xcode 8.3.3, Build version 8E3004b');
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeVersionText handles Xcode version string with unexpected format', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcode.xcodeVersionText, 'Xcode Ultra5000, Build version 8E3004b');
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeMajorVersion returns major version', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcode.xcodeMajorVersion, 8);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeMajorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcode.xcodeMajorVersion, isNull);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeMinorVersion returns minor version', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcode.xcodeMinorVersion, 3);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeMinorVersion returns 0 when minor version is unspecified', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8\nBuild version 8E3004b', ''));
expect(xcode.xcodeMinorVersion, 0);
}, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
});
testUsingContext('xcodeMinorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcode.xcodeMinorVersion, isNull);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('xcodeVersionSatisfactory is false when version is less than minimum', () { testUsingContext('xcodeVersionSatisfactory is false when xcodebuild tools are not installed', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version'])) when(mockXcodeProjectInterpreter.isInstalled).thenReturn(false);
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', '')); expect(xcode.isVersionSatisfactory, isFalse);
expect(xcode.xcodeVersionSatisfactory, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('xcodeVersionSatisfactory is false when version in unknown format', () { testUsingContext('xcodeVersionSatisfactory is true when version meets minimum', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version'])) when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
.thenReturn(new ProcessResult(1, 0, 'Xcode SuperHD\nBuild version 7A1001', '')); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
expect(xcode.xcodeVersionSatisfactory, isFalse); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
expect(xcode.isVersionSatisfactory, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('xcodeVersionSatisfactory is true when version meets minimum', () { testUsingContext('xcodeVersionSatisfactory is true when major version exceeds minimum', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version'])) when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
.thenReturn(new ProcessResult(1, 0, 'Xcode 9.0\nBuild version 9A235', '')); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(10);
expect(xcode.xcodeVersionSatisfactory, isTrue); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(0);
expect(xcode.isVersionSatisfactory, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('xcodeVersionSatisfactory is true when version exceeds minimum', () { testUsingContext('xcodeVersionSatisfactory is true when minor version exceeds minimum', () {
when(mockProcessManager.runSync(<String>['/usr/bin/xcodebuild', '-version'])) when(mockXcodeProjectInterpreter.isInstalled).thenReturn(true);
.thenReturn(new ProcessResult(1, 0, 'Xcode 10.0\nBuild version 10A123', '')); when(mockXcodeProjectInterpreter.majorVersion).thenReturn(9);
expect(xcode.xcodeVersionSatisfactory, isTrue); when(mockXcodeProjectInterpreter.minorVersion).thenReturn(1);
expect(xcode.isVersionSatisfactory, isTrue);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager, XcodeProjectInterpreter: () => mockXcodeProjectInterpreter,
}); });
testUsingContext('eulaSigned is false when clang is not installed', () { testUsingContext('eulaSigned is false when clang is not installed', () {
......
...@@ -165,8 +165,8 @@ void main() { ...@@ -165,8 +165,8 @@ void main() {
testUsingContext( testUsingContext(
'old Xcode doesn\'t support screenshot', 'old Xcode doesn\'t support screenshot',
() { () {
when(mockXcode.xcodeMajorVersion).thenReturn(7); when(mockXcode.majorVersion).thenReturn(7);
when(mockXcode.xcodeMinorVersion).thenReturn(1); when(mockXcode.minorVersion).thenReturn(1);
expect(deviceUnderTest.supportsScreenshot, false); expect(deviceUnderTest.supportsScreenshot, false);
}, },
overrides: <Type, Generator>{Xcode: () => mockXcode} overrides: <Type, Generator>{Xcode: () => mockXcode}
...@@ -175,8 +175,8 @@ void main() { ...@@ -175,8 +175,8 @@ void main() {
testUsingContext( testUsingContext(
'Xcode 8.2+ supports screenshots', 'Xcode 8.2+ supports screenshots',
() async { () async {
when(mockXcode.xcodeMajorVersion).thenReturn(8); when(mockXcode.majorVersion).thenReturn(8);
when(mockXcode.xcodeMinorVersion).thenReturn(2); when(mockXcode.minorVersion).thenReturn(2);
expect(deviceUnderTest.supportsScreenshot, true); expect(deviceUnderTest.supportsScreenshot, true);
final MockFile mockFile = new MockFile(); final MockFile mockFile = new MockFile();
when(mockFile.path).thenReturn(fs.path.join('some', 'path', 'to', 'screenshot.png')); when(mockFile.path).thenReturn(fs.path.join('some', 'path', 'to', 'screenshot.png'));
......
import 'package:test/test.dart'; // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import 'package:test/test.dart';
import '../src/context.dart';
const String xcodebuild = '/usr/bin/xcodebuild';
void main() { void main() {
group('xcodebuild versioning', () {
MockProcessManager mockProcessManager;
XcodeProjectInterpreter xcodeProjectInterpreter;
FakePlatform macOS;
FileSystem fs;
setUp(() {
mockProcessManager = new MockProcessManager();
xcodeProjectInterpreter = new XcodeProjectInterpreter();
macOS = fakePlatform('macos');
fs = new MemoryFileSystem();
fs.file(xcodebuild).createSync(recursive: true);
});
void testUsingOsxContext(String description, dynamic testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
ProcessManager: () => mockProcessManager,
Platform: () => macOS,
FileSystem: () => fs,
});
}
testUsingOsxContext('versionText returns null when xcodebuild is not installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenThrow(const ProcessException(xcodebuild, const <String>['-version']));
expect(xcodeProjectInterpreter.versionText, isNull);
});
testUsingOsxContext('versionText returns null when xcodebuild is not fully installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version'])).thenReturn(
new ProcessResult(
0,
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance',
'',
),
);
expect(xcodeProjectInterpreter.versionText, isNull);
});
testUsingOsxContext('versionText returns formatted version text', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.versionText, 'Xcode 8.3.3, Build version 8E3004b');
});
testUsingOsxContext('versionText handles Xcode version string with unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.versionText, 'Xcode Ultra5000, Build version 8E3004b');
});
testUsingOsxContext('majorVersion returns major version', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.majorVersion, 8);
});
testUsingOsxContext('majorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.majorVersion, isNull);
});
testUsingOsxContext('minorVersion returns minor version', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, 3);
});
testUsingOsxContext('minorVersion returns 0 when minor version is unspecified', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, 0);
});
testUsingOsxContext('minorVersion is null when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.minorVersion, isNull);
});
testUsingContext('isInstalled is false when not on MacOS', () {
fs.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse);
}, overrides: <Type, Generator>{
Platform: () => fakePlatform('notMacOS')
});
testUsingOsxContext('isInstalled is false when xcodebuild does not exist', () {
fs.file(xcodebuild).deleteSync();
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is false when Xcode is not fully installed', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version'])).thenReturn(
new ProcessResult(
0,
1,
"xcode-select: error: tool 'xcodebuild' requires Xcode, "
"but active developer directory '/Library/Developer/CommandLineTools' "
'is a command line tools instance',
'',
),
);
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is false when version has unexpected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode Ultra5000\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.isInstalled, isFalse);
});
testUsingOsxContext('isInstalled is true when version has expected format', () {
when(mockProcessManager.runSync(<String>[xcodebuild, '-version']))
.thenReturn(new ProcessResult(1, 0, 'Xcode 8.3.3\nBuild version 8E3004b', ''));
expect(xcodeProjectInterpreter.isInstalled, isTrue);
});
});
group('Xcode project properties', () { group('Xcode project properties', () {
test('properties from default project can be parsed', () { test('properties from default project can be parsed', () {
const String output = ''' const String output = '''
...@@ -118,3 +253,10 @@ Information about project "Runner": ...@@ -118,3 +253,10 @@ Information about project "Runner":
}); });
}); });
} }
Platform fakePlatform(String name) {
return new FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter { }
...@@ -291,7 +291,16 @@ class MockUsage implements Usage { ...@@ -291,7 +291,16 @@ class MockUsage implements Usage {
class MockXcodeProjectInterpreter implements XcodeProjectInterpreter { class MockXcodeProjectInterpreter implements XcodeProjectInterpreter {
@override @override
bool get canInterpretXcodeProjects => true; bool get isInstalled => true;
@override
String get versionText => 'Xcode 9.2';
@override
int get majorVersion => 9;
@override
int get minorVersion => 2;
@override @override
Map<String, String> getBuildSettings(String projectPath, String target) { Map<String, String> getBuildSettings(String projectPath, String target) {
......
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