gradle_test.dart 33.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_errors.dart';
11
import 'package:flutter_tools/src/android/gradle_utils.dart';
12
import 'package:flutter_tools/src/artifacts.dart';
13
import 'package:flutter_tools/src/base/common.dart';
14
import 'package:flutter_tools/src/base/file_system.dart';
15
import 'package:flutter_tools/src/base/logger.dart';
16
import 'package:flutter_tools/src/base/platform.dart';
17
import 'package:flutter_tools/src/build_info.dart';
18
import 'package:flutter_tools/src/cache.dart';
19
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
20
import 'package:flutter_tools/src/project.dart';
21
import 'package:flutter_tools/src/reporting/reporting.dart';
22
import 'package:test/fake.dart';
23

24 25
import '../../src/common.dart';
import '../../src/context.dart';
26

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

35
void main() {
36
  Cache.flutterRoot = getFlutterRoot();
37 38

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

145 146 147 148 149 150 151 152
    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']);
    });

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

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

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

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

    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',
      ]));
    });

185 186 187 188 189
  });

  group('gradle build', () {
    testUsingContext('do not crash if there is no Android SDK', () async {
      expect(() {
190
        updateLocalProperties(project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory));
191
      }, throwsToolExit(
192
        message: '${globals.logger.terminal.warningMark} No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.',
193 194 195
      ));
    }, overrides: <Type, Generator>{
      AndroidSdk: () => null,
196
    });
197 198 199 200 201 202 203 204 205 206 207 208 209 210

    test('androidXPluginWarningRegex should match lines with the AndroidX plugin warnings', () {
      final List<String> nonMatchingLines = <String>[
        ':app:preBuild UP-TO-DATE',
        'BUILD SUCCESSFUL in 0s',
        'Generic plugin AndroidX text',
        '',
      ];
      final List<String> matchingLines = <String>[
        '*********************************************************************************************************************************',
        "WARNING: This version of image_picker will break your Android build if it or its dependencies aren't compatible with AndroidX.",
        'See https://goo.gl/CP92wY for more information on the problem and how to fix it.',
        'This warning prints for all Android build failures. The real root cause of the error may be unrelated.',
      ];
211
      for (final String m in nonMatchingLines) {
212 213
        expect(androidXPluginWarningRegex.hasMatch(m), isFalse);
      }
214
      for (final String m in matchingLines) {
215 216
        expect(androidXPluginWarningRegex.hasMatch(m), isTrue);
      }
217
    });
218
  });
219

220 221
  group('Config files', () {
    Directory tempDir;
222
    FileSystem fileSystem;
223 224

    setUp(() {
225 226
      fileSystem = MemoryFileSystem.test();
      tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_settings_aar_test.');
227 228 229
    });

    testUsingContext('create settings_aar.gradle when current settings.gradle loads plugins', () {
230
      const String currentSettingsGradle = r'''
231 232 233 234 235 236 237 238 239 240 241 242
include ':app'

def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}

plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
243 244 245 246
    if (pluginDirectory.exists()) {
        include ":$name"
        project(":$name").projectDir = pluginDirectory
    }
247 248 249 250 251 252 253 254 255
}
''';

      const String settingsAarFile = '''
include ':app'
''';

      tempDir.childFile('settings.gradle').writeAsStringSync(currentSettingsGradle);

256 257
      final String toolGradlePath = fileSystem.path.join(
          fileSystem.path.absolute(Cache.flutterRoot),
258 259 260
          'packages',
          'flutter_tools',
          'gradle');
261 262
      fileSystem.directory(toolGradlePath).createSync(recursive: true);
      fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings.gradle.legacy_versions'))
263 264
          .writeAsStringSync(currentSettingsGradle);

265
      fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings_aar.gradle.tmpl'))
266 267
          .writeAsStringSync(settingsAarFile);

268
      createSettingsAarGradle(tempDir, testLogger);
269

270
      expect(testLogger.statusText, contains('created successfully'));
271 272
      expect(tempDir.childFile('settings_aar.gradle').existsSync(), isTrue);
    }, overrides: <Type, Generator>{
273
      FileSystem: () => fileSystem,
274
      ProcessManager: () => FakeProcessManager.any(),
275 276
    });

277
    testUsingContext("create settings_aar.gradle when current settings.gradle doesn't load plugins", () {
278 279 280 281 282 283 284 285 286 287
      const String currentSettingsGradle = '''
include ':app'
''';

      const String settingsAarFile = '''
include ':app'
''';

      tempDir.childFile('settings.gradle').writeAsStringSync(currentSettingsGradle);

288 289
      final String toolGradlePath = fileSystem.path.join(
          fileSystem.path.absolute(Cache.flutterRoot),
290 291 292
          'packages',
          'flutter_tools',
          'gradle');
293 294
      fileSystem.directory(toolGradlePath).createSync(recursive: true);
      fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings.gradle.legacy_versions'))
295 296
          .writeAsStringSync(currentSettingsGradle);

297
      fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings_aar.gradle.tmpl'))
298 299
          .writeAsStringSync(settingsAarFile);

300
      createSettingsAarGradle(tempDir, testLogger);
301

302
      expect(testLogger.statusText, contains('created successfully'));
303 304
      expect(tempDir.childFile('settings_aar.gradle').existsSync(), isTrue);
    }, overrides: <Type, Generator>{
305
      FileSystem: () => fileSystem,
306
      ProcessManager: () => FakeProcessManager.any(),
307 308 309
    });
  });

