1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:file/memory.dart';
6import 'package:file_testing/file_testing.dart';
7import 'package:flutter_tools/src/android/gradle_errors.dart';
8import 'package:flutter_tools/src/android/gradle_utils.dart';
9import 'package:flutter_tools/src/android/java.dart';
10import 'package:flutter_tools/src/base/bot_detector.dart';
11import 'package:flutter_tools/src/base/file_system.dart';
12import 'package:flutter_tools/src/base/logger.dart';
13import 'package:flutter_tools/src/base/platform.dart';
14import 'package:flutter_tools/src/base/terminal.dart';
15import 'package:flutter_tools/src/project.dart';
16import 'package:test/fake.dart';
17
18import '../../src/common.dart';
19import '../../src/context.dart';
20import '../../src/fake_process_manager.dart';
21import '../../src/fakes.dart';
22
23void main() {
24 late FileSystem fileSystem;
25 late FakeProcessManager processManager;
26
27 setUp(() {
28 fileSystem = MemoryFileSystem.test();
29 processManager = FakeProcessManager.empty();
30 });
31
32 group('gradleErrors', () {
33 testWithoutContext('list of errors', () {
34 // If you added a new Gradle error, please update this test.
35 expect(
36 gradleErrors,
37 equals(<GradleHandledError>[
38 licenseNotAcceptedHandler,
39 networkErrorHandler,
40 permissionDeniedErrorHandler,
41 flavorUndefinedHandler,
42 r8DexingBugInAgp73Handler,
43 minSdkVersionHandler,
44 transformInputIssueHandler,
45 lockFileDepMissingHandler,
46 minCompileSdkVersionHandler,
47 incompatibleJavaAndAgpVersionsHandler,
48 outdatedGradleHandler,
49 sslExceptionHandler,
50 zipExceptionHandler,
51 incompatibleJavaAndGradleVersionsHandler,
52 remoteTerminatedHandshakeHandler,
53 couldNotOpenCacheDirectoryHandler,
54 incompatibleCompileSdk35AndAgpVersionHandler,
55 usageOfV1EmbeddingReferencesHandler,
56 jlinkErrorWithJava21AndSourceCompatibility,
57 missingNdkSourcePropertiesFile,
58 incompatibleKotlinVersionHandler,
59 ]),
60 );
61 });
62 });
63
64 group('network errors', () {
65 testUsingContext(
66 'retries if gradle fails while downloading',
67 () async {
68 const errorMessage = r'''
69Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle.org/distributions/gradle-4.1.1-all.zip
70at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1872)
71at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
72at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
73at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
74at org.gradle.wrapper.Download.download(Download.java:44)
75at org.gradle.wrapper.Install$1.call(Install.java:61)
76at org.gradle.wrapper.Install$1.call(Install.java:48)
77at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
78at org.gradle.wrapper.Install.createDist(Install.java:48)
79at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
80at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
81
82 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
83 expect(
84 await networkErrorHandler.handler(
85 line: '',
86 project: FakeFlutterProject(),
87 usesAndroidX: true,
88 ),
89 equals(GradleBuildStatus.retry),
90 );
91
92 expect(
93 testLogger.errorText,
94 contains('Gradle threw an error while downloading artifacts from the network.'),
95 );
96 },
97 overrides: <Type, Generator>{
98 FileSystem: () => fileSystem,
99 ProcessManager: () => processManager,
100 },
101 );
102
103 testUsingContext('retries if remote host terminated ssl handshake', () async {
104 const errorMessage = r'''
105Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
106 at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1696)
107 at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1514)
108 at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1416)
109 at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:456)
110 at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:427)
111 at java.base/sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:572)
112 at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:197)
113 at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2783)
114 at java.base/sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2695)
115 at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1854)
116 at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
117 at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
118 at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
119 at org.gradle.wrapper.Download.download(Download.java:44)
120 at org.gradle.wrapper.Install$1.call(Install.java:61)
121 at org.gradle.wrapper.Install$1.call(Install.java:48)
122 at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
123 at org.gradle.wrapper.Install.createDist(Install.java:48)
124 at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
125 at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)
126Caused by: java.io.EOFException: SSL peer shut down incorrectly
127 at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:483)
128 at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472)
129 at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160)
130 at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
131 at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1506)''';
132
133 expect(formatTestErrorMessage(errorMessage, remoteTerminatedHandshakeHandler), isTrue);
134 expect(
135 await remoteTerminatedHandshakeHandler.handler(
136 line: '',
137 project: FakeFlutterProject(),
138 usesAndroidX: true,
139 ),
140 equals(GradleBuildStatus.retry),
141 );
142
143 expect(
144 testLogger.errorText,
145 contains('Gradle threw an error while downloading artifacts from the network.'),
146 );
147 });
148
149 testUsingContext(
150 'retries if gradle fails downloading with proxy error',
151 () async {
152 const errorMessage = r'''
153Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request"
154at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2124)
155at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183)
156at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1546)
157at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
158at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
159at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
160at org.gradle.wrapper.Download.download(Download.java:44)
161at org.gradle.wrapper.Install$1.call(Install.java:61)
162at org.gradle.wrapper.Install$1.call(Install.java:48)
163at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
164at org.gradle.wrapper.Install.createDist(Install.java:48)
165at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
166at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
167
168 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
169 expect(
170 await networkErrorHandler.handler(
171 line: '',
172 project: FakeFlutterProject(),
173 usesAndroidX: true,
174 ),
175 equals(GradleBuildStatus.retry),
176 );
177
178 expect(
179 testLogger.errorText,
180 contains('Gradle threw an error while downloading artifacts from the network.'),
181 );
182 },
183 overrides: <Type, Generator>{
184 FileSystem: () => fileSystem,
185 ProcessManager: () => processManager,
186 },
187 );
188
189 testUsingContext(
190 'retries if gradle fails downloading with bad gateway error',
191 () async {
192 const errorMessage = r'''
193Exception in thread "main" java.io.IOException: Server returned HTTP response code: 502 for URL: https://objects.githubusercontent.com/github-production-release-asset-2e65be/696192900/1e77bbfb-4cde-4376-92ea-fc4ff57b8362?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=FFFF%2F20231220%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20231220T160553Z&X-Amz-Expires=300&X-Amz-Signature=ffff&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=696192900&response-content-disposition=attachment%3B%20filename%3Dgradle-8.2.1-all.zip&response-content-type=application%2Foctet-stream
194at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1997)
195at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
196at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:224)
197at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
198at org.gradle.wrapper.Download.download(Download.java:44)
199at org.gradle.wrapper.Install$1.call(Install.java:61)
200at org.gradle.wrapper.Install$1.call(Install.java:48)
201at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
202at org.gradle.wrapper.Install.createDist(Install.java:48)
203at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
204at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
205
206 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
207 expect(
208 await networkErrorHandler.handler(
209 line: '',
210 project: FakeFlutterProject(),
211 usesAndroidX: true,
212 ),
213 equals(GradleBuildStatus.retry),
214 );
215
216 expect(
217 testLogger.errorText,
218 contains('Gradle threw an error while downloading artifacts from the network.'),
219 );
220 },
221 overrides: <Type, Generator>{
222 FileSystem: () => fileSystem,
223 ProcessManager: () => processManager,
224 },
225 );
226
227 testUsingContext(
228 'retries if gradle times out waiting for exclusive access to zip',
229 () async {
230 const errorMessage = '''
231Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached waiting for exclusive access to file: /User/documents/gradle-5.6.2-all.zip
232 at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:61)
233 at org.gradle.wrapper.Install.createDist(Install.java:48)
234 at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
235 at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
236
237 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
238 expect(
239 await networkErrorHandler.handler(
240 line: '',
241 project: FakeFlutterProject(),
242 usesAndroidX: true,
243 ),
244 equals(GradleBuildStatus.retry),
245 );
246
247 expect(
248 testLogger.errorText,
249 contains('Gradle threw an error while downloading artifacts from the network.'),
250 );
251 },
252 overrides: <Type, Generator>{
253 FileSystem: () => fileSystem,
254 ProcessManager: () => processManager,
255 },
256 );
257
258 testUsingContext(
259 'retries if remote host closes connection',
260 () async {
261 const errorMessage = r'''
262Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
263Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
264 at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:994)
265 at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
266 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
267 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
268 at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
269 at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
270 at sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2729)
271 at sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2641)
272 at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1824)
273 at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
274 at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
275 at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
276 at org.gradle.wrapper.Download.download(Download.java:44)
277 at org.gradle.wrapper.Install$1.call(Install.java:61)
278 at org.gradle.wrapper.Install$1.call(Install.java:48)
279 at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
280 at org.gradle.wrapper.Install.createDist(Install.java:48)
281 at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
282 at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
283
284 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
285 expect(
286 await networkErrorHandler.handler(
287 line: '',
288 project: FakeFlutterProject(),
289 usesAndroidX: true,
290 ),
291 equals(GradleBuildStatus.retry),
292 );
293
294 expect(
295 testLogger.errorText,
296 contains('Gradle threw an error while downloading artifacts from the network.'),
297 );
298 },
299 overrides: <Type, Generator>{
300 FileSystem: () => fileSystem,
301 ProcessManager: () => processManager,
302 },
303 );
304
305 testUsingContext(
306 'retries if file opening fails',
307 () async {
308 const errorMessage = r'''
309Downloading https://services.gradle.org/distributions/gradle-3.5.0-all.zip
310Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle-dn.com/distributions/gradle-3.5.0-all.zip
311 at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1890)
312 at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
313 at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
314 at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
315 at org.gradle.wrapper.Download.download(Download.java:44)
316 at org.gradle.wrapper.Install$1.call(Install.java:61)
317 at org.gradle.wrapper.Install$1.call(Install.java:48)
318 at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
319 at org.gradle.wrapper.Install.createDist(Install.java:48)
320 at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
321 at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
322
323 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
324 expect(
325 await networkErrorHandler.handler(
326 line: '',
327 project: FakeFlutterProject(),
328 usesAndroidX: true,
329 ),
330 equals(GradleBuildStatus.retry),
331 );
332
333 expect(
334 testLogger.errorText,
335 contains('Gradle threw an error while downloading artifacts from the network.'),
336 );
337 },
338 overrides: <Type, Generator>{
339 FileSystem: () => fileSystem,
340 ProcessManager: () => processManager,
341 },
342 );
343
344 testUsingContext(
345 'retries if the connection is reset',
346 () async {
347 const errorMessage = r'''
348Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
349Exception in thread "main" java.net.SocketException: Connection reset
350 at java.net.SocketInputStream.read(SocketInputStream.java:210)
351 at java.net.SocketInputStream.read(SocketInputStream.java:141)
352 at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
353 at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:593)
354 at sun.security.ssl.InputRecord.read(InputRecord.java:532)
355 at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
356 at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
357 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
358 at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
359 at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
360 at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
361 at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
362 at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
363 at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
364 at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
365 at org.gradle.wrapper.Download.download(Download.java:44)
366 at org.gradle.wrapper.Install$1.call(Install.java:61)
367 at org.gradle.wrapper.Install$1.call(Install.java:48)
368 at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
369 at org.gradle.wrapper.Install.createDist(Install.java:48)
370 at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
371 at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
372
373 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
374 expect(
375 await networkErrorHandler.handler(
376 line: '',
377 project: FakeFlutterProject(),
378 usesAndroidX: true,
379 ),
380 equals(GradleBuildStatus.retry),
381 );
382
383 expect(
384 testLogger.errorText,
385 contains('Gradle threw an error while downloading artifacts from the network.'),
386 );
387 },
388 overrides: <Type, Generator>{
389 FileSystem: () => fileSystem,
390 ProcessManager: () => processManager,
391 },
392 );
393
394 testUsingContext(
395 'retries if Gradle could not get a resource',
396 () async {
397 const errorMessage = '''
398A problem occurred configuring root project 'android'.
399> Could not resolve all artifacts for configuration ':classpath'.
400 > Could not resolve net.sf.proguard:proguard-gradle:6.0.3.
401 Required by:
402 project : > com.android.tools.build:gradle:3.3.0
403 > Could not resolve net.sf.proguard:proguard-gradle:6.0.3.
404 > Could not parse POM https://jcenter.bintray.com/net/sf/proguard/proguard-gradle/6.0.3/proguard-gradle-6.0.3.pom
405 > Could not resolve net.sf.proguard:proguard-parent:6.0.3.
406 > Could not resolve net.sf.proguard:proguard-parent:6.0.3.
407 > Could not get resource 'https://jcenter.bintray.com/net/sf/proguard/proguard-parent/6.0.3/proguard-parent-6.0.3.pom'.
408 > Could not GET 'https://jcenter.bintray.com/net/sf/proguard/proguard-parent/6.0.3/proguard-parent-6.0.3.pom'. Received status code 504 from server: Gateway Time-out''';
409
410 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
411 expect(
412 await networkErrorHandler.handler(
413 line: '',
414 project: FakeFlutterProject(),
415 usesAndroidX: true,
416 ),
417 equals(GradleBuildStatus.retry),
418 );
419
420 expect(
421 testLogger.errorText,
422 contains('Gradle threw an error while downloading artifacts from the network.'),
423 );
424 },
425 overrides: <Type, Generator>{
426 FileSystem: () => fileSystem,
427 ProcessManager: () => processManager,
428 },
429 );
430
431 testUsingContext(
432 'retries if Gradle could not get a resource (non-Gateway)',
433 () async {
434 const errorMessage = '''
435* Error running Gradle:
436Exit code 1 from: /home/travis/build/flutter/flutter sdk/examples/flutter_gallery/android/gradlew app:properties:
437Starting a Gradle Daemon (subsequent builds will be faster)
438Picked up _JAVA_OPTIONS: -Xmx2048m -Xms512m
439FAILURE: Build failed with an exception.
440* What went wrong:
441A problem occurred configuring root project 'android'.
442> Could not resolve all files for configuration ':classpath'.
443 > Could not resolve com.android.tools.build:gradle:3.1.2.
444 Required by:
445 project :
446 > Could not resolve com.android.tools.build:gradle:3.1.2.
447 > Could not get resource 'https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.2/gradle-3.1.2.pom'.
448 > Could not GET 'https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.1.2/gradle-3.1.2.pom'.
449 > Remote host closed connection during handshake''';
450
451 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
452 expect(
453 await networkErrorHandler.handler(
454 line: '',
455 project: FakeFlutterProject(),
456 usesAndroidX: true,
457 ),
458 equals(GradleBuildStatus.retry),
459 );
460
461 expect(
462 testLogger.errorText,
463 contains('Gradle threw an error while downloading artifacts from the network.'),
464 );
465 },
466 overrides: <Type, Generator>{
467 FileSystem: () => fileSystem,
468 ProcessManager: () => processManager,
469 },
470 );
471
472 testUsingContext(
473 'retries if connection times out',
474 () async {
475 const errorMessage = r'''
476Exception in thread "main" java.net.ConnectException: Connection timed out
477java.base/sun.nio.ch.Net.connect0(Native Method)
478 at java.base/sun.nio.ch.Net.connect(Net.java:579)
479 at java.base/sun.nio.ch.Net.connect(Net.java:568)
480 at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:588)
481 at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327)
482 at java.base/java.net.Socket.connect(Socket.java:633)
483 at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:299)
484 at java.base/sun.security.ssl.BaseSSLSocketImpl.connect(BaseSSLSocketImpl.java:174)
485 at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:183)
486 at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:498)
487 at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:603)
488 at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:266)
489 at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380)''';
490
491 expect(formatTestErrorMessage(errorMessage, networkErrorHandler), isTrue);
492 expect(
493 await networkErrorHandler.handler(
494 line: '',
495 project: FakeFlutterProject(),
496 usesAndroidX: true,
497 ),
498 equals(GradleBuildStatus.retry),
499 );
500
501 expect(
502 testLogger.errorText,
503 contains('Gradle threw an error while downloading artifacts from the network.'),
504 );
505 },
506 overrides: <Type, Generator>{
507 FileSystem: () => fileSystem,
508 ProcessManager: () => processManager,
509 },
510 );
511 });
512
513 group('permission errors', () {
514 testUsingContext('throws toolExit if gradle is missing execute permissions', () async {
515 const errorMessage = '''
516Permission denied
517Command: /home/android/gradlew assembleRelease
518''';
519 expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue);
520 expect(
521 await permissionDeniedErrorHandler.handler(
522 usesAndroidX: true,
523 line: '',
524 project: FakeFlutterProject(),
525 ),
526 equals(GradleBuildStatus.exit),
527 );
528
529 expect(testLogger.statusText, contains('Gradle does not have execution permission.'));
530 expect(
531 testLogger.statusText,
532 contains(
533 '\n'
534 '┌─ Flutter Fix ───────────────────────────────────────────────────────────────────────────────────┐\n'
535 '│ [!] Gradle does not have execution permission. │\n'
536 '│ You should change the ownership of the project directory to your user, or move the project to a │\n'
537 '│ directory with execute permissions. │\n'
538 '└─────────────────────────────────────────────────────────────────────────────────────────────────┘\n',
539 ),
540 );
541 });
542
543 testUsingContext('pattern', () async {
544 const errorMessage = '''
545Permission denied
546Command: /home/android/gradlew assembleRelease
547''';
548 expect(formatTestErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue);
549 });
550
551 testUsingContext('handler', () async {
552 expect(
553 await permissionDeniedErrorHandler.handler(
554 usesAndroidX: true,
555 line: '',
556 project: FakeFlutterProject(),
557 ),
558 equals(GradleBuildStatus.exit),
559 );
560
561 expect(testLogger.statusText, contains('Gradle does not have execution permission.'));
562 expect(
563 testLogger.statusText,
564 contains(
565 '\n'
566 '┌─ Flutter Fix ───────────────────────────────────────────────────────────────────────────────────┐\n'
567 '│ [!] Gradle does not have execution permission. │\n'
568 '│ You should change the ownership of the project directory to your user, or move the project to a │\n'
569 '│ directory with execute permissions. │\n'
570 '└─────────────────────────────────────────────────────────────────────────────────────────────────┘\n',
571 ),
572 );
573 });
574 });
575
576 group('license not accepted', () {
577 testWithoutContext('pattern', () {
578 expect(
579 licenseNotAcceptedHandler.test(
580 'You have not accepted the license agreements of the following SDK components',
581 ),
582 isTrue,
583 );
584 });
585
586 testUsingContext('handler', () async {
587 await licenseNotAcceptedHandler.handler(
588 line:
589 'You have not accepted the license agreements of the following SDK components: [foo, bar]',
590 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
591 usesAndroidX: true,
592 );
593
594 expect(
595 testLogger.statusText,
596 contains(
597 '\n'
598 '┌─ Flutter Fix ─────────────────────────────────────────────────────────────────────────────────┐\n'
599 '│ [!] Unable to download needed Android SDK components, as the following licenses have not been │\n'
600 '│ accepted: foo, bar │\n'
601 '│ │\n'
602 '│ To resolve this, please run the following command in a Terminal: │\n'
603 '│ flutter doctor --android-licenses │\n'
604 '└───────────────────────────────────────────────────────────────────────────────────────────────┘\n',
605 ),
606 );
607 });
608 });
609
610 group('flavor undefined', () {
611 testWithoutContext('pattern', () {
612 expect(
613 flavorUndefinedHandler.test('Task assembleFooRelease not found in root project.'),
614 isTrue,
615 );
616 expect(
617 flavorUndefinedHandler.test('Task assembleBarRelease not found in root project.'),
618 isTrue,
619 );
620 expect(flavorUndefinedHandler.test('Task assembleBar not found in root project.'), isTrue);
621 expect(
622 flavorUndefinedHandler.test('Task assembleBar_foo not found in root project.'),
623 isTrue,
624 );
625 });
626
627 testUsingContext(
628 'handler - with flavor',
629 () async {
630 processManager.addCommand(
631 const FakeCommand(
632 command: <String>['gradlew', 'app:tasks', '--all', '--console=auto'],
633 stdout: '''
634assembleRelease
635assembleFlavor1
636assembleFlavor1Release
637assembleFlavor_2
638assembleFlavor_2Release
639assembleDebug
640assembleProfile
641assembles
642assembleFooTest
643 ''',
644 ),
645 );
646
647 await flavorUndefinedHandler.handler(
648 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
649 usesAndroidX: true,
650 line: '',
651 );
652
653 expect(
654 testLogger.statusText,
655 contains(
656 'Gradle project does not define a task suitable '
657 'for the requested build.',
658 ),
659 );
660 expect(
661 testLogger.statusText,
662 contains(
663 '\n'
664 '┌─ Flutter Fix ───────────────────────────────────────────────────────────────────────────────────┐\n'
665 '│ [!] Gradle project does not define a task suitable for the requested build. │\n'
666 '│ │\n'
667 '│ The /android/app/build.gradle file defines product flavors: flavor1, flavor_2. You must specify │\n'
668 '│ a --flavor option to select one of them. │\n'
669 '└─────────────────────────────────────────────────────────────────────────────────────────────────┘\n',
670 ),
671 );
672 expect(processManager, hasNoRemainingExpectations);
673 },
674 overrides: <Type, Generator>{
675 Java: () => FakeJava(),
676 GradleUtils: () => FakeGradleUtils(),
677 Platform: () => fakePlatform('android'),
678 FileSystem: () => fileSystem,
679 ProcessManager: () => processManager,
680 },
681 );
682
683 testUsingContext(
684 'handler - without flavor',
685 () async {
686 processManager.addCommand(
687 const FakeCommand(
688 command: <String>['gradlew', 'app:tasks', '--all', '--console=auto'],
689 stdout: '''
690assembleRelease
691assembleDebug
692assembleProfile
693 ''',
694 ),
695 );
696
697 await flavorUndefinedHandler.handler(
698 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
699 usesAndroidX: true,
700 line: '',
701 );
702
703 expect(
704 testLogger.statusText,
705 contains(
706 '\n'
707 '┌─ Flutter Fix ─────────────────────────────────────────────────────────────────────────────────┐\n'
708 '│ [!] Gradle project does not define a task suitable for the requested build. │\n'
709 '│ │\n'
710 '│ The /android/app/build.gradle file does not define any custom product flavors. You cannot use │\n'
711 '│ the --flavor option. │\n'
712 '└───────────────────────────────────────────────────────────────────────────────────────────────┘\n',
713 ),
714 );
715 expect(processManager, hasNoRemainingExpectations);
716 },
717 overrides: <Type, Generator>{
718 Java: () => FakeJava(),
719 GradleUtils: () => FakeGradleUtils(),
720 Platform: () => fakePlatform('android'),
721 FileSystem: () => fileSystem,
722 ProcessManager: () => processManager,
723 },
724 );
725 });
726
727 group('higher minSdkVersion', () {
728 const stdoutLine =
729 'uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [:webview_flutter] /tmp/cirrus-ci-build/all_plugins/build/webview_flutter/intermediates/library_manifest/release/AndroidManifest.xml as the library might be using APIs not available in 21';
730
731 testWithoutContext('pattern', () {
732 expect(minSdkVersionHandler.test(stdoutLine), isTrue);
733 });
734
735 testUsingContext(
736 'suggestion',
737 () async {
738 await minSdkVersionHandler.handler(
739 line: stdoutLine,
740 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
741 usesAndroidX: true,
742 );
743
744 expect(
745 testLogger.statusText,
746 contains(
747 '\n'
748 '┌─ Flutter Fix ─────────────────────────────────────────────────────────────────────────────────┐\n'
749 '│ The plugin webview_flutter requires a higher Android SDK version. │\n'
750 '│ Fix this issue by adding the following to the file /android/app/build.gradle: │\n'
751 '│ android { │\n'
752 '│ defaultConfig { │\n'
753 '│ minSdkVersion 21 │\n'
754 '│ } │\n'
755 '│ } │\n'
756 '│ │\n'
757 '│ Following this change, your app will not be available to users running Android SDKs below 21. │\n'
758 '│ Consider searching for a version of this plugin that supports these lower versions of the │\n'
759 '│ Android SDK instead. │\n'
760 '│ For more information, see: https://flutter.dev/to/review-gradle-config │\n'
761 '└───────────────────────────────────────────────────────────────────────────────────────────────┘\n',
762 ),
763 );
764 },
765 overrides: <Type, Generator>{
766 GradleUtils: () => FakeGradleUtils(),
767 Platform: () => fakePlatform('android'),
768 FileSystem: () => fileSystem,
769 ProcessManager: () => processManager,
770 },
771 );
772 });
773
774 // https://issuetracker.google.com/issues/141126614
775 group('transform input issue', () {
776 testWithoutContext('pattern', () {
777 expect(
778 transformInputIssueHandler.test('https://issuetracker.google.com/issues/158753935'),
779 isTrue,
780 );
781 });
782
783 testUsingContext(
784 'suggestion',
785 () async {
786 await transformInputIssueHandler.handler(
787 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
788 usesAndroidX: true,
789 line: '',
790 );
791
792 expect(
793 testLogger.statusText,
794 contains(
795 '\n'
796 '┌─ Flutter Fix ─────────────────────────────────────────────────────────────────┐\n'
797 '│ This issue appears to be https://github.com/flutter/flutter/issues/58247. │\n'
798 '│ Fix this issue by adding the following to the file /android/app/build.gradle: │\n'
799 '│ android { │\n'
800 '│ lintOptions { │\n'
801 '│ checkReleaseBuilds false │\n'
802 '│ } │\n'
803 '│ } │\n'
804 '└───────────────────────────────────────────────────────────────────────────────┘\n',
805 ),
806 );
807 },
808 overrides: <Type, Generator>{
809 GradleUtils: () => FakeGradleUtils(),
810 Platform: () => fakePlatform('android'),
811 FileSystem: () => fileSystem,
812 ProcessManager: () => processManager,
813 },
814 );
815 });
816
817 group('Dependency mismatch', () {
818 testWithoutContext('pattern', () {
819 expect(
820 lockFileDepMissingHandler.test(
821 '''
822* What went wrong:
823Execution failed for task ':app:generateDebugFeatureTransitiveDeps'.
824> Could not resolve all artifacts for configuration ':app:debugRuntimeClasspath'.
825 > Resolved 'androidx.lifecycle:lifecycle-common:2.2.0' which is not part of the dependency lock state
826 > Resolved 'androidx.customview:customview:1.0.0' which is not part of the dependency lock state''',
827 ),
828 isTrue,
829 );
830 });
831
832 testUsingContext(
833 'suggestion',
834 () async {
835 await lockFileDepMissingHandler.handler(
836 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
837 usesAndroidX: true,
838 line: '',
839 );
840
841 expect(
842 testLogger.statusText,
843 contains(
844 '\n'
845 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────┐\n'
846 '│ You need to update the lockfile, or disable Gradle dependency locking. │\n'
847 '│ To regenerate the lockfiles run: `./gradlew :generateLockfiles` in /android/build.gradle │\n'
848 '│ To remove dependency locking, remove the `dependencyLocking` from /android/build.gradle │\n'
849 '└──────────────────────────────────────────────────────────────────────────────────────────┘\n',
850 ),
851 );
852 },
853 overrides: <Type, Generator>{
854 GradleUtils: () => FakeGradleUtils(),
855 Platform: () => fakePlatform('android'),
856 FileSystem: () => fileSystem,
857 ProcessManager: () => processManager,
858 },
859 );
860 });
861
862 testUsingContext(
863 'generates correct gradle command for Unix-like environment',
864 () async {
865 await lockFileDepMissingHandler.handler(
866 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
867 usesAndroidX: true,
868 line: '',
869 );
870
871 expect(
872 testLogger.statusText,
873 contains(
874 '\n'
875 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────┐\n'
876 '│ You need to update the lockfile, or disable Gradle dependency locking. │\n'
877 '│ To regenerate the lockfiles run: `./gradlew :generateLockfiles` in /android/build.gradle │\n'
878 '│ To remove dependency locking, remove the `dependencyLocking` from /android/build.gradle │\n'
879 '└──────────────────────────────────────────────────────────────────────────────────────────┘\n'
880 '',
881 ),
882 );
883 },
884 overrides: <Type, Generator>{
885 GradleUtils: () => FakeGradleUtils(),
886 Platform: () => fakePlatform('linux'),
887 FileSystem: () => fileSystem,
888 ProcessManager: () => processManager,
889 },
890 );
891
892 testUsingContext(
893 'generates correct gradle command for windows environment',
894 () async {
895 await lockFileDepMissingHandler.handler(
896 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
897 usesAndroidX: true,
898 line: '',
899 );
900 expect(
901 testLogger.statusText,
902 contains(
903 '\n'
904 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────┐\n'
905 '│ You need to update the lockfile, or disable Gradle dependency locking. │\n'
906 '│ To regenerate the lockfiles run: `.\\gradlew.bat :generateLockfiles` in /android/build.gradle │\n'
907 '│ To remove dependency locking, remove the `dependencyLocking` from /android/build.gradle │\n'
908 '└──────────────────────────────────────────────────────────────────────────────────────────────┘\n'
909 '',
910 ),
911 );
912 },
913 overrides: <Type, Generator>{
914 GradleUtils: () => FakeGradleUtils(),
915 Platform: () => fakePlatform('windows'),
916 FileSystem: () => fileSystem,
917 ProcessManager: () => processManager,
918 },
919 );
920
921 group('Incompatible Kotlin version', () {
922 testWithoutContext('pattern', () {
923 expect(
924 incompatibleKotlinVersionHandler.test(
925 'Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.5.1, expected version is 1.1.15.',
926 ),
927 isTrue,
928 );
929 expect(
930 incompatibleKotlinVersionHandler.test(
931 "class 'kotlin.Unit' was compiled with an incompatible version of Kotlin.",
932 ),
933 isTrue,
934 );
935 });
936
937 testUsingContext(
938 'suggestion',
939 () async {
940 await incompatibleKotlinVersionHandler.handler(
941 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
942 usesAndroidX: true,
943 line: '',
944 );
945
946 expect(
947 testLogger.statusText,
948 contains(
949 '\n'
950 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────┐\n'
951 '│ [!] Your project requires a newer version of the Kotlin Gradle plugin. │\n'
952 '│ Find the latest version on https://kotlinlang.org/docs/releases.html#release-details, then │\n'
953 '│ update the │\n'
954 '│ version number of the plugin with id "org.jetbrains.kotlin.android" in the plugins block of │\n'
955 '│ /android/settings.gradle. │\n'
956 '│ │\n'
957 '│ Alternatively (if your project was created before Flutter 3.19), update │\n'
958 '│ /android/build.gradle │\n'
959 "│ ext.kotlin_version = '<latest-version>' │\n"
960 '└──────────────────────────────────────────────────────────────────────────────────────────────┘\n',
961 ),
962 );
963 },
964 overrides: <Type, Generator>{
965 GradleUtils: () => FakeGradleUtils(),
966 Platform: () => fakePlatform('android'),
967 FileSystem: () => fileSystem,
968 ProcessManager: () => processManager,
969 },
970 );
971 });
972
973 group('Bump Gradle', () {
974 const errorMessage = '''
975A problem occurred evaluating project ':app'.
976> Failed to apply plugin [id 'kotlin-android']
977 > The current Gradle version 4.10.2 is not compatible with the Kotlin Gradle plugin. Please use Gradle 6.1.1 or newer, or the previous version of the Kotlin plugin.
978''';
979
980 testWithoutContext('pattern', () {
981 expect(outdatedGradleHandler.test(errorMessage), isTrue);
982 });
983
984 testUsingContext(
985 'suggestion',
986 () async {
987 await outdatedGradleHandler.handler(
988 line: errorMessage,
989 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
990 usesAndroidX: true,
991 );
992
993 expect(
994 testLogger.statusText,
995 contains(
996 '\n'
997 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────┐\n'
998 '│ [!] Your project needs to upgrade Gradle and the Android Gradle plugin. │\n'
999 '│ │\n'
1000 '│ To fix this issue, replace the following content: │\n'
1001 '│ /android/build.gradle: │\n'
1002 "│ - classpath 'com.android.tools.build:gradle:<current-version>' │\n"
1003 "│ + classpath 'com.android.tools.build:gradle:$templateAndroidGradlePluginVersion' │\n"
1004 '│ /android/gradle/wrapper/gradle-wrapper.properties: │\n'
1005 '│ - https://services.gradle.org/distributions/gradle--all.zip │\n'
1006 '│ + https://services.gradle.org/distributions/gradle-$templateDefaultGradleVersion-all.zip │\n'
1007 '└──────────────────────────────────────────────────────────────────────────────────┘\n',
1008 ),
1009 );
1010 },
1011 overrides: <Type, Generator>{
1012 GradleUtils: () => FakeGradleUtils(),
1013 Platform: () => fakePlatform('android'),
1014 FileSystem: () => fileSystem,
1015 ProcessManager: () => processManager,
1016 },
1017 );
1018 });
1019
1020 group('Required compileSdkVersion', () {
1021 const errorMessage = '''
1022Execution failed for task ':app:checkDebugAarMetadata'.
1023> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckAarMetadataWorkAction
1024 > One or more issues found when checking AAR metadata values:
1025
1026 The minCompileSdk (31) specified in a
1027 dependency's AAR metadata (META-INF/com/android/build/gradle/aar-metadata.properties)
1028 is greater than this module's compileSdkVersion (android-30).
1029 Dependency: androidx.window:window-java:1.0.0-beta04.
1030 AAR metadata file: ~/.gradle/caches/transforms-3/2adc32c5b3f24bed763d33fbfb203338/transformed/jetified-window-java-1.0.0-beta04/META-INF/com/android/build/gradle/aar-metadata.properties.
1031
1032 The minCompileSdk (31) specified in a
1033 dependency's AAR metadata (META-INF/com/android/build/gradle/aar-metadata.properties)
1034 is greater than this module's compileSdkVersion (android-30).
1035 Dependency: androidx.window:window:1.0.0-beta04.
1036 AAR metadata file: ~/.gradle/caches/transforms-3/88f7e476ef68cecca729426edff955b5/transformed/jetified-window-1.0.0-beta04/META-INF/com/android/build/gradle/aar-metadata.properties.
1037''';
1038
1039 testWithoutContext('pattern', () {
1040 expect(minCompileSdkVersionHandler.test(errorMessage), isTrue);
1041 });
1042
1043 testUsingContext(
1044 'suggestion',
1045 () async {
1046 await minCompileSdkVersionHandler.handler(
1047 line: errorMessage,
1048 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1049 usesAndroidX: true,
1050 );
1051
1052 expect(
1053 testLogger.statusText,
1054 contains(
1055 '\n'
1056 '┌─ Flutter Fix ──────────────────────────────────────────────────────────────────┐\n'
1057 '│ [!] Your project requires a higher compileSdk version. │\n'
1058 '│ Fix this issue by bumping the compileSdk version in /android/app/build.gradle: │\n'
1059 '│ android { │\n'
1060 '│ compileSdk 31 │\n'
1061 '│ } │\n'
1062 '└────────────────────────────────────────────────────────────────────────────────┘\n',
1063 ),
1064 );
1065 },
1066 overrides: <Type, Generator>{
1067 GradleUtils: () => FakeGradleUtils(),
1068 Platform: () => fakePlatform('android'),
1069 FileSystem: () => fileSystem,
1070 ProcessManager: () => processManager,
1071 },
1072 );
1073 });
1074
1075 group('incompatible java and android gradle plugin versions error', () {
1076 const errorMessage = '''
1077* What went wrong:
1078An exception occurred applying plugin request [id: 'com.android.application']
1079> Failed to apply plugin 'com.android.internal.application'.
1080 > Android Gradle plugin requires Java 17 to run. You are currently using Java 11.
1081 You can try some of the following options:
1082 - changing the IDE settings.
1083 - changing the JAVA_HOME environment variable.
1084 - changing `org.gradle.java.home` in `gradle.properties`.
1085''';
1086
1087 testWithoutContext('pattern', () {
1088 expect(incompatibleJavaAndAgpVersionsHandler.test(errorMessage), isTrue);
1089 });
1090
1091 testUsingContext(
1092 'suggestion',
1093 () async {
1094 await incompatibleJavaAndAgpVersionsHandler.handler(
1095 line: errorMessage,
1096 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1097 usesAndroidX: true,
1098 );
1099
1100 // Ensure the error notes the required Java version, the Java version currently used,
1101 // the android studio and android sdk installation link, the flutter command to set
1102 // the Java version Flutter uses, and the flutter doctor command.
1103 expect(
1104 testLogger.statusText,
1105 contains(
1106 'Android Gradle plugin requires Java 17 to run. You are currently using Java 11.',
1107 ),
1108 );
1109 expect(testLogger.statusText, contains('https://developer.android.com/studio/install'));
1110 expect(testLogger.statusText, contains('`flutter config --jdk-dir=“</path/to/jdk>“`'));
1111 expect(testLogger.statusText, contains('`flutter doctor --verbose`'));
1112 },
1113 overrides: <Type, Generator>{
1114 GradleUtils: () => FakeGradleUtils(),
1115 Platform: () => fakePlatform('android'),
1116 FileSystem: () => fileSystem,
1117 ProcessManager: () => processManager,
1118 },
1119 );
1120 });
1121
1122 group('SSLException', () {
1123 testWithoutContext('pattern', () {
1124 expect(
1125 sslExceptionHandler.test(r'''
1126Exception in thread "main" javax.net.ssl.SSLException: Tag mismatch!
1127at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:129)
1128at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:321)
1129at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:264)
1130at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:259)
1131at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:129)
1132at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1155)
1133at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1125)
1134at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:823)
1135at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:290)
1136at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
1137at java.base/sun.net.www.MeteredStream.read(MeteredStream.java:134)
1138at java.base/java.io.FilterInputStream.read(FilterInputStream.java:133)
1139at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3444)
1140at java.base/sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3437)
1141at org.gradle.wrapper.Download.downloadInternal(Download.java:62)
1142at org.gradle.wrapper.Download.download(Download.java:44)
1143at org.gradle.wrapper.Install$1.call(Install.java:61)
1144at org.gradle.wrapper.Install$1.call(Install.java:48)
1145at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
1146at org.gradle.wrapper.Install.createDist(Install.java:48)
1147at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
1148at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''),
1149 isTrue,
1150 );
1151
1152 expect(
1153 sslExceptionHandler.test(r'''
1154Caused by: javax.crypto.AEADBadTagException: Tag mismatch!
1155at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:580)
1156at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1049)
1157at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:985)
1158at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:491)
1159at java.base/javax.crypto.CipherSpi.bufferCrypt(CipherSpi.java:779)
1160at java.base/javax.crypto.CipherSpi.engineDoFinal(CipherSpi.java:730)
1161at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2497)
1162at java.base/sun.security.ssl.SSLCipher$T12GcmReadCipherGenerator$GcmReadCipher.decrypt(SSLCipher.java:1613)
1163at java.base/sun.security.ssl.SSLSocketInputRecord.decodeInputRecord(SSLSocketInputRecord.java:262)
1164at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:190)
1165at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:108)'''),
1166 isTrue,
1167 );
1168 });
1169
1170 testUsingContext(
1171 'suggestion',
1172 () async {
1173 final GradleBuildStatus status = await sslExceptionHandler.handler(
1174 project: FakeFlutterProject(),
1175 usesAndroidX: true,
1176 line: '',
1177 );
1178
1179 expect(status, GradleBuildStatus.retry);
1180 expect(
1181 testLogger.errorText,
1182 contains('Gradle threw an error while downloading artifacts from the network.'),
1183 );
1184 },
1185 overrides: <Type, Generator>{
1186 GradleUtils: () => FakeGradleUtils(),
1187 Platform: () => fakePlatform('android'),
1188 FileSystem: () => fileSystem,
1189 ProcessManager: () => processManager,
1190 },
1191 );
1192 });
1193
1194 group('Zip exception', () {
1195 testWithoutContext('pattern', () {
1196 expect(
1197 zipExceptionHandler.test(r'''
1198Exception in thread "main" java.util.zip.ZipException: error in opening zip file
1199at java.util.zip.ZipFile.open(Native Method)
1200at java.util.zip.ZipFile.(ZipFile.java:225)
1201at java.util.zip.ZipFile.(ZipFile.java:155)
1202at java.util.zip.ZipFile.(ZipFile.java:169)
1203at org.gradle.wrapper.Install.unzip(Install.java:214)
1204at org.gradle.wrapper.Install.access$600(Install.java:27)
1205at org.gradle.wrapper.Install$1.call(Install.java:74)
1206at org.gradle.wrapper.Install$1.call(Install.java:48)
1207at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
1208at org.gradle.wrapper.Install.createDist(Install.java:48)
1209at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
1210at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)'''),
1211 isTrue,
1212 );
1213 });
1214
1215 testUsingContext(
1216 'suggestion',
1217 () async {
1218 fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
1219
1220 final GradleBuildStatus result = await zipExceptionHandler.handler(
1221 project: FakeFlutterProject(),
1222 usesAndroidX: true,
1223 line: '',
1224 );
1225
1226 expect(result, equals(GradleBuildStatus.retry));
1227 expect(fileSystem.file('foo/.gradle/fizz.zip'), exists);
1228 expect(
1229 testLogger.errorText,
1230 contains('[!] Your .gradle directory under the home directory might be corrupted.\n'),
1231 );
1232 expect(testLogger.statusText, '');
1233 },
1234 overrides: <Type, Generator>{
1235 Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
1236 FileSystem: () => fileSystem,
1237 ProcessManager: () => processManager,
1238 BotDetector: () => const FakeBotDetector(false),
1239 },
1240 );
1241
1242 testUsingContext(
1243 'suggestion if running as bot',
1244 () async {
1245 fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
1246
1247 final GradleBuildStatus result = await zipExceptionHandler.handler(
1248 project: FakeFlutterProject(),
1249 usesAndroidX: true,
1250 line: '',
1251 );
1252
1253 expect(result, equals(GradleBuildStatus.retry));
1254 expect(fileSystem.file('foo/.gradle/fizz.zip'), isNot(exists));
1255
1256 expect(
1257 testLogger.errorText,
1258 contains('[!] Your .gradle directory under the home directory might be corrupted.\n'),
1259 );
1260 expect(testLogger.statusText, contains('Deleting foo/.gradle\n'));
1261 },
1262 overrides: <Type, Generator>{
1263 Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
1264 FileSystem: () => fileSystem,
1265 ProcessManager: () => processManager,
1266 BotDetector: () => const FakeBotDetector(true),
1267 },
1268 );
1269
1270 testUsingContext(
1271 'suggestion if stdin has terminal and user entered y',
1272 () async {
1273 fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
1274
1275 final GradleBuildStatus result = await zipExceptionHandler.handler(
1276 line: '',
1277 usesAndroidX: true,
1278 project: FakeFlutterProject(),
1279 );
1280
1281 expect(result, equals(GradleBuildStatus.retry));
1282 expect(fileSystem.file('foo/.gradle/fizz.zip'), isNot(exists));
1283 expect(
1284 testLogger.errorText,
1285 contains('[!] Your .gradle directory under the home directory might be corrupted.\n'),
1286 );
1287 expect(testLogger.statusText, contains('Deleting foo/.gradle\n'));
1288 },
1289 overrides: <Type, Generator>{
1290 Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
1291 FileSystem: () => fileSystem,
1292 ProcessManager: () => processManager,
1293 AnsiTerminal: () => _TestPromptTerminal('y'),
1294 BotDetector: () => const FakeBotDetector(false),
1295 },
1296 );
1297
1298 testUsingContext(
1299 'suggestion if stdin has terminal and user entered n',
1300 () async {
1301 fileSystem.file('foo/.gradle/fizz.zip').createSync(recursive: true);
1302
1303 final GradleBuildStatus result = await zipExceptionHandler.handler(
1304 line: '',
1305 usesAndroidX: true,
1306 project: FakeFlutterProject(),
1307 );
1308
1309 expect(result, equals(GradleBuildStatus.retry));
1310 expect(fileSystem.file('foo/.gradle/fizz.zip'), exists);
1311 expect(
1312 testLogger.errorText,
1313 contains('[!] Your .gradle directory under the home directory might be corrupted.\n'),
1314 );
1315 expect(testLogger.statusText, '');
1316 },
1317 overrides: <Type, Generator>{
1318 Platform: () => FakePlatform(environment: <String, String>{'HOME': 'foo/'}),
1319 FileSystem: () => fileSystem,
1320 ProcessManager: () => processManager,
1321 AnsiTerminal: () => _TestPromptTerminal('n'),
1322 BotDetector: () => const FakeBotDetector(false),
1323 },
1324 );
1325 });
1326
1327 group('incompatible java and gradle versions error', () {
1328 const errorMessage = '''
1329Could not compile build file '…/example/android/build.gradle'.
1330> startup failed:
1331 General error during conversion: Unsupported class file major version 61
1332 java.lang.IllegalArgumentException: Unsupported class file major version 61
1333''';
1334
1335 testWithoutContext('pattern', () {
1336 expect(incompatibleJavaAndGradleVersionsHandler.test(errorMessage), isTrue);
1337 });
1338
1339 testUsingContext(
1340 'suggestion',
1341 () async {
1342 await incompatibleJavaAndGradleVersionsHandler.handler(
1343 line: errorMessage,
1344 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1345 usesAndroidX: true,
1346 );
1347
1348 // Ensure the error notes the incompatible Gradle/AGP/Java versions, links to related resources,
1349 // and a portion of the path to where to change their gradle version.
1350 expect(
1351 testLogger.statusText,
1352 contains('Gradle version is incompatible with the Java version'),
1353 );
1354 expect(testLogger.statusText, contains('gradle-wrapper.properties'));
1355 expect(
1356 testLogger.statusText,
1357 contains('https://docs.gradle.org/current/userguide/compatibility.html#java'),
1358 );
1359 },
1360 overrides: <Type, Generator>{
1361 GradleUtils: () => FakeGradleUtils(),
1362 Platform: () => fakePlatform('android'),
1363 FileSystem: () => fileSystem,
1364 ProcessManager: () => processManager,
1365 },
1366 );
1367 });
1368
1369 testUsingContext(
1370 'couldNotOpenCacheDirectoryHandler',
1371 () async {
1372 final GradleBuildStatus status = await couldNotOpenCacheDirectoryHandler.handler(
1373 line: '''
1374FAILURE: Build failed with an exception.
1375
1376* Where:
1377Script '/Volumes/Work/s/w/ir/x/w/flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy' line: 276
1378
1379* What went wrong:
1380A problem occurred evaluating script.
1381> Failed to apply plugin class 'FlutterPlugin'.
1382 > Could not open cache directory 41rl0ui7kgmsyfwn97o2jypl6 (/Volumes/Work/s/w/ir/cache/gradle/caches/6.7/gradle-kotlin-dsl/41rl0ui7kgmsyfwn97o2jypl6).
1383 > Failed to create Jar file /Volumes/Work/s/w/ir/cache/gradle/caches/6.7/generated-gradle-jars/gradle-api-6.7.jar.''',
1384 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1385 usesAndroidX: true,
1386 );
1387 expect(testLogger.errorText, contains('Gradle threw an error while resolving dependencies'));
1388 expect(status, GradleBuildStatus.retry);
1389 },
1390 overrides: <Type, Generator>{
1391 GradleUtils: () => FakeGradleUtils(),
1392 Platform: () => fakePlatform('android'),
1393 FileSystem: () => fileSystem,
1394 ProcessManager: () => processManager,
1395 },
1396 );
1397
1398 testUsingContext(
1399 'compileSdk 35 and AGP < 8.1',
1400 () async {
1401 const errorExample = r'''
1402Execution failed for task ':app:bundleReleaseResources'.
1403> A failure occurred while executing com.android.build.gradle.internal.res.Aapt2ProcessResourcesRunnable
1404 > Android resource linking failed
1405 aapt2 E 08-19 15:06:26 76078 5921862 LoadedArsc.cpp:94] RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.
1406 aapt2 E 08-19 15:06:26 76078 5921862 ApkAssets.cpp:152] Failed to load resources table in APK '/Users/mackall/Library/Android/sdk/platforms/android-35/android.jar'.
1407 error: failed to load include path /Users/mackall/Library/Android/sdk/platforms/android-35/android.jar.
1408 ''';
1409
1410 await incompatibleCompileSdk35AndAgpVersionHandler.handler(
1411 line: errorExample,
1412 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1413 usesAndroidX: true,
1414 );
1415
1416 expect(
1417 testLogger.statusText,
1418 contains(
1419 '\n'
1420 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────────┐\n'
1421 '│ [!] Using compileSdk 35 requires Android Gradle Plugin (AGP) 8.1.0 or higher. │\n'
1422 '│ Please upgrade to a newer AGP version. The version of AGP that your project uses is likely │\n'
1423 '│ defined in: │\n'
1424 '│ /android/settings.gradle, │\n'
1425 '│ in the \'plugins\' closure (by the number following "com.android.application"). │\n'
1426 '│ Alternatively, if your project was created with an older version of the templates, it is likely │\n'
1427 '│ in the buildscript.dependencies closure of the top-level build.gradle: │\n'
1428 '│ /android/build.gradle, │\n'
1429 '│ as the number following "com.android.tools.build:gradle:". │\n'
1430 '│ │\n'
1431 '│ Finally, if you have a strong reason to avoid upgrading AGP, you can temporarily lower the │\n'
1432 '│ compileSdk version in the following file: │\n'
1433 '│ /android/app/build.gradle │\n'
1434 '└──────────────────────────────────────────────────────────────────────────────────────────────────┘\n'
1435 '',
1436 ),
1437 );
1438 },
1439 overrides: <Type, Generator>{
1440 GradleUtils: () => FakeGradleUtils(),
1441 Platform: () => fakePlatform('android'),
1442 FileSystem: () => fileSystem,
1443 ProcessManager: () => processManager,
1444 },
1445 );
1446
1447 testUsingContext(
1448 'AGP 7.3.0 R8 bug',
1449 () async {
1450 const errorExample = r'''
1451ERROR:/Users/mackall/.gradle/caches/transforms-3/bd2c84591857c6d4c308221ffece862e/transformed/jetified-media3-exoplayer-dash-1.4.0-runtime.jar: R8: com.android.tools.r8.internal.Y10: Unused argument with users in androidx
1452 ''';
1453
1454 await r8DexingBugInAgp73Handler.handler(
1455 line: errorExample,
1456 project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
1457 usesAndroidX: true,
1458 );
1459
1460 expect(
1461 testLogger.statusText,
1462 contains(
1463 '\n'
1464 '┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────────┐\n'
1465 '│ [!] Version 7.3 of the Android Gradle Plugin (AGP) uses a version of R8 that contains a bug │\n'
1466 '│ which causes this error (see more info at https://issuetracker.google.com/issues/242308990). │\n'
1467 '│ To fix this error, update to a newer version of AGP (at least 7.4.0). │\n'
1468 '│ │\n'
1469 '│ The version of AGP that your project uses is likely defined in: │\n'
1470 '│ /android/settings.gradle, │\n'
1471 '│ in the \'plugins\' closure (by the number following "com.android.application"). │\n'
1472 '│ Alternatively, if your project was created with an older version of the templates, it is likely │\n'
1473 '│ in the buildscript.dependencies closure of the top-level build.gradle: │\n'
1474 '│ /android/build.gradle, │\n'
1475 '│ as the number following "com.android.tools.build:gradle:". │\n'
1476 '└──────────────────────────────────────────────────────────────────────────────────────────────────┘\n'
1477 '',
1478 ),
1479 );
1480 },
1481 overrides: <Type, Generator>{
1482 GradleUtils: () => FakeGradleUtils(),
1483 Platform: () => fakePlatform('android'),
1484 FileSystem: () => fileSystem,
1485 ProcessManager: () => processManager,
1486 },
1487 );
1488
1489 testUsingContext(
1490 'Usage of removed v1 embedding references',
1491 () async {
1492 const errorExample = r'''
1493/Users/jesswon/.pub-cache/hosted/pub.dev/video_player_android-2.5.0/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java:42: error: cannot find symbol
1494 private VideoPlayerPlugin(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
1495 ^
1496 symbol: class Registrar
1497 location: interface PluginRegistry
14981 error
1499
1500FAILURE: Build failed with an exception.
1501 ''';
1502
1503 final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
1504 await usageOfV1EmbeddingReferencesHandler.handler(
1505 line: errorExample,
1506 project: project,
1507 usesAndroidX: true,
1508 );
1509
1510 // Main fix text.
1511 expect(
1512 testLogger.statusText,
1513 contains(
1514 "To fix this error, please upgrade your current package's dependencies to latest versions by",
1515 ),
1516 );
1517 expect(testLogger.statusText, contains('running `flutter pub upgrade`.'));
1518 // Text and link to file an issue.
1519 expect(
1520 testLogger.statusText,
1521 contains('If that does not work, please file an issue for the problematic plugin(s) here:'),
1522 );
1523 expect(testLogger.statusText, contains('https://github.com/flutter/flutter/issues'));
1524 },
1525 overrides: <Type, Generator>{
1526 GradleUtils: () => FakeGradleUtils(),
1527 Platform: () => fakePlatform('android'),
1528 FileSystem: () => fileSystem,
1529 ProcessManager: () => processManager,
1530 },
1531 );
1532
1533 testUsingContext(
1534 'Java 21 and jlink bug',
1535 () async {
1536 const errorExample = r'''
1537* What went wrong:
1538Execution failed for task ':shared_preferences_android:compileReleaseJavaWithJavac'.
1539> Could not resolve all files for configuration ':shared_preferences_android:androidJdkImage'.
1540 > Failed to transform core-for-system-modules.jar to match attributes {artifactType=_internal_android_jdk_image, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
1541 > Execution failed for JdkImageTransform: /Users/mackall/Library/Android/sdk/platforms/android-34/core-for-system-modules.jar.
1542 > Error while executing process /Users/mackall/Desktop/JDKs/21/jdk-21.0.2.jdk/Contents/Home/bin/jlink with arguments {--module-path /Users/mackall/.gradle/caches/8.9/transforms/2890fec03da42154757073d3208548e5-79660961-f91d-4df2-90bc-b9a3f2a270bd/transformed/output/temp/jmod --add-modules java.base --output /Users/mackall/.gradle/caches/8.9/transforms/2890fec03da42154757073d3208548e5-79660961-f91d-4df2-90bc-b9a3f2a270bd/transformed/output/jdkImage --disable-plugin system-modules}
1543 ''';
1544
1545 final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
1546 await jlinkErrorWithJava21AndSourceCompatibility.handler(
1547 line: errorExample,
1548 project: project,
1549 usesAndroidX: true,
1550 );
1551
1552 // Main fix text.
1553 expect(
1554 testLogger.statusText,
1555 contains('To fix this error, please upgrade your AGP version to at least 8.2.1.'),
1556 );
1557 // Paths to AGP location.
1558 expect(testLogger.statusText, contains('/android/settings.gradle'));
1559 expect(testLogger.statusText, contains('/android/build.gradle'));
1560 // Links to info.
1561 expect(testLogger.statusText, contains('https://issuetracker.google.com/issues/294137077'));
1562 expect(testLogger.statusText, contains('https://github.com/flutter/flutter/issues/156304'));
1563 },
1564 overrides: <Type, Generator>{
1565 GradleUtils: () => FakeGradleUtils(),
1566 Platform: () => fakePlatform('android'),
1567 FileSystem: () => fileSystem,
1568 ProcessManager: () => processManager,
1569 },
1570 );
1571
1572 testUsingContext(
1573 'Missing NDK source.properties file',
1574 () async {
1575 const unixErrorExample = r'''
1576* What went wrong:
1577A problem occurred configuring project ':app'.
1578> [CXX1101] NDK at /Users/mackall/Library/Android/sdk/ndk/26.3.11579264 did not have a source.properties file
1579 ''';
1580
1581 final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
1582 await missingNdkSourcePropertiesFile.handler(
1583 line: unixErrorExample,
1584 project: project,
1585 usesAndroidX: true,
1586 );
1587
1588 expect(
1589 testLogger.statusText,
1590 contains('This can be fixed by deleting the local NDK copy at'),
1591 );
1592 expect(
1593 testLogger.statusText,
1594 contains('/Users/mackall/Library/Android/sdk/ndk/26.3.11579264'),
1595 );
1596
1597 const windowsErrorExample = r'''
1598* What went wrong:
1599A problem occurred configuring project ':app'.
1600> [CXX1101] NDK at C:\Users\mackall\Library\Android\sdk\ndk\26.3.11579264 did not have a source.properties file
1601 ''';
1602 await missingNdkSourcePropertiesFile.handler(
1603 line: windowsErrorExample,
1604 project: project,
1605 usesAndroidX: true,
1606 );
1607
1608 expect(
1609 testLogger.statusText,
1610 contains('This can be fixed by deleting the local NDK copy at'),
1611 );
1612 expect(
1613 testLogger.statusText,
1614 contains(r'C:\Users\mackall\Library\Android\sdk\ndk\26.3.11579264'),
1615 );
1616 },
1617 overrides: <Type, Generator>{
1618 GradleUtils: () => FakeGradleUtils(),
1619 Platform: () => fakePlatform('android'),
1620 FileSystem: () => fileSystem,
1621 ProcessManager: () => processManager,
1622 },
1623 );
1624}
1625
1626bool formatTestErrorMessage(String errorMessage, GradleHandledError error) {
1627 return errorMessage.split('\n').any((String line) => error.test(line));
1628}
1629
1630Platform fakePlatform(String name) {
1631 return FakePlatform(environment: <String, String>{'HOME': '/'}, operatingSystem: name);
1632}
1633
1634class FakeGradleUtils extends Fake implements GradleUtils {
1635 @override
1636 String getExecutable(FlutterProject project) {
1637 return 'gradlew';
1638 }
1639}
1640
1641/// Simple terminal that returns the specified string when
1642/// promptForCharInput is called.
1643class _TestPromptTerminal extends Fake implements AnsiTerminal {
1644 _TestPromptTerminal(this.promptResult);
1645
1646 final String promptResult;
1647
1648 @override
1649 bool get stdinHasTerminal => true;
1650
1651 @override
1652 Future<String> promptForCharInput(
1653 List<String> acceptedCharacters, {
1654 required Logger logger,
1655 String? prompt,
1656 int? defaultChoiceIndex,
1657 bool displayAcceptedCharacters = true,
1658 }) {
1659 return Future<String>.value(promptResult);
1660 }
1661}
1662
1663class FakeFlutterProject extends Fake implements FlutterProject {}
1664