flutter/packages/flutter_tools/test/android/gradle_test.dart
Jason Simmons e031613a2a
Use "gradle tasks --all" to query build variants (#21761)
Previously flutter_tools had used "gradle properties" to find the build types
and flavors supported by the Gradle project.  Tasks should work more reliably
across different versions of the Android Gradle plugin.

Fixes https://github.com/flutter/flutter/issues/20781
2018-10-04 10:06:31 -07:00

338 lines
12 KiB
Dart

// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:mockito/mockito.dart';
import 'package:platform/platform.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/pubspec_schema.dart';
void main() {
Cache.flutterRoot = getFlutterRoot();
group('gradle build', () {
test('do not crash if there is no Android SDK', () async {
Exception shouldBeToolExit;
try {
// We'd like to always set androidSdk to null and test updateLocalProperties. But that's
// currently impossible as the test is not hermetic. Luckily, our bots don't have Android
// SDKs yet so androidSdk should be null by default.
//
// This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit
// will be null and our expectation would fail. That would remind us to make these tests
// hermetic before adding Android SDKs to the bots.
updateLocalProperties(project: await FlutterProject.current());
} on Exception catch (e) {
shouldBeToolExit = e;
}
// Ensure that we throw a meaningful ToolExit instead of a general crash.
expect(shouldBeToolExit, isToolExit);
});
test('regexp should only match lines without the error message', () {
final List<String> nonMatchingLines = <String>[
'NDK is missing a "platforms" directory.',
'If you are using NDK, verify the ndk.dir is set to a valid NDK directory. It is currently set to /usr/local/company/home/username/Android/Sdk/ndk-bundle.',
'If you are not using NDK, unset the NDK variable from ANDROID_NDK_HOME or local.properties to remove this warning.',
];
final List<String> matchingLines = <String>[
':app:preBuild UP-TO-DATE',
'BUILD SUCCESSFUL in 0s',
'',
'Something NDK related mentioning ANDROID_NDK_HOME',
];
for (String m in nonMatchingLines) {
expect(ndkMessageFilter.hasMatch(m), isFalse);
}
for (String m in matchingLines) {
expect(ndkMessageFilter.hasMatch(m), isTrue);
}
});
});
group('gradle project', () {
GradleProject projectFrom(String properties, String tasks) => GradleProject.fromAppProperties(properties, tasks);
test('should extract build directory from app properties', () {
final GradleProject project = projectFrom('''
someProperty: someValue
buildDir: /Users/some/apps/hello/build/app
someOtherProperty: someOtherValue
''', '');
expect(
fs.path.normalize(project.apkDirectory.path),
fs.path.normalize('/Users/some/apps/hello/build/app/outputs/apk'),
);
});
test('should extract default build variants from app properties', () {
final GradleProject project = projectFrom('buildDir: /Users/some/apps/hello/build/app', '''
someTask
assemble
assembleAndroidTest
assembleDebug
assembleProfile
assembleRelease
someOtherTask
''');
expect(project.buildTypes, <String>['debug', 'profile', 'release']);
expect(project.productFlavors, isEmpty);
});
test('should extract custom build variants from app properties', () {
final GradleProject project = projectFrom('buildDir: /Users/some/apps/hello/build/app', '''
someTask
assemble
assembleAndroidTest
assembleDebug
assembleFree
assembleFreeAndroidTest
assembleFreeDebug
assembleFreeProfile
assembleFreeRelease
assemblePaid
assemblePaidAndroidTest
assemblePaidDebug
assemblePaidProfile
assemblePaidRelease
assembleProfile
assembleRelease
someOtherTask
''');
expect(project.buildTypes, <String>['debug', 'profile', 'release']);
expect(project.productFlavors, <String>['free', 'paid']);
});
test('should provide apk file name for default build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'));
expect(project.apkFileFor(BuildInfo.debug), 'app-debug.apk');
expect(project.apkFileFor(BuildInfo.profile), 'app-profile.apk');
expect(project.apkFileFor(BuildInfo.release), 'app-release.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide apk file name for flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'));
expect(project.apkFileFor(const BuildInfo(BuildMode.debug, 'free')), 'app-free-debug.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'paid')), 'app-paid-release.apk');
expect(project.apkFileFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide assemble task name for default build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'));
expect(project.assembleTaskFor(BuildInfo.debug), 'assembleDebug');
expect(project.assembleTaskFor(BuildInfo.profile), 'assembleProfile');
expect(project.assembleTaskFor(BuildInfo.release), 'assembleRelease');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('should provide assemble task name for flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'));
expect(project.assembleTaskFor(const BuildInfo(BuildMode.debug, 'free')), 'assembleFreeDebug');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'paid')), 'assemblePaidRelease');
expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
});
group('Gradle local.properties', () {
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
FakePlatform android;
FileSystem fs;
setUp(() {
fs = MemoryFileSystem();
mockArtifacts = MockLocalEngineArtifacts();
mockProcessManager = MockProcessManager();
android = fakePlatform('android');
});
void testUsingAndroidContext(String description, dynamic testMethod()) {
testUsingContext(description, testMethod, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
ProcessManager: () => mockProcessManager,
Platform: () => android,
FileSystem: () => fs,
});
}
String propertyFor(String key, File file) {
return file
.readAsLinesSync()
.where((String line) => line.startsWith('$key='))
.map((String line) => line.split('=')[1])
.first;
}
Future<void> checkBuildVersion({
String manifest,
BuildInfo buildInfo,
String expectedBuildName,
String expectedBuildNumber,
}) async {
when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.android_arm, any)).thenReturn('engine');
when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'android_arm'));
final File manifestFile = fs.file('path/to/project/pubspec.yaml');
manifestFile.createSync(recursive: true);
manifestFile.writeAsStringSync(manifest);
// write schemaData otherwise pubspec.yaml file can't be loaded
writeEmptySchemaFile(fs);
try {
updateLocalProperties(
project: await FlutterProject.fromPath('path/to/project'),
buildInfo: buildInfo,
);
final File localPropertiesFile = fs.file('path/to/project/android/local.properties');
expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName);
expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber);
} on Exception {
// Android SDK not found, skip test
}
}
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);
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);
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');
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);
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);
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);
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);
await checkBuildVersion(
manifest: manifest,
buildInfo: buildInfo,
expectedBuildName: '1.0.2',
expectedBuildNumber: '3',
);
});
});
}
Platform fakePlatform(String name) {
return FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
}
class MockLocalEngineArtifacts extends Mock implements LocalEngineArtifacts {}
class MockProcessManager extends Mock implements ProcessManager {}
class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}