Unverified Commit 515003e4 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate xcode.dart and xcode_validator.dart to null safety (#80614)

parent 2c7309c7
......@@ -9,7 +9,7 @@ import 'package:meta/meta.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../build_info.dart';
import '../globals.dart' as globals;
import '../globals_null_migrated.dart' as globals;
import '../ios/xcodeproj.dart';
import '../project.dart';
import '../runner/flutter_command.dart';
......
......@@ -12,12 +12,10 @@ import 'device.dart';
import 'doctor.dart';
import 'fuchsia/fuchsia_sdk.dart';
import 'globals_null_migrated.dart' as globals;
import 'ios/ios_workflow.dart';
import 'ios/simulators.dart';
import 'macos/cocoapods.dart';
import 'macos/cocoapods_validator.dart';
import 'macos/xcdevice.dart';
import 'macos/xcode.dart';
import 'project.dart';
import 'reporting/crash_reporting.dart';
import 'runner/local_engine.dart';
......@@ -44,8 +42,6 @@ LocalEngineLocator get localEngineLocator => context.get<LocalEngineLocator>();
CocoaPods get cocoaPods => context.get<CocoaPods>();
FuchsiaArtifacts get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
IOSSimulatorUtils get iosSimulatorUtils => context.get<IOSSimulatorUtils>();
IOSWorkflow get iosWorkflow => context.get<IOSWorkflow>();
Xcode get xcode => context.get<Xcode>();
XCDevice get xcdevice => context.get<XCDevice>();
......
......@@ -23,8 +23,10 @@ import 'base/terminal.dart';
import 'base/time.dart';
import 'base/user_messages.dart';
import 'cache.dart';
import 'ios/ios_workflow.dart';
import 'ios/plist_parser.dart';
import 'ios/xcodeproj.dart';
import 'macos/xcode.dart';
import 'persistent_tool_state.dart';
import 'reporting/reporting.dart';
import 'version.dart';
......@@ -40,6 +42,8 @@ AndroidSdk? get androidSdk => context.get<AndroidSdk>();
FlutterVersion get flutterVersion => context.get<FlutterVersion>()!;
Usage get flutterUsage => context.get<Usage>()!;
XcodeProjectInterpreter? get xcodeProjectInterpreter => context.get<XcodeProjectInterpreter>();
Xcode? get xcode => context.get<Xcode>();
IOSWorkflow? get iosWorkflow => context.get<IOSWorkflow>();
PersistentToolState? get persistentToolState => PersistentToolState.instance;
......
......@@ -7,7 +7,7 @@
import '../base/process.dart';
import '../device.dart';
import '../emulator.dart';
import '../globals.dart' as globals;
import '../globals_null_migrated.dart' as globals;
import 'simulators.dart';
class IOSEmulators extends EmulatorDiscovery {
......
......@@ -2,10 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import '../base/platform.dart';
import '../doctor_validator.dart';
import '../features.dart';
......@@ -13,9 +9,9 @@ import '../macos/xcode.dart';
class IOSWorkflow implements Workflow {
const IOSWorkflow({
@required Platform platform,
@required FeatureFlags featureFlags,
@required Xcode xcode,
required Platform platform,
required FeatureFlags featureFlags,
required Xcode xcode,
}) : _platform = platform,
_featureFlags = featureFlags,
_xcode = xcode;
......
......@@ -18,7 +18,7 @@ import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../flutter_manifest.dart';
import '../globals.dart' as globals;
import '../globals_null_migrated.dart' as globals;
import '../macos/cocoapod_utils.dart';
import '../macos/xcode.dart';
import '../project.dart';
......
......@@ -22,7 +22,7 @@ import '../convert.dart';
import '../devfs.dart';
import '../device.dart';
import '../device_port_forwarder.dart';
import '../globals.dart' as globals;
import '../globals_null_migrated.dart' as globals;
import '../macos/xcode.dart';
import '../project.dart';
import '../protocol_discovery.dart';
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'dart:async';
import 'package:file/memory.dart';
......@@ -41,11 +39,11 @@ String getSDKNameForIOSEnvironmentType(EnvironmentType environmentType) {
/// A utility class for interacting with Xcode command line tools.
class Xcode {
Xcode({
@required Platform platform,
@required ProcessManager processManager,
@required Logger logger,
@required FileSystem fileSystem,
@required XcodeProjectInterpreter xcodeProjectInterpreter,
required Platform platform,
required ProcessManager processManager,
required Logger logger,
required FileSystem fileSystem,
required XcodeProjectInterpreter xcodeProjectInterpreter,
}) : _platform = platform,
_fileSystem = fileSystem,
_xcodeProjectInterpreter = xcodeProjectInterpreter,
......@@ -58,10 +56,10 @@ class Xcode {
/// buffer logger, and test [XcodeProjectInterpreter].
@visibleForTesting
factory Xcode.test({
@required ProcessManager processManager,
XcodeProjectInterpreter xcodeProjectInterpreter,
Platform platform,
FileSystem fileSystem,
required ProcessManager processManager,
XcodeProjectInterpreter? xcodeProjectInterpreter,
Platform? platform,
FileSystem? fileSystem,
}) {
platform ??= FakePlatform(
operatingSystem: 'macos',
......@@ -83,8 +81,8 @@ class Xcode {
bool get isInstalledAndMeetsVersionCheck => _platform.isMacOS && isInstalled && isRequiredVersionSatisfactory;
String _xcodeSelectPath;
String get xcodeSelectPath {
String? _xcodeSelectPath;
String? get xcodeSelectPath {
if (_xcodeSelectPath == null) {
try {
_xcodeSelectPath = _processUtils.runSync(
......@@ -101,11 +99,11 @@ class Xcode {
bool get isInstalled => _xcodeProjectInterpreter.isInstalled;
Version get currentVersion => _xcodeProjectInterpreter.version;
Version? get currentVersion => _xcodeProjectInterpreter.version;
String get versionText => _xcodeProjectInterpreter.versionText;
String? get versionText => _xcodeProjectInterpreter.versionText;
bool _eulaSigned;
bool? _eulaSigned;
/// Has the EULA been signed?
bool get eulaSigned {
if (_eulaSigned == null) {
......@@ -124,10 +122,10 @@ class Xcode {
_eulaSigned = false;
}
}
return _eulaSigned;
return _eulaSigned ?? false;
}
bool _isSimctlInstalled;
bool? _isSimctlInstalled;
/// Verifies that simctl is installed by trying to run it.
bool get isSimctlInstalled {
......@@ -143,21 +141,23 @@ class Xcode {
_isSimctlInstalled = false;
}
}
return _isSimctlInstalled;
return _isSimctlInstalled ?? false;
}
bool get isRequiredVersionSatisfactory {
if (!_xcodeProjectInterpreter.isInstalled) {
final Version? version = currentVersion;
if (version == null) {
return false;
}
return currentVersion >= xcodeRequiredVersion;
return version >= xcodeRequiredVersion;
}
bool get isRecommendedVersionSatisfactory {
if (!_xcodeProjectInterpreter.isInstalled) {
final Version? version = currentVersion;
if (version == null) {
return false;
}
return currentVersion >= xcodeRecommendedVersion;
return version >= xcodeRecommendedVersion;
}
/// See [XcodeProjectInterpreter.xcrunCommand].
......@@ -188,21 +188,17 @@ class Xcode {
return runResult.stdout.trim();
}
String getSimulatorPath() {
if (xcodeSelectPath == null) {
String? getSimulatorPath() {
final String? selectPath = xcodeSelectPath;
if (selectPath == null) {
return null;
}
final List<String> searchPaths = <String>[
_fileSystem.path.join(xcodeSelectPath, 'Applications', 'Simulator.app'),
];
return searchPaths.where((String p) => p != null).firstWhere(
(String p) => _fileSystem.directory(p).existsSync(),
orElse: () => null,
);
final String appPath = _fileSystem.path.join(selectPath, 'Applications', 'Simulator.app');
return _fileSystem.directory(appPath).existsSync() ? appPath : null;
}
}
EnvironmentType environmentTypeFromSdkroot(Directory sdkroot) {
EnvironmentType? environmentTypeFromSdkroot(Directory sdkroot) {
assert(sdkroot != null);
// iPhoneSimulator.sdk or iPhoneOS.sdk
final String sdkName = sdkroot.basename.toLowerCase();
......
......@@ -2,18 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import '../base/user_messages.dart';
import '../doctor_validator.dart';
import 'xcode.dart';
class XcodeValidator extends DoctorValidator {
XcodeValidator({
@required Xcode xcode,
@required UserMessages userMessages,
required Xcode xcode,
required UserMessages userMessages,
}) : _xcode = xcode,
_userMessages = userMessages,
super('Xcode - develop for iOS and macOS');
......@@ -25,13 +21,19 @@ class XcodeValidator extends DoctorValidator {
Future<ValidationResult> validate() async {
final List<ValidationMessage> messages = <ValidationMessage>[];
ValidationType xcodeStatus = ValidationType.missing;
String xcodeVersionInfo;
String? xcodeVersionInfo;
final String? xcodeSelectPath = _xcode.xcodeSelectPath;
if (_xcode.isInstalled) {
xcodeStatus = ValidationType.installed;
messages.add(ValidationMessage(_userMessages.xcodeLocation(_xcode.xcodeSelectPath)));
messages.add(ValidationMessage(_xcode.versionText));
if (xcodeSelectPath != null) {
messages.add(ValidationMessage(_userMessages.xcodeLocation(xcodeSelectPath)));
}
final String? versionText = _xcode.versionText;
if (versionText != null) {
messages.add(ValidationMessage(versionText));
}
if (!_xcode.isInstalledAndMeetsVersionCheck) {
xcodeStatus = ValidationType.partial;
......@@ -58,7 +60,7 @@ class XcodeValidator extends DoctorValidator {
} else {
xcodeStatus = ValidationType.missing;
if (_xcode.xcodeSelectPath == null || _xcode.xcodeSelectPath.isEmpty) {
if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) {
messages.add(ValidationMessage.error(_userMessages.xcodeMissing));
} else {
messages.add(ValidationMessage.error(_userMessages.xcodeIncomplete));
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/ios/ios_workflow.dart';
......
......@@ -2,52 +2,55 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/base/version.dart';
import 'package:flutter_tools/src/doctor_validator.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/macos/xcode.dart';
import 'package:flutter_tools/src/macos/xcode_validator.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
class MockXcode extends Mock implements Xcode {}
import '../../src/fake_process_manager.dart';
void main() {
group('Xcode validation', () {
MockXcode xcode;
setUp(() {
xcode = MockXcode();
});
testWithoutContext('Emits missing status when Xcode is not installed', () async {
when(xcode.isInstalled).thenReturn(false);
when(xcode.xcodeSelectPath).thenReturn(null);
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.missing);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode not installed'));
});
testWithoutContext('Emits missing status when Xcode installation is incomplete', () async {
when(xcode.isInstalled).thenReturn(false);
when(xcode.xcodeSelectPath).thenReturn('/Library/Developer/CommandLineTools');
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.missing);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode installation is incomplete'));
});
testWithoutContext('Emits partial status when Xcode version too low', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 7.0.1\nBuild version 7C1002\n');
when(xcode.currentVersion).thenReturn(Version(7, 0, 1));
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(false);
when(xcode.isRecommendedVersionSatisfactory).thenReturn(false);
when(xcode.eulaSigned).thenReturn(true);
when(xcode.isSimctlInstalled).thenReturn(true);
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(7, 0, 1)),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
......@@ -56,14 +59,11 @@ void main() {
});
testWithoutContext('Emits partial status when Xcode below recommended version', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 11.0\nBuild version 11A420a\n');
when(xcode.currentVersion).thenReturn(Version(11, 0, 0));
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.isRecommendedVersionSatisfactory).thenReturn(false);
when(xcode.eulaSigned).thenReturn(true);
when(xcode.isSimctlInstalled).thenReturn(true);
final ProcessManager processManager = FakeProcessManager.any();
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(11, 0, 0)),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
......@@ -72,40 +72,113 @@ void main() {
}, skip: true); // Unskip and update when minimum and required check versions diverge.
testWithoutContext('Emits partial status when Xcode EULA not signed', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.isRecommendedVersionSatisfactory).thenReturn(true);
when(xcode.eulaSigned).thenReturn(false);
when(xcode.isSimctlInstalled).thenReturn(true);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
exitCode: 1,
stderr:
'Xcode EULA has not been accepted.\nLaunch Xcode and accept the license.',
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('code end user license agreement not signed'));
});
testWithoutContext('Emits partial status when simctl is not installed', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.isRecommendedVersionSatisfactory).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true);
when(xcode.isSimctlInstalled).thenReturn(false);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
exitCode: 1,
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.partial);
expect(result.messages.last.type, ValidationMessageType.error);
expect(result.messages.last.message, contains('Xcode requires additional components'));
});
testWithoutContext('Succeeds when all checks pass', () async {
when(xcode.isInstalled).thenReturn(true);
when(xcode.versionText)
.thenReturn('Xcode 8.2.1\nBuild version 8C1002\n');
when(xcode.isInstalledAndMeetsVersionCheck).thenReturn(true);
when(xcode.isRecommendedVersionSatisfactory).thenReturn(true);
when(xcode.eulaSigned).thenReturn(true);
when(xcode.isSimctlInstalled).thenReturn(true);
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['/usr/bin/xcode-select', '--print-path'],
stdout: '/Library/Developer/CommandLineTools',
),
const FakeCommand(
command: <String>[
'which',
'sysctl',
],
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
const FakeCommand(
command: <String>['xcrun', 'clang'],
),
const FakeCommand(
command: <String>['xcrun', 'simctl', 'list'],
),
]);
final Xcode xcode = Xcode.test(
processManager: processManager,
xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager),
);
final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages());
final ValidationResult result = await validator.validate();
expect(result.type, ValidationType.installed);
......
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