Michael Goderbauer 6f09064e78
Stand-alone widget tree with multiple render trees to enable multi-view rendering (#125003)
This change enables Flutter to generate multiple Scenes to be rendered into separate FlutterViews from a single widget tree. Each Scene is described by a separate render tree, which are all associated with the single widget tree.

This PR implements the framework-side mechanisms to describe the content to be rendered into multiple views. Separate engine-side changes are necessary to provide these views to the framework and to draw the framework-generated Scene into them.

## Summary of changes

The details of this change are described in [flutter.dev/go/multiple-views](https://flutter.dev/go/multiple-views). Below is a high-level summary organized by layers.

### Rendering layer changes

* The `RendererBinding` no longer owns a single `renderView`. In fact, it doesn't OWN any `RenderView`s at all anymore. Instead, it offers an API (`addRenderView`/`removeRenderView`) to add and remove `RenderView`s that then will be MANAGED by the binding. The `RenderView` itself is now owned by a higher-level abstraction (e.g. the `RawView` Element of the widgets layer, see below), who is also in charge of adding it to the binding. When added, the binding will interact with the `RenderView` to produce a frame (e.g. by calling `compositeFrame` on it) and to perform hit tests for incoming pointer events. Multiple `RenderView`s can be added to the binding (typically one per `FlutterView`) to produce multiple Scenes.
* Instead of owning a single `pipelineOwner`, the `RendererBinding` now owns the root of the `PipelineOwner` tree (exposed as `rootPipelineOwner` on the binding). Each `PipelineOwner` in that tree (except for the root) typically manages its own render tree typically rooted in one of the `RenderView`s mentioned in the previous bullet. During frame production, the binding will instruct each `PipelineOwner` of that tree to flush layout, paint, semantics etc. A higher-level abstraction (e.g. the widgets layer, see below) is in charge of adding `PipelineOwner`s to this tree.
* Backwards compatibility: The old `renderView` and `pipelineOwner` properties of the `RendererBinding` are retained, but marked as deprecated. Care has been taken to keep their original behavior for the deprecation period, i.e. if you just call `runApp`, the render tree bootstrapped by this call is rooted in the deprecated `RendererBinding.renderView` and managed by the deprecated `RendererBinding.pipelineOwner`.

### Widgets layer changes

* The `WidgetsBinding` no longer attaches the widget tree to an existing render tree. Instead, it bootstraps a stand-alone widget tree that is not backed by a render tree. For this, `RenderObjectToWidgetAdapter` has been replaced by `RootWidget`.
* Multiple render trees can be bootstrapped and attached to the widget tree with the help of the `View` widget, which internally is backed by a `RawView` widget. Configured with a `FlutterView` to render into, the `RawView` creates a new `PipelineOwner` and a new `RenderView` for the new render tree. It adds the new `RenderView` to the `RendererBinding` and its `PipelineOwner` to the pipeline owner tree.
* The `View` widget can only appear in certain well-defined locations in the widget tree since it bootstraps a new render tree and does not insert a `RenderObject` into an ancestor. However, almost all Elements expect that their children insert `RenderObject`s, otherwise they will not function properly. To produce a good error message when the `View` widget is used in an illegal location, the `debugMustInsertRenderObjectIntoSlot` method has been added to Element, where a child can ask whether a given slot must insert a RenderObject into its ancestor or not. In practice, the `View` widget can be used as a child of the `RootWidget`, inside the `view` slot of the `ViewAnchor` (see below) and inside a `ViewCollection` (see below). In those locations, the `View` widget may be wrapped in other non-RenderObjectWidgets (e.g. InheritedWidgets).
* The new `ViewAnchor` can be used to create a side-view inside a parent `View`. The `child` of the `ViewAnchor` widget renders into the parent `View` as usual, but the `view` slot can take on another `View` widget, which has access to all inherited widgets above the `ViewAnchor`. Metaphorically speaking, the view is anchored to the location of the `ViewAnchor` in the widget tree.
* The new `ViewCollection` widget allows for multiple sibling views as it takes a list of `View`s as children. It can be used in all the places that accept a `View` widget.

## Google3

As of July 5, 2023 this change passed a TAP global presubmit (TGP) in google3: tap/OCL:544707016:BASE:545809771:1688597935864:e43dd651

## Note to reviewers

This change is big (sorry). I suggest focusing the initial review on the changes inside of `packages/flutter` first. The majority of the changes describe above are implemented in (listed in suggested review order):

* `rendering/binding.dart`
* `widgets/binding.dart`
* `widgets/view.dart`
* `widgets/framework.dart`

All other changes included in the PR are basically the fallout of what's implemented in those files. Also note that a lot of the lines added in this PR are documentation and tests.

I am also very happy to walk reviewers through the code in person or via video call, if that is helpful.

I appreciate any feedback.

## Feedback to address before submitting ("TODO")
2023-07-17 16:14:08 +00:00
..
2019-11-27 15:04:02 -08:00
2023-07-13 18:40:50 +00:00
2023-03-01 01:21:56 +00:00
2019-03-06 13:13:45 -08:00

Flutter's Build Infrastructure

This directory exists to support building Flutter on our build infrastructure.

Flutter build results are available at:

Flutter infra requires special permissions to retrigger builds on the build dashboard. File an infra ticket to request permission.

The Cirrus-based bots run the test.dart script for each PR and submission. This does testing for the tools, for the framework, and (for submitted changes only) rebuilds and updates the master branch API docs staging site. For tagged dev and beta builds, it also builds and deploys the gallery app to the app stores. It is configured by the .cirrus.yml.

The build dashboard includes post-commit testing run on physical devices. See //dev/devicelab for more information.

LUCI (Layered Universal Continuous Integration)

A set of infra scripts run on Windows, Linux, and Mac machines. The configuration for how many machines and what kind are managed internally by Google. File an infra ticket to request new machine types to be added. Both of these technologies are highly specific to the LUCI project, which is the successor to Chromium's infra and the foundation to Flutter's infrastructure.

Prerequisites

To work on this infrastructure you will need:

  • depot_tools
  • Python package installer: sudo apt-get install python-pip
  • Python coverage package (only needed for training_simulation): sudo pip install coverage

To run prepare_package.dart locally:

  • Make sure the depot_tools is in your PATH. If you're on Windows, you also need an environment variable called DEPOT_TOOLS with the path to depot_tools as value.
  • Run gsutil.py config (or python3 %DEPOT_TOOLS%\gsutil.py on Windows) to authenticate with your auth token.
  • Create a local temp directory. cd into it.
  • Run dart [path to your normal Flutter repo]/dev/bots/prepare_package.dart --temp_dir=. --revision=[revision to package] --branch=[branch to deploy to] --publish.
  • If you're running into gsutil permission issues, check with @Hixie to make sure you have the right push permissions.

Editing a recipe

Flutter has several recipes depending on the test. The recipes share common actions through recipe_modules. Searching the builder config in infra will indicate the recipe used for a test.

Recipes are just Python with some limitations on what can be imported. They are documented by the luci/recipes-py GitHub project.

The typical cycle for editing a recipe is:

  1. Check out the recipes project using git clone https://flutter.googlesource.com/recipes.
  2. Make your edits (probably to files in //recipes/recipes).
  3. Update the tests. Run recipes.py test train to update the existing expected output to match the new output. Verify completely new test cases by altering the GenTests method of the recipe. The recipe is required to have 100% test coverage.
  4. Run led get-builder 'luci.flutter.staging:BUILDER_NAME' | led edit -pa git_ref='refs/pull/<PR number>/head' | led edit -pa git_url='https://github.com/flutter/<repo>' | led edit-recipe-bundle | led launch, where BUILDER_NAME is the builder name (e.g. Linux Engine), and git_ref/git_url is the ref/url of the intended changes to build.
    • If led fails, ensure that your depot_tools checkout is up to date.
  5. To submit a CL, you need a local branch first (git checkout -b [some branch name]).
  6. Upload the patch (git commit, git cl upload), and open the outputted URL to the CL.
  7. Use "Find owners" to get reviewers for the CL

Android Tools

The Android SDK and NDK used by Flutter's Chrome infra bots are stored in Google Cloud. During the build, a bot runs the download_android_tools.py script that downloads the required version of the Android SDK into dev/bots/android_tools.

To check which components are currently installed, download the current SDK stored in Google Cloud using the download_android_tools.py script, then dev/bots/android_tools/sdk/tools/bin/sdkmanager --list. If you find that some components need to be updated or installed, follow the steps below:

How to update Android SDK on Google Cloud Storage

  1. Run Android SDK Manager and update packages $ dev/bots/android_tools/sdk/tools/android update sdk Use android.bat on Windows.

  2. Use the UI to choose the packages you want to install and/or update.

  3. Run dev/bots/android_tools/sdk/tools/bin/sdkmanager --update. On Windows, run sdkmanager.bat instead. If the process fails with an error saying that it is unable to move files (Windows makes files and directories read-only when another process is holding them open), make a copy of the dev/bots/android_tools/sdk/tools directory, run the sdkmanager.bat from the copy, and use the --sdk_root option pointing at dev/bots/android_tools/sdk.

  4. Run dev/bots/android_tools/sdk/tools/bin/sdkmanager --licenses and accept the licenses for the newly installed components. It also helps to run this command a second time and make sure that it prints "All SDK package licenses accepted".

  5. Run upload_android_tools.py -t sdk $ dev/bots/upload_android_tools.py -t sdk

How to update Android NDK on Google Cloud Storage

  1. Download a new NDK binary (e.g. android-ndk-r10e-linux-x86_64.bin)

  2. cd dev/bots/android_tools $ cd dev/bots/android_tools

  3. Remove the old ndk directory $ rm -rf ndk

  4. Run the new NDK binary file $ ./android-ndk-r10e-linux-x86_64.bin

  5. Rename the extracted directory to ndk $ mv android-ndk-r10e ndk

  6. Run upload_android_tools.py -t ndk $ cd ../.. $ dev/bots/upload_android_tools.py -t ndk

Flutter codelabs build test

The Flutter codelabs exercise Material Components in the form of a demo application. The code for the codelabs is similar to, but distinct from, the code for the Shrine demo app in Flutter Gallery.

The Flutter codelabs build test ensures that the final version of the Material Components for Flutter Codelabs can be built. This test serves as a smoke test for the Flutter framework and should not fail. If it does, please address any issues in your PR and rerun the test. If you feel that the test failing is not a direct result of changes made in your PR or that breaking this test is absolutely necessary, escalate this issue by submitting an issue to the MDC-Flutter Team.

Unpublishing published archives

Flutter downloadable archives are built for each release by our continuous integration systems using the prepare_package.dart script, but if something goes very wrong, and a release is published that wasn't intended to be published, the unpublish_package.dart script may be used to remove the package or packages from the channels in which they were published.

For example To remove a published package corresponding to the git hash d444a455de87a2e40b7f576dc12ffd9ab82fd491, first do a dry run of the script to see what it will do:

$ dart ./unpublish_package.dart --temp_dir=/tmp/foo --revision d444a455de87a2e40b7f576dc12ffd9ab82fd491

And once you've verified the output of the dry run to be sure it is what you want to do, run:

$ dart ./unpublish_package.dart --confirm --temp_dir=/tmp/foo --revision d444a455de87a2e40b7f576dc12ffd9ab82fd491

and it will perform the actions. You will of course need to have access to the cloud storage server and have gsutil installed to perform this operation. Only runs on Linux or macOS systems.

See dart ./unpublish_package.dart --help for more details.

Once the package is unpublished, it will not be available from the website for download, and will not be rebuilt (even though there is a tagged revision in the repo still) unless someone forces the packaging build to run again at that revision to rebuild the package.