mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
152 lines
7.7 KiB
Markdown
152 lines
7.7 KiB
Markdown
Flutter builds APKs through a long set of steps that ultimately defer to Gradle.
|
|
The logic lives in many places: the Flutter framework, the Flutter app template,
|
|
and a mix of both Dart and Gradle scripts. This doc is a high level description
|
|
of the overall flow, which can be hard to follow given how many different
|
|
places at once it lives and how asynchronous Gradle builds are.
|
|
|
|
- The Flutter tool fetches any plugin dependencies of the Flutter app from pub
|
|
and downloads it to the pub cache. This step follows normal `pub get` behavior
|
|
and version solving.
|
|
- The Flutter tool writes project metadata to some local temporary files. This
|
|
includes things like the location of the local Flutter SDK, and paths to all
|
|
the newly downloaded plugins from pub on disk.
|
|
- The Flutter tool invokes Gradle to build the APK.
|
|
- Gradle reads the app's `build.gradle`, which applies the Flutter framework's
|
|
`flutter.gradle` plugin.
|
|
- `flutter.gradle` adds the Flutter engine and all of its support library
|
|
dependencies as a `jar` dependency to the Flutter app.
|
|
- `flutter.gradle` adds all of the Flutter plugins of an app as "subproject"
|
|
Gradle dependencies (usually).
|
|
- Gradle compiles the Flutter app and all of its subprojects (Flutter plugins)
|
|
into a root APK.
|
|
|
|
Those steps are deliberately listed as bullet points and not a numbered list.
|
|
Gradle invokes a lot of these steps at seemingly random and parallel times.
|
|
Time is relative.
|
|
|
|
## Plugins are compiled as subprojects (usually)
|
|
|
|
Flutter plugins are typically included in Flutter apps as source code and
|
|
compiled by Gradle, the same as the rest of the code in the Flutter app.
|
|
|
|
This is done by using the Gradle concept of
|
|
["subprojects"](https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:subproject_configuration),
|
|
a way of structuring build tasks so that they're marked as being dependent on
|
|
each other. All Flutter apps are built with one subproject for the actual
|
|
Android code of the app and other subprojects for the Android code of any
|
|
plugins.
|
|
|
|
For example, let's assume that there's a Flutter app that uses two plugins:
|
|
`foo` and `bar`. Let's also assume `foo` depends on `baz`, so `baz` is included
|
|
as part of the build at runtime. Running `./gradlew projects` in the app's
|
|
`android` directory (after first running `flutter build apk` to give the Flutter
|
|
tool's Dart code an opportunity to set up the build) would reveal the following
|
|
project structure:
|
|
|
|
```console
|
|
$ ./gradlew projects
|
|
|
|
> Task :projects
|
|
|
|
------------------------------------------------------------
|
|
Root project
|
|
------------------------------------------------------------
|
|
|
|
Root project 'android'
|
|
+--- Project ':app'
|
|
+--- Project ':bar'
|
|
+--- Project ':baz''
|
|
\--- Project ':foo'
|
|
```
|
|
|
|
### The how
|
|
|
|
- [The Flutter tool's Dart
|
|
code](https://github.com/flutter/flutter/blob/82410a5ae7159b9f39e85b713decf532840ac70c/packages/flutter_tools/lib/src/build_system/targets/assets.dart#L115-L119)
|
|
writes a `.flutter-plugins` file containing a list of all the plugins used by
|
|
the app and their location on disk.
|
|
- [`flutter.gradle`](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/packages/flutter_tools/gradle/flutter.gradle#L243-L264)
|
|
reads from this file and actually adds the plugins as Gradle subproject
|
|
dependencies of the app.
|
|
- This also depends on the Flutter app having a block of code in its template
|
|
that actually references and builds these subprojects once they're defined,
|
|
see ["hello world" as an example of the app's Gradle
|
|
script](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/examples/hello_world/android/settings.gradle#L15-L19).
|
|
|
|
### Special Considerations
|
|
|
|
Including plugin dependencies as source code and then compiling them is not
|
|
something Gradle generally expects apps to be doing. In the past we've hit
|
|
several issues caused by Gradle tooling around dependency management being built
|
|
for typical Android patterns (including dependencies as `.aars` or `.jars`) and
|
|
breaking down for the Flutter use case.
|
|
|
|
For example:
|
|
|
|
- Plugins have their own `build.gradle` scripts that may conflict with the app
|
|
and other plugins in unexpected ways. How Gradle handles these conflicts is
|
|
situational and sometimes undefined behavior.
|
|
- If a plugin has a problem in its Android code, the entire user's app will fail
|
|
to compile with a stack trace that may or may not really point to the plugin's
|
|
code on disk.
|
|
- If the plugin's code conflicts with the app's Android source code (for
|
|
example, the plugin has a conflicting transitive dependency), Gradle will
|
|
generally fail to compile with difficult to translate stack traces.
|
|
- These subprojects are added in an `afterEvaluate` hook in `flutter.gradle`.
|
|
Generally adding any logic in `afterEvaluate` blocks is discouraged because it
|
|
runs after so much of Gradle's standard pipeline.
|
|
|
|
## Sometimes plugins are compiled as AARs
|
|
|
|
Because of the problems with compiling the plugins as subprojects, the
|
|
Flutter team explored switching the plugins to instead be compiled first as
|
|
standalone AARs and then included as binary dependencies as is more typical for
|
|
Android dependencies and expected by Gradle. This effort ended up being blocked,
|
|
but is still occasionally automatically used as a fallback attempt when one of the typical
|
|
problems with building as subprojects is detected as failing a build today.
|
|
|
|
The basic AAR flow goes like this:
|
|
|
|
- The Flutter tool fetches any plugin dependencies of the Flutter app from pub
|
|
and downloads it to the pub cache. This step follows normal `pub get` behavior
|
|
and version solving.
|
|
- The Flutter tool writes project metadata to some local temporary files. This
|
|
includes things like the location of the local Flutter SDK, and paths to all
|
|
the newly downloaded plugins from pub on disk.
|
|
- The Flutter tool invokes Gradle to build the APK.
|
|
- Gradle reads the app's `build.gradle`, which applies the Flutter framework's
|
|
`flutter.gradle` plugin.
|
|
- `flutter.gradle` adds the Flutter engine and all of its support library
|
|
dependencies as a `jar` dependency to the Flutter app.
|
|
- `flutter.gradle` compiles all of the plugins individually into `aars`, and
|
|
adds all of the Flutter plugins as `aar` dependencies to the Flutter app.
|
|
- Gradle compiles the Flutter app and all of its binary dependencies into a root
|
|
APK.
|
|
|
|
### Why this isn't the standard
|
|
|
|
There were a couple blocking issues that kept us from following this flow in all
|
|
cases:
|
|
|
|
- The implementation of this depends on `TaskInternal.execute()`, a Gradle API
|
|
that's been removed with no replacement in Gradle 5.
|
|
- This style of build is extremely slow compared to the previous build flow.
|
|
- The plugins currently in the Flutter ecosystem were written under the previous
|
|
build system and in many cases depend on the transitive dependency bleed to
|
|
work. For example, many plugins built this way will fail to compile
|
|
individually because they're missing an explicit dependency on
|
|
`androidx.annotation`. There are also other plugins that deliberately expose
|
|
their own transitive `jar` dependencies in a way where they're automatically
|
|
included in the app at build time by the subproject system, but apps including them as
|
|
AARs will fail with "Class not found errors" unless the transitive plugin dependencies
|
|
from the plugin are manually added into the apps' Gradle dependencies as well.
|
|
|
|
### When this is still used
|
|
|
|
We still fall back on this as a silent retry if we detect that the failure is
|
|
caused by one of the likely error cases from the subproject structure:
|
|
specifically, if we detect that the build has failed because of a likely
|
|
AndroidX incompatibility issue. Jetifier is one of the Gradle tools that doesn't
|
|
function normally with the subproject structure, so building the plugins as AARs
|
|
can sometimes fix errors in that class.
|