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

5
import 'package:process/process.dart';
6

7
import '../base/io.dart';
8
import '../base/user_messages.dart';
9
import '../base/version.dart';
10
import '../doctor_validator.dart';
11

12 13 14 15 16 17 18
/// A combination of version description and parsed version number.
class _VersionInfo {
  /// Constructs a VersionInfo from a version description string.
  ///
  /// This should contain a version number. For example:
  ///     "clang version 9.0.1-6+build1"
  _VersionInfo(this.description) {
19
    final String? versionString = RegExp(r'[0-9]+\.[0-9]+(?:\.[0-9]+)?').firstMatch(description)?.group(0);
20 21 22 23 24 25 26
    number = Version.parse(versionString);
  }

  // The full info string reported by the binary.
  String description;

  // The parsed Version.
27
  Version? number;
28 29
}

30
/// A validator that checks for Clang and Make build dependencies.
31
class LinuxDoctorValidator extends DoctorValidator {
32
  LinuxDoctorValidator({
33 34
    required ProcessManager processManager,
    required UserMessages userMessages,
35
  }) : _processManager = processManager,
36
       _userMessages = userMessages,
37 38 39
       super('Linux toolchain - develop for Linux desktop');

  final ProcessManager _processManager;
40 41 42 43 44
  final UserMessages _userMessages;

  static const String kClangBinary = 'clang++';
  static const String kCmakeBinary = 'cmake';
  static const String kNinjaBinary = 'ninja';
45
  static const String kPkgConfigBinary = 'pkg-config';
46

47 48 49 50
  final Map<String, Version> _requiredBinaryVersions = <String, Version>{
    kClangBinary: Version(3, 4, 0),
    kCmakeBinary: Version(3, 10, 0),
    kNinjaBinary: Version(1, 8, 0),
51
    kPkgConfigBinary: Version(0, 29, 0),
52
  };
53

54
  final List<String> _requiredGtkLibraries = <String>[
55 56 57 58 59
    'gtk+-3.0',
    'glib-2.0',
    'gio-2.0',
  ];

60 61 62 63
  @override
  Future<ValidationResult> validate() async {
    ValidationType validationType = ValidationType.installed;
    final List<ValidationMessage> messages = <ValidationMessage>[];
64

65
    final Map<String, _VersionInfo?> installedVersions = <String, _VersionInfo?>{
66 67 68 69 70 71
      // Sort the check to make the call order predictable for unit tests.
      for (String binary in _requiredBinaryVersions.keys.toList()..sort())
          binary: await _getBinaryVersion(binary)
    };

    // Determine overall validation level.
72
    if (installedVersions.values.any((_VersionInfo? versionInfo) => versionInfo?.number == null)) {
73
      validationType = ValidationType.missing;
74
    } else if (installedVersions.keys.any((String binary) =>
75
          installedVersions[binary]!.number! < _requiredBinaryVersions[binary]!)) {
76 77 78 79 80
      validationType = ValidationType.partial;
    }

    // Message for Clang.
    {
81 82
      final _VersionInfo? version = installedVersions[kClangBinary];
      if (version == null || version.number == null) {
83 84
        messages.add(ValidationMessage.error(_userMessages.clangMissing));
      } else {
85
        assert(_requiredBinaryVersions.containsKey(kClangBinary));
86
        messages.add(ValidationMessage(version.description));
87 88
        final Version requiredVersion = _requiredBinaryVersions[kClangBinary]!;
        if (version.number! < requiredVersion) {
89 90 91 92 93 94 95
          messages.add(ValidationMessage.error(_userMessages.clangTooOld(requiredVersion.toString())));
        }
      }
    }

    // Message for CMake.
    {
96 97
      final _VersionInfo? version = installedVersions[kCmakeBinary];
      if (version == null || version.number == null) {
98
        messages.add(ValidationMessage.error(_userMessages.cmakeMissing));
99
      } else {
100
        assert(_requiredBinaryVersions.containsKey(kCmakeBinary));
101
        messages.add(ValidationMessage(version.description));
102 103
        final Version requiredVersion = _requiredBinaryVersions[kCmakeBinary]!;
        if (version.number! < requiredVersion) {
104 105
          messages.add(ValidationMessage.error(_userMessages.cmakeTooOld(requiredVersion.toString())));
        }
106 107 108
      }
    }

109 110
    // Message for ninja.
    {
111 112
      final _VersionInfo? version = installedVersions[kNinjaBinary];
      if (version == null || version.number == null) {
113 114
        messages.add(ValidationMessage.error(_userMessages.ninjaMissing));
      } else {
115
        assert(_requiredBinaryVersions.containsKey(kNinjaBinary));
116
        // The full version description is just the number, so add context.
117
        messages.add(ValidationMessage(_userMessages.ninjaVersion(version.description)));
118 119
        final Version requiredVersion = _requiredBinaryVersions[kNinjaBinary]!;
        if (version.number! < requiredVersion) {
120 121 122 123 124
          messages.add(ValidationMessage.error(_userMessages.ninjaTooOld(requiredVersion.toString())));
        }
      }
    }

125 126
    // Message for pkg-config.
    {
127 128
      final _VersionInfo? version = installedVersions[kPkgConfigBinary];
      if (version == null || version.number == null) {
129 130
        messages.add(ValidationMessage.error(_userMessages.pkgConfigMissing));
      } else {
131
        assert(_requiredBinaryVersions.containsKey(kPkgConfigBinary));
132 133
        // The full version description is just the number, so add context.
        messages.add(ValidationMessage(_userMessages.pkgConfigVersion(version.description)));
134 135
        final Version requiredVersion = _requiredBinaryVersions[kPkgConfigBinary]!;
        if (version.number! < requiredVersion) {
136 137 138 139 140
          messages.add(ValidationMessage.error(_userMessages.pkgConfigTooOld(requiredVersion.toString())));
        }
      }
    }

141
    // Messages for libraries.
142 143
    {
      bool libraryMissing = false;
144
      for (final String library in _requiredGtkLibraries) {
145 146 147 148 149 150 151 152 153 154 155
        if (!await _libraryIsPresent(library)) {
          libraryMissing = true;
          break;
        }
      }
      if (libraryMissing) {
        validationType = ValidationType.missing;
        messages.add(ValidationMessage.error(_userMessages.gtkLibrariesMissing));
      }
    }

156 157 158 159 160 161 162
    return ValidationResult(validationType, messages);
  }

  /// Returns the installed version of [binary], or null if it's not installed.
  ///
  /// Requires tha [binary] take a '--version' flag, and print a version of the
  /// form x.y.z somewhere on the first line of output.
163 164
  Future<_VersionInfo?> _getBinaryVersion(String binary) async {
    ProcessResult? result;
165
    try {
166 167
      result = await _processManager.run(<String>[
        binary,
168 169
        '--version',
      ]);
170
    } on ArgumentError {
171 172
      // ignore error.
    }
173 174
    if (result == null || result.exitCode != 0) {
      return null;
175
    }
176 177
    final String firstLine = (result.stdout as String).split('\n').first.trim();
    return _VersionInfo(firstLine);
178
  }
179 180 181

  /// Checks that [library] is available via pkg-config.
  Future<bool> _libraryIsPresent(String library) async {
182
    ProcessResult? result;
183 184 185 186 187 188
    try {
      result = await _processManager.run(<String>[
        'pkg-config',
        '--exists',
        library,
      ]);
189
    } on ArgumentError {
190 191 192 193
      // ignore error.
    }
    return (result?.exitCode ?? 1) == 0;
  }
194
}