blob: ad3a325aebd5209d379b920d9481d986695e24c1 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/android/gradle_errors.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';
import '../../src/common.dart';
import '../../src/context.dart';
const String kModulePubspec = '''
name: test
flutter:
module:
androidPackage: com.example
androidX: true
''';
void main() {
Cache.flutterRoot = getFlutterRoot();
group('build artifacts', () {
FileSystem fileSystem;
setUp(() {
fileSystem = MemoryFileSystem.test();
});
testWithoutContext('getApkDirectory in app projects', () {
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
expect(
getApkDirectory(project).path, '/build/app/outputs/flutter-apk',
);
});
testWithoutContext('getApkDirectory in module projects', () {
fileSystem.currentDirectory
.childFile('pubspec.yaml')
.writeAsStringSync(kModulePubspec);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
expect(project.isModule, true);
expect(
getApkDirectory(project).path, '/build/host/outputs/apk',
);
});
testWithoutContext('getBundleDirectory in app projects', () {
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
expect(
getBundleDirectory(project).path, '/build/app/outputs/bundle',
);
});
testWithoutContext('getBundleDirectory in module projects', () {
fileSystem.currentDirectory
.childFile('pubspec.yaml')
.writeAsStringSync(kModulePubspec);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
expect(project.isModule, true);
expect(
getBundleDirectory(project).path, '/build/host/outputs/bundle',
);
});
testWithoutContext('getRepoDirectory', () {
expect(
getRepoDirectory(fileSystem.directory('foo')).path,
equals(fileSystem.path.join('foo','outputs', 'repo')),
);
});
});
group('gradle tasks', () {
testWithoutContext('assemble release', () {
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
equals('assembleRelease'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.release, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooRelease'),
);
});
testWithoutContext('assemble debug', () {
expect(
getAssembleTaskFor(BuildInfo.debug),
equals('assembleDebug'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.debug, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooDebug'),
);
});
testWithoutContext('assemble profile', () {
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, null, treeShakeIcons: false)),
equals('assembleProfile'),
);
expect(
getAssembleTaskFor(const BuildInfo(BuildMode.profile, 'flavorFoo', treeShakeIcons: false)),
equals('assembleFlavorFooProfile'),
);
});
});
group('listApkPaths', () {
testWithoutContext('Finds APK without flavor in release', () {
final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, '', treeShakeIcons: false)),
);
expect(apks, <String>['app-release.apk']);
});
testWithoutContext('Finds APK with flavor in release mode', () {
final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
);
expect(apks, <String>['app-flavor1-release.apk']);
});
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']);
});
testWithoutContext('Finds APK with flavor in release mode - AGP v3', () {
final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false)),
);
expect(apks, <String>['app-flavor1-release.apk']);
});
testWithoutContext('Finds APK with split-per-abi', () {
final Iterable<String> apks = listApkPaths(
const AndroidBuildInfo(BuildInfo(BuildMode.release, 'flavor1', treeShakeIcons: false), splitPerAbi: true),
);
expect(apks, unorderedEquals(<String>[
'app-armeabi-v7a-flavor1-release.apk',
'app-arm64-v8a-flavor1-release.apk',
'app-x86_64-flavor1-release.apk',
]));
});
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',
]));
});
});
group('gradle build', () {
testUsingContext('do not crash if there is no Android SDK', () async {
expect(() {
updateLocalProperties(project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory));
}, throwsToolExit(
message: '${globals.logger.terminal.warningMark} No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.',
));
}, overrides: <Type, Generator>{
AndroidSdk: () => null,
});
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.',
];
for (final String m in nonMatchingLines) {
expect(androidXPluginWarningRegex.hasMatch(m), isFalse);
}
for (final String m in matchingLines) {
expect(androidXPluginWarningRegex.hasMatch(m), isTrue);
}
});
});
group('Config files', () {
Directory tempDir;
FileSystem fileSystem;
setUp(() {
fileSystem = MemoryFileSystem.test();
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_settings_aar_test.');
});
testUsingContext('create settings_aar.gradle when current settings.gradle loads plugins', () {
const String currentSettingsGradle = r'''
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()
if (pluginDirectory.exists()) {
include ":$name"
project(":$name").projectDir = pluginDirectory
}
}
''';
const String settingsAarFile = '''
include ':app'
''';
tempDir.childFile('settings.gradle').writeAsStringSync(currentSettingsGradle);
final String toolGradlePath = fileSystem.path.join(
fileSystem.path.absolute(Cache.flutterRoot),
'packages',
'flutter_tools',
'gradle');
fileSystem.directory(toolGradlePath).createSync(recursive: true);
fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings.gradle.legacy_versions'))
.writeAsStringSync(currentSettingsGradle);
fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings_aar.gradle.tmpl'))
.writeAsStringSync(settingsAarFile);
createSettingsAarGradle(tempDir, testLogger);
expect(testLogger.statusText, contains('created successfully'));
expect(tempDir.childFile('settings_aar.gradle').existsSync(), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext("create settings_aar.gradle when current settings.gradle doesn't load plugins", () {
const String currentSettingsGradle = '''
include ':app'
''';
const String settingsAarFile = '''
include ':app'
''';
tempDir.childFile('settings.gradle').writeAsStringSync(currentSettingsGradle);
final String toolGradlePath = fileSystem.path.join(
fileSystem.path.absolute(Cache.flutterRoot),
'packages',
'flutter_tools',
'gradle');
fileSystem.directory(toolGradlePath).createSync(recursive: true);
fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings.gradle.legacy_versions'))
.writeAsStringSync(currentSettingsGradle);
fileSystem.file(fileSystem.path.join(toolGradlePath, 'settings_aar.gradle.tmpl'))
.writeAsStringSync(settingsAarFile);
createSettingsAarGradle(tempDir, testLogger);
expect(testLogger.statusText, contains('created successfully'));
expect(tempDir.childFile('settings_aar.gradle').existsSync(), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
});
group('Gradle local.properties', () {
Artifacts localEngineArtifacts;
FileSystem fs;
setUp(() {
fs = MemoryFileSystem.test();
localEngineArtifacts = Artifacts.test(localEngine: 'out/android_arm');
});
void testUsingAndroidContext(String description, dynamic Function() testMethod) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
Artifacts: () => localEngineArtifacts,
Platform: () => FakePlatform(operatingSystem: 'linux'),
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
}
String propertyFor(String key, File file) {
final Iterable<String> result = file.readAsLinesSync()
.where((String line) => line.startsWith('$key='))
.map((String line) => line.split('=')[1]);
return result.isEmpty ? null : result.first;
}
Future<void> checkBuildVersion({
String manifest,
BuildInfo buildInfo,
String expectedBuildName,
String expectedBuildNumber,
}) async {
final File manifestFile = globals.fs.file('path/to/project/pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync(manifest);
updateLocalProperties(
project: FlutterProject.fromDirectoryTest(globals.fs.directory('path/to/project')),
buildInfo: buildInfo,
requireAndroidSdk: false,
);
final File localPropertiesFile = globals.fs.file('path/to/project/android/local.properties');
expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName);
expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber);
}
testUsingAndroidContext('extract build name and number from pubspec.yaml', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: '1',
);
});
testUsingAndroidContext('extract build name from pubspec.yaml', () async {
const String manifest = '''
name: test
version: 1.0.0
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: null,
);
});
testUsingAndroidContext('allow build info to override build name', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '1',
);
});
testUsingAndroidContext('allow build info to override build number', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.0',
expectedBuildNumber: '3',
);
});
testUsingAndroidContext('allow build info to override build name and number', () async {
const String manifest = '''
name: test
version: 1.0.0+1
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
testUsingAndroidContext('allow build info to override build name and set number', () async {
const String manifest = '''
name: test
version: 1.0.0
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
testUsingAndroidContext('allow build info to set build name and number', () async {
const String manifest = '''
name: test
dependencies:
flutter:
sdk: flutter
flutter:
''';
const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
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,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
expectedBuildName: null,
expectedBuildNumber: null,
);
await checkBuildVersion(
manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: '3', treeShakeIcons: false),
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
await checkBuildVersion(
manifest: manifest,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: '1.0.3', buildNumber: '4', treeShakeIcons: false),
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,
buildInfo: const BuildInfo(BuildMode.release, null, buildName: null, buildNumber: null, treeShakeIcons: false),
expectedBuildName: null,
expectedBuildNumber: null,
);
});
});
group('gradle version', () {
testWithoutContext('should be compatible with the Android plugin version', () {
// 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');
expect(getGradleVersionFor('3.4.0'), '5.6.2');
expect(getGradleVersionFor('3.5.0'), '5.6.2');
expect(getGradleVersionFor('4.0.0'), '6.7');
expect(getGradleVersionFor('4.1.0'), '6.7');
});
testWithoutContext('throws on unsupported versions', () {
expect(() => getGradleVersionFor('3.6.0'),
throwsA(predicate<Exception>((Exception e) => e is ToolExit)));
});
});
group('isAppUsingAndroidX', () {
FileSystem fs;
setUp(() {
fs = MemoryFileSystem.test();
});
testUsingContext('returns true when the project is using AndroidX', () async {
final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
androidDirectory
.childFile('gradle.properties')
.writeAsStringSync('android.useAndroidX=true');
expect(isAppUsingAndroidX(androidDirectory), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns false when the project is not using AndroidX', () async {
final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
androidDirectory
.childFile('gradle.properties')
.writeAsStringSync('android.useAndroidX=false');
expect(isAppUsingAndroidX(androidDirectory), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('returns false when gradle.properties does not exist', () async {
final Directory androidDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_android.');
expect(isAppUsingAndroidX(androidDirectory), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
});
});
group('buildPluginsAsAar', () {
FileSystem fs;
FakeProcessManager fakeProcessManager;
FakeAndroidSdk androidSdk;
AndroidGradleBuilder builder;
BufferLogger logger;
Platform platform;
setUp(() {
logger = BufferLogger.test();
fs = MemoryFileSystem.test();
fakeProcessManager = FakeProcessManager.empty();
androidSdk = FakeAndroidSdk();
platform = FakePlatform(operatingSystem: 'linux');
builder = AndroidGradleBuilder(
logger: logger,
processManager: fakeProcessManager,
fileSystem: fs,
artifacts: Artifacts.test(),
usage: TestUsage(),
gradleUtils: FakeGradleUtils(),
platform: platform,
);
});
testUsingContext('calls gradle', () async {
final Directory androidDirectory = globals.fs.directory('android.');
androidDirectory.createSync();
androidDirectory
.childFile('pubspec.yaml')
.writeAsStringSync('name: irrelevant');
final Directory plugin1 = globals.fs.directory('plugin1.');
plugin1
..createSync()
..childFile('pubspec.yaml')
.writeAsStringSync('''
name: irrelevant
flutter:
plugin:
androidPackage: irrelevant
''');
plugin1
.childDirectory('android')
.childFile('build.gradle')
.createSync(recursive: true);
final Directory plugin2 = globals.fs.directory('plugin2.');
plugin2
..createSync()
..childFile('pubspec.yaml')
.writeAsStringSync('''
name: irrelevant
flutter:
plugin:
androidPackage: irrelevant
''');
plugin2
.childDirectory('android')
.childFile('build.gradle')
.createSync(recursive: true);
androidDirectory
.childFile('.flutter-plugins')
.writeAsStringSync('''
plugin1=${plugin1.path}
plugin2=${plugin2.path}
''');
final Directory buildDirectory = androidDirectory
.childDirectory('build');
buildDirectory
.childDirectory('outputs')
.childDirectory('repo')
.createSync(recursive: true);
final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot);
final String initScript = globals.fs.path.join(
flutterRoot,
'packages',
'flutter_tools',
'gradle',
'aar_init_script.gradle',
);
fakeProcessManager.addCommands(<FakeCommand>[
FakeCommand(
command: <String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-PbuildNumber=1.0',
'-q',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=true',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'assembleAarRelease',
],
workingDirectory: plugin1.childDirectory('android').path,
),
FakeCommand(
command: <String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-PbuildNumber=1.0',
'-q',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=true',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'assembleAarRelease',
],
workingDirectory: plugin2.childDirectory('android').path,
)]);
await builder.buildPluginsAsAar(
FlutterProject.fromDirectoryTest(androidDirectory),
const AndroidBuildInfo(BuildInfo(
BuildMode.release,
'',
treeShakeIcons: true,
dartObfuscation: true,
buildNumber: '2.0'
)),
buildDirectory: buildDirectory,
);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
AndroidSdk: () => androidSdk,
FileSystem: () => fs,
Platform: () => platform,
ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(),
});
testUsingContext('skips plugin without a android/build.gradle file', () async {
final Directory androidDirectory = globals.fs.directory('android.');
androidDirectory.createSync();
androidDirectory
.childFile('pubspec.yaml')
.writeAsStringSync('name: irrelevant');
final Directory plugin1 = globals.fs.directory('plugin1.');
plugin1
..createSync()
..childFile('pubspec.yaml')
.writeAsStringSync('''
name: irrelevant
flutter:
plugin:
androidPackage: irrelevant
''');
androidDirectory
.childFile('.flutter-plugins')
.writeAsStringSync('''
plugin1=${plugin1.path}
''');
// Create an empty android directory.
// https://github.com/flutter/flutter/issues/46898
plugin1.childDirectory('android').createSync();
final Directory buildDirectory = androidDirectory.childDirectory('build');
buildDirectory
.childDirectory('outputs')
.childDirectory('repo')
.createSync(recursive: true);
await builder.buildPluginsAsAar(
FlutterProject.fromDirectoryTest(androidDirectory),
const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory,
);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
AndroidSdk: () => androidSdk,
FileSystem: () => fs,
ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(),
});
});
group('printHowToConsumeAar', () {
BufferLogger logger;
FileSystem fileSystem;
setUp(() {
logger = BufferLogger.test();
fileSystem = MemoryFileSystem.test();
});
testWithoutContext('stdout contains release, debug and profile', () async {
printHowToConsumeAar(
buildModes: const <String>{'release', 'debug', 'profile'},
androidPackage: 'com.mycompany',
repoDirectory: fileSystem.directory('build/'),
buildNumber: '2.2',
logger: logger,
fileSystem: fileSystem,
);
expect(
logger.statusText,
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'
' String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
' repositories {\n'
' maven {\n'
" url 'build/'\n"
' }\n'
' maven {\n'
' url "\$storageUrl/download.flutter.io"\n'
' }\n'
' }\n'
'\n'
' 3. Make the host app depend on the Flutter module:\n'
'\n'
' dependencies {\n'
" releaseImplementation 'com.mycompany:flutter_release:2.2'\n"
" debugImplementation 'com.mycompany:flutter_debug:2.2'\n"
" profileImplementation 'com.mycompany:flutter_profile:2.2'\n"
' }\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'
)
);
});
testWithoutContext('stdout contains release', () async {
printHowToConsumeAar(
buildModes: const <String>{'release'},
androidPackage: 'com.mycompany',
repoDirectory: fileSystem.directory('build/'),
logger: logger,
fileSystem: fileSystem,
);
expect(
logger.statusText,
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'
' String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
' repositories {\n'
' maven {\n'
" url 'build/'\n"
' }\n'
' maven {\n'
' url "\$storageUrl/download.flutter.io"\n'
' }\n'
' }\n'
'\n'
' 3. Make the host app depend on the Flutter module:\n'
'\n'
' dependencies {\n'
" releaseImplementation 'com.mycompany:flutter_release:1.0'\n"
' }\n'
'\n'
'To learn more, visit https://flutter.dev/go/build-aar\n'
)
);
});
testWithoutContext('stdout contains debug', () async {
printHowToConsumeAar(
buildModes: const <String>{'debug'},
androidPackage: 'com.mycompany',
repoDirectory: fileSystem.directory('build/'),
logger: logger,
fileSystem: fileSystem,
);
expect(
logger.statusText,
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'
' String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
' repositories {\n'
' maven {\n'
" url 'build/'\n"
' }\n'
' maven {\n'
' url "\$storageUrl/download.flutter.io"\n'
' }\n'
' }\n'
'\n'
' 3. Make the host app depend on the Flutter module:\n'
'\n'
' dependencies {\n'
" debugImplementation 'com.mycompany:flutter_debug:1.0'\n"
' }\n'
'\n'
'To learn more, visit https://flutter.dev/go/build-aar\n'
)
);
});
testWithoutContext('stdout contains profile', () async {
printHowToConsumeAar(
buildModes: const <String>{'profile'},
androidPackage: 'com.mycompany',
repoDirectory: fileSystem.directory('build/'),
buildNumber: '1.0',
logger: logger,
fileSystem: fileSystem,
);
expect(
logger.statusText,
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'
' String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"\n'
' repositories {\n'
' maven {\n'
" url 'build/'\n"
' }\n'
' maven {\n'
' url "\$storageUrl/download.flutter.io"\n'
' }\n'
' }\n'
'\n'
' 3. Make the host app depend on the Flutter module:\n'
'\n'
' dependencies {\n'
" profileImplementation 'com.mycompany:flutter_profile:1.0'\n"
' }\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'
)
);
});
});
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()),
);
}, skip: true); // TODO(jonahwilliams): This is an integration test and should be moved to the integration shard.
}
class FakeGradleUtils extends GradleUtils {
@override
String getExecutable(FlutterProject project) {
return 'gradlew';
}
}
class FakeAndroidSdk extends Fake implements AndroidSdk {}