gradle_test.dart 23.2 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 6
// @dart = 2.8

7
import 'package:file/memory.dart';
8
import 'package:flutter_tools/src/android/android_sdk.dart';
9
import 'package:flutter_tools/src/android/gradle.dart';
10
import 'package:flutter_tools/src/android/gradle_utils.dart';
11
import 'package:flutter_tools/src/artifacts.dart';
12
import 'package:flutter_tools/src/base/common.dart';
13
import 'package:flutter_tools/src/base/file_system.dart';
14
import 'package:flutter_tools/src/base/logger.dart';
15
import 'package:flutter_tools/src/base/platform.dart';
16
import 'package:flutter_tools/src/build_info.dart';
17
import 'package:flutter_tools/src/cache.dart';
18
import 'package:flutter_tools/src/globals.dart' as globals;
19
import 'package:flutter_tools/src/project.dart';
20
import 'package:test/fake.dart';
21

22 23
import '../../src/common.dart';
import '../../src/context.dart';
24

25 26 27 28 29 30 31 32
const String kModulePubspec = '''
name: test
flutter:
  module:
    androidPackage: com.example
    androidX: true
''';

33
void main() {
34
  Cache.flutterRoot = getFlutterRoot();
35 36

  group('build artifacts', () {
37 38 39 40 41 42 43
    FileSystem fileSystem;

    setUp(() {
      fileSystem = MemoryFileSystem.test();
    });

    testWithoutContext('getApkDirectory in app projects', () {
44
      final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
45 46

      expect(
47
        getApkDirectory(project).path, '/build/app/outputs/flutter-apk',
48
      );
49 50
    });

51
    testWithoutContext('getApkDirectory in module projects', () {
52 53 54 55
      fileSystem.currentDirectory
        .childFile('pubspec.yaml')
        .writeAsStringSync(kModulePubspec);
      final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
56

57
      expect(project.isModule, true);
58
      expect(
59
        getApkDirectory(project).path, '/build/host/outputs/apk',
60
      );
61 62
    });

63
    testWithoutContext('getBundleDirectory in app projects', () {
64
      final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
65 66

      expect(
67
        getBundleDirectory(project).path, '/build/app/outputs/bundle',
68
      );
69 70
    });

71
    testWithoutContext('getBundleDirectory in module projects', () {
72 73 74 75
      fileSystem.currentDirectory
        .childFile('pubspec.yaml')
        .writeAsStringSync(kModulePubspec);
      final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
76

77
      expect(project.isModule, true);
78
      expect(
79
        getBundleDirectory(project).path, '/build/host/outputs/bundle',
80
      );
81 82
    });

83
    testWithoutContext('getRepoDirectory', () {
84
      expect(
85 86
        getRepoDirectory(fileSystem.directory('foo')).path,
        equals(fileSystem.path.join('foo','outputs', 'repo')),
87 88 89 90 91
      );
    });
  });

  group('gradle tasks', () {
92
    testWithoutContext('assemble release', () {
93
      expect(
94
        getAssembleTaskFor(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
95 96 97
        equals('assembleRelease'),
      );
      expect(
98
        getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo', treeShakeIcons: false)),
99 100 101 102
        equals('assembleFlavorFooRelease'),
      );
    });

103
    testWithoutContext('assemble debug', () {
104
      expect(
105
        getAssembleTaskFor(BuildInfo.debug),
106 107 108
        equals('assembleDebug'),
      );
      expect(
109
        getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo', treeShakeIcons: false)),
110 111
        equals('assembleFlavorFooDebug'),
      );
112
    });
113

114
    testWithoutContext('assemble profile', () {
115
      expect(
116
        getAssembleTaskFor(const BuildInfo(BuildMode.profile, null, treeShakeIcons: false)),
117 118 119
        equals('assembleProfile'),
      );
      expect(
120
        getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo', treeShakeIcons: false)),
121 122 123 124 125
        equals('assembleFlavorFooProfile'),
      );
    });
  });

126 127 128
  group('listApkPaths', () {
    testWithoutContext('Finds APK without flavor in release', () {
      final Iterable<String> apks = listApkPaths(
129
        const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)),
130
      );
131

132 133
      expect(apks, <String>['app-release.apk']);
    });
134

135 136
    testWithoutContext('Finds APK with flavor in release mode', () {
      final Iterable<String> apks = listApkPaths(
137
        const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
138 139
      );

140 141
      expect(apks, <String>['app-flavor1-release.apk']);
    });
142

143 144 145 146 147 148 149 150
    testWithoutContext('Finds APK with flavor in release mode', () {
      final Iterable<String> apks = listApkPaths(
        const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavorA', treeShakeIcons: false)),
      );

      expect(apks, <String>['app-flavora-release.apk']);
    });

151 152
    testWithoutContext('Finds APK with flavor in release mode - AGP v3', () {
      final Iterable<String> apks = listApkPaths(
153
        const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
154
      );
155 156

      expect(apks, <String>['app-flavor1-release.apk']);
157
    });
158

159 160 161
    testWithoutContext('Finds APK with split-per-abi', () {
      final Iterable<String> apks = listApkPaths(
        const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false), splitPerAbi: true),
162
      );
163 164 165 166 167 168

      expect(apks, unorderedEquals(<String>[
        'app-armeabi-v7a-flavor1-release.apk',
        'app-arm64-v8a-flavor1-release.apk',
        'app-x86_64-flavor1-release.apk',
      ]));
169
    });
