Unverified Commit c9b466f9 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Revert "Add flutter build aar (#35217)" (#36731)

This reverts commit 11460b83.
parent 11460b83
......@@ -961,14 +961,9 @@ Future<void> _androidGradleTests(String subShard) async {
if (subShard == 'gradle1') {
await _runDevicelabTest('gradle_plugin_light_apk_test', env: env);
await _runDevicelabTest('gradle_plugin_fat_apk_test', env: env);
await _runDevicelabTest('gradle_jetifier_test', env: env);
await _runDevicelabTest('gradle_plugin_dependencies_test', env: env);
await _runDevicelabTest('gradle_migrate_settings_test', env: env);
}
if (subShard == 'gradle2') {
await _runDevicelabTest('gradle_plugin_bundle_test', env: env);
await _runDevicelabTest('module_test', env: env);
await _runDevicelabTest('build_aar_plugin_test', env: env);
await _runDevicelabTest('build_aar_module_test', env: env);
}
}
// Copyright (c) 2019 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 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew';
/// Tests that AARs can be built on module projects.
Future<void> main() async {
await task(() async {
section('Find Java');
final String javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create module project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>['--org', 'io.flutter.devicelab', '--template', 'module', 'hello'],
);
});
section('Add plugins');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = pubspec.readAsStringSync();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n device_info:\n package_info:\n',
);
pubspec.writeAsStringSync(content, flush: true);
await inDirectory(projectDir, () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build release AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['aar', '--verbose'],
);
});
final String repoPath = path.join(
projectDir.path,
'build',
'host',
'outputs',
'repo',
);
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'flutter_release',
'1.0',
'flutter_release-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'flutter_release',
'1.0',
'flutter_release-1.0.pom',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'deviceinfo',
'device_info_release',
'1.0',
'device_info_release-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'deviceinfo',
'device_info_release',
'1.0',
'device_info_release-1.0.pom',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'packageinfo',
'package_info_release',
'1.0',
'package_info_release-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'packageinfo',
'package_info_release',
'1.0',
'package_info_release-1.0.pom',
));
section('Build debug AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['aar', '--verbose', '--debug'],
);
});
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'flutter_release',
'1.0',
'flutter_release-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'flutter_debug',
'1.0',
'flutter_debug-1.0.pom',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'deviceinfo',
'device_info_debug',
'1.0',
'device_info_debug-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'deviceinfo',
'device_info_debug',
'1.0',
'device_info_debug-1.0.pom',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'packageinfo',
'package_info_debug',
'1.0',
'package_info_debug-1.0.aar',
));
checkFileExists(path.join(
repoPath,
'io',
'flutter',
'plugins',
'packageinfo',
'package_info_debug',
'1.0',
'package_info_debug-1.0.pom',
));
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
// Copyright (c) 2019 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 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew';
/// Tests that AARs can be built on plugin projects.
Future<void> main() async {
await task(() async {
section('Find Java');
final String javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create plugin project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>[
'--org', 'io.flutter.devicelab',
'--template', 'plugin',
'hello',
],
);
});
section('Build release AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['aar', '--verbose'],
);
});
final String repoPath = path.join(
projectDir.path,
'build',
'outputs',
'repo',
);
final File releaseAar = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_release',
'1.0',
'hello_release-1.0.aar',
));
if (!exists(releaseAar)) {
return TaskResult.failure('Failed to build the release AAR file.');
}
final File releasePom = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_release',
'1.0',
'hello_release-1.0.pom',
));
if (!exists(releasePom)) {
return TaskResult.failure('Failed to build the release POM file.');
}
section('Build debug AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'aar',
'--verbose',
'--debug',
],
);
});
final File debugAar = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_debug',
'1.0',
'hello_debug-1.0.aar',
));
if (!exists(debugAar)) {
return TaskResult.failure('Failed to build the debug AAR file.');
}
final File debugPom = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_debug',
'1.0',
'hello_debug-1.0.pom',
));
if (!exists(debugPom)) {
return TaskResult.failure('Failed to build the debug POM file.');
}
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
// Copyright (c) 2019 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 'dart:io';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew';
/// Tests that Jetifier can translate plugins that use support libraries.
Future<void> main() async {
await task(() async {
section('Find Java');
final String javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create Flutter AndroidX app project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>[
'--org', 'io.flutter.devicelab',
'--androidx',
'hello',
],
);
});
section('Add plugin that uses support libraries');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = pubspec.readAsStringSync();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n firebase_auth: 0.7.0\n',
);
pubspec.writeAsStringSync(content, flush: true);
await inDirectory(projectDir, () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build release APK');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--verbose',
],
);
});
final File releaseApk = File(path.join(
projectDir.path,
'build',
'app',
'outputs',
'apk',
'release',
'app-release.apk',
));
if (!exists(releaseApk)) {
return TaskResult.failure('Failed to build release APK.');
}
checkApkContainsClasses(releaseApk, <String>[
// The plugin class defined by `firebase_auth`.
'io.flutter.plugins.firebaseauth.FirebaseAuthPlugin',
// Used by `firebase_auth`.
'com.google.firebase.FirebaseApp',
// Base class for activities that enables composition of higher level components.
'androidx.core.app.ComponentActivity',
]);
section('Build debug APK');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--debug', '--verbose',
],
);
});
final File debugApk = File(path.join(
projectDir.path,
'build',
'app',
'outputs',
'apk',
'debug',
'app-debug.apk',
));
if (!exists(debugApk)) {
return TaskResult.failure('Failed to build debug APK.');
}
checkApkContainsClasses(debugApk, <String>[
// The plugin class defined by `firebase_auth`.
'io.flutter.plugins.firebaseauth.FirebaseAuthPlugin',
// Used by `firebase_auth`.
'com.google.firebase.FirebaseApp',
// Base class for activities that enables composition of higher level components.
'androidx.core.app.ComponentActivity',
]);
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
// Copyright (c) 2019 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 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew';
/// Tests that [settings_aar.gradle] is created when possible.
Future<void> main() async {
await task(() async {
section('Find Java');
final String javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create app project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>['hello'],
);
});
section('Override settings.gradle V1');
final String relativeNewSettingsGradle = path.join('android', 'settings_aar.gradle');
section('Build APK');
String stdout;
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
);
});
const String newFileContent = 'include \':app\'';
final File settingsGradle = File(path.join(projectDir.path, 'android', 'settings.gradle'));
final File newSettingsGradle = File(path.join(projectDir.path, 'android', 'settings_aar.gradle'));
if (!newSettingsGradle.existsSync()) {
return TaskResult.failure('Expected file: `${newSettingsGradle.path}`.');
}
if (newSettingsGradle.readAsStringSync().trim() != newFileContent) {
return TaskResult.failure('Expected to create `${newSettingsGradle.path}` V1.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`') ||
!stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Expected update message in stdout.');
}
section('Override settings.gradle V2');
const String deprecatedFileContentV2 = '''
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":\$name"
project(":\$name").projectDir = pluginDirectory
}
''';
settingsGradle.writeAsStringSync(deprecatedFileContentV2, flush: true);
newSettingsGradle.deleteSync();
section('Build APK');
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
);
});
if (newSettingsGradle.readAsStringSync().trim() != newFileContent) {
return TaskResult.failure('Expected to create `${newSettingsGradle.path}` V2.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`') ||
!stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Expected update message in stdout.');
}
section('Override settings.gradle with custom logic');
const String customDeprecatedFileContent = '''
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":\$name"
project(":\$name").projectDir = pluginDirectory
}
// some custom logic
''';
settingsGradle.writeAsStringSync(customDeprecatedFileContent, flush: true);
newSettingsGradle.deleteSync();
section('Build APK');
final StringBuffer stderr = StringBuffer();
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
stderr: stderr,
);
});
if (newSettingsGradle.existsSync()) {
return TaskResult.failure('Unexpected file: `${newSettingsGradle.path}`.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`')) {
return TaskResult.failure('Expected update message in stdout.');
}
if (stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Unexpected message in stdout.');
}
if (!stderr.toString().contains('Flutter tried to create the file '
'`$relativeNewSettingsGradle`, but failed.')) {
return TaskResult.failure('Expected failure message in stdout.');
}
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
// Copyright (c) 2019 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 'dart:io';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew';
/// Tests that projects can include plugins that have a transtive dependency in common.
/// For more info see: https://github.com/flutter/flutter/issues/27254.
Future<void> main() async {
await task(() async {
section('Find Java');
final String javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create Flutter AndroidX app project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>[
'--org', 'io.flutter.devicelab',
'--androidx',
'hello',
],
);
});
section('Add plugin that have conflicting dependencies');
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
String content = pubspec.readAsStringSync();
// `flutter_local_notifications` uses `androidx.core:core:1.0.1`
// `firebase_core` and `firebase_messaging` use `androidx.core:core:1.0.0`.
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n flutter_local_notifications: 0.7.1+3\n firebase_core:\n firebase_messaging:\n',
);
pubspec.writeAsStringSync(content, flush: true);
await inDirectory(projectDir, () async {
await flutter(
'packages',
options: <String>['get'],
);
});
section('Build release APK');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--verbose',
],
);
});
final File releaseApk = File(path.join(
projectDir.path,
'build',
'app',
'outputs',
'apk',
'release',
'app-release.apk',
));
if (!exists(releaseApk)) {
return TaskResult.failure('Failed to build release APK.');
}
checkApkContainsClasses(releaseApk, <String>[
// Used by `flutter_local_notifications`.
'com.google.gson.Gson',
// Used by `firebase_core` and `firebase_messaging`.
'com.google.firebase.FirebaseApp',
// Used by `firebase_core`.
'com.google.firebase.FirebaseOptions',
// Used by `firebase_messaging`.
'com.google.firebase.messaging.FirebaseMessaging',
]);
section('Build debug APK');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--debug',
'--verbose',
],
);
});
final File debugApk = File(path.join(
projectDir.path,
'build',
'app',
'outputs',
'apk',
'debug',
'app-debug.apk',
));
if (!exists(debugApk)) {
return TaskResult.failure('Failed to build debug APK.');
}
checkApkContainsClasses(debugApk, <String>[
// Used by `flutter_local_notifications`.
'com.google.gson.Gson',
// Used by `firebase_core` and `firebase_messaging`.
'com.google.firebase.FirebaseApp',
// Used by `firebase_core`.
'com.google.firebase.FirebaseOptions',
// Used by `firebase_messaging`.
'com.google.firebase.messaging.FirebaseMessaging',
]);
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
......@@ -42,7 +42,7 @@ Future<void> main() async {
String content = await pubspec.readAsString();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n device_info:\n package_info:\n',
'\ndependencies:\n battery:\n package_info:\n',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
......
......@@ -143,7 +143,7 @@ Future<void> main() async {
String content = await pubspec.readAsString();
content = content.replaceFirst(
'\ndependencies:\n',
'\ndependencies:\n device_info:\n package_info:\n',
'\ndependencies:\n battery:\n package_info:\n',
);
await pubspec.writeAsString(content, flush: true);
await inDirectory(projectDir, () async {
......
......@@ -83,93 +83,6 @@ bool hasMultipleOccurrences(String text, Pattern pattern) {
return text.indexOf(pattern) != text.lastIndexOf(pattern);
}
/// Utility class to analyze the content inside an APK using dexdump,
/// which is provided by the Android SDK.
/// https://android.googlesource.com/platform/art/+/master/dexdump/dexdump.cc
class ApkExtractor {
ApkExtractor(this.apkFile);
/// The APK.
final File apkFile;
bool _extracted = false;
Directory _outputDir;
Future<void> _extractApk() async {
if (_extracted) {
return;
}
_outputDir = apkFile.parent.createTempSync('apk');
if (Platform.isWindows) {
await eval('7za', <String>['x', apkFile.path], workingDirectory: _outputDir.path);
} else {
await eval('unzip', <String>[apkFile.path], workingDirectory: _outputDir.path);
}
_extracted = true;
}
/// Returns the full path to the [dexdump] tool.
Future<String> _findDexDump() async {
final String androidHome = Platform.environment['ANDROID_HOME'] ??
Platform.environment['ANDROID_SDK_ROOT'];
if (androidHome == null || androidHome.isEmpty) {
throw Exception('Unset env flag: `ANDROID_HOME` or `ANDROID_SDK_ROOT`.');
}
String dexdumps;
if (Platform.isWindows) {
dexdumps = await eval('dir', <String>['/s/b', 'dexdump.exe'],
workingDirectory: androidHome);
} else {
dexdumps = await eval('find', <String>[androidHome, '-name', 'dexdump']);
}
if (dexdumps.isEmpty) {
throw Exception('Couldn\'t find a dexdump executable.');
}
return dexdumps.split('\n').first;
}
// Removes any temporary directory.
void dispose() {
if (!_extracted) {
return;
}
rmTree(_outputDir);
_extracted = true;
}
/// Returns true if the APK contains a given class.
Future<bool> containsClass(String className) async {
await _extractApk();
final String dexDump = await _findDexDump();
final String classesDex = path.join(_outputDir.path, 'classes.dex');
if (!File(classesDex).existsSync()) {
throw Exception('Couldn\'t find classes.dex in the APK.');
}
final String classDescriptors = await eval(dexDump,
<String>[classesDex], printStdout: false);
if (classDescriptors.isEmpty) {
throw Exception('No descriptors found in classes.dex.');
}
return classDescriptors.contains(className.replaceAll('.', '/'));
}
}
/// Checks that the classes are contained in the APK, throws otherwise.
Future<void> checkApkContainsClasses(File apk, List<String> classes) async {
final ApkExtractor extractor = ApkExtractor(apk);
for (String className in classes) {
if (!(await extractor.containsClass(className))) {
throw Exception('APK doesn\'t contain class `$className`.');
}
}
extractor.dispose();
}
class FlutterProject {
FlutterProject(this.parent, this.name);
......
......@@ -303,7 +303,7 @@ Future<int> exec(
/// Executes a command and returns its standard output as a String.
///
/// For logging purposes, the command's output is also printed out by default.
/// For logging purposes, the command's output is also printed out.
Future<String> eval(
String executable,
List<String> arguments, {
......@@ -311,8 +311,6 @@ Future<String> eval(
bool canFail = false, // as in, whether failures are ok. False means that they are fatal.
String workingDirectory,
StringBuffer stderr, // if not null, the stderr will be written here
bool printStdout = true,
bool printStderr = true,
}) async {
final Process process = await startProcess(executable, arguments, environment: environment, workingDirectory: workingDirectory);
......@@ -323,18 +321,14 @@ Future<String> eval(
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
if (printStdout) {
print('stdout: $line');
}
print('stdout: $line');
output.writeln(line);
}, onDone: () { stdoutDone.complete(); });
process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String line) {
if (printStderr) {
print('stderr: $line');
}
print('stderr: $line');
stderr?.writeln(line);
}, onDone: () { stderrDone.complete(); });
......@@ -625,10 +619,3 @@ void setLocalEngineOptionIfNecessary(List<String> options, [String flavor]) {
options.add('--local-engine=${osNames[deviceOperatingSystem]}_$flavor');
}
}
/// Checks that the file exists, otherwise throws a [FileSystemException].
void checkFileExists(String file) {
if (!exists(File(file))) {
throw FileSystemException('Expected file to exit.', file);
}
}
// Copyright 2019 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.
//
// This script is used to initialize the build in a module or plugin project.
// During this phase, the script applies the Maven plugin and configures the
// destination of the local repository.
// The local repository will contain the AAR and POM files.
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.maven.MavenDeployer
import org.gradle.api.plugins.MavenPlugin
import org.gradle.api.tasks.Upload
void configureProject(Project project, File outputDir) {
if (!project.hasProperty("android")) {
throw new GradleException("Android property not found.")
}
if (!project.android.hasProperty("libraryVariants")) {
throw new GradleException("Can't generate AAR on a non Android library project.");
}
project.apply plugin: "maven"
project.android.libraryVariants.all { variant ->
addAarTask(project, variant)
}
// Snapshot versions include the timestamp in the artifact name.
// Therefore, remove the snapshot part, so new runs of `flutter build aar` overrides existing artifacts.
// This version isn't relevant in Flutter since the pub version is used
// to resolve dependencies.
project.version = project.version.replace("-SNAPSHOT", "")
project.uploadArchives {
repositories {
mavenDeployer {
repository(url: "file://${outputDir}/outputs/repo")
}
}
}
// Check if the project uses the Flutter plugin (defined in flutter.gradle).
Boolean usesFlutterPlugin = project.plugins.find { it.class.name == "FlutterPlugin" } != null
if (!usesFlutterPlugin) {
// Plugins don't include their dependencies under the assumption that the parent project adds them.
if (project.properties['android.useAndroidX']) {
project.dependencies {
compileOnly "androidx.annotation:annotation:+"
}
} else {
project.dependencies {
compileOnly "com.android.support:support-annotations:+"
}
}
project.dependencies {
// The Flutter plugin already adds `flutter.jar`.
compileOnly project.files("${getFlutterRoot(project)}/bin/cache/artifacts/engine/android-arm-release/flutter.jar")
}
}
}
String getFlutterRoot(Project project) {
if (!project.hasProperty("flutter-root")) {
throw new GradleException("The `-Pflutter-root` flag must be specified.")
}
return project.property("flutter-root")
}
void addAarTask(Project project, variant) {
String variantName = variant.name.capitalize()
String taskName = "assembleAar${variantName}"
project.tasks.create(name: taskName) {
// This check is required to be able to configure the archives before `uploadArchives` runs.
if (!project.gradle.startParameter.taskNames.contains(taskName)) {
return
}
// NOTE(blasten): `android.defaultPublishConfig` must equal the variant name to build.
// Where variant name is `<product-flavor><Build-Type>`. However, it's too late to configure
// `defaultPublishConfig` at this point. Therefore, the code below ensures that the
// default build config uses the artifacts produced for the specific build variant.
Task bundle = project.tasks.findByName("bundle${variantName}Aar") // gradle:3.2.0
if (bundle == null) {
bundle = project.tasks.findByName("bundle${variantName}") // gradle:3.1.0
}
if (bundle == null) {
throw new GradleException("Can't generate AAR for variant ${variantName}.");
}
project.uploadArchives.repositories.mavenDeployer {
pom {
artifactId = "${project.name}_${variant.name.toLowerCase()}"
}
}
// Clear the current archives since the current one is assigned based on
// `android.defaultPublishConfig` which defaults to `release`.
project.configurations["archives"].artifacts.clear()
// Add the artifact that will be published.
project.artifacts.add("archives", bundle)
// Generate the Maven artifacts.
finalizedBy "uploadArchives"
}
}
projectsEvaluated {
if (rootProject.property("is-plugin").toBoolean()) {
if (rootProject.hasProperty("output-dir")) {
rootProject.buildDir = rootProject.property("output-dir")
} else {
rootProject.buildDir = "../build";
}
// In plugin projects, the Android library is the root project.
configureProject(rootProject, rootProject.buildDir)
return
}
// In module projects, the Android library project is the `:flutter` subproject.
Project androidLibrarySubproject = rootProject.subprojects.find { it.name == "flutter" }
// In module projects, the `buildDir` is defined in the `:app` subproject.
Project appSubproject = rootProject.subprojects.find { it.name == "app" }
assert appSubproject != null
assert androidLibrarySubproject != null
if (appSubproject.hasProperty("output-dir")) {
appSubproject.buildDir = appSubproject.property("output-dir")
} else {
appSubproject.buildDir = "../build/host"
}
configureProject(androidLibrarySubproject, appSubproject.buildDir)
}
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()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
;EOF
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
// Copyright 2019 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 static groovy.io.FileType.FILES
import java.nio.file.Path
import java.nio.file.Paths
import com.android.builder.model.AndroidProject
import com.android.build.OutputFile
import java.nio.file.Path
import java.nio.file.Paths
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
......@@ -96,7 +91,7 @@ class FlutterPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create("flutter", FlutterExtension)
project.afterEvaluate this.&addFlutterTasks
project.afterEvaluate this.&addFlutterTask
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
// Configuring split per ABI allows to generate separate APKs for each abi.
......@@ -208,116 +203,40 @@ class FlutterPlugin implements Plugin<Project> {
})
}
}
}
/**
* Returns the directory where the plugins are built.
*/
private File getPluginBuildDir(Project project) {
// Module projects specify this flag to include plugins in the same repo as the module project.
if (project.ext.has("pluginBuildDir")) {
return project.ext.get("pluginBuildDir")
}
return project.buildDir
}
private Properties getPluginList(Project project) {
File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
return readPropertiesIfExist(pluginsFile)
}
private void addPluginTasks(Project project) {
Properties plugins = getPluginList(project)
project.android.buildTypes.each { buildType ->
plugins.each { name, path ->
String buildModeValue = buildType.debuggable ? "debug" : "release"
List<String> taskNameParts = ["build", "plugin", buildModeValue]
taskNameParts.addAll(name.split("_"))
String taskName = toCammelCase(taskNameParts)
// Build types can be extended. For example, a build type can extend the `debug` mode.
// In such cases, prevent creating the same task.
if (project.tasks.findByName(taskName) == null) {
project.tasks.create(name: taskName, type: FlutterPluginTask) {
flutterExecutable this.flutterExecutable
buildMode buildModeValue
verbose isVerbose(project)
pluginDir project.file(path)
sourceDir project.file(project.flutter.source)
intermediateDir getPluginBuildDir(project)
Properties plugins = readPropertiesIfExist(pluginsFile)
plugins.each { name, _ ->
def pluginProject = project.rootProject.findProject(":$name")
if (pluginProject != null) {
project.dependencies {
if (project.getConfigurations().findByName("implementation")) {
implementation pluginProject
} else {
compile pluginProject
}
}
}
}
}
private void buildPlugins(Project project, Set buildTypes) {
List<Project> projects = [project]
// Module projects set the `hostProjects` extra property in `include_flutter.groovy`.
// This is required to set the local repository in each host app project.
if (project.ext.has("hostProjects")) {
projects.addAll(project.ext.get("hostProjects"))
}
projects.each { hostProject ->
hostProject.repositories {
maven {
url "${getPluginBuildDir(project)}/outputs/repo"
}
}
}
buildTypes.each { buildType ->
project.tasks.withType(FlutterPluginTask).all { pluginTask ->
String buildMode = buildType.debuggable ? "debug" : "release"
if (pluginTask.buildMode != buildMode) {
return
}
pluginTask.execute()
pluginTask.intermediateDir.eachFileRecurse(FILES) { file ->
if (file.name != "maven-metadata.xml") {
return
pluginProject.afterEvaluate {
pluginProject.android.buildTypes {
profile {
initWith debug
}
}
def mavenMetadata = new XmlParser().parse(file)
String groupId = mavenMetadata.groupId.text()
String artifactId = mavenMetadata.artifactId.text()
if (!artifactId.endsWith(buildMode)) {
return
pluginProject.android.buildTypes.each {
def buildMode = buildModeFor(it)
addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
}
pluginProject.android.buildTypes.whenObjectAdded {
def buildMode = buildModeFor(it)
addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
}
// Add the plugin dependency based on the Maven metadata.
addApiDependencies(project, buildType.name, "$groupId:$artifactId:+@aar", {
transitive = true
})
}
}
}
}
/**
* Returns a set with the build type names that apply to the given list of tasks
* required to configure the plugin dependencies.
*/
private Set getBuildTypesForTasks(Project project, List<String> tasksToExecute) {
Set buildTypes = []
tasksToExecute.each { task ->
project.android.buildTypes.each { buildType ->
if (task == "androidDependencies" || task.endsWith("dependencies")) {
// The tasks to query the dependencies includes all the build types.
buildTypes.add(buildType)
} else if (task.endsWith("assemble")) {
// The `assemble` task includes all the build types.
buildTypes.add(buildType)
} else if (task.endsWith(buildType.name.capitalize())) {
buildTypes.add(buildType)
}
} else {
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
}
}
return buildTypes
}
private static String toCammelCase(List<String> parts) {
if (parts.empty) {
return ""
}
return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}"
}
private String resolveProperty(Project project, String name, String defaultValue) {
......@@ -368,17 +287,6 @@ class FlutterPlugin implements Plugin<Project> {
return project.hasProperty('localEngineOut')
}
private static Boolean isVerbose(Project project) {
if (project.hasProperty('verbose')) {
return project.property('verbose').toBoolean()
}
return false
}
private static Boolean buildPluginAsAar() {
return System.getProperty('build-plugins-as-aars') == 'true'
}
/**
* Returns the platform that is used to extract the `libflutter.so` and the .class files.
*
......@@ -396,24 +304,30 @@ class FlutterPlugin implements Plugin<Project> {
if (project.state.failure) {
return
}
String configuration;
if (project.getConfigurations().findByName("compileOnly")) {
configuration = "${variantName}CompileOnly";
} else {
configuration = "${variantName}Provided";
project.dependencies {
String configuration;
if (project.getConfigurations().findByName("compileOnly")) {
configuration = "${variantName}CompileOnly";
} else {
configuration = "${variantName}Provided";
}
add(configuration, files)
}
project.dependencies.add(configuration, files)
}
private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
String configuration;
// `compile` dependencies are now `api` dependencies.
if (project.getConfigurations().findByName("api")) {
configuration = "${variantName}Api";
} else {
configuration = "${variantName}Compile";
private static void addApiDependencies(Project project, String variantName, FileCollection files) {
project.dependencies {
String configuration;
// `compile` dependencies are now `api` dependencies.
if (project.getConfigurations().findByName("api")) {
configuration = "${variantName}Api";
} else {
configuration = "${variantName}Compile";
}
add(configuration, files)
}
project.dependencies.add(configuration, dependency, config)
}
/**
......@@ -441,13 +355,14 @@ class FlutterPlugin implements Plugin<Project> {
return "${targetArch}-release"
}
private void addFlutterTasks(Project project) {
private void addFlutterTask(Project project) {
if (project.state.failure) {
return
}
if (project.flutter.source == null) {
throw new GradleException("Must provide Flutter source directory")
}
String target = project.flutter.target
if (target == null) {
target = 'lib/main.dart'
......@@ -456,6 +371,10 @@ class FlutterPlugin implements Plugin<Project> {
target = project.property('target')
}
Boolean verboseValue = null
if (project.hasProperty('verbose')) {
verboseValue = project.property('verbose').toBoolean()
}
String[] fileSystemRootsValue = null
if (project.hasProperty('filesystem-roots')) {
fileSystemRootsValue = project.property('filesystem-roots').split('\\|')
......@@ -521,9 +440,10 @@ class FlutterPlugin implements Plugin<Project> {
}
}
def compileTasks = targetPlatforms.collect { targetArch ->
def flutterTasks = []
targetPlatforms.each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')])
String taskName = "compile${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}${targetArch.replace('android-', '').capitalize()}"
FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
......@@ -532,7 +452,7 @@ class FlutterPlugin implements Plugin<Project> {
localEngineSrcPath this.localEngineSrcPath
abi abiValue
targetPath target
verbose isVerbose(project)
verbose verboseValue
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
......@@ -546,8 +466,8 @@ class FlutterPlugin implements Plugin<Project> {
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
}
flutterTasks.add(compileTask)
}
def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
def libFlutterPlatforms = targetPlatforms.collect()
// x86/x86_64 native library used for debugging only, for now.
......@@ -576,13 +496,13 @@ class FlutterPlugin implements Plugin<Project> {
include 'lib/**'
}
}
dependsOn compileTasks
dependsOn flutterTasks
// Add the ELF library.
compileTasks.each { compileTask ->
from(compileTask.intermediateDir) {
flutterTasks.each { flutterTask ->
from(flutterTask.intermediateDir) {
include '*.so'
rename { String filename ->
return "lib/${compileTask.abi}/lib${filename}"
return "lib/${flutterTask.abi}/lib${filename}"
}
}
}
......@@ -596,7 +516,7 @@ class FlutterPlugin implements Plugin<Project> {
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
dependsOn compileTasks
dependsOn flutterTasks
if (packageAssets && cleanPackageAssets) {
dependsOn packageAssets
dependsOn cleanPackageAssets
......@@ -607,7 +527,7 @@ class FlutterPlugin implements Plugin<Project> {
variant.mergeAssets.mustRunAfter("clean${variant.mergeAssets.name.capitalize()}")
into variant.mergeAssets.outputDir
}
compileTasks.each { flutterTask ->
flutterTasks.each { flutterTask ->
with flutterTask.assets
}
}
......@@ -630,62 +550,12 @@ class FlutterPlugin implements Plugin<Project> {
processResources.dependsOn(copyFlutterAssetsTask)
}
}
if (project.android.hasProperty("applicationVariants")) {
project.android.applicationVariants.all addFlutterDeps
} else {
project.android.libraryVariants.all addFlutterDeps
}
if (buildPluginAsAar()) {
addPluginTasks(project)
List<String> tasksToExecute = project.gradle.startParameter.taskNames
Set buildTypes = getBuildTypesForTasks(project, tasksToExecute)
if (tasksToExecute.contains("clean")) {
// Because the plugins are built during configuration, the task "clean"
// cannot run in conjunction with an assembly task.
if (!buildTypes.empty) {
throw new GradleException("Can't run the clean task along with other assemble tasks")
}
}
// Build plugins when a task "assembly*" will be called later.
if (!buildTypes.empty) {
// Build the plugin during configuration.
// This is required when Jetifier is enabled, otherwise the implementation dependency
// cannot be added.
buildPlugins(project, buildTypes)
}
} else {
getPluginList(project).each { name, _ ->
def pluginProject = project.rootProject.findProject(":$name")
if (pluginProject != null) {
project.dependencies {
if (project.getConfigurations().findByName("implementation")) {
implementation pluginProject
} else {
compile pluginProject
}
}
pluginProject.afterEvaluate {
pluginProject.android.buildTypes {
profile {
initWith debug
}
}
pluginProject.android.buildTypes.each {
def buildMode = buildModeFor(it)
addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
}
pluginProject.android.buildTypes.whenObjectAdded {
def buildMode = buildModeFor(it)
addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
}
}
} else {
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
}
}
}
}
}
......@@ -914,59 +784,6 @@ class FlutterTask extends BaseFlutterTask {
}
}
class FlutterPluginTask extends DefaultTask {
File flutterExecutable
@Optional @Input
Boolean verbose
@Input
String buildMode
@Input
File pluginDir
@Input
File intermediateDir
File sourceDir
@InputFiles
FileCollection getSourceFiles() {
return project.fileTree(
dir: sourceDir,
exclude: ["android", "ios"],
include: ["pubspec.yaml"]
)
}
@OutputDirectory
File getOutputDirectory() {
return intermediateDir
}
@TaskAction
void build() {
intermediateDir.mkdirs()
project.exec {
executable flutterExecutable.absolutePath
workingDir pluginDir
args "build", "aar"
args "--quiet"
args "--suppress-analytics"
args "--output-dir", "${intermediateDir}"
switch (buildMode) {
case 'release':
args "--release"
break
case 'debug':
args "--debug"
break
default:
assert false
}
if (verbose) {
args "--verbose"
}
}
}
}
gradle.useLogger(new FlutterEventLogger())
class FlutterEventLogger extends BuildAdapter implements TaskExecutionListener {
......
To manually update `settings.gradle`, follow these steps:
1. Copy `settings.gradle` as `settings_aar.gradle`
2. Remove the following code from `settings_aar.gradle`:
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()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
// Copyright 2019 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:meta/meta.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../project.dart';
import 'android_sdk.dart';
import 'gradle.dart';
/// Provides a method to build a module or plugin as AAR.
abstract class AarBuilder {
/// Builds the AAR artifacts.
Future<void> build({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required String outputDir,
});
}
/// Default implementation of [AarBuilder].
class AarBuilderImpl extends AarBuilder {
AarBuilderImpl();
/// Builds the AAR and POM files for the current Flutter module or plugin.
@override
Future<void> build({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required String outputDir,
}) async {
if (!project.android.isUsingGradle) {
throwToolExit(
'The build process for Android has changed, and the current project configuration\n'
'is no longer valid. Please consult\n\n'
' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
'for details on how to upgrade the project.'
);
}
if (!project.manifest.isModule && !project.manifest.isPlugin) {
throwToolExit('AARs can only be built for plugin or module projects.');
}
// Validate that we can find an Android SDK.
if (androidSdk == null) {
throwToolExit('No Android SDK found. Try setting the `ANDROID_SDK_ROOT` environment variable.');
}
await buildGradleAar(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
outputDir: outputDir,
);
androidSdk.reinitialize();
}
}
......@@ -17,10 +17,8 @@ import '../base/platform.dart';
import '../base/process.dart';
import '../base/terminal.dart';
import '../base/utils.dart';
import '../base/version.dart';
import '../build_info.dart';
import '../cache.dart';
import '../features.dart';
import '../flutter_manifest.dart';
import '../globals.dart';
import '../project.dart';
......@@ -29,10 +27,10 @@ import '../runner/flutter_command.dart';
import 'android_sdk.dart';
import 'android_studio.dart';
const String gradleVersion = '4.10.2';
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
GradleProject _cachedGradleAppProject;
GradleProject _cachedGradleLibraryProject;
GradleProject _cachedGradleProject;
String _cachedGradleExecutable;
enum FlutterPluginVersion {
......@@ -104,19 +102,14 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2:
return fs.file((await _gradleAppProject()).apkDirectory.childFile('app.apk'));
return fs.file((await _gradleProject()).apkDirectory.childFile('app.apk'));
}
return null;
}
Future<GradleProject> _gradleAppProject() async {
_cachedGradleAppProject ??= await _readGradleProject(isLibrary: false);
return _cachedGradleAppProject;
}
Future<GradleProject> _gradleLibraryProject() async {
_cachedGradleLibraryProject ??= await _readGradleProject(isLibrary: true);
return _cachedGradleLibraryProject;
Future<GradleProject> _gradleProject() async {
_cachedGradleProject ??= await _readGradleProject();
return _cachedGradleProject;
}
/// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
......@@ -134,98 +127,32 @@ Future<void> checkGradleDependencies() async {
progress.stop();
}
/// Tries to create `settings_aar.gradle` in an app project by removing the subprojects
/// from the existing `settings.gradle` file. This operation will fail if the existing
/// `settings.gradle` file has local edits.
void createSettingsAarGradle(Directory androidDirectory) {
final File newSettingsFile = androidDirectory.childFile('settings_aar.gradle');
if (newSettingsFile.existsSync()) {
return;
}
final File currentSettingsFile = androidDirectory.childFile('settings.gradle');
if (!currentSettingsFile.existsSync()) {
return;
}
final String currentFileContent = currentSettingsFile.readAsStringSync();
final String newSettingsRelativeFile = fs.path.relative(newSettingsFile.path);
final Status status = logger.startProgress('✏️ Creating `$newSettingsRelativeFile`...',
timeout: timeoutConfiguration.fastOperation);
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
final File deprecatedFile = fs.file(fs.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'deprecated_settings.gradle'));
assert(deprecatedFile.existsSync());
// Get the `settings.gradle` content variants that should be patched.
final List<String> deprecatedFilesContent = deprecatedFile.readAsStringSync().split(';EOF');
bool exactMatch = false;
for (String deprecatedFileContent in deprecatedFilesContent) {
if (currentFileContent.trim() == deprecatedFileContent.trim()) {
exactMatch = true;
break;
}
}
if (!exactMatch) {
status.cancel();
printError('*******************************************************************************************');
printError('Flutter tried to create the file `$newSettingsRelativeFile`, but failed.');
// Print how to manually update the file.
printError(fs.file(fs.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'manual_migration_settings.gradle.md')).readAsStringSync());
printError('*******************************************************************************************');
throwToolExit('Please create the file and run this command again.');
}
// Copy the new file.
final String settingsAarContent = fs.file(fs.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'settings_aar.gradle.tmpl')).readAsStringSync();
newSettingsFile.writeAsStringSync(settingsAarContent);
status.stop();
printStatus('✅ `$newSettingsRelativeFile` created successfully.');
}
// Note: Dependencies are resolved and possibly downloaded as a side-effect
// of calculating the app properties using Gradle. This may take minutes.
Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
Future<GradleProject> _readGradleProject() async {
final FlutterProject flutterProject = FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject);
updateLocalProperties(project: flutterProject);
final FlutterManifest manifest = flutterProject.manifest;
final Directory hostAppGradleRoot = flutterProject.android.hostAppGradleRoot;
if (featureFlags.isPluginAsAarEnabled &&
!manifest.isPlugin && !manifest.isModule) {
createSettingsAarGradle(hostAppGradleRoot);
}
if (manifest.isPlugin) {
assert(isLibrary);
return GradleProject(
<String>['debug', 'profile', 'release'],
<String>[], // Plugins don't have flavors.
flutterProject.directory.childDirectory('build').path,
);
}
final Status status = logger.startProgress('Resolving dependencies...', timeout: timeoutConfiguration.slowOperation);
GradleProject project;
// Get the properties and tasks from Gradle, so we can determinate the `buildDir`,
// flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
try {
final RunResult propertiesRunResult = await runCheckedAsync(
<String>[gradle, isLibrary ? 'properties' : 'app:properties'],
workingDirectory: hostAppGradleRoot.path,
<String>[gradle, 'app:properties'],
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv,
);
final RunResult tasksRunResult = await runCheckedAsync(
<String>[gradle, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
workingDirectory: hostAppGradleRoot.path,
<String>[gradle, 'app:tasks', '--all', '--console=auto'],
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv,
);
project = GradleProject.fromAppProperties(propertiesRunResult.stdout, tasksRunResult.stdout);
} catch (exception) {
if (getFlutterPluginVersion(flutterProject.android) == FlutterPluginVersion.managed) {
status.cancel();
// Handle known exceptions.
throwToolExitIfLicenseNotAccepted(exception);
// Handle known exceptions. This will exit if handled.
handleKnownGradleExceptions(exception.toString());
// Print a general Gradle error and exit.
printError('* Error running Gradle:\n$exception\n');
throwToolExit('Please review your Gradle project setup in the android/ folder.');
......@@ -233,23 +160,23 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
// Fall back to the default
project = GradleProject(
<String>['debug', 'profile', 'release'],
<String>[],
fs.path.join(flutterProject.android.hostAppGradleRoot.path, 'app', 'build')
<String>[], flutterProject.android.gradleAppOutV1Directory,
flutterProject.android.gradleAppBundleOutV1Directory,
);
}
status.stop();
return project;
}
/// Handle Gradle error thrown when Gradle needs to download additional
/// Android SDK components (e.g. Platform Tools), and the license
/// for that component has not been accepted.
void throwToolExitIfLicenseNotAccepted(Exception exception) {
const String licenseNotAcceptedMatcher =
void handleKnownGradleExceptions(String exceptionString) {
// Handle Gradle error thrown when Gradle needs to download additional
// Android SDK components (e.g. Platform Tools), and the license
// for that component has not been accepted.
const String matcher =
r'You have not accepted the license agreements of the following SDK components:'
r'\s*\[(.+)\]';
final RegExp licenseFailure = RegExp(licenseNotAcceptedMatcher, multiLine: true);
final Match licenseMatch = licenseFailure.firstMatch(exception.toString());
final RegExp licenseFailure = RegExp(matcher, multiLine: true);
final Match licenseMatch = licenseFailure.firstMatch(exceptionString);
if (licenseMatch != null) {
final String missingLicenses = licenseMatch.group(1);
final String errorMessage =
......@@ -306,7 +233,6 @@ void injectGradleWrapper(Directory directory) {
_locateGradlewExecutable(directory);
final File propertiesFile = directory.childFile(fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
if (!propertiesFile.existsSync()) {
final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
propertiesFile.writeAsStringSync('''
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
......@@ -318,78 +244,6 @@ distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersio
}
}
/// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
bool _isWithinVersionRange(String targetVersion, {String min, String max}) {
final Version parsedTargetVersion = Version.parse(targetVersion);
return parsedTargetVersion >= Version.parse(min) &&
parsedTargetVersion <= Version.parse(max);
}
const String defaultGradleVersion = '4.10.2';
/// Returns the Gradle version that is required by the given Android Gradle plugin version
/// by picking the largest compatible version from
/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
String getGradleVersionFor(String androidPluginVersion) {
if (_isWithinVersionRange(androidPluginVersion, min: '1.0.0', max: '1.1.3')) {
return '2.3';
}
if (_isWithinVersionRange(androidPluginVersion, min: '1.2.0', max: '1.3.1')) {
return '2.9';
}
if (_isWithinVersionRange(androidPluginVersion, min: '1.5.0', max: '1.5.0')) {
return '2.2.1';
}
if (_isWithinVersionRange(androidPluginVersion, min: '2.0.0', max: '2.1.2')) {
return '2.13';
}
if (_isWithinVersionRange(androidPluginVersion, min: '2.1.3', max: '2.2.3')) {
return '2.14.1';
}
if (_isWithinVersionRange(androidPluginVersion, min: '2.3.0', max: '2.9.9')) {
return '3.3';
}
if (_isWithinVersionRange(androidPluginVersion, min: '3.0.0', max: '3.0.9')) {
return '4.1';
}
if (_isWithinVersionRange(androidPluginVersion, min: '3.1.0', max: '3.1.9')) {
return '4.4';
}
if (_isWithinVersionRange(androidPluginVersion, min: '3.2.0', max: '3.2.1')) {
return '4.6';
}
if (_isWithinVersionRange(androidPluginVersion, min: '3.3.0', max: '3.3.2')) {
return '4.10.2';
}
if (_isWithinVersionRange(androidPluginVersion, min: '3.4.0', max: '3.5.0')) {
return '5.1.1';
}
throwToolExit('Unsuported Android Plugin version: $androidPluginVersion.');
return '';
}
final RegExp _androidPluginRegExp = RegExp('com\.android\.tools\.build\:gradle\:(\\d+\.\\d+\.\\d+\)');
/// Returns the Gradle version that the current Android plugin depends on when found,
/// otherwise it returns a default version.
///
/// The Android plugin version is specified in the [build.gradle] file within
/// the project's Android directory.
String getGradleVersionForAndroidPlugin(Directory directory) {
final File buildFile = directory.childFile('build.gradle');
if (!buildFile.existsSync()) {
return defaultGradleVersion;
}
final String buildFileContent = buildFile.readAsStringSync();
final Iterable<Match> pluginMatches = _androidPluginRegExp.allMatches(buildFileContent);
if (pluginMatches.isEmpty) {
return defaultGradleVersion;
}
final String androidPluginVersion = pluginMatches.first.group(1);
return getGradleVersionFor(androidPluginVersion);
}
/// Overwrite local.properties in the specified Flutter project's Android
/// sub-project, if needed.
///
......@@ -493,95 +347,6 @@ Future<void> buildGradleProject({
}
}
Future<void> buildGradleAar({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required String outputDir,
}) async {
final FlutterManifest manifest = project.manifest;
GradleProject gradleProject;
if (manifest.isModule) {
gradleProject = await _gradleAppProject();
} else if (manifest.isPlugin) {
gradleProject = await _gradleLibraryProject();
} else {
throwToolExit('AARs can only be built for plugin or module projects.');
}
if (outputDir != null && outputDir.isNotEmpty) {
gradleProject.buildDirectory = outputDir;
}
final String aarTask = gradleProject.aarTaskFor(androidBuildInfo.buildInfo);
if (aarTask == null) {
printUndefinedTask(gradleProject, androidBuildInfo.buildInfo);
throwToolExit('Gradle build aborted.');
}
final Status status = logger.startProgress(
'Running Gradle task \'$aarTask\'...',
timeout: timeoutConfiguration.slowOperation,
multilineOutput: true,
);
final String gradle = await _ensureGradle(project);
final String gradlePath = fs.file(gradle).absolute.path;
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle');
final List<String> command = <String>[
gradlePath,
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${gradleProject.buildDirectory}',
'-Pis-plugin=${manifest.isPlugin}',
'-Dbuild-plugins-as-aars=true',
];
if (target != null && target.isNotEmpty) {
command.add('-Ptarget=$target');
}
if (androidBuildInfo.targetArchs.isNotEmpty) {
final String targetPlatforms = androidBuildInfo.targetArchs
.map(getPlatformNameForAndroidArch).join(',');
command.add('-Ptarget-platform=$targetPlatforms');
}
command.add(aarTask);
final Stopwatch sw = Stopwatch()..start();
int exitCode = 1;
try {
exitCode = await runCommandAndStreamOutput(
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: _gradleEnv,
mapFunction: (String line) {
// Always print the full line in verbose mode.
if (logger.isVerbose) {
return line;
}
return null;
},
);
} finally {
status.stop();
}
flutterUsage.sendTiming('build', 'gradle-aar', Duration(milliseconds: sw.elapsedMilliseconds));
if (exitCode != 0) {
throwToolExit('Gradle task $aarTask failed with exit code $exitCode', exitCode: exitCode);
}
final Directory repoDirectory = gradleProject.repoDirectory;
if (!repoDirectory.existsSync()) {
throwToolExit('Gradle task $aarTask failed to produce $repoDirectory', exitCode: exitCode);
}
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green);
}
Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async {
// Run 'gradlew build'.
final Status status = logger.startProgress(
......@@ -624,22 +389,6 @@ String _calculateSha(File file) {
return sha;
}
void printUndefinedTask(GradleProject project, BuildInfo buildInfo) {
printError('');
printError('The Gradle project does not define a task suitable for the requested build.');
if (!project.buildTypes.contains(buildInfo.modeName)) {
printError('Review the android/app/build.gradle file and ensure it defines a ${buildInfo.modeName} build type.');
return;
}
if (project.productFlavors.isEmpty) {
printError('The android/app/build.gradle file does not define any custom product flavors.');
printError('You cannot use the --flavor option.');
} else {
printError('The android/app/build.gradle file defines product flavors: ${project.productFlavors.join(', ')}');
printError('You must specify a --flavor option to select one of them.');
}
}
Future<void> _buildGradleProjectV2(
FlutterProject flutterProject,
String gradle,
......@@ -647,7 +396,7 @@ Future<void> _buildGradleProjectV2(
String target,
bool isBuildingBundle,
) async {
final GradleProject project = await _gradleAppProject();
final GradleProject project = await _gradleProject();
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
String assembleTask;
......@@ -657,9 +406,22 @@ Future<void> _buildGradleProjectV2(
} else {
assembleTask = project.assembleTaskFor(buildInfo);
}
if (assembleTask == null) {
printUndefinedTask(project, buildInfo);
throwToolExit('Gradle build aborted.');
printError('');
printError('The Gradle project does not define a task suitable for the requested build.');
if (!project.buildTypes.contains(buildInfo.modeName)) {
printError('Review the android/app/build.gradle file and ensure it defines a ${buildInfo.modeName} build type.');
} else {
if (project.productFlavors.isEmpty) {
printError('The android/app/build.gradle file does not define any custom product flavors.');
printError('You cannot use the --flavor option.');
} else {
printError('The android/app/build.gradle file defines product flavors: ${project.productFlavors.join(', ')}');
printError('You must specify a --flavor option to select one of them.');
}
throwToolExit('Gradle build aborted.');
}
}
final Status status = logger.startProgress(
'Running Gradle task \'$assembleTask\'...',
......@@ -698,14 +460,6 @@ Future<void> _buildGradleProjectV2(
.map(getPlatformNameForAndroidArch).join(',');
command.add('-Ptarget-platform=$targetPlatforms');
}
if (featureFlags.isPluginAsAarEnabled) {
// Pass a system flag instead of a project flag, so this flag can be
// read from include_flutter.groovy.
command.add('-Dbuild-plugins-as-aars=true');
if (!flutterProject.manifest.isModule) {
command.add('--settings-file=settings_aar.gradle');
}
}
command.add(assembleTask);
bool potentialAndroidXFailure = false;
final Stopwatch sw = Stopwatch()..start();
......@@ -850,6 +604,7 @@ Map<String, String> get _gradleEnv {
// Use java bundled with Android Studio.
env['JAVA_HOME'] = javaPath;
}
// Don't log analytics for downstream Flutter commands.
// e.g. `flutter build bundle`.
env['FLUTTER_SUPPRESS_ANALYTICS'] = 'true';
......@@ -857,15 +612,11 @@ Map<String, String> get _gradleEnv {
}
class GradleProject {
GradleProject(
this.buildTypes,
this.productFlavors,
this.buildDirectory,
);
GradleProject(this.buildTypes, this.productFlavors, this.apkDirectory, this.bundleDirectory);
factory GradleProject.fromAppProperties(String properties, String tasks) {
// Extract build directory.
final String buildDirectory = properties
final String buildDir = properties
.split('\n')
.firstWhere((String s) => s.startsWith('buildDir: '))
.substring('buildDir: '.length)
......@@ -897,36 +648,17 @@ class GradleProject {
if (productFlavors.isEmpty)
buildTypes.addAll(variants);
return GradleProject(
buildTypes.toList(),
productFlavors.toList(),
buildDirectory,
);
buildTypes.toList(),
productFlavors.toList(),
fs.directory(fs.path.join(buildDir, 'outputs', 'apk')),
fs.directory(fs.path.join(buildDir, 'outputs', 'bundle')),
);
}
/// The build types such as [release] or [debug].
final List<String> buildTypes;
/// The product flavors defined in build.gradle.
final List<String> productFlavors;
/// The build directory. This is typically <project>build/.
String buildDirectory;
/// The directory where the APK artifact is generated.
Directory get apkDirectory {
return fs.directory(fs.path.join(buildDirectory, 'outputs', 'apk'));
}
/// The directory where the app bundle artifact is generated.
Directory get bundleDirectory {
return fs.directory(fs.path.join(buildDirectory, 'outputs', 'bundle'));
}
/// The directory where the repo is generated.
/// Only applicable to AARs.
Directory get repoDirectory {
return fs.directory(fs.path.join(buildDirectory, 'outputs', 'repo'));
}
final Directory apkDirectory;
final Directory bundleDirectory;
String _buildTypeFor(BuildInfo buildInfo) {
final String modeName = camelCase(buildInfo.modeName);
......@@ -976,14 +708,6 @@ class GradleProject {
return 'bundle${toTitleCase(productFlavor)}${toTitleCase(buildType)}';
}
String aarTaskFor(BuildInfo buildInfo) {
final String buildType = _buildTypeFor(buildInfo);
final String productFlavor = _productFlavorFor(buildInfo);
if (buildType == null || productFlavor == null)
return null;
return 'assembleAar${toTitleCase(productFlavor)}${toTitleCase(buildType)}';
}
String bundleFileFor(BuildInfo buildInfo) {
// For app bundle all bundle names are called as app.aab. Product flavors
// & build types are differentiated as folders, where the aab will be added.
......
......@@ -9,7 +9,6 @@ import '../commands/build_macos.dart';
import '../commands/build_windows.dart';
import '../runner/flutter_command.dart';
import 'build_aar.dart';
import 'build_aot.dart';
import 'build_apk.dart';
import 'build_appbundle.dart';
......@@ -20,7 +19,6 @@ import 'build_web.dart';
class BuildCommand extends FlutterCommand {
BuildCommand({bool verboseHelp = false}) {
addSubcommand(BuildAarCommand(verboseHelp: verboseHelp));
addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));
addSubcommand(BuildAppBundleCommand(verboseHelp: verboseHelp));
addSubcommand(BuildAotCommand());
......
// Copyright 2019 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 '../android/aar.dart';
import '../base/context.dart';
import '../base/os.dart';
import '../build_info.dart';
import '../project.dart';
import '../reporting/usage.dart';
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
import 'build.dart';
/// The AAR builder in the current context.
AarBuilder get aarBuilder => context.get<AarBuilder>() ?? AarBuilderImpl();
class BuildAarCommand extends BuildSubCommand {
BuildAarCommand({bool verboseHelp = false}) {
addBuildModeFlags(verboseHelp: verboseHelp);
usesFlavorOption();
usesPubOption();
argParser
..addMultiOption('target-platform',
splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64'],
allowed: <String>['android-arm', 'android-arm64', 'android-x86', 'android-x64'],
help: 'The target platform for which the project is compiled.',
)
..addOption('output-dir',
help: 'The absolute path to the directory where the repository is generated.'
'By default, this is \'<current-directory>android/build\'. ',
);
}
@override
final String name = 'aar';
@override
Future<Map<String, String>> get usageValues async {
final Map<String, String> usage = <String, String>{};
final FlutterProject futterProject = _getProject();
if (futterProject == null) {
return usage;
}
if (futterProject.manifest.isModule) {
usage[kCommandBuildAarProjectType] = 'module';
} else if (futterProject.manifest.isPlugin) {
usage[kCommandBuildAarProjectType] = 'plugin';
} else {
usage[kCommandBuildAarProjectType] = 'app';
}
usage[kCommandBuildAarTargetPlatform] =
(argResults['target-platform'] as List<String>).join(',');
return usage;
}
@override
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
DevelopmentArtifact.universal,
DevelopmentArtifact.android,
};
@override
final String description = 'Build a repository containing an AAR and a POM file.\n\n'
'The POM file is used to include the dependencies that the AAR was compiled against.\n\n'
'To learn more about how to use these artifacts, see '
'https://docs.gradle.org/current/userguide/repository_types.html#sub:maven_local';
@override
Future<FlutterCommandResult> runCommand() async {
final BuildInfo buildInfo = getBuildInfo();
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName));
await aarBuilder.build(
project: _getProject(),
target: '', // Not needed because this command only builds Android's code.
androidBuildInfo: androidBuildInfo,
outputDir: argResults['output-dir'],
);
return null;
}
/// Returns the [FlutterProject] which is determinated from the remaining command-line
/// argument if any or the current working directory.
FlutterProject _getProject() {
if (argResults.rest.isEmpty) {
return FlutterProject.current();
}
return FlutterProject.fromPath(findProjectRoot(argResults.rest.first));
}
}
......@@ -36,9 +36,6 @@ class FeatureFlags {
/// Whether flutter desktop for Windows is enabled.
bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature);
/// Whether plugins are built as AARs in app projects.
bool get isPluginAsAarEnabled => _isEnabled(flutterBuildPluginAsAarFeature);
// Calculate whether a particular feature is enabled for the current channel.
static bool _isEnabled(Feature feature) {
final String currentChannel = FlutterVersion.instance.channel;
......@@ -68,7 +65,6 @@ const List<Feature> allFeatures = <Feature>[
flutterLinuxDesktopFeature,
flutterMacOSDesktopFeature,
flutterWindowsDesktopFeature,
flutterBuildPluginAsAarFeature,
];
/// The [Feature] for flutter web.
......@@ -119,20 +115,6 @@ const Feature flutterWindowsDesktopFeature = Feature(
),
);
/// The [Feature] for building plugins as AARs in an app project.
const Feature flutterBuildPluginAsAarFeature = Feature(
name: 'Build plugins independently as AARs in app projects',
configSetting: 'enable-build-plugin-as-aar',
master: FeatureChannelSetting(
available: true,
enabledByDefault: true,
),
dev: FeatureChannelSetting(
available: true,
enabledByDefault: false,
),
);
/// A [Feature] is a process for conditionally enabling tool features.
///
/// All settings are optional, and if not provided will generally default to
......
......@@ -510,6 +510,10 @@ class AndroidProject {
return fs.directory(fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk'));
}
Directory get gradleAppBundleOutV1Directory {
return fs.directory(fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'bundle'));
}
/// Whether the current flutter project has an Android sub-project.
bool existsSync() {
return parent.isModule || _editableHostAppDirectory.existsSync();
......
......@@ -57,16 +57,13 @@ const String kCommandBuildBundleIsModule = 'cd25';
const String kCommandResult = 'cd26';
const String kCommandHasTerminal = 'cd31';
const String kCommandBuildAarTargetPlatform = 'cd34';
const String kCommandBuildAarProjectType = 'cd35';
const String reloadExceptionTargetPlatform = 'cd27';
const String reloadExceptionSdkName = 'cd28';
const String reloadExceptionEmulator = 'cd29';
const String reloadExceptionFullRestart = 'cd30';
const String enabledFlutterFeatures = 'cd32';
// Next ID: cd36
// Next ID: cd34
Usage get flutterUsage => Usage.instance;
......
......@@ -26,9 +26,6 @@ if (flutterVersionName == null) {
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
group '{{androidIdentifier}}'
version '1.0'
android {
compileSdkVersion 28
......
......@@ -6,37 +6,24 @@ def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
gradle.include ':flutter'
gradle.project(':flutter').projectDir = new File(flutterProjectRoot, '.android/Flutter')
if (System.getProperty('build-plugins-as-aars') != 'true') {
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
gradle.include ":$name"
gradle.project(":$name").projectDir = pluginDirectory
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
gradle.include ":$name"
gradle.project(":$name").projectDir = pluginDirectory
}
gradle.getGradle().projectsLoaded { g ->
g.rootProject.beforeEvaluate { p ->
_mainModuleName = binding.variables['mainModuleName']
if (_mainModuleName != null && !_mainModuleName.empty) {
p.ext.mainModuleName = _mainModuleName
}
def subprojects = []
def flutterProject
p.subprojects { sp ->
if (sp.name == 'flutter') {
flutterProject = sp
} else {
subprojects.add(sp)
}
}
assert flutterProject != null
flutterProject.ext.hostProjects = subprojects
flutterProject.ext.pluginBuildDir = new File(flutterProjectRoot, 'build/host')
}
g.rootProject.afterEvaluate { p ->
p.subprojects { sp ->
......
group '{{androidIdentifier}}'
version '1.0'
version '1.0-SNAPSHOT'
buildscript {
repositories {
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
......@@ -6,9 +6,7 @@ import 'dart:async';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/base/logger.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/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
......@@ -170,20 +168,20 @@ someOtherTask
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>[], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.debug)).first, 'app-debug.apk');
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.profile)).first, 'app-profile.apk');
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo.release)).first, 'app-release.apk');
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue);
});
test('should provide apk file name for flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.debug, 'free'))).first, 'app-free-debug.apk');
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'paid'))).first, 'app-paid-release.apk');
expect(project.apkFilesFor(const AndroidBuildInfo(BuildInfo(BuildMode.release, 'unknown'))).isEmpty, isTrue);
});
test('should provide apks for default build types and each ABI', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.apkFilesFor(
const AndroidBuildInfo(
BuildInfo.debug,
......@@ -226,7 +224,7 @@ someOtherTask
).isEmpty, isTrue);
});
test('should provide apks for each ABI and flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.apkFilesFor(
const AndroidBuildInfo(
BuildInfo(BuildMode.debug, 'free'),
......@@ -269,154 +267,54 @@ someOtherTask
).isEmpty, isTrue);
});
test('should provide bundle file name for default build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.bundleFileFor(BuildInfo.debug), 'app.aab');
expect(project.bundleFileFor(BuildInfo.profile), 'app.aab');
expect(project.bundleFileFor(BuildInfo.release), 'app.aab');
expect(project.bundleFileFor(const BuildInfo(BuildMode.release, 'unknown')), 'app.aab');
});
test('should provide bundle file name for flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.bundleFileFor(const BuildInfo(BuildMode.debug, 'free')), 'app.aab');
expect(project.bundleFileFor(const BuildInfo(BuildMode.release, 'paid')), 'app.aab');
expect(project.bundleFileFor(const BuildInfo(BuildMode.release, 'unknown')), 'app.aab');
});
test('should provide assemble task name for default build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),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'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),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);
});
test('should respect format of the flavored build types', () {
final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.assembleTaskFor(const BuildInfo(BuildMode.debug, 'randomFlavor')), 'assembleRandomFlavorDebug');
});
test('bundle should provide assemble task name for default build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>[], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.bundleTaskFor(BuildInfo.debug), 'bundleDebug');
expect(project.bundleTaskFor(BuildInfo.profile), 'bundleProfile');
expect(project.bundleTaskFor(BuildInfo.release), 'bundleRelease');
expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('bundle should provide assemble task name for flavored build types', () {
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug', 'profile', 'release'], <String>['free', 'paid'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.bundleTaskFor(const BuildInfo(BuildMode.debug, 'free')), 'bundleFreeDebug');
expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'paid')), 'bundlePaidRelease');
expect(project.bundleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
});
test('bundle should respect format of the flavored build types', () {
final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], '/some/dir');
final GradleProject project = GradleProject(<String>['debug'], <String>['randomFlavor'], fs.directory('/some/dir'),fs.directory('/some/dir'));
expect(project.bundleTaskFor(const BuildInfo(BuildMode.debug, 'randomFlavor')), 'bundleRandomFlavorDebug');
});
});
group('Config files', () {
BufferLogger mockLogger;
Directory tempDir;
setUp(() {
mockLogger = BufferLogger();
tempDir = fs.systemTempDirectory.createTempSync('settings_aar_test.');
});
testUsingContext('create settings_aar.gradle', () {
const String deprecatedFile = '''
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()
include ":\$name"
project(":\$name").projectDir = pluginDirectory
}
''';
const String settingsAarFile = '''
include ':app'
''';
tempDir.childFile('settings.gradle').writeAsStringSync(deprecatedFile);
final String toolGradlePath = fs.path.join(
fs.path.absolute(Cache.flutterRoot),
'packages',
'flutter_tools',
'gradle');
fs.directory(toolGradlePath).createSync(recursive: true);
fs.file(fs.path.join(toolGradlePath, 'deprecated_settings.gradle'))
.writeAsStringSync(deprecatedFile);
fs.file(fs.path.join(toolGradlePath, 'settings_aar.gradle.tmpl'))
.writeAsStringSync(settingsAarFile);
createSettingsAarGradle(tempDir);
expect(mockLogger.statusText, contains('created successfully'));
expect(tempDir.childFile('settings_aar.gradle').existsSync(), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
Logger: () => mockLogger,
});
});
group('Undefined task', () {
BufferLogger mockLogger;
setUp(() {
mockLogger = BufferLogger();
});
testUsingContext('print undefined build type', () {
final GradleProject project = GradleProject(<String>['debug', 'release'],
const <String>['free', 'paid'], '/some/dir');
printUndefinedTask(project, const BuildInfo(BuildMode.profile, 'unknown'));
expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
expect(mockLogger.errorText, contains('Review the android/app/build.gradle file and ensure it defines a profile build type'));
}, overrides: <Type, Generator>{
Logger: () => mockLogger,
});
testUsingContext('print no flavors', () {
final GradleProject project = GradleProject(<String>['debug', 'release'],
const <String>[], '/some/dir');
printUndefinedTask(project, const BuildInfo(BuildMode.debug, 'unknown'));
expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
expect(mockLogger.errorText, contains('The android/app/build.gradle file does not define any custom product flavors'));
expect(mockLogger.errorText, contains('You cannot use the --flavor option'));
}, overrides: <Type, Generator>{
Logger: () => mockLogger,
});
testUsingContext('print flavors', () {
final GradleProject project = GradleProject(<String>['debug', 'release'],
const <String>['free', 'paid'], '/some/dir');
printUndefinedTask(project, const BuildInfo(BuildMode.debug, 'unknown'));
expect(mockLogger.errorText, contains('The Gradle project does not define a task suitable for the requested build'));
expect(mockLogger.errorText, contains('The android/app/build.gradle file defines product flavors: free, paid'));
}, overrides: <Type, Generator>{
Logger: () => mockLogger,
});
});
group('Gradle local.properties', () {
MockLocalEngineArtifacts mockArtifacts;
MockProcessManager mockProcessManager;
......@@ -642,52 +540,6 @@ flutter:
);
});
});
group('gradle version', () {
test('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.1.1');
expect(getGradleVersionFor('3.5.0'), '5.1.1');
});
test('throws on unsupported versions', () {
expect(() => getGradleVersionFor('3.6.0'),
throwsA(predicate<Exception>((Exception e) => e is ToolExit)));
});
});
}
Platform fakePlatform(String name) {
......
// Copyright 2019 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 'package:args/command_runner.dart';
import 'package:flutter_tools/src/android/aar.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_aar.dart';
import 'package:flutter_tools/src/reporting/usage.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
void main() {
Cache.disableLocking();
group('getUsage', () {
Directory tempDir;
AarBuilder mockAarBuilder;
setUp(() {
tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
mockAarBuilder = MockAarBuilder();
when(mockAarBuilder.build(
project: anyNamed('project'),
androidBuildInfo: anyNamed('androidBuildInfo'),
target: anyNamed('target'),
outputDir: anyNamed('outputDir'))).thenAnswer((_) => Future<void>.value());
});
tearDown(() {
tryToDelete(tempDir);
});
Future<BuildAarCommand> runCommandIn(String target, { List<String> arguments }) async {
final BuildAarCommand command = BuildAarCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'aar',
...?arguments,
target,
]);
return command;
}
testUsingContext('indicate that project is a module', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']);
final BuildAarCommand command = await runCommandIn(projectPath);
expect(await command.usageValues,
containsPair(kCommandBuildAarProjectType, 'module'));
}, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder,
}, timeout: allowForCreateFlutterProject);
testUsingContext('indicate that project is a plugin', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=plugin', '--project-name=aar_test']);
final BuildAarCommand command = await runCommandIn(projectPath);
expect(await command.usageValues,
containsPair(kCommandBuildAarProjectType, 'plugin'));
}, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder,
}, timeout: allowForCreateFlutterProject);
testUsingContext('indicate the target platform', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']);
final BuildAarCommand command = await runCommandIn(projectPath,
arguments: <String>['--target-platform=android-arm']);
expect(await command.usageValues,
containsPair(kCommandBuildAarTargetPlatform, 'android-arm'));
}, overrides: <Type, Generator>{
AarBuilder: () => mockAarBuilder,
}, timeout: allowForCreateFlutterProject);
});
}
class MockAarBuilder extends Mock implements AarBuilder {}
......@@ -418,21 +418,6 @@ void main() {
expect(featureFlags.isWindowsEnabled, false);
}));
/// Plugins as AARS
test('plugins built as AARs with config on master', () => testbed.run(() {
when(mockFlutterVerion.channel).thenReturn('master');
when<bool>(mockFlutterConfig.getValue('enable-build-plugin-as-aar')).thenReturn(true);
expect(featureFlags.isPluginAsAarEnabled, true);
}));
test('plugins built as AARs with config on dev', () => testbed.run(() {
when(mockFlutterVerion.channel).thenReturn('dev');
when<bool>(mockFlutterConfig.getValue('enable-build-plugin-as-aar')).thenReturn(true);
expect(featureFlags.isPluginAsAarEnabled, true);
}));
});
}
......
......@@ -697,7 +697,6 @@ class TestFeatureFlags implements FeatureFlags {
this.isMacOSEnabled = false,
this.isWebEnabled = false,
this.isWindowsEnabled = false,
this.isPluginAsAarEnabled = false,
});
@override
......@@ -711,7 +710,4 @@ class TestFeatureFlags implements FeatureFlags {
@override
final bool isWindowsEnabled;
@override
final bool isPluginAsAarEnabled;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment