xcode_validator.dart 5 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import '../base/user_messages.dart';
6 7
import '../base/version.dart';
import '../build_info.dart';
8
import '../doctor_validator.dart';
9
import '../ios/simulators.dart';
10 11
import 'xcode.dart';

12 13 14 15 16 17 18 19
String _iOSSimulatorMissing(String version) => '''
iOS $version Simulator not installed; this may be necessary for iOS and macOS development.
To download and install the platform, open Xcode, select Xcode > Settings > Platforms,
and click the GET button for the required platform.

For more information, please visit:
  https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes''';

20
class XcodeValidator extends DoctorValidator {
21
  XcodeValidator({
22
    required Xcode xcode,
23
    required IOSSimulatorUtils iosSimulatorUtils,
24
    required UserMessages userMessages,
25 26 27 28
  })  : _xcode = xcode,
        _iosSimulatorUtils = iosSimulatorUtils,
        _userMessages = userMessages,
        super('Xcode - develop for iOS and macOS');
29 30

  final Xcode _xcode;
31
  final IOSSimulatorUtils _iosSimulatorUtils;
32
  final UserMessages _userMessages;
33 34 35 36 37

  @override
  Future<ValidationResult> validate() async {
    final List<ValidationMessage> messages = <ValidationMessage>[];
    ValidationType xcodeStatus = ValidationType.missing;
38 39 40
    String? xcodeVersionInfo;

    final String? xcodeSelectPath = _xcode.xcodeSelectPath;
41

42
    if (_xcode.isInstalled) {
43
      xcodeStatus = ValidationType.success;
44 45 46 47 48
      if (xcodeSelectPath != null) {
        messages.add(ValidationMessage(_userMessages.xcodeLocation(xcodeSelectPath)));
      }
      final String? versionText = _xcode.versionText;
      if (versionText != null) {
49 50 51 52
        xcodeVersionInfo = versionText;
        if (xcodeVersionInfo.contains(',')) {
          xcodeVersionInfo = xcodeVersionInfo.substring(0, xcodeVersionInfo.indexOf(','));
        }
53
      }
54 55 56
      if (_xcode.buildVersion != null) {
        messages.add(ValidationMessage('Build ${_xcode.buildVersion}'));
      }
57
      if (!_xcode.isInstalledAndMeetsVersionCheck) {
58
        xcodeStatus = ValidationType.partial;
59
        messages.add(ValidationMessage.error(_userMessages.xcodeOutdated(xcodeRequiredVersion.toString())));
60 61
      } else if (!_xcode.isRecommendedVersionSatisfactory) {
        xcodeStatus = ValidationType.partial;
62
        messages.add(ValidationMessage.hint(_userMessages.xcodeRecommended(xcodeRecommendedVersion.toString())));
63 64
      }

65
      if (!_xcode.eulaSigned) {
66
        xcodeStatus = ValidationType.partial;
67
        messages.add(ValidationMessage.error(_userMessages.xcodeEula));
68
      }
69
      if (!_xcode.isSimctlInstalled) {
70
        xcodeStatus = ValidationType.partial;
71
        messages.add(ValidationMessage.error(_userMessages.xcodeMissingSimct));
72 73
      }

74 75 76 77 78
      final ValidationMessage? missingSimulatorMessage = await _validateSimulatorRuntimeInstalled();
      if (missingSimulatorMessage != null) {
        xcodeStatus = ValidationType.partial;
        messages.add(missingSimulatorMessage);
      }
79 80
    } else {
      xcodeStatus = ValidationType.missing;
81
      if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) {
82
        messages.add(ValidationMessage.error(_userMessages.xcodeMissing));
83
      } else {
84
        messages.add(ValidationMessage.error(_userMessages.xcodeIncomplete));
85 86 87 88 89
      }
    }

    return ValidationResult(xcodeStatus, messages, statusInfo: xcodeVersionInfo);
  }
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130

  /// Validate the Xcode-installed iOS simulator SDK has a corresponding iOS
  /// simulator runtime installed.
  ///
  /// Starting with Xcode 15, the iOS simulator runtime is no longer downloaded
  /// with Xcode and must be downloaded and installed separately.
  /// iOS applications cannot be run without it.
  Future<ValidationMessage?> _validateSimulatorRuntimeInstalled() async {
    // Skip this validation if Xcode is not installed, Xcode is a version less
    // than 15, simctl is not installed, or if the EULA is not signed.
    if (!_xcode.isInstalled ||
        _xcode.currentVersion == null ||
        _xcode.currentVersion!.major < 15 ||
        !_xcode.isSimctlInstalled ||
        !_xcode.eulaSigned) {
      return null;
    }

    final Version? platformSDKVersion = await _xcode.sdkPlatformVersion(EnvironmentType.simulator);
    if (platformSDKVersion == null) {
      return const ValidationMessage.error('Unable to find the iPhone Simulator SDK.');
    }

    final List<IOSSimulatorRuntime> runtimes = await _iosSimulatorUtils.getAvailableIOSRuntimes();
    if (runtimes.isEmpty) {
      return const ValidationMessage.error('Unable to get list of installed Simulator runtimes.');
    }

    // Verify there is a simulator runtime installed matching the
    // iphonesimulator SDK major version.
    try {
      runtimes.firstWhere(
        (IOSSimulatorRuntime runtime) =>
            runtime.version?.major == platformSDKVersion.major,
      );
    } on StateError {
      return ValidationMessage.hint(_iOSSimulatorMissing(platformSDKVersion.toString()));
    }

    return null;
  }
131
}