mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This commit is contained in:
parent
39eecd5226
commit
16d408a7a0
@ -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) {
|
||||
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")
|
||||
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 {
|
||||
// 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 ""
|
||||
@ -927,56 +878,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,25 @@ 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;
|
||||
/// Cached app projects. The key is the [FluterProject]'s path, the value is [GradleProject].
|
||||
final Map<String, GradleProject> _cachedAppProject = <String, GradleProject>{};
|
||||
|
||||
/// Gets the [GradleProject] for the [project] if built as an app.
|
||||
Future<GradleProject> getAppProject(FlutterProject project) async {
|
||||
final String projectPath = project.directory.path;
|
||||
_cachedAppProject[projectPath] ??= await _readGradleProject(project, isLibrary: false);
|
||||
return _cachedAppProject[projectPath];
|
||||
}
|
||||
|
||||
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;
|
||||
/// Cached library projects such as plugins or modules.
|
||||
/// The key is the [FluterProject]'s path, the value is [GradleProject].
|
||||
final Map<String, GradleProject> _cachedLibraryProject = <String, GradleProject>{};
|
||||
|
||||
/// Gets the [GradleProject] for the [project] if built as a library.
|
||||
Future<GradleProject> getLibraryProject(FlutterProject project) async {
|
||||
final String projectPath = project.directory.path;
|
||||
_cachedLibraryProject[projectPath] ??= await _readGradleProject(project, isLibrary: true);
|
||||
return _cachedLibraryProject[projectPath];
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +139,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 +216,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 +227,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 +586,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 +617,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 +636,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 +729,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,13 +802,12 @@ 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) {
|
||||
command.add('--settings-file=settings_aar.gradle');
|
||||
}
|
||||
// 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;
|
||||
@ -844,24 +854,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 +921,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 +937,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() {
|
||||
@ -1150,6 +1149,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;
|
||||
@ -1230,11 +1376,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(
|
||||
@ -1244,11 +1392,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'));
|
||||
@ -1279,6 +1427,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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user