3
3
// found in the LICENSE file.
4
4
5
5
import 'dart:async' ;
6
+ import 'dart:convert' ;
6
7
7
8
import '../base/common.dart' ;
8
9
import '../base/context.dart' ;
9
- import '../base/file_system.dart' ;
10
10
import '../base/io.dart' ;
11
- import '../base/os.dart' ;
12
11
import '../base/platform.dart' ;
13
12
import '../base/process.dart' ;
14
13
import '../base/process_manager.dart' ;
@@ -17,10 +16,20 @@ import '../base/version.dart';
17
16
import '../doctor.dart' ;
18
17
import '../globals.dart' ;
19
18
import 'android_sdk.dart' ;
20
- import 'android_studio.dart' as android_studio;
21
19
22
20
AndroidWorkflow get androidWorkflow => context.putIfAbsent (AndroidWorkflow , () => new AndroidWorkflow ());
23
21
22
+ enum LicensesAccepted {
23
+ none,
24
+ some,
25
+ all,
26
+ unknown,
27
+ }
28
+
29
+ final RegExp licenseCounts = new RegExp (r'(\d+) of (\d+) SDK package licenses? not accepted.' );
30
+ final RegExp licenseNotAccepted = new RegExp (r'licenses? not accepted' , caseSensitive: false );
31
+ final RegExp licenseAccepted = new RegExp (r'All SDK package licenses accepted.' );
32
+
24
33
class AndroidWorkflow extends DoctorValidator implements Workflow {
25
34
AndroidWorkflow () : super ('Android toolchain - develop for Android devices' );
26
35
@@ -33,41 +42,8 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
33
42
@override
34
43
bool get canLaunchDevices => androidSdk != null && androidSdk.validateSdkWellFormed ().isEmpty;
35
44
36
- static const String _kJavaHomeEnvironmentVariable = 'JAVA_HOME' ;
37
- static const String _kJavaExecutable = 'java' ;
38
45
static const String _kJdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/' ;
39
46
40
- /// First try Java bundled with Android Studio, then sniff JAVA_HOME, then fallback to PATH.
41
- static String _findJavaBinary () {
42
-
43
- if (android_studio.javaPath != null )
44
- return fs.path.join (android_studio.javaPath, 'bin' , 'java' );
45
-
46
- final String javaHomeEnv = platform.environment[_kJavaHomeEnvironmentVariable];
47
- if (javaHomeEnv != null ) {
48
- // Trust JAVA_HOME.
49
- return fs.path.join (javaHomeEnv, 'bin' , 'java' );
50
- }
51
-
52
- // MacOS specific logic to avoid popping up a dialog window.
53
- // See: http://stackoverflow.com/questions/14292698/how-do-i-check-if-the-java-jdk-is-installed-on-mac.
54
- if (platform.isMacOS) {
55
- try {
56
- final String javaHomeOutput = runCheckedSync (< String > ['/usr/libexec/java_home' ], hideStdout: true );
57
- if (javaHomeOutput != null ) {
58
- final List <String > javaHomeOutputSplit = javaHomeOutput.split ('\n ' );
59
- if ((javaHomeOutputSplit != null ) && (javaHomeOutputSplit.isNotEmpty)) {
60
- final String javaHome = javaHomeOutputSplit[0 ].trim ();
61
- return fs.path.join (javaHome, 'bin' , 'java' );
62
- }
63
- }
64
- } catch (_) { /* ignore */ }
65
- }
66
-
67
- // Fallback to PATH based lookup.
68
- return os.which (_kJavaExecutable)? .path;
69
- }
70
-
71
47
/// Returns false if we cannot determine the Java version or if the version
72
48
/// is not compatible.
73
49
bool _checkJavaVersion (String javaBinary, List <ValidationMessage > messages) {
@@ -154,7 +130,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
154
130
}
155
131
156
132
// Now check for the JDK.
157
- final String javaBinary = _findJavaBinary ();
133
+ final String javaBinary = AndroidSdk . findJavaBinary ();
158
134
if (javaBinary == null ) {
159
135
messages.add (new ValidationMessage .error (
160
136
'No Java Development Kit (JDK) found; You must have the environment '
@@ -169,25 +145,66 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
169
145
return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
170
146
}
171
147
148
+ // Check for licenses.
149
+ switch (await licensesAccepted) {
150
+ case LicensesAccepted .all:
151
+ messages.add (new ValidationMessage ('All Android licenses accepted.' ));
152
+ break ;
153
+ case LicensesAccepted .some:
154
+ messages.add (new ValidationMessage .hint ('Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses' ));
155
+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
156
+ case LicensesAccepted .none:
157
+ messages.add (new ValidationMessage .error ('Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses' ));
158
+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
159
+ case LicensesAccepted .unknown:
160
+ messages.add (new ValidationMessage .error ('Android license status unknown.' ));
161
+ return new ValidationResult (ValidationType .partial, messages, statusInfo: sdkVersionText);
162
+ }
163
+
172
164
// Success.
173
165
return new ValidationResult (ValidationType .installed, messages, statusInfo: sdkVersionText);
174
166
}
175
167
168
+ Future <LicensesAccepted > get licensesAccepted async {
169
+ LicensesAccepted status = LicensesAccepted .unknown;
170
+
171
+ void _onLine (String line) {
172
+ if (licenseAccepted.hasMatch (line)) {
173
+ status = LicensesAccepted .all;
174
+ } else if (licenseCounts.hasMatch (line)) {
175
+ final Match match = licenseCounts.firstMatch (line);
176
+ if (match.group (1 ) != match.group (2 )) {
177
+ status = LicensesAccepted .some;
178
+ } else {
179
+ status = LicensesAccepted .none;
180
+ }
181
+ } else if (licenseNotAccepted.hasMatch (line)) {
182
+ // In case the format changes, a more general match will keep doctor
183
+ // mostly working.
184
+ status = LicensesAccepted .none;
185
+ }
186
+ }
187
+
188
+ final Process process = await runCommand (< String > [androidSdk.sdkManagerPath, '--licenses' ], environment: androidSdk.sdkManagerEnv);
189
+ process.stdin.write ('n\n ' );
190
+ final Future <void > output = process.stdout.transform (const Utf8Decoder (allowMalformed: true )).transform (const LineSplitter ()).listen (_onLine).asFuture <void >(null );
191
+ final Future <void > errors = process.stderr.transform (const Utf8Decoder (allowMalformed: true )).transform (const LineSplitter ()).listen (_onLine).asFuture <void >(null );
192
+ try {
193
+ await Future .wait <void >(< Future <void >> [output, errors]).timeout (const Duration (seconds: 30 ));
194
+ } catch (TimeoutException ) {
195
+ printTrace ('Intentionally killing ${androidSdk .sdkManagerPath }' );
196
+ processManager.killPid (process.pid);
197
+ }
198
+ return status;
199
+ }
200
+
176
201
/// Run the Android SDK manager tool in order to accept SDK licenses.
177
202
static Future <bool > runLicenseManager () async {
178
203
if (androidSdk == null ) {
179
204
printStatus ('Unable to locate Android SDK.' );
180
205
return false ;
181
206
}
182
207
183
- // If we can locate Java, then add it to the path used to run the Android SDK manager.
184
- final Map <String , String > sdkManagerEnv = < String , String > {};
185
- final String javaBinary = _findJavaBinary ();
186
- if (javaBinary != null ) {
187
- sdkManagerEnv['PATH' ] =
188
- fs.path.dirname (javaBinary) + os.pathVarSeparator + platform.environment['PATH' ];
189
- }
190
-
191
208
if (! processManager.canRun (androidSdk.sdkManagerPath))
192
209
throwToolExit (
193
210
'Android sdkmanager tool not found.\n '
@@ -205,7 +222,7 @@ class AndroidWorkflow extends DoctorValidator implements Workflow {
205
222
206
223
final Process process = await runCommand (
207
224
< String > [androidSdk.sdkManagerPath, '--licenses' ],
208
- environment: sdkManagerEnv,
225
+ environment: androidSdk. sdkManagerEnv,
209
226
);
210
227
211
228
waitGroup <Null >(< Future <Null >> [
0 commit comments