Unverified Commit 96482eeb authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Re-enable AAR plugins when an AndroidX failure occurred (#40810)

parent c238045a
......@@ -1022,6 +1022,7 @@ Future<void> _androidGradleTests(String subShard) async {
await _runDevicelabTest('gradle_plugin_fat_apk_test', env: env);
await _runDevicelabTest('gradle_r8_test', env: env);
await _runDevicelabTest('gradle_non_android_plugin_test', env: env);
await _runDevicelabTest('gradle_jetifier_test', env: env);
}
if (subShard == 'gradle2') {
await _runDevicelabTest('gradle_plugin_bundle_test', env: env);
......
......@@ -64,6 +64,7 @@ Future<void> main() async {
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--no-shrink',
'--verbose',
],
);
......@@ -100,7 +101,9 @@ Future<void> main() async {
options: <String>[
'apk',
'--target-platform', 'android-arm',
'--debug', '--verbose',
'--debug',
'--no-shrink',
'--verbose',
],
);
});
......
......@@ -7,6 +7,7 @@
// destination of the local repository.
// The local repository will contain the AAR and POM files.
import java.nio.file.Paths
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.maven.MavenDeployer
......@@ -39,16 +40,32 @@ void configureProject(Project project, File outputDir) {
}
}
}
// Check if the project uses the Flutter plugin (defined in flutter.gradle).
Boolean usesFlutterPlugin = project.plugins.find { it.class.name == "FlutterPlugin" } != null
if (!usesFlutterPlugin) {
if (!project.property("is-plugin").toBoolean()) {
return
}
if (project.hasProperty('localEngineOut')) {
// TODO(egarciad): Support local engine.
// This most likely requires refactoring `flutter.gradle`, so the logic can be reused.
throw new GradleException(
"Local engine isn't supported when building the plugins as AAR. " +
"See: https://github.com/flutter/flutter/issues/40866")
}
// This is a Flutter plugin project. Plugin projects don't apply the Flutter Gradle plugin,
// as a result, add the dependency on the embedding.
project.repositories {
maven {
url "http://download.flutter.io"
}
}
String engineVersion = Paths.get(getFlutterRoot(project), "bin", "internal", "engine.version")
.toFile().text.trim()
project.dependencies {
// Some plugins don't include `annotations` and they don't set
// `android.useAndroidX=true` in `gradle.properties`.
compileOnly "androidx.annotation:annotation:+"
compileOnly "com.android.support:support-annotations:+"
// The Flutter plugin already adds `flutter.jar`.
compileOnly project.files("${getFlutterRoot(project)}/bin/cache/artifacts/engine/android-arm-release/flutter.jar")
// Add the embedding dependency.
compileOnly ("io.flutter:flutter_embedding_release:1.0.0-$engineVersion") {
// We only need to expose io.flutter.plugin.*
// No need for the embedding transitive dependencies.
transitive = false
}
}
}
......
......@@ -8,6 +8,8 @@ import com.android.builder.model.AndroidProject
import com.android.build.OutputFile
import java.nio.file.Path
import java.nio.file.Paths
import java.util.regex.Matcher
import java.util.regex.Pattern
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
......@@ -256,29 +258,65 @@ class FlutterPlugin implements Plugin<Project> {
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
getPluginList().each this.&configurePlugin
getPluginList().each this.&configurePluginProject
return
}
addPluginTasks()
List<String> tasksToExecute = project.gradle.startParameter.taskNames
Set buildTypes = getBuildTypesForTasks(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")
if (useLocalEngine()) {
throw new GradleException("Local engine isn't supported when building the plugins as AAR")
}
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"))
}
// Configure the repository for the plugins.
projects.each { hostProject ->
hostProject.repositories {
maven {
url "${getPluginBuildDir()}/outputs/repo"
}
}
}
// 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.
buildAarPlugins(buildTypes)
getPluginList().each { pluginName, pluginPath ->
configurePluginAar(pluginName, pluginPath, project)
}
}
private void configurePlugin(String name, String _) {
private static final Pattern GROUP_PATTERN = ~/group\s+\'(.+)\'/
private static final Pattern PROJECT_NAME_PATTERN = ~/rootProject\.name\s+=\s+\'(.+)\'/
// Adds the plugin AAR dependency to the app project.
private void configurePluginAar(String pluginName, String pluginPath, Project project) {
// Extract the group id from the plugin's build.gradle.
// This is `group '<group-id>'`
File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle"));
if (!pluginBuildFile.exists()) {
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.")
}
Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text)
assert groupParts.count == 1
assert groupParts.hasGroup()
String groupId = groupParts[0][1]
// Extract the artifact name from the plugin's settings.gradle.
// This is `rootProject.name = '<artifact-name>'`
File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle"));
if (!pluginSettings.exists()) {
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.")
}
Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text)
assert projectNameParts.count == 1
assert projectNameParts.hasGroup()
String artifactId = "${projectNameParts[0][1]}_release"
assert !groupId.empty
project.dependencies.add("api", "$groupId:$artifactId:+")
}
// Adds the plugin project dependency to the app project .
private void configurePluginProject(String name, String _) {
Project pluginProject = project.rootProject.findProject(":$name")
if (pluginProject == null) {
project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
......@@ -343,93 +381,6 @@ class FlutterPlugin implements Plugin<Project> {
return androidPlugins
}
private void addPluginTasks() {
Properties plugins = getPluginList()
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()
pluginDir project.file(path)
sourceDir project.file(project.flutter.source)
intermediateDir getPluginBuildDir()
}
}
}
}
}
private void buildAarPlugins(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()}/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
}
def mavenMetadata = new XmlParser().parse(file)
String groupId = mavenMetadata.groupId.text()
String artifactId = mavenMetadata.artifactId.text()
if (!artifactId.endsWith(buildMode)) {
return
}
// 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(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)
}
}
}
return buildTypes
}
private static String toCammelCase(List<String> parts) {
if (parts.empty) {
return ""
......@@ -920,56 +871,3 @@ class FlutterTask extends BaseFlutterTask {
buildBundle()
}
}
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"
}
}
}
}
......@@ -22,7 +22,6 @@ 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';
......@@ -47,18 +46,20 @@ class GradleUtils {
return _cachedExecutable;
}
GradleProject _cachedAppProject;
/// Gets the [GradleProject] for the current [FlutterProject] if built as an app.
Future<GradleProject> get appProject async {
_cachedAppProject ??= await _readGradleProject(isLibrary: false);
return _cachedAppProject;
final Map<FlutterProject, GradleProject> _cachedAppProject =
<FlutterProject, GradleProject>{};
/// Gets the [GradleProject] for the [project] if built as an app.
Future<GradleProject> getAppProject(FlutterProject project) async {
_cachedAppProject[project] ??= await _readGradleProject(project, isLibrary: false);
return _cachedAppProject[project];
}
GradleProject _cachedLibraryProject;
/// Gets the [GradleProject] for the current [FlutterProject] if built as a library.
Future<GradleProject> get libraryProject async {
_cachedLibraryProject ??= await _readGradleProject(isLibrary: true);
return _cachedLibraryProject;
final Map<FlutterProject, GradleProject> _cachedLibraryProject =
<FlutterProject, GradleProject>{};
/// Gets the [GradleProject] for the [project] if built as a library.
Future<GradleProject> getLibraryProject(FlutterProject project) async {
_cachedLibraryProject[project] ??= await _readGradleProject(project, isLibrary: true);
return _cachedLibraryProject[project];
}
}
......@@ -133,7 +134,8 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2:
final GradleProject gradleProject = await gradleUtils.appProject;
final GradleProject gradleProject =
await gradleUtils.getAppProject(FlutterProject.current());
return fs.file(gradleProject.apkDirectory.childFile('app.apk'));
}
return null;
......@@ -209,8 +211,10 @@ void createSettingsAarGradle(Directory androidDirectory) {
// 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 {
final FlutterProject flutterProject = FlutterProject.current();
Future<GradleProject> _readGradleProject(
FlutterProject flutterProject, {
bool isLibrary = false,
}) async {
final String gradlew = await gradleUtils.getExecutable(flutterProject);
updateLocalProperties(project: flutterProject);
......@@ -218,10 +222,6 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
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(
......@@ -581,9 +581,9 @@ Future<void> buildGradleAar({
GradleProject gradleProject;
if (manifest.isModule) {
gradleProject = await gradleUtils.appProject;
gradleProject = await gradleUtils.getAppProject(project);
} else if (manifest.isPlugin) {
gradleProject = await gradleUtils.libraryProject;
gradleProject = await gradleUtils.getLibraryProject(project);
} else {
throwToolExit('AARs can only be built for plugin or module projects.');
}
......@@ -612,13 +612,11 @@ Future<void> buildGradleAar({
'-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(',');
......@@ -633,34 +631,30 @@ Future<void> buildGradleAar({
command.add(aarTask);
final Stopwatch sw = Stopwatch()..start();
int exitCode = 1;
RunResult result;
try {
exitCode = await processUtils.stream(
result = await processUtils.run(
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));
flutterUsage.sendTiming('build', 'gradle-aar', sw.elapsed);
if (exitCode != 0) {
throwToolExit('Gradle task $aarTask failed with exit code $exitCode', exitCode: exitCode);
if (result.exitCode != 0) {
printStatus(result.stdout, wrap: false);
printError(result.stderr, wrap: false);
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(result.stdout, wrap: false);
printError(result.stderr, wrap: false);
throwToolExit('Gradle task $aarTask failed to produce $repoDirectory.', exitCode: exitCode);
}
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green);
}
......@@ -730,21 +724,33 @@ Future<void> _buildGradleProjectV2(
FlutterProject flutterProject,
AndroidBuildInfo androidBuildInfo,
String target,
bool isBuildingBundle,
) async {
bool isBuildingBundle, {
bool shouldBuildPluginAsAar = false,
}) async {
final String gradlew = await gradleUtils.getExecutable(flutterProject);
final GradleProject project = await gradleUtils.appProject;
final GradleProject gradleProject = await gradleUtils.getAppProject(flutterProject);
if (shouldBuildPluginAsAar) {
// Create a settings.gradle that doesn't import the plugins as subprojects.
createSettingsAarGradle(flutterProject.android.hostAppGradleRoot);
await buildPluginsAsAar(
flutterProject,
androidBuildInfo,
buildDirectory: gradleProject.buildDirectory,
);
}
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
String assembleTask;
if (isBuildingBundle) {
assembleTask = project.bundleTaskFor(buildInfo);
assembleTask = gradleProject.bundleTaskFor(buildInfo);
} else {
assembleTask = project.assembleTaskFor(buildInfo);
assembleTask = gradleProject.assembleTaskFor(buildInfo);
}
if (assembleTask == null) {
printUndefinedTask(project, buildInfo);
printUndefinedTask(gradleProject, buildInfo);
throwToolExit('Gradle build aborted.');
}
final Status status = logger.startProgress(
......@@ -791,14 +797,13 @@ Future<void> _buildGradleProjectV2(
.map(getPlatformNameForAndroidArch).join(',');
command.add('-Ptarget-platform=$targetPlatforms');
}
if (featureFlags.isPluginAsAarEnabled) {
if (shouldBuildPluginAsAar) {
// 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) {
// Don't use settings.gradle from the current project since it includes the plugins as subprojects.
command.add('--settings-file=settings_aar.gradle');
}
}
command.add(assembleTask);
bool potentialAndroidXFailure = false;
bool potentialR8Failure = false;
......@@ -844,24 +849,60 @@ Future<void> _buildGradleProjectV2(
printStatus('To learn more, see: https://developer.android.com/studio/build/shrink-code', indent: 4);
BuildEvent('r8-failure').send();
} else if (potentialAndroidXFailure) {
printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
BuildEvent('android-x-failure').send();
final bool hasPlugins = flutterProject.flutterPluginsFile.existsSync();
final bool usesAndroidX = isAppUsingAndroidX(flutterProject.android.hostAppGradleRoot);
if (!hasPlugins) {
// If the app doesn't use any plugin, then it's unclear where the incompatibility is coming from.
BuildEvent('android-x-failure', eventError: 'app-not-using-plugins').send();
}
if (hasPlugins && !usesAndroidX) {
// If the app isn't using AndroidX, then the app is likely using a plugin already migrated to AndroidX.
printStatus('AndroidX incompatibilities may have caused this build to fail. ');
printStatus('Please migrate your app to AndroidX. See https://goo.gl/CP92wY.');
BuildEvent('android-x-failure', eventError: 'app-not-using-androidx').send();
}
if (hasPlugins && usesAndroidX && shouldBuildPluginAsAar) {
// This is a dependency conflict instead of an AndroidX failure since by this point
// the app is using AndroidX, the plugins are built as AARs, Jetifier translated
// Support libraries for AndroidX equivalents.
BuildEvent('android-x-failure', eventError: 'using-jetifier').send();
}
if (hasPlugins && usesAndroidX && !shouldBuildPluginAsAar) {
printStatus(
'The built failed likely due to AndroidX incompatibilities in a plugin. '
'The tool is about to try using Jetfier to solve the incompatibility.'
);
BuildEvent('android-x-failure', eventError: 'not-using-jetifier').send();
// The app is using Androidx, but Jetifier hasn't run yet.
// Call the current method again, build the plugins as AAR, so Jetifier can translate
// the dependencies.
// NOTE: Don't build the plugins as AARs by default since this drastically increases
// the build time.
await _buildGradleProjectV2(
flutterProject,
androidBuildInfo,
target,
isBuildingBundle,
shouldBuildPluginAsAar: true,
);
return;
}
}
throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode);
}
flutterUsage.sendTiming('build', 'gradle-v2', Duration(milliseconds: sw.elapsedMilliseconds));
if (!isBuildingBundle) {
final Iterable<File> apkFiles = findApkFiles(project, androidBuildInfo);
final Iterable<File> apkFiles = findApkFiles(gradleProject, androidBuildInfo);
if (apkFiles.isEmpty) {
throwToolExit('Gradle build failed to produce an Android package.');
}
// Copy the first APK to app.apk, so `flutter run`, `flutter install`, etc. can find it.
// TODO(blasten): Handle multiple APKs.
apkFiles.first.copySync(project.apkDirectory.childFile('app.apk').path);
apkFiles.first.copySync(gradleProject.apkDirectory.childFile('app.apk').path);
printTrace('calculateSha: ${project.apkDirectory}/app.apk');
final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1');
printTrace('calculateSha: ${gradleProject.apkDirectory}/app.apk');
final File apkShaFile = gradleProject.apkDirectory.childFile('app.apk.sha1');
apkShaFile.writeAsStringSync(_calculateSha(apkFiles.first));
for (File apkFile in apkFiles) {
......@@ -875,7 +916,7 @@ Future<void> _buildGradleProjectV2(
color: TerminalColor.green);
}
} else {
final File bundleFile = findBundleFile(project, buildInfo);
final File bundleFile = findBundleFile(gradleProject, buildInfo);
if (bundleFile == null) {
throwToolExit('Gradle build failed to produce an Android bundle package.');
}
......@@ -891,6 +932,61 @@ Future<void> _buildGradleProjectV2(
}
}
/// Returns [true] if the current app uses AndroidX.
// TODO(egarciad): https://github.com/flutter/flutter/issues/40800
// Remove `FlutterManifest.usesAndroidX` and provide a unified `AndroidProject.usesAndroidX`.
@visibleForTesting
bool isAppUsingAndroidX(Directory androidDirectory) {
final File properties = androidDirectory.childFile('gradle.properties');
if (!properties.existsSync()) {
return false;
}
return properties.readAsStringSync().contains('android.useAndroidX=true');
}
/// Builds the plugins as AARs.
@visibleForTesting
Future<void> buildPluginsAsAar(
FlutterProject flutterProject,
AndroidBuildInfo androidBuildInfo, {
String buildDirectory,
}) async {
final File flutterPluginFile = flutterProject.flutterPluginsFile;
if (!flutterPluginFile.existsSync()) {
return;
}
final List<String> plugins = flutterPluginFile.readAsStringSync().split('\n');
for (String plugin in plugins) {
final List<String> pluginParts = plugin.split('=');
if (pluginParts.length != 2) {
continue;
}
final Directory pluginDirectory = fs.directory(pluginParts.last);
assert(pluginDirectory.existsSync());
final String pluginName = pluginParts.first;
logger.printStatus('Building plugin $pluginName...');
try {
await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors.
),
),
target: '',
outputDir: buildDirectory,
);
} on ToolExit {
// Log the entire plugin entry in `.flutter-plugins` since it
// includes the plugin name and the version.
BuildEvent('plugin-aar-failure', eventError: plugin).send();
throwToolExit('The plugin $pluginName could not be built due to the issue above. ');
}
}
}
@visibleForTesting
Iterable<File> findApkFiles(GradleProject project, AndroidBuildInfo androidBuildInfo) {
final Iterable<String> apkFileNames = project.apkFilesFor(androidBuildInfo);
......
......@@ -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;
......
......@@ -125,6 +125,7 @@ class BuildEvent extends UsageEvent {
BuildEvent(String parameter, {
this.command,
this.settings,
this.eventError,
}) : super(
'build' +
(FlutterCommand.current == null ? '' : '-${FlutterCommand.current.name}'),
......@@ -132,6 +133,7 @@ class BuildEvent extends UsageEvent {
final String command;
final String settings;
final String eventError;
@override
void send() {
......@@ -140,6 +142,8 @@ class BuildEvent extends UsageEvent {
CustomDimensions.buildEventCommand: command,
if (settings != null)
CustomDimensions.buildEventSettings: settings,
if (eventError != null)
CustomDimensions.buildEventError: eventError,
});
flutterUsage.sendEvent(category, parameter, parameters: parameters);
}
......
......@@ -53,6 +53,7 @@ enum CustomDimensions {
commandBuildApkSplitPerAbi, // cd40
commandBuildAppBundleTargetPlatform, // cd41
commandBuildAppBundleBuildMode, // cd42
buildEventError, // cd43
}
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
......
......@@ -25,7 +25,6 @@ import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart';
import '../../src/pubspec_schema.dart';
void main() {
......@@ -1155,6 +1154,153 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
});
});
group('isAppUsingAndroidX', () {
FileSystem fs;
setUp(() {
fs = MemoryFileSystem();
});
testUsingContext('returns true when the project is using AndroidX', () async {
final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.');
androidDirectory
.childFile('gradle.properties')
.writeAsStringSync('android.useAndroidX=true');
expect(isAppUsingAndroidX(androidDirectory), isTrue);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('returns false when the project is not using AndroidX', () async {
final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.');
androidDirectory
.childFile('gradle.properties')
.writeAsStringSync('android.useAndroidX=false');
expect(isAppUsingAndroidX(androidDirectory), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
testUsingContext('returns false when gradle.properties does not exist', () async {
final Directory androidDirectory = fs.systemTempDirectory.createTempSync('android.');
expect(isAppUsingAndroidX(androidDirectory), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => fs,
});
});
group('buildPluginsAsAar', () {
FileSystem fs;
MockProcessManager mockProcessManager;
MockAndroidSdk mockAndroidSdk;
setUp(() {
fs = MemoryFileSystem();
mockProcessManager = MockProcessManager();
when(mockProcessManager.run(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
mockAndroidSdk = MockAndroidSdk();
when(mockAndroidSdk.directory).thenReturn('irrelevant');
});
testUsingContext('calls gradle', () async {
final Directory androidDirectory = fs.directory('android.');
androidDirectory.createSync();
androidDirectory
.childFile('pubspec.yaml')
.writeAsStringSync('name: irrelevant');
final Directory plugin1 = fs.directory('plugin1.');
plugin1
..createSync()
..childFile('pubspec.yaml')
.writeAsStringSync('''
name: irrelevant
flutter:
plugin:
androidPackage: irrelevant
''');
final Directory plugin2 = fs.directory('plugin2.');
plugin2
..createSync()
..childFile('pubspec.yaml')
.writeAsStringSync('''
name: irrelevant
flutter:
plugin:
androidPackage: irrelevant
''');
androidDirectory
.childFile('.flutter-plugins')
.writeAsStringSync('''
plugin1=${plugin1.path}
plugin2=${plugin2.path}
''');
final Directory buildDirectory = androidDirectory.childDirectory('build');
buildDirectory
.childDirectory('outputs')
.childDirectory('repo')
.createSync(recursive: true);
await buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory.path,
);
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
final String initScript = fs.path.join(flutterRoot, 'packages',
'flutter_tools', 'gradle', 'aar_init_script.gradle');
verify(mockProcessManager.run(
<String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-Ptarget-platform=android-arm,android-arm64',
'assembleAarRelease',
],
environment: anyNamed('environment'),
workingDirectory: plugin1.childDirectory('android').path),
).called(1);
verify(mockProcessManager.run(
<String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-Ptarget-platform=android-arm,android-arm64',
'assembleAarRelease',
],
environment: anyNamed('environment'),
workingDirectory: plugin2.childDirectory('android').path),
).called(1);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
FileSystem: () => fs,
GradleUtils: () => FakeGradleUtils(),
ProcessManager: () => mockProcessManager,
});
});
group('gradle build', () {
MockAndroidSdk mockAndroidSdk;
MockAndroidStudio mockAndroidStudio;
......@@ -1235,11 +1381,13 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
fs.currentDirectory = 'path/to/project';
// Let any process start. Assert after.
when(mockProcessManager.start(
when(mockProcessManager.run(
any,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory'))
).thenAnswer((Invocation invocation) => Future<Process>.value(MockProcess()));
).thenAnswer(
(_) async => ProcessResult(1, 0, '', ''),
);
fs.directory('build/outputs/repo').createSync(recursive: true);
await buildGradleAar(
......@@ -1249,11 +1397,11 @@ at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
target: ''
);
final List<String> actualGradlewCall = verify(mockProcessManager.start(
final List<String> actualGradlewCall = verify(mockProcessManager.run(
captureAny,
environment: anyNamed('environment'),
workingDirectory: anyNamed('workingDirectory')),
).captured.single;
).captured.last;
expect(actualGradlewCall, contains('/path/to/project/.android/gradlew'));
expect(actualGradlewCall, contains('-PlocalEngineOut=out/android_arm'));
......@@ -1284,6 +1432,14 @@ Platform fakePlatform(String name) {
return FakePlatform.fromPlatform(const LocalPlatform())..operatingSystem = name;
}
class FakeGradleUtils extends GradleUtils {
@override
Future<String> getExecutable(FlutterProject project) async {
return 'gradlew';
}
}
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockAndroidStudio extends Mock implements AndroidStudio {}
class MockDirectory extends Mock implements Directory {}
class MockFile extends Mock implements File {}
......
......@@ -432,21 +432,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(false);
expect(featureFlags.isPluginAsAarEnabled, false);
}));
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(false);
expect(featureFlags.isPluginAsAarEnabled, false);
}));
});
}
......
......@@ -693,7 +693,6 @@ class TestFeatureFlags implements FeatureFlags {
this.isMacOSEnabled = false,
this.isWebEnabled = false,
this.isWindowsEnabled = false,
this.isPluginAsAarEnabled = false,
});
@override
......@@ -707,7 +706,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