170 171 172 173 174 175 176 177 178 179 180 181 182

    testWithoutContext('Finds APK with split-per-abi when flavor contains uppercase letters', () {
      final Iterable<String> apks = listApkPaths(
        const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavorA', treeShakeIcons: false), splitPerAbi: true),
      );

      expect(apks, unorderedEquals(<String>[
        'app-armeabi-v7a-flavora-release.apk',
        'app-arm64-v8a-flavora-release.apk',
        'app-x86_64-flavora-release.apk',
      ]));
    });

183 184 185 186 187
  });

  group('gradle build', () {
    testUsingContext('do not crash if there is no Android SDK', () async {
      expect(() {
188
        updateLocalProperties(project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory));
189
      }, throwsToolExit(
190
        message: '${globals.logger.terminal.warningMark} No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.',
191 192 193
      ));
    }, overrides: <Type, Generator>{
      AndroidSdk: () => null,
194
    });
195 196
  });

197
  group('Gradle local.properties', () {
198
    Artifacts localEngineArtifacts;
199
    FileSystem fs;
200 201

    setUp(() {
202
      fs = MemoryFileSystem.test();
203
      localEngineArtifacts = Artifacts.test(localEngine: 'out/android_arm');
204 205
    });

206
    void testUsingAndroidContext(String description, dynamic Function() testMethod) {
207
      testUsingContext(description, testMethod, overrides: <Type, Generator>{
208
        Artifacts: () => localEngineArtifacts,
209
        Platform: () => FakePlatform(),
210
        FileSystem: () => fs,
211
        ProcessManager: () => FakeProcessManager.any(),
212
      });
213 214 215
    }

    String propertyFor(String key, File file) {
216
      final Iterable<String> result = file.readAsLinesSync()
217
          .where((String line) => line.startsWith('$key='))
218 219
          .map((String line) => line.split('=')[1]);
      return result.isEmpty ? null : result.first;
220 221 222 223 224 225 226 227
    }

    Future<void> checkBuildVersion({
      String manifest,
      BuildInfo buildInfo,
      String expectedBuildName,
      String expectedBuildNumber,
    }) async {
228
      final File manifestFile = globals.fs.file('path/to/project/pubspec.yaml');
229 230
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync(manifest);
231 232


233
      updateLocalProperties(
234
        project: FlutterProject.fromDirectoryTest(globals.fs.directory('path/to/project')),
235 236 237 238
        buildInfo: buildInfo,
        requireAndroidSdk: false,
      );

239
      final File localPropertiesFile = globals.fs.file('path/to/project/android/local.properties');
240 241
      expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName);
      expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber);
242 243
    }

244
    testUsingAndroidContext('extract build name and number from pubspec.yaml', () async {
245 246 247 248 249 250 251 252 253
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';

254
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
255 256 257 258 259 260 261 262
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '1',
      );
    });