310
  group('Gradle local.properties', () {
311
    Artifacts localEngineArtifacts;
312
    FileSystem fs;
313 314

    setUp(() {
315
      fs = MemoryFileSystem.test();
316
      localEngineArtifacts = Artifacts.test(localEngine: 'out/android_arm');
317 318
    });

319
    void testUsingAndroidContext(String description, dynamic Function() testMethod) {
320
      testUsingContext(description, testMethod, overrides: <Type, Generator>{
321
        Artifacts: () => localEngineArtifacts,
322
        Platform: () => FakePlatform(operatingSystem: 'linux'),
323
        FileSystem: () => fs,
324
        ProcessManager: () => FakeProcessManager.any(),
325
      });
326 327 328
    }

    String propertyFor(String key, File file) {
329
      final Iterable<String> result = file.readAsLinesSync()
330
          .where((String line) => line.startsWith('$key='))
331 332
          .map((String line) => line.split('=')[1]);
      return result.isEmpty ? null : result.first;
333 334 335 336 337 338 339 340
    }

    Future<void> checkBuildVersion({
      String manifest,
      BuildInfo buildInfo,
      String expectedBuildName,
      String expectedBuildNumber,
    }) async {
341
      final File manifestFile = globals.fs.file('path/to/project/pubspec.yaml');
342 343
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync(manifest);
344 345


346
      updateLocalProperties(
347
        project: FlutterProject.fromDirectoryTest(globals.fs.directory('path/to/project')),
348 349 350 351
        buildInfo: buildInfo,
        requireAndroidSdk: false,
      );

352
      final File localPropertiesFile = globals.fs.file('path/to/project/android/local.properties');
353 354
      expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName);
      expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber);
355 356
    }

357
    testUsingAndroidContext('extract build name and number from pubspec.yaml', () async {
358 359 360 361 362 363 364 365 366
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';

367
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
368 369 370 371 372 373 374 375
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '1',
      );
    });

376
    testUsingAndroidContext('extract build name from pubspec.yaml', () async {
377 378 379 380 381 382 383 384
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
385
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
386 387 388 389 390 391 392 393
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: null,
      );
    });

394
    testUsingAndroidContext('allow build info to override build name', () async {
395 396 397 398 399 400 401 402
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
403
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
404 405 406 407 408 409 410 411
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '1',
      );
    });

412
    testUsingAndroidContext('allow build info to override build number', () async {
413 414 415 416 417 418 419 420
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
421
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
422 423 424 425 426 427 428 429
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.0',
        expectedBuildNumber: '3',
      );
    });

430
    testUsingAndroidContext('allow build info to override build name and number', () async {
431 432 433 434 435 436 437 438
      const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
439
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
440 441 442 443 444 445 446 447
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

448
    testUsingAndroidContext('allow build info to override build name and set number', () async {
449 450 451 452 453 454 455 456
      const String manifest = '''
name: test
version: 1.0.0
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
457
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
458 459 460 461 462 463 464 465
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });

466
    testUsingAndroidContext('allow build info to set build name and number', () async {
467 468 469 470 471 472 473
      const String manifest = '''
name: test
dependencies:
  flutter:
    sdk: flutter
flutter:
''';
474
      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
475 476 477 478 479 480 481
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: buildInfo,
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
    });
482 483 484 485 486 487 488 489 490 491 492

    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,
493
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
494 495 496 497 498
        expectedBuildName: null,
        expectedBuildNumber: null,
      );
      await checkBuildVersion(
        manifest: manifest,
499
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false),
500 501 502 503 504
        expectedBuildName: '1.0.2',
        expectedBuildNumber: '3',
      );
      await checkBuildVersion(
        manifest: manifest,
505
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.3', buildNumber: '4', treeShakeIcons: false),
506 507 508 509 510 511 512 513 514 515 516 517 518
        expectedBuildName: '1.0.3',
        expectedBuildNumber: '4',
      );
      // Values don't get unset.
      await checkBuildVersion(
        manifest: manifest,
        buildInfo: null,
        expectedBuildName: '1.0.3',
        expectedBuildNumber: '4',
      );
      // Values get unset.
      await checkBuildVersion(
        manifest: manifest,
519
        buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
520 521 522 523
        expectedBuildName: null,
        expectedBuildNumber: null,
      );
    });
524
  });
525 526

  group('gradle version', () {
527
    testWithoutContext('should be compatible with the Android plugin version', () {
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
      // 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');

562 563
      expect(getGradleVersionFor('3.4.0'), '5.6.2');
      expect(getGradleVersionFor('3.5.0'), '5.6.2');
564 565 566

      expect(getGradleVersionFor('4.0.0'), '6.7');
      expect(getGradleVersionFor('4.1.0'), '6.7');
567 568
    });

569
    testWithoutContext('throws on unsupported versions', () {
570 571 572 573
      expect(() => getGradleVersionFor('3.6.0'),
          throwsA(predicate<Exception>((Exception e) => e is ToolExit)));
    });
  });
574

575 576 577 578
  group('isAppUsingAndroidX', () {
    FileSystem fs;

    setUp(() {
579
      fs = MemoryFileSystem.test();
580 581 582
    });

    testUsingContext('returns true when the project is using AndroidX', () async {
583
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
584 585 586 587 588 589 590 591 592

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

      expect(isAppUsingAndroidX(androidDirectory), isTrue);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
593
      ProcessManager: () => FakeProcessManager.any(),
594 595 596
    });

    testUsingContext('returns false when the project is not using AndroidX', () async {
597
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
598 599 600 601 602 603 604 605 606

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

      expect(isAppUsingAndroidX(androidDirectory), isFalse);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
607
      ProcessManager: () => FakeProcessManager.any(),
608 609 610
    });

    testUsingContext('returns false when gradle.properties does not exist', () async {
611
      final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
612 613 614 615 616

      expect(isAppUsingAndroidX(androidDirectory), isFalse);

    }, overrides: <Type, Generator>{
      FileSystem: () => fs,
617
      ProcessManager: () => FakeProcessManager.any(),
618 619 620 621 622
    });
  });

  group('buildPluginsAsAar', () {
    FileSystem fs;
623
    FakeProcessManager fakeProcessManager;
624
    FakeAndroidSdk androidSdk;
625
    AndroidGradleBuilder builder;
626
    BufferLogger logger;
627
    Platform platform;
628 629

    setUp(() {
630
      logger = BufferLogger.test();
631
      fs = MemoryFileSystem.test();
632
      fakeProcessManager = FakeProcessManager.empty();
633
      androidSdk = FakeAndroidSdk();
634 635
      platform = FakePlatform(operatingSystem: 'linux');

636 637 638 639 640
      builder = AndroidGradleBuilder(
        logger: logger,
        processManager: fakeProcessManager,
        fileSystem: fs,
        artifacts: Artifacts.test(),
641
        usage: TestUsage(),
642
        gradleUtils: FakeGradleUtils(),
643
        platform: platform,
644
      );
645 646 647
    });

    testUsingContext('calls gradle', () async {
648
      final Directory androidDirectory = globals.fs.directory('android.');
649 650 651 652 653
      androidDirectory.createSync();
      androidDirectory
        .childFile('pubspec.yaml')
        .writeAsStringSync('name: irrelevant');

654
      final Directory plugin1 = globals.fs.directory('plugin1.');
655 656 657 658 659 660 661 662 663
      plugin1
        ..createSync()
        ..childFile('pubspec.yaml')
        .writeAsStringSync('''
name: irrelevant
flutter:
  plugin:
    androidPackage: irrelevant
''');
664

665 666 667 668
      plugin1
        .childDirectory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);
669

670
      final Directory plugin2 = globals.fs.directory('plugin2.');
671 672 673 674 675 676 677 678 679 680
      plugin2
        ..createSync()
        ..childFile('pubspec.yaml')
        .writeAsStringSync('''
name: irrelevant
flutter:
  plugin:
    androidPackage: irrelevant
''');

681 682 683 684
      plugin2
        .childDirectory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);
685

686 687 688 689 690 691
      androidDirectory
        .childFile('.flutter-plugins')
        .writeAsStringSync('''
plugin1=${plugin1.path}
plugin2=${plugin2.path}
''');
692 693
      final Directory buildDirectory = androidDirectory
        .childDirectory('build');
694 695 696 697 698
      buildDirectory
        .childDirectory('outputs')
        .childDirectory('repo')
        .createSync(recursive: true);

699 700
      final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot);
      final String initScript = globals.fs.path.join(
701 702 703 704 705 706
        flutterRoot,
        'packages',
        'flutter_tools',
        'gradle',
        'aar_init_script.gradle',
      );
707

708 709
      fakeProcessManager.addCommands(<FakeCommand>[
        FakeCommand(
710 711 712 713 714 715 716 717
          command: <String>[
            'gradlew',
            '-I=$initScript',
            '-Pflutter-root=$flutterRoot',
            '-Poutput-dir=${buildDirectory.path}',
            '-Pis-plugin=true',
            '-PbuildNumber=1.0',
            '-q',
718 719 720
            '-Pdart-obfuscation=false',
            '-Ptrack-widget-creation=false',
            '-Ptree-shake-icons=true',
721 722 723 724
            '-Ptarget-platform=android-arm,android-arm64,android-x64',
            'assembleAarRelease',
          ],
          workingDirectory: plugin1.childDirectory('android').path,
725 726
        ),
        FakeCommand(
727 728 729 730 731 732 733 734
          command: <String>[
            'gradlew',
            '-I=$initScript',
            '-Pflutter-root=$flutterRoot',
            '-Poutput-dir=${buildDirectory.path}',
            '-Pis-plugin=true',
            '-PbuildNumber=1.0',
            '-q',
735 736 737
            '-Pdart-obfuscation=false',
            '-Ptrack-widget-creation=false',
            '-Ptree-shake-icons=true',
738 739 740 741
            '-Ptarget-platform=android-arm,android-arm64,android-x64',
            'assembleAarRelease',
          ],
          workingDirectory: plugin2.childDirectory('android').path,
742
        )]);
743

744
      await builder.buildPluginsAsAar(
745
        FlutterProject.fromDirectoryTest(androidDirectory),
746 747 748 749 750 751 752 753 754 755
        const AndroidBuildInfo(BuildInfo(
          BuildMode.release,
          '',
          treeShakeIcons: true,
          dartObfuscation: true,
          buildNumber: '2.0'
        )),
        buildDirectory: buildDirectory,
      );
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
756
    }, overrides: <Type, Generator>{
757
      AndroidSdk: () => androidSdk,
758
      FileSystem: () => fs,
759
      Platform: () => platform,
760
      ProcessManager: () => fakeProcessManager,
761 762 763
      GradleUtils: () => FakeGradleUtils(),
    });

764
    testUsingContext('skips plugin without a android/build.gradle file', () async {
765
      final Directory androidDirectory = globals.fs.directory('android.');
766 767 768 769 770
      androidDirectory.createSync();
      androidDirectory
        .childFile('pubspec.yaml')
        .writeAsStringSync('name: irrelevant');

771
      final Directory plugin1 = globals.fs.directory('plugin1.');
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
      plugin1
        ..createSync()
        ..childFile('pubspec.yaml')
        .writeAsStringSync('''
name: irrelevant
flutter:
  plugin:
    androidPackage: irrelevant
''');

      androidDirectory
        .childFile('.flutter-plugins')
        .writeAsStringSync('''
plugin1=${plugin1.path}
''');
787 788 789 790
      // Create an empty android directory.
      // https://github.com/flutter/flutter/issues/46898
      plugin1.childDirectory('android').createSync();

791 792 793 794 795 796 797
      final Directory buildDirectory = androidDirectory.childDirectory('build');

      buildDirectory
        .childDirectory('outputs')
        .childDirectory('repo')
        .createSync(recursive: true);

798
      await builder.buildPluginsAsAar(
799
        FlutterProject.fromDirectoryTest(androidDirectory),
800 801 802
        const AndroidBuildInfo(BuildInfo.release),
        buildDirectory: buildDirectory,
      );
803
      expect(fakeProcessManager.hasRemainingExpectations, isFalse);
804
    }, overrides: <Type, Generator>{
805
      AndroidSdk: () => androidSdk,
806
      FileSystem: () => fs,
807
      ProcessManager: () => fakeProcessManager,
808
      GradleUtils: () => FakeGradleUtils(),
809 810 811
    });
  });

812
  group('printHowToConsumeAar', () {
813 814 815 816 817 818 819 820 821
    BufferLogger logger;
    FileSystem fileSystem;

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

    testWithoutContext('stdout contains release, debug and profile', () async {
822 823 824
      printHowToConsumeAar(
        buildModes: const <String>{'release', 'debug', 'profile'},
        androidPackage: 'com.mycompany',
825
        repoDirectory: fileSystem.directory('build/'),
826
        buildNumber: '2.2',
827 828
        logger: logger,
        fileSystem: fileSystem,
829 830 831
      );

      expect(
832
        logger.statusText,
833 834 835 836 837 838
        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'
839
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
840 841
          '      repositories {\n'
          '        maven {\n'
842
          "            url 'build/'\n"
843 844
          '        }\n'
          '        maven {\n'
845
          '            url "\$storageUrl/download.flutter.io"\n'
846 847 848 849 850 851
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
852 853 854
          "      releaseImplementation 'com.mycompany:flutter_release:2.2'\n"
          "      debugImplementation 'com.mycompany:flutter_debug:2.2'\n"
          "      profileImplementation 'com.mycompany:flutter_profile:2.2'\n"
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872
          '    }\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'
        )
      );
    });

873
    testWithoutContext('stdout contains release', () async {
874 875 876
      printHowToConsumeAar(
        buildModes: const <String>{'release'},
        androidPackage: 'com.mycompany',
877 878 879
        repoDirectory: fileSystem.directory('build/'),
        logger: logger,
        fileSystem: fileSystem,
880 881 882
      );

      expect(
883
        logger.statusText,
884 885 886 887 888 889
        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'
890
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
891 892
          '      repositories {\n'
          '        maven {\n'
893
          "            url 'build/'\n"
894 895
          '        }\n'
          '        maven {\n'
896
          '            url "\$storageUrl/download.flutter.io"\n'
897 898 899 900 901 902
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
903
          "      releaseImplementation 'com.mycompany:flutter_release:1.0'\n"
904 905 906 907 908 909 910
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });

911
    testWithoutContext('stdout contains debug', () async {
912 913 914
      printHowToConsumeAar(
        buildModes: const <String>{'debug'},
        androidPackage: 'com.mycompany',
915 916 917
        repoDirectory: fileSystem.directory('build/'),
        logger: logger,
        fileSystem: fileSystem,
918 919 920
      );

      expect(
921
        logger.statusText,
922 923 924 925 926 927
        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'
928
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
929 930
          '      repositories {\n'
          '        maven {\n'
931
          "            url 'build/'\n"
932 933
          '        }\n'
          '        maven {\n'
934
          '            url "\$storageUrl/download.flutter.io"\n'
935 936 937 938 939 940
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
941
          "      debugImplementation 'com.mycompany:flutter_debug:1.0'\n"
942 943 944 945 946 947 948
          '    }\n'
          '\n'
          'To learn more, visit https://flutter.dev/go/build-aar\n'
        )
      );
    });

949
    testWithoutContext('stdout contains profile', () async {
950 951 952
      printHowToConsumeAar(
        buildModes: const <String>{'profile'},
        androidPackage: 'com.mycompany',
953
        repoDirectory: fileSystem.directory('build/'),
954
        buildNumber: '1.0',
955 956
        logger: logger,
        fileSystem: fileSystem,
957 958 959
      );

      expect(
960
        logger.statusText,
961 962 963 964 965 966
        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'
967
          '      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
968 969
          '      repositories {\n'
          '        maven {\n'
970
          "            url 'build/'\n"
971 972
          '        }\n'
          '        maven {\n'
973
          '            url "\$storageUrl/download.flutter.io"\n'
974 975 976 977 978 979
          '        }\n'
          '      }\n'
          '\n'
          '  3. Make the host app depend on the Flutter module:\n'
          '\n'
          '    dependencies {\n'
980
          "      profileImplementation 'com.mycompany:flutter_profile:1.0'\n"
981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998
          '    }\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'
        )
      );
    });
  });
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009

  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()),
    );
1010
  }, skip: true); // TODO(jonahwilliams): This is an integration test and should be moved to the integration shard.
1011
}
1012

1013 1014
class FakeGradleUtils extends GradleUtils {
  @override
1015
  String getExecutable(FlutterProject project) {
1016 1017 1018 1019
    return 'gradlew';
  }
}

1020
class FakeAndroidSdk extends Fake implements AndroidSdk {}