android_workflow.dart 7.63 KB
Newer Older
1 2 3 4
// Copyright 2016 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.

5
import 'dart:async';
6

7
import '../base/context.dart';
8
import '../base/file_system.dart';
9
import '../base/io.dart';
10
import '../base/os.dart';
11
import '../base/platform.dart';
12
import '../base/process.dart';
13
import '../base/process_manager.dart';
14
import '../doctor.dart';
15
import '../globals.dart';
16
import 'android_sdk.dart';
17
import 'android_studio.dart' as android_studio;
18

19 20
AndroidWorkflow get androidWorkflow => context.putIfAbsent(AndroidWorkflow, () => new AndroidWorkflow());

21 22
class AndroidWorkflow extends DoctorValidator implements Workflow {
  AndroidWorkflow() : super('Android toolchain - develop for Android devices');
23

24
  @override
25 26
  bool get appliesToHostPlatform => true;

27
  @override
28 29
  bool get canListDevices => getAdbPath(androidSdk) != null;

30
  @override
31
  bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed().isEmpty;
32

33 34 35 36
  static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME';
  static const String _kJavaExecutable = 'java';
  static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';

37
  /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
38
  static String _findJavaBinary() {
39

40 41 42
    if (android_studio.javaPath != null)
      return fs.path.join(android_studio.javaPath, 'bin', 'java');

43 44 45 46 47 48 49 50 51 52 53 54 55
    final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
    if (javaHomeEnv != null) {
      // Trust JAVA_HOME.
      return fs.path.join(javaHomeEnv, 'bin', 'java');
    }

    // MacOS specific logic to avoid popping up a dialog window.
    // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
    if (platform.isMacOS) {
      try {
        final String javaHomeOutput = runCheckedSync(<String>['/usr/libexec/java_home'], hideStdout: true);
        if (javaHomeOutput != null) {
          final List<String> javaHomeOutputSplit = javaHomeOutput.split('\n');
56
          if ((javaHomeOutputSplit != null) && (javaHomeOutputSplit.isNotEmpty)) {
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
            final String javaHome = javaHomeOutputSplit[0].trim();
            return fs.path.join(javaHome, 'bin', 'java');
          }
        }
      } catch (_) { /* ignore */ }
    }

    // Fallback to PATH based lookup.
    return os.which(_kJavaExecutable)?.path;
  }

  /// Returns false if we cannot determine the Java version or if the version
  /// is not compatible.
  bool _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) {
    if (!processManager.canRun(javaBinary)) {
      messages.add(new ValidationMessage.error('Cannot execute $javaBinary to determine the version'));
      return false;
    }
    String javaVersion;
    try {
      printTrace('java -version');
      final ProcessResult result = processManager.runSync(<String>[javaBinary, '-version']);
      if (result.exitCode == 0) {
        final List<String> versionLines = result.stderr.split('\n');
        javaVersion = versionLines.length >= 2 ? versionLines[1] : versionLines[0];
      }
    } catch (_) { /* ignore */ }
    if (javaVersion == null) {
      // Could not determine the java version.
      messages.add(new ValidationMessage.error('Could not determine java version'));
      return false;
    }
89
    messages.add(new ValidationMessage('Java version $javaVersion'));
90 91 92 93
    // TODO(johnmccutchan): Validate version.
    return true;
  }

94
  @override
95
  Future<ValidationResult> validate() async {
96
    final List<ValidationMessage> messages = <ValidationMessage>[];
97 98

    if (androidSdk == null) {
99
      // No Android SDK found.
100
      if (platform.environment.containsKey(kAndroidHome)) {
101
        final String androidHomeDir = platform.environment[kAndroidHome];
102 103
        messages.add(new ValidationMessage.error(
          '$kAndroidHome = $androidHomeDir\n'
104
          'but Android SDK not found at this location.'
105 106 107
        ));
      } else {
        messages.add(new ValidationMessage.error(
108
          'Unable to locate Android SDK.\n'
109
          'Install Android Studio from: https://developer.android.com/studio/index.html\n'
110
          'On first launch it will assist you in installing the Android SDK components.\n'
111 112
          '(or visit https://flutter.io/setup/#android-setup for detailed instructions).\n'
          'If Android SDK has been installed to a custom location, set \$$kAndroidHome to that location.'
113 114
        ));
      }
115

116 117
      return new ValidationResult(ValidationType.missing, messages);
    }
118

119
    messages.add(new ValidationMessage('Android SDK at ${androidSdk.directory}'));
120

121 122 123
    String sdkVersionText;
    if (androidSdk.latestVersion != null) {
      sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
124

125 126 127 128 129
      messages.add(new ValidationMessage(
        'Platform ${androidSdk.latestVersion.platformVersionName}, '
        'build-tools ${androidSdk.latestVersion.buildToolsVersionName}'
      ));
    }
130

131 132 133 134
    if (platform.environment.containsKey(kAndroidHome)) {
      final String androidHomeDir = platform.environment[kAndroidHome];
      messages.add(new ValidationMessage('$kAndroidHome = $androidHomeDir'));
    }
135

136
    final List<String> validationResult = androidSdk.validateSdkWellFormed();
137

138 139 140 141 142 143
    if (validationResult.isNotEmpty) {
      // Android SDK is not functional.
      messages.addAll(validationResult.map((String message) {
        return new ValidationMessage.error(message);
      }));
      messages.add(new ValidationMessage(
144 145
          'Try re-installing or updating your Android SDK,\n'
          'visit https://flutter.io/setup/#android-setup for detailed instructions.'));
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
      return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
    }

    // Now check for the JDK.
    final String javaBinary = _findJavaBinary();
    if (javaBinary == null) {
      messages.add(new ValidationMessage.error(
          'No Java Development Kit (JDK) found; You must have the environment '
          'variable JAVA_HOME set and the java binary in your PATH. '
          'You can download the JDK from $_kJdkDownload.'));
      return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
    }
    messages.add(new ValidationMessage('Java binary at: $javaBinary'));

    // Check JDK version.
    if (!_checkJavaVersion(javaBinary, messages)) {
      return new ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
163 164
    }

165 166
    // Success.
    return new ValidationResult(ValidationType.installed, messages, statusInfo: sdkVersionText);
167
  }
168 169 170 171 172 173 174 175 176 177 178 179 180

  /// Run the Android SDK manager tool in order to accept SDK licenses.
  static Future<bool> runLicenseManager() async {
    if (androidSdk == null) {
      printStatus('Unable to locate Android SDK.');
      return false;
    }

    // If we can locate Java, then add it to the path used to run the Android SDK manager.
    final Map<String, String> sdkManagerEnv = <String, String>{};
    final String javaBinary = _findJavaBinary();
    if (javaBinary != null) {
      sdkManagerEnv['PATH'] =
181
          fs.path.dirname(javaBinary) + os.pathVarSeparator + platform.environment['PATH'];
182 183
    }

184 185 186 187 188 189 190
    final String sdkManagerPath = fs.path.join(
        androidSdk.directory, 'tools', 'bin',
        platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager',
    );
    final Process process = await runCommand(
        <String>[sdkManagerPath, '--licenses'],
        environment: sdkManagerEnv,
191 192 193 194 195 196 197 198
    );
    stdout.addStream(process.stdout);
    stderr.addStream(process.stderr);
    process.stdin.addStream(stdin);

    final int exitCode = await process.exitCode;
    return exitCode == 0;
  }
199
}