263
    testUsingAndroidContext('extract build name from pubspec.yaml', () async {
264 265 266 267 268 269 270 271
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
272
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
273 274 275 276 277 278 279
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
      );
    });

280
    testUsingAndroidContext('allow build info to override build name', () async {
281 282 283 284 285 286 287 288
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
289
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
290 291 292 293 294 295 296 297
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '1',
      );
    });

298
    testUsingAndroidContext('allow build info to override build number', () async {
299 300 301 302 303 304 305 306
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
307
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
308 309 310 311 312 313 314 315
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '3',
      );
    });

316
    testUsingAndroidContext('allow build info to override build name and number', () async {
317 318 319 320 321 322 323 324
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
325
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
326 327 328 329 330 331 332 333
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

334
    testUsingAndroidContext('allow build info to override build name and set number', () async {
335 336 337 338 339 340 341 342
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
343
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
344 345 346 347 348 349 350 351
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

352
    testUsingAndroidContext('allow build info to set build name and number', () async {
353 354 355 356 357 358 359
      const String manifest = '''
name: test
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
360
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
361 362 363 364 365 366 367
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });
368 369 370 371 372 373 374 375 376 377 378

    testUsingAndroidContext('allow build info to unset build name and number', () async {
      const String manifest = '''
name: test
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
      await checkBuildVersion(
        manifest: manifest,
379
        buildInfo: const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
380 381 382
      );
      await checkBuildVersion(
        manifest: manifest,
383
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false),
384 385 386 387 388
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
      await checkBuildVersion(
        manifest: manifest,
389
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.3', buildNumber: '4', treeShakeIcons: false),
390 391 392 393 394 395 396 397 398 399 400 401
        expectedBuildName: '1.0.3',
        expectedBuildNumber: '4',
      );
      // Values don't get unset.
      await checkBuildVersion(
        manifest: manifest,
        expectedBuildName: '1.0.3',
        expectedBuildNumber: '4',
      );
      // Values get unset.
      await checkBuildVersion(
        manifest: manifest,
402
        buildInfo: const BuildInfo(BuildMode.release, null, treeShakeIcons: false),
403 404
      );
    });
405
  });
406 407

  group('gradle version', () {
408
    testWithoutContext('should be compatible with the Android plugin version', () {
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
      // Granular versions.
      expect(getGradleVersionFor('1.0.0'), '2.3');
      expect(getGradleVersionFor('1.0.1'), '2.3');
      expect(getGradleVersionFor('1.0.2'), '2.3');
      expect(getGradleVersionFor('1.0.4'), '2.3');
      expect(getGradleVersionFor('1.0.8'), '2.3');
      expect(getGradleVersionFor('1.1.0'), '2.3');
      expect(getGradleVersionFor('1.1.2'), '2.3');
      expect(getGradleVersionFor('1.1.2'), '2.3');
      expect(getGradleVersionFor('1.1.3'), '2.3');
      // Version Ranges.
      expect(getGradleVersionFor('1.2.0'), '2.9');
      expect(getGradleVersionFor('1.3.1'), '2.9');

      expect(getGradleVersionFor('1.5.0'), '2.2.1');

      expect(getGradleVersionFor('2.0.0'), '2.13');
      expect(getGradleVersionFor('2.1.2'), '2.13');

      expect(getGradleVersionFor('2.1.3'), '2.14.1');
      expect(getGradleVersionFor('2.2.3'), '2.14.1');

      expect(getGradleVersionFor('2.3.0'), '3.3');

      expect(getGradleVersionFor('3.0.0'), '4.1');

      expect(getGradleVersionFor('3.1.0'), '4.4');

      expect(getGradleVersionFor('3.2.0'), '4.6');
      expect(getGradleVersionFor('3.2.1'), '4.6');

      expect(getGradleVersionFor('3.3.0'), '4.10.2');
      expect(getGradleVersionFor('3.3.2'), '4.10.2');

443 444
      expect(getGradleVersionFor('3.4.0'), '5.6.2');
      expect(getGradleVersionFor('3.5.0'), '5.6.2');
445 446 447

      expect(getGradleVersionFor('4.0.0'), '6.7');
      expect(getGradleVersionFor('4.1.0'), '6.7');
448 449 450 451

      expect(getGradleVersionFor('7.0'), '7.4');
      expect(getGradleVersionFor('7.1.2'), '7.4');
      expect(getGradleVersionFor('7.2'), '7.4');
452 453
    });

454
    testWithoutContext('throws on unsupported versions', () {
455 456 457 458
      expect(() => getGradleVersionFor('3.6.0'),
          throwsA(predicate<Exception>((Exception e) => e is ToolExit)));
    });
  });
459

460 461 462 463
  group('isAppUsingAndroidX', () {
    FileSystem fs;

    setUp(() {
464
      fs = MemoryFileSystem.test();
465 466 467
    });

    testUsingContext('returns true when the project is using AndroidX', () async {
468
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
469 470 471 472 473 474 475 476 477

      androidDirectory
        .childFile('gradle.properties')
        .writeAsStringSync('android.useAndroidX=true');

      expect(isAppUsingAndroidX(androidDirectory), isTrue);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
478
      ProcessManager: () => FakeProcessManager.any(),
479 480 481
    });

    testUsingContext('returns false when the project is not using AndroidX', () async {
482
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
483 484 485 486 487 488 489 490 491

      androidDirectory
        .childFile('gradle.properties')
        .writeAsStringSync('android.useAndroidX=false');

      expect(isAppUsingAndroidX(androidDirectory), isFalse);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
492
      ProcessManager: () => FakeProcessManager.any(),
493 494 495
    });

    testUsingContext('returns false when gradle.properties does not exist', () async {
496
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
497 498 499 500 501

      expect(isAppUsingAndroidX(androidDirectory), isFalse);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
502
      ProcessManager: () => FakeProcessManager.any(),
503 504 505
    });
  });

506
  group('printHowToConsumeAar', () {
507 508 509 510 511 512 513 514 515
    BufferLogger logger;
    FileSystem fileSystem;

    setUp(() {
      logger = BufferLogger.test();
      fileSystem = MemoryFileSystem.test();
    });

    testWithoutContext('stdout contains release, debug and profile', () async {
516 517 518
      printHowToConsumeAar(
        buildModes: const <String>{'release', 'debug', 'profile'},
        androidPackage: 'com.mycompany',
519
        repoDirectory: fileSystem.directory('build/'),
520
        buildNumber: '2.2',
521 522
        logger: logger,
        fileSystem: fileSystem,
523 524 525
      );

      expect(
526
        logger.statusText,
527 528 529 530 531 532
        contains(
          '\n'
          'Consuming the Module\n'
          '  1. Open <host>/app/build.gradle\n'
          '  2. Ensure you have the repositories configured, otherwise add them:\n'
          '\n'
533
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
534 535
          '      repositories {\n'
          '        maven {\n'
536
          "            url 'build/'\n"
537 538
          '        }\n'
          '        maven {\n'
539
          '            url "\$storageUrl/download.flutter.io"\n'
540 541 542 543 544 545
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
546 547 548
          "      releaseImplementation 'com.mycompany:flutter:2.2:release'\n"
          "      debugImplementation 'com.mycompany:flutter:2.2:debug'\n"
          "      profileImplementation 'com.mycompany:flutter:2.2:profile'\n"
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
          '    }\n'
          '\n'
          '\n'
          '  4. Add the `profile` build type:\n'
          '\n'
          '    android {\n'
          '      buildTypes {\n'
          '        profile {\n'
          '          initWith debug\n'
          '        }\n'
          '      }\n'
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });

567
    testWithoutContext('stdout contains release', () async {
568 569 570
      printHowToConsumeAar(
        buildModes: const <String>{'release'},
        androidPackage: 'com.mycompany',
571 572 573
        repoDirectory: fileSystem.directory('build/'),
        logger: logger,
        fileSystem: fileSystem,
574 575 576
      );

      expect(
577
        logger.statusText,
578 579 580 581 582 583
        contains(
          '\n'
          'Consuming the Module\n'
          '  1. Open <host>/app/build.gradle\n'
          '  2. Ensure you have the repositories configured, otherwise add them:\n'
          '\n'
584
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
585 586
          '      repositories {\n'
          '        maven {\n'
587
          "            url 'build/'\n"
588 589
          '        }\n'
          '        maven {\n'
590
          '            url "\$storageUrl/download.flutter.io"\n'
591 592 593 594 595 596
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
597
          "      releaseImplementation 'com.mycompany:flutter:1.0:release'\n"
598 599 600 601 602 603 604
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });

605
    testWithoutContext('stdout contains debug', () async {
606 607 608
      printHowToConsumeAar(
        buildModes: const <String>{'debug'},
        androidPackage: 'com.mycompany',
609 610 611
        repoDirectory: fileSystem.directory('build/'),
        logger: logger,
        fileSystem: fileSystem,
612 613 614
      );

      expect(
615
        logger.statusText,
616 617 618 619 620 621
        contains(
          '\n'
          'Consuming the Module\n'
          '  1. Open <host>/app/build.gradle\n'
          '  2. Ensure you have the repositories configured, otherwise add them:\n'
          '\n'
622
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
623 624
          '      repositories {\n'
          '        maven {\n'
625
          "            url 'build/'\n"
626 627
          '        }\n'
          '        maven {\n'
628
          '            url "\$storageUrl/download.flutter.io"\n'
629 630 631 632 633 634
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
635
          "      debugImplementation 'com.mycompany:flutter:1.0:debug'\n"
636 637 638 639 640 641 642
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });

643
    testWithoutContext('stdout contains profile', () async {
644 645 646
      printHowToConsumeAar(
        buildModes: const <String>{'profile'},
        androidPackage: 'com.mycompany',
647
        repoDirectory: fileSystem.directory('build/'),
648
        buildNumber: '1.0',
649 650
        logger: logger,
        fileSystem: fileSystem,
651 652 653
      );

      expect(
654
        logger.statusText,
655 656 657 658 659 660
        contains(
          '\n'
          'Consuming the Module\n'
          '  1. Open <host>/app/build.gradle\n'
          '  2. Ensure you have the repositories configured, otherwise add them:\n'
          '\n'
661
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
662 663
          '      repositories {\n'
          '        maven {\n'
664
          "            url 'build/'\n"
665 666
          '        }\n'
          '        maven {\n'
667
          '            url "\$storageUrl/download.flutter.io"\n'
668 669 670 671 672 673
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
674
          "      profileImplementation 'com.mycompany:flutter:1.0:profile'\n"
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
          '    }\n'
          '\n'
          '\n'
          '  4. Add the `profile` build type:\n'
          '\n'
          '    android {\n'
          '      buildTypes {\n'
          '        profile {\n'
          '          initWith debug\n'
          '        }\n'
          '      }\n'
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });
  });
693 694 695 696 697 698 699 700 701 702 703

  test('Current settings.gradle is in our legacy settings.gradle file set', () {
    // If this test fails, you probably edited templates/app/android.tmpl.
    // That's fine, but you now need to add a copy of that file to gradle/settings.gradle.legacy_versions, separated
    // from the previous versions by a line that just says ";EOF".
    final File templateSettingsDotGradle = globals.fs.file(globals.fs.path.join(Cache.flutterRoot, 'packages', 'flutter_tools', 'templates', 'app', 'android.tmpl', 'settings.gradle'));
    final File legacySettingsDotGradleFiles = globals.fs.file(globals.fs.path.join(Cache.flutterRoot, 'packages','flutter_tools', 'gradle', 'settings.gradle.legacy_versions'));
    expect(
      legacySettingsDotGradleFiles.readAsStringSync().split(';EOF').map<String>((String body) => body.trim()),
      contains(templateSettingsDotGradle.readAsStringSync().trim()),
    );
704
    // TODO(zanderso): This is an integration test and should be moved to the integration shard.
705
  }, skip: true); // https://github.com/flutter/flutter/issues/87922
706
}
707

708 709
class FakeGradleUtils extends GradleUtils {
  @override
710
  String getExecutable(FlutterProject project) {
711 712 713 714
    return 'gradlew';
  }
}

715
class FakeAndroidSdk extends Fake implements AndroidSdk { }