diff --git a/flutter/.ci.yaml b/flutter/.ci.yaml new file mode 100644 index 00000000..11b920b3 --- /dev/null +++ b/flutter/.ci.yaml @@ -0,0 +1,7027 @@ +# Describes the targets run in continuous integration environment. +# +# Flutter infra uses this file to generate a checklist of tasks to be performed +# for every commit. +# +# The recipes mentioned below refer to those in this repo: +# https://flutter.googlesource.com/recipes/+/refs/heads/main/recipes/ +# +# The "flutter_drone" recipe just defers to dev/bots/test.dart in this repo, +# with the shard set according to the "shard" key in this file. +# +# More information at: +# * https://github.com/flutter/cocoon/blob/main/CI_YAML.md +enabled_branches: + - master + - flutter-\d+\.\d+-candidate\.\d+ + +platform_properties: + staging_build_linux: + properties: + dependencies: >- + [ + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Ubuntu + cores: "8" + device_type: none + ignore_flakiness: "true" + linux: + properties: + dependencies: >- + [ + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Ubuntu + cores: "8" + device_type: none + # The current android emulator config names can be found here: + # https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/android/avd/proto + # You may use those names for the android_virtual_device version. You may find the + # avd_cipd_version by clicking on the latest available instance and looking for the + # build_id: here: + # https://chrome-infra-packages.appspot.com/p/chromium/tools/android/avd/linux-amd64. + linux_android_emu: + properties: + contexts: >- + [ + "android_virtual_device" + ] + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "android_virtual_device", "version": "android_35_google_apis_x64.textpb"}, + {"dependency": "avd_cipd_version", "version": "build_id:8733065022087935185"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Ubuntu + cores: "8" + device_type: none + kvm: "1" + linux_android_emu_34: + properties: + contexts: >- + [ + "android_virtual_device" + ] + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "android_virtual_device", "version": "android_34_google_apis_x64.textpb"}, + {"dependency": "avd_cipd_version", "version": "build_id:8733065022087935185"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Ubuntu + cores: "8" + device_type: none + kvm: "1" + linux_build_test: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Ubuntu + cores: "8" + device_type: none + linux_android: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Linux + device_type: "msm8952" + + linux_pixel_7pro: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Linux + device_type: "Pixel 7 Pro" + + linux_mokey: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + os: Linux + device_type: "mokey" + + mac: + properties: + contexts: >- + [ + "osx_sdk" + ] + dependencies: >- + [ + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + device_type: none + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_arm64: + properties: + contexts: >- + [ + "osx_sdk" + ] + dependencies: >- + [ + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + device_type: none + cpu: arm64 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_benchmark: + properties: + contexts: >- + [ + "osx_sdk" + ] + dependencies: >- + [ + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + device_type: none + mac_model: "Macmini8,1" + os: Mac-13|Mac-14 + tags: > + ["devicelab", "hostonly", "mac"] + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_x64: + properties: + contexts: >- + [ + "osx_sdk" + ] + dependencies: >- + [ + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + device_type: none + cpu: x86 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_build_test: + properties: + contexts: >- + [ + "osx_sdk" + ] + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + device_type: none + cpu: x86 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_android: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Mac-13|Mac-14 + cpu: x86 + device_type: "msm8952" + mac_arm64_android: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Mac-13|Mac-14 + cpu: arm64 + device_type: "msm8952" + + mac_mokey: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Mac-13|Mac-14 + cpu: x86 + device_type: "mokey" + mac_arm64_mokey: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Mac-13|Mac-14 + cpu: arm64 + device_type: "mokey" + + mac_pixel_7pro: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Mac-13|Mac-14 + cpu: x86 + device_type: "Pixel 7 Pro" + mac_ios: + properties: + contexts: >- + [ + "osx_sdk_devicelab" + ] + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + cpu: x86 + device_os: iOS-17 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_x64_ios: + properties: + contexts: >- + [ + "osx_sdk_devicelab" + ] + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "apple_signing", "version": "version:to_2025"} + ] + os: Mac-13|Mac-14 + cpu: x86 + device_os: iOS-17 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + mac_arm64_ios: + properties: + contexts: >- + [ + "osx_sdk_devicelab" + ] + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "apple_signing", "version": "none"} + ] + os: Mac-13|Mac-14 + cpu: arm64 + device_os: iOS-17 + $flutter/osx_sdk : >- + { + "sdk_version": "15a240d" + } + windows: + properties: + os: Windows-10 + device_type: none + windows_arm64: + properties: + # The arch can be removed after https://github.com/flutter/flutter/issues/135722. + arch: arm + os: Windows + cpu: arm64 + windows_android: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Windows-10 + device_type: "msm8952" + windows_mokey: + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + os: Windows-10 + device_type: "mokey" + +targets: + - name: Linux analyze + recipe: flutter/flutter_drone + timeout: 60 + properties: + shard: analyze + dependencies: >- + [ + {"dependency": "ktlint", "version": "version_1_1_1"} + ] + tags: > + ["framework","hostonly","shard","linux"] + + # This is a benchmark that does not require an attached device. However, we + # are intentionally running it in the devicelab to ensure that it does not + # run on a VM in order to avoid noisy results from the benchmark. + - name: Mac_arm64 analyzer_benchmark + bringup: true # Flaky https://github.com/flutter/flutter/issues/161306 + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + test_timeout_secs: "3600" # 1 hour + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: analyzer_benchmark + + - name: Linux coverage + presubmit: false + recipe: flutter/coverage + timeout: 120 + enabled_branches: + # Don't run this on release branches + - master + properties: + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux packages_autoroller + presubmit: false + recipe: pub_autoroller/pub_autoroller + # This takes a while because we need to fetch network dependencies and run + # Gradle for every android app in the repo + timeout: 45 + enabled_branches: + # Don't run this on release branches + - master + properties: + tags: > + ["framework","hostonly","linux"] + # Requires Android SDK since we may re-generate Gradle lockfiles + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "gh_cli", "version": "version:2.8.0-2-g32256d38"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + + - name: Linux_android_emu android views + recipe: devicelab/devicelab_drone + bringup: true + properties: + tags: > + ["framework","hostonly","linux"] + task_name: android_views + presubmit_max_attempts: "2" + timeout: 60 + + - name: Linux_android_emu_34 android views + recipe: devicelab/devicelab_drone + properties: + tags: > + ["framework","hostonly","linux"] + task_name: android_views + timeout: 60 + + - name: Linux build_aar_module_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab","hostonly"] + task_name: build_aar_module_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux build_tests_1_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + shard: build_tests + subshard: "1_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux build_tests_2_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + shard: build_tests + subshard: "2_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux build_tests_3_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + shard: build_tests + subshard: "3_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux build_tests_4_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + shard: build_tests + subshard: "4_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux build_tests_5_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + shard: build_tests + subshard: "5_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux ci_yaml flutter roller + recipe: infra/ci_yaml + presubmit: false + timeout: 30 + enabled_branches: + # Don't run this on release branches + - master + properties: + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - .ci.yaml + - DEPS + - engine/** + + - name: Linux customer_testing + # This really just runs dev/bots/customer_testing/ci.sh, + # but it does so indirectly via the flutter_drone recipe + # calling the dev/bots/test.dart script. + enabled_branches: + - master + recipe: flutter/flutter_drone + # Timeout in minutes for the whole task. + timeout: 60 + properties: + shard: customer_testing + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" # Allows 45 minutes (up from 30 default) + + - name: Linux docs_publish + recipe: flutter/docs + presubmit: false + timeout: 60 + dimensions: + os: "Linux" + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"}, + {"dependency": "firebase", "version": "v11.0.1"} + ] + tags: > + ["framework", "hostonly", "linux"] + backfill: "false" + validation: docs + validation_name: Docs + firebase_project: main-docs-flutter-prod + release_ref: refs/heads/master + release_build: "true" + drone_dimensions: + - os=Linux + + - name: Linux docs_test + recipe: flutter/flutter_drone + timeout: 90 # https://github.com/flutter/flutter/issues/120901 + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"} + ] + firebase_project: "" + release_ref: "" + tags: > + ["framework", "hostonly", "shard", "linux"] + shard: docs + runIf: + - bin/** + - dev/** + - packages/flutter/** + - packages/flutter_drive/** + - packages/flutter_localizations/** + - packages/flutter_test/** + - packages/flutter_web_plugins/** + - packages/integration_test/** + - .ci.yaml + - engine/** + - DEPS + - dartdoc_options.yaml + + - name: Linux engine_dependency_proxy_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: engine_dependency_proxy_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux firebase_release_smoke_test + recipe: firebaselab/firebaselab + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["firebaselab"] + task_name: release_smoke_test + physical_devices: >- + [ + "--device", "model=shiba,version=34", + "--device", "model=redfin,version=30", + "--device", "model=griffin,version=24" + ] + # TODO(flutter/flutter#123331): This device is flaking. + # "--device", "model=Nexus6P,version=25" + virtual_devices: >- + [ + "--device", "model=Nexus5.gce_x86,version=21", + "--device", "model=Nexus5.gce_x86,version=22", + "--device", "model=Nexus5.gce_x86,version=23", + "--device", "model=Nexus6P,version=25", + "--device", "model=Nexus6P,version=26", + "--device", "model=Nexus6P,version=27", + "--device", "model=NexusLowRes,version=29" + ] + + - name: Linux flutter_packaging_test + recipe: packaging/packaging + presubmit: false + enabled_branches: + - master + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/bots/** + + - name: Linux flutter_plugins + recipe: flutter/flutter_drone + enabled_branches: + - master + timeout: 60 + properties: + shard: flutter_plugins + subshard: analyze + tags: > + ["framework", "hostonly", "shard", "linux"] + + - name: Linux framework_tests_libraries + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: libraries + tags: > + ["framework","hostonly","shard", "linux"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux framework_tests_slow + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: framework_tests + subshard: slow + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux framework_tests_misc + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "android_sdk", "version": "version:35v1"} + ] + shard: framework_tests + subshard: misc + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - dev/** + - examples/api/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux framework_tests_widgets + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: widgets + tags: > + ["framework","hostonly","shard", "linux"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux fuchsia_precache + recipe: flutter/flutter_drone + timeout: 60 + presubmit: false + properties: + shard: fuchsia_precache + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - engine/** + - DEPS + - .ci.yaml + + - name: Linux gradle_desugar_classes_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: gradle_desugar_classes_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux gradle_java8_compile_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: gradle_java8_compile_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux gradle_plugin_bundle_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: gradle_plugin_bundle_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux gradle_plugin_fat_apk_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: gradle_plugin_fat_apk_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux gradle_plugin_light_apk_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: gradle_plugin_light_apk_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux module_custom_host_app_name_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: module_custom_host_app_name_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux module_host_with_custom_build_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: module_host_with_custom_build_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux build_android_host_app_with_module_aar + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: build_android_host_app_with_module_aar + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux build_android_host_app_with_module_source + recipe: devicelab/devicelab_drone + timeout: 60 + presubmit: false + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: build_android_host_app_with_module_source + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux plugin_dependencies_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: plugin_dependencies_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux plugin_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + # TODO(fujino): delete once propagation from + # https://github.com/flutter/flutter/issues/158521 completes. + drone_dimensions: > + ["os=Linux", "os=Ubuntu-20"] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: plugin_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux plugin_test_linux + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "curl", "version": "version:7.64.0"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: plugin_test_linux + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux run_debug_test_linux + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + xvfb: "1" + dependencies: >- + [ + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: run_debug_test_linux + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux linux_desktop_impeller + recipe: devicelab/devicelab_drone + timeout: 60 + presubmit: false + properties: + xvfb: "1" + dependencies: >- + [ + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: linux_desktop_impeller + + - name: Linux android_release_builds_exclude_dev_dependencies_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: android_release_builds_exclude_dev_dependencies_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux run_release_test_linux + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + xvfb: "1" + dependencies: >- + [ + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "ninja", "version": "version:1.9.0"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: run_release_test_linux + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux skp_generator + enabled_branches: + - main + - master + recipe: flutter/flutter_drone + timeout: 60 + properties: + shard: skp_generator + subshard: "0" + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux technical_debt__cost + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} + ] + tags: > + ["devicelab", "hostonly", "linux"] + task_name: technical_debt__cost + + - name: Linux test_ownership + recipe: infra/test_ownership + enabled_branches: + - main + - master + properties: + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - engine/** + - DEPS + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_1_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "1_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_2_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "2_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_3_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "3_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_4_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "4_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_5_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "5_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_integration_tests_6_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_integration_tests + subshard: "6_6" + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux android_preview_tool_integration_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + # This makes use of UpsideDownCake, a preview version of android. Preview versions eventually + # get removed from the sdk manager, so it is hosted on CIPD to ensure integration testing + # doesn't flake when that happens. + # https://chrome-infra-packages.appspot.com/p/flutter/android/sdk/all/linux-amd64/+/version:udcv1 + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:udcv1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: android_preview_tool_integration_tests + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux android_java11_tool_integration_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, + {"dependency": "cmake", "version": "build_id:8787856497187628321"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ninja", "version": "version:1.9.0"}, + {"dependency": "open_jdk", "version": "version:11"} + ] + shard: android_java11_tool_integration_tests + tags: > + ["framework", "hostonly", "shard", "linux"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux android_java11_dependency_smoke_tests + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:11"} + ] + task_name: android_java11_dependency_smoke_tests + tags: > + ["devicelab", "hostonly", "linux"] + test_timeout_secs: "2700" + runIf: + - packages/flutter_tools/templates/** + - .ci.yaml + - engine/** + - DEPS + - dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart + + - name: Linux android_java17_dependency_smoke_tests + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + task_name: android_java17_dependency_smoke_tests + tags: > + ["devicelab", "hostonly", "linux"] + test_timeout_secs: "2700" + runIf: + - packages/flutter_tools/templates/** + - .ci.yaml + - engine/** + - DEPS + - dev/devicelab/bin/tasks/android_java17_dependency_smoke_tests.dart + + - name: Linux tool_tests_commands + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: commands + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux tool_tests_general + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: general + tags: > + ["framework", "hostonly", "shard", "linux"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux_android_emu flutter_driver_android_test + recipe: flutter/flutter_drone + bringup: true + timeout: 60 + properties: + shard: android_engine_tests + tags: > + ["framework", "hostonly", "shard", "linux"] + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + presubmit_max_attempts: "2" + + - name: Linux_android_emu_34 flutter_driver_android_test + recipe: flutter/flutter_drone + timeout: 60 + properties: + shard: android_engine_tests + tags: > + ["framework", "hostonly", "shard", "linux"] + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + + - name: Linux web_benchmarks_canvaskit + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} + ] + tags: > + ["devicelab","hostonly", "linux"] + task_name: web_benchmarks_canvaskit + + - name: Linux web_benchmarks_skwasm + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} + ] + tags: > + ["devicelab"] + task_name: web_benchmarks_skwasm + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_benchmarks_skwasm_st + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} + ] + tags: > + ["devicelab"] + task_name: web_benchmarks_skwasm_st + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_long_running_tests_1_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_long_running_tests + subshard: "1_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_long_running_tests_2_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_long_running_tests + subshard: "2_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_long_running_tests_3_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_long_running_tests + subshard: "3_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_long_running_tests_4_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_long_running_tests + subshard: "4_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_long_running_tests_5_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_long_running_tests + subshard: "5_5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_0 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "0" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_1 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "1" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_2 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "2" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_3 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "3" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "4" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "6" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tests_7_last + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tests + subshard: "7_last" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_0 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "0" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_1 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "1" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_2 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "2" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_3 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "3" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "4" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "6" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_canvaskit_tests_7_last + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_canvaskit_tests + subshard: "7_last" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_0 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "0" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_1 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "1" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_2 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "2" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_3 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "3" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "4" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - DEPS + - engine/** + + - name: Linux web_skwasm_tests_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "5" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_6 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "6" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_skwasm_tests_7_last + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_skwasm_tests + subshard: "7_last" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux web_tool_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tool_tests + subshard: "1_1" + tags: > + ["framework", "hostonly", "shard", "linux"] + # Retry for flakes caused by https://github.com/flutter/flutter/issues/132654 + presubmit_max_attempts: "2" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Linux_android_emu android_defines_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + tags: > + ["devicelab", "linux"] + task_name: android_defines_test + presubmit_max_attempts: "2" + + - name: Linux_android_emu_34 android_defines_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + tags: > + ["devicelab", "linux"] + task_name: android_defines_test + + - name: Linux_pixel_7pro android_obfuscate_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: android_obfuscate_test + + - name: Linux_pixel_7pro android_semantics_integration_test + recipe: devicelab/devicelab_drone + bringup: true # Failing: https://github.com/flutter/flutter/issues/153594 + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: android_semantics_integration_test + + # linux mokey benchmark + - name: Linux_mokey android_view_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: android_view_scroll_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey animated_image_gc_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: animated_image_gc_perf + + # linux mokey benchmark + - name: Linux_mokey animated_complex_opacity_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: animated_complex_opacity_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey animated_complex_image_filtered_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: animated_complex_image_filtered_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey animated_placeholder_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: animated_placeholder_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey backdrop_filter_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: backdrop_filter_perf__e2e_summary + + - name: Linux_pixel_7pro backdrop_filter_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: backdrop_filter_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro draw_atlas_perf_opengles__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: draw_atlas_perf_opengles__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro draw_atlas_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: draw_atlas_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro dynamic_path_tessellation_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: dynamic_path_tessellation_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro static_path_tessellation_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: static_path_tessellation_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro hello_world_impeller + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: hello_world_impeller + + - name: Linux_pixel_7pro basic_material_app_android__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: basic_material_app_android__compile + + - name: Linux_pixel_7pro channels_integration_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: channels_integration_test + + # linux mokey benchmark + - name: Linux_mokey clipper_cache_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux","mokey"] + task_name: clipper_cache_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey color_filter_and_fade_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: color_filter_and_fade_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey color_filter_cache_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: color_filter_cache_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey color_filter_with_unstable_child_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux","mokey"] + task_name: color_filter_with_unstable_child_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey shader_mask_cache_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: shader_mask_cache_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey complex_layout_android__scroll_smoothness + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: complex_layout_android__scroll_smoothness + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey complex_layout_scroll_perf__devtools_memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux","mokey"] + task_name: complex_layout_scroll_perf__devtools_memory + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey complex_layout_scroll_perf__memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux","mokey"] + task_name: complex_layout_scroll_perf__memory + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey complex_layout_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux","mokey"] + task_name: complex_layout_scroll_perf__timeline_summary + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + - name: Linux_pixel_7pro complex_layout_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: complex_layout_scroll_perf__timeline_summary + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + - name: Linux_pixel_7pro complex_layout_scroll_perf_impeller__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: complex_layout_scroll_perf_impeller__timeline_summary + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + - name: Linux_pixel_7pro complex_layout_scroll_perf_impeller_gles__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: complex_layout_scroll_perf_impeller_gles__timeline_summary + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey complex_layout_semantics_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: complex_layout_semantics_perf + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey complex_layout__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: complex_layout__start_up + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + # linux mokey benchmark + - name: Linux_mokey cubic_bezier_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: cubic_bezier_perf__e2e_summary + + - name: Linux_pixel_7pro cubic_bezier_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: cubic_bezier_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey cull_opacity_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: cull_opacity_perf__e2e_summary + + - name: Linux_pixel_7pro cull_opacity_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: cull_opacity_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey devtools_profile_start_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: devtools_profile_start_test + + - name: Linux_pixel_7pro drive_perf_debug_warning + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: drive_perf_debug_warning + + - name: Linux_pixel_7pro embedded_android_views_integration_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: embedded_android_views_integration_test + + - name: Linux_android_emu external_textures_integration_test + recipe: devicelab/devicelab_drone + bringup: true + timeout: 60 + # Functionally the same as "presubmit: false", except that we will run on + # presubmit during engine rolls. This test is the *only* automated e2e + # test for external textures for the engine, it should never break. + runIf: + - engine/** + - DEPS + - .ci.yaml + properties: + tags: > + ["devicelab", "linux"] + task_name: external_textures_integration_test + presubmit_max_attempts: "2" + + - name: Linux_android_emu_34 external_textures_integration_test + recipe: devicelab/devicelab_drone + timeout: 60 + # Functionally the same as "presubmit: false", except that we will run on + # presubmit during engine rolls. This test is the *only* automated e2e + # test for external textures for the engine, it should never break. + runIf: + - engine/** + - DEPS + - .ci.yaml + properties: + tags: > + ["devicelab", "linux"] + task_name: external_textures_integration_test + + # linux mokey benchmark + - name: Linux_mokey fading_child_animation_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: fading_child_animation_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey fast_scroll_heavy_gridview__memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: fast_scroll_heavy_gridview__memory + + # linux mokey benchmark + - name: Linux_mokey fast_scroll_large_images__memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: fast_scroll_large_images__memory + + - name: Linux_pixel_7pro flavors_test + bringup: true # Flaky https://github.com/flutter/flutter/issues/156955 + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: flavors_test + + # linux mokey benchmark + - name: Linux_mokey flutter_engine_group_performance + recipe: devicelab/devicelab_drone + bringup: true # https://github.com/flutter/flutter/issues/149666 + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_engine_group_performance + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__back_button_memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__back_button_memory + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__image_cache_memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__image_cache_memory + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__memory_nav + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab" ,"android", "linux", "mokey"] + task_name: flutter_gallery__memory_nav + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: flutter_gallery__start_up + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__start_up_delayed + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__start_up_delayed + + - name: Linux_pixel_7pro flutter_gallery_android__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: flutter_gallery_android__compile + + - name: Linux_pixel_7pro flutter_gallery_v2_chrome_run_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: flutter_gallery_v2_chrome_run_test + + - name: Linux flutter_gallery_v2_web_compile_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "linux"] + task_name: flutter_gallery_v2_web_compile_test + + # linux mokey benchmark + - name: Linux_mokey flutter_test_performance + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: flutter_test_performance + + # linux mokey benchmark + - name: Linux_mokey flutter_view__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_view__start_up + + - name: Linux_pixel_7pro frame_policy_delay_test_android + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: frame_policy_delay_test_android + + # linux mokey benchmark + - name: Linux_mokey fullscreen_textfield_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: fullscreen_textfield_perf + + # linux mokey benchmark + - name: Linux_mokey fullscreen_textfield_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: fullscreen_textfield_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey very_long_picture_scrolling_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 120 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: very_long_picture_scrolling_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey hello_world__memory + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: hello_world__memory + + # linux mokey benchmark + - name: Linux_mokey home_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: home_scroll_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey hot_mode_dev_cycle_linux__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: hot_mode_dev_cycle_linux__benchmark + runIf: + - .ci.yaml + - dev/** + - DEPS + - engine/** + + # linux mokey test + - name: Linux_mokey hybrid_android_views_integration_test + recipe: devicelab/devicelab_drone + presubmit: false + bringup: true # https://github.com/flutter/flutter/issues/148085 + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: hybrid_android_views_integration_test + + # linux mokey benchmark + - name: Linux_mokey image_list_jit_reported_duration + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: image_list_jit_reported_duration + + # linux mokey benchmark + - name: Linux_mokey imagefiltered_transform_animation_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: imagefiltered_transform_animation_perf__timeline_summary + + - name: Linux_pixel_7pro imagefiltered_transform_animation_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: imagefiltered_transform_animation_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey image_list_reported_duration + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: image_list_reported_duration + + - name: Linux_pixel_7pro integration_ui_driver + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: integration_ui_driver + + - name: Linux_pixel_7pro integration_ui_keyboard_resize + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: integration_ui_keyboard_resize + + - name: Linux_pixel_7pro integration_ui_textfield + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: integration_ui_textfield + + # linux mokey benchmark + - name: Linux_mokey large_image_changer_perf_android + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: large_image_changer_perf_android + + - name: Linux_pixel_7pro linux_chrome_dev_mode + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: linux_chrome_dev_mode + + # linux mokey benchmark + - name: Linux_mokey multi_widget_construction_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: multi_widget_construction_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey list_text_layout_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: list_text_layout_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey list_text_layout_impeller_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + ignore_flakiness: "true" + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: list_text_layout_impeller_perf__e2e_summary + + - name: Linux_pixel_7pro native_assets_android + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: native_assets_android + + # linux mokey benchmark + - name: Linux_mokey new_gallery__crane_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: new_gallery__crane_perf + + # linux mokey benchmark + - name: Linux_mokey old_gallery__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: old_gallery__transition_perf + + - name: Linux_build_test flutter_gallery__transition_perf + recipe: devicelab/devicelab_drone_build_test + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__transition_perf + artifact: gallery__transition_perf + drone_dimensions: > + ["device_os=U","os=Linux", "device_type=mokey"] + + - name: Linux_build_test flutter_gallery__transition_perf_e2e + recipe: devicelab/devicelab_drone_build_test + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__transition_perf_e2e + artifact: gallery__transition_perf_e2e + drone_dimensions: > + ["device_os=U","os=Linux", "device_type=mokey"] + + - name: Linux_build_test flutter_gallery__transition_perf_hybrid + recipe: devicelab/devicelab_drone_build_test + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__transition_perf_hybrid + artifact: gallery__transition_perf_hybrid + drone_dimensions: > + ["device_os=U","os=Linux", "device_type=mokey"] + + # linux mokey benchmark + - name: Linux_mokey flutter_gallery__transition_perf_with_semantics + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: flutter_gallery__transition_perf_with_semantics + + # Mokey, Impeller + - name: Linux_mokey new_gallery_impeller__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: new_gallery_impeller__transition_perf + + # Mokey, Impeller (OpenGL) + - name: Linux_mokey new_gallery_opengles_impeller__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: new_gallery_opengles_impeller__transition_perf + + # Mokey, Skia + - name: Linux_mokey new_gallery__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: new_gallery__transition_perf + + # Pixel 7 Pro, Skia + - name: Linux_pixel_7pro new_gallery__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: new_gallery__transition_perf + + # Pixel 7 Pro, Impeller (Vulkan) + - name: Linux_pixel_7pro new_gallery_impeller_old_zoom__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: new_gallery_impeller_old_zoom__transition_perf + + # Pixel 7 Pro, Impeller (Vulkan) + - name: Linux_pixel_7pro new_gallery_impeller__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: new_gallery_impeller__transition_perf + + # Pixel 7 Pro, Impeller (OpenGL) + - name: Linux_pixel_7pro new_gallery_opengles_impeller__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: new_gallery_opengles_impeller__transition_perf + + # linux mokey benchmark + - name: Linux_mokey picture_cache_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: picture_cache_perf__e2e_summary + + - name: Linux_pixel_7pro picture_cache_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: picture_cache_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey android_picture_cache_complexity_scoring_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: android_picture_cache_complexity_scoring_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey slider_perf_android + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: slider_perf_android + + # linux mokey benchmark + - name: Linux_mokey platform_channels_benchmarks + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: platform_channels_benchmarks + + - name: Linux_pixel_7pro platform_channels_benchmarks + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: platform_channels_benchmarks + + - name: Linux_pixel_7pro platform_channel_sample_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: platform_channel_sample_test + + - name: Linux_pixel_7pro platform_interaction_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: platform_interaction_test + + # linux mokey benchmark + - name: Linux_mokey platform_views_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: platform_views_scroll_perf__timeline_summary + + - name: Linux_pixel_7pro platform_views_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: platform_views_scroll_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey platform_views_scroll_perf_impeller__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + ignore_flakiness: "true" + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: platform_views_scroll_perf_impeller__timeline_summary + + - name: Linux_pixel_7pro platform_views_scroll_perf_impeller__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: platform_views_scroll_perf_impeller__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey platform_view__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: platform_view__start_up + + - name: Linux_pixel_7pro routing_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: routing_test + + - name: Linux_pixel_7pro service_extensions_test + bringup: true # Flaky https://github.com/flutter/flutter/issues/157852 + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: service_extensions_test + + # linux mokey benchmark + - name: Linux_mokey textfield_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: textfield_perf__e2e_summary + + - name: Linux_pixel_7pro textfield_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: textfield_perf__timeline_summary + + # linux mokey benchmark + - name: Linux_mokey tiles_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","android","linux", "mokey"] + task_name: tiles_scroll_perf__timeline_summary + dependencies: >- + [ + {"dependency": "open_jdk", "version": "version:17"} + ] + + - name: Linux web_size__compile_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "linux"] + task_name: web_size__compile_test + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_one_rect_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_one_rect_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_col_of_rows_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_col_of_rows_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_opacity_of_grid_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_opacity_of_grid_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_grid_of_opacity_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_grid_of_opacity_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_fade_transition_text_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_fade_transition_text_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey gradient_dynamic_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: gradient_dynamic_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey gradient_consistent_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: gradient_consistent_perf__e2e_summary + + # linux mokey benchmark + - name: Linux_mokey gradient_static_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: gradient_static_perf__e2e_summary + + - name: Linux_pixel_7pro android_choreographer_do_frame_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: android_choreographer_do_frame_test + + # linux mokey benchmark + - name: Linux_mokey animated_blur_backdrop_filter_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "mokey"] + task_name: animated_blur_backdrop_filter_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro animated_blur_backdrop_filter_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: animated_blur_backdrop_filter_perf__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro animated_advanced_blend_perf_opengles__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: animated_advanced_blend_perf_opengles__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro animated_advanced_blend_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: animated_advanced_blend_perf__timeline_summary + + - name: Mac_ios animated_advanced_blend_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: animated_advanced_blend_perf_ios__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro rrect_blur_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: rrect_blur_perf__timeline_summary + + - name: Mac_ios rrect_blur_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: rrect_blur_perf_ios__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro animated_blur_backdrop_filter_perf_opengles__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: animated_blur_backdrop_filter_perf_opengles__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro draw_vertices_perf_opengles__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: draw_vertices_perf_opengles__timeline_summary + + # Uses Impeller. + - name: Linux_pixel_7pro draw_vertices_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "pixel", "7pro"] + task_name: draw_vertices_perf__timeline_summary + + - name: Mac_ios draw_vertices_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: draw_vertices_perf_ios__timeline_summary + + - name: Mac_ios draw_atlas_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: draw_atlas_perf_ios__timeline_summary + + - name: Mac_ios static_path_tessellation_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: static_path_tessellation_perf_ios__timeline_summary + + - name: Mac_ios dynamic_path_tessellation_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: dynamic_path_tessellation_perf_ios__timeline_summary + + - name: Staging_build_linux analyze + presubmit: false + bringup: true + recipe: flutter/flutter_drone + timeout: 60 + properties: + shard: analyze + ignore_flakiness: "true" + tags: > + ["framework","hostonly","shard","linux"] + + - name: Mac_benchmark animated_complex_opacity_perf_macos__e2e_summary + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + task_name: animated_complex_opacity_perf_macos__e2e_summary + + - name: Mac_benchmark basic_material_app_macos__compile + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + task_name: basic_material_app_macos__compile + + - name: Mac build_ios_framework_module_test + recipe: devicelab/devicelab_drone + timeout: 60 + # Flake rate of >3.5% in presubmit + presubmit: false # https://github.com/flutter/flutter/issues/150642 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: build_ios_framework_module_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 build_ios_framework_module_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: build_ios_framework_module_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 build_tests_1_4 + recipe: flutter/flutter_drone + presubmit: false # Rely on Mac_arm build_tests in presubmit. + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "1_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_x64 build_tests_2_4 + recipe: flutter/flutter_drone + presubmit: false # Rely on Mac_arm build_tests in presubmit. + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "2_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_x64 build_tests_3_4 + recipe: flutter/flutter_drone + presubmit: false # Rely on Mac_arm build_tests in presubmit. + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "3_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_x64 build_tests_4_4 + recipe: flutter/flutter_drone + presubmit: false # Rely on Mac_arm build_tests in presubmit. + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "4_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_arm64 build_tests_1_4 + recipe: flutter/flutter_drone + timeout: 60 + # Flake rate of >2.5% in presubmit + presubmit: false # https://github.com/flutter/flutter/issues/150642 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "1_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_arm64 build_tests_2_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "2_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_arm64 build_tests_3_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "3_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_arm64 build_tests_4_4 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: build_tests + subshard: "4_4" + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_benchmark complex_layout_macos__start_up + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + task_name: complex_layout_macos__start_up + + - name: Mac_benchmark complex_layout_scroll_perf_macos__timeline_summary + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + task_name: complex_layout_scroll_perf_macos__timeline_summary + + - name: Mac customer_testing + enabled_branches: + - master + recipe: flutter/flutter_drone + # Timeout in minutes for the whole task. + timeout: 60 + properties: + shard: customer_testing + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" # Allows 45 minutes (up from 30 default) + + - name: Mac dart_plugin_registry_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: dart_plugin_registry_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac flavors_test_macos + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: flavors_test_macos + + - name: Mac_benchmark flutter_gallery_macos__compile + bringup: true # Flaky https://github.com/flutter/flutter/issues/160495 + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + task_name: flutter_gallery_macos__compile + + - name: Mac flutter_packaging_test + recipe: packaging/packaging + presubmit: false + enabled_branches: + - master + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/bots/** + + - name: Mac_arm64 flutter_packaging_test + recipe: packaging/packaging + presubmit: false + enabled_branches: + - master + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/bots/** + + - name: Mac_benchmark flutter_view_macos__start_up + bringup: true # Flaky https://github.com/flutter/flutter/issues/159540 + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + task_name: flutter_view_macos__start_up + + - name: Mac framework_tests_libraries + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: x86 # https://github.com/flutter/flutter/issues/119880 + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: libraries + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac framework_tests_impeller + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: x86 # https://github.com/flutter/flutter/issues/119880 + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: impeller + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 framework_tests_misc + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "android_sdk", "version": "version:35v1"} + ] + shard: framework_tests + subshard: misc + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - examples/api/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 framework_tests_misc + recipe: flutter/flutter_drone + timeout: 60 + properties: + os: Mac-14 + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "android_sdk", "version": "version:35v1"} + ] + # TODO(vashworth): Remove once Xcode 16 is used by all tests + $flutter/osx_sdk : >- + { + "sdk_version": "16c5032a" + } + shard: framework_tests + subshard: misc + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - examples/api/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac framework_tests_widgets + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: x86 # https://github.com/flutter/flutter/issues/119880 + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: widgets + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac gradle_plugin_bundle_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: gradle_plugin_bundle_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_benchmark hello_world_macos__compile + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + task_name: hello_world_macos__compile + + - name: Mac integration_ui_test_test_macos + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "mac"] + task_name: integration_ui_test_test_macos + + - name: Mac module_custom_host_app_name_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: module_custom_host_app_name_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac module_host_with_custom_build_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: module_host_with_custom_build_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac build_android_host_app_with_module_aar + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: build_android_host_app_with_module_aar + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac build_android_host_app_with_module_source + recipe: devicelab/devicelab_drone + timeout: 60 + presubmit: false + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: build_android_host_app_with_module_source + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 module_test_ios + recipe: devicelab/devicelab_drone + timeout: 60 + # Flake rate of >10% in presubmit + presubmit: false # https://github.com/flutter/flutter/issues/150642 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: module_test_ios + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_benchmark platform_view_macos__start_up + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + task_name: platform_view_macos__start_up + + - name: Mac platform_channel_sample_test_macos + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "mac"] + task_name: platform_channel_sample_test_macos + + - name: Mac plugin_dependencies_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_dependencies_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 plugin_lint_mac + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_lint_mac + runIf: + - dev/** + - packages/flutter_tools/** + - packages/integration_test/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 plugin_lint_mac + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: plugin_lint_mac + runIf: + - dev/** + - packages/flutter_tools/** + - packages/integration_test/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac plugin_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac plugin_test_ios + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_test_ios + # Retry for flakes caused by https://github.com/flutter/flutter/issues/151772 + presubmit_max_attempts: "2" + test_timeout_secs: "3600" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac plugin_test_macos + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: plugin_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 tool_host_cross_arch_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + shard: tool_host_cross_arch_tests + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 tool_host_cross_arch_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + shard: tool_host_cross_arch_tests + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac tool_integration_tests_1_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: arm64 + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: tool_integration_tests + subshard: "1_5" + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac tool_integration_tests_2_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: arm64 + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: tool_integration_tests + subshard: "2_5" + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac tool_integration_tests_3_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: arm64 + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: tool_integration_tests + subshard: "3_5" + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac tool_integration_tests_4_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: arm64 + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: tool_integration_tests + subshard: "4_5" + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac tool_integration_tests_5_5 + recipe: flutter/flutter_drone + timeout: 60 + properties: + cpu: arm64 + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: tool_integration_tests + subshard: "5_5" + tags: > + ["framework", "hostonly", "shard", "mac"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 tool_tests_commands + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: commands + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac_arm64 tool_tests_commands + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: commands + tags: > + ["framework", "hostonly", "shard", "mac"] + + - name: Mac tool_tests_general + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: general + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_x64 verify_binaries_codesigned + enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ + recipe: flutter/flutter_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["framework", "hostonly", "shard", "mac"] + shard: verify_binaries_codesigned + + - name: Mac_arm64 verify_binaries_codesigned + enabled_branches: + - flutter-\d+\.\d+-candidate\.\d+ + recipe: flutter/flutter_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["framework", "hostonly", "shard", "mac"] + shard: verify_binaries_codesigned + + - name: Mac web_tool_tests + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tool_tests + subshard: "1_1" + tags: > + ["framework", "hostonly", "shard", "mac"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + # mac mokey test + - name: Mac_arm64_mokey entrypoint_dart_registrant + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: entrypoint_dart_registrant + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + # mac mokey benchmark + - name: Mac_mokey hello_world_android__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: hello_world_android__compile + + # mac mokey test + - name: Mac_arm64_mokey hello_world_android__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: hello_world_android__compile + + # mac mokey benchmark + - name: Mac_mokey hot_mode_dev_cycle__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: hot_mode_dev_cycle__benchmark + + # mac mokey test + - name: Mac_mokey integration_test_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: integration_test_test + + # mac mokey test + - name: Mac_arm64_mokey integration_test_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: integration_test_test + + # mac mokey test + - name: Mac_arm64_mokey integration_ui_frame_number + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: integration_ui_frame_number + + # mac mokey benchmark + - name: Mac_mokey microbenchmarks + recipe: devicelab/devicelab_drone + presubmit: false + bringup: true # Flaky: codefu + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: microbenchmarks + + # mac mokey test + - name: Mac_arm64_mokey native_assets_android + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: native_assets_android + + # mac mokey test + - name: Mac_mokey run_debug_test_android + recipe: devicelab/devicelab_drone + presubmit: false + runIf: + - .ci.yaml + - dev/** + - DEPS + - engine/** + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: run_debug_test_android + + # mac mokey test + - name: Mac_arm64_mokey run_debug_test_android + recipe: devicelab/devicelab_drone + presubmit: false + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/** + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: run_debug_test_android + + # mac mokey test + - name: Mac_mokey run_release_test + bringup: true # Flaky https://github.com/flutter/flutter/issues/153830 + recipe: devicelab/devicelab_drone + presubmit: false + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/** + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "mokey"] + task_name: run_release_test + + # mac mokey test + - name: Mac_arm64_mokey run_release_test + recipe: devicelab/devicelab_drone + presubmit: false + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/** + timeout: 60 + properties: + tags: > + ["devicelab", "android", "mac", "arm64", "mokey"] + task_name: run_release_test + + - name: Mac_ios animation_with_microtasks_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: animation_with_microtasks_perf_ios__timeline_summary + + - name: Mac_ios backdrop_filter_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: backdrop_filter_perf_ios__timeline_summary + + - name: Mac_arm64_ios basic_material_app_ios__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: basic_material_app_ios__compile + + - name: Mac_ios channels_integration_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: channels_integration_test_ios + + - name: Mac_ios complex_layout_ios__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: complex_layout_ios__start_up + + - name: Mac_ios complex_layout_scroll_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: complex_layout_scroll_perf_ios__timeline_summary + + - name: Mac_ios complex_layout_scroll_perf_bad_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: complex_layout_scroll_perf_bad_ios__timeline_summary + + - name: Mac_ios color_filter_and_fade_perf_ios__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: color_filter_and_fade_perf_ios__e2e_summary + + - name: Mac_ios imagefiltered_transform_animation_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: imagefiltered_transform_animation_perf_ios__timeline_summary + + # TODO(https://github.com/flutter/flutter/issues/106806): Find a way to + # re-enable this without "ignore_flakiness: "true"", likely by loostening the + # test assertions, or potentially not running the frame rate tests at all on + # iOS (for example, doing pixel-tests instead). + # + # Also, rename this to "external_textures_integration_test" to be consistent + # with the Android test, but that can wait until we've figured out the flake. + - name: Mac_ios external_ui_integration_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: external_textures_integration_test_ios + ignore_flakiness: "true" + + - name: Mac_ios route_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: route_test_ios + + - name: Mac_ios flavors_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: flavors_test_ios + + - name: Mac_arm64_ios flutter_gallery_ios__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac", "arm64"] + task_name: flutter_gallery_ios__compile + + - name: Mac_ios flutter_gallery_ios__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: flutter_gallery_ios__start_up + + - name: Mac_ios flutter_view_ios__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: flutter_view_ios__start_up + + - name: Mac_arm64_ios hello_world_ios__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac", "arm64"] + task_name: hello_world_ios__compile + + - name: Mac_x64 hot_mode_dev_cycle_macos_target__benchmark + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: hot_mode_dev_cycle_macos_target__benchmark + runIf: + - dev/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 hot_mode_dev_cycle_macos_target__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: hot_mode_dev_cycle_macos_target__benchmark + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/** + + - name: Mac_x64_ios integration_test_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_test_test_ios + + - name: Mac_arm64_ios integration_test_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_test_test_ios + + - name: Mac_ios integration_ui_ios_driver + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_ui_ios_driver + + - name: Mac_ios integration_ui_ios_frame_number + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_ui_ios_frame_number + + - name: Mac_ios integration_ui_ios_keyboard_resize + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_ui_ios_keyboard_resize + + - name: Mac_ios integration_ui_ios_textfield + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_ui_ios_textfield + + - name: Mac_x64 ios_app_with_extensions_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: ios_app_with_extensions_test + + - name: Mac_arm64 ios_app_with_extensions_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: ios_app_with_extensions_test + + - name: Mac_ios ios_defines_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: ios_defines_test + + - name: Mac_ios ios_platform_view_tests + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: ios_platform_view_tests + + - name: Mac_ios large_image_changer_perf_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: large_image_changer_perf_ios + + - name: Mac_x64 macos_chrome_dev_mode + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: macos_chrome_dev_mode + + - name: Mac_arm64 macos_chrome_dev_mode + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: macos_chrome_dev_mode + + - name: Mac_ios microbenchmarks_ios + bringup: true # Flakey https://github.com/flutter/flutter/issues/156457 + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: microbenchmarks_ios + + - name: Mac native_assets_ios_simulator + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: native_assets_ios_simulator + + - name: Mac_ios native_assets_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: native_assets_ios + + - name: Mac_ios native_platform_view_ui_tests_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: native_platform_view_ui_tests_ios + + - name: Mac_ios new_gallery_ios__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: new_gallery_ios__transition_perf + + - name: Mac_ios new_gallery_skia_ios__transition_perf + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: new_gallery_skia_ios__transition_perf + + - name: Mac_ios platform_channel_sample_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_channel_sample_test_ios + + - name: Mac_ios platform_channel_sample_test_swift + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_channel_sample_test_swift + + - name: Mac_ios platform_channels_benchmarks_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_channels_benchmarks_ios + + - name: Mac_ios platform_interaction_test_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_interaction_test_ios + + - name: Mac_ios platform_view_ios__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_view_ios__start_up + + - name: Mac_ios platform_views_scroll_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_views_scroll_perf_ios__timeline_summary + + - name: Mac_ios platform_views_scroll_perf_ad_banners__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_views_scroll_perf_ad_banners__timeline_summary + + - name: Mac_ios platform_views_scroll_perf_bottom_ad_banner__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_views_scroll_perf_bottom_ad_banner__timeline_summary + + - name: Mac_ios platform_views_scroll_perf_non_intersecting_impeller_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: platform_views_scroll_perf_non_intersecting_impeller_ios__timeline_summary + + - name: Mac_ios post_backdrop_filter_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: post_backdrop_filter_perf_ios__timeline_summary + + - name: Mac_ios simple_animation_perf_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: simple_animation_perf_ios + + - name: Mac_ios wide_gamut_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: wide_gamut_ios + + - name: Mac_x64_ios hot_mode_dev_cycle_ios__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: hot_mode_dev_cycle_ios__benchmark + + - name: Mac_arm64_ios hot_mode_dev_cycle_ios__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + # TODO(vashworth): Use "hot_mode_dev_cycle_ios__benchmark" once https://github.com/flutter/flutter/issues/142305 is fixed. + task_name: hot_mode_dev_cycle_ios__benchmark_no_dds + + - name: Mac_x64 hot_mode_dev_cycle_ios_simulator + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: hot_mode_dev_cycle_ios_simulator + + - name: Mac_ios fullscreen_textfield_perf_ios__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: fullscreen_textfield_perf_ios__e2e_summary + + - name: Mac_ios very_long_picture_scrolling_perf_ios__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 120 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: very_long_picture_scrolling_perf_ios__e2e_summary + + - name: Mac_ios tiles_scroll_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: tiles_scroll_perf_ios__timeline_summary + + - name: Mac_build_test flutter_gallery__transition_perf_e2e_ios + recipe: devicelab/devicelab_drone_build_test + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: flutter_gallery__transition_perf_e2e_ios + drone_dimensions: > + ["device_os=iOS-17","os=Mac-13|Mac-14", "cpu=x86"] + + - name: Mac_ios animated_blur_backdrop_filter_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: animated_blur_backdrop_filter_perf_ios__timeline_summary + + - name: Mac_ios draw_points_perf_ios__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: draw_points_perf_ios__timeline_summary + + - name: Mac_ios spell_check_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: spell_check_test_ios + + - name: Mac_x64 native_ui_tests_macos + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: native_ui_tests_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 native_ui_tests_macos + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: native_ui_tests_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac channels_integration_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: channels_integration_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac run_debug_test_macos + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac"] + task_name: run_debug_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 run_debug_test_macos + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: run_debug_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 run_release_test_macos + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: run_release_test_macos + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Mac_arm64 mac_desktop_impeller + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} + ] + tags: > + ["devicelab", "hostonly", "mac", "arm64"] + task_name: run_release_test_macos + + - name: Windows build_tests_1_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "1_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_2_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "2_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_3_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "3_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_4_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "4_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_5_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "5_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_6_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "6_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_7_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "7_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_8_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "8_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows build_tests_9_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: build_tests + subshard: "9_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + + - name: Windows customer_testing + enabled_branches: + - master + recipe: flutter/flutter_drone + # Timeout in minutes for the whole task. + timeout: 60 + properties: + shard: customer_testing + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" # Allows 45 minutes (up from 30 default) + + - name: Windows framework_tests_libraries + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: libraries + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows framework_tests_libraries_leak_tracking + recipe: flutter/flutter_drone + timeout: 120 + properties: + test_timeout_secs: "3600" # 1 hour + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: libraries + tags: > + ["framework", "hostonly", "shard", "windows"] + env_variables: >- + { + "LEAK_TRACKING": "true", + "TEST_RANDOMIZATION_OFF": "true" + } + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows framework_tests_misc + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "android_sdk", "version": "version:35v1"} + ] + shard: framework_tests + subshard: misc + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - dev/** + - examples/api/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows framework_tests_widgets + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: widgets + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows framework_tests_widgets_leak_tracking + recipe: flutter/flutter_drone + timeout: 120 + properties: + test_timeout_secs: "3600" # 1 hour + dependencies: >- + [ + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: framework_tests + subshard: widgets + tags: > + ["framework", "hostonly", "shard", "windows"] + env_variables: >- + { + "LEAK_TRACKING": "true", + "TEST_RANDOMIZATION_OFF": "true" + } + runIf: + - dev/** + - packages/flutter/** + - packages/flutter_driver/** + - packages/integration_test/** + - packages/flutter_localizations/** + - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_test/** + - packages/flutter_goldens/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows gradle_plugin_bundle_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: gradle_plugin_bundle_test + runIf: + - dev/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows hot_mode_dev_cycle_win_target__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: hot_mode_dev_cycle_win_target__benchmark + + - name: Windows_arm64 hot_mode_dev_cycle_win_target__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: hot_mode_dev_cycle_win_target__benchmark + + - name: Windows module_custom_host_app_name_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: module_custom_host_app_name_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows module_host_with_custom_build_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: module_host_with_custom_build_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows build_android_host_app_with_module_aar + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: build_android_host_app_with_module_aar + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows build_android_host_app_with_module_source + recipe: devicelab/devicelab_drone + timeout: 60 + presubmit: false + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: build_android_host_app_with_module_source + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows platform_channel_sample_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: platform_channel_sample_test_windows + + - name: Windows_arm64 platform_channel_sample_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: platform_channel_sample_test_windows + + - name: Windows plugin_dependencies_test + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: plugin_dependencies_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows plugin_test + bringup: true # Flaky https://github.com/flutter/flutter/issues/148834 + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: plugin_test + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows plugin_test_windows + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: plugin_test_windows + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows_arm64 plugin_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: plugin_test_windows + test_timeout_secs: "900" # 15 minutes + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows run_debug_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: run_debug_test_windows + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows_arm64 run_debug_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: run_debug_test_windows + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows run_release_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: run_release_test_windows + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows_arm64 run_release_test_windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: run_release_test_windows + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_1_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "1_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_2_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "2_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_3_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "3_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_4_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "4_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_5_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "5_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_6_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "6_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_7_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "7_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_8_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "8_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_integration_tests_9_9 + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, + {"dependency": "vs_build", "version": "version:vs2019"} + ] + shard: tool_integration_tests + subshard: "9_9" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_tests_commands + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: commands + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows tool_tests_general + recipe: flutter/flutter_drone + timeout: 60 + properties: + add_recipes_cq: "true" + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "open_jdk", "version": "version:17"} + ] + shard: tool_tests + subshard: general + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows web_tool_tests_1_2 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tool_tests + subshard: "1_2" + tags: > + ["framework", "hostonly", "shard", "windows"] + test_timeout_secs: "2700" + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows web_tool_tests_2_2 + recipe: flutter/flutter_drone + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:35v1"}, + {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, + {"dependency": "open_jdk", "version": "version:17"}, + {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} + ] + shard: web_tool_tests + subshard: "2_2" + tags: > + ["framework", "hostonly", "shard"] + runIf: + - dev/** + - packages/flutter_tools/** + - bin/** + - .ci.yaml + - engine/** + - DEPS + + - name: Windows windows_home_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: windows_home_scroll_perf__timeline_summary + + - name: Windows_arm64 windows_home_scroll_perf__timeline_summary + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: windows_home_scroll_perf__timeline_summary + + + - name: Windows hello_world_win_desktop__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: hello_world_win_desktop__compile + + - name: Windows windows_desktop_impeller + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: windows_desktop_impeller + + - name: Windows_arm64 hello_world_win_desktop__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: hello_world_win_desktop__compile + + - name: Windows flutter_gallery_win_desktop__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_gallery_win_desktop__compile + + - name: Windows_arm64 flutter_gallery_win_desktop__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_gallery_win_desktop__compile + + - name: Windows flutter_gallery_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_gallery_win_desktop__start_up + + - name: Windows_arm64 flutter_gallery_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_gallery_win_desktop__start_up + + - name: Windows complex_layout_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: complex_layout_win_desktop__start_up + + - name: Windows_arm64 complex_layout_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: complex_layout_win_desktop__start_up + + - name: Windows flutter_view_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_view_win_desktop__start_up + + - name: Windows_arm64 flutter_view_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: flutter_view_win_desktop__start_up + + - name: Windows platform_view_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: platform_view_win_desktop__start_up + + - name: Windows_arm64 platform_view_win_desktop__start_up + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + task_name: platform_view_win_desktop__start_up + + # windows mokey test + - name: Windows_mokey basic_material_app_win__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: basic_material_app_win__compile + + # windows mokey test + - name: Windows_mokey channels_integration_test_win + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: channels_integration_test_win + + # windows mokey test + - name: Windows_mokey flavors_test_win + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: flavors_test + + # windows mokey test + - name: Windows_mokey flutter_gallery_win__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: flutter_gallery_win__compile + + # windows mokey benchmark + - name: Windows_mokey hot_mode_dev_cycle_win__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: hot_mode_dev_cycle_win__benchmark + + # windows mokey test + - name: Windows_mokey native_assets_android + bringup: true # Flaky https://github.com/flutter/flutter/issues/156063 + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: native_assets_android + + # windows mokey test + - name: Windows_mokey windows_chrome_dev_mode + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "windows", "mokey"] + task_name: windows_chrome_dev_mode + + - name: Windows flutter_packaging_test + recipe: packaging/packaging + presubmit: false + enabled_branches: + - master + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "windows"] + runIf: + - .ci.yaml + - engine/** + - DEPS + - dev/bots/** + + - name: Windows windows_startup_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows"] + task_name: windows_startup_test + + - name: Windows_arm64 windows_startup_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "vs_build", "version": "version:vs2019"} + ] + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: windows_startup_test + + - name: Windows flutter_tool_startup__windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows"] + task_name: flutter_tool_startup + + - name: Windows_arm64 flutter_tool_startup__windows + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "windows", "arm64"] + task_name: flutter_tool_startup + + - name: Linux flutter_tool_startup__linux + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "hostonly", "linux"] + task_name: flutter_tool_startup + + - name: Mac_benchmark flutter_tool_startup__macos + presubmit: false + recipe: devicelab/devicelab_drone + timeout: 60 + properties: + task_name: flutter_tool_startup + + - name: Linux flutter_packaging + recipe: packaging/packaging + timeout: 60 + scheduler: release + bringup: true # https://github.com/flutter/flutter/issues/126286 + enabled_branches: + - beta + - stable + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "linux"] + drone_dimensions: + - os=Linux + + - name: Mac flutter_packaging + recipe: packaging/packaging + timeout: 60 + scheduler: release + enabled_branches: + - beta + - stable + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "mac"] + drone_dimensions: + - os=Mac + - cpu=x86 + + + - name: Mac_arm64 flutter_packaging + recipe: packaging/packaging + timeout: 60 + scheduler: release + enabled_branches: + - beta + - stable + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "mac"] + drone_dimensions: + - os=Mac + - cpu=arm64 + + - name: Windows flutter_packaging + recipe: packaging/packaging + timeout: 60 + scheduler: release + bringup: true + enabled_branches: + - beta + - stable + properties: + task_name: flutter_packaging + tags: > + ["framework", "hostonly", "shard", "windows"] + drone_dimensions: + - os=Windows + + + - name: Linux docs_deploy_beta + recipe: flutter/docs + scheduler: release + bringup: true + enabled_branches: + - beta + presubmit: false + timeout: 60 + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"}, + {"dependency": "firebase", "version": "v11.0.1"} + ] + tags: > + ["framework", "hostonly", "linux"] + validation: docs_deploy + validation_name: Docs_deploy + firebase_project: master-docs-flutter-dev + drone_dimensions: + - os=Linux + + - name: Linux docs_deploy_stable + recipe: flutter/docs + scheduler: release + bringup: true + enabled_branches: + - stable + presubmit: false + timeout: 60 + properties: + cores: "32" + dependencies: >- + [ + {"dependency": "dashing", "version": "0.4.0"}, + {"dependency": "firebase", "version": "v11.0.1"} + ] + tags: > + ["framework", "hostonly", "linux"] + validation: docs_deploy + validation_name: Docs_deploy + firebase_project: docs-flutter-dev + drone_dimensions: + - os=Linux diff --git a/flutter/.gitattributes b/flutter/.gitattributes new file mode 100644 index 00000000..1f9dafd2 --- /dev/null +++ b/flutter/.gitattributes @@ -0,0 +1,34 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Always perform LF normalization on these files +*.dart text +*.gradle text +*.html text +*.java text +*.json text +*.md text +*.py text +*.sh text +*.txt text +*.xml text +*.yaml text + +# Make sure that these Windows files always have CRLF line endings in checkout +*.bat text eol=crlf +*.ps1 text eol=crlf +*.rc text eol=crlf +*.sln text eol=crlf +*.props text eol=crlf +*.vcxproj text eol=crlf +*.vcxproj.filters text eol=crlf +# Including templatized versions. +*.sln.tmpl text eol=crlf +*.props.tmpl text eol=crlf +*.vcxproj.tmpl text eol=crlf + +# Never perform LF normalization on these files +*.ico binary +*.jar binary +*.png binary +*.zip binary diff --git a/flutter/.github/PULL_REQUEST_TEMPLATE.md b/flutter/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..020de084 --- /dev/null +++ b/flutter/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ + + +*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* + +*List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* + +*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* + +## Pre-launch Checklist + +- [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. +- [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. +- [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. +- [ ] I signed the [CLA]. +- [ ] I listed at least one issue that this PR fixes in the description above. +- [ ] I updated/added relevant documentation (doc comments with `///`). +- [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. +- [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. +- [ ] All existing and new tests are passing. + +If you need help, consider asking for advice on the #hackers-new channel on [Discord]. + + +[Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview +[Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md +[test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests +[Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md +[Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement +[CLA]: https://cla.developers.google.com/ +[flutter/tests]: https://github.com/flutter/tests +[breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes +[Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md +[Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md diff --git a/flutter/.github/dependabot.yml b/flutter/.github/dependabot.yml new file mode 100644 index 00000000..6ed70373 --- /dev/null +++ b/flutter/.github/dependabot.yml @@ -0,0 +1,22 @@ +# See Dependabot documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + all-github-actions: + patterns: [ "*" ] + reviewers: + - "christopherfujino" + - "jmagman" + labels: + - "team" + - "team-infra" + - "autosubmit" + ignore: + - dependency-name: "codecov/codecov-action" + update-types: ["version-update:semver-patch"] diff --git a/flutter/.github/labeler.yml b/flutter/.github/labeler.yml new file mode 100644 index 00000000..185b182b --- /dev/null +++ b/flutter/.github/labeler.yml @@ -0,0 +1,252 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# See https://github.com/actions/labeler/blob/main/README.md for docs. +# Use git ls-files '' to see the list of matching files. + +'a: accessibility': + - changed-files: + - any-glob-to-any-file: + - '**/accessibility/*' + - '**/*accessibility*' + - '**/semantics/*' + - '**/*semantics*' + +'a: animation': + - changed-files: + - any-glob-to-any-file: + - '**/animation/*' + - '**/*animation*' + +'a: desktop': + - changed-files: + - any-glob-to-any-file: + - '**/linux/**/*' + - '**/macos/**/*' + - '**/windows/**/*' + - engine/src/flutter/shell/platform/darwin/common/**/* + +'a: internationalization': + - changed-files: + - any-glob-to-any-file: + - packages/flutter_localizations/**/* + +'a: tests': + - changed-files: + - any-glob-to-any-file: + - packages/flutter_driver/**/* + - packages/flutter_goldens/**/* + - packages/flutter_test/**/* + - packages/integration_test/**/* + +'a: text input': + - changed-files: + - all-globs-to-any-file: + - '**/*text*' + - '!**/*context*' + - '!**/*texture*' + +'d: api docs': + - changed-files: + - any-glob-to-any-file: + - examples/api/**/* + +'d: docs/': + - changed-files: + - any-glob-to-any-file: + - docs/**/* + +'d: examples': + - changed-files: + - any-glob-to-any-file: + - examples/**/* + +'e: embedder': + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/shell/platform/embedder + +'e: impeller': + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/impeller/**/* + +engine: + - changed-files: + - any-glob-to-any-file: + - DEPS + - engine/**/* + - docs/engine/**/* + +'f: cupertino': + - changed-files: + - any-glob-to-any-file: + - '**/cupertino/*' + - '**/*cupertino*' + - docs/libraries/cupertino/**/* + +'f: focus': + - changed-files: + - any-glob-to-any-file: + - '**/focus/*' + - '**/*focus*' + +'f: gestures': + - changed-files: + - any-glob-to-any-file: + - '**/gestures/*' + - '**/*gestures*' + +'f: material design': + - changed-files: + - any-glob-to-any-file: + - '**/material/*' + - '**/*material*' + - docs/libraries/material/**/* + +'f: routes': + - changed-files: + - any-glob-to-any-file: + - '**/navigator/*' + - '**/*navigator*' + - '**/route/*' + - '**/*route*' + +'f: scrolling': + - changed-files: + - any-glob-to-any-file: + - '**/*scroll*' + - '**/scroll/*' + - '**/*sliver*' + - '**/sliver/*' + - '**/*viewport*' + - '**/viewport/*' + +framework: + - changed-files: + - any-glob-to-any-file: + - packages/flutter/**/* + - packages/flutter_driver/**/* + - packages/flutter_goldens/**/* + - packages/flutter_localizations/**/* + - packages/flutter_test/**/* + - packages/integration_test/**/* + - examples/api/**/* + - docs/about/**/* + - docs/contributing/**/* + - docs/libraries/**/* + +'f: integration_test': + - changed-files: + - any-glob-to-any-file: + - packages/integration_test/**/* + +package: + - changed-files: + - any-glob-to-any-file: + - docs/ecosystem/**/* + +platform-android: + - changed-files: + - any-glob-to-any-file: + - docs/platform/android/**/* + - engine/src/flutter/shell/platform/android/**/* + - packages/flutter_tools/*android*' + - packages/flutter_tools/gradle/**/* + +platform-ios: + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/shell/platform/darwin/common/**/* + - engine/src/flutter/shell/platform/darwin/ios/**/* + - packages/flutter_tools/lib/src/ios/**/* + +platform-fuchsia: + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/shell/platform/fuchsia/**/* + +platform-linux: + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/shell/platform/linux/**/* + +platform-macos: + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/src/flutter/shell/platform/darwin/common/**/* + - engine/src/flutter/shell/platform/darwin/macos/**/* + +platform-web: + - changed-files: + - any-glob-to-any-file: + - '**/web_sdk/**/*' + - engine/src/flutter/lib/web_ui/**/* + - packages/flutter_web_plugins + +platform-windows: + - changed-files: + - any-glob-to-any-file: + - engine/src/flutter/shell/platform/windows/**/* + +'customer: gallery': + - changed-files: + - any-glob-to-any-file: + - examples/flutter_gallery/**/* + +'c: tech-debt': + - changed-files: + - any-glob-to-any-file: + - '**/fix_data.yaml' + - '**/*.expect' + - '**/*test_fixes*' + +team: + - changed-files: + - any-glob-to-any-file: + - docs/about/**/* + - docs/contributing/**/* + - docs/postmortems/**/* + +team-android: + - changed-files: + - any-glob-to-any-file: + - docs/platform/android/**/* + +team-ecosystem: + - changed-files: + - any-glob-to-any-file: + - docs/ecosystem/**/* + +team-engine: + - changed-files: + - any-glob-to-any-file: + - docs/engine/**/* + +team-infra: + - changed-files: + - any-glob-to-any-file: + - docs/infra/**/* + +team-release: + - changed-files: + - any-glob-to-any-file: + - docs/releases/**/* + +team-tool: + - changed-files: + - any-glob-to-any-file: + - docs/tool/**/* + +team-web: + - changed-files: + - any-glob-to-any-file: + - docs/platforms/web/**/* + +tool: + - changed-files: + - any-glob-to-any-file: + - packages/flutter_tools/**/* + - packages/fuchsia_remote_debug_protocol/**/* + - docs/tool/**/* diff --git a/flutter/.github/release.yml b/flutter/.github/release.yml new file mode 100644 index 00000000..c8ed3a87 --- /dev/null +++ b/flutter/.github/release.yml @@ -0,0 +1,69 @@ +changelog: + exclude: + authors: + - engine-flutter-autoroll + - fluttergithubbot + labels: + - passed first triage + - passed secondary triage + - team-release + - "team: flakes" + - revert + categories: + - title: Framework + labels: + - framework + - "a: text input" + - "f: scrolling" + - "f: routes" + - "f: selection" + - "f: gestures" + exclude: + labels: + - "f: material design" + - title: Material + labels: + - "f: material design" + # Mobile + - title: iOS + labels: + - platform-ios + - "f: cupertino" + - title: Android + labels: + - platform-android + # Desktop + - title: macOS + labels: + - platform-mac + - title: Windows + labels: + - platform-windows + - title: Linux + labels: + - platform-linux + # Web + - title: Web + labels: + - platform-web + - "browser: chrome-desktop" + - "browser: edge" + - "browser: firefox" + - "browser: safari-macos" + # Misc + - title: Tooling + labels: + - tool + - title: DevTools + labels: + - "d: devtools" + - "d: intellij" + - "d: tools_metadata" + - "f: inspector" + - title: Documentation + labels: + - "d: examples" + - "d: api docs" + - title: Other Changes + labels: + - '*' diff --git a/flutter/.gitignore b/flutter/.gitignore new file mode 100644 index 00000000..d43cb65e --- /dev/null +++ b/flutter/.gitignore @@ -0,0 +1,152 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + +# This is dynamic in a monorepo +bin/internal/engine.version + +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/* +.ccls-cache + +# Flutter repo-specific +/bin/cache/ +/bin/internal/bootstrap.bat +/bin/internal/bootstrap.sh +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/devicelab/ABresults*.json +/dev/docs/doc/ +/dev/docs/api_docs.zip +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version +analysis_benchmark.json + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +**/generated_plugin_registrant.dart +.packages +.pub-preload-cache/ +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +.gradle/ +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/.last_build_id +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/ephemeral +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/Flutter/ephemeral/ +**/Pods/ +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/ephemeral +**/xcuserdata/ + +# Windows +**/windows/flutter/ephemeral/ +**/windows/flutter/generated_plugin_registrant.cc +**/windows/flutter/generated_plugin_registrant.h +**/windows/flutter/generated_plugins.cmake + +# Linux +**/linux/flutter/ephemeral/ +**/linux/flutter/generated_plugin_registrant.cc +**/linux/flutter/generated_plugin_registrant.h +**/linux/flutter/generated_plugins.cmake + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock +!.vscode/settings.json + +# Monorepo +.cipd +.gclient +.gclient_entries +.python-version +.gclient_previous_custom_vars +.gclient_previous_sync_commits diff --git a/flutter/AUTHORS b/flutter/AUTHORS new file mode 100644 index 00000000..69cb72a8 --- /dev/null +++ b/flutter/AUTHORS @@ -0,0 +1,134 @@ +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization +# +# Anyone who has contributed to the Flutter project in any way (not +# limited to submitting PRs) is welcome to submit a PR to add their +# name to this file. +# +# Thanks to everyone for your contributions! + +Google Inc. +The Chromium Authors +The Fuchsia Authors +Jim Simon +Lex Berezhny +Wyatt Arent +Michael Perrotte +Günter Zöchbauer +Raju Bitter +Michael Beckler +Alexandre Ardhuin +Luke Freeman +Vincent Le Quéméner +Mike Hoolehan +German Saprykin +Stefano Rodriguez +Yusuke Konishi +Fredrik Simón +Ali Bitek +Tetsuhiro Ueda +Dan Field +Noah Groß +Victor Choueiri +Christian Mürtz +Lukasz Piliszczuk +Felix Schmidt +Artur Rymarz +Chema Molins +Stefan Mitev +Jasper van Riet +Mattijs Fuijkschot +Volodymyr Lykhonis +TruongSinh Tran-Nguyen +Sander Dalby Larsen +Marco Scannadinari +Frederik Schweiger +Martin Staadecker +Igor Katsuba +Diego Velásquez +Simon Lightfoot +Sarbagya Dhaubanjar +Rody Davis Jr +Robin Jespersen +Jefferson Quesado +Mark Diener +Alek Åström +Efthymios Sarpmpanis +Cédric Wyss +Michel Feinstein +Michael Lee +Katarina Sheremet +Nicolas Schneider +Mikhail Zotyev +Maria Melnik +Ayush Bherwani +Luke Cheng +Brian Wang +法的空间 +CaiJingLong +Alex Li +Ram Navan +meritozh +Terrence Addison Tandijono(flotilla) +YeungKC +Nobuhiro Tabuki +nt4f04uNd +Anurag Roy +Andrey Kabylin +vimerzhao +Pedro Massango +Hidenori Matsubayashi +Perqin Xie +Seongyun Kim +Ludwik Trammer +J-P Nurmi +Marian Triebe +Alexis Rouillard +Mirko Mucaria +Karol Czeryna +Callum Moffat +Koutaro Mori +Sergei Smitskoi +Casey Rogers +Pradumna Saraf +Kai Yu +Denis Grafov +TheOneWithTheBraid +Alberto Miola +Twin Sun, LLC +Taskulu LDA +Jonathan Joelson +Elsabe Ros +Nguyễn Phúc Lợi +Jingyi Chen +Junhua Lin <1075209054@qq.com> +Tomasz Gucio +Jason C.H +Hubert Jóźwiak +David Neuy +Eli Albert +Jan Kuß +André Sousa +Bartek Pacia +Mike Rydstrom +Harish Anbalagan +Kim Jiun +LinXunFeng +Sabin Neupane +Mahdi Bagheri <1839491@gmail.com> +Mok Kah Wai +Lucas Saudon +Om Phatak +Amir Panahandeh +Kostiantyn Sokolovskyi +Valentin Vignal +Cedric Vanden Bosch +Flop +Dimil Kalathiya +Nate Wilson +dy0gu +Albert Wolszon +Mohammed Chahboun +Abdessalem Mohellebi diff --git a/flutter/CHANGELOG.md b/flutter/CHANGELOG.md new file mode 100644 index 00000000..3d625433 --- /dev/null +++ b/flutter/CHANGELOG.md @@ -0,0 +1,839 @@ +In general, our philosophy is to update the `stable` channel on a quarterly basis with feature updates. In the intervening period, occasionally we may decide a bug or regression warrants a hotfix. We tend to be extremely conservative with these hotfixes, since there's always a risk that fixing one bug introduces a new one, and we want the `stable` channel to always represent our most tested builds. + +We intend to announce hotfixes to the [flutter-announce](https://groups.google.com/forum/#!forum/flutter-announce) group, and we recommend that you subscribe to that list if you publish an application using Flutter. + +Note that we only hotfix the latest version -- if you see bugs on older versions of the `stable` channel, please consider moving to the latest `stable` channel version. + +To ensure that you have the latest stable version with the hotfixes listed below, use the flutter tool at the command line as follows: + +``` +$ flutter channel stable +$ flutter upgrade +``` + + + +## Flutter 3.29.1 Changes + +### [3.29.1](https://github.com/flutter/flutter/releases/tag/3.29.1) + +- [flutter/163830](https://github.com/flutter/flutter/pull/163830) - Fix Tab linear and elastic animation blink. +- [flutter/164119](https://github.com/flutter/flutter/pull/164119) - Configuration changes to run test on macOS 14 for Flutter's CI. +- [flutter/164155](https://github.com/flutter/flutter/pull/164155) - Roll .ci.yaml changes into the LUCI configuration only when the master branch is updated. +- [flutter/164191](https://github.com/flutter/flutter/pull/164191) - Improve safaridriver launch process in Flutter's CI testing. +- [flutter/164193](https://github.com/flutter/flutter/pull/164193) - Provide guided error message when app crashes due to JIT restriction on iPhones. +- [flutter/164050](https://github.com/flutter/flutter/pull/164050) - Fixes test reorderable_list_test.dart failing for certain ordering seeds, such as 20250221. +- [flutter/163316](https://github.com/flutter/flutter/pull/163316) - Configuration changes to run test on macOS 14 for Flutter's CI. +- [flutter/163581](https://github.com/flutter/flutter/pull/163581) - Fix crash when using BackdropFilters in certain GLES drivers. +- [flutter/163616](https://github.com/flutter/flutter/pull/163616) - Disable Vulkan on known bad Xclipse GPU drivers. +- [flutter/163666](https://github.com/flutter/flutter/pull/163666) - always post new task during gesture dispatch. +- [flutter/163667](https://github.com/flutter/flutter/pull/163667) - ensure that OpenGL "flipped" textures do not leak via texture readback. +- [flutter/163741](https://github.com/flutter/flutter/pull/163741) - Flutter tool respects tracked engine.version. +- [flutter/163754](https://github.com/flutter/flutter/pull/163754) - Fix text glitch when returning to foreground. +- [flutter/163058](https://github.com/flutter/flutter/pull/163058) - Fixes jittery +glyphs. +- [flutter/163201](https://github.com/flutter/flutter/pull/163201) - Fixes buttons with icons ignore foregroundColor. +- [flutter/163265](https://github.com/flutter/flutter/pull/163265) - disable Vulkan on known bad exynos SoCs. +- [flutter/163261](https://github.com/flutter/flutter/pull/163261) - Fixes for Impeller DrawVertices issues involving snapshots with empty sizes +- [flutter/163672](https://github.com/flutter/flutter/pull/163672) - Check for tracked engine.version before overriding + +## Flutter 3.29 Changes + +### [3.29.0](https://github.com/flutter/flutter/releases/tag/3.29.0) +Initial stable release. + +## Flutter 3.27 Changes + +### [3.27.2](https://github.com/flutter/flutter/releases/tag/3.27.2) + +- [flutter/159729](https://github.com/flutter/flutter/issues/159729) Flutter module template triggers a warning when built for Android. +- [flutter/161176](https://github.com/flutter/flutter/issues/161176) Dropdown Menu can create an infinite loop. +- [flutter/161330](https://github.com/flutter/flutter/issues/161330) Using ScrollViewKeyboardDismissBehavior.onDrag in a SingleChildScrollView causes text fields to immediately unfocus if the keyboard opening scrolls the text field to keep it visible. +- [flutter/160127](https://github.com/flutter/flutter/issues/160127) Some Flutter web plugins do not add the `crossOrigin` property to tags. +- [flutter/160155](https://github.com/flutter/flutter/issues/160155) Failed assertion in web engine: "The targeted input element must be the active input element". +- [flutter/160199](https://github.com/flutter/flutter/issues/160199) Some images on the web render blank. +- [flutter/160459](https://github.com/flutter/flutter/issues/160459) Incorrect Z order rendering in drawPoints may cause lines to overlap when one should be drawn in front of the other. +- [flutter/160409](https://github.com/flutter/flutter/issues/160409) App may crashes because of obsolete engine assertion. +- [flutter/158192](https://github.com/flutter/flutter/issues/158192) Positions of display cutouts on Android may not update - as returned by MediaQuery and used by SafeArea - upon screen orientation change. + +### [3.27.1](https://github.com/flutter/flutter/releases/tag/3.27.1) + +- [flutter/160041](https://github.com/flutter/flutter/issues/160041) - [Impeller][Android] Disables Impeller on older Android devices. +- [flutter/160206](https://github.com/flutter/flutter/issues/160206) - [Impeller][Android] Disables Android HardwareBuffer based swapchains on all devices. +- [flutter/160208](https://github.com/flutter/flutter/issues/160208) - [iOS] Fixes an issue on iOS preventing the ability to tap web view links in some plugins. + +### [3.27.0](https://github.com/flutter/flutter/releases/tag/3.27.0) +Initial stable release. + +## Flutter 3.24 Changes + +### [3.24.5](https://github.com/flutter/flutter/releases/tag/3.24.5) +- [flutter/158125](https://github.com/flutter/flutter/pull/158125) - [iOS] Fixed a tool issue causing failures when `flutter build ios-framework --xcframework` copies Flutter debug symbols. +- [flutter/56301](https://github.com/flutter/engine/pull/56301) - [Android] Fixes a crash on Android devices when the surface is released unexpectedly when using PlatformView's. + +### [3.24.4](https://github.com/flutter/flutter/releases/tag/3.24.4) +- [dart 3.5.4 changelog](https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md#354---2024-10-17) +- [flutter/154915](https://github.com/flutter/engine/pull/55366) - [macOS] Comply with the new Apple privacy manifest policy for the macOS Flutter engine framework and prevent the "Missing privacy manifest" warning when submitting a macOS app to the App Store. +- [flutter/153471](https://github.com/flutter/flutter/issues/153471) - [Tool] Fixes RPCError crash when setting up log filtering for Android devices. + +### [3.24.3](https://github.com/flutter/flutter/releases/tag/3.24.3) +- [dart 3.5.3 changelog](https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md#353---2024-09-11) +- [flutter/154275](https://github.com/flutter/flutter/issues/154275) - [Android] Fixes performance issues on Android caused by engine threads not matching the core count. +- [flutter/154276](https://github.com/flutter/flutter/issues/154276) - [Impeller] Fixes an issue on iOS preventing mesh gradients from rendering correctly. +- [flutter/154349](https://github.com/flutter/flutter/issues/154349) - [Wasm] Fixes an issue on web causing Platform Views to break when compiled to Wasm. +- [flutter/154564](https://github.com/flutter/flutter/issues/154564) - [Impeller][iOS] Fixes an issue when using Impeller on iOS when using backdrop filters on older iPads, causing the GPU to hang. +- [flutter/154712](https://github.com/flutter/flutter/issues/154712) - [iOS] Fixes an issue on iOS causing video playback to flicker. +- [flutter/154892](https://github.com/flutter/flutter/issues/154892) - [Impeller][iOS] Fixes an issue when using Impeller on iOS causing a memory leak when using Platform Views. +- [flutter/154536](https://github.com/flutter/flutter/issues/154536) - [Tool] Fixes a CLI crash that occurs when shutting down after running a Flutter app on a browser. +- [flutter/154720](https://github.com/flutter/flutter/pull/154720) - Fixes an issue with the `Drawer` widget, causing it to open or close incorrectly. +- [flutter/154944](https://github.com/flutter/flutter/pull/154944) - [Tool] Fixes a Flutter tool crash that occurs when building Flutter modules for Android when using AGP 8.0+. + +### [3.24.2](https://github.com/flutter/flutter/releases/tag/3.24.2) +- [Dart 3.5.2 Changelog](https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md#352---2024-08-28) +- [flutter/153949](https://github.com/flutter/flutter/issues/153949) - Fixes a crash on Android when deleting `EditableText` inside `CupertinoPageRoute`, with a CJK (chinese, japanese, korean) keyboard. +- [flutter/153939](https://github.com/flutter/flutter/issues/153939) - Fixes an issue on iOS where Flutter `TextField`s may stop accepting input. +- [flutter/152420](https://github.com/flutter/flutter/issues/152420) - Fixes scrolling jank on Android and iOS when a `SelectionArea`/`SelectableRegion` is used as a child of a Scrollable like `ListView` or `PageView`. +- [flutter/154199](https://github.com/flutter/flutter/pull/154199) - Removes excessive logging when building a freshly created template app for Android. +- [flutter/153967](https://github.com/flutter/flutter/pull/153967) - Fixes a host build failure on macOS when the `native assets` experiment is enabled, and there are no native asset frameworks to codesign. +- [flutter/153769](https://github.com/flutter/flutter/pull/153769) - When running a Flutter app, display a concise error message when connection to the device is lost. +- [flutter/154270](https://github.com/flutter/flutter/pull/154270) - Prevent preemptive gradle crash for android builds that would fail to build anyway but with a confusing error message. +- [flutter/54735](https://github.com/flutter/engine/pull/54735) - Fixes an error on Flutter Web where `onTap` is called twice on various widgets (`GestureDetector`, `InkWell`) when semantics are enabled. + +### [3.24.1](https://github.com/flutter/flutter/releases/tag/3.24.1) + +- [dart/56464](https://github.com/dart-lang/sdk/issues/56464) - Fixes resolving `include:` in `analysis_options.yaml` file in a nested folder in the workspace. +- [dart/56423](https://github.com/dart-lang/sdk/issues/56423) - Fixes source maps generated by `dart compile wasm` when optimizations are enabled. +- [dart/56374](https://github.com/dart-lang/sdk/issues/56374) - Fixes a bug in the `dart2wasm` compiler in unsound `-O3` / `-O4` modes where a implicit setter for a field of generic type will store null instead of the field value. +- [dart/56440](https://github.com/dart-lang/sdk/issues/56440) - Fixes a bug in the `dart2wasm` compiler that can trigger in certain situations when using partial instantiations of generic tear-offs (constructors or static methods) in constant expressions. +- [dart/56457](https://github.com/dart-lang/sdk/issues/56457) - The algorithm for computing the standard upper bound of two types, also known as UP, is provided the missing implementation for `StructuralParameterType` objects. In some corner cases the lacking implementation resulted in a crash of the compiler. +- [flutter/152047](https://github.com/flutter/flutter/issues/152047) - [Web] Fixes an issue in Flutter Web apps where when semantics are enabled, tapping on the label of a checkbox in a mobile browser won't togle the checkbox. +- [flutter/153308](https://github.com/flutter/flutter/issues/153308) - [Web] Adds source map support in `flutter run` / `flutter build` for `dart2wasm` for debugging in Chrome DevTools. +- [flutter/54446](https://github.com/flutter/engine/pull/54446) - [Web] Fixes an issue in Flutter Web apps where the app may crash if CanvasKit is loaded from the network instead of a cache. +- [flutter/152955](https://github.com/flutter/flutter/issues/152955) - [Impeller] Fixes an issue where when using unbound `saveLayers` rendering issues would occur. +- [flutter/153037](https://github.com/flutter/flutter/issues/153037) - [Impeller] Fixes an issue where RTL glyphs would render incorrectly. +- [flutter/153038](https://github.com/flutter/flutter/issues/153038) - [Impeller] Fixes an issue where padding would be applied incorrectly in `Canvas.drawVerticies` when using texture coordinates. +- [flutter/153041](https://github.com/flutter/flutter/issues/153041) - [Impeller] Fixes an rare issue causing applications to crash when using platform views on older iPhones. +- [flutter/153188](https://github.com/flutter/flutter/issues/153188) - [Impeller] Fixes a rendering issue on iOS devices using Impeller where clips do not appear around entities drawn with certain advanced blend modes. +- [flutter/54513](https://github.com/flutter/engine/pull/54513) - [iOS/MacOS] Fixes an issue preventing iOS Apps Store validation from failing for Flutter apps using Xcode versions before Xcode 16. +- [flutter/54518](https://github.com/flutter/engine/pull/54518) - Fixes an issue on OpenGL ES devices where a black screen would appear instead of the Flutter app output. +- [flutter/153117](https://github.com/flutter/flutter/pull/153117) [iOS/MacOS] Fixes an issue where compilation errors are not displayed in the output of `flutter run` when using Xcode 16. +- [flutter/153321](https://github.com/flutter/flutter/issues/153321) - [Desktop] Fixes an issue where older Windows devices could not run Flutter apps built using Flutter 3.21 or later. +- [flutter/153294](https://github.com/flutter/flutter/pull/153294) [Tool] Fixes an issue in the Flutter tool streamlining the crash message that occurs when running `flutter run -d chrome` and Chrome is closed before Flutter tries to close it. +- [flutter/153579](https://github.com/flutter/flutter/pull/153579) [Tool] Fixes an issue where users would experience large crash messages when `flutter run` or `flutter debug-adapter` are unable to connect to the Flutter web app. + +### [3.24.0](https://github.com/flutter/flutter/releases/tag/3.24.0) +Initial stable release. + +## Flutter 3.22 Changes + +### [3.22.3](https://github.com/flutter/flutter/releases/tag/3.22.3) (July 17, 2024) + +- [dart/55979](https://github.com/dart-lang/sdk/issues/55979) - Fixes an issue where `const bool.fromEnvironment('dart.library.ffi')` is true and conditional import condition `dart.library.ffi` is true in dart2wasm. +- [dart/55943](https://github.com/dart-lang/sdk/issues/55943) - Fixes an issue where FFI calls with variadic arguments on MacOS Arm64 would mangle the arguments. +- [flutter/149700](https://github.com/flutter/flutter/issues/149700) - [Impeller] Fixes rendering corruption when running on Intel mac simulators. +- [flutter/149701](https://github.com/flutter/flutter/issues/149701) - [Impeller] Fixes an issue on iOS that causese paths to render incorrectly. +- [flutter/149702](https://github.com/flutter/flutter/issues/149702) - [Impeller] Corrects and issue on iOs where coverage computation results in distored pixels in Impeller targets. +- [flutter/149704](https://github.com/flutter/flutter/issues/149704) - [Impeller] Fixes and issue on iOS where flickering may be occur when translating a blurred rounded rectangle. +- [flutter/149745](https://github.com/flutter/flutter/issues/149745) - [Impeller] Fixes a segfault on iOS when tessellating empty convex polygons. +- [flutter/149771](https://github.com/flutter/flutter/issues/149771) - [Impeller] Fixes a rendering error on iOS when advanced blend is double scaled. +- [flutter/53183](https://github.com/flutter/engine/pull/53183) - Fixes an issue where Linux apps show visual corruption on some frames. +- [flutter/149856](https://github.com/flutter/flutter/issues/149856) - Clarifies Flutter Fix log on how to update Kotlin Gradle Plugin that was introduced in Flutter 3.19. +- [flutter/150617](https://github.com/flutter/flutter/pull/150617) - Fixes a bug in `flutter test` where `--flavor` wasn't considered when validating cached assets, causing the flavor-conditional asset bundling feature to not work as expected. +- [flutter/150724](https://github.com/flutter/flutter/issues/150724) - Fixes an issue on Web+Linux that prevents users from inputting data using the numpad. +- [flutter/150787](https://github.com/flutter/flutter/pull/150787) - Fixes and issue on Windows when running certain commands, such as `flutter run` or `flutter build`, users get a lengthy crash message including the full contents of a FileSystemException. + +### [3.22.2](https://github.com/flutter/flutter/releases/tag/3.22.2) (June 06, 2024) +* [dart/55818](https://github.com/dart-lang/sdk/issues/55818) - Fixes an issue where `DART_VM_OPTIONS` were not correctly parsed for standalone Dart executables created with `dart compile exe`. +* [dart/55873](https://github.com/dart-lang/sdk/issues/55873) - Fixes a bug in dart2wasm that can result in a runtime error that says `array.new_fixed()` has a constant larger than 10000. +* [dart/55894](https://github.com/dart-lang/sdk/issues/55894) - Adds support for `--enable-experiment` flag to `dart compile` wasm. +* [dart/55895](https://github.com/dart-lang/sdk/issues/55895) - Fixes an issue in dart2wasm compiler that can result in incorrect nullability of type parameter. +* [dart/55890](https://github.com/dart-lang/sdk/issues/55890) - Disallows `dart:ffi` imports in user code in dart2wasm as dart2wasm's currently only supports a small subset of `dart:ffi`. +* [flutter/148885](https://github.com/flutter/flutter/issues/148885) - Fixes a platform view issue on android 14 when multiple activities are used and `onMemoryTrim` is called. +* [flutter/149178](https://github.com/flutter/flutter/issues/149178) - Fixes an issue on iOS where users are unable to focus on a `TextField` or open the keyboard again after side pop from another screen. +* [flutter/149210](https://github.com/flutter/flutter/issues/149210) - Fixes an `EditableText` crash that occurs when a custom `TextEditingController` only implements the `TextEditingController` interface. +* [flutter/149588](https://github.com/flutter/flutter/issues/149588) - Fixes a crash that occurs when rendering children in `TwoDimentionalViewport` using keep alive widgets (e.g InkWell). +* [flutter/148916](https://github.com/flutter/flutter/pull/148916) - Fixes an issue in the `ColorScheme.fromSeed` method to respect the seed color even if the seed color is very bright. +* [flutter/149345](https://github.com/flutter/flutter/pull/149345) - Adds a service extension that DevTools uses to support a "Track widget build counts" feature in DevTools 2.36.0. +* [flutter/149378](https://github.com/flutter/flutter/pull/149378) - Fixes a focus issue on iOS and MacOS that causes `TextFields` to not function after cupertino back swipes. +* [flutter/52987](https://github.com/flutter/engine/pull/52987) - Fixes an issue on Android where platform view inputs are mapped to the wrong location. + +### [3.22.1](https://github.com/flutter/flutter/releases/tag/3.22.1) (May 22, 2024) +* [dart/55714](https://github.com/dart-lang/sdk/issues/55714) - Fixes a bug in the CFE which could manifest as compilation errors of Flutter + web apps when compiled with dart2wasm. +* [dart/55758](https://github.com/dart-lang/sdk/issues/55758) - Fixes a bug in the pub client, such that `dart run` will not interfere with + Flutter l10n (at least for most cases). +* [flutter/147142](https://github.com/flutter/flutter/issues/147142) - Fixes a read/write permission issue when building Flutter apps for MacOS. + +### [3.22.0](https://github.com/flutter/flutter/releases/tag/3.22.0) (May 14, 2024) +Initial stable release. + +## Flutter 3.19 Changes + +### [3.19.6](https://github.com/flutter/flutter/releases/tag/3.19.6) (April 17, 2024) +* [dart/55430](https://github.com/dart-lang/sdk/issues/55430) - Fixes an issue with JS interop in dart2wasm where JS interop methods that used the enclosing library‘s @JS annotation were actually using the invocation’s enclosing library's @JS annotation. +* [flutter/145563](https://github.com/flutter/flutter/issues/145563) - Fixes severe performance regression on Firefox in v. 3.19. +* [flutter/144439](https://github.com/flutter/flutter/issues/144439) - Removes the --enable-impeller run flag and FLTEnableImpeller plist key on iOS. + +### [3.19.5](https://github.com/flutter/flutter/releases/tag/3.19.5) (March 28, 2024) +* [dart/55211](https://github.com/dart-lang/sdk/issues/55211) - Fixes an issue where dart vm crashed when running on pre-SSE41 older CPUs on Windows. + +### [3.19.4](https://github.com/flutter/flutter/releases/tag/3.19.4) (March 21, 2024) +* [flutter/144211](https://github.com/flutter/flutter/issues/144211) - Reverts a clipping optimization that is broken when multiple clips are applied with a backdrop filter. +* [flutter/144213](https://github.com/flutter/flutter/issues/144213) - Fix flickering of gaussian blurs in scrolling containers. +* [dart/55158](https://github.com/dart-lang/sdk/issues/55158) - Fixes an exception when executing hot reload after making compilation-successful changes. +* [dart/55194](https://github.com/dart-lang/sdk/issues/55194) - ​​Fix crashes on web platforms that contains an extension type declaration where the extension type constructor invokes a redirecting factory in its initializer. +* [dart/55184](https://github.com/dart-lang/sdk/issues/55184) - Fix issues where it is unable to run commit queue and post-submit testing on beta and stable when Goma is shut down. +* [dart/55240](https://github.com/dart-lang/sdk/issues/55240) - ​​Fix DateTime.timeZoneName on Windows. + +### [3.19.3](https://github.com/flutter/flutter/releases/tag/3.16.3) (March 07, 2024) +* [flutter/144565](https://github.com/flutter/flutter/issues/144565) - Fixes a tool crash when attempting to render a frame with raster stats on an application with the Impeller backend. +* [dart/55057](https://github.com/dart-lang/sdk/issues/55057) - Fixes an issue in dart2js where object literal constructors in interop extension types would fail to compile without an `@JS` annotation on the library. +* [dart/55095](https://github.com/dart-lang/sdk/issues/55095) - ​​Disallows certain types involving extension types from being used as the operand of an `await` expression, unless the extension type itself implements`Future`. + +### [3.19.2](https://github.com/flutter/flutter/releases/tag/3.19.2) (February 28, 2024) +* [flutter/143886](https://github.com/flutter/flutter/issues/143886) - Fixes a parsing issue that caused the Flutter tool to crash in some circumstances. + +### [3.19.1](https://github.com/flutter/flutter/releases/tag/3.19.1) (February 21, 2024) +* [flutter/143574](https://github.com/flutter/flutter/issues/143574) - Fixes an issue in Flutter web builds that disallowed the use of`--flavor` while launching. + +## Flutter 3.16 Changes + +### [3.16.9](https://github.com/flutter/flutter/releases/tag/3.16.9) (January 25, 2024) +* [dart/54699](https://github.com/dart-lang/sdk/issues/54699) - Fix an issue that causes Flutter apps to freeze when breakpoints are added to multiple isolates at the same time and an issue that causes Flutter apps to crash during hot reload. + +### [3.16.8](https://github.com/flutter/flutter/releases/tag/3.16.8) (January 17, 2024) +* [dart/54494](https://github.com/dart-lang/sdk/issues/54494) - Fix Dart2js stack overflow in value range analysis. + +### [3.16.7](https://github.com/flutter/flutter/releases/tag/3.16.7) (January 11, 2024) +* [dart/54427](https://github.com/dart-lang/sdk/issues/54427) - Upgrades Dart DevTools to version 2.28.5. +* [dart/54428](https://github.com/dart-lang/sdk/issues/54428) - Fixes an issue with serving static DevTools assets. + +### [3.16.6](https://github.com/flutter/flutter/releases/tag/3.16.6) (January 10, 2024) +* [flutter/141017](https://github.com/flutter/flutter/issues/141017) - Migrates event sent with every command for analytics. +* [flutter/136060](https://github.com/flutter/flutter/issues/136060) - Fixes Xcode 15 crashes EXC_BAD_ACCESS when using the Networking framework. +* [flutter/140416](https://github.com/flutter/flutter/issues/140416) - Fixes PathNotFoundException deleting temp dir in IOSCoreDeviceControl._listCoreDevices. +* [dartlang/webdev/2297](https://github.com/dart-lang/webdev/issues/2297) - Fixes DWDS error when debugging on web. + +### [3.16.5](https://github.com/flutter/flutter/releases/tag/3.16.5) (December 20, 2023) +* [flutter/138711](https://github.com/flutter/flutter/issues/138711) - Fixes AvailabilityVersionCheck failure on iOS +* [flutter/139571](https://github.com/flutter/flutter/issues/139571) - Fixes AnimatedOpacity affecting blended color overlay render +* [flutter/139294](https://github.com/flutter/flutter/issues/139294) - Fixes ImageFiltered flickers when widget is rendered on top +* [flutter/138193](https://github.com/flutter/flutter/issues/138193) - Fixes testMultiplePlatformViewsWithOverlays test on MacOS + +### [3.16.4](https://github.com/flutter/flutter/releases/tag/3.16.4) (December 13, 2023) +* [flutter/139180](https://github.com/flutter/flutter/issues/139180) - Fix tool crash on flutter create when unable to run Java. +* [flutter/138434](https://github.com/flutter/flutter/issues/138434) - Fix tool crash on deleting directories that do not exist +* [flutter/135277](https://github.com/flutter/flutter/issues/135277) - Eliminates an excessive amount of Xcode error/warning output to the console when building or running macOS Flutter apps. + +### [3.16.3](https://github.com/flutter/flutter/releases/tag/3.16.3) (December 5, 2023) +* [CVE-2023-6345](https://nvd.nist.gov/vuln/detail/CVE-2023-6345) - Skia fix for possible integer overflow on Canvas calls with user generated data +* [flutter/138550](https://github.com/flutter/flutter/issues/138550) - Fixes crash on iPad when selection "Share..." from selection controls. +* [flutter/138842](https://github.com/flutter/flutter/issues/138842) - Fix rendering bug with elevation 0 material components. +* [flutter/138850](https://github.com/flutter/flutter/issues/138850) - Add ability to customize NavigationBar indicator overlay and fixes a bug with the indicator shape. +* [dart/53086](https://github.com/dart-lang/sdk/issues/53086) - DDS fix to ensure threadID integers are serialized correctly by Debug Adapter Protocol (DAP) clients. +* [dart/53999](https://github.com/dart-lang/sdk/issues/53999) - Adjusts the nullablity computations in the implementation of the upper bound algorithm in the CFE +* [dart/54112](https://github.com/dart-lang/sdk/issues/54112) - Fixes missing closure code completion entries for function parameters for LSP-based editors like VS Code. + +### [3.16.2](https://github.com/flutter/flutter/releases/tag/3.16.2) (November 30, 2023) +* [flutter/138535](https://github.com/flutter/flutter/issues/138535) - Fixes android execution failed for task ':app:mergeDebugNativeLibs'. +* [flutter/138598](https://github.com/flutter/flutter/issues/138598) - Fixes SVG rendering issue on IOS. + +### [3.16.1](https://github.com/flutter/flutter/releases/tag/3.16.1) (November 27, 2023) +* [flutter/138030](https://github.com/flutter/flutter/issues/138030) - Fixes file deletion crash which can occur during iOS archive. +* [flutter/134716](https://github.com/flutter/flutter/issues/134716) - Fix iOS 17 keyboard freeze when switching languages +* [flutter/138180](https://github.com/flutter/flutter/issues/138180) - Prevents a crash in flutter doctor for macOS users who have an IntelliJ or Android Studio installation with a missing CFBundleIdentifier in its plist. +* [flutter/138040](https://github.com/flutter/flutter/issues/138040) - Ignore exceptions in Flutter tool when trying to set the echo mode of the terminal when the STDIN pipe has been broken. +* [flutter/124145](https://github.com/flutter/flutter/issues/124145) - Fixes a JSON array parsing bug that causes seg fault when --coverage is used + +### [3.16.0](https://github.com/flutter/flutter/releases/tag/3.16.0) (Nov 15, 2023) +Initial stable release. + +## Flutter 3.13 Changes + +### [3.13.9](https://github.com/flutter/flutter/releases/tag/3.13.9) (October 25, 2023) +* [dart/53784](https://github.com/dart-lang/sdk/issues/53784) - [dart2js] Fixes compatibility with Node.js 21 + +### [3.13.8](https://github.com/flutter/flutter/releases/tag/3.13.8) (October 18, 2023) +* [dart/53747](https://github.com/dart-lang/sdk/issues/53747) - Fixes a visual issue in the Dart VM preventing users from seeing variable values when debugging. +* [flutter/136552](https://github.com/flutter/flutter/issues/136552) - [iOS] Fixes issues with voice over when visiting a PlatformView in iOS applications. +* [flutter/136654](https://github.com/flutter/flutter/issues/136654) - [Android] Fixes rendering issues when using PlatformViews in Android applications on high refresh rate phones. + +### [3.13.7](https://github.com/flutter/flutter/releases/tag/3.13.7) (October 11, 2023) +* [flutter/135442](https://github.com/flutter/flutter/issues/135442) - Fix Xcode 15 launch failure with iOS 17 + +### [3.13.6](https://github.com/flutter/flutter/releases/tag/3.13.6) (September 27, 2023) +* [flutter/133013](https://github.com/flutter/flutter/issues/133013) - [Impeller] Fix issues with PNG decompression +* [flutter/132838](https://github.com/flutter/flutter/issues/132838) - Fix clip Imagefilter.blur on iOS +* [dart/53579](https://github.com/dart-lang/sdk/issues/53579) - Fixes a compiler crash when using @staticInterop or @anonymous factory constructors with type parameters. +* [dart/53503](https://github.com/dart-lang/sdk/issues/53503) - Fixes segmentation faults that terminate processes when encountering handled exceptions in the FFI library. +* [dart/53541](https://github.com/dart-lang/sdk/issues/53541) - Fixes slow variable access while debugging Flutter applications. + +### [3.13.5](https://github.com/flutter/flutter/releases/tag/3.13.5) (September 20, 2023) +* [flutter/134825](https://github.com/flutter/flutter/issues/134825) - Fixes an issue where apps built in profile mode would not install or run on physical iOS 17 devices. +* [flutter/45598](https://github.com/flutter/engine/pull/45598) - Fix permissions on macos artifacts making mac framework readable and executable by all + +### [3.13.4](https://github.com/flutter/flutter/releases/tag/3.13.4) (September 13, 2023) +* [dart/53449](https://github.com/dart-lang/sdk/issues/53449) - Fixes a dart2js issue causing a compiler crash when using a typed record pattern outside of the scope of a function body. +* [dart/53450](https://github.com/dart-lang/sdk/issues/53450) - Fixes a pause in the debugger when reaching an unhandled exception. +* [flutter/133658](https://github.com/flutter/flutter/issues/133658) - Fixes crash when using the --analyze-size argument. +* [flutter/133890](https://github.com/flutter/flutter/issues/133890) - Fixes incorrect autocorrect highlights in text fields in iOS 17. +* [flutter/134468](https://github.com/flutter/flutter/issues/134468) - Fixes an issue where users are not able to input text for IME language in iOS 17. +* [flutter/45742](https://github.com/flutter/engine/pull/45742) - Fixes CVE-2023-4863 - Security vulnerability in WebP. + +### [3.13.3](https://github.com/flutter/flutter/releases/tag/3.13.3) (September 7, 2023) + +* [flutter/133147](https://github.com/flutter/flutter/issues/133147) - fixes image-picker crashes on iOS +* [flutter/133069](https://github.com/flutter/flutter/issues/133069) - fixes issue where console prints dart:ui_web warnings in new flutter project +* [flutter/133441](https://github.com/flutter/flutter/issues/133441) - fixes issue where `flutter upgrade` crashes and reports "unknown flutter tag". +* [flutter/133055](https://github.com/flutter/flutter/issues/133055) - fixes issue where running `flutter doctor` crashes on FileSystemException +* [flutter/132788](https://github.com/flutter/flutter/issues/132788) - fixes a visual overflow caused by SliverMainAxisGroup where clip behavior isn’t applied + +### [3.13.2](https://github.com/flutter/flutter/releases/tag/3.13.2) (August 30, 2023) + +* [flutter/132764](https://github.com/flutter/flutter/pull/132764) - Fixes lower bound of children from TwoDimensionalChildBuilderDelegate. + +### [3.13.1](https://github.com/flutter/flutter/releases/tag/3.13.1) (August 23, 2023) + +* [flutter/132883](https://github.com/flutter/flutter/issues/132883) - Fixes an issue where Flutter apps would not compile when using custom icon fonts that contain spaces. +* [flutter/132959](https://github.com/flutter/flutter/issues/132959) - Fixes an issue where macOS applications using plugins with Xcode 15 would not compile. +* [flutter/132763](https://github.com/flutter/flutter/issues/132763) - Fixes auto-correction position in iOS 17. +* [flutter/132982](https://github.com/flutter/flutter/issues/132982) - [Impeller] Fixes an issue where applications would freeze if the app was minimized while an animation was occurring. + +## Flutter 3.10 Changes + +### [3.10.6](https://github.com/flutter/flutter/releases/tag/3.10.6) (July 12, 2023) + +* [flutter/129161](https://github.com/flutter/flutter/issues/129161) - Fix regression in the GestureRecognizers used by the TextField where it would not fire the onTapDown or onTapUp callbacks which made selection not work +* [flutter/130084](https://github.com/flutter/flutter/issues/130084) - Using canvas.drawPicture where the nested picture fails to restore clips established in the child picture and makes content disappear. +* [dart/52767](https://github.com/dart-lang/sdk/issues/52767) - Fixes a flow in flow analysis that causes it to sometimes ignore destructuring assignments. +* [dart/52869](https://github.com/dart-lang/sdk/issues/52869) - Fixes an infinite loop in some web development compiles that include `is` or `as` expressions involving record types with named fields. +* [dart/52791](https://github.com/dart-lang/sdk/issues/52791) - Fixes a memory leak in Dart analyzer's file-watching. +* [dart/52793](https://github.com/dart-lang/sdk/issues/52793) - Fixes a memory leak of file system watcher related data structures. + +### [3.10.5](https://github.com/flutter/flutter/releases/tag/3.10.5) (June 14, 2023) + +* [flutter/127628](https://github.com/flutter/flutter/pull/127628) - Fixes an issue preventing the use of `integration_test` when using AGP 8.0. +* [flutter/126043](https://github.com/flutter/flutter/issues/126403) - Fixes an error encountered when attempting to use `add-to-app` on Android when generating Flutter modules. +* [flutter/127090](https://github.com/flutter/flutter/issues/127090) - Fixes an issue preventing assets from being displayed properly on low pixel density devices. +* [flutter/128320](https://github.com/flutter/flutter/issues/128230) - Fixes an issue where image assets are not displayed when serving with Microsoft IIS. +* [dart/52403](https://github.com/dart-lang/sdk/issues/52403) - Fixes a bad cast in the frontend which can manifest as a crash in the dart2js +`ListFactorySpecializer` during Flutter web builds. +* [dart/1224](https://github.com/dart-lang/dart_style/issues/1224) - Handles formatting nullable record types with no fields. +* [dart/52480](https://github.com/dart-lang/sdk/issues/52480) - Fixes error when using records when targeting the web in development mode. + +### [3.10.4](https://github.com/flutter/flutter/releases/tag/3.10.4) (June 07, 2023) + +* [flutter/127836](https://github.com/flutter/flutter/issues/127836) - Fixes SliverAppBar's FlexibleSpaceBar overlaps + + +### [3.10.3](https://github.com/flutter/flutter/releases/tag/3.10.3) (June 02, 2023) + +* [flutter/126435](https://github.com/flutter/flutter/issues/126435) - Fixes the position of `SearchAnchor` when used in a nested navigator. +* [flutter/127486](https://github.com/flutter/flutter/issues/127486) - [Impeller] Fixes an issue causing noise when using combinations of UV mapping and color blending. +* [flutter/126878](https://github.com/flutter/flutter/issues/126878) - [Impeller] Fixes an issue where images do not appear on iOS devices. +* [flutter/1127587](https://github.com/flutter/flutter/issues/124612) - [Impeller] Fixes a crash when applying backdrop blurs to platform views. +* [flutter/127103](https://github.com/flutter/flutter/issues/127103) - [Impeller] Fixes an issue where text is not rendered correctly when a transform is applied. +* [flutter/126487](https://github.com/flutter/flutter/issues/126487) - [Impeller] Fixes an issue where blur is not respected at certain value. +* [dart/52449](https://github.com/dart-lang/sdk/issues/52449) - Fixes an AOT compiler crash when generating an implicit getter returning an unboxed record. +* [dart/52373](https://github.com/dart-lang/sdk/issues/52373) - Fixes a situation in which variables appearing in multiple branches of an or-pattern might be erroneously reported as being mismatched. +* [dart/52334](https://github.com/dart-lang/sdk/issues/52334) - Adds missing `interface` modifiers on the purely abstract classes `MultiStreamController`, `StreamConsumer`, `StreamIterator` and `StreamTransformer`. +* [dart/52373](https://github.com/dart-lang/sdk/issues/52373) - Fixes an error during debugging when `InternetAddress.tryParse` is used. +* [dart/126884](https://github.com/flutter/flutter/issues/126884) - Fixes a VM issue causing crashes on hot reload. +* [dart/4195](https://github.com/dart-lang/linter/issues/4195) - Improves linter support. +* [dart/52439](https://github.com/dart-lang/sdk/issues/52439) - Fixes an issue in variable patterns preventing users from expressing a pattern match using a variable or wildcard pattern with a nullable record type. +* [dart/52386](https://github.com/dart-lang/sdk/issues/52386) - Updates warnings and provide instructions for updating the Dart pub cache on Windows. + +### [3.10.2](https://github.com/flutter/flutter/releases/tag/3.10.2) (May 24, 2023) +This hotfix release addresses the following issues: +* [flutter/126532](https://github.com/flutter/flutter/issues/126532) - [Impeller] Fixes saveLayer ignores opacity of paint with blend mode lighten. +* [flutter/126739](https://github.com/flutter/flutter/issues/126739) - [Impeller] Fixes ImageShader alignment is different for different PaintingStyle. +* [flutter/126701](https://github.com/flutter/flutter/issues/126701) - [Impeller] Fixes InkSparkle splash not clipping on iOS. +* [flutter/126661](https://github.com/flutter/flutter/issues/126661) - Fixes PointerInterceptor reverses transformHitTests in a scaled context. +* [flutter/127183](https://github.com/flutter/flutter/issues/127183) - [Impeller] Fixes drawing path with image shader is not correct. +* [dart/52438](https://github.com/dart-lang/sdk/issues/52438) - Fixes a dart2js crash when using a switch case expression on a record where the fields don't match the cases. +* [dart/3392](https://github.com/dart-lang/dartdoc/issues/3392) - Add chips for class and mixin pages on dartdoc generated pages. +* [dart/52352](https://github.com/dart-lang/sdk/issues/52352) - Fixes a situation causing the parser to fail resulting in an infinite loop leading to higher memory usage. +* [dart/52078](https://github.com/dart-lang/sdk/issues/52078) - Add clear errors when mixing inheritance in pre and post Dart 3 libraries. + + +### [3.10.1](https://github.com/flutter/flutter/releases/tag/3.10.1) (May 17, 2023) + +This hotfix release addresses the following issues: +* [flutter/126510](https://github.com/flutter/flutter/issues/125276) - [Impeller] Fixes errors in text transformation when using impeller. +* [flutter/126854](https://github.com/flutter/flutter/issues/126854) - [Impeller] Fixes visual glitches and crashes when using wide gamut color support on iOS. +* [flutter/124883](https://github.com/flutter/flutter/issues/124883) - Fixes an issue where images do not render on Flutter web apps when the host machine has Internet Download Manager installed. +* [flutter/126491](https://github.com/flutter/flutter/issues/126491) - Fixes an issue where `CupertinoPicker` and `ListWheelViewport` crash with certain configurations on development builds. +* [flutter/124529](https://github.com/flutter/flutter/issues/124529) - Fixes an issue where iOS and macOS apps will not build when using Xcode 14.3 and adding dependencies with low iOS target versions. +* [flutter/122376](https://github.com/flutter/flutter/issues/122376) - Adds a migrator to update the Gradle version when it conflicts with the Android Studio version of Java is detected. +* [dart/124369](https://github.com/flutter/flutter/issues/124369) - Fixes a compiler crash involving redirecting factories and FFI. +* [dart/51899](https://github.com/dart-lang/sdk/issues/51899) - Fixes a dart2js crash when using a combination of local functions, generics, and records. +* [dart/52191](https://github.com/dart-lang/sdk/issues/52191) - Fixes incorrect error using a void in a switch case expression. +* [dart/52041](https://github.com/dart-lang/sdk/issues/52041) - Fixes a false error when using in switch case expressions when the switch refers to a private getter. +* [dart/52260](https://github.com/dart-lang/sdk/issues/52260) - Prevent the use of when and as as variable names in patterns. +* [dart/52241](https://github.com/dart-lang/sdk/issues/52241) - Fixes an inconsistency in type promotion between the analyzer and VM. +* [dart/1212](https://github.com/dart-lang/dart_style/issues/1212) - Improve performance on functions with many parameters. + +### [3.10.0](https://github.com/flutter/flutter/releases/tag/3.10.0) (May 10, 2023) +Initial stable release. + +## Flutter 3.7 Changes + +### [3.7.12](https://github.com/flutter/flutter/releases/tag/3.7.12) (Apr 19, 2023) + +This hotfix release addresses the following issues: + +* [flutter/124838](https://github.com/flutter/flutter/issues/124838) - Support Gradle 8 + +### [3.7.11](https://github.com/flutter/flutter/releases/tag/3.7.11) (Apr 12, 2023) + +This hotfix release addresses the following issues: + +* [flutter/124529](https://github.com/flutter/flutter/issues/124529) - Fix Xcode 14.3 will not build when plugin transitive dependencies have a low deployment target + * [flutter/124340](https://github.com/flutter/flutter/issues/124340) - Fixes an issue where iOS and MacOS fail to build when targeting low deployment targets when using xCode 14.3. +* [flutter/124208](https://github.com/flutter/flutter/issues/124208) - Fix orientation preferences on iOS 16+ + * [flutter/116711](https://github.com/flutter/flutter/issues/116711) - Fixes an issue where orientation preferences are not respected on iOS 16 and above. +* [flutter/124403](https://github.com/flutter/flutter/issues/124403) - Clarify errors around Java/Gradle incompatibility + * [flutter/122376](https://github.com/flutter/flutter/issues/122376) - Clarify errors around Java/Gradle incompatibility. + +### [3.7.10](https://github.com/flutter/flutter/releases/tag/3.7.10) (Apr 05, 2023) +This hotfix release addresses the following issues: +* [flutter/123890](https://github.com/flutter/flutter/issues/123890) - Fixes an issue where upgrading to Xcode 14.3 breaks the ability to publish iOS and macOS applications. + +### [3.7.9](https://github.com/flutter/flutter/releases/tag/3.7.9) (Mar 30, 2023) +This hotfix release addresses the following issues: +* [dart/51798](https://github.com/dart-lang/sdk/issues/51798) - Fixes a false `Out of Memory` exception causing slowdowns. + +### [3.7.8](https://github.com/flutter/flutter/releases/tag/3.7.8) (Mar 22, 2023) +This hotfix release addresses the following issues: +* [flutter/119441](https://github.com/flutter/flutter/issues/119441) - Fixes an issue where the `Toolbar` widget is incorrectly positioned when inside of a textfield in the Appbar. + +### [3.7.7](https://github.com/flutter/flutter/releases/tag/3.7.7) (Mar 08, 2023) +This hotfix release addresses the following issues: +* [flutter/121256](https://github.com/flutter/flutter/issues/121256) - Fixes an issue where Android users can not use add2app because it can not locate build/host/apk/app-debug.apk. +* [engine/120455](https://github.com/flutter/flutter/issues/120455) +Cached DisplayList opacity inheritance fix. +* [dart/121270](https://github.com/flutter/flutter/issues/121270) - Fixes mobile device VM crashes caused by particular use of RegExp on mobile devices. + +### [3.7.6](https://github.com/flutter/flutter/releases/tag/3.7.6) (Mar 01, 2023) +This hotfix release addresses the following issues: +* [dart/50981](https://github.com/dart-lang/sdk/issues/50981) - Improve performance of Dart Analysis Server by limiting the analysis context to 1. +* [dart/51481](https://github.com/dart-lang/sdk/issues/51481) - Update DDC test and builder configuration +* [flutter/114031](https://github.com/flutter/flutter/issues/114031) - Fixes a crash when using `flutter doctor --android-licenses` on macOS. +* [flutter/106674](https://github.com/flutter/flutter/issues/106674) - Fixes an issue where Flutter is unable to find the current JDK in specific versions of Android Studio. + +### [3.7.5](https://github.com/flutter/flutter/releases/tag/3.7.5) (Feb 22, 2023) +This hotfix release addresses the following issues: +* [flutter/119180](https://github.com/flutter/flutter/issues/119180) - Apple Pencil writes on Flutter apps instead of scrolling when outside of a text field. +* [flutter/120220](https://github.com/flutter/flutter/issues/120220) - [Impeller] Flutter apps may crash when some clip operations are used. + +### [3.7.4](https://github.com/flutter/flutter/releases/tag/3.7.4) (Feb 21, 2023) +This hotfix release addresses the following issues: +* [flutter/116360](https://github.com/flutter/flutter/issues/116360) - Flutter web apps will not load if accessed through any other path than `/`. +* [flutter/119557](https://github.com/flutter/flutter/issues/119557) - Localization files incorrectly overridden stopping Flutter applications from running. +* [flutter/116459](https://github.com/flutter/flutter/issues/116459) - Localization files do not parse when using numbers as select cases. + +### [3.7.3](https://github.com/flutter/flutter/releases/tag/3.7.3) (Feb 9, 2023) +This hotfix release addresses the following issues: +* [flutter/119507](https://github.com/flutter/flutter/issues/119507) - Asset inclusion regression can cause unexpected app bundle size increase +* [flutter/119289](https://github.com/flutter/flutter/issues/119289) - [Impeller] ImageFilter.blur Edge sampling issue. +* [flutter/119950](https://github.com/flutter/flutter/issues/119950) - [Impeller] Improve blur performance for Android and iPad Pro. +* [flutter/119190](https://github.com/flutter/flutter/pull/119190) - Fix lexer issue where select/plural/other/underscores cannot be in identifier names. + +### [3.7.2](https://github.com/flutter/flutter/releases/tag/3.7.2) (Feb 8, 2023) +This hotfix release addresses the following issues: +* [flutter/119881](https://github.com/flutter/flutter/issues/119881) - [Impeller] App performance decreases when using emulated dashed lines. +* [flutter/119245](https://github.com/flutter/flutter/issues/119245) - [Impeller] App crashes due to invalid textures when using impeller. +* [flutter/119489](https://github.com/flutter/flutter/issues/119489) - [Impeller] Text glyphs render incorrectly on different font weights +* [flutter/103847](https://github.com/flutter/flutter/issues/103847) - Fix animation jank on some iPhone models. +* [flutter/119593](https://github.com/flutter/flutter/issues/119593) - Localization files fail to generate when `FLUTTER_STORAGE_BASE_URL` is overridden. +* [flutter/119084](https://github.com/flutter/flutter/issues/119084) - When requesting to evaluate multiple expressions while debugging Flutter web apps, tooling fails before finishing operations. +* [flutter/119261](https://github.com/flutter/flutter/issues/119261) - Flutter tool crashes when attempting to update the artifact cache. +* [flutter/117420](https://github.com/flutter/flutter/issues/117420) - Ink ripple is rendered incorrectly inside of the `NavigationBar` widget when using Material 3. +* [dart/50622](https://github.com/dart-lang/sdk/issues/50622) - VM crashes when mixing the use of double and float calculations in debug/JIT configuration. +* [flutter/119220](https://github.com/flutter/flutter/issues/119220) - Compiler may crash when attempting to inline a method with lots of optional parameters with distinct default values. +* [dart/51087](https://github.com/dart-lang/sdk/issues/51087) - `part_of_different_library` error may be encountered when using `PackageBuildWorkspace`. + +### [3.7.1](https://github.com/flutter/flutter/releases/tag/3.7.1) (Feb 1, 2023) +This hotfix release addresses the following issues: +* [flutter/116782](https://github.com/flutter/flutter/issues/116782) - Material 3 Navigation Drawer does not support scrolling or safe areas +* [flutter/119414](https://github.com/flutter/flutter/issues/119414) - ImageFilter in ListView causes wrong offset on Android and iOS +* [flutter/119181](https://github.com/flutter/flutter/issues/119181) - CastError when running `flutter pub get` +* [flutter/118613](https://github.com/flutter/flutter/issues/118613) - [Impeller] Fonts are blurry when rendering on iOS +* [flutter/118945](https://github.com/flutter/flutter/issues/118945) - [Impeller] Objects with large stroke width not drawn correctly on iOS +* [flutter/117428](https://github.com/flutter/flutter/issues/117428) - [Impeller] Text is transformed incorrectly on iOS +* [flutter/119072](https://github.com/flutter/flutter/issues/119072) - [Impeller] Draw calls could be improperly culled +* [flutter/118847](https://github.com/flutter/flutter/issues/118847) - [Impeller] Float samplers can get re-ordered compared to SkSL +* [flutter/119014](https://github.com/flutter/flutter/issues/119014) - Replace iPhone 6s with iPhone 11 as flutter test devices + +### [3.7.0](https://github.com/flutter/flutter/releases/tag/3.7.0) (Jan 24, 2023) +Initial stable release. + +## Flutter 3.3 Changes + +### [3.3.10](https://github.com/flutter/flutter/releases/tag/3.3.10) (Dec 16, 2022) +This hotfix release addresses the following issues: +* [flutter/113314](https://github.com/flutter/flutter/issues/113314) - Glitches appear when scrolling on Android TV devices. +* [flutter/80401](https://github.com/flutter/flutter/issues/80401) - Some widgets are not visible when nested inside of `ClipRRect` in CanvasKit mode when using Flutter web on Safari. + +### [3.3.9](https://github.com/flutter/flutter/releases/tag/3.3.9) (Nov 23, 2022) +This hotfix release addresses the following issues: +* [dart/50199](https://github.com/dart-lang/sdk/issues/50119) - fix error when using private variable setters in mixins on dart web. +* [dart/50392](https://github.com/dart-lang/sdk/issues/50392) - Type parameter nullability performs incorrectly in factory constructors. + +### [3.3.8](https://github.com/flutter/flutter/releases/tag/3.3.8) (Nov 09, 2022) +This hotfix release addresses the following issues: +* [flutter/113973](https://github.com/flutter/flutter/issues/113973) - Fix null safety issue in TextFormField when Android devices pass no data +* [flutter/109632](https://github.com/flutter/flutter/issues/109632) - Fix type conversion in TextInput that didn’t allow num types + +### [3.3.7](https://github.com/flutter/flutter/releases/tag/3.3.6) (Nov 2, 2022) +This hotfix release addresses the following issues: +* [flutter/113550](https://github.com/flutter/flutter/issues/113550) - Fix unnecessary null safe exceptions in input decorators on Android +* [flutter/100522](https://github.com/flutter/flutter/issues/100522) - Speculative fix for iOS screen flickering + +### [3.3.6](https://github.com/flutter/flutter/releases/tag/3.3.6) (Oct 26, 2022) +This hotfix release addresses the following issues: +* [flutter/111255](https://github.com/flutter/flutter/issues/111255) - Using WebView leads to size error in platform_views since Flutter 3.3.0 + +### [3.3.5](https://github.com/flutter/flutter/releases/tag/3.3.5) (Oct 19, 2022) +This hotfix release addresses the following issues: +* [flutter/113035](https://github.com/flutter/flutter/pull/113035) - Apps crash when `FadeInImage` switches from cached to uncached images. +* [flutter/112228](https://github.com/flutter/flutter/pull/112228) - Move documentation build and deployment to post-submit. +* [flutter/36807](https://github.com/flutter/engine/pull/36807) - Apps crash when combining emojis and Korean text. +* [flutter/112887](https://github.com/flutter/flutter/pull/112887) - When debugging web apps, erroneous errors are displayed. + +### [3.3.4](https://github.com/flutter/flutter/releases/tag/3.3.4) (Oct 05, 2022) +This hotfix release addresses the following issues: +* [Flutter/36181](https://github.com/flutter/engine/pull/36181) - On Flutter desktop apps, pixel snapping performs incorrectly when using opacity layers at certain DPRs and screen sizes. +* [flutter/36491](https://github.com/flutter/engine/pull/36491) - On android devices with a refresh rate greater than 60hz, frames jump when scrolling. + +### [3.3.3](https://github.com/flutter/flutter/releases/tag/3.3.3) (Sept 28, 2022) +This hotfix release addresses the following issues: +* [flutter/111475](https://github.com/flutter/flutter/issues/111475) - Signing errors on iOS pod bundle resources on Xcode 14 "Signing for "x" requires a development team." +* [flutter/110671](https://github.com/flutter/flutter/issues/110671) - App crashes on latest versions when AnimatedContainer / Container height is set to 0 and throws uncaught exception +* [flutter/107590](https://github.com/flutter/flutter/issues/107590) - Flutter tools ShaderCompilerException with exit code -1073740791. +* [flutter/110640](https://github.com/flutter/flutter/issues/110640) - Fatal crash with java.lang.AssertionError when selecting text in TextField. +* [dart/50075](https://github.com/dart-lang/sdk/issues/50075) - Security vulnerability: There is a auth bypass vulnerability in Dart SDK, specifically dart:uri core library, used to parse and validate URLs. +* [dart/50052](https://github.com/dart-lang/sdk/issues/50052) - Avoid CFE crash when input contains invalid super parameters usage. + +### [3.3.2](https://github.com/flutter/flutter/releases/tag/3.3.2) (Sept 14, 2022) +This hotfix release addresses the following issues: +* [flutter/111411](https://github.com/flutter/flutter/issues/111411) - Package assets fail to load. +* [flutter/111296](https://github.com/flutter/flutter/issues/111296) - Custom embedders fail to build for 32 bit targets. +* [flutter/111274](https://github.com/flutter/flutter/issues/111274) - Android plugins crash when using platform view's Virtual Display fallback. +* [flutter/111231](https://github.com/flutter/flutter/issues/111231) - Text rendering is handled incorrectly. +* [dart/49923](https://github.com/dart-lang/sdk/issues/49923) - Incorrect type propagation when using `late` variables in catch blocks. + +### [3.3.1](https://github.com/flutter/flutter/releases/tag/3.3.1) (Sept 7, 2022) +This hotfix release addresses the following issues: +* [flutter/110820](https://github.com/flutter/flutter/issues/110820) - Windows apps crash when accessibility is enabled on apps that use widgets with custom semantic actions. + +### [3.3.0](https://github.com/flutter/flutter/releases/tag/3.3.0) (Aug 30, 2022) +Initial stable release. +## Flutter 3.0 Changes +### [3.0.5](https://github.com/flutter/flutter/releases/tag/3.0.5) (July 13, 2022) +This hotfix release addresses the following issues: +* [flutter/106601](https://github.com/flutter/flutter/issues/106601) - Flutter tool fails on visual studio on certain locales on Windows. +* [flutter/106510](https://github.com/flutter/flutter/issues/106510) - Flutter crashes on launch on ARM devices. +* [dart/49054](https://github.com/dart-lang/sdk/issues/49054) - Improves code completion for Flutter. +* [dart/49402](https://github.com/dart-lang/sdk/issues/49402) - Compiler crashes when using Finalizable parameters. +### [3.0.4](https://github.com/flutter/flutter/releases/tag/3.0.4) (July 1, 2022) +This hotfix release addresses the following issues: +* [flutter/105183](https://github.com/flutter/flutter/issues/105183) - Pointer compression on iOS causes OOM +* [flutter/103870](https://github.com/flutter/flutter/issues/103870) - Application crashes on system low memory events +* [flutter/105674](https://github.com/flutter/flutter/issues/105674) - Rendering artifacts from ImagedFiltered/ColorFiltered in animated views +### [3.0.3](https://github.com/flutter/flutter/releases/tag/3.0.3) (June 22, 2022) +This hotfix release addresses the following issues: +* [dart/49188](https://github.com/dart-lang/sdk/issues/49188) - Improve analysis of enums and switch. +* [dart/49075](https://github.com/dart-lang/sdk/issues/49075) - Fix compiler crash when initializing Finalizable objects. +### [3.0.2](https://github.com/flutter/flutter/releases/tag/3.0.2) (June 10, 2022) +This hotfix release addresses the following issues: +* [flutter/104785](https://github.com/flutter/flutter/issues/104785) - Flutter web apps show a black screen on Safari 13. +* [flutter/102451](https://github.com/flutter/flutter/issues/102451) - `flutter doctor` crashes for Windows users using Visual Studio 2022. +* [flutter/103846](https://github.com/flutter/flutter/issues/103846) - Unexpected line breaks occur when using new text renderer. +* [flutter/104569](https://github.com/flutter/flutter/pull/104569) - Ink Sparkle slows down applications using Material 3. +* [flutter/103404](https://github.com/flutter/flutter/issues/103404) - SliverReorderableList does not drag on Android devices. +* [flutter/103556](https://github.com/flutter/flutter/issues/103566) - Nested horizontal sliders in widgets with horizontal drag gestures do not work in Android applications. + * [flutter/100375](https://github.com/flutter/flutter/issues/100375) - Build process fails when building Windows applications. + * [dart/49027](https://github.com/dart-lang/sdk/issues/49027) - Code suggestion for initState/dispose/setState no longer work on intellij. +* [dart/3424](https://github.com/dart-lang/pub/issues/3424) - `dart pub login` fails when attempting to publish a package. +* [dart/49097](https://github.com/dart-lang/sdk/issues/49097) - `dart analyze` throws errors when using enhance Enums feature. +### [3.0.1](https://github.com/flutter/flutter/releases/tag/3.0.1) (May 19, 2022) +This hotfix release addresses the following issues: + * [flutter/102947](https://github.com/flutter/flutter/issues/102947) - Radial gradients behave incorrectly when painting text. +### [3.0.0](https://github.com/flutter/flutter/releases/tag/3.0.0) (May 11, 2022) +Initial stable release. +## Flutter 2.10 Changes +### [2.10.5](https://github.com/flutter/flutter/releases/tag/2.10.5) (April 18, 2022) +This hotfix release addresses the following issues: + * [flutter/101224](https://github.com/flutter/flutter/issues/101224) - Flutter web debugger fails when using chrome 100 or greater. +### [2.10.4](https://github.com/flutter/flutter/releases/tag/2.10.4) (March 28, 2022) +This hotfix release addresses the following issues: + * [flutter/93871](https://github.com/flutter/flutter/issues/93871) - Custom embedders fail to build when using default sysroot (GCC 11). + * [dart/48559](https://github.com/dart-lang/sdk/issues/48559) - Flutter web apps crash when using package:freezed. +### [2.10.3](https://github.com/flutter/flutter/releases/tag/2.10.3) (March 02, 2022) +This hotfix release addresses the following issues: + * [flutter/98973](https://github.com/flutter/flutter/issues/98973) - Deadlock in application startup in profile/release mode. + * [flutter/98739](https://github.com/flutter/flutter/issues/98739) - ios: Visual glitch when scrolling a list in a Scaffold that has a Material and Container as bottomNavigationBar. + * [flutter/97086](https://github.com/flutter/flutter/issues/97086) - Windows: Fail to launch app in debug mode. +### [2.10.2](https://github.com/flutter/flutter/releases/tag/2.10.2) (February 18, 2022) +This hotfix release addresses the following issues: + * [flutter/95211](https://github.com/flutter/flutter/issues/95211) - Transform animation with BackdropFilter is causing a crash. + * [flutter/98155](https://github.com/flutter/flutter/issues/98155) - App crashes after upgrading to 2.10.x using webview + video_player plugin. + * [flutter/98361](https://github.com/flutter/flutter/issues/98361) - Error in DL bounds calculations causes incorrect SVG rendering. + * [flutter/97767](https://github.com/flutter/flutter/issues/97767) - New material icons are not properly rendered. + * [flutter/95711](https://github.com/flutter/flutter/issues/95711) - Linux builds default to building GLFW. +### [2.10.1](https://github.com/flutter/flutter/releases/tag/2.10.1) (February 9, 2022) +This hotfix release addresses the following issues: + * [flutter/94043](https://github.com/flutter/flutter/issues/94043) - Autofill does not work in `TextField`. + * [flutter/96411](https://github.com/flutter/flutter/issues/96411) - Safari: Unable to enter text into `TextField`. + * [flutter/96661](https://github.com/flutter/flutter/issues/96661) - Platform views throw fatal exception: Methods marked with @UiThread must be executed on the main thread. + * [flutter/97103](https://github.com/flutter/flutter/issues/97103) - Images become corrupted when using CanvasKit. + * [flutter/97679](https://github.com/flutter/flutter/issues/97679) - Don't remove overlay views when the rasterizer is being torn down. + * [dart/48301](https://github.com/dart-lang/sdk/issues/48301) - Avoid speculative conversion in ffi Pointer.asTypedList. +### [2.10.0](https://github.com/flutter/flutter/releases/tag/2.10.0) (February 3, 2022) +Initial stable release. +## Flutter 2.8 Changes +### [2.8.1](https://github.com/flutter/flutter/releases/tag/2.8.1) (December 16, 2021) +This hotfix release addresses the following issues: + * [flutter/94914](https://github.com/flutter/flutter/issues/94914) - Apps using `google_sign_in` or `google_maps` don't build in iOS Simulator on ARM macOS + * [flutter/90783](https://github.com/flutter/flutter/issues/90783) - In rare circumstances, engine may crash during app termination on iOS and macOS + * [dart/47914](https://github.com/dart-lang/sdk/issues/47914) - AOT compilation fails with error "Invalid argument(s): Missing canonical name for Reference" + * [dart/47815](https://github.com/dart-lang/sdk/issues/47815) - Running `dart pub publish` with custom pub package server that has URL containing a path may fail. + +### [2.8.0](https://github.com/flutter/flutter/releases/tag/2.8.0) (December 8, 2021) +Initial stable release. + +## Flutter 2.5 Changes +### [2.5.3](https://github.com/flutter/flutter/releases/tag/2.5.3) (October 15, 2021) +This hotfix release addresses the following issues: + * [dart/47321](https://github.com/dart-lang/sdk/issues/47321) - Fix a potential out-of-memory condition with analysis server plugins + * [dart/47432](https://github.com/dart-lang/sdk/issues/47432) - Fix certificate loading on Windows when there are expired certificates + * [flutter/83792](https://github.com/flutter/flutter/issues/83792) - Fix HTTPS issue related to: "HttpClient throws Invalid argument(s): Invalid internet address" + +### [2.5.2]((https://github.com/flutter/flutter/releases/tag/2.5.2)) (September 30, 2021) +This hotfix release addresses the following issues: + * [dart/47285](https://github.com/dart-lang/sdk/issues/47285) - Fix a regression to the performance of code completions + * [dart/47316](https://github.com/dart-lang/sdk/issues/47316) - Dynamic tables in ELF files have invalid relocated addresses + * [flutter/89912](https://github.com/flutter/flutter/issues/89912) - Building iOS app generates unnecessary Flutter.build folder + +### [2.5.1]((https://github.com/flutter/flutter/releases/tag/2.5.1)) (September 17, 2021) +This hotfix release addresses the following issues: + * [flutter/88767](https://github.com/flutter/flutter/issues/88767) - java.lang.SecurityException: Permission denial crash at launch + * [flutter/88236](https://github.com/flutter/flutter/issues/88236) - null check exception during keyboard keypress + * [flutter/88221](https://github.com/flutter/flutter/issues/88221) - Material routes delayed on push and pop + * [flutter/84113](https://github.com/flutter/flutter/issues/84113) - HTTP exceptions talking to VM Service + * [flutter/83632](https://github.com/flutter/flutter/issues/83632) - Scroll view velocity too high + +### 2.5.0 (September 8, 2021) +Initial stable release. + +## Flutter 2.2 Changes +### [2.2.3](https://github.com/flutter/flutter/pull/85719) (July 2, 2021) +This hotfix release addresses the following issues: + * [flutter/84212](https://github.com/flutter/flutter/issues/84212) - Upgrading to 2.2.1 cause main.dart to crash + * [flutter/83213](https://github.com/flutter/flutter/issues/83213) - TextFormField not responding to inputs on Android when typing on Microsoft SwiftKey + * [flutter/82838](https://github.com/flutter/flutter/issues/82838) - Flutter Web failing to compile with "Undetermined Nullability" + * [flutter/82874](https://github.com/flutter/flutter/issues/82874) - PopupMenuButton is broken after upgrade to Flutter 2.2. + +### [2.2.2](https://github.com/flutter/flutter/pull/84364) (June 11, 2021) +This hotfix release addresses the following issues: + * [dart/46249](https://github.com/dart-lang/sdk/issues/46249) - Ensure start/stop file watching requests are run on the dart thread. + * [dart/46210](https://github.com/dart-lang/sdk/issues/46210) - Fix an analyze crash when analyzing against package:meta v1.4.0 + * [dart/46173](https://github.com/dart-lang/sdk/issues/46173) - Merge a3767f7db86a85fcd6201e9357ad47b884002b66 to stable channel (2.13) + * [dart/46300](https://github.com/dart-lang/sdk/issues/46300) - Fix OOM VM test (`transferable_throws_oom_test` crashing after upgrade from Ubuntu 16) + * [dart/46298](https://github.com/dart-lang/sdk/issues/46298) - Ensure start/stop file watching requests are run on the Dart thread + * [flutter/83799](https://github.com/flutter/flutter/issues/83799) - Tool may crash if pub is missing from the artifact cache + * [flutter/83102](https://github.com/flutter/flutter/issues/83102) - Generated l10n file is missing ‘intl’ import with Flutter 2.2.0 + * [flutter/83094](https://github.com/flutter/flutter/issues/83094) - Flutter AOT precompiler crash + * [flutter/82874](https://github.com/flutter/flutter/issues/82874) - PopupMenuButton is broken after upgrade to Flutter 2.2. + +### [2.2.1](https://github.com/flutter/flutter/pull/83372) (May 27, 2021) +This hotfix release addresses the following issues: + - [flutter/80978](https://github.com/flutter/flutter/issues/80978) - Error "Command PhaseScriptExecution failed with a nonzero exit code" when building on macOS + - [dart/45990](https://github.com/dart-lang/sdk/issues/45990) - CastMap performs an invalid cast on 'remove', breaking shared_preferences plugin + - [dart/45907](https://github.com/dart-lang/sdk/issues/45907) - DDC missing nullability information from recursive type hierarchies + - [flutter/52106](https://github.com/flutter/flutter/issues/52106) - [Web] Accessibility focus border doesn’t follow when navigating through interactive elements with tab key + - [flutter/82768](https://github.com/flutter/flutter/issues/82768) - [Web] svgClip memory leak in Canvaskit renderer + +### 2.2.0 (May 18, 2021) +Initial stable release. + +## Flutter 2.0 Changes +### [2.0.6](https://github.com/flutter/flutter/pull/81508) (April 29, 2021) +This hotfix release addresses the following issue: + - [flutter/81326](https://github.com/flutter/flutter/issues/81326) - macOS binaries not codesigned + +### [2.0.5](https://github.com/flutter/flutter/pull/80570) (April 16, 2021) +This hotfix release addresses the following issue: + - [dart/45306](https://github.com/dart-lang/sdk/issues/45306) - Segmentation fault on specific code + +### [2.0.4](https://github.com/flutter/flutter/pull/79486) (April 2, 2021) +This hotfix release addresses the following issues: + - [flutter/78589](https://github.com/flutter/flutter/issues/78589) - Cocoapod transitive dependencies with bitcode fail to link against debug Flutter framework + - [flutter/76122](https://github.com/flutter/flutter/issues/76122) - Adding a WidgetSpan widget causes web HTML renderer painting issue + - [flutter/75280](https://github.com/flutter/flutter/issues/75280) - Dragging the "draggable" widget causes widget to freeze in the overlay layer on Web + +### [2.0.3](https://github.com/flutter/flutter/pull/78489) (March 19, 2021) +This hotfix release addresses the following issues: + - [flutter/75261](https://github.com/flutter/flutter/issues/75261) - Unable to deep link into Android app + - [flutter/78167](https://github.com/flutter/flutter/issues/78167) - Flutter crash after going to version 2 + - [flutter/75677](https://github.com/flutter/flutter/issues/75677) - NoSuchMethodError: The method 'cancel' was called on null at AnsiSpinner.finish + - [flutter/77419](https://github.com/flutter/flutter/pull/77419) - Fix Autovalidate enum references in fix data + +### [2.0.2](https://github.com/flutter/flutter/pull/77850) (March 12, 2021) +This hotfix release addresses the following issues: + - [flutter/77251](https://github.com/flutter/flutter/issues/77251) - Flutter may show multiple snackbars when Scaffold is nested + - [flutter/75473](https://github.com/flutter/flutter/issues/75473) - CanvasKit throws error when using Path.from + - [flutter/76597](https://github.com/flutter/flutter/issues/76597) - When multiple Flutter engines are active, destroying one engine causes crash + - [flutter/75061](https://github.com/flutter/flutter/issues/75061) - '_initialButtons == kPrimaryButton': is not true + - [flutter/77419](https://github.com/flutter/flutter/pull/77419) - Fix Autovalidate enum references in fix data + - [dart/45214](https://github.com/dart-lang/sdk/issues/45214) - Bad state exception can occur when HTTPS connection attempt errors or is aborted + - [dart/45140](https://github.com/dart-lang/sdk/issues/45140) - Uint8List reports type exception while using + operator in null safety mode + +### [2.0.1](https://github.com/flutter/flutter/pull/77194) (March 5, 2021) +This hotfix release addresses the following issue: + - [flutter/77173](https://github.com/flutter/flutter/issues/77173) - Building for macOS target fails when Flutter is installed from website + +### 2.0.0 (March 3, 2021) +Initial stable release. + +## Flutter 1.22 Changes +### [1.22.6](https://github.com/flutter/flutter/pull/74355) (Jan 25, 2021) +This hotfix release addresses the following issue: + - [flutter/70895](https://github.com/flutter/flutter/issues/70895) - Build error when switching between dev/beta and stable branches. + +### [1.22.5](https://github.com/flutter/flutter/pull/72079) (Dec 10, 2020) +This hotfix release addresses the following issue: + - [flutter/70577](https://github.com/flutter/flutter/issues/70577) - Reliability regression in the camera plugin on iOS + +### [1.22.4](https://github.com/flutter/flutter/pull/70327) (Nov 13, 2020) +This hotfix release addresses the following issues: + - [flutter/43620](https://github.com/flutter/flutter/issues/43620) - Dart analyzer terminates during development + - [flutter/58200](https://github.com/flutter/flutter/issues/58200) - Apple AppStore submission fails with error: “The bundle Runner.app/Frameworks/App.framework does not sue Infpport the minimum OS Version specified in the Info.plist” + - [flutter/69722](https://github.com/flutter/flutter/issues/69722) - Setting a custom observatory port for debugging does not take effect + - [flutter/66144](https://github.com/flutter/flutter/issues/66144) - Setting autoFillHint to text form field may cause focus issues + - [flutter/69449](https://github.com/flutter/flutter/issues/69449) - Potential race condition in FlutterPlatformViewsController + - [flutter/65133](https://github.com/flutter/flutter/issues/65133) - Support targeting physical iOS devices on Apple Silicon + +### [1.22.3](https://github.com/flutter/flutter/pull/69234) (October 30, 2020) +This hotfix release addresses the following issues: + - [flutter/67828](https://github.com/flutter/flutter/issues/67828) - Multiple taps required to delete text in some input fields. + - [flutter/66108](https://github.com/flutter/flutter/issues/66108) - Reading Android clipboard may throw a security exception if it contains media + +### [1.22.2](https://github.com/flutter/flutter/pull/68135) (October 16, 2020) +This hotfix release addresses the following issues: + - [flutter/67869](https://github.com/flutter/flutter/issues/67869) - Stylus tap gesture is improperly registered. + - [flutter/67986](https://github.com/flutter/flutter/issues/67986) - Android Studio 4.1 not properly supported. + - [flutter/67213](https://github.com/flutter/flutter/issues/67213) - Webviews in hybrid composition can cause a crash. + - [flutter/67345](https://github.com/flutter/flutter/issues/67345) - VoiceOver accessibility issue with some pages. + - [flutter/66764](https://github.com/flutter/flutter/issues/66764) - Native webviews may not be properly disposed of in hybrid composition. + +### [1.22.1](https://github.com/flutter/flutter/pull/67552) (October 8, 2020) +This hotfix release addresses the following issues: + - [flutter/66940](https://github.com/flutter/flutter/issues/66940) - autovalidate property inadvertently removed. + - [flutter/66962](https://github.com/flutter/flutter/issues/66962) - The new --analyze-size flag crashes when used with --split-debug-info + - [flutter/66908](https://github.com/flutter/flutter/issues/66908) - Flutter Activity causing exceptions in some Android versions. + - [flutter/66647](https://github.com/flutter/flutter/issues/66647) - Layout modifications performed by background threads causes exceptions on IOS14. + +### 1.22.0 (October 1, 2020) +Initial stable release. + +## Flutter 1.20 Changes +### [1.20.4](https://github.com/flutter/flutter/pull/65787) (September 15, 2020) +This hotfix release addresses the following issues: + - [flutter/64045](https://github.com/flutter/flutter/issues/64045) - Cannot deploy to physical device running iOS 14 + +### [1.20.3](https://github.com/flutter/flutter/pull/64984) (September 2, 2020) +This hotfix release addresses the following issues: + - [flutter/63876](https://github.com/flutter/flutter/issues/63876) - Performance regression for Image animation. + - [flutter/64228](https://github.com/flutter/flutter/issues/64228) - WebView may freeze in release mode on iOS. + - [flutter/64414](https://github.com/flutter/flutter/issues/64414) - Task switching may freeze on some Android versions. + - [flutter/63560](https://github.com/flutter/flutter/issues/63560) - Building AARs may cause a stack overflow. + - [flutter/57210](https://github.com/flutter/flutter/issues/57210) - Certain assets may cause issues with iOS builds. + - [flutter/63590](https://github.com/flutter/flutter/issues/63590) - Passing null values from functions run via Isolates throws an exception. + - [flutter/63427](https://github.com/flutter/flutter/issues/63427) - Wrong hour/minute order in timePicker in RTL mode. + +### [1.20.2](https://github.com/flutter/flutter/pull/63591) (August 13, 2020) +This hotfix release addresses the following issues: + - [flutter/63038](https://github.com/flutter/flutter/issues/63038) - Crash due to serialization of generic DartType (UnknownType) + - [flutter/46167](https://github.com/flutter/flutter/issues/46167) - iOS platform view cancels gesture while a new clip layer is added during the gesture + - [flutter/62198](https://github.com/flutter/flutter/issues/62198) - SliverList throws Exception when first item is SizedBox.shrink() + - [flutter/59029](https://github.com/flutter/flutter/issues/59029) - build ios --release can crash with ArgumentError: Invalid argument(s) + - [flutter/62775](https://github.com/flutter/flutter/issues/62775) - TimePicker is not correct in RTL (right-to-left) languages + - [flutter/55535](https://github.com/flutter/flutter/issues/55535) - New DatePicker widget is not fully localized + - [flutter/63373](https://github.com/flutter/flutter/issues/63373) - Double date separators appearing in DatePicker, preventing date selection + - [flutter/63176](https://github.com/flutter/flutter/issues/63176) - App.framework path in Podfile incorrect + +### [1.20.1](https://github.com/flutter/flutter/pull/62990) (August 6, 2020) +This hotfix release addresses the following issues: + - [flutter/60215](https://github.com/flutter/flutter/issues/60215) - Creating an Android-only plug-in creates a no-op iOS folder. + +### 1.20.0 (August 5, 2020) +Initial stable release. + +## Flutter 1.17 Changes +### [1.17.5](https://github.com/flutter/flutter/pull/60611) (June 30, 2020) +This hotfix release addresses the following issues: + - [flutter-intellij/4642]https://github.com/flutter/flutter-intellij/issues/4642 - Intellij/Android Studio plugins fail to show connected Android devices. + +### [1.17.4](https://github.com/flutter/flutter/pull/59695) (June 18, 2020) +This hotfix release addresses the following issues: + - [flutter/56826](https://github.com/flutter/flutter/issues/56826) - xcdevice polling may use all free hard drive space + +### [1.17.3](https://github.com/flutter/flutter/pull/58646) (June 4, 2020) +This hotfix release addresses the following issues: + - [flutter/54420](https://github.com/flutter/flutter/issues/54420) - Exhausted heap space can cause machine to freeze + +### [1.17.2](https://github.com/flutter/flutter/pull/58050) (May 28, 2020) +This hotfix release addresses the following issues: + - [flutter/57326](https://github.com/flutter/flutter/issues/57326) - CupertinoSegmentedControl does not always respond to selections + - [flutter/56898](https://github.com/flutter/flutter/issues/56898) - DropdownButtonFormField is not re-rendered after value is changed programmatically + - [flutter/56853](https://github.com/flutter/flutter/issues/56853) - Incorrect git error may be presented when flutter upgrade fails + - [flutter/55552](https://github.com/flutter/flutter/issues/55552) - Hot reload may fail after a hot restart + - [flutter/56507](https://github.com/flutter/flutter/issues/56507) - iOS builds may fail with “The path does not exist” error message + +### [1.17.1](https://github.com/flutter/flutter/pull/57052) (May 13, 2020) +This hotfix release addresses the following issues: + - [flutter/26345](https://github.com/flutter/flutter/issues/26345) - Updating `AndroidView` layer tree causes crash on Xiaomi and Moto devices + - [flutter/56567](https://github.com/flutter/flutter/issues/56567) - Xcode legacy build system causes build failures on iOS + - [flutter/56473](https://github.com/flutter/flutter/issues/56473) - Build `--tree-shake-icons` build option crashes computer + - [flutter/56688](https://github.com/flutter/flutter/issues/56688) - Regression in `Navigator.pushAndRemoveUntil` + - [flutter/56479](https://github.com/flutter/flutter/issues/56479) - Crash while getting static type context for signature shaking + +### 1.17.0 (May 5, 2020) +Initial stable release. + +## Flutter 1.12 Changes +### Hotfix.9 (April 1, 2020) +This hotfix release addresses the following issues: + - [flutter/47819](https://github.com/flutter/flutter/issues/47819) - Crashes on ARMv8 Android devices + - [flutter/49185](https://github.com/flutter/flutter/issues/49185) - Issues using Flutter 1.12 with Linux 5.5 + - [flutter/51712](https://github.com/flutter/flutter/issues/51712) - fixes for licensing from Android sdkmanager tool not being found + +### [Hotfix.8](https://github.com/flutter/flutter/pull/50591) (February 11, 2020) +This hotfix release addresses the following issues: + - [flutter/50066](https://github.com/flutter/flutter/issues/50066) - binaries unsigned in last hotfix + - [flutter/49787](https://github.com/flutter/flutter/issues/49787) - in a previous hotfix, we inadvertently broke Xcode 10 support. Reverting this change would have caused other problems (and users would still have to upgrade their Xcode with the next stable release), we decided to increase our minimum supported Xcode version. Please see the linked issue for more context on this decision. + - [flutter/45732](https://github.com/flutter/flutter/issues/45732) - Android log reader fix + - [flutter/47609](https://github.com/flutter/flutter/issues/47609) - Android log reader fix + +### [Hotfix.7](https://github.com/flutter/flutter/pull/49437) (January 26, 2020) +This hotfix release addresses the following issues: +- [flutter/47164](https://github.com/flutter/flutter/issues/47164) - blackscreen / crash on certain Huawei devices +- [flutter/47804](https://github.com/flutter/flutter/issues/47804) - Flutter engine crashes on some Android devices due to "Failed to setup Skia Gr context" +- [flutter/46172](https://github.com/flutter/flutter/issues/46172) - reportFullyDrawn causes crash on Android KitKat + +### Hotfix.5 (December 11, 2019) +Initial stable release. diff --git a/flutter/CODEOWNERS b/flutter/CODEOWNERS new file mode 100644 index 00000000..9ce241c4 --- /dev/null +++ b/flutter/CODEOWNERS @@ -0,0 +1,14 @@ +# Below is a list of Flutter team members' GitHub handles who are +# suggested reviewers for contributions to this repository. +# +# These names are just suggestions. It is fine to have your changes +# reviewed by someone else. +# +# Use git ls-files '' without a / prefix to see the list of matching files. + +/packages/flutter_tools/templates/module/ios/ @jmagman +/packages/flutter_tools/templates/**/Podfile* @jmagman + +# Working on flutter_driver improvements. +# See https://github.com/flutter/flutter/issues/148028. +/packages/flutter_driver/** @matanlurey diff --git a/flutter/CODE_OF_CONDUCT.md b/flutter/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..090da8c8 --- /dev/null +++ b/flutter/CODE_OF_CONDUCT.md @@ -0,0 +1,52 @@ + + +# Code of conduct + +The Flutter project expects Flutter's contributors to act professionally +and respectfully. Flutter contributors are expected to maintain the safety +and dignity of Flutter's social environments (such as GitHub and Discord). + +Specifically: + +* Respect people, their identities, their culture, and their work. +* Be kind. Be courteous. Be welcoming. +* Listen. Consider and acknowledge people's points before responding. + +Should you experience anything that makes you feel unwelcome in Flutter's +community, please contact [conduct@flutter.dev](mailto:conduct@flutter.dev) +or, if you prefer, directly contact someone on the project, for instance +[Hixie](mailto:ian@hixie.ch). + +The Flutter project will not tolerate harassment in Flutter's +community, even outside of Flutter's public communication channels. + +## Conflict resolution + +When multiple contributors disagree on the direction for a particular +patch or the general direction of the project, the conflict should be +resolved by communication. The people who disagree should get +together, try to understand each other's points of view, and work to +find a design that addresses everyone's concerns. + +This is usually sufficient to resolve issues. If you cannot come to an +agreement, ask for the advice of a more senior member of the project. + +Be wary of agreement by attrition, where one person argues a point +repeatedly until other participants give up in the interests of moving +on. This is not conflict resolution, as it does not address everyone's +concerns. Be wary of agreement by compromise, where two good competing +solutions are merged into one mediocre solution. A conflict is +addressed when the participants agree that the final solution is +_better_ than all the conflicting proposals. Sometimes the solution is +more work than either of the proposals. [Embrace the yak shave](./docs/contributing/Style-guide-for-Flutter-repo.md#lazy-programming). + +## Questions + +It's always ok to ask questions. Our systems are large, and nobody will be +an expert in all the systems. Once you find the answer, document it in +the first place you looked. That way, the next person will be brought +up to speed even quicker. + +!["I try not to make fun of people for admitting they don't know things, because for each thing 'everyone knows' by the time they're adults, every day there are, on average, 10,000 people in the US hearing about it for the first time. If I make fun of people, I train them not to tell me when they have those moments. And I miss out on the fun." "Diet coke and mentos thing? What's that?" "Oh, man! We're going to the grocery store." "Why?" "You're one of today's lucky 10,000."](https://imgs.xkcd.com/comics/ten_thousand.png) + +Source: _[xkcd, May 2012](https://xkcd.com/1053/)_ diff --git a/flutter/CONTRIBUTING.md b/flutter/CONTRIBUTING.md new file mode 100644 index 00000000..6de7575d --- /dev/null +++ b/flutter/CONTRIBUTING.md @@ -0,0 +1,211 @@ + + +Contributing to Flutter +======================= + +_tl;dr: join [Discord](./docs/contributing/Chat.md), be [courteous](CODE_OF_CONDUCT.md), follow the steps below to set up a development environment; if you stick around and contribute, you can [join the team](./docs/contributing/Contributor-access.md) and get commit access._ + +Welcome +------- + +We invite you to join the Flutter team, which is made up of volunteers and sponsored folk alike! +There are many ways to contribute, including writing code, filing issues on GitHub, helping people +on our mailing lists, our chat channels, or on Stack Overflow, helping to triage, reproduce, or +fix bugs that people have filed, adding to our documentation, +doing outreach about Flutter, or helping out in any other way. + +We grant commit access (which includes full rights to the issue +database, such as being able to edit labels) to people who have gained +our trust and demonstrated a commitment to Flutter. For more details +see the [Contributor access](./docs/contributing/Contributor-access.md) +page in our docs. + +We communicate primarily over GitHub and [Discord](./docs/contributing/Chat.md). + +Before you get started, we encourage you to read these documents which describe some of our community norms: + +1. [Our code of conduct](CODE_OF_CONDUCT.md), which stipulates explicitly + that everyone must be gracious, respectful, and professional. This + also documents our conflict resolution policy and encourages people + to ask questions. + +2. [Values](./docs/about/Values.md), + which talks about what we care most about. + +Helping out in the issue database +--------------------------------- + +Triage is the process of going through bug reports and determining if they are valid, finding out +how to reproduce them, catching duplicate reports, and generally making our issues list +useful for our engineers. + +If you want to help us triage, you are very welcome to do so! + +1. Join the #hackers-triage [Discord channel](./docs/contributing/Chat.md). + +2. Read [our code of conduct](CODE_OF_CONDUCT.md), which stipulates explicitly + that everyone must be gracious, respectful, and professional. If you're helping out + with triage, you are representing the Flutter team, and so you want to make sure to + make a good impression! + +3. Help out as described in our [triage guide](./docs/triage/README.md) + You won't be able to add labels at first, so instead start by trying to + do the other steps, e.g. trying to reproduce the problem and asking for people to + provide enough details that you can reproduce the problem, pointing out duplicates, + and so on. Chat on the #hackers-triage channel to let us know what you're up to! + +4. Familiarize yourself with our + [issue hygiene](./docs/contributing/issue_hygiene/README.md) wiki page, + which covers the meanings of some important GitHub labels and + milestones. + +5. Once you've been doing this for a while, someone will invite you to the flutter-hackers + team on GitHub and you'll be able to add labels too. See the + [contributor access](./docs/contributing/Contributor-access.md) wiki + page for details. + + +Quality Assurance +----------------- + +One of the most useful tasks, closely related to triage, is finding and filing bug reports. Testing +beta releases, looking for regressions, creating test cases, adding to our test suites, and +other work along these lines can really drive the quality of the product up. Creating tests +that increase our test coverage, writing tests for issues others have filed, all these tasks +are really valuable contributions to open source projects. + +If this interests you, you can jump in and submit bug reports without needing anyone's permission! +The #quality-assurance channel on our [Discord server](./docs/contributing/Chat.md) +is a good place to talk about what you're doing. We're especially eager for QA testing when +we announce a beta release. See [quality assurance](./docs/releases/Quality-Assurance.md) for +more details. + +If you want to contribute test cases, you can also submit PRs. See the next section +for how to set up your development environment, or ask in #hackers-test on Discord. + +> As a personal side note, this is exactly the kind of work that first got me into open +> source. I was a Quality Assurance volunteer on the Mozilla project, writing test cases for +> browsers, long before I wrote a line of code for any open source project. —Hixie + + +Developing for Flutter +---------------------- + +If you prefer to write code, consider starting with the list of good +first issues for [Flutter][flutter-gfi] or for [Flutter DevTools][devtools-gfi]. +Reference the respective sections below for further instructions. + +[flutter-gfi]: https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22 +[devtools-gfi]: https://github.com/flutter/devtools/labels/good%20first%20issue + +### Framework and Engine + +To develop for Flutter, you will eventually need to become familiar +with our processes and conventions. This section lists the documents +that describe these methodologies. The following list is ordered: you +are strongly recommended to go through these documents in the order +presented. + +1. [Setting up your engine development environment](./engine/src/flutter/docs/contributing/Setting-up-the-Engine-development-environment.md), + which describes the steps you need to configure your computer to + work on Flutter's engine. If you only want to write code for the + Flutter framework, you can skip this step. Flutter's engine mainly + uses C++, Java, and Objective-C. + +2. [Setting up your framework development environment](./docs/contributing/Setting-up-the-Framework-development-environment.md), + which describes the steps you need to configure your computer to + work on Flutter's framework. Flutter's framework mainly uses Dart. + +3. [Tree hygiene](./docs/contributing/Tree-hygiene.md), + which covers how to land a PR, how to do code review, how to + handle breaking changes, how to handle regressions, and how to + handle post-commit test failures. + +4. [Our style guide](./docs/contributing/Style-guide-for-Flutter-repo.md), + which includes advice for designing APIs for Flutter, and how to + format code in the framework. + +5. [Flutter design doc template](https://flutter.dev/go/template), + which should be used when proposing a new technical design. This is a good + practice to do before coding more intricate changes. + See also our [guidance for writing design docs](./docs/contributing/Design-Documents.md). + +[![How to contribute to Flutter](https://img.youtube.com/vi/4yBgOBAOx_A/0.jpg)](https://www.youtube.com/watch?v=4yBgOBAOx_A) + +In addition to the documents, there is a video linked above on **How to contribute to Flutter** +from the [Flutter](https://youtube.com/c/flutterdev) YouTube channel, +there are many pages in [our docs](./docs/README.md), +and an article [Contributing to Flutter: Getting Started](https://medium.com/@ayushbherwani/contributing-to-flutter-getting-started-a0db68cbcd5b) +on Medium that may be of interest. For a curated list of pages see the sidebar +on the wiki's home page. They are more or less listed in order of importance. + +### DevTools + +Contributing code to Dart & Flutter DevTools may be a good place to start if you are +looking to dip your toes into contributing with a relatively low-cost setup or if you +are generally excited about improving the Dart & Flutter developer experience. + +Please see the DevTools [CONTRIBUTING.md](https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md) +guide to get started. + +### Helping with existing PRs + +Once you've learned the process of contributing, if you aren't sure what to work on next you +might be interested in helping other developers complete their contributions by picking up an +incomplete patch from the list of [issues with partial patches][has-partial-patch]. + +[has-partial-patch]: https://github.com/flutter/flutter/labels/has%20partial%20patch + +Outreach +-------- + +If your interests lie in the direction of developer relations and developer outreach, +whether advocating for Flutter, answering questions in fora like +[Stack Overflow](https://stackoverflow.com/questions/tagged/flutter?sort=Newest&filters=NoAnswers,NoAcceptedAnswer&edited=true) +or [Reddit](https://www.reddit.com/r/flutterhelp/new/?f=flair_name%3A%22OPEN%22), +or creating content for our [documentation](https://docs.flutter.dev/) +or sites like [YouTube](https://www.youtube.com/results?search_query=flutter&sp=EgQIAxAB), +the best starting point is to join the #hackers-devrel [Discord channel](./docs/contributing/Chat.md). +From there, you can describe what you're interested in doing, and go ahead and do it! +As others become familiar with your work, they may have feedback, be interested in +collaborating, or want to coordinate their efforts with yours. + + +API documentation +----------------- + +Another great area to contribute in is sample code and API documentation. If this is an area that interests you, join our +[Discord](./docs/contributing/Chat.md) server and introduce yourself on the #hackers-devrel, #hackers-framework, +or #hackers-engine channels, describing your area of interest. As our API docs are integrated into our source code, see the +"developing for Flutter" section above for a guide on how to set up your developer environment. + +To contribute API documentation, an excellent command of the English language is particularly helpful, as is a careful attention to detail. +We have a [whole section in our style guide](./docs/contributing/Style-guide-for-Flutter-repo.md#documentation-dartdocs-javadocs-etc) +that you should read before you write API documentation. It includes notes on the "Flutter Voice", such as our word and grammar conventions. + +In general, a really productive way to improve documentation is to use Flutter and stop any time you have a question: find the answer, then +document the answer where you first looked for it. + +We also keep [a list of areas that need better API documentation](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22d%3A+api+docs%22+sort%3Areactions-%2B1-desc). +In many cases, we have written down what needs to be said in the relevant issue, we just haven't gotten around to doing it! + +We're especially eager to add sample code and diagrams to our API documentation. Diagrams are generated from Flutter code that +draws to a canvas, and stored in a [special repository](https://github.com/flutter/assets-for-api-docs/#readme). It can be a lot of fun +to create new diagrams for the API docs. + + +Releases +-------- + +If you are interested in participating in our release process, which may involve writing release notes and blog posts, coordinating the actual +generation of binaries, updating our release tooling, and other work of that nature, then reach out on the #hackers-releases +channel of our [Discord](./docs/contributing/Chat.md) server. + + +Social events in the contributor community +------------------------------------------ + +Finally, one area where you could have a lot of impact is in contributing to social interactions among the Flutter contributor community itself. +This could take the form of organizing weekly video chats on our Discord, or planning tech talks from contributors, for example. +If this is an area that is of interest to you, please join our [Discord](./docs/contributing/Chat.md) and ping Hixie on the #hackers +channel! diff --git a/flutter/DEPS b/flutter/DEPS new file mode 100644 index 00000000..f26c4e14 --- /dev/null +++ b/flutter/DEPS @@ -0,0 +1,1022 @@ +# The dependencies referenced by the Flutter Engine. +# +# This file is referenced by the .gclient file at the root of the checkout. +# To preview changes to the dependencies, update this file and run +# `gclient sync`. +# +# When adding a new dependency, please update the top-level .gitignore file +# to list the dependency's destination directory. +# +# This is a release branch. + +vars = { + 'chromium_git': 'https://chromium.googlesource.com', + 'swiftshader_git': 'https://swiftshader.googlesource.com', + 'dart_git': 'https://dart.googlesource.com', + 'flutter_git': 'https://flutter.googlesource.com', + 'skia_git': 'https://skia.googlesource.com', + 'llvm_git': 'https://llvm.googlesource.com', + 'skia_revision': 'e7b8d078851fd505475fe74359e31a421e6968ea', + + # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY + # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. + 'canvaskit_cipd_instance': '61aeJQ9laGfEFF_Vlc_u0MCkqB6xb2hAYHRBxKH-Uw4C', + + # Do not download the Emscripten SDK by default. + # This prevents us from downloading the Emscripten toolchain for builds + # which do not build for the web. This toolchain is needed to build CanvasKit + # for the web engine. + 'download_emsdk': False, + + # For experimental features some dependencies may only be avaialable in the master/main + # channels. This variable is being set when CI is checking out the repository. + 'release_candidate': False, + + # As Dart does, we use Fuchsia's GN and Clang toolchain. These revision + # should be kept up to date with the revisions pulled by Dart. + # + # The list of revisions for these tools comes from Fuchsia, here: + # https://fuchsia.googlesource.com/integration/+/HEAD/toolchain + # If there are problems with the toolchain, contact fuchsia-toolchain@. + # + # Note, if you are *manually* rolling clang (i.e. the auto-roll is disabled) + # you'll need to run post-submits (i.e. for Clang Tidy) in order to test that + # updates to Clang Tidy will not turn the tree red. + # + # See https://github.com/flutter/flutter/wiki/Engine-pre‐submits-and-post‐submits#post-submit + 'clang_version': 'git_revision:725656bdd885483c39f482a01ea25d67acf39c46', + + 'reclient_version': 'git_revision:29a9d3cb597b6a7d67fa3e9aa8a7cab1c81232ee', + + 'gcloud_version': 'version:2@444.0.0.chromium.3', + + 'esbuild_version': '0.19.5', + + # When updating the Dart revision, ensure that all entries that are + # dependencies of Dart are also updated to match the entries in the + # Dart SDK's DEPS file for that revision of Dart. The DEPS file for + # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS + # You can use //tools/dart/create_updated_flutter_deps.py to produce + # updated revision list of existing dependencies. + 'dart_revision': 'a8bfb132c5f7b9555d13ea79eaf0eaa77825824d', + + # WARNING: DO NOT EDIT MANUALLY + # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py + 'dart_binaryen_rev': '87f9dac127b387715d8d96ac7ec8fd469d8c2dab', + 'dart_boringssl_rev': '822902749a5956bba09c7e9e451104e8a74f02c5', + 'dart_core_rev': '7a71ad6b9170e09d5cbe39f3fccdee648659f1e7', + 'dart_devtools_rev': '8762b31f0d0ffeea6449fd02740e9ce7acb32503', + 'dart_http_rev': '6ecd13a88b82c2fdc8a555f218bafb0ad0370f51', + 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', + 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', + 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', + 'dart_protobuf_rev': 'b7dd58cdbd879beee4c3fbf8ee80fce8e97bad26', + 'dart_pub_rev': '58de642dc1d07601f6eb2b4ecd94555c0210106b', + 'dart_tools_rev': 'b412ba4550bb634caf3c1064b7ebb671cd5e9247', + 'dart_web_rev': 'af5de5e8548060c0795713ee7129ba6d5ff9f1b2', + 'dart_webdev_rev': 'e72f365a4408dce73bc023e624adc6a9a72dd7a2', + 'dart_webkit_inspection_protocol_rev': 'effa75205516757795683d527c3dea9546eb0c32', + + 'ocmock_rev': 'c4ec0e3a7a9f56cfdbd0aa01f4f97bb4b75c5ef8', # v3.7.1 + + # Download a prebuilt Dart SDK by default + 'download_dart_sdk': True, + + # Download a prebuilt esbuild by default + 'download_esbuild': True, + + # Checkout Android dependencies only on platforms where we build for Android targets. + 'download_android_deps': 'host_os == "mac" or (host_os == "linux" and host_cpu == "x64")', + + # Checkout Java dependencies only on platforms that do not have java installed on path. + 'download_jdk': True, + + # Checkout Windows dependencies only if we are building on Windows. + 'download_windows_deps' : 'host_os == "win"', + + # Checkout Linux dependencies only when building on Linux. + 'download_linux_deps': 'host_os == "linux"', + + # The minimum macOS SDK version. This must match the setting in + # //flutter/tools/gn. + 'mac_sdk_min': '10.14', + + # Checkout Fuchsia dependencies only on Linux. This is the umbrella flag which + # controls the behavior of all fuchsia related flags. I.e. any fuchsia related + # logic or condition may not work if this flag is False. + # TODO(zijiehe): Make this condition more strict to only download fuchsia + # dependencies when necessary: b/40935282 + 'download_fuchsia_deps': 'host_os == "linux"', + # Downloads the fuchsia SDK as listed in fuchsia_sdk_path var. This variable + # is currently only used for the Fuchsia LSC process and is not intended for + # local development. + 'download_fuchsia_sdk': False, + 'fuchsia_sdk_path': '', + # Whether to download and run the Fuchsia emulator locally to test Fuchsia + # builds. + 'run_fuchsia_emu': False, + + # An LLVM backend needs LLVM binaries and headers. To avoid build time + # increases we can use prebuilts. We don't want to download this on every + # CQ/CI bot nor do we want the average Dart developer to incur that cost. + # So by default we will not download prebuilts. This variable is needed in + # the flutter engine to ensure that Dart gn has access to it as well. + "checkout_llvm": False, + + # Setup Git hooks by default. + 'setup_githooks': True, + + # When this is true, the Flutter Engine's configuration files and scripts for + # RBE will be downloaded from CIPD. This option is only usable by Googlers. + 'use_rbe': False, + + # This is not downloaded be default because it increases the + # `gclient sync` time by between 1 and 3 minutes. This option is enabled + # in flutter/ci/builders/mac_impeller_cmake_example.json, and is likely to + # only be useful locally when reproducing issues found by the bot. + 'download_impeller_cmake_example': False, + + # Upstream URLs for third party dependencies, used in + # determining common ancestor commit for vulnerability scanning + # prefixed with 'upstream_' in order to be identified by parsing tool. + # The vulnerability database being used in this scan can be browsed + # using this UI https://osv.dev/list + # If a new dependency needs to be added, the upstream (non-mirrored) + # git URL for that dependency should be added to this list + # with the key-value pair being: + # 'upstream_[dep name from last slash and before .git in URL]':'[git URL]' + # example: + "upstream_abseil-cpp": "https://github.com/abseil/abseil-cpp.git", + "upstream_angle": "https://github.com/google/angle.git", + "upstream_archive": "https://github.com/brendan-duncan/archive.git", + "upstream_benchmark": "https://github.com/google/benchmark.git", + "upstream_boringssl": "https://github.com/openssl/openssl.git", + "upstream_brotli": "https://github.com/google/brotli.git", + "upstream_dart_style": "https://github.com/dart-lang/dart_style.git", + "upstream_dartdoc": "https://github.com/dart-lang/dartdoc.git", + "upstream_equatable": "https://github.com/felangel/equatable.git", + "upstream_ffi": "https://github.com/dart-lang/ffi.git", + "upstream_flatbuffers": "https://github.com/google/flatbuffers.git", + "upstream_freetype2": "https://gitlab.freedesktop.org/freetype/freetype.git", + "upstream_gcloud": "https://github.com/dart-lang/gcloud.git", + "upstream_glfw": "https://github.com/glfw/glfw.git", + "upstream_glob": "https://github.com/dart-lang/glob.git", + "upstream_googleapis": "https://github.com/google/googleapis.dart.git", + "upstream_googletest": "https://github.com/google/googletest.git", + "upstream_gtest-parallel": "https://github.com/google/gtest-parallel.git", + "upstream_harfbuzz": "https://github.com/harfbuzz/harfbuzz.git", + "upstream_http": "https://github.com/dart-lang/http.git", + "upstream_icu": "https://github.com/unicode-org/icu.git", + "upstream_intl": "https://github.com/dart-lang/intl.git", + "upstream_imgui": "https://github.com/ocornut/imgui.git", + "upstream_inja": "https://github.com/pantor/inja.git", + "upstream_json": "https://github.com/nlohmann/json.git", + "upstream_libcxx": "https://github.com/llvm-mirror/libcxx.git", + "upstream_libcxxabi": "https://github.com/llvm-mirror/libcxxabi.git", + "upstream_libexpat": "https://github.com/libexpat/libexpat.git", + "upstream_libjpeg-turbo": "https://github.com/libjpeg-turbo/libjpeg-turbo.git", + "upstream_libpng": "https://github.com/glennrp/libpng.git", + "upstream_libtess2": "https://github.com/memononen/libtess2.git", + "upstream_libwebp": "https://chromium.googlesource.com/webm/libwebp.git", + "upstream_leak_tracker": "https://github.com/dart-lang/leak_tracker.git", + "upstream_markdown": "https://github.com/dart-lang/markdown.git", + "upstream_mockito": "https://github.com/dart-lang/mockito.git", + "upstream_ocmock": "https://github.com/erikdoe/ocmock.git", + "upstream_packages": "https://github.com/flutter/packages.git", + "upstream_process_runner": "https://github.com/google/process_runner.git", + "upstream_process": "https://github.com/google/process.dart.git", + "upstream_protobuf": "https://github.com/google/protobuf.dart.git", + "upstream_pub_semver": "https://github.com/dart-lang/pub_semver.git", + "upstream_pub": "https://github.com/dart-lang/pub.git", + "upstream_pyyaml": "https://github.com/yaml/pyyaml.git", + "upstream_quiver-dart": "https://github.com/google/quiver-dart.git", + "upstream_rapidjson": "https://github.com/Tencent/rapidjson.git", + "upstream_sdk": "https://github.com/dart-lang/sdk.git", + "upstream_shaderc": "https://github.com/google/shaderc.git", + "upstream_shelf": "https://github.com/dart-lang/shelf.git", + "upstream_skia": "https://skia.googlesource.com/skia.git", + "upstream_sqlite": "https://github.com/sqlite/sqlite.git", + "upstream_SwiftShader": "https://swiftshader.googlesource.com/SwiftShader.git", + "upstream_tar": "https://github.com/simolus3/tar.git", + "upstream_test": "https://github.com/dart-lang/test.git", + "upstream_usage": "https://github.com/dart-lang/usage.git", + "upstream_vector_math": "https://github.com/google/vector_math.dart.git", + "upstream_VulkanMemoryAllocator": "https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git", + "upstream_web_socket_channel": "https://github.com/dart-lang/web_socket_channel.git", + "upstream_webdev": "https://github.com/dart-lang/webdev.git", + "upstream_webkit_inspection_protocol": "https://github.com/google/webkit_inspection_protocol.dart.git", + "upstream_wuffs-mirror-release-c": "https://github.com/google/wuffs-mirror-release-c.git", + "upstream_yapf": "https://github.com/google/yapf.git", + "upstream_zlib": "https://github.com/madler/zlib.git", + + # The version / instance id of the cipd:chromium/fuchsia/test-scripts which + # will be used altogether with fuchsia-sdk to setup the build / test + # environment. + 'fuchsia_test_scripts_version': 'r9Dc5VRF6sE3pJH20k2d1Yko3xSlwljH_nuw7O8vcb4C', + + # The version / instance id of the cipd:chromium/fuchsia/gn-sdk which will be + # used altogether with fuchsia-sdk to generate gn based build rules. + 'fuchsia_gn_sdk_version': 'tHRCseOuPnZ5H4a7kb4Zl6YQ2rhEDWIzcEX3G9NPFhkC', +} + +gclient_gn_args_file = 'engine/src/flutter/third_party/dart/build/config/gclient_args.gni' +gclient_gn_args = [ + 'checkout_llvm' +] + +# Only these hosts are allowed for dependencies in this DEPS file. +# If you need to add a new host, contact chrome infrastructure team. +allowed_hosts = [ + 'boringssl.googlesource.com', + 'chrome-infra-packages.appspot.com', + 'chromium.googlesource.com', + 'dart.googlesource.com', + 'flutter.googlesource.com', + 'llvm.googlesource.com', + 'skia.googlesource.com', + 'swiftshader.googlesource.com', +] + +deps = { + 'engine/src/flutter/third_party/depot_tools': + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '580b4ff3f5cd0dcaa2eacda28cefe0f45320e8f7', + + 'engine/src/flutter/third_party/rapidjson': + Var('flutter_git') + '/third_party/rapidjson' + '@' + 'ef3564c5c8824989393b87df25355baf35ff544b', + + 'engine/src/flutter/third_party/harfbuzz': + Var('flutter_git') + '/third_party/harfbuzz' + '@' + 'ea8f97c615f0ba17dc25013ef67dbd6bfaaa76f2', + + 'engine/src/flutter/third_party/libcxx': + Var('llvm_git') + '/llvm-project/libcxx' + '@' + '44079a4cc04cdeffb9cfe8067bfb3c276fb2bab0', + + 'engine/src/flutter/third_party/libcxxabi': + Var('llvm_git') + '/llvm-project/libcxxabi' + '@' + '2ce528fb5e0f92e57c97ec3ff53b75359d33af12', + + 'engine/src/flutter/third_party/glfw': + Var('flutter_git') + '/third_party/glfw' + '@' + 'dd8a678a66f1967372e5a5e3deac41ebf65ee127', + + 'engine/src/flutter/third_party/shaderc': + Var('chromium_git') + '/external/github.com/google/shaderc' + '@' + '37e25539ce199ecaf19fb7f7d27818716d36686d', + + 'engine/src/flutter/third_party/vulkan-deps': + Var('chromium_git') + '/vulkan-deps' + '@' + '014f44e134a1de387791bffacc32ff9d8db71176', + + 'engine/src/flutter/third_party/flatbuffers': + Var('chromium_git') + '/external/github.com/google/flatbuffers' + '@' + '0a80646371179f8a7a5c1f42c31ee1d44dcf6709', + + 'engine/src/flutter/third_party/icu': + Var('chromium_git') + '/chromium/deps/icu.git' + '@' + '4239b1559d11d4fa66c100543eda4161e060311e', + + 'engine/src/flutter/third_party/gtest-parallel': + Var('chromium_git') + '/external/github.com/google/gtest-parallel' + '@' + '38191e2733d7cbaeaef6a3f1a942ddeb38a2ad14', + + 'engine/src/flutter/third_party/benchmark': + Var('chromium_git') + '/external/github.com/google/benchmark' + '@' + '431abd149fd76a072f821913c0340137cc755f36', + + 'engine/src/flutter/third_party/googletest': + Var('chromium_git') + '/external/github.com/google/googletest' + '@' + '7f036c5563af7d0329f20e8bb42effb04629f0c0', + + 'engine/src/flutter/third_party/brotli': + Var('skia_git') + '/external/github.com/google/brotli.git' + '@' + '350100a5bb9d9671aca85213b2ec7a70a361b0cd', + + 'engine/src/flutter/third_party/yapf': + Var('flutter_git') + '/third_party/yapf' + '@' + '212c5b5ad8e172d2d914ae454c121c89cccbcb35', + + 'engine/src/flutter/third_party/boringssl/src': + 'https://boringssl.googlesource.com/boringssl.git' + '@' + Var('dart_boringssl_rev'), + + 'engine/src/flutter/third_party/perfetto': + Var('flutter_git') + "/third_party/perfetto" + '@' + Var('dart_perfetto_rev'), + + 'engine/src/flutter/third_party/protobuf': + Var('flutter_git') + '/third_party/protobuf' + '@' + Var('dart_libprotobuf_rev'), + + # TODO(67373): These are temporarily checked in, but this dep can be restored + # once the buildmoot is completed. + # 'engine/src/flutter/build/secondary/third_party/protobuf': + # Var('flutter_git') + '/third_party/protobuf-gn' + '@' + Var('dart_protobuf_gn_rev'), + + 'engine/src/flutter/third_party/dart': + Var('dart_git') + '/sdk.git' + '@' + Var('dart_revision'), + + # WARNING: Unused Dart dependencies in the list below till "WARNING:" marker are removed automatically - see create_updated_flutter_deps.py. + + 'engine/src/flutter/third_party/dart/third_party/binaryen/src': + Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@87f9dac127b387715d8d96ac7ec8fd469d8c2dab', + + 'engine/src/flutter/third_party/dart/third_party/devtools': + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:8762b31f0d0ffeea6449fd02740e9ce7acb32503'}]}, + + 'engine/src/flutter/third_party/dart/third_party/pkg/core': + Var('dart_git') + '/core.git' + '@' + Var('dart_core_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/dart_style': + Var('dart_git') + '/dart_style.git@21de99ec0ff8ace4d946a746fb427fffd6afa535', + + 'engine/src/flutter/third_party/dart/third_party/pkg/dartdoc': + Var('dart_git') + '/dartdoc.git@c7f11603effa88ddacabfd555993f322fca8b3fe', + + 'engine/src/flutter/third_party/dart/third_party/pkg/glob': + Var('dart_git') + '/glob.git@eee18d1a577d5f965f6afbbd251798e065dced43', + + 'engine/src/flutter/third_party/dart/third_party/pkg/http': + Var('dart_git') + '/http.git' + '@' + Var('dart_http_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/intl': + Var('dart_git') + '/intl.git@5d65e3808ce40e6282e40881492607df4e35669f', + + 'engine/src/flutter/third_party/dart/third_party/pkg/leak_tracker': + Var('dart_git') + '/leak_tracker.git@f5620600a5ce1c44f65ddaa02001e200b096e14c', + + 'engine/src/flutter/third_party/dart/third_party/pkg/markdown': + Var('dart_git') + '/markdown.git@19aaded4300d24bedcbf52ade792b203ddf030b0', + + 'engine/src/flutter/third_party/dart/third_party/pkg/native': + Var('dart_git') + '/native.git@14368a80bae9e3f381a2e59c91405338d82451ee', + + 'engine/src/flutter/third_party/dart/third_party/pkg/protobuf': + Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/pub': + Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/shelf': + Var('dart_git') + '/shelf.git@2b5b683e78f5cc84e479a43297fd7b5489d7db02', + + 'engine/src/flutter/third_party/dart/third_party/pkg/tar': + Var('dart_git') + '/external/github.com/simolus3/tar.git@5a1ea943e70cdf3fa5e1102cdbb9418bd9b4b81a', + + 'engine/src/flutter/third_party/dart/third_party/pkg/test': + Var('dart_git') + '/test.git@f364fc8291d668d85c702a5b9f9a4f2e5c1ade0e', + + 'engine/src/flutter/third_party/dart/third_party/pkg/tools': + Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/web': + Var('dart_git') + '/web.git' + '@' + Var('dart_web_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/web_socket_channel': + Var('dart_git') + '/web_socket_channel.git@a937243563e8ee75d11fb23610297d4f6e5cb2b9', + + 'engine/src/flutter/third_party/dart/third_party/pkg/webdev': + Var('dart_git') + '/webdev.git' + '@' + Var('dart_webdev_rev'), + + 'engine/src/flutter/third_party/dart/third_party/pkg/webkit_inspection_protocol': + Var('dart_git') + '/external/github.com/google/webkit_inspection_protocol.dart.git' + '@' + Var('dart_webkit_inspection_protocol_rev'), + + 'engine/src/flutter/third_party/dart/tools/sdks/dart-sdk': + {'dep_type': 'cipd', 'packages': [{'package': 'dart/dart-sdk/${{platform}}', 'version': 'git_revision:f863f0b43625eb04539a34d7cc25029ba80dd522'}]}, + + # WARNING: end of dart dependencies list that is cleaned up automatically - see create_updated_flutter_deps.py. + + # Prebuilt Dart SDK of the same revision as the Dart SDK source checkout + 'engine/src/flutter/prebuilts/linux-x64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/linux-amd64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "linux" and download_dart_sdk' + }, + 'engine/src/flutter/prebuilts/linux-arm64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/linux-arm64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "linux" and download_dart_sdk' + }, + 'engine/src/flutter/prebuilts/macos-x64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/mac-amd64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "mac" and download_dart_sdk' + }, + 'engine/src/flutter/prebuilts/macos-arm64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/mac-arm64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "mac" and download_dart_sdk' + }, + 'engine/src/flutter/prebuilts/windows-x64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/windows-amd64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "win" and download_dart_sdk' + }, + 'engine/src/flutter/prebuilts/windows-arm64/dart-sdk': { + 'packages': [ + { + 'package': 'flutter/dart-sdk/windows-arm64', + 'version': 'git_revision:'+Var('dart_revision') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "win" and download_dart_sdk' + }, + + # esbuild download + 'engine/src/flutter/prebuilts/linux-x64/esbuild': { + 'packages': [ + { + 'package': 'flutter/tools/esbuild/linux-amd64', + 'version': Var('esbuild_version') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "linux" and download_esbuild' + }, + 'engine/src/flutter/prebuilts/macos-x64/esbuild': { + 'packages': [ + { + 'package': 'flutter/tools/esbuild/mac-amd64', + 'version': Var('esbuild_version') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "mac" and download_esbuild' + }, + 'engine/src/flutter/prebuilts/macos-arm64/esbuild': { + 'packages': [ + { + 'package': 'flutter/tools/esbuild/mac-arm64', + 'version': Var('esbuild_version') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "mac" and download_esbuild' + }, + 'engine/src/flutter/prebuilts/windows-x64/esbuild': { + 'packages': [ + { + 'package': 'flutter/tools/esbuild/windows-amd64', + 'version': Var('esbuild_version') + } + ], + 'dep_type': 'cipd', + 'condition': 'host_os == "win" and download_esbuild' + }, + + 'engine/src/flutter/third_party/expat': + Var('chromium_git') + '/external/github.com/libexpat/libexpat.git' + '@' + '654d2de0da85662fcc7644a7acd7c2dd2cfb21f0', + + 'engine/src/flutter/third_party/freetype2': + Var('flutter_git') + '/third_party/freetype2' + '@' + 'bfc3453fdc85d87b45c896f68bf2e49ebdaeef0a', + + 'engine/src/flutter/third_party/skia': + Var('skia_git') + '/skia.git' + '@' + Var('skia_revision'), + + 'engine/src/flutter/third_party/ocmock': + Var('flutter_git') + '/third_party/ocmock' + '@' + Var('ocmock_rev'), + + 'engine/src/flutter/third_party/libjpeg-turbo/src': + Var('flutter_git') + '/third_party/libjpeg-turbo' + '@' + '0fb821f3b2e570b2783a94ccd9a2fb1f4916ae9f', + + 'engine/src/flutter/third_party/libpng': + Var('flutter_git') + '/third_party/libpng' + '@' + 'de36b892e921c684ef718fec24739ae9bb49c977', + + 'engine/src/flutter/third_party/libwebp': + Var('chromium_git') + '/webm/libwebp.git' + '@' + 'ca332209cb5567c9b249c86788cb2dbf8847e760', # 1.3.2 + + 'engine/src/flutter/third_party/wuffs': + Var('skia_git') + '/external/github.com/google/wuffs-mirror-release-c.git' + '@' + '600cd96cf47788ee3a74b40a6028b035c9fd6a61', + + 'engine/src/flutter/third_party/zlib': + Var('chromium_git') + '/chromium/src/third_party/zlib.git' + '@' + '7d77fb7fd66d8a5640618ad32c71fdeb7d3e02df', + + 'engine/src/flutter/third_party/cpu_features/src': + Var('chromium_git') + '/external/github.com/google/cpu_features.git' + '@' + '936b9ab5515dead115606559502e3864958f7f6e', + + 'engine/src/flutter/third_party/inja': + Var('flutter_git') + '/third_party/inja' + '@' + '88bd6112575a80d004e551c98cf956f88ff4d445', + + 'engine/src/flutter/third_party/libtess2': + Var('flutter_git') + '/third_party/libtess2' + '@' + '725e5e08ec8751477565f1d603fd7eb9058c277c', + + 'engine/src/flutter/third_party/sqlite': + Var('flutter_git') + '/third_party/sqlite' + '@' + '0f61bd2023ba94423b4e4c8cfb1a23de1fe6a21c', + + 'engine/src/flutter/third_party/pyyaml': + Var('flutter_git') + '/third_party/pyyaml.git' + '@' + '03c67afd452cdff45b41bfe65e19a2fb5b80a0e8', + + 'engine/src/flutter/third_party/swiftshader': + Var('swiftshader_git') + '/SwiftShader.git' + '@' + '2fa7e9b99ae4e70ea5ae2cc9c8d3afb43391384f', + + 'engine/src/flutter/third_party/angle': + Var('chromium_git') + '/angle/angle.git' + '@' + '6a09e41ce6ea8c93524faae1a925eb01562f53b1', + + 'engine/src/flutter/third_party/vulkan_memory_allocator': + Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + '7de5cc00de50e71a3aab22dea52fbb7ff4efceb6', + + 'engine/src/flutter/third_party/abseil-cpp': + Var('flutter_git') + '/third_party/abseil-cpp.git' + '@' + 'ff6504dc527b25fef0f3c531e7dba0ed6b69c162', + + # Dart packages + 'engine/src/flutter/third_party/pkg/archive': + Var('chromium_git') + '/external/github.com/brendan-duncan/archive.git' + '@' + 'f1d164f8f5d8aea0be620a9b1e8d300b75a29388', # 3.6.1 + + 'engine/src/flutter/third_party/pkg/coverage': + Var('flutter_git') + '/third_party/coverage.git' + '@' + 'bb0ab721ee4ceef1abfa413d8d6fd46013b583b9', # 1.7.2 + + 'engine/src/flutter/third_party/pkg/equatable': + Var('flutter_git') + '/third_party/equatable.git' + '@' + '2117551ff3054f8edb1a58f63ffe1832a8d25623', # 2.0.5 + + 'engine/src/flutter/third_party/pkg/flutter_packages': + Var('flutter_git') + '/mirrors/packages' + '@' + '25454e63851fe7933f04d025606e68c1eac4fe0f', # various + + 'engine/src/flutter/third_party/pkg/gcloud': + Var('flutter_git') + '/third_party/gcloud.git' + '@' + 'a5276b85c4714378e84b1fb478b8feeeb686ac26', # 0.8.6-dev + + 'engine/src/flutter/third_party/pkg/googleapis': + Var('flutter_git') + '/third_party/googleapis.dart.git' + '@' + '526011f56d98eab183cc6075ee1392e8303e43e2', # various + + 'engine/src/flutter/third_party/pkg/io': + Var('flutter_git') + '/third_party/io.git' + '@' + '997a6243aad20af4238147d9ec00bf638b9169af', # 1.0.5-wip + + 'engine/src/flutter/third_party/pkg/node_preamble': + Var('flutter_git') + '/third_party/node_preamble.dart.git' + '@' + '47245865175929ec452d8058e563c267b64c3d64', # 2.0.2 + + 'engine/src/flutter/third_party/pkg/process': + Var('dart_git') + '/process.dart' + '@' + '0c9aeac86dcc4e3a6cf760b76fed507107e244d5', # 4.2.1 + + 'engine/src/flutter/third_party/pkg/process_runner': + Var('flutter_git') + '/third_party/process_runner.git' + '@' + 'f24c69efdcaf109168f23d381fa281453d2bc9b1', # 4.1.2 + + 'engine/src/flutter/third_party/pkg/vector_math': + Var('dart_git') + '/external/github.com/google/vector_math.dart.git' + '@' + '0a5fd95449083d404df9768bc1b321b88a7d2eef', # 2.1.0 + + 'engine/src/flutter/third_party/imgui': + Var('flutter_git') + '/third_party/imgui.git' + '@' + '3ea0fad204e994d669f79ed29dcaf61cd5cb571d', + + 'engine/src/flutter/third_party/json': + Var('flutter_git') + '/third_party/json.git' + '@' + '17d9eacd248f58b73f4d1be518ef649fe2295642', + + 'engine/src/flutter/third_party/gradle': { + 'packages': [ + { + # See tools/gradle/README.md for update instructions. + # Version here means the CIPD tag. + 'version': 'version:8.9', + 'package': 'flutter/gradle' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd' + }, + + 'engine/src/flutter/third_party/android_tools/trace_to_text': { + 'packages': [ + { + # 25.0 downloads for both mac-amd64 and mac-arm64 + # 26.1 is not found with either platform + # 27.1 is the latest release of perfetto + 'version': 'git_tag:v25.0', + 'package': 'perfetto/trace_to_text/${{platform}}' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd' + }, + + 'engine/src/flutter/third_party/android_tools/google-java-format': { + 'packages': [ + { + 'package': 'flutter/android/google-java-format', + 'version': 'version:1.7-1' + } + ], + # We want to be able to format these as part of CI, and the CI step that + # checks formatting runs without downloading the rest of the Android build + # tooling. Therefore unlike all the other Android-related tools, we want to + # download this every time. + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/android_tools': { + 'packages': [ + { + 'package': 'flutter/android/sdk/all/${{platform}}', + 'version': 'version:35v1' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/android_embedding_dependencies': { + 'packages': [ + { + 'package': 'flutter/android/embedding_bundle', + 'version': 'last_updated:2024-09-10T16:32:16-0700' + } + ], + 'condition': 'download_android_deps', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/java/openjdk': { + 'packages': [ + { + 'package': 'flutter/java/openjdk/${{platform}}', + 'version': 'version:17' + } + ], + # Always download the JDK since java is required for running the formatter. + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/gn': { + 'packages': [ + { + 'package': 'gn/gn/${{platform}}', + 'version': 'git_revision:b79031308cc878488202beb99883ec1f2efd9a6d' + }, + ], + 'dep_type': 'cipd', + }, + 'engine/src/flutter/third_party/ninja': { + 'packages': [ + { + 'package': 'infra/3pp/tools/ninja/${{platform}}', + 'version': 'version:2@1.11.1.chromium.4', + } + ], + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/prebuilts/emsdk': { + 'url': Var('skia_git') + '/external/github.com/emscripten-core/emsdk.git' + '@' + '2514ec738de72cebbba7f4fdba0cf2fabcb779a5', + 'condition': 'download_emsdk', + }, + + # Clang on mac and linux are expected to typically be the same revision. + # They are separated out so that the autoroller can more easily manage them. + 'engine/src/flutter/buildtools/mac-x64/clang': { + 'packages': [ + { + 'package': 'fuchsia/third_party/clang/mac-amd64', + 'version': Var('clang_version'), + } + ], + 'condition': 'host_os == "mac"', # On ARM64 Macs too because Goma doesn't support the host-arm64 toolchain. + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/mac-arm64/clang': { + 'packages': [ + { + 'package': 'fuchsia/third_party/clang/mac-arm64', + 'version': Var('clang_version'), + } + ], + 'condition': 'host_os == "mac" and host_cpu == "arm64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/linux-x64/clang': { + 'packages': [ + { + 'package': 'fuchsia/third_party/clang/linux-amd64', + 'version': Var('clang_version'), + } + ], + 'condition': 'host_os == "linux" or host_os == "mac"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/linux-arm64/clang': { + 'packages': [ + { + 'package': 'fuchsia/third_party/clang/linux-arm64', + 'version': Var('clang_version'), + } + ], + 'condition': 'host_os == "linux" and host_cpu == "arm64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/windows-x64/clang': { + 'packages': [ + { + 'package': 'fuchsia/third_party/clang/windows-amd64', + 'version': Var('clang_version'), + } + ], + 'condition': 'download_windows_deps', + 'dep_type': 'cipd', + }, + + # RBE binaries and configs. + 'engine/src/flutter/buildtools/linux-x64/reclient': { + 'packages': [ + { + 'package': 'infra/rbe/client/${{platform}}', + 'version': Var('reclient_version'), + } + ], + 'condition': 'use_rbe and host_os == "linux" and host_cpu == "x64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/mac-arm64/reclient': { + 'packages': [ + { + 'package': 'infra/rbe/client/${{platform}}', + 'version': Var('reclient_version'), + } + ], + 'condition': 'use_rbe and host_os == "mac" and host_cpu == "arm64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/mac-x64/reclient': { + 'packages': [ + { + 'package': 'infra/rbe/client/${{platform}}', + 'version': Var('reclient_version'), + } + ], + 'condition': 'use_rbe and host_os == "mac" and host_cpu == "x64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/windows-x64/reclient': { + 'packages': [ + { + 'package': 'infra/rbe/client/${{platform}}', + 'version': Var('reclient_version'), + } + ], + 'condition': 'use_rbe and download_windows_deps', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/build/rbe': { + 'packages': [ + { + 'package': 'flutter_internal/rbe/reclient_cfgs', + 'version': 'XIomtC8MFuQrF9qI5xYcFfcfKXZTbcY6nL6NgF-pSRIC', + } + ], + 'condition': 'use_rbe', + 'dep_type': 'cipd', + }, + + # gcloud + 'engine/src/flutter/buildtools/linux-x64/gcloud': { + 'packages': [ + { + 'package': 'infra/3pp/tools/gcloud/${{platform}}', + 'version': Var('gcloud_version'), + } + ], + 'condition': 'use_rbe and host_os == "linux" and host_cpu == "x64"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/buildtools/mac-arm64/gcloud': { + 'packages': [ + { + 'package': 'infra/3pp/tools/gcloud/${{platform}}', + 'version': Var('gcloud_version'), + } + ], + 'condition': 'use_rbe and host_os == "mac" and host_cpu == "arm64"', + 'dep_type': 'cipd', + }, + + # Get the SDK from https://chrome-infra-packages.appspot.com/p/fuchsia/sdk/core at the 'latest' tag + # Get the toolchain from https://chrome-infra-packages.appspot.com/p/fuchsia/clang at the 'goma' tag + 'engine/src/fuchsia/sdk/linux': { + 'packages': [ + { + 'package': 'fuchsia/sdk/core/linux-amd64', + 'version': 'zvsXvTuk-Z1Mgtn34lhXHPbQAAPOVrHYuEZZJqa6N9oC' + } + ], + 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/tools/fuchsia/test_scripts': { + 'packages': [ + { + 'package': 'chromium/fuchsia/test-scripts', + 'version': Var('fuchsia_test_scripts_version'), + } + ], + 'condition': 'download_fuchsia_deps', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/tools/fuchsia/gn-sdk': { + 'packages': [ + { + 'package': 'chromium/fuchsia/gn-sdk', + 'version': Var('fuchsia_gn_sdk_version'), + } + ], + 'condition': 'download_fuchsia_deps', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/impeller-cmake-example': { + 'url': Var('flutter_git') + '/third_party/impeller-cmake-example.git' + '@' + '9f8298ec31dcbebbf019bd487888166abf2f55e6', + 'condition': 'download_impeller_cmake_example', + }, + + # cmake is only used by impeller-cmake-example. + 'engine/src/flutter/buildtools/mac-x64/cmake': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cmake/mac-amd64', + 'version': 'CGpMvZoP962wdEINR9d4OEvEW7ZOv0MPrHNKbBUBS0sC', + } + ], + 'condition': 'download_impeller_cmake_example and host_os == "mac"', + 'dep_type': 'cipd', + }, + + 'engine/src/flutter/third_party/google_fonts_for_unit_tests': { + 'packages': [ + { + 'package': 'flutter/flutter_font_fallbacks', + 'version': '44bd38be0bc8c189a397ca6dd6f737746a9e0c6117b96a8f84f1edf6acd1206b' + } + ], + 'dep_type': 'cipd', + } +} + +recursedeps = [ + 'engine/src/flutter/third_party/vulkan-deps', +] + +hooks = [ + { + # Generate the Dart SDK's .dart_tool/package_confg.json file. + 'name': 'Generate .dart_tool/package_confg.json', + 'pattern': '.', + 'action': ['python3', 'engine/src/flutter/third_party/dart/tools/generate_package_config.py'], + }, + { + # Generate the sdk/version file. + 'name': 'Generate sdk/version', + 'pattern': '.', + 'action': ['python3', 'engine/src/flutter/third_party/dart/tools/generate_sdk_version_file.py'], + }, + { + # Update the Windows toolchain if necessary. + 'name': 'win_toolchain', + 'condition': 'download_windows_deps', + 'pattern': '.', + 'action': ['python3', 'engine/src/build/vs_toolchain.py', 'update'], + }, + { + 'name': 'dia_dll', + 'pattern': '.', + 'condition': 'download_windows_deps', + 'action': [ + 'python3', + 'engine/src/flutter/tools/dia_dll.py', + ], + }, + { + 'name': 'linux_sysroot_x64', + 'pattern': '.', + 'condition': 'download_linux_deps', + 'action': [ + 'python3', + 'engine/src/build/linux/sysroot_scripts/install-sysroot.py', + '--arch=x64'], + }, + { + 'name': 'linux_sysroot_arm64', + 'pattern': '.', + 'condition': 'download_linux_deps', + 'action': [ + 'python3', + 'engine/src/build/linux/sysroot_scripts/install-sysroot.py', + '--arch=arm64'], + }, + { + 'name': 'pub get --offline', + 'pattern': '.', + 'action': [ + 'python3', + 'engine/src/flutter/tools/pub_get_offline.py', + ] + }, + { + 'name': 'Download Fuchsia SDK', + 'pattern': '.', + 'condition': 'download_fuchsia_deps and download_fuchsia_sdk', + 'action': [ + 'python3', + 'engine/src/flutter/tools/download_fuchsia_sdk.py', + '--fail-loudly', + '--verbose', + '--host-os', + Var('host_os'), + '--fuchsia-sdk-path', + Var('fuchsia_sdk_path'), + ] + }, + { + 'name': 'Activate Emscripten SDK', + 'pattern': '.', + 'condition': 'download_emsdk', + 'action': [ + 'python3', + 'engine/src/flutter/tools/activate_emsdk.py', + ] + }, + { + 'name': 'Setup githooks', + 'pattern': '.', + 'condition': 'setup_githooks', + 'action': [ + 'python3', + 'engine/src/flutter/tools/githooks/setup.py', + ] + }, + { + 'name': 'impeller-cmake-example submodules', + 'pattern': '.', + 'condition': 'download_impeller_cmake_example', + 'action': [ + 'python3', + 'engine/src/flutter/ci/impeller_cmake_build_test.py', + '--path', + 'flutter/third_party/impeller-cmake-example', + '--setup', + ] + }, + { + 'name': 'Download Fuchsia system images', + 'pattern': '.', + 'condition': 'run_fuchsia_emu', + 'action': [ + 'env', + 'DOWNLOAD_FUCHSIA_SDK={download_fuchsia_sdk}', + 'FUCHSIA_SDK_PATH={fuchsia_sdk_path}', + 'python3', + 'engine/src/flutter/tools/fuchsia/with_envs.py', + 'engine/src/flutter/tools/fuchsia/test_scripts/update_product_bundles.py', + 'terminal.x64,terminal.qemu-arm64', + ] + }, + # The following two scripts check if they are running in the LUCI + # environment, and do nothing if so. This is because Xcode is not yet + # installed in CI when these hooks are run. + { + 'name': 'Find the iOS device SDKs', + 'pattern': '.', + 'condition': 'host_os == "mac"', + 'action': [ + 'python3', + 'engine/src/build/config/ios/ios_sdk.py', + # This cleans up entries under flutter/prebuilts for this script and the + # following script. + '--as-gclient-hook' + ] + }, + { + 'name': 'Find the macOS SDK', + 'pattern': '.', + 'condition': 'host_os == "mac"', + 'action': [ + 'python3', + 'engine/src/build/mac/find_sdk.py', + '--as-gclient-hook', + Var('mac_sdk_min') + ] + }, + { + 'name': 'Generate Fuchsia GN build rules', + 'pattern': '.', + 'condition': 'download_fuchsia_deps', + 'action': [ + 'python3', + 'engine/src/flutter/tools/fuchsia/with_envs.py', + 'engine/src/flutter/tools/fuchsia/test_scripts/gen_build_defs.py', + ], + }, +] diff --git a/flutter/LICENSE b/flutter/LICENSE new file mode 100644 index 00000000..aea51a33 --- /dev/null +++ b/flutter/LICENSE @@ -0,0 +1,25 @@ +Copyright 2014 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/flutter/PATENT_GRANT b/flutter/PATENT_GRANT new file mode 100644 index 00000000..0df05896 --- /dev/null +++ b/flutter/PATENT_GRANT @@ -0,0 +1,17 @@ +Google hereby grants to you a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this +section) patent license to make, have made, use, offer to sell, sell, +import, transfer, and otherwise run, modify and propagate the contents +of this implementation, where such license applies only to those +patent claims, both currently owned by Google and acquired in the +future, licensable by Google that are necessarily infringed by this +implementation. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute +or order or agree to the institution of patent litigation or any other +patent enforcement activity against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that this +implementation constitutes direct or contributory patent infringement, +or inducement of patent infringement, then any patent rights granted +to you under this License for this implementation shall terminate as +of the date such litigation is filed. diff --git a/flutter/README.md b/flutter/README.md new file mode 100644 index 00000000..b455ba1b --- /dev/null +++ b/flutter/README.md @@ -0,0 +1,124 @@ + +

+ + + Flutter + +

+
+ +[![Flutter CI Status](https://flutter-dashboard.appspot.com/api/public/build-status-badge?repo=flutter)](https://flutter-dashboard.appspot.com/#/build?repo=flutter) +[![Discord badge][]][Discord instructions] +[![Twitter handle][]][Twitter badge] +[![codecov](https://codecov.io/gh/flutter/flutter/branch/master/graph/badge.svg?token=11yDrJU2M2)](https://codecov.io/gh/flutter/flutter) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5631/badge)](https://bestpractices.coreinfrastructure.org/projects/5631) +[![SLSA 1](https://slsa.dev/images/gh-badge-level1.svg)](https://slsa.dev) + +Flutter is Google's SDK for crafting beautiful, fast user experiences for +mobile, web, and desktop from a single codebase. Flutter works with existing +code, is used by developers and organizations around the world, and is free and +open source. + +## Documentation + +* [Install Flutter](https://flutter.dev/get-started/) +* [Flutter documentation](https://docs.flutter.dev/) +* [Development wiki](./docs/README.md) +* [Contributing to Flutter](https://github.com/flutter/flutter/blob/main/CONTRIBUTING.md) + +For announcements about new releases, follow the +[flutter-announce@googlegroups.com](https://groups.google.com/forum/#!forum/flutter-announce) +mailing list. Our documentation also tracks [breaking +changes](https://docs.flutter.dev/release/breaking-changes) across releases. + +## Terms of service + +The Flutter tool may occasionally download resources from Google servers. By +downloading or using the Flutter SDK, you agree to the Google Terms of Service: +https://policies.google.com/terms + +For example, when installed from GitHub (as opposed to from a prepackaged +archive), the Flutter tool will download the Dart SDK from Google servers +immediately when first run, as it is used to execute the `flutter` tool itself. +This will also occur when Flutter is upgraded (e.g. by running the `flutter +upgrade` command). + +## About Flutter + +We think Flutter will help you create beautiful, fast apps, with a productive, +extensible and open development model, whether you're targeting iOS or Android, +web, Windows, macOS, Linux or embedding it as the UI toolkit for a platform of +your choice. + +### Beautiful user experiences + +We want to enable designers to deliver their full creative vision without being +forced to water it down due to limitations of the underlying framework. +Flutter's [layered architecture] gives you control over every pixel on the +screen and its powerful compositing capabilities let you overlay and animate +graphics, video, text, and controls without limitation. Flutter includes a full +[set of widgets][widget catalog] that deliver pixel-perfect experiences whether +you're building for iOS ([Cupertino]) or other platforms ([Material]), along with +support for customizing or creating entirely new visual components. + +

Reflectly hero image

+ +### Fast results + +Flutter is fast. It's powered by hardware-accelerated 2D graphics +libraries like [Skia] (which underpins Chrome and Android) and +[Impeller]. We architected Flutter to +support glitch-free, jank-free graphics at the native speed of your device. + +Flutter code is powered by the world-class [Dart platform], which enables +compilation to 32-bit and 64-bit ARM machine code for iOS and Android, +JavaScript and WebAssembly for the web, as well as Intel x64 and ARM +for desktop devices. + +

Dart diagram

+ +### Productive development + +Flutter offers [stateful hot reload][Hot reload], allowing you to make changes to your code +and see the results instantly without restarting your app or losing its state. + +[![Hot reload animation][]][Hot reload] + +### Extensible and open model + +Flutter works with any development tool (or none at all), and also includes +editor plug-ins for both [Visual Studio Code] and [IntelliJ / Android Studio]. +Flutter provides [tens of thousands of packages][Flutter packages] to speed your +development, regardless of your target platform. And accessing other native code +is easy, with support for both FFI ([on Android][Android FFI], [on iOS][iOS FFI], +[on macOS][macOS FFI], and [on Windows][Windows FFI]) as well as +[platform-specific APIs][platform channels]. + +Flutter is a fully open-source project, and we welcome contributions. +Information on how to get started can be found in our +[contributor guide](CONTRIBUTING.md). + +[flutter.dev]: https://flutter.dev +[Discord instructions]: ./docs/contributing/Chat.md +[Discord badge]: https://img.shields.io/discord/608014603317936148?logo=discord +[Twitter handle]: https://img.shields.io/twitter/follow/flutterdev.svg?style=social&label=Follow +[Twitter badge]: https://twitter.com/intent/follow?screen_name=flutterdev +[layered architecture]: https://docs.flutter.dev/resources/inside-flutter +[architectural overview]: https://docs.flutter.dev/resources/architectural-overview +[widget catalog]: https://flutter.dev/widgets/ +[Cupertino]: https://docs.flutter.dev/development/ui/widgets/cupertino +[Material]: https://docs.flutter.dev/development/ui/widgets/material +[Skia]: https://skia.org/ +[Dart platform]: https://dart.dev/ +[Hot reload animation]: https://github.com/flutter/website/blob/main/src/content/assets/images/docs/tools/android-studio/hot-reload.gif?raw=true +[Hot reload]: https://docs.flutter.dev/development/tools/hot-reload +[Visual Studio Code]: https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter +[IntelliJ / Android Studio]: https://plugins.jetbrains.com/plugin/9212-flutter +[Flutter packages]: https://pub.dev/flutter +[Android FFI]: https://docs.flutter.dev/development/platform-integration/android/c-interop +[iOS FFI]: https://docs.flutter.dev/development/platform-integration/ios/c-interop +[macOS FFI]: https://docs.flutter.dev/development/platform-integration/macos/c-interop +[Windows FFI]: https://docs.flutter.dev/development/platform-integration/windows/building#integrating-with-windows +[platform channels]: https://docs.flutter.dev/development/platform-integration/platform-channels +[interop example]: https://github.com/flutter/flutter/tree/main/examples/platform_channel +[Impeller]: https://docs.flutter.dev/perf/impeller diff --git a/flutter/TESTOWNERS b/flutter/TESTOWNERS new file mode 100644 index 00000000..77699dcf --- /dev/null +++ b/flutter/TESTOWNERS @@ -0,0 +1,350 @@ +# Below is a list of Flutter team members' GitHub handles who are +# test owners of this repository. +# +# These owners are mainly team leaders and their sub-teams. Please feel +# free to claim ownership by adding your handle to corresponding tests. +# +# This file will be used as a reference when new flaky bugs are filed and +# the TL will be assigned and the sub-team will be labeled by default +# for further triage. + +## Linux Android DeviceLab tests +/dev/devicelab/bin/tasks/analyzer_benchmark.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/android_choreographer_do_frame_test.dart @reidbaker @flutter/engine +/dev/devicelab/bin/tasks/android_defines_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/android_java11_dependency_smoke_tests.dart @gmackall @flutter/android +/dev/devicelab/bin/tasks/android_java17_dependency_smoke_tests.dart @gmackall @flutter/android +/dev/devicelab/bin/tasks/android_lifecycles_test.dart @reidbaker @flutter/engine +/dev/devicelab/bin/tasks/android_obfuscate_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/android_picture_cache_complexity_scoring_perf__timeline_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/android_stack_size_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/android_view_scroll_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/animated_complex_image_filtered_perf__e2e_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/animated_complex_opacity_perf__e2e_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/animated_image_gc_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/animated_placeholder_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/backdrop_filter_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/basic_material_app_android__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/clipper_cache_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/codegen_integration.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/color_filter_and_fade_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/color_filter_cache_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/color_filter_with_unstable_child_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_android__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/complex_layout_android__scroll_smoothness.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf__devtools_memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_semantics_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cubic_bezier_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cubic_bezier_perf_sksl_warmup__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cull_opacity_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/devtools_profile_start_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/engine_dependency_proxy_test.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fast_scroll_heavy_gridview__memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__back_button_memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__image_cache_memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__memory_nav.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__start_up.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__start_up_delayed.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_hybrid.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_with_semantics.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_android__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_sksl_warmup__transition_perf_e2e.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_v2_chrome_run_test.dart @yjbanov @flutter/web +/dev/devicelab/bin/tasks/flutter_gallery_v2_web_compile_test.dart @yjbanov @flutter/web +/dev/devicelab/bin/tasks/flutter_test_performance.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/frame_policy_delay_test_android.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fullscreen_textfield_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fullscreen_textfield_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/gradle_java8_compile_test.dart @reidbaker @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux__benchmark.dart @bkonyi @flutter/tool +/dev/devicelab/bin/tasks/image_list_jit_reported_duration.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/image_list_reported_duration.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/large_image_changer_perf_android.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/linux_chrome_dev_mode.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/list_text_layout_impeller_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/list_text_layout_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/multi_widget_construction_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/new_gallery__crane_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/old_gallery__transition_perf.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_col_of_alpha_savelayer_rows_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_col_of_rows_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_fade_transition_text_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_grid_of_alpha_savelayers_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_grid_of_opacity_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_one_rect_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/opacity_peephole_opacity_of_grid_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/picture_cache_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/platform_channels_benchmarks.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_impeller__timeline_summary.dart @bdero @flutter/engine +/dev/devicelab/bin/tasks/plugin_dependencies_test.dart @stuartmorgan @flutter/tool +/dev/devicelab/bin/tasks/routing_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/shader_mask_cache_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/spell_check_test_ios.dart @camsim99 @flutter/android +/dev/devicelab/bin/tasks/spell_check_test.dart @camsim99 @flutter/android +/dev/devicelab/bin/tasks/textfield_perf__e2e_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/web_size__compile_test.dart @yjbanov @flutter/web +/dev/devicelab/bin/tasks/wide_gamut_ios.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/animated_advanced_blend_perf__timeline_summary.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/animated_advanced_blend_perf_ios__timeline_summary.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/animated_advanced_blend_perf_opengles__timeline_summary.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/animated_blur_backdrop_filter_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/animated_blur_backdrop_filter_perf_opengles__timeline_summary.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/slider_perf_android.dart @tahatesser @flutter/framework +/dev/devicelab/bin/tasks/draw_vertices_perf_opengles__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_atlas_perf_opengles__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_vertices_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_atlas_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/static_path_tessellation_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/dynamic_path_tessellation_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_impeller__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_impeller_gles__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/rrect_blur_perf__timeline_summary.dart @gaaclarke @flutter/engine + +## Windows Android DeviceLab tests +/dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/channels_integration_test_win.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_win__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/flavors_test_win.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/flutter_gallery_win__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_win__benchmark.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart @yjbanov @flutter/web + +## Mac Android DeviceLab tests +/dev/devicelab/bin/tasks/android_semantics_integration_test.dart @Piinks @flutter/framework +/dev/devicelab/bin/tasks/backdrop_filter_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/channels_integration_test.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/color_filter_and_fade_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout__start_up.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf__memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cubic_bezier_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cubic_bezier_perf_sksl_warmup__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/cull_opacity_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/drive_perf_debug_warning.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/embedded_android_views_integration_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/external_textures_integration_test.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fading_child_animation_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fast_scroll_large_images__memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flavors_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/flutter_view__start_up.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fullscreen_textfield_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/hello_world__memory.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/hello_world_android__compile.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/hello_world_impeller.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/home_scroll_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/hot_mode_dev_cycle__benchmark.dart @bkonyi @flutter/tool +/dev/devicelab/bin/tasks/hybrid_android_views_integration_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/integration_test_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_driver.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_frame_number.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_keyboard_resize.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_screenshot.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_textfield.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/microbenchmarks.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/new_gallery__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/new_gallery_impeller__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/new_gallery_impeller_old_zoom__transition_perf.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/new_gallery_opengles_impeller__transition_perf.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/picture_cache_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/platform_channel_sample_test.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/platform_interaction_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/platform_view__start_up.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/run_release_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/service_extensions_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/textfield_perf__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/tiles_scroll_perf__timeline_summary.dart @jtmcdole @flutter/engine + +## Mac iOS DeviceLab tests +/dev/devicelab/bin/tasks/animated_complex_opacity_perf_ios__e2e_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/animation_with_microtasks_perf_ios__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/basic_material_app_ios__compile.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/channels_integration_test_ios.dart @jmagman @flutter/engine +/dev/devicelab/bin/tasks/codegen_integration_mac.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/color_filter_and_fade_perf_ios__e2e_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_ios__start_up.dart @louisehsu @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_bad_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/cubic_bezier_perf_ios_sksl_warmup__timeline_summary.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/external_textures_integration_test_ios.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flavors_test_ios.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @jmagman @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @louisehsu @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_ios_sksl_warmup__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/flutter_view_ios__start_up.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @louisehsu @flutter/engine +/dev/devicelab/bin/tasks/hello_world_ios__compile.dart @jmagman @flutter/engine +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios__benchmark.dart @louisehsu @flutter/tool +# TODO(vashworth): Remove once https://github.com/flutter/flutter/issues/142305 is fixed. +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios__benchmark_no_dds.dart @louisehsu @flutter/tool +/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/integration_test_test_ios.dart @jmagman @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart @louisehsu @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_ios_frame_number.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart @louisehsu @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart @louisehsu @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart @louisehsu @flutter/tool +/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/ios_defines_test.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/ios_picture_cache_complexity_scoring_perf__timeline_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/ios_platform_view_tests.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @louisehsu @flutter/engine +/dev/devicelab/bin/tasks/native_assets_android.dart @dcharkes @flutter/android +/dev/devicelab/bin/tasks/native_assets_ios.dart @dcharkes @flutter/ios +/dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios +/dev/devicelab/bin/tasks/new_gallery_ios__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/new_gallery_skia_ios__transition_perf.dart @jtmcdole @flutter/engine +/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_channel_sample_test_swift.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_channels_benchmarks_ios.dart @gaaclarke @flutter/engine +/dev/devicelab/bin/tasks/platform_interaction_test_ios.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_view_ios__start_up.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/platform_views_scroll_perf_ad_banners__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_bottom_ad_banner__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_non_intersecting_impeller_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/post_backdrop_filter_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/route_test_ios.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/tiles_scroll_perf_ios__timeline_summary.dart @hellohuanlin @flutter/engine +/dev/devicelab/bin/tasks/very_long_picture_scrolling_perf_ios__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/animated_blur_backdrop_filter_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_points_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_vertices_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/draw_atlas_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/static_path_tessellation_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/dynamic_path_tessellation_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/rrect_blur_perf_ios__timeline_summary.dart @gaaclarke @flutter/engine + +## Host only DeviceLab tests +/dev/devicelab/bin/tasks/animated_complex_opacity_perf_macos__e2e_summary.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/android_release_builds_exclude_dev_dependencies_test.dart @camsim99 @flutter/android +/dev/devicelab/bin/tasks/basic_material_app_macos__compile.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/build_aar_module_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/build_ios_framework_module_test.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/channels_integration_test_macos.dart @gaaclarke @flutter/desktop +/dev/devicelab/bin/tasks/complex_layout_macos__compile.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/complex_layout_macos__start_up.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_macos__timeline_summary.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/complex_layout_win_desktop__compile.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/complex_layout_win_desktop__start_up.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/dart_plugin_registry_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/entrypoint_dart_registrant.dart @aaclarke @flutter/plugin +/dev/devicelab/bin/tasks/flavors_test_macos.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/flutter_gallery_macos__compile.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/flutter_gallery_macos__start_up.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/flutter_gallery_win_desktop__compile.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/flutter_gallery_win_desktop__start_up.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/flutter_tool_startup.dart @jensjoha @flutter/tool +/dev/devicelab/bin/tasks/flutter_tool_startup__linux.dart @jensjoha @flutter/tool +/dev/devicelab/bin/tasks/flutter_tool_startup__macos.dart @jensjoha @flutter/tool +/dev/devicelab/bin/tasks/flutter_tool_startup__windows.dart @jensjoha @flutter/tool +/dev/devicelab/bin/tasks/flutter_view_macos__start_up.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/flutter_view_win_desktop__start_up.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/gradle_desugar_classes_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/hello_world_macos__compile.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/hello_world_win_desktop__compile.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios_simulator.dart @louisehsu @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_macos_target__benchmark.dart @cbracken @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_win_target__benchmark.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/integration_ui_test_test_macos.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/module_custom_host_app_name_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/build_android_host_app_with_module_aar.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/build_android_host_app_with_module_source.dart @gmackall @flutter/android +/dev/devicelab/bin/tasks/module_test_ios.dart @jmagman @flutter/tool +/dev/devicelab/bin/tasks/native_assets_ios_simulator.dart @dcharkes @flutter/ios +/dev/devicelab/bin/tasks/native_ui_tests_macos.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/platform_channel_sample_test_macos.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/platform_channel_sample_test_windows.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/platform_view_macos__start_up.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/platform_view_win_desktop__start_up.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/plugin_lint_mac.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/plugin_test.dart @stuartmorgan @flutter/plugin +/dev/devicelab/bin/tasks/plugin_test_ios.dart @stuartmorgan @flutter/ios +/dev/devicelab/bin/tasks/plugin_test_linux.dart @stuartmorgan @flutter/desktop +/dev/devicelab/bin/tasks/plugin_test_macos.dart @cbracken @flutter/desktop +/dev/devicelab/bin/tasks/plugin_test_windows.dart @loic-sharma @flutter/desktop +/dev/devicelab/bin/tasks/run_debug_test_android.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/run_debug_test_android.dart @andrewkolos @flutter/tool +/dev/devicelab/bin/tasks/run_debug_test_linux.dart @loic-sharma @flutter/tool +/dev/devicelab/bin/tasks/run_debug_test_macos.dart @cbracken @flutter/tool +/dev/devicelab/bin/tasks/run_debug_test_windows.dart @loic-sharma @flutter/tool +/dev/devicelab/bin/tasks/run_release_test_linux.dart @loic-sharma @flutter/tool +/dev/devicelab/bin/tasks/run_release_test_macos.dart @cbracken @flutter/tool +/dev/devicelab/bin/tasks/run_release_test_windows.dart @loic-sharma @flutter/tool +/dev/devicelab/bin/tasks/technical_debt__cost.dart @Piinks @flutter/framework +/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @yjbanov @flutter/web +/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @eyebrowsoffire @flutter/web +/dev/devicelab/bin/tasks/web_benchmarks_skwasm_st.dart @eyebrowsoffire @flutter/web +/dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop +/dev/devicelab/bin/tasks/windows_desktop_impeller.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/mac_desktop_impeller.dart @jonahwilliams @flutter/engine +/dev/devicelab/bin/tasks/linux_desktop_impeller.dart @jonahwilliams @flutter/engine + +## Host only framework tests +# Linux docs_deploy_beta +# Linux docs_deploy_stable +# Linux docs_publish +/dev/bots/docs.sh @Piinks @flutter/framework +# Linux packages_autoroller +/dev/conductor/core @christopherfujino @flutter/tool +# Linux web_e2e_test +/dev/integration_tests/web_e2e_tests @yjbanov @flutter/web +# Linux web_smoke_test +/examples/hello_world/test_driver/smoke_web_engine.dart @yjbanov @flutter/web +# Linux android views +/dev/integration_tests/android_views @gmackall @flutter/android +# Linux deferred components +/dev/integration_tests/deferred_components_test @gmackall @flutter/android + +## Firebase tests +/dev/integration_tests/release_smoke_test @reidbaker @flutter/android + +## Shards tests +# TODO(keyonghan): add files/paths for below framework host only testss. +# https://github.com/flutter/flutter/issues/82068 +# +# analyze @Piinks @flutter/framework +# build_tests @andrewkolos @flutter/tool +# ci_yaml flutter roller @keyonghan @flutter/infra +# coverage @goderbauer @flutter/infra +# customer_testing @Piinks @flutter/framework +# docs @Piinks @flutter/framework +# flutter_driver_android_test @matanlurey @johnmccutchan +# flutter_packaging @christopherfujino @flutter/infra +# flutter_plugins @stuartmorgan @flutter/plugin +# framework_tests @Piinks @flutter/framework +# fuchsia_precache @bkonyi @flutter/tool +# realm_checker @eyebrowsoffire @flutter/tool +# skp_generator @Hixie +# test_ownership @keyonghan +# tool_host_cross_arch_tests @andrewkolos @flutter/tool +# tool_integration_tests @bkonyi @flutter/tool +# android_preview_tool_integration_tests @gmackall @flutter/android +# android_java11_tool_integration_tests @gmackall @flutter/android +# tool_tests @andrewkolos @flutter/tool +# verify_binaries_codesigned @cbracken @flutter/releases +# web_canvaskit_tests @yjbanov @flutter/web +# web_integration_tests @yjbanov @flutter/web +# web_long_running_tests @yjbanov @flutter/web +# web_tests @yjbanov @flutter/web +# web_skwasm_tests @eyebrowsoffire @flutter/web +# web_tool_tests @yjbanov @flutter/tool diff --git a/flutter/analysis_options.yaml b/flutter/analysis_options.yaml new file mode 100644 index 00000000..fd050940 --- /dev/null +++ b/flutter/analysis_options.yaml @@ -0,0 +1,269 @@ +# Specify analysis options. +# +# For a list of lints, see: https://dart.dev/tools/linter-rules +# For guidelines on configuring static analysis, see: +# https://dart.dev/tools/analysis +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - https://github.com/flutter/packages/blob/main/analysis_options.yaml +# +# This file contains the analysis options used for code in the flutter/flutter +# repository. + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + errors: + # allow deprecated members (we do this because otherwise we have to annotate + # every member in every test, assert, etc, when we or the Dart SDK deprecates + # something (https://github.com/flutter/flutter/issues/143312) + deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore + exclude: + - "bin/cache/**" + # Ignore protoc generated files + - "dev/conductor/lib/proto/*" + - "engine/**" + +formatter: + page_width: 100 + +linter: + rules: + # This list is derived from the list of all available lints located at + # https://github.com/dart-lang/sdk/blob/main/pkg/linter/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_specify_types + # - always_use_package_imports # we do this commonly + - annotate_overrides + - annotate_redeclares + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # blocked on https://github.com/dart-lang/linter/issues/3023 + # - avoid_catching_errors # blocked on https://github.com/dart-lang/linter/issues/4998 + # - avoid_classes_with_only_static_members # we do this commonly for `abstract final class`es + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + # - avoid_final_parameters # incompatible with prefer_final_parameters + - avoid_function_literals_in_foreach_calls + # - avoid_futureor_void # not yet tested + # - avoid_implementing_value_types # see https://github.com/dart-lang/linter/issues/4558 + - avoid_init_to_null + - avoid_js_rounded_ints + # - avoid_multiple_declarations_per_line # seems to be a stylistic choice we don't subscribe to + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # would have been nice to enable this but by now there's too many places that break it + - avoid_print + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + - avoid_returning_null_for_void + # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives + - avoid_setters_without_getters + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + - avoid_unnecessary_containers + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # doesn't match the typical style of this repo + - cast_nullable_to_non_nullable + # - close_sinks # not reliable enough + - collection_methods_unrelated_type + - combinators_ordering + # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 + - conditional_uri_does_not_exist + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - dangling_library_doc_comments + - depend_on_referenced_packages + - deprecated_consistency + # - deprecated_member_use_from_same_package # we allow self-references to deprecated members + # - diagnostic_describe_all_properties # enabled only at the framework level (packages/flutter/lib) + - directives_ordering + # - discarded_futures # too many false positives, similar to unawaited_futures + # - do_not_use_environment # there are appropriate times to use the environment, especially in our tests and build logic + # - document_ignores # not yet tested + - empty_catches + - empty_constructor_bodies + - empty_statements + - eol_at_end_of_file + - exhaustive_cases + - file_names + - flutter_style_todos + - hash_and_equals + - implementation_imports + - implicit_call_tearoffs + - implicit_reopen + - invalid_case_patterns + - invalid_runtime_check_with_js_interop_types + # - join_return_with_assignment # not required by flutter style + - leading_newlines_in_multiline_strings + - library_annotations + - library_names + - library_prefixes + - library_private_types_in_public_api + # - lines_longer_than_80_chars # not required by flutter style + - literal_only_boolean_expressions + # - matching_super_parameters # blocked on https://github.com/dart-lang/language/issues/2509 + - missing_code_block_language_in_doc_comment + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_default_cases + - no_duplicate_case_values + - no_leading_underscores_for_library_prefixes + - no_leading_underscores_for_local_identifiers + - no_literal_bool_comparisons + - no_logic_in_create_state + # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - no_self_assignments + - no_wildcard_variable_uses + - non_constant_identifier_names + - noop_primitive_operations + - null_check_on_nullable_type_parameter + - null_closures + # - omit_local_variable_types # opposite of always_specify_types + # - omit_obvious_local_variable_types # not yet tested + # - omit_obvious_property_types # not yet tested + # - one_member_abstracts # too many false positives + - only_throw_errors # this does get disabled in a few places where we have legacy code that uses strings et al + - overridden_fields + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not required by flutter style + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # far too many false positives + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + # - prefer_expression_function_bodies # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + # - prefer_final_parameters # adds too much verbosity + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # conflicts with ./docs/contributing/Style-guide-for-Flutter-repo.md#use-double-literals-for-double-constants + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + # - prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere + - prefer_null_aware_operators + - prefer_relative_imports + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + # - require_trailing_commas # would be nice, but requires a lot of manual work: 10,000+ code locations would need to be reformatted by hand after bulk fix is applied + - secure_pubspec_urls + - sized_box_for_whitespace + - sized_box_shrink_expand + - slash_for_doc_comments + - sort_child_properties_last + - sort_constructors_first + # - sort_pub_dependencies # prevents separating pinned transitive dependencies + - sort_unnamed_constructors_first + # - specify_nonobvious_local_variable_types # not yet tested + # - specify_nonobvious_property_types # not yet tested + - strict_top_level_inference + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + - type_literal_in_constant_pattern + # - unawaited_futures # too many false positives, especially with the way AnimationController works + # - unintended_html_in_doc_comment # blocked on https://github.com/dart-lang/linter/issues/5065 + # - unnecessary_async # not yet tested + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_breaks + - unnecessary_const + - unnecessary_constructor_name + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_late + - unnecessary_library_directive + # - unnecessary_library_name # blocked on https://github.com/dart-lang/dartdoc/issues/3882 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_aware_operator_on_extension_on_nullable + - unnecessary_null_checks + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + # - unnecessary_raw_strings # what's "necessary" is a matter of opinion; consistency across strings can help readability more than this lint + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unnecessary_to_list_in_spreads + - unnecessary_underscores + - unreachable_from_main + - unrelated_type_equality_checks + # - unsafe_variance # not yet tested + - use_build_context_synchronously + - use_colored_box + # - use_decorated_box # leads to bugs: DecoratedBox and Container are not equivalent (Container inserts extra padding) + - use_enums + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_if_null_to_convert_nulls_to_bools + - use_is_even_rather_than_modulo + - use_key_in_widget_constructors + - use_late_for_private_fields_and_variables + - use_named_constants + - use_raw_strings + - use_rethrow_when_possible + - use_setters_to_change_properties + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + - use_string_in_part_of_directives + - use_super_parameters + - use_test_throws_matchers + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - use_truncating_division + - valid_regexps + - void_checks diff --git a/flutter/dartdoc_options.yaml b/flutter/dartdoc_options.yaml new file mode 100644 index 00000000..54a2a69e --- /dev/null +++ b/flutter/dartdoc_options.yaml @@ -0,0 +1,42 @@ +# This file is used by dartdoc when generating API documentation for Flutter. +dartdoc: + # Before you can run dartdoc, the snippets tool needs to be + # activated with "pub global activate snippets " + # The dev/bots/docs.sh script does this automatically. + tools: + snippet: + command: ["bin/cache/artifacts/snippets/snippets", "--output-directory=doc/snippets", "--type=snippet"] + description: "Creates sample code documentation output from embedded documentation samples." + sample: + command: ["bin/cache/artifacts/snippets/snippets", "--output-directory=doc/snippets", "--type=sample"] + description: "Creates full application sample code documentation output from embedded documentation samples." + dartpad: + command: ["bin/cache/artifacts/snippets/snippets", "--output-directory=doc/snippets", "--type=dartpad"] + description: "Creates full application sample code documentation output from embedded documentation samples and displays it in an embedded DartPad." + errors: + ## Default errors of dartdoc: + - duplicate-file + - invalid-parameter + - tool-error + - unresolved-export + ## Warnings that are elevated to errors: + - ambiguous-doc-reference + - ambiguous-reexport + - broken-link + - category-order-gives-missing-package-name + - deprecated + - ignored-canonical-for + - missing-example-file + - missing-from-search-index + - no-canonical-found + - no-documentable-libraries + - no-library-level-docs + - orphaned-file + - reexported-private-api-across-packages + - unknown-file + - unknown-html-fragment + - unknown-macro + - unresolved-doc-reference + ## Ignores that are elevated to errors: + # - type-as-html # broken, https://github.com/dart-lang/dartdoc/issues/3545 + - missing-constant-constructor diff --git a/flutter/dev/README.md b/flutter/dev/README.md new file mode 100644 index 00000000..e48142ea --- /dev/null +++ b/flutter/dev/README.md @@ -0,0 +1,7 @@ +This directory contains tools and resources that the Flutter team uses +during the development of the framework. The tools in this directory +should not be necessary for developing Flutter applications, though of +course, they may be interesting if you are curious. + +The tests in this directory are run in the `framework_tests_misc-*` +shards. diff --git a/flutter/dev/a11y_assessments/README.md b/flutter/dev/a11y_assessments/README.md new file mode 100644 index 00000000..b6400496 --- /dev/null +++ b/flutter/dev/a11y_assessments/README.md @@ -0,0 +1,33 @@ +# a11y_assessments + +This app is used for internal testing. + +## Release a new version for Android + +pre-requisite: This can and should only be done by a googler and you must also +be in the flutter.dev play console account. + +1. Follow https://docs.flutter.dev/deployment/android to create a keystore file if you don't already +have one. + +2. Bump the pubspec.yaml version + +3. Create a key.properties file in `android/` directory following this format. +``` +storePassword= +keyPassword= +keyAlias=upload +storeFile= +``` + +4. Run `flutter build appbundle` and upload the artifact to play console + +## Release a new version for iOS + +pre-requisite: This can and should only be done by a googler and you must also +be in the FLUTTER.IO LLC developer account with iOS distribution permission. + +1. Bump the pubspec.yaml version +2. Run `flutter build ipa` and upload the artifact to app store using transporter or other tools. +For more information, see https://docs.flutter.dev/deployment/ios. +3. Once the app is in TestFlight, add appropriate testers to the app so they can start testing. \ No newline at end of file diff --git a/flutter/dev/a11y_assessments/analysis_options.yaml b/flutter/dev/a11y_assessments/analysis_options.yaml new file mode 100644 index 00000000..f04c6cf0 --- /dev/null +++ b/flutter/dev/a11y_assessments/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml diff --git a/flutter/dev/a11y_assessments/android/app/build.gradle b/flutter/dev/a11y_assessments/android/app/build.gradle new file mode 100644 index 00000000..d040546c --- /dev/null +++ b/flutter/dev/a11y_assessments/android/app/build.gradle @@ -0,0 +1,93 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + +android { + namespace "com.example.a11y_assessments" + compileSdk flutter.compileSdkVersion + + // Flutter's CI installs the NDK at a non-standard path. + // This non-standard structure is initially created by + // https://github.com/flutter/engine/blob/3.27.0/tools/android_sdk/create_cipd_packages.sh. + String systemNdkPath = System.getenv("ANDROID_NDK_PATH") + if (systemNdkPath != null) { + ndkVersion = flutter.ndkVersion + ndkPath = systemNdkPath + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "dev.flutter.a11yassessments" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.release + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/flutter/dev/a11y_assessments/android/app/src/debug/AndroidManifest.xml b/flutter/dev/a11y_assessments/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..e00f903e --- /dev/null +++ b/flutter/dev/a11y_assessments/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/flutter/dev/a11y_assessments/android/app/src/main/res/drawable-v21/launch_background.xml b/flutter/dev/a11y_assessments/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..9f19e2f9 --- /dev/null +++ b/flutter/dev/a11y_assessments/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/flutter/dev/a11y_assessments/android/build.gradle b/flutter/dev/a11y_assessments/android/build.gradle new file mode 100644 index 00000000..bedac9d2 --- /dev/null +++ b/flutter/dev/a11y_assessments/android/build.gradle @@ -0,0 +1,34 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the build.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' + +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') + dependencyLocking { + ignoredDependencies.add('io.flutter:*') + lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") + if (!project.hasProperty('local-engine-repo')) { + lockAllConfigurations() + } + } +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/flutter/dev/a11y_assessments/android/buildscript-gradle.lockfile b/flutter/dev/a11y_assessments/android/buildscript-gradle.lockfile new file mode 100644 index 00000000..291d301d --- /dev/null +++ b/flutter/dev/a11y_assessments/android/buildscript-gradle.lockfile @@ -0,0 +1,153 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.databinding:databinding-common:8.7.0=classpath +androidx.databinding:databinding-compiler-common:8.7.0=classpath +com.android.application:com.android.application.gradle.plugin:8.7.0=classpath +com.android.databinding:baseLibrary:8.7.0=classpath +com.android.tools.analytics-library:crash:31.7.0=classpath +com.android.tools.analytics-library:protos:31.7.0=classpath +com.android.tools.analytics-library:shared:31.7.0=classpath +com.android.tools.analytics-library:tracker:31.7.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:8.7.0-12006047=classpath +com.android.tools.build:aaptcompiler:8.7.0=classpath +com.android.tools.build:apksig:8.7.0=classpath +com.android.tools.build:apkzlib:8.7.0=classpath +com.android.tools.build:builder-model:8.7.0=classpath +com.android.tools.build:builder-test-api:8.7.0=classpath +com.android.tools.build:builder:8.7.0=classpath +com.android.tools.build:bundletool:1.17.1=classpath +com.android.tools.build:gradle-api:8.7.0=classpath +com.android.tools.build:gradle-settings-api:8.7.0=classpath +com.android.tools.build:gradle:8.7.0=classpath +com.android.tools.build:manifest-merger:31.7.0=classpath +com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath +com.android.tools.ddms:ddmlib:31.7.0=classpath +com.android.tools.layoutlib:layoutlib-api:31.7.0=classpath +com.android.tools.lint:lint-model:31.7.0=classpath +com.android.tools.lint:lint-typedef-remover:31.7.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=classpath +com.android.tools:annotations:31.7.0=classpath +com.android.tools:common:31.7.0=classpath +com.android.tools:dvlib:31.7.0=classpath +com.android.tools:repository:31.7.0=classpath +com.android.tools:sdk-common:31.7.0=classpath +com.android.tools:sdklib:31.7.0=classpath +com.android:signflinger:8.7.0=classpath +com.android:zipflinger:8.7.0=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.17.0=classpath +com.google.auto.value:auto-value-annotations:1.6.2=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.10.1=classpath +com.google.crypto.tink:tink:1.7.0=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.18.0=classpath +com.google.flatbuffers:flatbuffers-java:1.12.0=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:32.0.1-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:2.8=classpath +com.google.jimfs:jimfs:1.1=classpath +com.google.protobuf:protobuf-java-util:3.22.3=classpath +com.google.protobuf:protobuf-java:3.22.3=classpath +com.google.testing.platform:core-proto:0.0.9-alpha02=classpath +com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath +com.squareup:javapoet:1.10.0=classpath +com.squareup:javawriter:2.5.0=classpath +com.sun.activation:javax.activation:1.2.0=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath +commons-io:commons-io:2.13.0=classpath +commons-logging:commons-logging:1.2=classpath +io.grpc:grpc-api:1.57.0=classpath +io.grpc:grpc-context:1.57.0=classpath +io.grpc:grpc-core:1.57.0=classpath +io.grpc:grpc-netty:1.57.0=classpath +io.grpc:grpc-protobuf-lite:1.57.0=classpath +io.grpc:grpc-protobuf:1.57.0=classpath +io.grpc:grpc-stub:1.57.0=classpath +io.netty:netty-buffer:4.1.93.Final=classpath +io.netty:netty-codec-http2:4.1.93.Final=classpath +io.netty:netty-codec-http:4.1.93.Final=classpath +io.netty:netty-codec-socks:4.1.93.Final=classpath +io.netty:netty-codec:4.1.93.Final=classpath +io.netty:netty-common:4.1.93.Final=classpath +io.netty:netty-handler-proxy:4.1.93.Final=classpath +io.netty:netty-handler:4.1.93.Final=classpath +io.netty:netty-resolver:4.1.93.Final=classpath +io.netty:netty-transport-native-unix-common:4.1.93.Final=classpath +io.netty:netty-transport:4.1.93.Final=classpath +io.perfmark:perfmark-api:0.26.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath +javax.inject:javax.inject:1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath +net.sf.jopt-simple:jopt-simple:4.9=classpath +net.sf.kxml:kxml2:2.3.0=classpath +org.apache.commons:commons-compress:1.21=classpath +org.apache.httpcomponents:httpclient:4.5.14=classpath +org.apache.httpcomponents:httpcore:4.4.16=classpath +org.apache.httpcomponents:httpmime:4.5.6=classpath +org.bitbucket.b_c:jose4j:0.9.5=classpath +org.bouncycastle:bcpkix-jdk18on:1.77=classpath +org.bouncycastle:bcprov-jdk18on:1.77=classpath +org.bouncycastle:bcutil-jdk18on:1.77=classpath +org.checkerframework:checker-qual:3.33.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.23=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath +org.jdom:jdom2:2.0.6=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.8.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.8.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.8.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.9.20=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.8.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.8.10=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:23.0.0=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.6=classpath +org.ow2.asm:asm-commons:9.6=classpath +org.ow2.asm:asm-tree:9.6=classpath +org.ow2.asm:asm-util:9.6=classpath +org.ow2.asm:asm:9.6=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +empty= diff --git a/flutter/dev/a11y_assessments/android/gradle.properties b/flutter/dev/a11y_assessments/android/gradle.properties new file mode 100644 index 00000000..25971708 --- /dev/null +++ b/flutter/dev/a11y_assessments/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/flutter/dev/a11y_assessments/android/project-app.lockfile b/flutter/dev/a11y_assessments/android/project-app.lockfile new file mode 100644 index 00000000..2ef2994f --- /dev/null +++ b/flutter/dev/a11y_assessments/android/project-app.lockfile @@ -0,0 +1,145 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.8.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.4.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.8.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.8.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.13.1=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugImplementationDependenciesMetadata,profileApiDependenciesMetadata,profileImplementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseImplementationDependenciesMetadata +androidx.core:core:1.13.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.7.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.interpolator:interpolator:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.7.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.3.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window.extensions.core:core:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.2.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.ddms:ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.emulator:proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-gradle:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-additional-test-output:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-apk-installer:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-coverage:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-emulator-control:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-logcat:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-host-retention:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:android-test-plugin-result-listener-gradle:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:utp-common:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools:annotations:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:common:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.getkeepsafe.relinker:relinker:1.4.5=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.android:annotations:4.1.1.4=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.api.grpc:proto-google-common-protos:2.17.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.gson:gson:2.10.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.crypto.tink:tink:1.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.errorprone:error_prone_annotations:2.18.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:failureaccess:1.0.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:guava:32.0.1-jre=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:listenablefuture:1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.j2objc:j2objc-annotations:2.8=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.protobuf:protobuf-java:3.22.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-device-provider-local:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha02=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:core:0.0.9-alpha02=_internal-unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +commons-io:commons-io:2.13.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +io.grpc:grpc-api:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-context:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-core:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-netty:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf-lite:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-stub:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-buffer:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http2:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-socks:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler-proxy:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-resolver:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport-native-unix-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.perfmark:perfmark-api:0.26.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib +org.checkerframework:checker-qual:3.33.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.codehaus.mojo:animal-sniffer-annotations:1.23=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.8.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.8.10=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.8.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.10=apiDependenciesMetadata,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.10=apiDependenciesMetadata,implementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.10=apiDependenciesMetadata,implementationDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib:1.8.10=apiDependenciesMetadata,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.8.22=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:atomicfu:0.20.2=debugApiDependenciesMetadata,debugImplementationDependenciesMetadata,profileApiDependenciesMetadata,profileImplementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseImplementationDependenciesMetadata +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,apiDependenciesMetadata,debugApiDependenciesMetadata,debugImplementationDependenciesMetadata,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileApiDependenciesMetadata,profileImplementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseImplementationDependenciesMetadata +org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/flutter/dev/a11y_assessments/android/settings.gradle b/flutter/dev/a11y_assessments/android/settings.gradle new file mode 100644 index 00000000..4a155b35 --- /dev/null +++ b/flutter/dev/a11y_assessments/android/settings.gradle @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +buildscript { + dependencyLocking { + lockFile = file("${rootProject.projectDir}/buildscript-gradle.lockfile") + lockAllConfigurations() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false +} + +include ":app" diff --git a/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/flutter/dev/a11y_assessments/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/flutter/dev/a11y_assessments/ios/Runner/Info.plist b/flutter/dev/a11y_assessments/ios/Runner/Info.plist new file mode 100644 index 00000000..74b9bb41 --- /dev/null +++ b/flutter/dev/a11y_assessments/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + A11y Assessments + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + a11y_assessments + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/flutter/dev/a11y_assessments/ios/Runner/Runner-Bridging-Header.h b/flutter/dev/a11y_assessments/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..95b7baf3 --- /dev/null +++ b/flutter/dev/a11y_assessments/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1,5 @@ +// Copyright 2014 The Flutter 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 "GeneratedPluginRegistrant.h" diff --git a/flutter/dev/a11y_assessments/ios/RunnerTests/RunnerTests.swift b/flutter/dev/a11y_assessments/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..6bfa5737 --- /dev/null +++ b/flutter/dev/a11y_assessments/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter 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 Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/flutter/dev/a11y_assessments/lib/common/dynamic_title.dart b/flutter/dev/a11y_assessments/lib/common/dynamic_title.dart new file mode 100644 index 00000000..41b1679f --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/common/dynamic_title.dart @@ -0,0 +1,17 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +class DynamicTitle extends StatelessWidget { + const DynamicTitle({super.key, required this.title, required this.child}); + + final String title; + final Widget child; + + @override + Widget build(BuildContext context) { + return Title(title: title, color: Theme.of(context).colorScheme.primary, child: child); + } +} diff --git a/flutter/dev/a11y_assessments/lib/main.dart b/flutter/dev/a11y_assessments/lib/main.dart new file mode 100644 index 00000000..1990c0f6 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/main.dart @@ -0,0 +1,104 @@ +// Copyright 2014 The Flutter 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:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'use_cases/use_cases.dart'; + +void main() { + runApp(const App()); + if (kIsWeb) { + SemanticsBinding.instance.ensureSemantics(); + } +} + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) { + final ThemeData lightTheme = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xff6750a4), + contrastLevel: MediaQuery.highContrastOf(context) ? 1.0 : 0.0, + ), + ); + final ThemeData darkTheme = ThemeData( + colorScheme: ColorScheme.fromSeed( + brightness: Brightness.dark, + seedColor: const Color(0xff6750a4), + contrastLevel: MediaQuery.highContrastOf(context) ? 1.0 : 0.0, + ), + ); + + final Map routes = Map.fromEntries( + useCases.map( + (UseCase useCase) => MapEntry( + useCase.route, + (BuildContext context) => useCase.buildWithTitle(context), + ), + ), + ); + + return MaterialApp( + title: 'Accessibility Assessments Home Page', + theme: lightTheme, + darkTheme: darkTheme, + routes: {'/': (_) => const HomePage(), ...routes}, + ); + } +} + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => HomePageState(); +} + +class HomePageState extends State { + final ScrollController scrollController = ScrollController(); + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + Widget _buildUseCaseItem(int index, UseCase useCase) { + return Padding( + padding: const EdgeInsets.all(10), + child: Builder( + builder: (BuildContext context) { + return TextButton( + key: Key(useCase.name), + onPressed: + () => Navigator.of(context).pushNamed(useCase.route, arguments: useCase.name), + child: Text(useCase.name), + ); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Semantics(headingLevel: 1, child: const Text('Accessibility Assessments')), + ), + body: Center( + child: ListView( + controller: scrollController, + children: List.generate( + useCases.length, + (int index) => _buildUseCaseItem(index, useCases[index]), + ), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/action_chip.dart b/flutter/dev/a11y_assessments/lib/use_cases/action_chip.dart new file mode 100644 index 00000000..04fcbd6b --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/action_chip.dart @@ -0,0 +1,54 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class ActionChipUseCase extends UseCase { + @override + String get name => 'ActionChip'; + + @override + String get route => '/action-chip'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + bool favorite = false; + + String pageTitle = getUseCaseName(ActionChipUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ActionChip( + avatar: const Icon(Icons.favorite), + label: const Text('Action'), + onPressed: () {}, + ), + const ActionChip(avatar: Icon(Icons.favorite), label: Text('Action')), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/app_bar.dart b/flutter/dev/a11y_assessments/lib/use_cases/app_bar.dart new file mode 100644 index 00000000..0708485b --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/app_bar.dart @@ -0,0 +1,115 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'use_cases.dart'; + +class AppBarUseCase extends UseCase { + @override + String get name => 'AppBar'; + + @override + String get route => '/app-bar'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + int currentIndex = 0; + + void _onChanged(int? value) { + setState(() { + currentIndex = value!; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: + [ + AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: const Text('AppBar')), + ), + AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: const Text('AppBar')), + actions: [ + IconButton( + icon: const Icon(Icons.add_alert), + tooltip: 'Show Snackbar', + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('This is a snackbar'))); + }, + ), + IconButton( + icon: const Icon(Icons.navigate_next), + tooltip: 'Go to the next page', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: const Text('Next Page')), + ), + body: const Center( + child: Text('This is the next page', style: TextStyle(fontSize: 24)), + ), + ); + }, + ), + ); + }, + ), + ], + ), + AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: const Text('AppBar')), + actions: [ + TextButton(onPressed: () {}, child: const Text('Action 1')), + TextButton(onPressed: () {}, child: const Text('Action 2')), + ], + ), + ][currentIndex], + body: ListView( + children: [ + RadioListTile( + title: const Text('1. Simple app bar'), + value: 0, + groupValue: currentIndex, + onChanged: _onChanged, + ), + RadioListTile( + title: const Text('2. App bar with actions'), + value: 1, + groupValue: currentIndex, + onChanged: _onChanged, + ), + RadioListTile( + title: const Text('3. App bar with text buttons'), + value: 2, + groupValue: currentIndex, + onChanged: _onChanged, + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/auto_complete.dart b/flutter/dev/a11y_assessments/lib/use_cases/auto_complete.dart new file mode 100644 index 00000000..bc0f6843 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/auto_complete.dart @@ -0,0 +1,75 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class AutoCompleteUseCase extends UseCase { + @override + String get name => 'AutoComplete'; + + @override + String get route => '/auto-complete'; + + @override + Widget build(BuildContext context) => const _MainWidget(); +} + +class _MainWidget extends StatefulWidget { + const _MainWidget(); + + @override + State<_MainWidget> createState() => _MainWidgetState(); +} + +class _MainWidgetState extends State<_MainWidget> { + static const List _kOptions = ['apple', 'banana', 'lemon']; + + static Widget _fieldViewBuilder( + BuildContext context, + TextEditingController textEditingController, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { + return TextFormField( + focusNode: focusNode, + controller: textEditingController, + onFieldSubmitted: (String value) { + onFieldSubmitted(); + }, + ); + } + + String pageTitle = getUseCaseName(AutoCompleteUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Type below to autocomplete the following possible results: $_kOptions.'), + Autocomplete( + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text == '') { + return const Iterable.empty(); + } + return _kOptions.where((String option) { + return option.contains(textEditingValue.text.toLowerCase()); + }); + }, + fieldViewBuilder: _fieldViewBuilder, + ), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/badge.dart b/flutter/dev/a11y_assessments/lib/use_cases/badge.dart new file mode 100644 index 00000000..f42ae217 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/badge.dart @@ -0,0 +1,46 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class BadgeUseCase extends UseCase { + @override + String get name => 'Badge'; + + @override + String get route => '/badge'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + String pageTitle = getUseCaseName(BadgeUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: const Center( + child: Badge( + label: Text('5', semanticsLabel: '5 new messages', style: TextStyle(color: Colors.white)), + backgroundColor: Colors.green, + child: Icon(Icons.mail, semanticLabel: 'Messages'), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/card.dart b/flutter/dev/a11y_assessments/lib/use_cases/card.dart new file mode 100644 index 00000000..aa8696a9 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/card.dart @@ -0,0 +1,49 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class CardUseCase extends UseCase { + @override + String get name => 'Card'; + + @override + String get route => '/card'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + bool favorite = false; + + String pageTitle = getUseCaseName(CardUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Card(child: Padding(padding: EdgeInsets.all(16), child: Text('Card'))), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart b/flutter/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart new file mode 100644 index 00000000..7ee015e1 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/check_box_list_tile.dart @@ -0,0 +1,59 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class CheckBoxListTile extends UseCase { + @override + String get name => 'CheckBoxListTile'; + + @override + String get route => '/check-box-list-tile'; + + @override + Widget build(BuildContext context) => _MainWidget(); +} + +class _MainWidget extends StatefulWidget { + @override + State<_MainWidget> createState() => _MainWidgetState(); +} + +class _MainWidgetState extends State<_MainWidget> { + bool _checked = false; + + String pageTitle = getUseCaseName(CheckBoxListTile()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo'))), + body: ListView( + children: [ + CheckboxListTile( + value: _checked, + onChanged: (bool? value) { + setState(() { + _checked = value!; + }); + }, + title: const Text('a check box list title'), + ), + CheckboxListTile( + value: _checked, + onChanged: (bool? value) { + setState(() { + _checked = value!; + }); + }, + title: const Text('a disabled check box list title'), + enabled: false, + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/date_picker.dart b/flutter/dev/a11y_assessments/lib/use_cases/date_picker.dart new file mode 100644 index 00000000..b5474880 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/date_picker.dart @@ -0,0 +1,52 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class DatePickerUseCase extends UseCase { + @override + String get name => 'DatePicker'; + + @override + String get route => '/date-picker'; + + @override + Widget build(BuildContext context) => const _MainWidget(); +} + +class _MainWidget extends StatefulWidget { + const _MainWidget(); + + @override + State<_MainWidget> createState() => _MainWidgetState(); +} + +class _MainWidgetState extends State<_MainWidget> { + String pageTitle = getUseCaseName(DatePickerUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: TextButton( + onPressed: + () => showDatePicker( + context: context, + initialEntryMode: DatePickerEntryMode.calendarOnly, + initialDate: DateTime.now(), + firstDate: DateTime.now().subtract(const Duration(days: 365)), + lastDate: DateTime.now().add(const Duration(days: 365)), + ), + child: const Text('Show Date Picker'), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/dialog.dart b/flutter/dev/a11y_assessments/lib/use_cases/dialog.dart new file mode 100644 index 00000000..b8aaa3a9 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/dialog.dart @@ -0,0 +1,75 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class DialogUseCase extends UseCase { + @override + String get name => 'Dialog'; + + @override + String get route => '/dialog'; + + @override + Widget build(BuildContext context) => _MainWidget(); +} + +class _MainWidget extends StatelessWidget { + _MainWidget(); + + final String pageTitle = getUseCaseName(DialogUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: TextButton( + onPressed: + () => showDialog( + context: context, + builder: + (BuildContext context) => Dialog( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('This is a typical dialog.'), + const SizedBox(height: 15), + Row( + children: [ + TextButton( + key: const Key('OK Button'), + autofocus: true, + onPressed: () { + Navigator.pop(context); + }, + child: const Text('OK'), + ), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + ], + ), + ], + ), + ), + ), + ), + child: const Text('Show Dialog'), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/drawer.dart b/flutter/dev/a11y_assessments/lib/use_cases/drawer.dart new file mode 100644 index 00000000..338acba3 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/drawer.dart @@ -0,0 +1,80 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class DrawerUseCase extends UseCase { + @override + String get name => 'drawer'; + + @override + String get route => '/drawer'; + + @override + Widget build(BuildContext context) => const DrawerExample(); +} + +class DrawerExample extends StatefulWidget { + const DrawerExample({super.key}); + + @override + State createState() => _DrawerExampleState(); +} + +class _DrawerExampleState extends State { + String selectedPage = ''; + + String pageTitle = getUseCaseName(DrawerUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + endDrawer: Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + const DrawerHeader( + decoration: BoxDecoration(color: Colors.blue), + child: Text('Drawer Header', style: TextStyle(color: Colors.white, fontSize: 24)), + ), + ListTile( + leading: const Icon(Icons.message), + title: const Text('Messages'), + onTap: () { + setState(() { + selectedPage = 'Messages'; + }); + }, + ), + ListTile( + leading: const Icon(Icons.account_circle), + title: const Text('Profile'), + onTap: () { + setState(() { + selectedPage = 'Profile'; + }); + }, + ), + ListTile( + leading: const Icon(Icons.settings), + title: const Text('Settings'), + onTap: () { + setState(() { + selectedPage = 'Settings'; + }); + }, + ), + ], + ), + ), + body: Center(child: Text('Page: $selectedPage')), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/expansion_tile.dart b/flutter/dev/a11y_assessments/lib/use_cases/expansion_tile.dart new file mode 100644 index 00000000..4621916e --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/expansion_tile.dart @@ -0,0 +1,69 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class ExpansionTileUseCase extends UseCase { + @override + String get name => 'ExpansionTile'; + + @override + String get route => '/expansion-tile'; + + @override + Widget build(BuildContext context) => const ExpansionTileExample(); +} + +class ExpansionTileExample extends StatefulWidget { + const ExpansionTileExample({super.key}); + + @override + State createState() => _ExpansionTileExampleState(); +} + +class _ExpansionTileExampleState extends State { + bool _customTileExpanded = false; + + String pageTitle = getUseCaseName(ExpansionTileUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Column( + children: [ + const ExpansionTile( + title: Text('ExpansionTile 1'), + subtitle: Text('Trailing expansion arrow icon'), + children: [ListTile(title: Text('This is tile number 1'))], + ), + ExpansionTile( + title: const Text('ExpansionTile 2'), + subtitle: const Text('Custom expansion arrow icon'), + trailing: Icon( + _customTileExpanded ? Icons.arrow_drop_down_circle : Icons.arrow_drop_down, + ), + children: const [ListTile(title: Text('This is tile number 2'))], + onExpansionChanged: (bool expanded) { + setState(() { + _customTileExpanded = expanded; + }); + }, + ), + const ExpansionTile( + title: Text('ExpansionTile 3'), + subtitle: Text('Leading expansion arrow icon'), + controlAffinity: ListTileControlAffinity.leading, + children: [ListTile(title: Text('This is tile number 3'))], + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/material_banner.dart b/flutter/dev/a11y_assessments/lib/use_cases/material_banner.dart new file mode 100644 index 00000000..e42933e1 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/material_banner.dart @@ -0,0 +1,80 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class MaterialBannerUseCase extends UseCase { + @override + String get name => 'MaterialBanner'; + + @override + String get route => '/material_banner'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + final FocusNode dismissButtonFocusNode = FocusNode(); + final FocusNode showButtonFocusNode = FocusNode(); + + String pageTitle = getUseCaseName(MaterialBannerUseCase()); + + @override + void dispose() { + dismissButtonFocusNode.dispose(); + showButtonFocusNode.dispose(); + super.dispose(); + } + + void hideBanner() { + ScaffoldMessenger.of(context).hideCurrentMaterialBanner(); + showButtonFocusNode.requestFocus(); + } + + void showBanner() { + ScaffoldMessenger.of(context).showMaterialBanner( + MaterialBanner( + padding: const EdgeInsets.all(20), + content: const Text('Hello, I am a Material Banner'), + leading: const Icon(Icons.agriculture_outlined), + backgroundColor: Colors.yellowAccent, + actions: [ + TextButton( + focusNode: dismissButtonFocusNode, + onPressed: hideBanner, + child: const Text('DISMISS'), + ), + ], + ), + ); + dismissButtonFocusNode.requestFocus(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: ElevatedButton( + focusNode: showButtonFocusNode, + onPressed: showBanner, + child: const Text('Show a MaterialBanner'), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/navigation_bar.dart b/flutter/dev/a11y_assessments/lib/use_cases/navigation_bar.dart new file mode 100644 index 00000000..681e48e7 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/navigation_bar.dart @@ -0,0 +1,69 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class NavigationBarUseCase extends UseCase { + @override + String get name => 'NavigationBar'; + + @override + String get route => '/navigation-bar'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + int currentPageIndex = 0; + + String pageTitle = getUseCaseName(NavigationBarUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + bottomNavigationBar: NavigationBar( + onDestinationSelected: (int index) { + setState(() { + currentPageIndex = index; + }); + }, + indicatorColor: Colors.amber[800], + selectedIndex: currentPageIndex, + destinations: const [ + NavigationDestination( + selectedIcon: Icon(Icons.home), + icon: Icon(Icons.home_outlined), + label: 'Home', + ), + NavigationDestination(icon: Icon(Icons.business), label: 'Business'), + NavigationDestination( + selectedIcon: Icon(Icons.school), + icon: Icon(Icons.school_outlined), + label: 'School', + ), + ], + ), + body: + [ + Container(alignment: Alignment.center, child: const Text('Page 1')), + Container(alignment: Alignment.center, child: const Text('Page 2')), + Container(alignment: Alignment.center, child: const Text('Page 3')), + ][currentPageIndex], + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/navigation_drawer.dart b/flutter/dev/a11y_assessments/lib/use_cases/navigation_drawer.dart new file mode 100644 index 00000000..c6bf8293 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/navigation_drawer.dart @@ -0,0 +1,104 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class ExampleDestination { + const ExampleDestination(this.label, this.icon, this.selectedIcon); + + final String label; + final Widget icon; + final Widget selectedIcon; +} + +const List destinations = [ + ExampleDestination('Messages', Icon(Icons.widgets_outlined), Icon(Icons.widgets)), + ExampleDestination('Profile', Icon(Icons.format_paint_outlined), Icon(Icons.format_paint)), + ExampleDestination('Settings', Icon(Icons.settings_outlined), Icon(Icons.settings)), +]; + +class NavigationDrawerUseCase extends UseCase { + @override + String get name => 'NavigationDrawer'; + + @override + String get route => '/navigation-drawer'; + + @override + Widget build(BuildContext context) => const NavigationDrawerExample(); +} + +class NavigationDrawerExample extends StatefulWidget { + const NavigationDrawerExample({super.key}); + + @override + State createState() => _NavigationDrawerExampleState(); +} + +class _NavigationDrawerExampleState extends State { + final GlobalKey scaffoldKey = GlobalKey(); + + int screenIndex = 0; + late bool showNavigationDrawer; + + void handleScreenChanged(int selectedScreen) { + setState(() { + screenIndex = selectedScreen; + }); + } + + void openDrawer() { + scaffoldKey.currentState!.openEndDrawer(); + } + + String pageTitle = getUseCaseName(NavigationDrawerUseCase()); + + Widget buildDrawerScaffold(BuildContext context) { + return Scaffold( + key: scaffoldKey, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: SafeArea( + bottom: false, + top: false, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('Page Index = $screenIndex'), + ElevatedButton(onPressed: openDrawer, child: const Text('Open Drawer')), + ], + ), + ), + ), + endDrawer: NavigationDrawer( + onDestinationSelected: handleScreenChanged, + selectedIndex: screenIndex, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text('Header', style: Theme.of(context).textTheme.titleSmall), + ), + ...destinations.map((ExampleDestination destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + const Padding(padding: EdgeInsets.fromLTRB(28, 16, 28, 10), child: Divider()), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return buildDrawerScaffold(context); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/navigation_rail.dart b/flutter/dev/a11y_assessments/lib/use_cases/navigation_rail.dart new file mode 100644 index 00000000..8958c28d --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/navigation_rail.dart @@ -0,0 +1,187 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class NavigationRailUseCase extends UseCase { + @override + String get name => 'NavigationRail'; + + @override + String get route => '/navigation-rail'; + + @override + Widget build(BuildContext context) => const NavRailExample(); +} + +class NavRailExample extends StatefulWidget { + const NavRailExample({super.key}); + + @override + State createState() => _NavRailExampleState(); +} + +class _NavRailExampleState extends State { + int _selectedIndex = 0; + NavigationRailLabelType labelType = NavigationRailLabelType.all; + bool showLeading = false; + bool showTrailing = false; + double groupAlignment = -1.0; + + String pageTitle = getUseCaseName(NavigationRailUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Row( + children: [ + NavigationRail( + selectedIndex: _selectedIndex, + groupAlignment: groupAlignment, + onDestinationSelected: (int index) { + setState(() { + _selectedIndex = index; + }); + }, + labelType: labelType, + leading: + showLeading + ? FloatingActionButton( + elevation: 0, + onPressed: () { + // Add your onPressed code here! + }, + child: const Icon(Icons.add), + ) + : const SizedBox(), + trailing: + showTrailing + ? IconButton( + onPressed: () { + // Add your onPressed code here! + }, + icon: const Icon(Icons.more_horiz_rounded), + ) + : const SizedBox(), + destinations: const [ + NavigationRailDestination( + icon: Icon(Icons.favorite_border), + selectedIcon: Icon(Icons.favorite), + label: Text('First'), + ), + NavigationRailDestination( + icon: Icon(Icons.bookmark_border), + selectedIcon: Icon(Icons.book), + label: Text('Second'), + ), + NavigationRailDestination( + icon: Icon(Icons.star_border), + selectedIcon: Icon(Icons.star), + label: Text('Third'), + ), + ], + ), + const VerticalDivider(thickness: 1, width: 1), + // This is the main content. + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('selectedIndex: $_selectedIndex'), + const SizedBox(height: 20), + Text('Label type: ${labelType.name}'), + const SizedBox(height: 10), + OverflowBar( + spacing: 10.0, + children: [ + ElevatedButton( + onPressed: () { + setState(() { + labelType = NavigationRailLabelType.none; + }); + }, + child: const Text('None'), + ), + ElevatedButton( + onPressed: () { + setState(() { + labelType = NavigationRailLabelType.selected; + }); + }, + child: const Text('Selected'), + ), + ElevatedButton( + onPressed: () { + setState(() { + labelType = NavigationRailLabelType.all; + }); + }, + child: const Text('All'), + ), + ], + ), + const SizedBox(height: 20), + Text('Group alignment: $groupAlignment'), + const SizedBox(height: 10), + OverflowBar( + spacing: 10.0, + children: [ + ElevatedButton( + onPressed: () { + setState(() { + groupAlignment = -1.0; + }); + }, + child: const Text('Top'), + ), + ElevatedButton( + onPressed: () { + setState(() { + groupAlignment = 0.0; + }); + }, + child: const Text('Center'), + ), + ElevatedButton( + onPressed: () { + setState(() { + groupAlignment = 1.0; + }); + }, + child: const Text('Bottom'), + ), + ], + ), + const SizedBox(height: 20), + OverflowBar( + spacing: 10.0, + children: [ + ElevatedButton( + onPressed: () { + setState(() { + showLeading = !showLeading; + }); + }, + child: Text(showLeading ? 'Hide Leading' : 'Show Leading'), + ), + ElevatedButton( + onPressed: () { + setState(() { + showTrailing = !showTrailing; + }); + }, + child: Text(showTrailing ? 'Hide Trailing' : 'Show Trailing'), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/radio_list_tile.dart b/flutter/dev/a11y_assessments/lib/use_cases/radio_list_tile.dart new file mode 100644 index 00000000..b0611a1b --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/radio_list_tile.dart @@ -0,0 +1,60 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class RadioListTileUseCase extends UseCase { + @override + String get name => 'RadioListTile'; + + @override + String get route => '/radio-list-tile'; + + @override + Widget build(BuildContext context) => _MainWidget(); +} + +class _MainWidget extends StatefulWidget { + @override + State<_MainWidget> createState() => _MainWidgetState(); +} + +enum SingingCharacter { lafayette, jefferson } + +class _MainWidgetState extends State<_MainWidget> { + SingingCharacter _value = SingingCharacter.lafayette; + + void _onChanged(SingingCharacter? value) { + setState(() { + _value = value!; + }); + } + + String pageTitle = getUseCaseName(RadioListTileUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo'))), + body: ListView( + children: [ + RadioListTile( + title: const Text('Lafayette'), + value: SingingCharacter.lafayette, + groupValue: _value, + onChanged: _onChanged, + ), + RadioListTile( + title: const Text('Jefferson'), + value: SingingCharacter.jefferson, + groupValue: _value, + onChanged: _onChanged, + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/slider.dart b/flutter/dev/a11y_assessments/lib/use_cases/slider.dart new file mode 100644 index 00000000..2bc1cc12 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/slider.dart @@ -0,0 +1,58 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class SliderUseCase extends UseCase { + @override + String get name => 'Slider'; + + @override + String get route => '/slider'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + double currentSliderValue = 20; + static const String accessibilityLabel = 'Accessibility Test Slider'; + + String pageTitle = getUseCaseName(SliderUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle demo')), + ), + body: Center( + child: Semantics( + label: accessibilityLabel, + child: Slider( + value: currentSliderValue, + max: 100, + divisions: 5, + label: currentSliderValue.round().toString(), + onChanged: (double value) { + setState(() { + currentSliderValue = value; + }); + }, + ), + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/snack_bar.dart b/flutter/dev/a11y_assessments/lib/use_cases/snack_bar.dart new file mode 100644 index 00000000..b0783eff --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/snack_bar.dart @@ -0,0 +1,64 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class SnackBarUseCase extends UseCase { + @override + String get name => 'SnackBar'; + + @override + String get route => '/snack-bar'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + String pageTitle = getUseCaseName(SnackBarUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: Column( + children: [ + ElevatedButton( + child: const Text('Show Snackbar'), + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Awesome Snackbar!'))); + }, + ), + ElevatedButton( + child: const Text('Show Snackbar with action '), + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Awesome Snackbar!'), + action: SnackBarAction(label: 'Action', onPressed: () {}), + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/switch_list_tile.dart b/flutter/dev/a11y_assessments/lib/use_cases/switch_list_tile.dart new file mode 100644 index 00000000..a2353f1c --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/switch_list_tile.dart @@ -0,0 +1,69 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class SwitchListTileUseCase extends UseCase { + @override + String get name => 'SwitchListTile'; + + @override + String get route => '/switch-list-tile'; + + @override + Widget build(BuildContext context) => const SwitchListTileExample(); +} + +class SwitchListTileExample extends StatefulWidget { + const SwitchListTileExample({super.key}); + + @override + State createState() => _SwitchListTileExampleState(); +} + +class _SwitchListTileExampleState extends State { + bool _lights1 = false; + bool _lights2 = false; + + String pageTitle = getUseCaseName(SwitchListTileUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Center( + child: Column( + children: [ + SwitchListTile( + title: const Text('Lights'), + value: _lights1, + onChanged: (bool value) { + setState(() { + _lights1 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + SwitchListTile( + title: const Text('Lights'), + subtitle: const Text('Subtitle'), + value: _lights2, + onChanged: (bool value) { + setState(() { + _lights2 = value; + }); + }, + secondary: const Icon(Icons.lightbulb_outline), + ), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/tab_bar_view.dart b/flutter/dev/a11y_assessments/lib/use_cases/tab_bar_view.dart new file mode 100644 index 00000000..f3a04fe6 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/tab_bar_view.dart @@ -0,0 +1,49 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'use_cases.dart'; + +class TabBarViewUseCase extends UseCase { + @override + String get name => 'TabBarView'; + + @override + String get route => '/tab-bar-view'; + + @override + Widget build(BuildContext context) => const TabBarViewExample(); +} + +class TabBarViewExample extends StatelessWidget { + const TabBarViewExample({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + initialIndex: 1, + length: 3, + child: Scaffold( + appBar: AppBar( + title: Semantics(headingLevel: 1, child: const Text('TabBarView Sample')), + bottom: const TabBar( + tabs: [ + Tab(icon: Icon(Icons.cloud_outlined), text: 'Cloudy'), + Tab(icon: Icon(Icons.beach_access_sharp), text: 'Rainy'), + Tab(icon: Icon(Icons.brightness_5_sharp), text: 'Sunny'), + ], + ), + ), + body: const TabBarView( + children: [ + Center(child: Text("It's cloudy here")), + Center(child: Text("It's rainy here")), + Center(child: Text("It's sunny here")), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/text_button.dart b/flutter/dev/a11y_assessments/lib/use_cases/text_button.dart new file mode 100644 index 00000000..438710e3 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/text_button.dart @@ -0,0 +1,73 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class TextButtonUseCase extends UseCase { + @override + String get name => 'TextButton'; + + @override + String get route => '/text-button'; + + @override + Widget build(BuildContext context) => const MainWidget(); +} + +class MainWidget extends StatefulWidget { + const MainWidget({super.key}); + + @override + State createState() => MainWidgetState(); +} + +class MainWidgetState extends State { + String pageTitle = getUseCaseName(TextButtonUseCase()); + final GlobalKey _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + // The validator receives the text that the user has entered. + validator: (String? value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: ElevatedButton( + onPressed: () { + // Validate returns true if the form is valid, or false otherwise. + if (_formKey.currentState!.validate()) { + // If the form is valid, display a snackbar. In the real world, + // this might also send a request to a server. + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Form submitted'))); + } + }, + child: const Text('Submit'), + ), + ), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/text_field.dart b/flutter/dev/a11y_assessments/lib/use_cases/text_field.dart new file mode 100644 index 00000000..e6705933 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/text_field.dart @@ -0,0 +1,64 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class TextFieldUseCase extends UseCase { + @override + String get name => 'TextField'; + + @override + String get route => '/text-field'; + + @override + Widget build(BuildContext context) => _MainWidget(); +} + +class _MainWidget extends StatelessWidget { + _MainWidget(); + + final String pageTitle = getUseCaseName(TextFieldUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: ListView( + children: [ + Semantics( + label: 'Input field with suffix @gmail.com', + child: const TextField( + key: Key('enabled text field'), + maxLines: null, + decoration: InputDecoration( + labelText: 'Email', + suffixText: '@gmail.com', + hintText: 'Enter your email', + ), + ), + ), + Semantics( + label: 'Input field with suffix @gmail.com', + child: TextField( + key: const Key('disabled text field'), + maxLines: null, + decoration: const InputDecoration( + labelText: 'Email', + suffixText: '@gmail.com', + hintText: 'Enter your email', + ), + enabled: false, + controller: TextEditingController(text: 'xyz'), + ), + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/text_field_password.dart b/flutter/dev/a11y_assessments/lib/use_cases/text_field_password.dart new file mode 100644 index 00000000..9606394b --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/text_field_password.dart @@ -0,0 +1,49 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import '../utils.dart'; +import 'use_cases.dart'; + +class TextFieldPasswordUseCase extends UseCase { + @override + String get name => 'TextField password'; + + @override + String get route => '/text-field-password'; + + @override + Widget build(BuildContext context) => _MainWidget(); +} + +class _MainWidget extends StatelessWidget { + _MainWidget(); + + final String pageTitle = getUseCaseName(TextFieldPasswordUseCase()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Semantics(headingLevel: 1, child: Text('$pageTitle Demo')), + ), + body: ListView( + children: const [ + TextField( + key: Key('enabled password'), + decoration: InputDecoration(labelText: 'Password'), + obscureText: true, + ), + TextField( + key: Key('disabled password'), + decoration: InputDecoration(labelText: 'Password'), + enabled: false, + obscureText: true, + ), + ], + ), + ); + } +} diff --git a/flutter/dev/a11y_assessments/lib/use_cases/use_cases.dart b/flutter/dev/a11y_assessments/lib/use_cases/use_cases.dart new file mode 100644 index 00000000..36bf7916 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/use_cases/use_cases.dart @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter 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:flutter/widgets.dart'; + +import '../common/dynamic_title.dart'; +import 'action_chip.dart'; +import 'app_bar.dart'; +import 'auto_complete.dart'; +import 'badge.dart'; +import 'card.dart'; +import 'check_box_list_tile.dart'; +import 'date_picker.dart'; +import 'dialog.dart'; +import 'drawer.dart'; +import 'expansion_tile.dart'; +import 'material_banner.dart'; +import 'navigation_bar.dart'; +import 'navigation_drawer.dart'; +import 'navigation_rail.dart'; +import 'radio_list_tile.dart'; +import 'slider.dart'; +import 'snack_bar.dart'; +import 'switch_list_tile.dart'; +import 'tab_bar_view.dart'; +import 'text_button.dart'; +import 'text_field.dart'; +import 'text_field_password.dart'; + +abstract class UseCase { + String get name; + String get route; + + Widget buildWithTitle(BuildContext context) { + return DynamicTitle(title: name, child: build(context)); + } + + Widget build(BuildContext context); +} + +final List useCases = [ + CheckBoxListTile(), + DialogUseCase(), + SliderUseCase(), + TextFieldUseCase(), + TextFieldPasswordUseCase(), + DatePickerUseCase(), + AutoCompleteUseCase(), + BadgeUseCase(), + MaterialBannerUseCase(), + NavigationBarUseCase(), + TextButtonUseCase(), + RadioListTileUseCase(), + ActionChipUseCase(), + SnackBarUseCase(), + SwitchListTileUseCase(), + ExpansionTileUseCase(), + CardUseCase(), + DrawerUseCase(), + NavigationDrawerUseCase(), + NavigationRailUseCase(), + AppBarUseCase(), + TabBarViewUseCase(), +]; diff --git a/flutter/dev/a11y_assessments/lib/utils.dart b/flutter/dev/a11y_assessments/lib/utils.dart new file mode 100644 index 00000000..a65c24c3 --- /dev/null +++ b/flutter/dev/a11y_assessments/lib/utils.dart @@ -0,0 +1,7 @@ +// Copyright 2014 The Flutter 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 './use_cases/use_cases.dart'; + +String getUseCaseName(UseCase useCase) => useCase.name; diff --git a/flutter/dev/a11y_assessments/linux/.gitignore b/flutter/dev/a11y_assessments/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/flutter/dev/a11y_assessments/linux/CMakeLists.txt b/flutter/dev/a11y_assessments/linux/CMakeLists.txt new file mode 100644 index 00000000..07dc63e3 --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/CMakeLists.txt @@ -0,0 +1,139 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "a11y_assessments") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.a11y_assessments") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/flutter/dev/a11y_assessments/linux/flutter/CMakeLists.txt b/flutter/dev/a11y_assessments/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/flutter/dev/a11y_assessments/linux/main.cc b/flutter/dev/a11y_assessments/linux/main.cc new file mode 100644 index 00000000..281a29e1 --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/main.cc @@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/flutter/dev/a11y_assessments/linux/my_application.cc b/flutter/dev/a11y_assessments/linux/my_application.cc new file mode 100644 index 00000000..05e5133c --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/my_application.cc @@ -0,0 +1,108 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "a11y_assessments"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "a11y_assessments"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/flutter/dev/a11y_assessments/linux/my_application.h b/flutter/dev/a11y_assessments/linux/my_application.h new file mode 100644 index 00000000..8c66ec48 --- /dev/null +++ b/flutter/dev/a11y_assessments/linux/my_application.h @@ -0,0 +1,22 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/flutter/dev/a11y_assessments/macos/Runner/DebugProfile.entitlements b/flutter/dev/a11y_assessments/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/flutter/dev/a11y_assessments/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/flutter/dev/a11y_assessments/macos/Runner/Info.plist b/flutter/dev/a11y_assessments/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/flutter/dev/a11y_assessments/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/flutter/dev/a11y_assessments/macos/Runner/MainFlutterWindow.swift b/flutter/dev/a11y_assessments/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..a6a6b9af --- /dev/null +++ b/flutter/dev/a11y_assessments/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter 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 Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/flutter/dev/a11y_assessments/macos/Runner/Release.entitlements b/flutter/dev/a11y_assessments/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/flutter/dev/a11y_assessments/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/flutter/dev/benchmarks/complex_layout/android/settings.gradle b/flutter/dev/benchmarks/complex_layout/android/settings.gradle new file mode 100644 index 00000000..4a155b35 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/android/settings.gradle @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +buildscript { + dependencyLocking { + lockFile = file("${rootProject.projectDir}/buildscript-gradle.lockfile") + lockAllConfigurations() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false +} + +include ":app" diff --git a/flutter/dev/benchmarks/complex_layout/lib/main_bad.dart b/flutter/dev/benchmarks/complex_layout/lib/main_bad.dart new file mode 100644 index 00000000..ba69df47 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/lib/main_bad.dart @@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter 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:flutter/widgets.dart'; +import 'src/app.dart'; + +void main() { + runApp(const ComplexLayoutApp(badScroll: true)); +} diff --git a/flutter/dev/benchmarks/complex_layout/lib/src/app.dart b/flutter/dev/benchmarks/complex_layout/lib/src/app.dart new file mode 100644 index 00000000..43328d91 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/lib/src/app.dart @@ -0,0 +1,723 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import 'package:flutter/scheduler.dart' show timeDilation; + +enum ScrollMode { complex, tile } + +class ComplexLayoutApp extends StatefulWidget { + const ComplexLayoutApp({super.key, this.badScroll = false}); + + final bool badScroll; + + @override + ComplexLayoutAppState createState() => ComplexLayoutAppState(); + + static ComplexLayoutAppState? of(BuildContext context) => + context.findAncestorStateOfType(); +} + +class ComplexLayoutAppState extends State { + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: lightTheme ? ThemeData.light() : ThemeData.dark(), + title: 'Advanced Layout', + home: + scrollMode == ScrollMode.complex + ? ComplexLayout(badScroll: widget.badScroll) + : const TileScrollLayout(), + ); + } + + bool _lightTheme = true; + bool get lightTheme => _lightTheme; + set lightTheme(bool value) { + setState(() { + _lightTheme = value; + }); + } + + ScrollMode _scrollMode = ScrollMode.complex; + ScrollMode get scrollMode => _scrollMode; + set scrollMode(ScrollMode mode) { + setState(() { + _scrollMode = mode; + }); + } + + void toggleAnimationSpeed() { + setState(() { + timeDilation = (timeDilation != 1.0) ? 1.0 : 5.0; + }); + } +} + +class TileScrollLayout extends StatelessWidget { + const TileScrollLayout({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Tile Scrolling Layout')), + body: ListView.builder( + key: const Key('tiles-scroll'), + itemCount: 200, + itemBuilder: (BuildContext context, int index) { + return Padding( + padding: const EdgeInsets.all(5.0), + child: Material( + elevation: (index % 5 + 1).toDouble(), + color: Colors.white, + child: const IconBar(), + ), + ); + }, + ), + drawer: const GalleryDrawer(), + ); + } +} + +class ComplexLayout extends StatefulWidget { + const ComplexLayout({super.key, required this.badScroll}); + + final bool badScroll; + + @override + ComplexLayoutState createState() => ComplexLayoutState(); + + static ComplexLayoutState? of(BuildContext context) => + context.findAncestorStateOfType(); +} + +class ComplexLayoutState extends State { + @override + Widget build(BuildContext context) { + Widget body = ListView.builder( + key: const Key('complex-scroll'), // this key is used by the driver test + controller: ScrollController(), // So that the scroll offset can be tracked + itemCount: widget.badScroll ? 500 : null, + shrinkWrap: widget.badScroll, + itemBuilder: (BuildContext context, int index) { + if (index.isEven) { + return FancyImageItem(index, key: PageStorageKey(index)); + } else { + return FancyGalleryItem(index, key: PageStorageKey(index)); + } + }, + ); + if (widget.badScroll) { + body = ListView(key: const Key('complex-scroll-bad'), children: [body]); + } + + return Scaffold( + appBar: AppBar( + title: const Text('Advanced Layout'), + actions: [ + IconButton( + icon: const Icon(Icons.create), + tooltip: 'Search', + onPressed: () { + print('Pressed search'); + }, + ), + const TopBarMenu(), + ], + ), + body: Column(children: [Expanded(child: body), const BottomBar()]), + drawer: const GalleryDrawer(), + ); + } +} + +class TopBarMenu extends StatelessWidget { + const TopBarMenu({super.key}); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + onSelected: (String value) { + print('Selected: $value'); + }, + itemBuilder: + (BuildContext context) => >[ + const PopupMenuItem( + value: 'Friends', + child: MenuItemWithIcon(Icons.people, 'Friends', '5 new'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.event, 'Events', '12 upcoming'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.group, 'Groups', '14'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.image, 'Pictures', '12'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.near_me, 'Nearby', '33'), + ), + const PopupMenuItem( + value: 'Friends', + child: MenuItemWithIcon(Icons.people, 'Friends', '5'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.event, 'Events', '12'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.group, 'Groups', '14'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.image, 'Pictures', '12'), + ), + const PopupMenuItem( + value: 'Events', + child: MenuItemWithIcon(Icons.near_me, 'Nearby', '33'), + ), + ], + ); + } +} + +class MenuItemWithIcon extends StatelessWidget { + const MenuItemWithIcon(this.icon, this.title, this.subtitle, {super.key}); + + final IconData icon; + final String title; + final String subtitle; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon(icon), + Padding(padding: const EdgeInsets.only(left: 8.0, right: 8.0), child: Text(title)), + Text(subtitle, style: Theme.of(context).textTheme.bodySmall), + ], + ); + } +} + +class FancyImageItem extends StatelessWidget { + const FancyImageItem(this.index, {super.key}); + + final int index; + + @override + Widget build(BuildContext context) { + return ListBody( + children: [ + UserHeader('Ali Connors $index'), + const ItemDescription(), + const ItemImageBox(), + const InfoBar(), + const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0), child: Divider()), + const IconBar(), + const FatDivider(), + ], + ); + } +} + +class FancyGalleryItem extends StatelessWidget { + const FancyGalleryItem(this.index, {super.key}); + + final int index; + @override + Widget build(BuildContext context) { + return ListBody( + children: [ + const UserHeader('Ali Connors'), + ItemGalleryBox(index), + const InfoBar(), + const Padding(padding: EdgeInsets.symmetric(horizontal: 8.0), child: Divider()), + const IconBar(), + const FatDivider(), + ], + ); + } +} + +class InfoBar extends StatelessWidget { + const InfoBar({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const MiniIconWithText(Icons.thumb_up, '42'), + Text('3 Comments', style: Theme.of(context).textTheme.bodySmall), + ], + ), + ); + } +} + +class IconBar extends StatelessWidget { + const IconBar({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.only(left: 16.0, right: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconWithText(Icons.thumb_up, 'Like'), + IconWithText(Icons.comment, 'Comment'), + IconWithText(Icons.share, 'Share'), + ], + ), + ); + } +} + +class IconWithText extends StatelessWidget { + const IconWithText(this.icon, this.title, {super.key}); + + final IconData icon; + final String title; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(icon), + onPressed: () { + print('Pressed $title button'); + }, + ), + Text(title), + ], + ); + } +} + +class MiniIconWithText extends StatelessWidget { + const MiniIconWithText(this.icon, this.title, {super.key}); + + final IconData icon; + final String title; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Container( + width: 16.0, + height: 16.0, + decoration: ShapeDecoration( + color: Theme.of(context).primaryColor, + shape: const CircleBorder(), + ), + child: Icon(icon, color: Colors.white, size: 12.0), + ), + ), + Text(title, style: Theme.of(context).textTheme.bodySmall), + ], + ); + } +} + +class FatDivider extends StatelessWidget { + const FatDivider({super.key}); + + @override + Widget build(BuildContext context) { + return Container(height: 8.0, color: Theme.of(context).dividerColor); + } +} + +class UserHeader extends StatelessWidget { + const UserHeader(this.userName, {super.key}); + + final String userName; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(right: 8.0), + child: Image( + image: AssetImage('packages/flutter_gallery_assets/people/square/ali.png'), + width: 32.0, + height: 32.0, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyMedium, + children: [ + TextSpan(text: userName, style: const TextStyle(fontWeight: FontWeight.bold)), + const TextSpan(text: ' shared a new '), + const TextSpan(text: 'photo', style: TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + Row( + children: [ + Text('Yesterday at 11:55 • ', style: Theme.of(context).textTheme.bodySmall), + Icon( + Icons.people, + size: 16.0, + color: Theme.of(context).textTheme.bodySmall!.color, + ), + ], + ), + ], + ), + ), + const TopBarMenu(), + ], + ), + ); + } +} + +class ItemDescription extends StatelessWidget { + const ItemDescription({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + ), + ); + } +} + +class ItemImageBox extends StatelessWidget { + const ItemImageBox({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + children: [ + const SizedBox( + height: 230.0, + child: Image( + image: AssetImage( + 'packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png', + ), + ), + ), + Theme( + data: ThemeData.dark(), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: const Icon(Icons.edit), + onPressed: () { + print('Pressed edit button'); + }, + ), + IconButton( + icon: const Icon(Icons.zoom_in), + onPressed: () { + print('Pressed zoom button'); + }, + ), + ], + ), + ), + Positioned( + bottom: 4.0, + left: 4.0, + child: Container( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(2.0), + ), + padding: const EdgeInsets.all(4.0), + child: RichText( + text: const TextSpan( + style: TextStyle(color: Colors.white), + children: [ + TextSpan(text: 'Photo by '), + TextSpan( + style: TextStyle(fontWeight: FontWeight.bold), + text: 'Chris Godley', + ), + ], + ), + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text('Artisans of Southern India', style: Theme.of(context).textTheme.bodyLarge), + Text('Silk Spinners', style: Theme.of(context).textTheme.bodyMedium), + Text('Sivaganga, Tamil Nadu', style: Theme.of(context).textTheme.bodySmall), + ], + ), + ), + ], + ), + ), + ); + } +} + +class ItemGalleryBox extends StatelessWidget { + const ItemGalleryBox(this.index, {super.key}); + + final int index; + + @override + Widget build(BuildContext context) { + final List tabNames = ['A', 'B', 'C', 'D']; + + return SizedBox( + height: 200.0, + child: DefaultTabController( + length: tabNames.length, + child: Column( + children: [ + Expanded( + child: TabBarView( + children: + tabNames.map((String tabName) { + return Container( + key: PageStorageKey(tabName), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + child: Column( + children: [ + Expanded( + child: ColoredBox( + color: Theme.of(context).primaryColor, + child: Center( + child: Text( + tabName, + style: Theme.of( + context, + ).textTheme.headlineSmall!.copyWith(color: Colors.white), + ), + ), + ), + ), + Row( + children: [ + IconButton( + icon: const Icon(Icons.share), + onPressed: () { + print('Pressed share'); + }, + ), + IconButton( + icon: const Icon(Icons.event), + onPressed: () { + print('Pressed event'); + }, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text('This is item $tabName'), + ), + ), + ], + ), + ], + ), + ), + ), + ); + }).toList(), + ), + ), + const TabPageSelector(), + ], + ), + ), + ); + } +} + +class BottomBar extends StatelessWidget { + const BottomBar({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + border: Border(top: BorderSide(color: Theme.of(context).dividerColor)), + ), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + BottomBarButton(Icons.new_releases, 'News'), + BottomBarButton(Icons.people, 'Requests'), + BottomBarButton(Icons.chat, 'Messenger'), + BottomBarButton(Icons.bookmark, 'Bookmark'), + BottomBarButton(Icons.alarm, 'Alarm'), + ], + ), + ); + } +} + +class BottomBarButton extends StatelessWidget { + const BottomBarButton(this.icon, this.title, {super.key}); + + final IconData icon; + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + IconButton( + icon: Icon(icon), + onPressed: () { + print('Pressed: $title'); + }, + ), + Text(title, style: Theme.of(context).textTheme.bodySmall), + ], + ), + ); + } +} + +class GalleryDrawer extends StatelessWidget { + const GalleryDrawer({super.key}); + + void _changeTheme(BuildContext context, bool value) { + ComplexLayoutApp.of(context)?.lightTheme = value; + } + + void _changeScrollMode(BuildContext context, ScrollMode mode) { + ComplexLayoutApp.of(context)?.scrollMode = mode; + } + + @override + Widget build(BuildContext context) { + final ScrollMode currentMode = ComplexLayoutApp.of(context)!.scrollMode; + return Drawer( + // For real apps, see the Gallery material Drawer demo. More + // typically, a drawer would have a fixed header with a scrolling body + // below it. + child: ListView( + key: const PageStorageKey('gallery-drawer'), + padding: EdgeInsets.zero, + children: [ + const FancyDrawerHeader(), + ListTile( + key: const Key('scroll-switcher'), + title: const Text('Scroll Mode'), + onTap: () { + _changeScrollMode( + context, + currentMode == ScrollMode.complex ? ScrollMode.tile : ScrollMode.complex, + ); + Navigator.pop(context); + }, + trailing: Text(currentMode == ScrollMode.complex ? 'Tile' : 'Complex'), + ), + ListTile( + leading: const Icon(Icons.brightness_5), + title: const Text('Light'), + onTap: () { + _changeTheme(context, true); + }, + selected: ComplexLayoutApp.of(context)!.lightTheme, + trailing: Radio( + value: true, + groupValue: ComplexLayoutApp.of(context)!.lightTheme, + onChanged: (bool? value) { + _changeTheme(context, value!); + }, + ), + ), + ListTile( + leading: const Icon(Icons.brightness_7), + title: const Text('Dark'), + onTap: () { + _changeTheme(context, false); + }, + selected: !ComplexLayoutApp.of(context)!.lightTheme, + trailing: Radio( + value: false, + groupValue: ComplexLayoutApp.of(context)!.lightTheme, + onChanged: (bool? value) { + _changeTheme(context, value!); + }, + ), + ), + const Divider(), + ListTile( + leading: const Icon(Icons.hourglass_empty), + title: const Text('Animate Slowly'), + selected: timeDilation != 1.0, + onTap: () { + ComplexLayoutApp.of(context)!.toggleAnimationSpeed(); + }, + trailing: Checkbox( + value: timeDilation != 1.0, + onChanged: (bool? value) { + ComplexLayoutApp.of(context)!.toggleAnimationSpeed(); + }, + ), + ), + ], + ), + ); + } +} + +class FancyDrawerHeader extends StatelessWidget { + const FancyDrawerHeader({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.purple, + height: 200.0, + child: const SafeArea(bottom: false, child: Placeholder()), + ); + } +} diff --git a/flutter/dev/benchmarks/complex_layout/macos/Flutter/Flutter-Release.xcconfig b/flutter/dev/benchmarks/complex_layout/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/flutter/dev/benchmarks/complex_layout/macos/Podfile b/flutter/dev/benchmarks/complex_layout/macos/Podfile new file mode 100644 index 00000000..ae77cc1d --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Podfile @@ -0,0 +1,39 @@ +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/Base.lproj/MainMenu.xib b/flutter/dev/benchmarks/complex_layout/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/Configs/Warnings.xcconfig b/flutter/dev/benchmarks/complex_layout/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/DebugProfile.entitlements b/flutter/dev/benchmarks/complex_layout/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/Info.plist b/flutter/dev/benchmarks/complex_layout/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/MainFlutterWindow.swift b/flutter/dev/benchmarks/complex_layout/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..a6a6b9af --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// Copyright 2014 The Flutter 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 Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/flutter/dev/benchmarks/complex_layout/macos/Runner/Release.entitlements b/flutter/dev/benchmarks/complex_layout/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/flutter/dev/benchmarks/complex_layout/pubspec.yaml b/flutter/dev/benchmarks/complex_layout/pubspec.yaml new file mode 100644 index 00000000..8d5ba0b0 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/pubspec.yaml @@ -0,0 +1,90 @@ +name: complex_layout +description: A benchmark of a relatively complex layout. + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + + # To change the version of the gallery assets, edit + # //packages/flutter_tools/lib/src/commands/update_packages.dart + # and run + # flutter update-packages --force-upgrade + flutter_gallery_assets: 1.0.2 + + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webdriver: 3.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + test: 1.25.14 + integration_test: + sdk: flutter + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker: 10.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + assets: + - packages/flutter_gallery_assets/people/square/ali.png + - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png + +# PUBSPEC CHECKSUM: 79bc diff --git a/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_bad_test.dart b/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_bad_test.dart new file mode 100644 index 00000000..bbb7d2ac --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_bad_test.dart @@ -0,0 +1,57 @@ +// Copyright 2014 The Flutter 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:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + group('scrolling performance test', () { + late FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + + await driver.waitUntilFirstFrameRasterized(); + }); + + tearDownAll(() async { + driver.close(); + }); + + Future testScrollPerf(String listKey, String summaryName) async { + // The slight initial delay avoids starting the timing during a + // period of increased load on the device. Without this delay, the + // benchmark has greater noise. + // See: https://github.com/flutter/flutter/issues/19434 + await Future.delayed(const Duration(milliseconds: 250)); + + final Timeline timeline = await driver.traceAction(() async { + // Find the scrollable stock list + final SerializableFinder list = find.byValueKey(listKey); + expect(list, isNotNull); + + // Scroll down + for (int i = 0; i < 5; i += 1) { + await driver.scroll(list, 0.0, -300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + + // Scroll up + for (int i = 0; i < 5; i += 1) { + await driver.scroll(list, 0.0, 300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + }, retainPriorEvents: true); + + final TimelineSummary summary = TimelineSummary.summarize(timeline); + await summary.writeTimelineToFile(summaryName, pretty: true); + } + + test('complex_layout_scroll_perf', () async { + await testScrollPerf('complex-scroll-bad', 'complex_layout_scroll_perf'); + }, timeout: Timeout.none); + }); +} diff --git a/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_test.dart b/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_test.dart new file mode 100644 index 00000000..95b67ae1 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/test_driver/scroll_perf_test.dart @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter 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:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + group('scrolling performance test', () { + late FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + + await driver.waitUntilFirstFrameRasterized(); + }); + + tearDownAll(() async { + driver.close(); + }); + + Future testScrollPerf(String listKey, String summaryName) async { + // The slight initial delay avoids starting the timing during a + // period of increased load on the device. Without this delay, the + // benchmark has greater noise. + // See: https://github.com/flutter/flutter/issues/19434 + await Future.delayed(const Duration(milliseconds: 250)); + + await driver.forceGC(); + + final Timeline timeline = await driver.traceAction(() async { + // Find the scrollable stock list + final SerializableFinder list = find.byValueKey(listKey); + expect(list, isNotNull); + + // Scroll down + for (int i = 0; i < 5; i += 1) { + await driver.scroll(list, 0.0, -300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + + // Scroll up + for (int i = 0; i < 5; i += 1) { + await driver.scroll(list, 0.0, 300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + }, retainPriorEvents: true); + + final TimelineSummary summary = TimelineSummary.summarize(timeline); + await summary.writeTimelineToFile(summaryName, pretty: true); + } + + test('complex_layout_scroll_perf', () async { + await testScrollPerf('complex-scroll', 'complex_layout_scroll_perf'); + }, timeout: Timeout.none); + + test('tiles_scroll_perf', () async { + await driver.tap(find.byTooltip('Open navigation menu')); + await driver.tap(find.byValueKey('scroll-switcher')); + await testScrollPerf('tiles-scroll', 'tiles_scroll_perf'); + }, timeout: Timeout.none); + }); +} diff --git a/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart b/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart new file mode 100644 index 00000000..d9ca22f3 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter 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:complex_layout/main.dart' as app; +import 'package:flutter_driver/driver_extension.dart'; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart b/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart new file mode 100644 index 00000000..6ee838f3 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/test_driver/semantics_perf_test.dart @@ -0,0 +1,88 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + group('semantics performance test', () { + late FlutterDriver driver; + + setUpAll(() async { + // Turn off any accessibility services that may be running. The purpose of + // the test is to measure the time it takes to create the initial + // semantics tree in isolation. If accessibility services are on, the + // semantics tree gets generated during the first frame and we can't + // measure it in isolation. + final Process run = await Process.start(_adbPath(), const [ + 'shell', + 'settings', + 'put', + 'secure', + 'enabled_accessibility_services', + 'null', + ]); + await run.exitCode; + + driver = await FlutterDriver.connect(printCommunication: true); + }); + + tearDownAll(() async { + driver.close(); + }); + + test('initial tree creation', () async { + // Let app become fully idle. + await Future.delayed(const Duration(seconds: 2)); + + await driver.forceGC(); + + final Timeline timeline = await driver.traceAction(() async { + expect( + await driver.setSemantics(true), + isTrue, + reason: + 'Could not toggle semantics to on because semantics were already ' + 'on, but the test needs to toggle semantics to measure the initial ' + 'semantics tree generation in isolation.', + ); + }); + + final Iterable? semanticsEvents = timeline.events?.where( + (TimelineEvent event) => event.name == 'SEMANTICS', + ); + if (semanticsEvents?.length != 2) { + fail( + 'Expected exactly two "SEMANTICS" events, got ${semanticsEvents?.length}:\n$semanticsEvents', + ); + } + final Duration semanticsTreeCreation = Duration( + microseconds: + semanticsEvents!.last.timestampMicros! - semanticsEvents.first.timestampMicros!, + ); + + final String jsonEncoded = json.encode({ + 'initialSemanticsTreeCreation': semanticsTreeCreation.inMilliseconds, + }); + File( + p.join(testOutputsDirectory, 'complex_layout_semantics_perf.json'), + ).writeAsStringSync(jsonEncoded); + }, timeout: Timeout.none); + }); +} + +String _adbPath() { + final String? androidHome = + Platform.environment['ANDROID_HOME'] ?? Platform.environment['ANDROID_SDK_ROOT']; + if (androidHome == null) { + return 'adb'; + } else { + return p.join(androidHome, 'platform-tools', 'adb'); + } +} diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/Runner.rc b/flutter/dev/benchmarks/complex_layout/windows/runner/Runner.rc new file mode 100644 index 00000000..6d171eb6 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +// IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.yourcompany" "\0" + VALUE "FileDescription", "complex_layout" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "complex_layout" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.yourcompany. All rights reserved." "\0" + VALUE "OriginalFilename", "complex_layout.exe" "\0" + VALUE "ProductName", "complex_layout" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.cpp b/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..9cbd3109 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.cpp @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.h b/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.h new file mode 100644 index 00000000..bbc5836c --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/flutter_window.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/main.cpp b/flutter/dev/benchmarks/complex_layout/windows/runner/main.cpp new file mode 100644 index 00000000..a0da6862 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/main.cpp @@ -0,0 +1,47 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"complex_layout", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/resource.h b/flutter/dev/benchmarks/complex_layout/windows/runner/resource.h new file mode 100644 index 00000000..c245ff19 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/resource.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/runner.exe.manifest b/flutter/dev/benchmarks/complex_layout/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/utils.cpp b/flutter/dev/benchmarks/complex_layout/windows/runner/utils.cpp new file mode 100644 index 00000000..6abcd650 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/utils.cpp @@ -0,0 +1,68 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/utils.h b/flutter/dev/benchmarks/complex_layout/windows/runner/utils.h new file mode 100644 index 00000000..54414c98 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/utils.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.cpp b/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.cpp new file mode 100644 index 00000000..8fef19b0 --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.cpp @@ -0,0 +1,249 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.h b/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.h new file mode 100644 index 00000000..33786ecd --- /dev/null +++ b/flutter/dev/benchmarks/complex_layout/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/build.gradle b/flutter/dev/benchmarks/macrobenchmarks/android/build.gradle new file mode 100644 index 00000000..e0b1cd6b --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/build.gradle @@ -0,0 +1,36 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the build.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +allprojects { + repositories { + google() + mavenCentral() + } +} + +Directory newBuildDir = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + def newSubprojectBuildDir = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(':app') + dependencyLocking { + ignoredDependencies.add('io.flutter:*') + lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") + if (!project.hasProperty('local-engine-repo')) { + lockAllConfigurations() + } + } +} + +tasks.register("clean", Delete) { + delete rootProject.layout.buildDirectory +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/buildscript-gradle.lockfile b/flutter/dev/benchmarks/macrobenchmarks/android/buildscript-gradle.lockfile new file mode 100644 index 00000000..291d301d --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/buildscript-gradle.lockfile @@ -0,0 +1,153 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.databinding:databinding-common:8.7.0=classpath +androidx.databinding:databinding-compiler-common:8.7.0=classpath +com.android.application:com.android.application.gradle.plugin:8.7.0=classpath +com.android.databinding:baseLibrary:8.7.0=classpath +com.android.tools.analytics-library:crash:31.7.0=classpath +com.android.tools.analytics-library:protos:31.7.0=classpath +com.android.tools.analytics-library:shared:31.7.0=classpath +com.android.tools.analytics-library:tracker:31.7.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:8.7.0-12006047=classpath +com.android.tools.build:aaptcompiler:8.7.0=classpath +com.android.tools.build:apksig:8.7.0=classpath +com.android.tools.build:apkzlib:8.7.0=classpath +com.android.tools.build:builder-model:8.7.0=classpath +com.android.tools.build:builder-test-api:8.7.0=classpath +com.android.tools.build:builder:8.7.0=classpath +com.android.tools.build:bundletool:1.17.1=classpath +com.android.tools.build:gradle-api:8.7.0=classpath +com.android.tools.build:gradle-settings-api:8.7.0=classpath +com.android.tools.build:gradle:8.7.0=classpath +com.android.tools.build:manifest-merger:31.7.0=classpath +com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath +com.android.tools.ddms:ddmlib:31.7.0=classpath +com.android.tools.layoutlib:layoutlib-api:31.7.0=classpath +com.android.tools.lint:lint-model:31.7.0=classpath +com.android.tools.lint:lint-typedef-remover:31.7.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=classpath +com.android.tools:annotations:31.7.0=classpath +com.android.tools:common:31.7.0=classpath +com.android.tools:dvlib:31.7.0=classpath +com.android.tools:repository:31.7.0=classpath +com.android.tools:sdk-common:31.7.0=classpath +com.android.tools:sdklib:31.7.0=classpath +com.android:signflinger:8.7.0=classpath +com.android:zipflinger:8.7.0=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.17.0=classpath +com.google.auto.value:auto-value-annotations:1.6.2=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.10.1=classpath +com.google.crypto.tink:tink:1.7.0=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.18.0=classpath +com.google.flatbuffers:flatbuffers-java:1.12.0=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:32.0.1-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:2.8=classpath +com.google.jimfs:jimfs:1.1=classpath +com.google.protobuf:protobuf-java-util:3.22.3=classpath +com.google.protobuf:protobuf-java:3.22.3=classpath +com.google.testing.platform:core-proto:0.0.9-alpha02=classpath +com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath +com.squareup:javapoet:1.10.0=classpath +com.squareup:javawriter:2.5.0=classpath +com.sun.activation:javax.activation:1.2.0=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath +commons-io:commons-io:2.13.0=classpath +commons-logging:commons-logging:1.2=classpath +io.grpc:grpc-api:1.57.0=classpath +io.grpc:grpc-context:1.57.0=classpath +io.grpc:grpc-core:1.57.0=classpath +io.grpc:grpc-netty:1.57.0=classpath +io.grpc:grpc-protobuf-lite:1.57.0=classpath +io.grpc:grpc-protobuf:1.57.0=classpath +io.grpc:grpc-stub:1.57.0=classpath +io.netty:netty-buffer:4.1.93.Final=classpath +io.netty:netty-codec-http2:4.1.93.Final=classpath +io.netty:netty-codec-http:4.1.93.Final=classpath +io.netty:netty-codec-socks:4.1.93.Final=classpath +io.netty:netty-codec:4.1.93.Final=classpath +io.netty:netty-common:4.1.93.Final=classpath +io.netty:netty-handler-proxy:4.1.93.Final=classpath +io.netty:netty-handler:4.1.93.Final=classpath +io.netty:netty-resolver:4.1.93.Final=classpath +io.netty:netty-transport-native-unix-common:4.1.93.Final=classpath +io.netty:netty-transport:4.1.93.Final=classpath +io.perfmark:perfmark-api:0.26.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath +javax.inject:javax.inject:1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath +net.sf.jopt-simple:jopt-simple:4.9=classpath +net.sf.kxml:kxml2:2.3.0=classpath +org.apache.commons:commons-compress:1.21=classpath +org.apache.httpcomponents:httpclient:4.5.14=classpath +org.apache.httpcomponents:httpcore:4.4.16=classpath +org.apache.httpcomponents:httpmime:4.5.6=classpath +org.bitbucket.b_c:jose4j:0.9.5=classpath +org.bouncycastle:bcpkix-jdk18on:1.77=classpath +org.bouncycastle:bcprov-jdk18on:1.77=classpath +org.bouncycastle:bcutil-jdk18on:1.77=classpath +org.checkerframework:checker-qual:3.33.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.23=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath +org.jdom:jdom2:2.0.6=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.8.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.8.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.8.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.9.20=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.8.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.8.10=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:23.0.0=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.6=classpath +org.ow2.asm:asm-commons:9.6=classpath +org.ow2.asm:asm-tree:9.6=classpath +org.ow2.asm:asm-util:9.6=classpath +org.ow2.asm:asm:9.6=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +empty= diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/gradle.properties b/flutter/dev/benchmarks/macrobenchmarks/android/gradle.properties new file mode 100644 index 00000000..25971708 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties b/flutter/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..afa1e8eb --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/project-app.lockfile b/flutter/dev/benchmarks/macrobenchmarks/android/project-app.lockfile new file mode 100644 index 00000000..7c1e5c6c --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/project-app.lockfile @@ -0,0 +1,217 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.8.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.4.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.13.1=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.2.0=debugAndroidTestCompileClasspath +androidx.core:core:1.13.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.interpolator:interpolator:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.3.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window.extensions.core:core:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.emulator:proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-gradle:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-additional-test-output:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-apk-installer:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-coverage:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-emulator-control:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-logcat:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-host-retention:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:android-test-plugin-result-listener-gradle:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:utp-common:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:annotations:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:common:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath +com.getkeepsafe.relinker:relinker:1.4.5=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.android:annotations:4.1.1.4=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.api.grpc:proto-google-common-protos:2.17.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.gson:gson:2.10.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.gson:gson:2.8.5=lintClassPath +com.google.crypto.tink:tink:1.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.errorprone:error_prone_annotations:2.18.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:guava:32.0.1-jre=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:2.8=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.google.protobuf:protobuf-java:3.22.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-device-provider-local:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha02=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:core:0.0.9-alpha02=_internal-unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-io:commons-io:2.13.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +commons-logging:commons-logging:1.2=lintClassPath +io.grpc:grpc-api:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-context:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-core:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-netty:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf-lite:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-stub:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-buffer:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http2:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-socks:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler-proxy:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-resolver:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport-native-unix-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.perfmark:perfmark-api:0.26.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath +javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.checkerframework:checker-qual:3.33.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.23=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=lintClassPath +org.ow2.asm:asm:7.0=lintClassPath +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/project-integration_test.lockfile b/flutter/dev/benchmarks/macrobenchmarks/android/project-integration_test.lockfile new file mode 100644 index 00000000..2f06fc89 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/project-integration_test.lockfile @@ -0,0 +1,222 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.4.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.8.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestRuntimeClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.13.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.13.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.interpolator:interpolator:1.0.0=debugAndroidTestRuntimeClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.7.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.3.1=debugAndroidTestRuntimeClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window.extensions.core:core:1.0.0=debugAndroidTestRuntimeClasspath,debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.emulator:proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-gradle:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-additional-test-output:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-apk-installer:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-coverage:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-emulator-control:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-logcat:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-host-retention:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:android-test-plugin-result-listener-gradle:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:utp-common:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:annotations:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:common:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath +com.getkeepsafe.relinker:relinker:1.4.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.android:annotations:4.1.1.4=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.api.grpc:proto-google-common-protos:2.17.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.gson:gson:2.10.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.gson:gson:2.8.5=lintClassPath +com.google.crypto.tink:tink:1.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.errorprone:error_prone_annotations:2.18.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.errorprone:error_prone_annotations:2.3.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:guava:32.0.1-jre=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:2.8=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.google.protobuf:protobuf-java:3.22.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-device-provider-local:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha02=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:core:0.0.9-alpha02=_internal-unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-io:commons-io:2.13.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +commons-logging:commons-logging:1.2=lintClassPath +io.grpc:grpc-api:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-context:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-core:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-netty:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf-lite:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-stub:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-buffer:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http2:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-socks:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler-proxy:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-resolver:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport-native-unix-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.perfmark:perfmark-api:0.26.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath +javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath +junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-compat-qual:2.5.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.checkerframework:checker-qual:3.33.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.23=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.mockito:mockito-core:5.1.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-inline:5.1.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.objenesis:objenesis:3.3=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=lintClassPath +org.ow2.asm:asm:7.0=lintClassPath +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/project-url_launcher_android.lockfile b/flutter/dev/benchmarks/macrobenchmarks/android/project-url_launcher_android.lockfile new file mode 100644 index 00000000..6831d08f --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/project-url_launcher_android.lockfile @@ -0,0 +1,105 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:core:1.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +backport-util-concurrent:backport-util-concurrent:3.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +classworlds:classworlds:1.1-alpha-2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.almworks.sqlite4java:sqlite4java:0.282=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.auto.service:auto-service:1.0-rc4=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.auto:auto-common:0.8=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.2.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:27.0.1-jre=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.protobuf:protobuf-java:2.6.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.ibm.icu:icu4j:53.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.annotation:javax.annotation-api:1.3.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +nekohtml:nekohtml:1.9.6.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +nekohtml:xercesMinimal:1.9.6.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.ant:ant-launcher:1.8.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.ant:ant:1.8.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven.wagon:wagon-file:1.0-beta-6=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven.wagon:wagon-http-lightweight:1.0-beta-6=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven.wagon:wagon-http-shared:1.0-beta-6=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven.wagon:wagon-provider-api:1.0-beta-6=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-ant-tasks:2.1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-artifact-manager:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-artifact:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-error-diagnostics:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-model:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-plugin-registry:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-profile:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-project:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-repository-metadata:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.apache.maven:maven-settings:2.2.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.bouncycastle:bcprov-jdk15on:1.52=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-qual:2.5.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.17=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.plexus:plexus-container-default:1.0-alpha-9-stable-1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.plexus:plexus-interpolation:1.11=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.plexus:plexus-utils:1.5.15=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-core:1.10.19=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.objenesis:objenesis:2.1=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm:7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm:9.1=androidJacocoAnt +org.robolectric:annotations:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:junit:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:pluginapi:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:plugins-maven-dependency-resolver:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:resources:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:robolectric:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:sandbox:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:shadowapi:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:shadows-framework:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:utils-reflector:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.robolectric:utils:4.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath diff --git a/flutter/dev/benchmarks/macrobenchmarks/android/settings.gradle b/flutter/dev/benchmarks/macrobenchmarks/android/settings.gradle new file mode 100644 index 00000000..4a155b35 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/android/settings.gradle @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +buildscript { + dependencyLocking { + lockFile = file("${rootProject.projectDir}/buildscript-gradle.lockfile") + lockAllConfigurations() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false +} + +include ":app" diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rect.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rect.dart new file mode 100644 index 00000000..ecb3a200 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_draw_rect.dart @@ -0,0 +1,97 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'recorder.dart'; + +/// Repeatedly paints a grid of rectangles. +/// +/// Measures the performance of the `drawRect` operation. +class BenchDrawRect extends SceneBuilderRecorder { + /// A variant of the benchmark that uses the same [Paint] object for all rectangles. + /// + /// This variant focuses on the performance of the `drawRect` method itself. + BenchDrawRect.staticPaint() : benchmarkPaint = false, super(name: benchmarkName); + + /// A variant of the benchmark that creates a unique [Paint] for each rectangle. + /// + /// Does not cache the [Paint] objects across frames, but generates new + /// objects every time. This variant of the benchmark focuses on construction + /// and transfer of paint data to the renderer. + BenchDrawRect.variablePaint() : benchmarkPaint = true, super(name: variablePaintBenchmarkName); + + static const String benchmarkName = 'draw_rect'; + static const String variablePaintBenchmarkName = 'draw_rect_variable_paint'; + + /// Number of rows in the grid. + static const int kRows = 25; + + /// Number of columns in the grid. + static const int kColumns = 40; + + /// Whether each cell should gets its own unique [Paint] value. + /// + /// This is used to benchmark the efficiency of passing a large number of + /// paint objects to the rendering system. + final bool benchmarkPaint; + + /// Counter used to offset the rendered rects to make them wobble. + /// + /// The wobbling is there so a human could visually verify that the benchmark + /// is correctly pumping frames. + double wobbleCounter = 0; + + static final Paint _staticPaint = Paint()..color = const Color.fromARGB(255, 255, 0, 0); + + Paint makePaint(int row, int col) { + if (benchmarkPaint) { + final Paint paint = Paint(); + final double rowRatio = row / kRows; + paint.color = Color.fromARGB( + 255, + (255 * rowRatio).floor(), + (255 * col / kColumns).floor(), + 255, + ); + paint.filterQuality = FilterQuality.values[(FilterQuality.values.length * rowRatio).floor()]; + paint.strokeCap = StrokeCap.values[(StrokeCap.values.length * rowRatio).floor()]; + paint.strokeJoin = StrokeJoin.values[(StrokeJoin.values.length * rowRatio).floor()]; + paint.blendMode = BlendMode.values[(BlendMode.values.length * rowRatio).floor()]; + paint.style = PaintingStyle.values[(PaintingStyle.values.length * rowRatio).floor()]; + paint.strokeWidth = 1.0 + rowRatio; + paint.strokeMiterLimit = rowRatio; + return paint; + } else { + return _staticPaint; + } + } + + @override + void onDrawFrame(SceneBuilder sceneBuilder) { + final PictureRecorder pictureRecorder = PictureRecorder(); + final Canvas canvas = Canvas(pictureRecorder); + final Size viewSize = view.physicalSize; + + final Size cellSize = Size(viewSize.width / kColumns, viewSize.height / kRows); + final Size rectSize = cellSize * 0.8; + + for (int row = 0; row < kRows; row++) { + canvas.save(); + for (int col = 0; col < kColumns; col++) { + canvas.drawRect(Offset((wobbleCounter - 5).abs(), 0) & rectSize, makePaint(row, col)); + canvas.translate(cellSize.width, 0); + } + canvas.restore(); + canvas.translate(0, cellSize.height); + } + + wobbleCounter += 1; + wobbleCounter = wobbleCounter % 10; + final Picture picture = pictureRecorder.endRecording(); + sceneBuilder.pushOffset(0.0, 0.0); + sceneBuilder.addPicture(Offset.zero, picture); + sceneBuilder.pop(); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_dynamic_clip_on_static_picture.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_dynamic_clip_on_static_picture.dart new file mode 100644 index 00000000..079a0137 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_dynamic_clip_on_static_picture.dart @@ -0,0 +1,107 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'recorder.dart'; +import 'test_data.dart'; + +/// The height of each row. +const double kRowHeight = 20.0; + +/// Number of rows. +const int kRows = 100; + +/// Number of columns. +const int kColumns = 10; + +/// The amount the picture is scrolled on every iteration of the benchmark. +const double kScrollDelta = 2.0; + +/// Draws one complex picture, then moves a clip around it simulating scrolling +/// large static content. +/// +/// This benchmark measures how efficient we are at taking advantage of the +/// static picture when all that changes is the clip. +/// +/// See also: +/// +/// * `bench_text_out_of_picture_bounds.dart`, which measures a volatile +/// picture with a static clip. +/// * https://github.com/flutter/flutter/issues/42987, which this benchmark is +/// based on. +class BenchDynamicClipOnStaticPicture extends SceneBuilderRecorder { + BenchDynamicClipOnStaticPicture() : super(name: benchmarkName) { + // If the scrollable extent is too small, the benchmark may end up + // scrolling the picture out of the clip area entirely, resulting in + // bogus metric values. + const double maxScrollExtent = kDefaultTotalSampleCount * kScrollDelta; + const double pictureHeight = kRows * kRowHeight; + if (maxScrollExtent > pictureHeight) { + throw Exception( + 'Bad combination of constant values kRowHeight, kRows, and ' + 'kScrollData. With these numbers there is risk that the picture ' + 'will scroll out of the clip entirely. To fix the issue reduce ' + 'kScrollDelta, or increase either kRows or kRowHeight.', + ); + } + + // Create one static picture, then never change it again. + const Color black = Color.fromARGB(255, 0, 0, 0); + final PictureRecorder pictureRecorder = PictureRecorder(); + final Canvas canvas = Canvas(pictureRecorder); + viewSize = view.physicalSize / view.devicePixelRatio; + clipSize = Size(viewSize.width / 2, viewSize.height / 5); + final double cellWidth = viewSize.width / kColumns; + + final List paragraphs = generateLaidOutParagraphs( + paragraphCount: 500, + minWordCountPerParagraph: 3, + maxWordCountPerParagraph: 3, + widthConstraint: cellWidth, + color: black, + ); + + int paragraphCounter = 0; + double yOffset = 0.0; + for (int row = 0; row < kRows; row += 1) { + for (int column = 0; column < kColumns; column += 1) { + final double left = cellWidth * column; + canvas.save(); + canvas.clipRect(Rect.fromLTWH(left, yOffset, cellWidth, 20.0)); + canvas.drawParagraph( + paragraphs[paragraphCounter % paragraphs.length], + Offset(left, yOffset), + ); + canvas.restore(); + paragraphCounter += 1; + } + yOffset += kRowHeight; + } + + picture = pictureRecorder.endRecording(); + } + + static const String benchmarkName = 'dynamic_clip_on_static_picture'; + + late Size viewSize; + late Size clipSize; + late Picture picture; + double pictureVerticalOffset = 0.0; + + @override + void onDrawFrame(SceneBuilder sceneBuilder) { + // Render the exact same picture, but offset it as if it's being scrolled. + // This will move the clip along the Y axis in picture's local coordinates + // causing a repaint. If we're not efficient at managing clips and/or + // repaints this will jank (see https://github.com/flutter/flutter/issues/42987). + final Rect clip = Rect.fromLTWH(0.0, 0.0, clipSize.width, clipSize.height); + sceneBuilder.pushClipRect(clip); + sceneBuilder.pushOffset(0.0, pictureVerticalOffset); + sceneBuilder.addPicture(Offset.zero, picture); + sceneBuilder.pop(); + sceneBuilder.pop(); + pictureVerticalOffset -= kScrollDelta; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_harness.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_harness.dart new file mode 100644 index 00000000..8c95cf42 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_harness.dart @@ -0,0 +1,63 @@ +// Copyright 2014 The Flutter 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:ui' as ui; + +import 'package:flutter/material.dart'; + +import 'recorder.dart'; + +class BenchWidgetRecorder extends WidgetRecorder { + BenchWidgetRecorder() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_widget_recorder'; + + @override + Widget createWidget() { + // This is intentionally using a simple widget. The benchmark is meant to + // measure the overhead of the harness, so this method should induce as + // little work as possible. + return const SizedBox.expand(); + } +} + +class BenchWidgetBuildRecorder extends WidgetBuildRecorder { + BenchWidgetBuildRecorder() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_widget_build_recorder'; + + @override + Widget createWidget() { + // This is intentionally using a simple widget. The benchmark is meant to + // measure the overhead of the harness, so this method should induce as + // little work as possible. + return const SizedBox.expand(); + } +} + +class BenchRawRecorder extends RawRecorder { + BenchRawRecorder() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_raw_recorder'; + + @override + void body(Profile profile) { + profile.record('profile.record', () { + // This is intentionally empty. The benchmark only measures the overhead + // of the harness. + }, reported: true); + } +} + +class BenchSceneBuilderRecorder extends SceneBuilderRecorder { + BenchSceneBuilderRecorder() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_scene_builder_recorder'; + + @override + void onDrawFrame(ui.SceneBuilder sceneBuilder) { + // This is intentionally empty. The benchmark only measures the overhead + // of the harness. + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart new file mode 100644 index 00000000..46d70ec7 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart @@ -0,0 +1,90 @@ +// Copyright 2014 The Flutter 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:js_interop'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:web/web.dart' as web; + +import 'recorder.dart'; + +// Measures the performance of image decoding. +// +// The benchmark measures the decoding latency and not impact on jank. It +// cannot distinguish between blocking and non-blocking decoding. It naively +// measures the total time it takes to decode image frames. For example, the +// WASM codecs execute on the main thread and block the UI, leading to jank, +// but the browser's WebCodecs API is asynchronous running on a separate thread +// and does not jank. However, the benchmark result may be the same. +class BenchImageDecoding extends RawRecorder { + BenchImageDecoding() : super(name: benchmarkName, useCustomWarmUp: true); + + static const String benchmarkName = 'bench_image_decoding'; + + // These test images are taken from https://github.com/flutter/flutter_gallery_assets/tree/master/lib/splash_effects + static const List _imageUrls = [ + 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_1.gif', + 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_2.gif', + 'assets/packages/flutter_gallery_assets/splash_effects/splash_effect_3.gif', + ]; + + final List _imageData = []; + + @override + Future setUpAll() async { + if (_imageData.isNotEmpty) { + return; + } + for (final String imageUrl in _imageUrls) { + final Future fetchFuture = web.window.fetch(imageUrl.toJS).toDart; + final web.Response image = (await fetchFuture)! as web.Response; + final Future imageFuture = image.arrayBuffer().toDart; + final JSArrayBuffer imageBuffer = (await imageFuture)! as JSArrayBuffer; + _imageData.add(imageBuffer.toDart.asUint8List()); + } + } + + // The number of samples recorded so far. + int _sampleCount = 0; + + // The number of samples used for warm-up. + static const int _warmUpSampleCount = 5; + + // The number of samples used to measure performance after the warm-up. + static const int _measuredSampleCount = 20; + + @override + Future body(Profile profile) async { + await profile.recordAsync('recordImageDecode', () async { + final List> allDecodes = >[ + for (final Uint8List data in _imageData) _decodeImage(data), + ]; + await Future.wait(allDecodes); + }, reported: true); + + _sampleCount += 1; + if (_sampleCount == _warmUpSampleCount) { + profile.stopWarmingUp(); + } + if (_sampleCount >= _warmUpSampleCount + _measuredSampleCount) { + profile.stopBenchmark(); + } + } +} + +Future _decodeImage(Uint8List data) async { + final ui.Codec codec = await ui.instantiateImageCodec(data); + const int decodeFrameCount = 5; + if (codec.frameCount < decodeFrameCount) { + throw Exception( + 'Test image contains too few frames for this benchmark (${codec.frameCount}). ' + 'Choose a test image with at least $decodeFrameCount frames.', + ); + } + for (int i = 0; i < decodeFrameCount; i++) { + (await codec.getNextFrame()).image.dispose(); + } + codec.dispose(); +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart new file mode 100644 index 00000000..882ae926 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart @@ -0,0 +1,20 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'material3.dart'; +import 'recorder.dart'; + +/// Measures how expensive it is to construct the material 3 components screen. +class BenchMaterial3Components extends WidgetBuildRecorder { + BenchMaterial3Components() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_material3_components'; + + @override + Widget createWidget() { + return const TwoColumnMaterial3Components(); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart new file mode 100644 index 00000000..8e0d4d80 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart @@ -0,0 +1,149 @@ +// Copyright 2014 The Flutter 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:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/semantics.dart'; + +import 'material3.dart'; +import 'recorder.dart'; + +/// Measures the cost of semantics when constructing screens containing +/// Material 3 widgets. +class BenchMaterial3Semantics extends WidgetBuildRecorder { + BenchMaterial3Semantics() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_material3_semantics'; + + @override + Future setUpAll() async { + FlutterTimeline.debugCollectionEnabled = true; + super.setUpAll(); + SemanticsBinding.instance.ensureSemantics(); + } + + @override + Future tearDownAll() async { + FlutterTimeline.debugReset(); + } + + @override + void frameDidDraw() { + // Only record frames that show the widget. Frames that remove the widget + // are not interesting. + if (showWidget) { + final AggregatedTimings timings = FlutterTimeline.debugCollect(); + final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); + final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment'); + final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated( + 'Semantics.compileChildren', + ); + profile!.addTimedBlock(semanticsBlock, reported: true); + profile!.addTimedBlock(getFragmentBlock, reported: true); + profile!.addTimedBlock(compileChildrenBlock, reported: true); + } + + super.frameDidDraw(); + FlutterTimeline.debugReset(); + } + + @override + Widget createWidget() { + return const SingleColumnMaterial3Components(); + } +} + +/// Measures the cost of semantics when scrolling screens containing Material 3 +/// widgets. +/// +/// The implementation uses a ListView that jumps the scroll position between +/// 0 and 1 every frame. Such a small delta is not enough for lazy rendering to +/// add/remove widgets, but its enough to trigger the framework to recompute +/// some of the semantics. +/// +/// The expected output numbers of this benchmarks should be very small as +/// scrolling a list view should be a matter of shifting some widgets and +/// updating the projected clip imposed by the viewport. As of June 2023, the +/// numbers are not great. Semantics consumes >50% of frame time. +class BenchMaterial3ScrollSemantics extends WidgetRecorder { + BenchMaterial3ScrollSemantics() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_material3_scroll_semantics'; + + @override + Future setUpAll() async { + FlutterTimeline.debugCollectionEnabled = true; + super.setUpAll(); + SemanticsBinding.instance.ensureSemantics(); + } + + @override + Future tearDownAll() async { + FlutterTimeline.debugReset(); + } + + @override + void frameDidDraw() { + final AggregatedTimings timings = FlutterTimeline.debugCollect(); + final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); + final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment'); + final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated( + 'Semantics.compileChildren', + ); + profile!.addTimedBlock(semanticsBlock, reported: true); + profile!.addTimedBlock(getFragmentBlock, reported: true); + profile!.addTimedBlock(compileChildrenBlock, reported: true); + + super.frameDidDraw(); + FlutterTimeline.debugReset(); + } + + @override + Widget createWidget() => _ScrollTest(); +} + +class _ScrollTest extends StatefulWidget { + @override + State<_ScrollTest> createState() => _ScrollTestState(); +} + +class _ScrollTestState extends State<_ScrollTest> with SingleTickerProviderStateMixin { + late final Ticker ticker; + late final ScrollController scrollController; + + @override + void initState() { + super.initState(); + + scrollController = ScrollController(); + + bool forward = true; + + // A one-off timer is necessary to allow the framework to measure the + // available scroll extents before the scroll controller can be exercised + // to change the scroll position. + Timer.run(() { + ticker = createTicker((_) { + scrollController.jumpTo(forward ? 1 : 0); + forward = !forward; + }); + ticker.start(); + }); + } + + @override + void dispose() { + ticker.dispose(); + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleColumnMaterial3Components(scrollController: scrollController); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_hover.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_hover.dart new file mode 100644 index 00000000..23a72a71 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_hover.dart @@ -0,0 +1,176 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'recorder.dart'; + +class _NestedMouseRegion extends StatelessWidget { + const _NestedMouseRegion({required this.nests, required this.child}); + + final int nests; + final Widget child; + + @override + Widget build(BuildContext context) { + Widget current = child; + for (int i = 0; i < nests; i++) { + current = MouseRegion(onEnter: (_) {}, child: child); + } + return current; + } +} + +/// Creates a grid of mouse regions, then continuously hover over them. +/// +/// Measures our ability to hit test mouse regions. +class BenchMouseRegionGridHover extends WidgetRecorder { + BenchMouseRegionGridHover() : super(name: benchmarkName) { + _tester = _Tester(onDataPoint: handleDataPoint); + } + + static const String benchmarkName = 'bench_mouse_region_grid_hover'; + + late _Tester _tester; + + void handleDataPoint(Duration duration) { + profile!.addDataPoint('hitTestDuration', duration, reported: true); + } + + // Use a non-trivial border to force Web to switch painter + Border _getBorder(int columnIndex, int rowIndex) { + const BorderSide defaultBorderSide = BorderSide(); + + return Border( + left: columnIndex == 0 ? defaultBorderSide : BorderSide.none, + top: rowIndex == 0 ? defaultBorderSide : BorderSide.none, + right: defaultBorderSide, + bottom: defaultBorderSide, + ); + } + + bool started = false; + + @override + void frameDidDraw() { + if (!started) { + started = true; + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async { + _tester.start(); + registerDidStop(_tester.stop); + }); + } + super.frameDidDraw(); + } + + @override + Widget createWidget() { + const int rowsCount = 60; + const int columnsCount = 20; + const double containerSize = 20; + return Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + height: 400, + child: ListView.builder( + itemCount: rowsCount, + cacheExtent: rowsCount * containerSize, + physics: const ClampingScrollPhysics(), + itemBuilder: + (BuildContext context, int rowIndex) => _NestedMouseRegion( + nests: 10, + child: Row( + children: List.generate( + columnsCount, + (int columnIndex) => _NestedMouseRegion( + nests: 10, + child: Container( + decoration: BoxDecoration( + border: _getBorder(columnIndex, rowIndex), + color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127), + ), + width: containerSize, + height: containerSize, + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +abstract final class _UntilNextFrame { + static Completer? _completer; + + static Future wait() { + if (_UntilNextFrame._completer == null) { + _UntilNextFrame._completer = Completer(); + SchedulerBinding.instance.addPostFrameCallback((_) { + _UntilNextFrame._completer!.complete(); + _UntilNextFrame._completer = null; + }); + } + return _UntilNextFrame._completer!.future; + } +} + +class _Tester { + _Tester({required this.onDataPoint}); + + final ValueSetter onDataPoint; + + static const Duration hoverDuration = Duration(milliseconds: 20); + + bool _stopped = false; + + TestGesture get gesture { + return _gesture ??= TestGesture( + dispatcher: (PointerEvent event) async { + RendererBinding.instance.handlePointerEvent(event); + }, + kind: PointerDeviceKind.mouse, + ); + } + + TestGesture? _gesture; + + Duration currentTime = Duration.zero; + + Future _hoverTo(Offset location, Duration duration) async { + currentTime += duration; + final Stopwatch stopwatch = Stopwatch()..start(); + await gesture.moveTo(location, timeStamp: currentTime); + stopwatch.stop(); + onDataPoint(stopwatch.elapsed); + await _UntilNextFrame.wait(); + } + + Future start() async { + await Future.delayed(Duration.zero); + while (!_stopped) { + await _hoverTo(const Offset(30, 10), hoverDuration); + await _hoverTo(const Offset(10, 370), hoverDuration); + await _hoverTo(const Offset(370, 390), hoverDuration); + await _hoverTo(const Offset(390, 30), hoverDuration); + } + } + + void stop() { + _stopped = true; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart new file mode 100644 index 00000000..59ffa046 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart @@ -0,0 +1,168 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'recorder.dart'; + +/// Creates a grid of mouse regions, then continuously scrolls them up and down. +/// +/// Measures our ability to render mouse regions. +class BenchMouseRegionGridScroll extends WidgetRecorder { + BenchMouseRegionGridScroll() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_mouse_region_grid_scroll'; + + final _Tester _tester = _Tester(); + + // Use a non-trivial border to force Web to switch painter + Border _getBorder(int columnIndex, int rowIndex) { + const BorderSide defaultBorderSide = BorderSide(); + + return Border( + left: columnIndex == 0 ? defaultBorderSide : BorderSide.none, + top: rowIndex == 0 ? defaultBorderSide : BorderSide.none, + right: defaultBorderSide, + bottom: defaultBorderSide, + ); + } + + bool started = false; + + @override + void frameDidDraw() { + if (!started) { + started = true; + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async { + _tester.start(); + registerDidStop(_tester.stop); + }); + } + super.frameDidDraw(); + } + + @override + Widget createWidget() { + const int rowsCount = 60; + const int columnsCount = 20; + const double containerSize = 20; + return Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + height: 400, + child: ListView.builder( + itemCount: rowsCount, + cacheExtent: rowsCount * containerSize, + physics: const ClampingScrollPhysics(), + itemBuilder: + (BuildContext context, int rowIndex) => Row( + children: List.generate( + columnsCount, + (int columnIndex) => MouseRegion( + onEnter: (_) {}, + child: Container( + decoration: BoxDecoration( + border: _getBorder(columnIndex, rowIndex), + color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127), + ), + width: containerSize, + height: containerSize, + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +abstract final class _UntilNextFrame { + static Completer? _completer; + + static Future wait() { + if (_UntilNextFrame._completer == null) { + _UntilNextFrame._completer = Completer(); + SchedulerBinding.instance.addPostFrameCallback((_) { + _UntilNextFrame._completer!.complete(); + _UntilNextFrame._completer = null; + }); + } + return _UntilNextFrame._completer!.future; + } +} + +class _Tester { + static const int scrollFrequency = 60; + static const Offset dragStartLocation = Offset(200, 200); + static const Offset dragUpOffset = Offset(0, 200); + static const Offset dragDownOffset = Offset(0, -200); + static const Duration dragDuration = Duration(milliseconds: 200); + + bool _stopped = false; + + TestGesture get gesture { + return _gesture ??= TestGesture( + dispatcher: (PointerEvent event) async { + RendererBinding.instance.handlePointerEvent(event); + }, + kind: PointerDeviceKind.mouse, + ); + } + + TestGesture? _gesture; + + Duration currentTime = Duration.zero; + + Future _scroll(Offset start, Offset offset, Duration duration) async { + final int durationMs = duration.inMilliseconds; + final Duration fullFrameDuration = const Duration(seconds: 1) ~/ scrollFrequency; + final int frameDurationMs = fullFrameDuration.inMilliseconds; + + final int fullFrames = duration.inMilliseconds ~/ frameDurationMs; + final Offset fullFrameOffset = offset * (frameDurationMs.toDouble() / durationMs); + + final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames; + final Offset finalFrameOffset = offset - fullFrameOffset * fullFrames.toDouble(); + + await gesture.down(start, timeStamp: currentTime); + + for (int frame = 0; frame < fullFrames; frame += 1) { + currentTime += fullFrameDuration; + await gesture.moveBy(fullFrameOffset, timeStamp: currentTime); + await _UntilNextFrame.wait(); + } + + if (finalFrameOffset != Offset.zero) { + currentTime += finalFrameDuration; + await gesture.moveBy(finalFrameOffset, timeStamp: currentTime); + await _UntilNextFrame.wait(); + } + + await gesture.up(timeStamp: currentTime); + } + + Future start() async { + await Future.delayed(Duration.zero); + while (!_stopped) { + await _scroll(dragStartLocation, dragUpOffset, dragDuration); + await _scroll(dragStartLocation, dragDownOffset, dragDuration); + } + } + + void stop() { + _stopped = true; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_mixed_grid_hover.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_mixed_grid_hover.dart new file mode 100644 index 00000000..61f8fb1c --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_mixed_grid_hover.dart @@ -0,0 +1,197 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'recorder.dart'; + +class _NestedMouseRegion extends StatelessWidget { + const _NestedMouseRegion({required this.nests, required this.child}); + + final int nests; + final Widget child; + + @override + Widget build(BuildContext context) { + Widget current = child; + for (int i = 0; i < nests; i++) { + current = MouseRegion(onEnter: (_) {}, child: child); + } + return current; + } +} + +class _NestedListener extends StatelessWidget { + const _NestedListener({required this.nests, required this.child}); + + final int nests; + final Widget child; + + @override + Widget build(BuildContext context) { + Widget current = child; + for (int i = 0; i < nests; i++) { + current = Listener(onPointerDown: (_) {}, child: child); + } + return current; + } +} + +/// Creates a grid of mouse regions, then continuously hovers over them. +/// +/// Measures our ability to hit test mouse regions. +class BenchMouseRegionMixedGridHover extends WidgetRecorder { + BenchMouseRegionMixedGridHover() : super(name: benchmarkName) { + _tester = _Tester(onDataPoint: handleDataPoint); + } + + static const String benchmarkName = 'bench_mouse_region_mixed_grid_hover'; + + late _Tester _tester; + + void handleDataPoint(Duration duration) { + profile!.addDataPoint('hitTestDuration', duration, reported: true); + } + + // Use a non-trivial border to force Web to switch painter + Border _getBorder(int columnIndex, int rowIndex) { + const BorderSide defaultBorderSide = BorderSide(); + + return Border( + left: columnIndex == 0 ? defaultBorderSide : BorderSide.none, + top: rowIndex == 0 ? defaultBorderSide : BorderSide.none, + right: defaultBorderSide, + bottom: defaultBorderSide, + ); + } + + bool started = false; + + @override + void frameDidDraw() { + if (!started) { + started = true; + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async { + _tester.start(); + registerDidStop(_tester.stop); + }); + } + super.frameDidDraw(); + } + + @override + Widget createWidget() { + const int rowsCount = 60; + const int columnsCount = 20; + const double containerSize = 20; + return Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + height: 400, + child: ListView.builder( + itemCount: rowsCount, + cacheExtent: rowsCount * containerSize, + physics: const ClampingScrollPhysics(), + itemBuilder: + (BuildContext context, int rowIndex) => _NestedMouseRegion( + nests: 10, + child: Row( + children: List.generate( + columnsCount, + (int columnIndex) => _NestedListener( + nests: 40, + child: _NestedMouseRegion( + nests: 10, + child: Container( + decoration: BoxDecoration( + border: _getBorder(columnIndex, rowIndex), + color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127), + ), + width: containerSize, + height: containerSize, + ), + ), + ), + ), + ), + ), + ), + ), + ), + ); + } +} + +class _UntilNextFrame { + _UntilNextFrame._(); + + static Completer? _completer; + + static Future wait() { + if (_UntilNextFrame._completer == null) { + _UntilNextFrame._completer = Completer(); + SchedulerBinding.instance.addPostFrameCallback((_) { + _UntilNextFrame._completer!.complete(); + _UntilNextFrame._completer = null; + }); + } + return _UntilNextFrame._completer!.future; + } +} + +class _Tester { + _Tester({required this.onDataPoint}); + + final ValueSetter onDataPoint; + + static const Duration hoverDuration = Duration(milliseconds: 20); + + bool _stopped = false; + + TestGesture get gesture { + return _gesture ??= TestGesture( + dispatcher: (PointerEvent event) async { + RendererBinding.instance.handlePointerEvent(event); + }, + kind: PointerDeviceKind.mouse, + ); + } + + TestGesture? _gesture; + + Duration currentTime = Duration.zero; + + Future _hoverTo(Offset location, Duration duration) async { + currentTime += duration; + final Stopwatch stopwatch = Stopwatch()..start(); + await gesture.moveTo(location, timeStamp: currentTime); + stopwatch.stop(); + onDataPoint(stopwatch.elapsed); + await _UntilNextFrame.wait(); + } + + Future start() async { + await Future.delayed(Duration.zero); + while (!_stopped) { + await _hoverTo(const Offset(30, 10), hoverDuration); + await _hoverTo(const Offset(10, 370), hoverDuration); + await _hoverTo(const Offset(370, 390), hoverDuration); + await _hoverTo(const Offset(390, 30), hoverDuration); + } + } + + void stop() { + _stopped = true; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_pageview_scroll_linethrough.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_pageview_scroll_linethrough.dart new file mode 100644 index 00000000..befdc037 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_pageview_scroll_linethrough.dart @@ -0,0 +1,172 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'recorder.dart'; + +/// Creates a [PageView] that uses a font style that can't be rendered +/// using canvas (switching to DOM). +/// +/// Since the whole page uses a CustomPainter this is a good representation +/// for apps that have pictures with large number of painting commands. +class BenchPageViewScrollLineThrough extends WidgetRecorder { + BenchPageViewScrollLineThrough() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_page_view_scroll_line_through'; + + @override + Widget createWidget() => + const MaterialApp(title: 'PageView Scroll LineThrough Benchmark', home: _MyScrollContainer()); +} + +class _MyScrollContainer extends StatefulWidget { + const _MyScrollContainer(); + + @override + State<_MyScrollContainer> createState() => _MyScrollContainerState(); +} + +class _MyScrollContainerState extends State<_MyScrollContainer> { + static const Duration stepDuration = Duration(milliseconds: 500); + + late PageController pageController; + final _CustomPainter _painter = _CustomPainter('aa'); + int pageNumber = 0; + + @override + void initState() { + super.initState(); + + pageController = PageController(); + + // Without the timer the animation doesn't begin. + Timer.run(() async { + while (pageNumber < 25) { + await pageController.animateToPage( + pageNumber % 5, + duration: stepDuration, + curve: Curves.easeInOut, + ); + pageNumber++; + } + }); + } + + @override + void dispose() { + pageController.dispose(); + _painter._textPainter.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return PageView.builder( + controller: pageController, + itemBuilder: (BuildContext context, int position) { + return CustomPaint(painter: _painter, size: const Size(300, 500)); + }, + ); + } +} + +class _CustomPainter extends CustomPainter { + _CustomPainter(this.text); + + final String text; + final Paint _linePainter = Paint(); + final TextPainter _textPainter = TextPainter(); + static const double lineWidth = 0.5; + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + double xPosition, yPosition; + final double width = size.width / 7; + final double height = size.height / 6; + xPosition = 0; + const double viewPadding = 5; + const double circlePadding = 4; + yPosition = viewPadding; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaler = TextScaler.noScaling; + const TextStyle textStyle = TextStyle( + color: Colors.black87, + fontSize: 13, + fontFamily: 'Roboto', + ); + + _linePainter.isAntiAlias = true; + for (int i = 0; i < 42; i++) { + _linePainter.color = Colors.white; + + TextStyle temp = textStyle; + if (i % 7 == 0) { + temp = textStyle.copyWith(decoration: TextDecoration.lineThrough); + } + + final TextSpan span = TextSpan(text: text, style: temp); + + _textPainter.text = span; + + _textPainter.layout(maxWidth: width); + _linePainter.style = PaintingStyle.fill; + canvas.drawRect( + Rect.fromLTWH(xPosition, yPosition - viewPadding, width, height), + _linePainter, + ); + + _textPainter.paint( + canvas, + Offset(xPosition + (width / 2 - _textPainter.width / 2), yPosition + circlePadding), + ); + xPosition += width; + if (xPosition.round() >= size.width.round()) { + xPosition = 0; + yPosition += height; + } + } + + _drawVerticalAndHorizontalLines(canvas, size, yPosition, xPosition, height, width); + } + + void _drawVerticalAndHorizontalLines( + Canvas canvas, + Size size, + double yPosition, + double xPosition, + double height, + double width, + ) { + yPosition = height; + _linePainter.strokeWidth = lineWidth; + _linePainter.color = Colors.grey; + canvas.drawLine(const Offset(0, lineWidth), Offset(size.width, lineWidth), _linePainter); + for (int i = 0; i < 6; i++) { + canvas.drawLine(Offset(0, yPosition), Offset(size.width, yPosition), _linePainter); + yPosition += height; + } + + canvas.drawLine( + Offset(0, size.height - lineWidth), + Offset(size.width, size.height - lineWidth), + _linePainter, + ); + xPosition = width; + canvas.drawLine(const Offset(lineWidth, 0), Offset(lineWidth, size.height), _linePainter); + for (int i = 0; i < 6; i++) { + canvas.drawLine(Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); + xPosition += width; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths.dart new file mode 100644 index 00000000..33ab4283 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths.dart @@ -0,0 +1,28 @@ +// Copyright 2014 The Flutter 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 'bench_paths_recording.dart' as recording; +import 'recorder.dart'; + +/// Measure the performance of path construction. +/// +/// This benchmarks was generated by running flutter gallery and recording +/// path calls. +class BenchPathRecording extends RawRecorder { + BenchPathRecording() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_path_recording'; + + @override + Future setUpAll() async {} + + @override + void body(Profile profile) { + profile.record('recordPathConstruction', () { + for (int i = 1; i <= 10; i++) { + recording.createPaths(); + } + }, reported: true); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths_recording.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths_recording.dart new file mode 100644 index 00000000..773adcef --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_paths_recording.dart @@ -0,0 +1,6741 @@ +// Copyright 2014 The Flutter 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:ui'; + +// The code below was generated by modify several parts of the engine +// May 2020 and running Flutter Gallery. + +late PathFillType gFillType; +late Rect gBounds; +late List allPaths; + +late Path path8; +late Path path10; +late Path path12; +late Path path14; +late Path path16; +late Path path18; +late Path path34; +late Path path50; +late Path path60; +late Path path63; +late Path path66; +late Path path69; +late Path path72; +late Path path75; +late Path path78; +late Path path80; +late Path path82; +late Path path84; +late Path path86; +late Path path88; +late Path path119; +late Path path120; +late Path path121; +late Path path122; +late Path path123; +late Path path125; +late Path path127; +late Path path129; +late Path path131; +late Path path132; +late Path path134; +late Path path137; +late Path path140; +late Path path143; +late Path path145; +late Path path147; +late Path path208; +late Path path209; +late Path path210; +late Path path211; +late Path path213; +late Path path216; +late Path path219; +late Path path222; +late Path path225; +late Path path227; +late Path path229; +late Path path232; +late Path path235; +late Path path238; +late Path path240; +late Path path242; +late Path path277; +late Path path278; +late Path path279; +late Path path280; +late Path path281; +late Path path282; +late Path path284; +late Path path286; +late Path path288; +late Path path290; +late Path path292; +late Path path295; +late Path path298; +late Path path301; +late Path path330; +late Path path331; +late Path path332; +late Path path333; +late Path path334; +late Path path336; +late Path path338; +late Path path340; +late Path path342; +late Path path344; +late Path path345; +late Path path346; +late Path path349; +late Path path352; +late Path path356; +late Path path358; +late Path path359; +late Path path360; +late Path path361; +late Path path362; +late Path path363; +late Path path366; +late Path path369; +late Path path372; +late Path path373; +late Path path374; +late Path path375; +late Path path376; +late Path path379; +late Path path382; +late Path path385; +late Path path386; +late Path path387; +late Path path388; +late Path path389; +late Path path392; +late Path path395; +late Path path398; +late Path path399; +late Path path400; +late Path path401; +late Path path402; +late Path path405; +late Path path408; +late Path path411; +late Path path412; +late Path path413; +late Path path414; +late Path path415; +late Path path418; +late Path path421; +late Path path424; +late Path path425; +late Path path426; +late Path path427; +late Path path428; +late Path path431; +late Path path434; +late Path path437; +late Path path438; +late Path path439; +late Path path440; +late Path path441; +late Path path444; +late Path path447; +late Path path450; +late Path path451; +late Path path452; +late Path path453; +late Path path454; +late Path path457; +late Path path460; +late Path path463; +late Path path464; +late Path path465; +late Path path466; +late Path path467; +late Path path470; +late Path path473; +late Path path476; +late Path path477; +late Path path478; +late Path path479; +late Path path480; +late Path path483; +late Path path486; +late Path path489; +late Path path490; +late Path path491; +late Path path492; +late Path path493; +late Path path496; +late Path path499; +late Path path502; +late Path path503; +late Path path504; +late Path path505; +late Path path506; +late Path path509; +late Path path512; +late Path path515; +late Path path516; +late Path path517; +late Path path518; +late Path path519; +late Path path522; +late Path path525; +late Path path528; +late Path path529; +late Path path530; +late Path path531; +late Path path532; +late Path path535; +late Path path538; +late Path path541; +late Path path542; +late Path path543; +late Path path544; +late Path path545; +late Path path548; +late Path path551; +late Path path554; +late Path path555; +late Path path556; +late Path path557; +late Path path558; +late Path path561; +late Path path564; +late Path path573; +late Path path577; +late Path path579; +late Path path591; +late Path path592; +late Path path593; +late Path path594; +late Path path595; +late Path path597; +late Path path599; +late Path path601; +late Path path603; +late Path path606; +late Path path608; + +void createPaths() { + allPaths = []; + pathOps0(); + pathOps1(); + pathOps2(); + pathOps3(); + pathOps4(); + pathOps5(); + pathOps6(); + pathOps7(); + pathOps8(); + pathOps9(); + pathOps10(); + pathOps11(); + pathOps12(); + pathOps13(); + pathOps14(); + pathOps15(); + pathOps16(); + pathOps17(); + pathOps18(); + pathOps19(); + pathOps20(); + pathOps21(); + pathOps22(); + pathOps23(); + pathOps24(); + pathOps25(); + pathOps26(); + pathOps27(); + pathOps28(); + pathOps29(); + pathOps30(); + pathOps31(); + pathOps32(); + pathOps33(); + pathOps34(); + pathOps35(); + pathOps36(); + pathOps37(); + pathOps38(); + pathOps39(); + pathOps40(); + pathOps41(); + pathOps42(); + pathOps43(); + pathOps44(); + pathOps45(); + pathOps46(); + pathOps47(); + pathOps48(); + pathOps49(); + pathOps50(); + pathOps51(); + pathOps52(); + pathOps53(); + pathOps54(); + pathOps55(); + pathOps56(); + pathOps57(); + pathOps58(); + pathOps59(); + pathOps60(); + pathOps61(); + pathOps62(); + pathOps63(); + pathOps64(); + pathOps65(); + pathOps66(); + pathOps67(); + pathOps68(); + pathOps69(); + pathOps70(); + pathOps71(); + pathOps72(); + pathOps73(); + pathOps74(); + pathOps75(); + pathOps76(); + pathOps77(); + pathOps78(); + pathOps79(); + pathOps80(); + pathOps81(); + pathOps82(); + pathOps83(); + pathOps84(); + pathOps85(); + pathOps86(); + pathOps87(); + pathOps88(); + pathOps89(); + pathOps90(); + pathOps91(); + pathOps92(); + pathOps93(); + pathOps94(); + pathOps95(); + pathOps96(); + pathOps97(); + pathOps98(); + pathOps99(); + pathOps100(); + pathOps101(); + pathOps102(); + pathOps103(); + pathOps104(); + pathOps105(); + pathOps106(); + pathOps107(); + pathOps108(); + pathOps109(); + pathOps110(); + pathOps111(); + pathOps112(); + pathOps113(); + pathOps114(); + pathOps115(); + pathOps116(); + pathOps117(); + pathOps118(); + pathOps119(); + pathOps120(); + pathOps121(); + pathOps122(); + pathOps123(); + pathOps124(); + pathOps125(); + pathOps126(); + pathOps127(); + pathOps128(); + pathOps129(); + pathOps130(); + pathOps131(); + pathOps132(); + pathOps133(); + pathOps134(); + pathOps135(); + pathOps136(); + pathOps137(); + pathOps138(); + pathOps139(); + pathOps140(); + pathOps141(); + pathOps142(); + pathOps143(); + pathOps144(); + pathOps145(); + pathOps146(); + pathOps147(); + pathOps149(); + pathOps150(); + pathOps155(); + pathOps156(); + pathOps161(); + pathOps162(); + pathOps164(); + pathOps165(); + pathOps167(); + pathOps168(); + pathOps173(); + pathOps174(); + pathOps178(); + pathOps179(); + pathOps180(); + pathOps181(); + pathOps182(); + pathOps183(); + pathOps184(); + pathOps185(); + pathOps186(); + pathOps187(); + pathOps188(); + pathOps189(); + pathOps190(); + pathOps191(); + pathOps192(); + pathOps193(); + pathOps194(); + pathOps195(); + pathOps196(); + pathOps197(); + pathOps198(); + pathOps199(); + pathOps200(); + pathOps201(); + pathOps202(); + pathOps203(); + pathOps204(); + pathOps205(); + pathOps206(); + pathOps207(); + pathOps208(); + pathOps209(); + pathOps210(); + pathOps211(); + pathOps212(); + pathOps213(); + pathOps214(); + pathOps215(); + pathOps216(); + pathOps217(); + pathOps218(); + pathOps219(); + pathOps220(); + pathOps221(); + pathOps222(); + pathOps223(); + pathOps224(); + pathOps225(); + pathOps226(); + pathOps227(); + pathOps228(); + pathOps229(); + pathOps230(); + pathOps231(); + pathOps232(); + pathOps233(); + pathOps234(); + pathOps235(); + pathOps236(); + pathOps237(); + pathOps238(); + pathOps239(); + pathOps240(); + pathOps241(); + pathOps242(); + pathOps243(); + pathOps244(); + pathOps245(); + pathOps246(); + pathOps247(); + pathOps248(); + pathOps249(); + pathOps250(); + pathOps251(); + pathOps252(); + pathOps253(); + pathOps254(); + pathOps255(); + pathOps256(); + pathOps257(); + pathOps258(); + pathOps259(); + pathOps260(); + pathOps261(); + pathOps262(); + pathOps263(); + pathOps264(); + pathOps265(); + pathOps266(); + pathOps267(); + pathOps268(); + pathOps269(); + pathOps270(); + pathOps271(); + pathOps272(); + pathOps273(); + pathOps274(); + pathOps275(); + pathOps276(); + pathOps277(); + pathOps278(); + pathOps279(); + pathOps280(); + pathOps281(); + pathOps282(); + pathOps283(); + pathOps284(); + pathOps285(); + pathOps286(); + pathOps287(); + pathOps288(); + pathOps289(); + pathOps290(); + pathOps291(); + pathOps292(); + pathOps293(); + pathOps294(); + pathOps295(); + pathOps296(); + pathOps297(); + pathOps298(); + pathOps299(); + pathOps300(); + pathOps301(); + pathOps302(); + pathOps303(); + pathOps304(); + pathOps305(); + pathOps306(); + pathOps307(); + pathOps308(); + pathOps309(); + pathOps310(); + pathOps311(); + pathOps312(); + pathOps313(); + pathOps314(); + pathOps315(); + pathOps316(); + pathOps317(); + pathOps318(); + pathOps319(); + pathOps320(); + pathOps321(); + pathOps322(); + pathOps323(); + pathOps324(); + pathOps325(); + pathOps326(); + pathOps327(); + pathOps328(); + pathOps329(); + pathOps330(); + pathOps331(); + pathOps332(); + pathOps333(); + pathOps334(); + pathOps335(); + pathOps336(); + pathOps337(); + pathOps338(); + pathOps339(); + pathOps340(); + pathOps341(); + pathOps342(); + pathOps343(); + pathOps344(); + pathOps345(); + pathOps346(); + pathOps347(); + pathOps348(); + pathOps349(); + pathOps350(); + pathOps351(); + pathOps352(); + pathOps353(); + pathOps354(); + pathOps355(); + pathOps356(); + pathOps357(); + pathOps358(); + pathOps359(); + pathOps360(); + pathOps361(); + pathOps362(); + pathOps363(); + pathOps364(); + pathOps365(); + pathOps366(); + pathOps367(); + pathOps368(); + pathOps369(); + pathOps370(); + pathOps371(); + pathOps372(); + pathOps373(); + pathOps374(); + pathOps375(); + pathOps376(); + pathOps377(); + pathOps378(); + pathOps379(); + pathOps380(); + pathOps381(); + pathOps382(); + pathOps383(); + pathOps384(); + pathOps385(); + pathOps386(); + pathOps387(); + pathOps388(); + pathOps389(); + pathOps390(); + pathOps391(); + pathOps392(); + pathOps393(); + pathOps394(); + pathOps395(); + pathOps396(); + pathOps397(); + pathOps398(); + pathOps399(); + pathOps400(); + pathOps401(); + pathOps402(); + pathOps403(); + pathOps404(); + pathOps405(); + pathOps406(); + pathOps407(); + pathOps408(); + pathOps409(); + pathOps410(); + pathOps411(); + pathOps412(); + pathOps413(); + pathOps414(); + pathOps415(); + pathOps416(); + pathOps417(); + pathOps418(); + pathOps419(); + pathOps420(); + pathOps421(); + pathOps422(); + pathOps423(); + pathOps424(); + pathOps425(); + pathOps426(); + pathOps427(); + pathOps428(); + pathOps429(); + pathOps430(); + pathOps431(); + pathOps432(); + pathOps433(); + pathOps434(); + pathOps435(); + pathOps436(); + pathOps437(); + pathOps438(); + pathOps439(); + pathOps440(); + pathOps441(); + pathOps442(); + pathOps443(); + pathOps444(); + pathOps445(); + pathOps446(); + pathOps447(); + pathOps448(); + pathOps449(); + pathOps450(); + pathOps451(); + pathOps452(); + pathOps453(); + pathOps454(); + pathOps455(); + pathOps456(); + pathOps457(); + pathOps458(); + pathOps459(); + pathOps460(); + pathOps461(); + pathOps462(); + pathOps463(); + pathOps464(); + pathOps465(); + pathOps466(); + pathOps467(); + pathOps468(); + pathOps469(); + pathOps470(); + pathOps471(); + pathOps472(); + pathOps473(); + pathOps474(); + pathOps475(); + pathOps476(); + pathOps477(); + pathOps478(); + pathOps479(); + pathOps480(); + pathOps481(); + pathOps482(); + pathOps483(); + pathOps484(); + pathOps485(); + pathOps486(); + pathOps487(); + pathOps488(); + pathOps489(); + pathOps490(); + pathOps491(); + pathOps492(); + pathOps493(); + pathOps494(); + pathOps495(); + pathOps496(); + pathOps497(); + pathOps498(); + pathOps499(); + pathOps500(); + pathOps501(); + pathOps502(); + pathOps503(); + pathOps504(); + pathOps505(); + pathOps506(); + pathOps507(); + pathOps508(); + pathOps509(); + pathOps510(); + pathOps511(); + pathOps512(); + pathOps513(); + pathOps514(); + pathOps515(); + pathOps516(); + pathOps517(); + pathOps518(); + pathOps519(); + pathOps520(); + pathOps521(); + pathOps522(); + pathOps523(); + pathOps524(); + pathOps525(); + pathOps526(); + pathOps527(); + pathOps528(); + pathOps529(); + pathOps530(); + pathOps531(); + pathOps532(); + pathOps533(); + pathOps534(); + pathOps535(); + pathOps536(); + pathOps537(); + pathOps538(); + pathOps539(); + pathOps540(); + pathOps541(); + pathOps542(); + pathOps543(); + pathOps544(); + pathOps545(); + pathOps546(); + pathOps547(); + pathOps548(); + pathOps549(); + pathOps550(); + pathOps551(); + pathOps552(); + pathOps553(); + pathOps554(); + pathOps555(); + pathOps556(); + pathOps557(); + pathOps558(); + pathOps559(); + pathOps560(); + pathOps561(); + pathOps562(); + pathOps563(); + pathOps564(); + pathOps565(); + pathOps566(); + pathOps567(); + pathOps568(); + pathOps569(); + pathOps570(); + pathOps571(); + pathOps572(); + pathOps573(); + pathOps574(); + pathOps575(); + pathOps576(); + pathOps577(); + pathOps578(); + pathOps579(); + pathOps580(); + pathOps581(); + pathOps582(); + pathOps583(); + pathOps584(); + pathOps585(); + pathOps586(); + pathOps587(); + pathOps588(); + pathOps589(); + pathOps590(); + pathOps596(); + pathOps598(); + pathOps600(); + pathOps602(); + pathOps604(); + pathOps605(); + pathOps607(); +} + +void pathOps0() { + final Path path0 = Path(); + path0.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(20, 20, 60, 60), const Radius.circular(10)), + ); + gBounds = path0.getBounds(); + gBounds = path0.getBounds(); + gBounds = path0.getBounds(); + _runPathTest(path0); + gBounds = path0.getBounds(); + _runPathTest(path0); + allPaths.add(path0); +} + +void pathOps1() { + final Path path1 = Path(); + path1.addOval(const Rect.fromLTRB(20, 20, 60, 60)); + gBounds = path1.getBounds(); + gBounds = path1.getBounds(); + gBounds = path1.getBounds(); + gBounds = path1.getBounds(); + _runPathTest(path1); + gFillType = path1.fillType; + _runPathTest(path1); + gFillType = path1.fillType; + _runPathTest(path1); + gFillType = path1.fillType; + _runPathTest(path1); + gFillType = path1.fillType; + allPaths.add(path1); +} + +void pathOps2() { + final Path path2 = Path(); + path2.moveTo(20, 60); + path2.quadraticBezierTo(60, 20, 60, 60); + path2.close(); + path2.moveTo(60, 20); + path2.quadraticBezierTo(60, 60, 20, 60); + gBounds = path2.getBounds(); + gBounds = path2.getBounds(); + gBounds = path2.getBounds(); + gBounds = path2.getBounds(); + _runPathTest(path2); + gFillType = path2.fillType; + _runPathTest(path2); + gFillType = path2.fillType; + _runPathTest(path2); + gFillType = path2.fillType; + _runPathTest(path2); + gFillType = path2.fillType; + allPaths.add(path2); +} + +void pathOps3() { + final Path path3 = Path(); + path3.moveTo(20, 30); + path3.lineTo(40, 20); + path3.lineTo(60, 30); + path3.lineTo(60, 60); + path3.lineTo(20, 60); + path3.close(); + gBounds = path3.getBounds(); + gBounds = path3.getBounds(); + gBounds = path3.getBounds(); + gBounds = path3.getBounds(); + _runPathTest(path3); + gFillType = path3.fillType; + _runPathTest(path3); + gFillType = path3.fillType; + _runPathTest(path3); + gFillType = path3.fillType; + _runPathTest(path3); + gFillType = path3.fillType; + allPaths.add(path3); +} + +void pathOps4() { + final Path path4 = Path(); + path4.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(8, 8, 328, 248), const Radius.circular(16)), + ); + _runPathTest(path4); + allPaths.add(path4); +} + +void pathOps5() { + final Path path5 = Path(); + path5.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(8, 8, 328, 248), const Radius.circular(16)), + ); + _runPathTest(path5); + allPaths.add(path5); +} + +void pathOps6() { + final Path path6 = Path(); + path6.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path6.getBounds(); + allPaths.add(path6); +} + +void pathOps7() { + final Path path7 = Path(); + path7.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 179.5, 200), const Radius.circular(10)), + ); + gFillType = path7.fillType; + path8 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path121 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path210 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path278 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path332 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path359 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path372 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path385 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path398 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path411 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path424 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path437 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path450 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path463 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path476 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path489 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path502 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path515 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path528 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path541 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path554 = path7.shift(const Offset(15, 8)); + gFillType = path7.fillType; + path591 = path7.shift(const Offset(15, 8)); + allPaths.add(path7); +} + +void pathOps8() { + gBounds = path8.getBounds(); + allPaths.add(path8); +} + +void pathOps9() { + final Path path9 = Path(); + path9.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 179.5, 200), const Radius.circular(10)), + ); + gFillType = path9.fillType; + path10 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path120 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path209 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path279 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path331 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path360 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path373 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path386 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path399 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path412 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path425 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path438 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path451 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path464 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path477 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path490 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path503 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path516 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path529 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path542 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path555 = path9.shift(const Offset(15, 8)); + gFillType = path9.fillType; + path592 = path9.shift(const Offset(15, 8)); + allPaths.add(path9); +} + +void pathOps10() { + gBounds = path10.getBounds(); + allPaths.add(path10); +} + +void pathOps11() { + final Path path11 = Path(); + path11.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 179.5, 200), const Radius.circular(10)), + ); + gFillType = path11.fillType; + path12 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path119 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path208 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path280 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path330 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path361 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path374 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path387 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path400 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path413 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path426 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path439 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path452 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path465 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path478 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path491 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path504 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path517 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path530 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path543 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path556 = path11.shift(const Offset(15, 8)); + gFillType = path11.fillType; + path593 = path11.shift(const Offset(15, 8)); + allPaths.add(path11); +} + +void pathOps12() { + gBounds = path12.getBounds(); + allPaths.add(path12); +} + +void pathOps13() { + final Path path13 = Path(); + path13.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 179.5, 200), const Radius.circular(10)), + ); + gFillType = path13.fillType; + path14 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path122 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path211 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path277 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path333 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path362 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path375 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path388 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path401 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path414 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path427 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path440 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path453 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path466 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path479 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path492 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path505 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path518 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path531 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path544 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path557 = path13.shift(const Offset(15, 8)); + gFillType = path13.fillType; + path594 = path13.shift(const Offset(15, 8)); + allPaths.add(path13); +} + +void pathOps14() { + gBounds = path14.getBounds(); + allPaths.add(path14); +} + +void pathOps15() { + final Path path15 = Path(); + path15.addOval(const Rect.fromLTRB(0, 0, 58, 58)); + gFillType = path15.fillType; + path16 = path15.shift(const Offset(860, 79)); + gFillType = path15.fillType; + path143 = path15.shift(const Offset(860, 79)); + gFillType = path15.fillType; + path238 = path15.shift(const Offset(860, 79)); + gFillType = path15.fillType; + path301 = path15.shift(const Offset(860, 79)); + gFillType = path15.fillType; + path345 = path15.shift(const Offset(860, 79)); + allPaths.add(path15); +} + +void pathOps16() { + gBounds = path16.getBounds(); + allPaths.add(path16); +} + +void pathOps17() { + final Path path17 = Path(); + path17.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(0, 0, 250.66666666666666, 585), + const Radius.circular(10), + ), + ); + gFillType = path17.fillType; + path18 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path134 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path229 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path292 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path346 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path363 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path376 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path389 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path402 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path415 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path428 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path441 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path454 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path467 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path480 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path493 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path506 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path519 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path532 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path545 = path17.shift(const Offset(81, 0)); + gFillType = path17.fillType; + path558 = path17.shift(const Offset(81, 0)); + allPaths.add(path17); +} + +void pathOps18() { + gBounds = path18.getBounds(); + allPaths.add(path18); +} + +void pathOps19() { + final Path path19 = Path(); + path19.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path19.getBounds(); + allPaths.add(path19); +} + +void pathOps20() { + final Path path20 = Path(); + path20.reset(); + path20.moveTo(331.66666666666663, 86); + path20.lineTo(81, 86); + path20.lineTo(81, 84); + path20.lineTo(331.66666666666663, 84); + gBounds = path20.getBounds(); + _runPathTest(path20); + gFillType = path20.fillType; + allPaths.add(path20); +} + +void pathOps21() { + final Path path21 = Path(); + path21.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path21.getBounds(); + allPaths.add(path21); +} + +void pathOps22() { + final Path path22 = Path(); + path22.reset(); + path22.moveTo(234.66666666666666, 87); + path22.lineTo(96, 87); + path22.lineTo(96, 86); + path22.lineTo(234.66666666666666, 86); + gBounds = path22.getBounds(); + _runPathTest(path22); + gFillType = path22.fillType; + allPaths.add(path22); +} + +void pathOps23() { + final Path path23 = Path(); + path23.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path23.getBounds(); + allPaths.add(path23); +} + +void pathOps24() { + final Path path24 = Path(); + path24.reset(); + path24.moveTo(234.66666666666666, 101); + path24.lineTo(96, 101); + path24.lineTo(96, 100); + path24.lineTo(234.66666666666666, 100); + gBounds = path24.getBounds(); + _runPathTest(path24); + gFillType = path24.fillType; + allPaths.add(path24); +} + +void pathOps25() { + final Path path25 = Path(); + path25.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path25.getBounds(); + allPaths.add(path25); +} + +void pathOps26() { + final Path path26 = Path(); + path26.reset(); + path26.moveTo(234.66666666666666, 101); + path26.lineTo(96, 101); + path26.lineTo(96, 100); + path26.lineTo(234.66666666666666, 100); + gBounds = path26.getBounds(); + _runPathTest(path26); + gFillType = path26.fillType; + allPaths.add(path26); +} + +void pathOps27() { + final Path path27 = Path(); + path27.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path27.getBounds(); + allPaths.add(path27); +} + +void pathOps28() { + final Path path28 = Path(); + path28.reset(); + path28.moveTo(234.66666666666666, 101); + path28.lineTo(96, 101); + path28.lineTo(96, 100); + path28.lineTo(234.66666666666666, 100); + gBounds = path28.getBounds(); + _runPathTest(path28); + gFillType = path28.fillType; + allPaths.add(path28); +} + +void pathOps29() { + final Path path29 = Path(); + path29.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path29.getBounds(); + allPaths.add(path29); +} + +void pathOps30() { + final Path path30 = Path(); + path30.reset(); + path30.moveTo(234.66666666666666, 87); + path30.lineTo(96, 87); + path30.lineTo(96, 86); + path30.lineTo(234.66666666666666, 86); + gBounds = path30.getBounds(); + allPaths.add(path30); +} + +void pathOps31() { + final Path path31 = Path(); + path31.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path31.getBounds(); + allPaths.add(path31); +} + +void pathOps32() { + final Path path32 = Path(); + path32.reset(); + path32.moveTo(234.66666666666666, 87); + path32.lineTo(96, 87); + path32.lineTo(96, 86); + path32.lineTo(234.66666666666666, 86); + gBounds = path32.getBounds(); + allPaths.add(path32); +} + +void pathOps33() { + final Path path33 = Path(); + path33.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(0, 0, 250.66666666666666, 585), + const Radius.circular(10), + ), + ); + gFillType = path33.fillType; + path34 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path137 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path232 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path295 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path349 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path366 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path379 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path392 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path405 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path418 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path431 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path444 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path457 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path470 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path483 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path496 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path509 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path522 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path535 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path548 = path33.shift(const Offset(359.66666666666663, 0)); + gFillType = path33.fillType; + path561 = path33.shift(const Offset(359.66666666666663, 0)); + allPaths.add(path33); +} + +void pathOps34() { + gBounds = path34.getBounds(); + allPaths.add(path34); +} + +void pathOps35() { + final Path path35 = Path(); + path35.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path35.getBounds(); + allPaths.add(path35); +} + +void pathOps36() { + final Path path36 = Path(); + path36.reset(); + path36.moveTo(610.3333333333333, 86); + path36.lineTo(359.66666666666663, 86); + path36.lineTo(359.66666666666663, 84); + path36.lineTo(610.3333333333333, 84); + gBounds = path36.getBounds(); + _runPathTest(path36); + gFillType = path36.fillType; + allPaths.add(path36); +} + +void pathOps37() { + final Path path37 = Path(); + path37.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path37.getBounds(); + allPaths.add(path37); +} + +void pathOps38() { + final Path path38 = Path(); + path38.reset(); + path38.moveTo(234.66666666666666, 87); + path38.lineTo(96, 87); + path38.lineTo(96, 86); + path38.lineTo(234.66666666666666, 86); + gBounds = path38.getBounds(); + _runPathTest(path38); + gFillType = path38.fillType; + allPaths.add(path38); +} + +void pathOps39() { + final Path path39 = Path(); + path39.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path39.getBounds(); + allPaths.add(path39); +} + +void pathOps40() { + final Path path40 = Path(); + path40.reset(); + path40.moveTo(234.66666666666666, 87); + path40.lineTo(96, 87); + path40.lineTo(96, 86); + path40.lineTo(234.66666666666666, 86); + gBounds = path40.getBounds(); + _runPathTest(path40); + gFillType = path40.fillType; + allPaths.add(path40); +} + +void pathOps41() { + final Path path41 = Path(); + path41.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 73), Radius.zero), + ); + gBounds = path41.getBounds(); + allPaths.add(path41); +} + +void pathOps42() { + final Path path42 = Path(); + path42.reset(); + path42.moveTo(234.66666666666666, 73); + path42.lineTo(96, 73); + path42.lineTo(96, 72); + path42.lineTo(234.66666666666666, 72); + gBounds = path42.getBounds(); + _runPathTest(path42); + gFillType = path42.fillType; + allPaths.add(path42); +} + +void pathOps43() { + final Path path43 = Path(); + path43.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path43.getBounds(); + allPaths.add(path43); +} + +void pathOps44() { + final Path path44 = Path(); + path44.reset(); + path44.moveTo(234.66666666666666, 87); + path44.lineTo(96, 87); + path44.lineTo(96, 86); + path44.lineTo(234.66666666666666, 86); + gBounds = path44.getBounds(); + _runPathTest(path44); + gFillType = path44.fillType; + allPaths.add(path44); +} + +void pathOps45() { + final Path path45 = Path(); + path45.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path45.getBounds(); + allPaths.add(path45); +} + +void pathOps46() { + final Path path46 = Path(); + path46.reset(); + path46.moveTo(234.66666666666666, 87); + path46.lineTo(96, 87); + path46.lineTo(96, 86); + path46.lineTo(234.66666666666666, 86); + gBounds = path46.getBounds(); + _runPathTest(path46); + gFillType = path46.fillType; + allPaths.add(path46); +} + +void pathOps47() { + final Path path47 = Path(); + path47.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path47.getBounds(); + allPaths.add(path47); +} + +void pathOps48() { + final Path path48 = Path(); + path48.reset(); + path48.moveTo(234.66666666666666, 87); + path48.lineTo(96, 87); + path48.lineTo(96, 86); + path48.lineTo(234.66666666666666, 86); + gBounds = path48.getBounds(); + allPaths.add(path48); +} + +void pathOps49() { + final Path path49 = Path(); + path49.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(0, 0, 250.66666666666669, 585), + const Radius.circular(10), + ), + ); + gFillType = path49.fillType; + path50 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path140 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path235 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path298 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path352 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path369 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path382 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path395 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path408 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path421 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path434 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path447 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path460 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path473 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path486 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path499 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path512 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path525 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path538 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path551 = path49.shift(const Offset(638.3333333333333, 0)); + gFillType = path49.fillType; + path564 = path49.shift(const Offset(638.3333333333333, 0)); + allPaths.add(path49); +} + +void pathOps50() { + gBounds = path50.getBounds(); + allPaths.add(path50); +} + +void pathOps51() { + final Path path51 = Path(); + path51.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path51.getBounds(); + allPaths.add(path51); +} + +void pathOps52() { + final Path path52 = Path(); + path52.reset(); + path52.moveTo(889, 86); + path52.lineTo(638.3333333333333, 86); + path52.lineTo(638.3333333333333, 84); + path52.lineTo(889, 84); + gBounds = path52.getBounds(); + _runPathTest(path52); + gFillType = path52.fillType; + allPaths.add(path52); +} + +void pathOps53() { + final Path path53 = Path(); + path53.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 87), Radius.zero), + ); + gBounds = path53.getBounds(); + allPaths.add(path53); +} + +void pathOps54() { + final Path path54 = Path(); + path54.reset(); + path54.moveTo(234.66666666666669, 87); + path54.lineTo(96, 87); + path54.lineTo(96, 86); + path54.lineTo(234.66666666666669, 86); + gBounds = path54.getBounds(); + _runPathTest(path54); + gFillType = path54.fillType; + allPaths.add(path54); +} + +void pathOps55() { + final Path path55 = Path(); + path55.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 87), Radius.zero), + ); + gBounds = path55.getBounds(); + allPaths.add(path55); +} + +void pathOps56() { + final Path path56 = Path(); + path56.reset(); + path56.moveTo(234.66666666666669, 87); + path56.lineTo(96, 87); + path56.lineTo(96, 86); + path56.lineTo(234.66666666666669, 86); + gBounds = path56.getBounds(); + _runPathTest(path56); + gFillType = path56.fillType; + allPaths.add(path56); +} + +void pathOps57() { + final Path path57 = Path(); + path57.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 73), Radius.zero), + ); + gBounds = path57.getBounds(); + allPaths.add(path57); +} + +void pathOps58() { + final Path path58 = Path(); + path58.reset(); + path58.moveTo(234.66666666666669, 73); + path58.lineTo(96, 73); + path58.lineTo(96, 72); + path58.lineTo(234.66666666666669, 72); + gBounds = path58.getBounds(); + _runPathTest(path58); + gFillType = path58.fillType; + allPaths.add(path58); +} + +void pathOps59() { + final Path path59 = Path(); + path59.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 520, 560), const Radius.circular(40)), + ); + gFillType = path59.fillType; + path60 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path82 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path86 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path145 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path240 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path356 = path59.shift(const Offset(450, 136)); + gFillType = path59.fillType; + path606 = path59.shift(const Offset(450, 136)); + allPaths.add(path59); +} + +void pathOps60() { + gBounds = path60.getBounds(); + allPaths.add(path60); +} + +void pathOps61() { + final Path path61 = Path(); + path61.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path61.getBounds(); + allPaths.add(path61); +} + +void pathOps62() { + final Path path62 = Path(); + path62.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 52), const Radius.circular(10)), + ); + gFillType = path62.fillType; + path63 = path62.shift(const Offset(32, 0)); + gFillType = path62.fillType; + path123 = path62.shift(const Offset(32, 0)); + allPaths.add(path62); +} + +void pathOps63() { + gBounds = path63.getBounds(); + allPaths.add(path63); +} + +void pathOps64() { + final Path path64 = Path(); + path64.reset(); + path64.moveTo(56, 40); + path64.lineTo(56, 40); + path64.lineTo(58, 40); + path64.lineTo(58, 40); + gBounds = path64.getBounds(); + allPaths.add(path64); +} + +void pathOps65() { + final Path path65 = Path(); + path65.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 52), const Radius.circular(10)), + ); + gFillType = path65.fillType; + path66 = path65.shift(const Offset(32, 0)); + gFillType = path65.fillType; + path125 = path65.shift(const Offset(32, 0)); + allPaths.add(path65); +} + +void pathOps66() { + gBounds = path66.getBounds(); + allPaths.add(path66); +} + +void pathOps67() { + final Path path67 = Path(); + path67.reset(); + path67.moveTo(56, 40); + path67.lineTo(56, 40); + path67.lineTo(58, 40); + path67.lineTo(58, 40); + gBounds = path67.getBounds(); + allPaths.add(path67); +} + +void pathOps68() { + final Path path68 = Path(); + path68.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 52), const Radius.circular(10)), + ); + gFillType = path68.fillType; + path69 = path68.shift(const Offset(32, 0)); + gFillType = path68.fillType; + path127 = path68.shift(const Offset(32, 0)); + allPaths.add(path68); +} + +void pathOps69() { + gBounds = path69.getBounds(); + allPaths.add(path69); +} + +void pathOps70() { + final Path path70 = Path(); + path70.reset(); + path70.moveTo(56, 40); + path70.lineTo(56, 40); + path70.lineTo(58, 40); + path70.lineTo(58, 40); + gBounds = path70.getBounds(); + allPaths.add(path70); +} + +void pathOps71() { + final Path path71 = Path(); + path71.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 52), const Radius.circular(10)), + ); + gFillType = path71.fillType; + path72 = path71.shift(const Offset(32, 0)); + gFillType = path71.fillType; + path129 = path71.shift(const Offset(32, 0)); + allPaths.add(path71); +} + +void pathOps72() { + gBounds = path72.getBounds(); + allPaths.add(path72); +} + +void pathOps73() { + final Path path73 = Path(); + path73.reset(); + path73.moveTo(56, 40); + path73.lineTo(56, 40); + path73.lineTo(58, 40); + path73.lineTo(58, 40); + gBounds = path73.getBounds(); + allPaths.add(path73); +} + +void pathOps74() { + final Path path74 = Path(); + path74.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 52), const Radius.circular(10)), + ); + gFillType = path74.fillType; + path75 = path74.shift(const Offset(32, 0)); + gFillType = path74.fillType; + path132 = path74.shift(const Offset(32, 0)); + allPaths.add(path74); +} + +void pathOps75() { + gBounds = path75.getBounds(); + allPaths.add(path75); +} + +void pathOps76() { + final Path path76 = Path(); + path76.reset(); + path76.moveTo(56, 40); + path76.lineTo(56, 40); + path76.lineTo(58, 40); + path76.lineTo(58, 40); + gBounds = path76.getBounds(); + allPaths.add(path76); +} + +void pathOps77() { + final Path path77 = Path(); + path77.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 50), const Radius.circular(10)), + ); + gFillType = path77.fillType; + path78 = path77.shift(const Offset(32, 0)); + gFillType = path77.fillType; + path131 = path77.shift(const Offset(32, 0)); + allPaths.add(path77); +} + +void pathOps78() { + gBounds = path78.getBounds(); + allPaths.add(path78); +} + +void pathOps79() { + final Path path79 = Path(); + path79.addRRect( + RRect.fromRectAndCorners( + const Rect.fromLTRB(0, 0, 64, 56), + bottomLeft: const Radius.circular(10), + ), + ); + gFillType = path79.fillType; + path80 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path84 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path88 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path147 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path242 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path358 = path79.shift(const Offset(906, 136)); + gFillType = path79.fillType; + path608 = path79.shift(const Offset(906, 136)); + allPaths.add(path79); +} + +void pathOps80() { + gBounds = path80.getBounds(); + allPaths.add(path80); +} + +void pathOps81() { + final Path path81 = Path(); + path81.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path81.getBounds(); + allPaths.add(path81); +} + +void pathOps82() { + gBounds = path82.getBounds(); + allPaths.add(path82); +} + +void pathOps83() { + final Path path83 = Path(); + path83.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path83.getBounds(); + allPaths.add(path83); +} + +void pathOps84() { + gBounds = path84.getBounds(); + allPaths.add(path84); +} + +void pathOps85() { + final Path path85 = Path(); + path85.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path85.getBounds(); + allPaths.add(path85); +} + +void pathOps86() { + gBounds = path86.getBounds(); + allPaths.add(path86); +} + +void pathOps87() { + final Path path87 = Path(); + path87.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path87.getBounds(); + allPaths.add(path87); +} + +void pathOps88() { + gBounds = path88.getBounds(); + allPaths.add(path88); +} + +void pathOps89() { + final Path path89 = Path(); + path89.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 87), Radius.zero), + ); + gBounds = path89.getBounds(); + allPaths.add(path89); +} + +void pathOps90() { + final Path path90 = Path(); + path90.reset(); + path90.moveTo(234.66666666666669, 87); + path90.lineTo(96, 87); + path90.lineTo(96, 86); + path90.lineTo(234.66666666666669, 86); + gBounds = path90.getBounds(); + _runPathTest(path90); + gFillType = path90.fillType; + allPaths.add(path90); +} + +void pathOps91() { + final Path path91 = Path(); + path91.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 73), Radius.zero), + ); + gBounds = path91.getBounds(); + allPaths.add(path91); +} + +void pathOps92() { + final Path path92 = Path(); + path92.reset(); + path92.moveTo(234.66666666666669, 73); + path92.lineTo(96, 73); + path92.lineTo(96, 72); + path92.lineTo(234.66666666666669, 72); + gBounds = path92.getBounds(); + _runPathTest(path92); + gFillType = path92.fillType; + allPaths.add(path92); +} + +void pathOps93() { + final Path path93 = Path(); + path93.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path93.getBounds(); + allPaths.add(path93); +} + +void pathOps94() { + final Path path94 = Path(); + path94.reset(); + path94.moveTo(234.66666666666666, 101); + path94.lineTo(96, 101); + path94.lineTo(96, 100); + path94.lineTo(234.66666666666666, 100); + gBounds = path94.getBounds(); + _runPathTest(path94); + gFillType = path94.fillType; + allPaths.add(path94); +} + +void pathOps95() { + final Path path95 = Path(); + path95.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path95.getBounds(); + allPaths.add(path95); +} + +void pathOps96() { + final Path path96 = Path(); + path96.reset(); + path96.moveTo(234.66666666666666, 87); + path96.lineTo(96, 87); + path96.lineTo(96, 86); + path96.lineTo(234.66666666666666, 86); + gBounds = path96.getBounds(); + _runPathTest(path96); + gFillType = path96.fillType; + allPaths.add(path96); +} + +void pathOps97() { + final Path path97 = Path(); + path97.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path97.getBounds(); + allPaths.add(path97); +} + +void pathOps98() { + final Path path98 = Path(); + path98.reset(); + path98.moveTo(234.66666666666666, 101); + path98.lineTo(96, 101); + path98.lineTo(96, 100); + path98.lineTo(234.66666666666666, 100); + gBounds = path98.getBounds(); + _runPathTest(path98); + gFillType = path98.fillType; + allPaths.add(path98); +} + +void pathOps99() { + final Path path99 = Path(); + path99.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 101), Radius.zero), + ); + gBounds = path99.getBounds(); + allPaths.add(path99); +} + +void pathOps100() { + final Path path100 = Path(); + path100.reset(); + path100.moveTo(234.66666666666666, 101); + path100.lineTo(96, 101); + path100.lineTo(96, 100); + path100.lineTo(234.66666666666666, 100); + gBounds = path100.getBounds(); + _runPathTest(path100); + gFillType = path100.fillType; + allPaths.add(path100); +} + +void pathOps101() { + final Path path101 = Path(); + path101.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 73), Radius.zero), + ); + gBounds = path101.getBounds(); + allPaths.add(path101); +} + +void pathOps102() { + final Path path102 = Path(); + path102.reset(); + path102.moveTo(234.66666666666666, 73); + path102.lineTo(96, 73); + path102.lineTo(96, 72); + path102.lineTo(234.66666666666666, 72); + gBounds = path102.getBounds(); + _runPathTest(path102); + gFillType = path102.fillType; + allPaths.add(path102); +} + +void pathOps103() { + final Path path103 = Path(); + path103.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path103.getBounds(); + allPaths.add(path103); +} + +void pathOps104() { + final Path path104 = Path(); + path104.reset(); + path104.moveTo(234.66666666666666, 87); + path104.lineTo(96, 87); + path104.lineTo(96, 86); + path104.lineTo(234.66666666666666, 86); + gBounds = path104.getBounds(); + allPaths.add(path104); +} + +void pathOps105() { + final Path path105 = Path(); + path105.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path105.getBounds(); + allPaths.add(path105); +} + +void pathOps106() { + final Path path106 = Path(); + path106.reset(); + path106.moveTo(234.66666666666666, 87); + path106.lineTo(96, 87); + path106.lineTo(96, 86); + path106.lineTo(234.66666666666666, 86); + gBounds = path106.getBounds(); + allPaths.add(path106); +} + +void pathOps107() { + final Path path107 = Path(); + path107.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path107.getBounds(); + allPaths.add(path107); +} + +void pathOps108() { + final Path path108 = Path(); + path108.reset(); + path108.moveTo(234.66666666666666, 87); + path108.lineTo(96, 87); + path108.lineTo(96, 86); + path108.lineTo(234.66666666666666, 86); + gBounds = path108.getBounds(); + _runPathTest(path108); + gFillType = path108.fillType; + allPaths.add(path108); +} + +void pathOps109() { + final Path path109 = Path(); + path109.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path109.getBounds(); + allPaths.add(path109); +} + +void pathOps110() { + final Path path110 = Path(); + path110.reset(); + path110.moveTo(234.66666666666666, 87); + path110.lineTo(96, 87); + path110.lineTo(96, 86); + path110.lineTo(234.66666666666666, 86); + gBounds = path110.getBounds(); + _runPathTest(path110); + gFillType = path110.fillType; + allPaths.add(path110); +} + +void pathOps111() { + final Path path111 = Path(); + path111.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path111.getBounds(); + allPaths.add(path111); +} + +void pathOps112() { + final Path path112 = Path(); + path112.reset(); + path112.moveTo(234.66666666666666, 87); + path112.lineTo(96, 87); + path112.lineTo(96, 86); + path112.lineTo(234.66666666666666, 86); + gBounds = path112.getBounds(); + _runPathTest(path112); + gFillType = path112.fillType; + allPaths.add(path112); +} + +void pathOps113() { + final Path path113 = Path(); + path113.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path113.getBounds(); + allPaths.add(path113); +} + +void pathOps114() { + final Path path114 = Path(); + path114.reset(); + path114.moveTo(234.66666666666666, 87); + path114.lineTo(96, 87); + path114.lineTo(96, 86); + path114.lineTo(234.66666666666666, 86); + gBounds = path114.getBounds(); + _runPathTest(path114); + gFillType = path114.fillType; + allPaths.add(path114); +} + +void pathOps115() { + final Path path115 = Path(); + path115.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 87), Radius.zero), + ); + gBounds = path115.getBounds(); + allPaths.add(path115); +} + +void pathOps116() { + final Path path116 = Path(); + path116.reset(); + path116.moveTo(234.66666666666666, 87); + path116.lineTo(96, 87); + path116.lineTo(96, 86); + path116.lineTo(234.66666666666666, 86); + gBounds = path116.getBounds(); + allPaths.add(path116); +} + +void pathOps117() { + final Path path117 = Path(); + path117.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 87), Radius.zero), + ); + gBounds = path117.getBounds(); + allPaths.add(path117); +} + +void pathOps118() { + final Path path118 = Path(); + path118.reset(); + path118.moveTo(234.66666666666669, 87); + path118.lineTo(96, 87); + path118.lineTo(96, 86); + path118.lineTo(234.66666666666669, 86); + gBounds = path118.getBounds(); + _runPathTest(path118); + gFillType = path118.fillType; + allPaths.add(path118); +} + +void pathOps119() { + gBounds = path119.getBounds(); + allPaths.add(path119); +} + +void pathOps120() { + gBounds = path120.getBounds(); + allPaths.add(path120); +} + +void pathOps121() { + gBounds = path121.getBounds(); + allPaths.add(path121); +} + +void pathOps122() { + gBounds = path122.getBounds(); + allPaths.add(path122); +} + +void pathOps123() { + gBounds = path123.getBounds(); + allPaths.add(path123); +} + +void pathOps124() { + final Path path124 = Path(); + path124.reset(); + path124.moveTo(56, 40); + path124.lineTo(56, 40); + path124.lineTo(58, 40); + path124.lineTo(58, 40); + gBounds = path124.getBounds(); + allPaths.add(path124); +} + +void pathOps125() { + gBounds = path125.getBounds(); + allPaths.add(path125); +} + +void pathOps126() { + final Path path126 = Path(); + path126.reset(); + path126.moveTo(56, 40); + path126.lineTo(56, 40); + path126.lineTo(58, 40); + path126.lineTo(58, 40); + gBounds = path126.getBounds(); + allPaths.add(path126); +} + +void pathOps127() { + gBounds = path127.getBounds(); + allPaths.add(path127); +} + +void pathOps128() { + final Path path128 = Path(); + path128.reset(); + path128.moveTo(56, 40); + path128.lineTo(56, 40); + path128.lineTo(58, 40); + path128.lineTo(58, 40); + gBounds = path128.getBounds(); + allPaths.add(path128); +} + +void pathOps129() { + gBounds = path129.getBounds(); + allPaths.add(path129); +} + +void pathOps130() { + final Path path130 = Path(); + path130.reset(); + path130.moveTo(56, 40); + path130.lineTo(56, 40); + path130.lineTo(58, 40); + path130.lineTo(58, 40); + gBounds = path130.getBounds(); + allPaths.add(path130); +} + +void pathOps131() { + gBounds = path131.getBounds(); + allPaths.add(path131); +} + +void pathOps132() { + gBounds = path132.getBounds(); + allPaths.add(path132); +} + +void pathOps133() { + final Path path133 = Path(); + path133.reset(); + path133.moveTo(56, 40); + path133.lineTo(56, 40); + path133.lineTo(58, 40); + path133.lineTo(58, 40); + gBounds = path133.getBounds(); + allPaths.add(path133); +} + +void pathOps134() { + gBounds = path134.getBounds(); + allPaths.add(path134); +} + +void pathOps135() { + final Path path135 = Path(); + path135.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path135.getBounds(); + allPaths.add(path135); +} + +void pathOps136() { + final Path path136 = Path(); + path136.reset(); + path136.moveTo(331.66666666666663, 86); + path136.lineTo(81, 86); + path136.lineTo(81, 84); + path136.lineTo(331.66666666666663, 84); + gBounds = path136.getBounds(); + _runPathTest(path136); + gFillType = path136.fillType; + allPaths.add(path136); +} + +void pathOps137() { + gBounds = path137.getBounds(); + allPaths.add(path137); +} + +void pathOps138() { + final Path path138 = Path(); + path138.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path138.getBounds(); + allPaths.add(path138); +} + +void pathOps139() { + final Path path139 = Path(); + path139.reset(); + path139.moveTo(610.3333333333333, 86); + path139.lineTo(359.66666666666663, 86); + path139.lineTo(359.66666666666663, 84); + path139.lineTo(610.3333333333333, 84); + gBounds = path139.getBounds(); + _runPathTest(path139); + gFillType = path139.fillType; + allPaths.add(path139); +} + +void pathOps140() { + gBounds = path140.getBounds(); + allPaths.add(path140); +} + +void pathOps141() { + final Path path141 = Path(); + path141.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path141.getBounds(); + allPaths.add(path141); +} + +void pathOps142() { + final Path path142 = Path(); + path142.reset(); + path142.moveTo(889, 86); + path142.lineTo(638.3333333333333, 86); + path142.lineTo(638.3333333333333, 84); + path142.lineTo(889, 84); + gBounds = path142.getBounds(); + _runPathTest(path142); + gFillType = path142.fillType; + allPaths.add(path142); +} + +void pathOps143() { + gBounds = path143.getBounds(); + allPaths.add(path143); +} + +void pathOps144() { + final Path path144 = Path(); + path144.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path144.getBounds(); + allPaths.add(path144); +} + +void pathOps145() { + gBounds = path145.getBounds(); + allPaths.add(path145); +} + +void pathOps146() { + final Path path146 = Path(); + path146.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path146.getBounds(); + allPaths.add(path146); +} + +void pathOps147() { + gBounds = path147.getBounds(); + allPaths.add(path147); +} + +void pathOps149() { + final Path path149 = Path(); + path149.reset(); + path149.moveTo(-2, 0); + path149.cubicTo(-2, -1.100000023841858, -1.100000023841858, -2, 0, -2); + path149.cubicTo(1.100000023841858, -2, 2, -1.100000023841858, 2, 0); + path149.cubicTo(2, 1.100000023841858, 1.100000023841858, 2, 0, 2); + path149.cubicTo(-1.100000023841858, 2, -2, 1.100000023841858, -2, 0); + allPaths.add(path149); +} + +void pathOps150() { + final Path path150 = Path(); + path150.reset(); + path150.fillType = PathFillType.nonZero; + gBounds = path150.getBounds(); + _runPathTest(path150); + gFillType = path150.fillType; + path150.fillType = PathFillType.nonZero; + gBounds = path150.getBounds(); + _runPathTest(path150); + gFillType = path150.fillType; + path150.fillType = PathFillType.nonZero; + gBounds = path150.getBounds(); + allPaths.add(path150); +} + +void pathOps155() { + final Path path155 = Path(); + path155.reset(); + path155.moveTo(-3, -9); + path155.cubicTo(-3, -10.649999618530273, -1.649999976158142, -12, 0, -12); + path155.cubicTo(0, -12, 0, -12, 0, -12); + path155.cubicTo(1.649999976158142, -12, 3, -10.649999618530273, 3, -9); + path155.cubicTo(3, -9, 3, 9, 3, 9); + path155.cubicTo(3, 10.649999618530273, 1.649999976158142, 12, 0, 12); + path155.cubicTo(0, 12, 0, 12, 0, 12); + path155.cubicTo(-1.649999976158142, 12, -3, 10.649999618530273, -3, 9); + path155.cubicTo(-3, 9, -3, -9, -3, -9); + path155.close(); + allPaths.add(path155); +} + +void pathOps156() { + final Path path156 = Path(); + path156.reset(); + path156.fillType = PathFillType.nonZero; + gBounds = path156.getBounds(); + _runPathTest(path156); + gFillType = path156.fillType; + path156.fillType = PathFillType.nonZero; + gBounds = path156.getBounds(); + _runPathTest(path156); + gFillType = path156.fillType; + path156.fillType = PathFillType.nonZero; + gBounds = path156.getBounds(); + allPaths.add(path156); +} + +void pathOps161() { + final Path path161 = Path(); + path161.reset(); + path161.moveTo(-2, -10); + path161.cubicTo(-2, -11.100000381469727, -1.100000023841858, -12, 0, -12); + path161.cubicTo(0, -12, 0, -12, 0, -12); + path161.cubicTo(1.100000023841858, -12, 2, -11.100000381469727, 2, -10); + path161.cubicTo(2, -10, 2, 10, 2, 10); + path161.cubicTo(2, 11.100000381469727, 1.100000023841858, 12, 0, 12); + path161.cubicTo(0, 12, 0, 12, 0, 12); + path161.cubicTo(-1.100000023841858, 12, -2, 11.100000381469727, -2, 10); + path161.cubicTo(-2, 10, -2, -10, -2, -10); + path161.close(); + allPaths.add(path161); +} + +void pathOps162() { + final Path path162 = Path(); + path162.reset(); + path162.fillType = PathFillType.nonZero; + gBounds = path162.getBounds(); + _runPathTest(path162); + gFillType = path162.fillType; + path162.fillType = PathFillType.nonZero; + gBounds = path162.getBounds(); + _runPathTest(path162); + gFillType = path162.fillType; + path162.fillType = PathFillType.nonZero; + gBounds = path162.getBounds(); + allPaths.add(path162); +} + +void pathOps164() { + final Path path164 = Path(); + path164.reset(); + path164.moveTo(-3, -9); + path164.cubicTo(-3, -10.649999618530273, -1.649999976158142, -12, 0, -12); + path164.cubicTo(0, -12, 0, -12, 0, -12); + path164.cubicTo(1.649999976158142, -12, 3, -10.649999618530273, 3, -9); + path164.cubicTo(3, -9, 3, 9, 3, 9); + path164.cubicTo(3, 10.649999618530273, 1.649999976158142, 12, 0, 12); + path164.cubicTo(0, 12, 0, 12, 0, 12); + path164.cubicTo(-1.649999976158142, 12, -3, 10.649999618530273, -3, 9); + path164.cubicTo(-3, 9, -3, -9, -3, -9); + path164.close(); + allPaths.add(path164); +} + +void pathOps165() { + final Path path165 = Path(); + path165.reset(); + path165.fillType = PathFillType.nonZero; + gBounds = path165.getBounds(); + _runPathTest(path165); + gFillType = path165.fillType; + path165.fillType = PathFillType.nonZero; + gBounds = path165.getBounds(); + _runPathTest(path165); + gFillType = path165.fillType; + path165.fillType = PathFillType.nonZero; + gBounds = path165.getBounds(); + allPaths.add(path165); +} + +void pathOps167() { + final Path path167 = Path(); + path167.reset(); + path167.moveTo(2, 0); + path167.cubicTo(2, 1.1100000143051147, 1.100000023841858, 2, 0, 2); + path167.cubicTo(-1.1100000143051147, 2, -2, 1.1100000143051147, -2, 0); + path167.cubicTo(-2, -1.100000023841858, -1.1100000143051147, -2, 0, -2); + path167.cubicTo(1.100000023841858, -2, 2, -1.100000023841858, 2, 0); + allPaths.add(path167); +} + +void pathOps168() { + final Path path168 = Path(); + path168.reset(); + path168.fillType = PathFillType.nonZero; + gBounds = path168.getBounds(); + _runPathTest(path168); + gFillType = path168.fillType; + path168.fillType = PathFillType.nonZero; + gBounds = path168.getBounds(); + _runPathTest(path168); + gFillType = path168.fillType; + path168.fillType = PathFillType.nonZero; + gBounds = path168.getBounds(); + allPaths.add(path168); +} + +void pathOps173() { + final Path path173 = Path(); + path173.reset(); + path173.moveTo(-2, -10); + path173.cubicTo(-2, -11.100000381469727, -1.100000023841858, -12, 0, -12); + path173.cubicTo(0, -12, 0, -12, 0, -12); + path173.cubicTo(1.100000023841858, -12, 2, -11.100000381469727, 2, -10); + path173.cubicTo(2, -10, 2, 10, 2, 10); + path173.cubicTo(2, 11.100000381469727, 1.100000023841858, 12, 0, 12); + path173.cubicTo(0, 12, 0, 12, 0, 12); + path173.cubicTo(-1.100000023841858, 12, -2, 11.100000381469727, -2, 10); + path173.cubicTo(-2, 10, -2, -10, -2, -10); + path173.close(); + allPaths.add(path173); +} + +void pathOps174() { + final Path path174 = Path(); + path174.reset(); + path174.fillType = PathFillType.nonZero; + gBounds = path174.getBounds(); + _runPathTest(path174); + gFillType = path174.fillType; + path174.fillType = PathFillType.nonZero; + gBounds = path174.getBounds(); + _runPathTest(path174); + gFillType = path174.fillType; + path174.fillType = PathFillType.nonZero; + gBounds = path174.getBounds(); + allPaths.add(path174); +} + +void pathOps178() { + final Path path178 = Path(); + path178.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 105), Radius.zero), + ); + gBounds = path178.getBounds(); + allPaths.add(path178); +} + +void pathOps179() { + final Path path179 = Path(); + path179.reset(); + path179.moveTo(234.66666666666669, 105); + path179.lineTo(96, 105); + path179.lineTo(96, 104); + path179.lineTo(234.66666666666669, 104); + gBounds = path179.getBounds(); + _runPathTest(path179); + gFillType = path179.fillType; + allPaths.add(path179); +} + +void pathOps180() { + final Path path180 = Path(); + path180.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 94), Radius.zero), + ); + gBounds = path180.getBounds(); + allPaths.add(path180); +} + +void pathOps181() { + final Path path181 = Path(); + path181.reset(); + path181.moveTo(234.66666666666669, 94); + path181.lineTo(96, 94); + path181.lineTo(96, 93); + path181.lineTo(234.66666666666669, 93); + gBounds = path181.getBounds(); + _runPathTest(path181); + gFillType = path181.fillType; + allPaths.add(path181); +} + +void pathOps182() { + final Path path182 = Path(); + path182.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path182.getBounds(); + allPaths.add(path182); +} + +void pathOps183() { + final Path path183 = Path(); + path183.reset(); + path183.moveTo(234.66666666666666, 105); + path183.lineTo(96, 105); + path183.lineTo(96, 104); + path183.lineTo(234.66666666666666, 104); + gBounds = path183.getBounds(); + allPaths.add(path183); +} + +void pathOps184() { + final Path path184 = Path(); + path184.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path184.getBounds(); + allPaths.add(path184); +} + +void pathOps185() { + final Path path185 = Path(); + path185.reset(); + path185.moveTo(234.66666666666666, 105); + path185.lineTo(96, 105); + path185.lineTo(96, 104); + path185.lineTo(234.66666666666666, 104); + gBounds = path185.getBounds(); + _runPathTest(path185); + gFillType = path185.fillType; + allPaths.add(path185); +} + +void pathOps186() { + final Path path186 = Path(); + path186.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path186.getBounds(); + allPaths.add(path186); +} + +void pathOps187() { + final Path path187 = Path(); + path187.reset(); + path187.moveTo(234.66666666666666, 120); + path187.lineTo(96, 120); + path187.lineTo(96, 119); + path187.lineTo(234.66666666666666, 119); + gBounds = path187.getBounds(); + _runPathTest(path187); + gFillType = path187.fillType; + allPaths.add(path187); +} + +void pathOps188() { + final Path path188 = Path(); + path188.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 139), Radius.zero), + ); + gBounds = path188.getBounds(); + allPaths.add(path188); +} + +void pathOps189() { + final Path path189 = Path(); + path189.reset(); + path189.moveTo(234.66666666666666, 139); + path189.lineTo(96, 139); + path189.lineTo(96, 138); + path189.lineTo(234.66666666666666, 138); + gBounds = path189.getBounds(); + _runPathTest(path189); + gFillType = path189.fillType; + allPaths.add(path189); +} + +void pathOps190() { + final Path path190 = Path(); + path190.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 75), Radius.zero), + ); + gBounds = path190.getBounds(); + allPaths.add(path190); +} + +void pathOps191() { + final Path path191 = Path(); + path191.reset(); + path191.moveTo(234.66666666666666, 75); + path191.lineTo(96, 75); + path191.lineTo(96, 74); + path191.lineTo(234.66666666666666, 74); + gBounds = path191.getBounds(); + _runPathTest(path191); + gFillType = path191.fillType; + allPaths.add(path191); +} + +void pathOps192() { + final Path path192 = Path(); + path192.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path192.getBounds(); + allPaths.add(path192); +} + +void pathOps193() { + final Path path193 = Path(); + path193.reset(); + path193.moveTo(234.66666666666666, 90); + path193.lineTo(96, 90); + path193.lineTo(96, 89); + path193.lineTo(234.66666666666666, 89); + gBounds = path193.getBounds(); + allPaths.add(path193); +} + +void pathOps194() { + final Path path194 = Path(); + path194.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + allPaths.add(path194); +} + +void pathOps195() { + final Path path195 = Path(); + path195.reset(); + path195.moveTo(234.66666666666666, 105); + path195.lineTo(96, 105); + path195.lineTo(96, 104); + path195.lineTo(234.66666666666666, 104); + gBounds = path195.getBounds(); + allPaths.add(path195); +} + +void pathOps196() { + final Path path196 = Path(); + path196.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path196.getBounds(); + allPaths.add(path196); +} + +void pathOps197() { + final Path path197 = Path(); + path197.reset(); + path197.moveTo(234.66666666666666, 90); + path197.lineTo(96, 90); + path197.lineTo(96, 89); + path197.lineTo(234.66666666666666, 89); + gBounds = path197.getBounds(); + _runPathTest(path197); + gFillType = path197.fillType; + allPaths.add(path197); +} + +void pathOps198() { + final Path path198 = Path(); + path198.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path198.getBounds(); + allPaths.add(path198); +} + +void pathOps199() { + final Path path199 = Path(); + path199.reset(); + path199.moveTo(234.66666666666666, 90); + path199.lineTo(96, 90); + path199.lineTo(96, 89); + path199.lineTo(234.66666666666666, 89); + gBounds = path199.getBounds(); + _runPathTest(path199); + gFillType = path199.fillType; + allPaths.add(path199); +} + +void pathOps200() { + final Path path200 = Path(); + path200.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path200.getBounds(); + allPaths.add(path200); +} + +void pathOps201() { + final Path path201 = Path(); + path201.reset(); + path201.moveTo(234.66666666666666, 90); + path201.lineTo(96, 90); + path201.lineTo(96, 89); + path201.lineTo(234.66666666666666, 89); + gBounds = path201.getBounds(); + _runPathTest(path201); + gFillType = path201.fillType; + allPaths.add(path201); +} + +void pathOps202() { + final Path path202 = Path(); + path202.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path202.getBounds(); + allPaths.add(path202); +} + +void pathOps203() { + final Path path203 = Path(); + path203.reset(); + path203.moveTo(234.66666666666666, 90); + path203.lineTo(96, 90); + path203.lineTo(96, 89); + path203.lineTo(234.66666666666666, 89); + gBounds = path203.getBounds(); + _runPathTest(path203); + gFillType = path203.fillType; + allPaths.add(path203); +} + +void pathOps204() { + final Path path204 = Path(); + path204.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path204.getBounds(); + allPaths.add(path204); +} + +void pathOps205() { + final Path path205 = Path(); + path205.reset(); + path205.moveTo(234.66666666666666, 90); + path205.lineTo(96, 90); + path205.lineTo(96, 89); + path205.lineTo(234.66666666666666, 89); + gBounds = path205.getBounds(); + allPaths.add(path205); +} + +void pathOps206() { + final Path path206 = Path(); + path206.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 90), Radius.zero), + ); + gBounds = path206.getBounds(); + allPaths.add(path206); +} + +void pathOps207() { + final Path path207 = Path(); + path207.reset(); + path207.moveTo(234.66666666666669, 90); + path207.lineTo(96, 90); + path207.lineTo(96, 89); + path207.lineTo(234.66666666666669, 89); + gBounds = path207.getBounds(); + _runPathTest(path207); + gFillType = path207.fillType; + allPaths.add(path207); +} + +void pathOps208() { + gBounds = path208.getBounds(); + allPaths.add(path208); +} + +void pathOps209() { + gBounds = path209.getBounds(); + allPaths.add(path209); +} + +void pathOps210() { + gBounds = path210.getBounds(); + allPaths.add(path210); +} + +void pathOps211() { + gBounds = path211.getBounds(); + allPaths.add(path211); +} + +void pathOps212() { + final Path path212 = Path(); + path212.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 54), const Radius.circular(10)), + ); + gFillType = path212.fillType; + path213 = path212.shift(const Offset(32, 0)); + gFillType = path212.fillType; + path290 = path212.shift(const Offset(32, 0)); + gFillType = path212.fillType; + path334 = path212.shift(const Offset(32, 0)); + gFillType = path212.fillType; + path595 = path212.shift(const Offset(32, 0)); + allPaths.add(path212); +} + +void pathOps213() { + gBounds = path213.getBounds(); + allPaths.add(path213); +} + +void pathOps214() { + final Path path214 = Path(); + path214.reset(); + path214.moveTo(56, 42); + path214.lineTo(56, 42); + path214.lineTo(58, 42); + path214.lineTo(58, 42); + gBounds = path214.getBounds(); + allPaths.add(path214); +} + +void pathOps215() { + final Path path215 = Path(); + path215.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 54), const Radius.circular(10)), + ); + gFillType = path215.fillType; + path216 = path215.shift(const Offset(32, 0)); + gFillType = path215.fillType; + path288 = path215.shift(const Offset(32, 0)); + gFillType = path215.fillType; + path336 = path215.shift(const Offset(32, 0)); + gFillType = path215.fillType; + path597 = path215.shift(const Offset(32, 0)); + allPaths.add(path215); +} + +void pathOps216() { + gBounds = path216.getBounds(); + allPaths.add(path216); +} + +void pathOps217() { + final Path path217 = Path(); + path217.reset(); + path217.moveTo(56, 42); + path217.lineTo(56, 42); + path217.lineTo(58, 42); + path217.lineTo(58, 42); + gBounds = path217.getBounds(); + allPaths.add(path217); +} + +void pathOps218() { + final Path path218 = Path(); + path218.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 54), const Radius.circular(10)), + ); + gFillType = path218.fillType; + path219 = path218.shift(const Offset(32, 0)); + gFillType = path218.fillType; + path286 = path218.shift(const Offset(32, 0)); + gFillType = path218.fillType; + path338 = path218.shift(const Offset(32, 0)); + gFillType = path218.fillType; + path599 = path218.shift(const Offset(32, 0)); + allPaths.add(path218); +} + +void pathOps219() { + gBounds = path219.getBounds(); + allPaths.add(path219); +} + +void pathOps220() { + final Path path220 = Path(); + path220.reset(); + path220.moveTo(56, 42); + path220.lineTo(56, 42); + path220.lineTo(58, 42); + path220.lineTo(58, 42); + gBounds = path220.getBounds(); + allPaths.add(path220); +} + +void pathOps221() { + final Path path221 = Path(); + path221.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 54), const Radius.circular(10)), + ); + gFillType = path221.fillType; + path222 = path221.shift(const Offset(32, 0)); + gFillType = path221.fillType; + path284 = path221.shift(const Offset(32, 0)); + gFillType = path221.fillType; + path340 = path221.shift(const Offset(32, 0)); + gFillType = path221.fillType; + path601 = path221.shift(const Offset(32, 0)); + allPaths.add(path221); +} + +void pathOps222() { + gBounds = path222.getBounds(); + allPaths.add(path222); +} + +void pathOps223() { + final Path path223 = Path(); + path223.reset(); + path223.moveTo(56, 42); + path223.lineTo(56, 42); + path223.lineTo(58, 42); + path223.lineTo(58, 42); + gBounds = path223.getBounds(); + allPaths.add(path223); +} + +void pathOps224() { + final Path path224 = Path(); + path224.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 51), const Radius.circular(10)), + ); + gFillType = path224.fillType; + path225 = path224.shift(const Offset(32, 0)); + gFillType = path224.fillType; + path281 = path224.shift(const Offset(32, 0)); + gFillType = path224.fillType; + path344 = path224.shift(const Offset(32, 0)); + allPaths.add(path224); +} + +void pathOps225() { + gBounds = path225.getBounds(); + allPaths.add(path225); +} + +void pathOps226() { + final Path path226 = Path(); + path226.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 456, 54), const Radius.circular(10)), + ); + gFillType = path226.fillType; + path227 = path226.shift(const Offset(32, 0)); + gFillType = path226.fillType; + path282 = path226.shift(const Offset(32, 0)); + gFillType = path226.fillType; + path342 = path226.shift(const Offset(32, 0)); + gFillType = path226.fillType; + path603 = path226.shift(const Offset(32, 0)); + allPaths.add(path226); +} + +void pathOps227() { + gBounds = path227.getBounds(); + allPaths.add(path227); +} + +void pathOps228() { + final Path path228 = Path(); + path228.reset(); + path228.moveTo(56, 42); + path228.lineTo(56, 42); + path228.lineTo(58, 42); + path228.lineTo(58, 42); + gBounds = path228.getBounds(); + allPaths.add(path228); +} + +void pathOps229() { + gBounds = path229.getBounds(); + allPaths.add(path229); +} + +void pathOps230() { + final Path path230 = Path(); + path230.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path230.getBounds(); + allPaths.add(path230); +} + +void pathOps231() { + final Path path231 = Path(); + path231.reset(); + path231.moveTo(331.66666666666663, 86); + path231.lineTo(81, 86); + path231.lineTo(81, 84); + path231.lineTo(331.66666666666663, 84); + gBounds = path231.getBounds(); + _runPathTest(path231); + gFillType = path231.fillType; + allPaths.add(path231); +} + +void pathOps232() { + gBounds = path232.getBounds(); + allPaths.add(path232); +} + +void pathOps233() { + final Path path233 = Path(); + path233.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path233.getBounds(); + allPaths.add(path233); +} + +void pathOps234() { + final Path path234 = Path(); + path234.reset(); + path234.moveTo(610.3333333333333, 86); + path234.lineTo(359.66666666666663, 86); + path234.lineTo(359.66666666666663, 84); + path234.lineTo(610.3333333333333, 84); + gBounds = path234.getBounds(); + _runPathTest(path234); + gFillType = path234.fillType; + allPaths.add(path234); +} + +void pathOps235() { + gBounds = path235.getBounds(); + allPaths.add(path235); +} + +void pathOps236() { + final Path path236 = Path(); + path236.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path236.getBounds(); + allPaths.add(path236); +} + +void pathOps237() { + final Path path237 = Path(); + path237.reset(); + path237.moveTo(889, 86); + path237.lineTo(638.3333333333333, 86); + path237.lineTo(638.3333333333333, 84); + path237.lineTo(889, 84); + gBounds = path237.getBounds(); + _runPathTest(path237); + gFillType = path237.fillType; + allPaths.add(path237); +} + +void pathOps238() { + gBounds = path238.getBounds(); + allPaths.add(path238); +} + +void pathOps239() { + final Path path239 = Path(); + path239.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path239.getBounds(); + gBounds = path239.getBounds(); + gBounds = path239.getBounds(); + allPaths.add(path239); +} + +void pathOps240() { + gBounds = path240.getBounds(); + gBounds = path240.getBounds(); + gBounds = path240.getBounds(); + allPaths.add(path240); +} + +void pathOps241() { + final Path path241 = Path(); + path241.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path241.getBounds(); + gBounds = path241.getBounds(); + gBounds = path241.getBounds(); + allPaths.add(path241); +} + +void pathOps242() { + gBounds = path242.getBounds(); + allPaths.add(path242); +} + +void pathOps243() { + final Path path243 = Path(); + path243.moveTo(-3.0000008960834634, -8.999999251003146); + path243.cubicTo( + -3.0000008960834634, + -10.64999886953342, + -1.6500008722416055, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + ); + path243.cubicTo( + -8.960834634308412e-7, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + ); + path243.cubicTo( + 1.6499990800746787, + -11.999999251003146, + 2.9999991039165366, + -10.64999886953342, + 2.9999991039165366, + -8.999999251003146, + ); + path243.cubicTo( + 2.9999991039165366, + -8.999999251003146, + 2.9999991039165366, + 9.000000748996854, + 2.9999991039165366, + 9.000000748996854, + ); + path243.cubicTo( + 2.9999991039165366, + 10.650000367527127, + 1.6499990800746787, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + ); + path243.cubicTo( + -8.960834634308412e-7, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + ); + path243.cubicTo( + -1.6500008722416055, + 12.000000748996854, + -3.0000008960834634, + 10.650000367527127, + -3.0000008960834634, + 9.000000748996854, + ); + path243.cubicTo( + -3.0000008960834634, + 9.000000748996854, + -3.0000008960834634, + -8.999999251003146, + -3.0000008960834634, + -8.999999251003146, + ); + path243.close(); + allPaths.add(path243); +} + +void pathOps244() { + final Path path244 = Path(); + path244.moveTo(-2.0000008960834634, -9.999999251003146); + path244.cubicTo( + -2.0000008960834634, + -11.099999632472873, + -1.1000009199253213, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + ); + path244.cubicTo( + -8.960834634308412e-7, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + -8.960834634308412e-7, + -11.999999251003146, + ); + path244.cubicTo( + 1.0999991277583945, + -11.999999251003146, + 1.9999991039165366, + -11.099999632472873, + 1.9999991039165366, + -9.999999251003146, + ); + path244.cubicTo( + 1.9999991039165366, + -9.999999251003146, + 1.9999991039165366, + 10.000000748996854, + 1.9999991039165366, + 10.000000748996854, + ); + path244.cubicTo( + 1.9999991039165366, + 11.10000113046658, + 1.0999991277583945, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + ); + path244.cubicTo( + -8.960834634308412e-7, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + -8.960834634308412e-7, + 12.000000748996854, + ); + path244.cubicTo( + -1.1000009199253213, + 12.000000748996854, + -2.0000008960834634, + 11.10000113046658, + -2.0000008960834634, + 10.000000748996854, + ); + path244.cubicTo( + -2.0000008960834634, + 10.000000748996854, + -2.0000008960834634, + -9.999999251003146, + -2.0000008960834634, + -9.999999251003146, + ); + path244.close(); + allPaths.add(path244); +} + +void pathOps245() { + final Path path245 = Path(); + path245.moveTo(2.0000006178626677, 7.489968538720859e-7); + path245.cubicTo( + 2.0000006178626677, + 1.1100007633019686, + 1.1000006417045256, + 2.000000748996854, + 6.178626676955901e-7, + 2.000000748996854, + ); + path245.cubicTo( + -1.109999396442447, + 2.000000748996854, + -1.9999993821373323, + 1.1100007633019686, + -1.9999993821373323, + 7.489968538720859e-7, + ); + path245.cubicTo( + -1.9999993821373323, + -1.099999274845004, + -1.109999396442447, + -1.9999992510031461, + 6.178626676955901e-7, + -1.9999992510031461, + ); + path245.cubicTo( + 1.1000006417045256, + -1.9999992510031461, + 2.0000006178626677, + -1.099999274845004, + 2.0000006178626677, + 7.489968538720859e-7, + ); + allPaths.add(path245); +} + +void pathOps246() { + final Path path246 = Path(); + path246.moveTo(-3.0000008960834634, -9.000000721237882); + path246.cubicTo( + -3.0000008960834634, + -10.650000339768155, + -1.6500008722416055, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + ); + path246.cubicTo( + -8.960834634308412e-7, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + ); + path246.cubicTo( + 1.6499990800746787, + -12.000000721237882, + 2.9999991039165366, + -10.650000339768155, + 2.9999991039165366, + -9.000000721237882, + ); + path246.cubicTo( + 2.9999991039165366, + -9.000000721237882, + 2.9999991039165366, + 8.999999278762118, + 2.9999991039165366, + 8.999999278762118, + ); + path246.cubicTo( + 2.9999991039165366, + 10.649998897292392, + 1.6499990800746787, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + ); + path246.cubicTo( + -8.960834634308412e-7, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + ); + path246.cubicTo( + -1.6500008722416055, + 11.999999278762118, + -3.0000008960834634, + 10.649998897292392, + -3.0000008960834634, + 8.999999278762118, + ); + path246.cubicTo( + -3.0000008960834634, + 8.999999278762118, + -3.0000008960834634, + -9.000000721237882, + -3.0000008960834634, + -9.000000721237882, + ); + path246.close(); + allPaths.add(path246); +} + +void pathOps247() { + final Path path247 = Path(); + path247.moveTo(-2.0000008960834634, -10.000000721237882); + path247.cubicTo( + -2.0000008960834634, + -11.100001102707608, + -1.1000009199253213, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + ); + path247.cubicTo( + -8.960834634308412e-7, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + -8.960834634308412e-7, + -12.000000721237882, + ); + path247.cubicTo( + 1.0999991277583945, + -12.000000721237882, + 1.9999991039165366, + -11.100001102707608, + 1.9999991039165366, + -10.000000721237882, + ); + path247.cubicTo( + 1.9999991039165366, + -10.000000721237882, + 1.9999991039165366, + 9.999999278762118, + 1.9999991039165366, + 9.999999278762118, + ); + path247.cubicTo( + 1.9999991039165366, + 11.099999660231845, + 1.0999991277583945, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + ); + path247.cubicTo( + -8.960834634308412e-7, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + -8.960834634308412e-7, + 11.999999278762118, + ); + path247.cubicTo( + -1.1000009199253213, + 11.999999278762118, + -2.0000008960834634, + 11.099999660231845, + -2.0000008960834634, + 9.999999278762118, + ); + path247.cubicTo( + -2.0000008960834634, + 9.999999278762118, + -2.0000008960834634, + -10.000000721237882, + -2.0000008960834634, + -10.000000721237882, + ); + path247.close(); + allPaths.add(path247); +} + +void pathOps248() { + final Path path248 = Path(); + path248.moveTo(-2.0000005026809617, 2.324364061223605e-7); + path248.cubicTo( + -2.0000005026809617, + -1.0999997914054518, + -1.1000005265228197, + -1.9999997675635939, + -5.026809617447725e-7, + -1.9999997675635939, + ); + path248.cubicTo( + 1.0999995211608962, + -1.9999997675635939, + 1.9999994973190383, + -1.0999997914054518, + 1.9999994973190383, + 2.324364061223605e-7, + ); + path248.cubicTo( + 1.9999994973190383, + 1.100000256278264, + 1.0999995211608962, + 2.000000232436406, + -5.026809617447725e-7, + 2.000000232436406, + ); + path248.cubicTo( + -1.1000005265228197, + 2.000000232436406, + -2.0000005026809617, + 1.100000256278264, + -2.0000005026809617, + 2.324364061223605e-7, + ); + allPaths.add(path248); +} + +void pathOps249() { + final Path path249 = Path(); + path249.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path249.getBounds(); + allPaths.add(path249); +} + +void pathOps250() { + final Path path250 = Path(); + path250.reset(); + path250.moveTo(234.66666666666666, 90); + path250.lineTo(96, 90); + path250.lineTo(96, 89); + path250.lineTo(234.66666666666666, 89); + gBounds = path250.getBounds(); + allPaths.add(path250); +} + +void pathOps251() { + final Path path251 = Path(); + path251.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 105), Radius.zero), + ); + gBounds = path251.getBounds(); + allPaths.add(path251); +} + +void pathOps252() { + final Path path252 = Path(); + path252.reset(); + path252.moveTo(234.66666666666669, 105); + path252.lineTo(96, 105); + path252.lineTo(96, 104); + path252.lineTo(234.66666666666669, 104); + gBounds = path252.getBounds(); + _runPathTest(path252); + gFillType = path252.fillType; + allPaths.add(path252); +} + +void pathOps253() { + final Path path253 = Path(); + path253.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 90), Radius.zero), + ); + gBounds = path253.getBounds(); + allPaths.add(path253); +} + +void pathOps254() { + final Path path254 = Path(); + path254.reset(); + path254.moveTo(234.66666666666669, 90); + path254.lineTo(96, 90); + path254.lineTo(96, 89); + path254.lineTo(234.66666666666669, 89); + gBounds = path254.getBounds(); + _runPathTest(path254); + gFillType = path254.fillType; + allPaths.add(path254); +} + +void pathOps255() { + final Path path255 = Path(); + path255.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path255.getBounds(); + allPaths.add(path255); +} + +void pathOps256() { + final Path path256 = Path(); + path256.reset(); + path256.moveTo(234.66666666666666, 90); + path256.lineTo(96, 90); + path256.lineTo(96, 89); + path256.lineTo(234.66666666666666, 89); + gBounds = path256.getBounds(); + allPaths.add(path256); +} + +void pathOps257() { + final Path path257 = Path(); + path257.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path257.getBounds(); + allPaths.add(path257); +} + +void pathOps258() { + final Path path258 = Path(); + path258.reset(); + path258.moveTo(234.66666666666666, 90); + path258.lineTo(96, 90); + path258.lineTo(96, 89); + path258.lineTo(234.66666666666666, 89); + gBounds = path258.getBounds(); + _runPathTest(path258); + gFillType = path258.fillType; + allPaths.add(path258); +} + +void pathOps259() { + final Path path259 = Path(); + path259.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path259.getBounds(); + allPaths.add(path259); +} + +void pathOps260() { + final Path path260 = Path(); + path260.reset(); + path260.moveTo(234.66666666666666, 105); + path260.lineTo(96, 105); + path260.lineTo(96, 104); + path260.lineTo(234.66666666666666, 104); + gBounds = path260.getBounds(); + _runPathTest(path260); + gFillType = path260.fillType; + allPaths.add(path260); +} + +void pathOps261() { + final Path path261 = Path(); + path261.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path261.getBounds(); + allPaths.add(path261); +} + +void pathOps262() { + final Path path262 = Path(); + path262.reset(); + path262.moveTo(234.66666666666666, 90); + path262.lineTo(96, 90); + path262.lineTo(96, 89); + path262.lineTo(234.66666666666666, 89); + gBounds = path262.getBounds(); + _runPathTest(path262); + gFillType = path262.fillType; + allPaths.add(path262); +} + +void pathOps263() { + final Path path263 = Path(); + path263.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path263.getBounds(); + allPaths.add(path263); +} + +void pathOps264() { + final Path path264 = Path(); + path264.reset(); + path264.moveTo(234.66666666666666, 90); + path264.lineTo(96, 90); + path264.lineTo(96, 89); + path264.lineTo(234.66666666666666, 89); + gBounds = path264.getBounds(); + _runPathTest(path264); + gFillType = path264.fillType; + allPaths.add(path264); +} + +void pathOps265() { + final Path path265 = Path(); + path265.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path265.getBounds(); + allPaths.add(path265); +} + +void pathOps266() { + final Path path266 = Path(); + path266.reset(); + path266.moveTo(234.66666666666666, 90); + path266.lineTo(96, 90); + path266.lineTo(96, 89); + path266.lineTo(234.66666666666666, 89); + gBounds = path266.getBounds(); + _runPathTest(path266); + gFillType = path266.fillType; + allPaths.add(path266); +} + +void pathOps267() { + final Path path267 = Path(); + path267.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 75), Radius.zero), + ); + gBounds = path267.getBounds(); + allPaths.add(path267); +} + +void pathOps268() { + final Path path268 = Path(); + path268.reset(); + path268.moveTo(234.66666666666666, 75); + path268.lineTo(96, 75); + path268.lineTo(96, 74); + path268.lineTo(234.66666666666666, 74); + gBounds = path268.getBounds(); + _runPathTest(path268); + gFillType = path268.fillType; + allPaths.add(path268); +} + +void pathOps269() { + final Path path269 = Path(); + path269.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path269.getBounds(); + allPaths.add(path269); +} + +void pathOps270() { + final Path path270 = Path(); + path270.reset(); + path270.moveTo(234.66666666666666, 105); + path270.lineTo(96, 105); + path270.lineTo(96, 104); + path270.lineTo(234.66666666666666, 104); + gBounds = path270.getBounds(); + allPaths.add(path270); +} + +void pathOps271() { + final Path path271 = Path(); + path271.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 139), Radius.zero), + ); + gBounds = path271.getBounds(); + allPaths.add(path271); +} + +void pathOps272() { + final Path path272 = Path(); + path272.reset(); + path272.moveTo(234.66666666666666, 139); + path272.lineTo(96, 139); + path272.lineTo(96, 138); + path272.lineTo(234.66666666666666, 138); + gBounds = path272.getBounds(); + _runPathTest(path272); + gFillType = path272.fillType; + allPaths.add(path272); +} + +void pathOps273() { + final Path path273 = Path(); + path273.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path273.getBounds(); + allPaths.add(path273); +} + +void pathOps274() { + final Path path274 = Path(); + path274.reset(); + path274.moveTo(234.66666666666666, 120); + path274.lineTo(96, 120); + path274.lineTo(96, 119); + path274.lineTo(234.66666666666666, 119); + gBounds = path274.getBounds(); + _runPathTest(path274); + gFillType = path274.fillType; + allPaths.add(path274); +} + +void pathOps275() { + final Path path275 = Path(); + path275.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 94), Radius.zero), + ); + gBounds = path275.getBounds(); + allPaths.add(path275); +} + +void pathOps276() { + final Path path276 = Path(); + path276.reset(); + path276.moveTo(234.66666666666669, 94); + path276.lineTo(96, 94); + path276.lineTo(96, 93); + path276.lineTo(234.66666666666669, 93); + gBounds = path276.getBounds(); + _runPathTest(path276); + gFillType = path276.fillType; + allPaths.add(path276); +} + +void pathOps277() { + gBounds = path277.getBounds(); + allPaths.add(path277); +} + +void pathOps278() { + gBounds = path278.getBounds(); + allPaths.add(path278); +} + +void pathOps279() { + gBounds = path279.getBounds(); + allPaths.add(path279); +} + +void pathOps280() { + gBounds = path280.getBounds(); + allPaths.add(path280); +} + +void pathOps281() { + gBounds = path281.getBounds(); + allPaths.add(path281); +} + +void pathOps282() { + gBounds = path282.getBounds(); + allPaths.add(path282); +} + +void pathOps283() { + final Path path283 = Path(); + path283.reset(); + path283.moveTo(56, 42); + path283.lineTo(56, 42); + path283.lineTo(58, 42); + path283.lineTo(58, 42); + gBounds = path283.getBounds(); + allPaths.add(path283); +} + +void pathOps284() { + gBounds = path284.getBounds(); + allPaths.add(path284); +} + +void pathOps285() { + final Path path285 = Path(); + path285.reset(); + path285.moveTo(56, 42); + path285.lineTo(56, 42); + path285.lineTo(58, 42); + path285.lineTo(58, 42); + gBounds = path285.getBounds(); + allPaths.add(path285); +} + +void pathOps286() { + gBounds = path286.getBounds(); + allPaths.add(path286); +} + +void pathOps287() { + final Path path287 = Path(); + path287.reset(); + path287.moveTo(56, 42); + path287.lineTo(56, 42); + path287.lineTo(58, 42); + path287.lineTo(58, 42); + gBounds = path287.getBounds(); + allPaths.add(path287); +} + +void pathOps288() { + gBounds = path288.getBounds(); + allPaths.add(path288); +} + +void pathOps289() { + final Path path289 = Path(); + path289.reset(); + path289.moveTo(56, 42); + path289.lineTo(56, 42); + path289.lineTo(58, 42); + path289.lineTo(58, 42); + gBounds = path289.getBounds(); + allPaths.add(path289); +} + +void pathOps290() { + gBounds = path290.getBounds(); + allPaths.add(path290); +} + +void pathOps291() { + final Path path291 = Path(); + path291.reset(); + path291.moveTo(56, 42); + path291.lineTo(56, 42); + path291.lineTo(58, 42); + path291.lineTo(58, 42); + gBounds = path291.getBounds(); + allPaths.add(path291); +} + +void pathOps292() { + gBounds = path292.getBounds(); + allPaths.add(path292); +} + +void pathOps293() { + final Path path293 = Path(); + path293.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path293.getBounds(); + allPaths.add(path293); +} + +void pathOps294() { + final Path path294 = Path(); + path294.reset(); + path294.moveTo(331.66666666666663, 86); + path294.lineTo(81, 86); + path294.lineTo(81, 84); + path294.lineTo(331.66666666666663, 84); + gBounds = path294.getBounds(); + _runPathTest(path294); + gFillType = path294.fillType; + allPaths.add(path294); +} + +void pathOps295() { + gBounds = path295.getBounds(); + allPaths.add(path295); +} + +void pathOps296() { + final Path path296 = Path(); + path296.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path296.getBounds(); + allPaths.add(path296); +} + +void pathOps297() { + final Path path297 = Path(); + path297.reset(); + path297.moveTo(610.3333333333333, 86); + path297.lineTo(359.66666666666663, 86); + path297.lineTo(359.66666666666663, 84); + path297.lineTo(610.3333333333333, 84); + gBounds = path297.getBounds(); + _runPathTest(path297); + gFillType = path297.fillType; + allPaths.add(path297); +} + +void pathOps298() { + gBounds = path298.getBounds(); + allPaths.add(path298); +} + +void pathOps299() { + final Path path299 = Path(); + path299.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path299.getBounds(); + allPaths.add(path299); +} + +void pathOps300() { + final Path path300 = Path(); + path300.reset(); + path300.moveTo(889, 86); + path300.lineTo(638.3333333333333, 86); + path300.lineTo(638.3333333333333, 84); + path300.lineTo(889, 84); + gBounds = path300.getBounds(); + _runPathTest(path300); + gFillType = path300.fillType; + allPaths.add(path300); +} + +void pathOps301() { + gBounds = path301.getBounds(); + allPaths.add(path301); +} + +void pathOps302() { + final Path path302 = Path(); + path302.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 94), Radius.zero), + ); + gBounds = path302.getBounds(); + allPaths.add(path302); +} + +void pathOps303() { + final Path path303 = Path(); + path303.reset(); + path303.moveTo(234.66666666666669, 94); + path303.lineTo(96, 94); + path303.lineTo(96, 93); + path303.lineTo(234.66666666666669, 93); + gBounds = path303.getBounds(); + _runPathTest(path303); + gFillType = path303.fillType; + allPaths.add(path303); +} + +void pathOps304() { + final Path path304 = Path(); + path304.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path304.getBounds(); + allPaths.add(path304); +} + +void pathOps305() { + final Path path305 = Path(); + path305.reset(); + path305.moveTo(234.66666666666666, 105); + path305.lineTo(96, 105); + path305.lineTo(96, 104); + path305.lineTo(234.66666666666666, 104); + gBounds = path305.getBounds(); + allPaths.add(path305); +} + +void pathOps306() { + final Path path306 = Path(); + path306.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 105), Radius.zero), + ); + gBounds = path306.getBounds(); + allPaths.add(path306); +} + +void pathOps307() { + final Path path307 = Path(); + path307.reset(); + path307.moveTo(234.66666666666669, 105); + path307.lineTo(96, 105); + path307.lineTo(96, 104); + path307.lineTo(234.66666666666669, 104); + gBounds = path307.getBounds(); + _runPathTest(path307); + gFillType = path307.fillType; + allPaths.add(path307); +} + +void pathOps308() { + final Path path308 = Path(); + path308.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 105), Radius.zero), + ); + gBounds = path308.getBounds(); + allPaths.add(path308); +} + +void pathOps309() { + final Path path309 = Path(); + path309.reset(); + path309.moveTo(234.66666666666666, 105); + path309.lineTo(96, 105); + path309.lineTo(96, 104); + path309.lineTo(234.66666666666666, 104); + gBounds = path309.getBounds(); + _runPathTest(path309); + gFillType = path309.fillType; + allPaths.add(path309); +} + +void pathOps310() { + final Path path310 = Path(); + path310.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path310.getBounds(); + allPaths.add(path310); +} + +void pathOps311() { + final Path path311 = Path(); + path311.reset(); + path311.moveTo(234.66666666666666, 120); + path311.lineTo(96, 120); + path311.lineTo(96, 119); + path311.lineTo(234.66666666666666, 119); + gBounds = path311.getBounds(); + _runPathTest(path311); + gFillType = path311.fillType; + allPaths.add(path311); +} + +void pathOps312() { + final Path path312 = Path(); + path312.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 139), Radius.zero), + ); + gBounds = path312.getBounds(); + allPaths.add(path312); +} + +void pathOps313() { + final Path path313 = Path(); + path313.reset(); + path313.moveTo(234.66666666666666, 139); + path313.lineTo(96, 139); + path313.lineTo(96, 138); + path313.lineTo(234.66666666666666, 138); + gBounds = path313.getBounds(); + _runPathTest(path313); + gFillType = path313.fillType; + allPaths.add(path313); +} + +void pathOps314() { + final Path path314 = Path(); + path314.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 75), Radius.zero), + ); + gBounds = path314.getBounds(); + allPaths.add(path314); +} + +void pathOps315() { + final Path path315 = Path(); + path315.reset(); + path315.moveTo(234.66666666666666, 75); + path315.lineTo(96, 75); + path315.lineTo(96, 74); + path315.lineTo(234.66666666666666, 74); + gBounds = path315.getBounds(); + _runPathTest(path315); + gFillType = path315.fillType; + allPaths.add(path315); +} + +void pathOps316() { + final Path path316 = Path(); + path316.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path316.getBounds(); + allPaths.add(path316); +} + +void pathOps317() { + final Path path317 = Path(); + path317.reset(); + path317.moveTo(234.66666666666666, 90); + path317.lineTo(96, 90); + path317.lineTo(96, 89); + path317.lineTo(234.66666666666666, 89); + gBounds = path317.getBounds(); + allPaths.add(path317); +} + +void pathOps318() { + final Path path318 = Path(); + path318.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path318.getBounds(); + allPaths.add(path318); +} + +void pathOps319() { + final Path path319 = Path(); + path319.reset(); + path319.moveTo(234.66666666666666, 90); + path319.lineTo(96, 90); + path319.lineTo(96, 89); + path319.lineTo(234.66666666666666, 89); + gBounds = path319.getBounds(); + _runPathTest(path319); + gFillType = path319.fillType; + allPaths.add(path319); +} + +void pathOps320() { + final Path path320 = Path(); + path320.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path320.getBounds(); + allPaths.add(path320); +} + +void pathOps321() { + final Path path321 = Path(); + path321.reset(); + path321.moveTo(234.66666666666666, 90); + path321.lineTo(96, 90); + path321.lineTo(96, 89); + path321.lineTo(234.66666666666666, 89); + gBounds = path321.getBounds(); + _runPathTest(path321); + gFillType = path321.fillType; + allPaths.add(path321); +} + +void pathOps322() { + final Path path322 = Path(); + path322.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path322.getBounds(); + allPaths.add(path322); +} + +void pathOps323() { + final Path path323 = Path(); + path323.reset(); + path323.moveTo(234.66666666666666, 90); + path323.lineTo(96, 90); + path323.lineTo(96, 89); + path323.lineTo(234.66666666666666, 89); + gBounds = path323.getBounds(); + _runPathTest(path323); + gFillType = path323.fillType; + allPaths.add(path323); +} + +void pathOps324() { + final Path path324 = Path(); + path324.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path324.getBounds(); + allPaths.add(path324); +} + +void pathOps325() { + final Path path325 = Path(); + path325.reset(); + path325.moveTo(234.66666666666666, 90); + path325.lineTo(96, 90); + path325.lineTo(96, 89); + path325.lineTo(234.66666666666666, 89); + gBounds = path325.getBounds(); + _runPathTest(path325); + gFillType = path325.fillType; + allPaths.add(path325); +} + +void pathOps326() { + final Path path326 = Path(); + path326.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 90), Radius.zero), + ); + gBounds = path326.getBounds(); + allPaths.add(path326); +} + +void pathOps327() { + final Path path327 = Path(); + path327.reset(); + path327.moveTo(234.66666666666666, 90); + path327.lineTo(96, 90); + path327.lineTo(96, 89); + path327.lineTo(234.66666666666666, 89); + gBounds = path327.getBounds(); + allPaths.add(path327); +} + +void pathOps328() { + final Path path328 = Path(); + path328.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666669, 90), Radius.zero), + ); + gBounds = path328.getBounds(); + allPaths.add(path328); +} + +void pathOps329() { + final Path path329 = Path(); + path329.reset(); + path329.moveTo(234.66666666666669, 90); + path329.lineTo(96, 90); + path329.lineTo(96, 89); + path329.lineTo(234.66666666666669, 89); + gBounds = path329.getBounds(); + _runPathTest(path329); + gFillType = path329.fillType; + allPaths.add(path329); +} + +void pathOps330() { + gBounds = path330.getBounds(); + allPaths.add(path330); +} + +void pathOps331() { + gBounds = path331.getBounds(); + allPaths.add(path331); +} + +void pathOps332() { + gBounds = path332.getBounds(); + allPaths.add(path332); +} + +void pathOps333() { + gBounds = path333.getBounds(); + allPaths.add(path333); +} + +void pathOps334() { + gBounds = path334.getBounds(); + allPaths.add(path334); +} + +void pathOps335() { + final Path path335 = Path(); + path335.reset(); + path335.moveTo(56, 42); + path335.lineTo(56, 42); + path335.lineTo(58, 42); + path335.lineTo(58, 42); + gBounds = path335.getBounds(); + allPaths.add(path335); +} + +void pathOps336() { + gBounds = path336.getBounds(); + allPaths.add(path336); +} + +void pathOps337() { + final Path path337 = Path(); + path337.reset(); + path337.moveTo(56, 42); + path337.lineTo(56, 42); + path337.lineTo(58, 42); + path337.lineTo(58, 42); + gBounds = path337.getBounds(); + allPaths.add(path337); +} + +void pathOps338() { + gBounds = path338.getBounds(); + allPaths.add(path338); +} + +void pathOps339() { + final Path path339 = Path(); + path339.reset(); + path339.moveTo(56, 42); + path339.lineTo(56, 42); + path339.lineTo(58, 42); + path339.lineTo(58, 42); + gBounds = path339.getBounds(); + allPaths.add(path339); +} + +void pathOps340() { + gBounds = path340.getBounds(); + allPaths.add(path340); +} + +void pathOps341() { + final Path path341 = Path(); + path341.reset(); + path341.moveTo(56, 42); + path341.lineTo(56, 42); + path341.lineTo(58, 42); + path341.lineTo(58, 42); + gBounds = path341.getBounds(); + allPaths.add(path341); +} + +void pathOps342() { + gBounds = path342.getBounds(); + allPaths.add(path342); +} + +void pathOps343() { + final Path path343 = Path(); + path343.reset(); + path343.moveTo(56, 42); + path343.lineTo(56, 42); + path343.lineTo(58, 42); + path343.lineTo(58, 42); + gBounds = path343.getBounds(); + allPaths.add(path343); +} + +void pathOps344() { + gBounds = path344.getBounds(); + allPaths.add(path344); +} + +void pathOps345() { + gBounds = path345.getBounds(); + allPaths.add(path345); +} + +void pathOps346() { + gBounds = path346.getBounds(); + allPaths.add(path346); +} + +void pathOps347() { + final Path path347 = Path(); + path347.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path347.getBounds(); + allPaths.add(path347); +} + +void pathOps348() { + final Path path348 = Path(); + path348.reset(); + path348.moveTo(331.66666666666663, 86); + path348.lineTo(81, 86); + path348.lineTo(81, 84); + path348.lineTo(331.66666666666663, 84); + gBounds = path348.getBounds(); + _runPathTest(path348); + gFillType = path348.fillType; + allPaths.add(path348); +} + +void pathOps349() { + gBounds = path349.getBounds(); + allPaths.add(path349); +} + +void pathOps350() { + final Path path350 = Path(); + path350.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path350.getBounds(); + allPaths.add(path350); +} + +void pathOps351() { + final Path path351 = Path(); + path351.reset(); + path351.moveTo(610.3333333333333, 86); + path351.lineTo(359.66666666666663, 86); + path351.lineTo(359.66666666666663, 84); + path351.lineTo(610.3333333333333, 84); + gBounds = path351.getBounds(); + _runPathTest(path351); + gFillType = path351.fillType; + allPaths.add(path351); +} + +void pathOps352() { + gBounds = path352.getBounds(); + allPaths.add(path352); +} + +void pathOps353() { + final Path path353 = Path(); + path353.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path353.getBounds(); + allPaths.add(path353); +} + +void pathOps354() { + final Path path354 = Path(); + path354.reset(); + path354.moveTo(889, 86); + path354.lineTo(638.3333333333333, 86); + path354.lineTo(638.3333333333333, 84); + path354.lineTo(889, 84); + gBounds = path354.getBounds(); + _runPathTest(path354); + gFillType = path354.fillType; + allPaths.add(path354); +} + +void pathOps355() { + final Path path355 = Path(); + path355.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + gBounds = path355.getBounds(); + allPaths.add(path355); +} + +void pathOps356() { + gBounds = path356.getBounds(); + allPaths.add(path356); +} + +void pathOps357() { + final Path path357 = Path(); + path357.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + gBounds = path357.getBounds(); + allPaths.add(path357); +} + +void pathOps358() { + gBounds = path358.getBounds(); + allPaths.add(path358); +} + +void pathOps359() { + gBounds = path359.getBounds(); + allPaths.add(path359); +} + +void pathOps360() { + gBounds = path360.getBounds(); + allPaths.add(path360); +} + +void pathOps361() { + gBounds = path361.getBounds(); + allPaths.add(path361); +} + +void pathOps362() { + gBounds = path362.getBounds(); + allPaths.add(path362); +} + +void pathOps363() { + gBounds = path363.getBounds(); + allPaths.add(path363); +} + +void pathOps364() { + final Path path364 = Path(); + path364.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path364.getBounds(); + allPaths.add(path364); +} + +void pathOps365() { + final Path path365 = Path(); + path365.reset(); + path365.moveTo(331.66666666666663, 86); + path365.lineTo(81, 86); + path365.lineTo(81, 84); + path365.lineTo(331.66666666666663, 84); + gBounds = path365.getBounds(); + _runPathTest(path365); + gFillType = path365.fillType; + allPaths.add(path365); +} + +void pathOps366() { + gBounds = path366.getBounds(); + allPaths.add(path366); +} + +void pathOps367() { + final Path path367 = Path(); + path367.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path367.getBounds(); + allPaths.add(path367); +} + +void pathOps368() { + final Path path368 = Path(); + path368.reset(); + path368.moveTo(610.3333333333333, 86); + path368.lineTo(359.66666666666663, 86); + path368.lineTo(359.66666666666663, 84); + path368.lineTo(610.3333333333333, 84); + gBounds = path368.getBounds(); + _runPathTest(path368); + gFillType = path368.fillType; + allPaths.add(path368); +} + +void pathOps369() { + gBounds = path369.getBounds(); + allPaths.add(path369); +} + +void pathOps370() { + final Path path370 = Path(); + path370.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path370.getBounds(); + allPaths.add(path370); +} + +void pathOps371() { + final Path path371 = Path(); + path371.reset(); + path371.moveTo(889, 86); + path371.lineTo(638.3333333333333, 86); + path371.lineTo(638.3333333333333, 84); + path371.lineTo(889, 84); + gBounds = path371.getBounds(); + _runPathTest(path371); + gFillType = path371.fillType; + allPaths.add(path371); +} + +void pathOps372() { + gBounds = path372.getBounds(); + allPaths.add(path372); +} + +void pathOps373() { + gBounds = path373.getBounds(); + allPaths.add(path373); +} + +void pathOps374() { + gBounds = path374.getBounds(); + allPaths.add(path374); +} + +void pathOps375() { + gBounds = path375.getBounds(); + allPaths.add(path375); +} + +void pathOps376() { + gBounds = path376.getBounds(); + allPaths.add(path376); +} + +void pathOps377() { + final Path path377 = Path(); + path377.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path377.getBounds(); + allPaths.add(path377); +} + +void pathOps378() { + final Path path378 = Path(); + path378.reset(); + path378.moveTo(331.66666666666663, 86); + path378.lineTo(81, 86); + path378.lineTo(81, 84); + path378.lineTo(331.66666666666663, 84); + gBounds = path378.getBounds(); + _runPathTest(path378); + gFillType = path378.fillType; + allPaths.add(path378); +} + +void pathOps379() { + gBounds = path379.getBounds(); + allPaths.add(path379); +} + +void pathOps380() { + final Path path380 = Path(); + path380.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path380.getBounds(); + allPaths.add(path380); +} + +void pathOps381() { + final Path path381 = Path(); + path381.reset(); + path381.moveTo(610.3333333333333, 86); + path381.lineTo(359.66666666666663, 86); + path381.lineTo(359.66666666666663, 84); + path381.lineTo(610.3333333333333, 84); + gBounds = path381.getBounds(); + _runPathTest(path381); + gFillType = path381.fillType; + allPaths.add(path381); +} + +void pathOps382() { + gBounds = path382.getBounds(); + allPaths.add(path382); +} + +void pathOps383() { + final Path path383 = Path(); + path383.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path383.getBounds(); + allPaths.add(path383); +} + +void pathOps384() { + final Path path384 = Path(); + path384.reset(); + path384.moveTo(889, 86); + path384.lineTo(638.3333333333333, 86); + path384.lineTo(638.3333333333333, 84); + path384.lineTo(889, 84); + gBounds = path384.getBounds(); + _runPathTest(path384); + gFillType = path384.fillType; + allPaths.add(path384); +} + +void pathOps385() { + gBounds = path385.getBounds(); + allPaths.add(path385); +} + +void pathOps386() { + gBounds = path386.getBounds(); + allPaths.add(path386); +} + +void pathOps387() { + gBounds = path387.getBounds(); + allPaths.add(path387); +} + +void pathOps388() { + gBounds = path388.getBounds(); + allPaths.add(path388); +} + +void pathOps389() { + gBounds = path389.getBounds(); + allPaths.add(path389); +} + +void pathOps390() { + final Path path390 = Path(); + path390.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path390.getBounds(); + allPaths.add(path390); +} + +void pathOps391() { + final Path path391 = Path(); + path391.reset(); + path391.moveTo(331.66666666666663, 86); + path391.lineTo(81, 86); + path391.lineTo(81, 84); + path391.lineTo(331.66666666666663, 84); + gBounds = path391.getBounds(); + _runPathTest(path391); + gFillType = path391.fillType; + allPaths.add(path391); +} + +void pathOps392() { + gBounds = path392.getBounds(); + allPaths.add(path392); +} + +void pathOps393() { + final Path path393 = Path(); + path393.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path393.getBounds(); + allPaths.add(path393); +} + +void pathOps394() { + final Path path394 = Path(); + path394.reset(); + path394.moveTo(610.3333333333333, 86); + path394.lineTo(359.66666666666663, 86); + path394.lineTo(359.66666666666663, 84); + path394.lineTo(610.3333333333333, 84); + gBounds = path394.getBounds(); + _runPathTest(path394); + gFillType = path394.fillType; + allPaths.add(path394); +} + +void pathOps395() { + gBounds = path395.getBounds(); + allPaths.add(path395); +} + +void pathOps396() { + final Path path396 = Path(); + path396.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path396.getBounds(); + allPaths.add(path396); +} + +void pathOps397() { + final Path path397 = Path(); + path397.reset(); + path397.moveTo(889, 86); + path397.lineTo(638.3333333333333, 86); + path397.lineTo(638.3333333333333, 84); + path397.lineTo(889, 84); + gBounds = path397.getBounds(); + _runPathTest(path397); + gFillType = path397.fillType; + allPaths.add(path397); +} + +void pathOps398() { + gBounds = path398.getBounds(); + allPaths.add(path398); +} + +void pathOps399() { + gBounds = path399.getBounds(); + allPaths.add(path399); +} + +void pathOps400() { + gBounds = path400.getBounds(); + allPaths.add(path400); +} + +void pathOps401() { + gBounds = path401.getBounds(); + allPaths.add(path401); +} + +void pathOps402() { + gBounds = path402.getBounds(); + allPaths.add(path402); +} + +void pathOps403() { + final Path path403 = Path(); + path403.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path403.getBounds(); + allPaths.add(path403); +} + +void pathOps404() { + final Path path404 = Path(); + path404.reset(); + path404.moveTo(331.66666666666663, 86); + path404.lineTo(81, 86); + path404.lineTo(81, 84); + path404.lineTo(331.66666666666663, 84); + gBounds = path404.getBounds(); + _runPathTest(path404); + gFillType = path404.fillType; + allPaths.add(path404); +} + +void pathOps405() { + gBounds = path405.getBounds(); + allPaths.add(path405); +} + +void pathOps406() { + final Path path406 = Path(); + path406.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path406.getBounds(); + allPaths.add(path406); +} + +void pathOps407() { + final Path path407 = Path(); + path407.reset(); + path407.moveTo(610.3333333333333, 86); + path407.lineTo(359.66666666666663, 86); + path407.lineTo(359.66666666666663, 84); + path407.lineTo(610.3333333333333, 84); + gBounds = path407.getBounds(); + _runPathTest(path407); + gFillType = path407.fillType; + allPaths.add(path407); +} + +void pathOps408() { + gBounds = path408.getBounds(); + allPaths.add(path408); +} + +void pathOps409() { + final Path path409 = Path(); + path409.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path409.getBounds(); + allPaths.add(path409); +} + +void pathOps410() { + final Path path410 = Path(); + path410.reset(); + path410.moveTo(889, 86); + path410.lineTo(638.3333333333333, 86); + path410.lineTo(638.3333333333333, 84); + path410.lineTo(889, 84); + gBounds = path410.getBounds(); + _runPathTest(path410); + gFillType = path410.fillType; + allPaths.add(path410); +} + +void pathOps411() { + gBounds = path411.getBounds(); + allPaths.add(path411); +} + +void pathOps412() { + gBounds = path412.getBounds(); + allPaths.add(path412); +} + +void pathOps413() { + gBounds = path413.getBounds(); + allPaths.add(path413); +} + +void pathOps414() { + gBounds = path414.getBounds(); + allPaths.add(path414); +} + +void pathOps415() { + gBounds = path415.getBounds(); + allPaths.add(path415); +} + +void pathOps416() { + final Path path416 = Path(); + path416.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path416.getBounds(); + allPaths.add(path416); +} + +void pathOps417() { + final Path path417 = Path(); + path417.reset(); + path417.moveTo(331.66666666666663, 86); + path417.lineTo(81, 86); + path417.lineTo(81, 84); + path417.lineTo(331.66666666666663, 84); + gBounds = path417.getBounds(); + _runPathTest(path417); + gFillType = path417.fillType; + allPaths.add(path417); +} + +void pathOps418() { + gBounds = path418.getBounds(); + allPaths.add(path418); +} + +void pathOps419() { + final Path path419 = Path(); + path419.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path419.getBounds(); + allPaths.add(path419); +} + +void pathOps420() { + final Path path420 = Path(); + path420.reset(); + path420.moveTo(610.3333333333333, 86); + path420.lineTo(359.66666666666663, 86); + path420.lineTo(359.66666666666663, 84); + path420.lineTo(610.3333333333333, 84); + gBounds = path420.getBounds(); + _runPathTest(path420); + gFillType = path420.fillType; + allPaths.add(path420); +} + +void pathOps421() { + gBounds = path421.getBounds(); + allPaths.add(path421); +} + +void pathOps422() { + final Path path422 = Path(); + path422.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path422.getBounds(); + allPaths.add(path422); +} + +void pathOps423() { + final Path path423 = Path(); + path423.reset(); + path423.moveTo(889, 86); + path423.lineTo(638.3333333333333, 86); + path423.lineTo(638.3333333333333, 84); + path423.lineTo(889, 84); + gBounds = path423.getBounds(); + _runPathTest(path423); + gFillType = path423.fillType; + allPaths.add(path423); +} + +void pathOps424() { + gBounds = path424.getBounds(); + allPaths.add(path424); +} + +void pathOps425() { + gBounds = path425.getBounds(); + allPaths.add(path425); +} + +void pathOps426() { + gBounds = path426.getBounds(); + allPaths.add(path426); +} + +void pathOps427() { + gBounds = path427.getBounds(); + allPaths.add(path427); +} + +void pathOps428() { + gBounds = path428.getBounds(); + allPaths.add(path428); +} + +void pathOps429() { + final Path path429 = Path(); + path429.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path429.getBounds(); + allPaths.add(path429); +} + +void pathOps430() { + final Path path430 = Path(); + path430.reset(); + path430.moveTo(331.66666666666663, 86); + path430.lineTo(81, 86); + path430.lineTo(81, 84); + path430.lineTo(331.66666666666663, 84); + gBounds = path430.getBounds(); + _runPathTest(path430); + gFillType = path430.fillType; + allPaths.add(path430); +} + +void pathOps431() { + gBounds = path431.getBounds(); + allPaths.add(path431); +} + +void pathOps432() { + final Path path432 = Path(); + path432.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path432.getBounds(); + allPaths.add(path432); +} + +void pathOps433() { + final Path path433 = Path(); + path433.reset(); + path433.moveTo(610.3333333333333, 86); + path433.lineTo(359.66666666666663, 86); + path433.lineTo(359.66666666666663, 84); + path433.lineTo(610.3333333333333, 84); + gBounds = path433.getBounds(); + _runPathTest(path433); + gFillType = path433.fillType; + allPaths.add(path433); +} + +void pathOps434() { + gBounds = path434.getBounds(); + allPaths.add(path434); +} + +void pathOps435() { + final Path path435 = Path(); + path435.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path435.getBounds(); + allPaths.add(path435); +} + +void pathOps436() { + final Path path436 = Path(); + path436.reset(); + path436.moveTo(889, 86); + path436.lineTo(638.3333333333333, 86); + path436.lineTo(638.3333333333333, 84); + path436.lineTo(889, 84); + gBounds = path436.getBounds(); + _runPathTest(path436); + gFillType = path436.fillType; + allPaths.add(path436); +} + +void pathOps437() { + gBounds = path437.getBounds(); + allPaths.add(path437); +} + +void pathOps438() { + gBounds = path438.getBounds(); + allPaths.add(path438); +} + +void pathOps439() { + gBounds = path439.getBounds(); + allPaths.add(path439); +} + +void pathOps440() { + gBounds = path440.getBounds(); + allPaths.add(path440); +} + +void pathOps441() { + gBounds = path441.getBounds(); + allPaths.add(path441); +} + +void pathOps442() { + final Path path442 = Path(); + path442.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path442.getBounds(); + allPaths.add(path442); +} + +void pathOps443() { + final Path path443 = Path(); + path443.reset(); + path443.moveTo(331.66666666666663, 86); + path443.lineTo(81, 86); + path443.lineTo(81, 84); + path443.lineTo(331.66666666666663, 84); + gBounds = path443.getBounds(); + _runPathTest(path443); + gFillType = path443.fillType; + allPaths.add(path443); +} + +void pathOps444() { + gBounds = path444.getBounds(); + allPaths.add(path444); +} + +void pathOps445() { + final Path path445 = Path(); + path445.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path445.getBounds(); + allPaths.add(path445); +} + +void pathOps446() { + final Path path446 = Path(); + path446.reset(); + path446.moveTo(610.3333333333333, 86); + path446.lineTo(359.66666666666663, 86); + path446.lineTo(359.66666666666663, 84); + path446.lineTo(610.3333333333333, 84); + gBounds = path446.getBounds(); + _runPathTest(path446); + gFillType = path446.fillType; + allPaths.add(path446); +} + +void pathOps447() { + gBounds = path447.getBounds(); + allPaths.add(path447); +} + +void pathOps448() { + final Path path448 = Path(); + path448.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path448.getBounds(); + allPaths.add(path448); +} + +void pathOps449() { + final Path path449 = Path(); + path449.reset(); + path449.moveTo(889, 86); + path449.lineTo(638.3333333333333, 86); + path449.lineTo(638.3333333333333, 84); + path449.lineTo(889, 84); + gBounds = path449.getBounds(); + _runPathTest(path449); + gFillType = path449.fillType; + allPaths.add(path449); +} + +void pathOps450() { + gBounds = path450.getBounds(); + allPaths.add(path450); +} + +void pathOps451() { + gBounds = path451.getBounds(); + allPaths.add(path451); +} + +void pathOps452() { + gBounds = path452.getBounds(); + allPaths.add(path452); +} + +void pathOps453() { + gBounds = path453.getBounds(); + allPaths.add(path453); +} + +void pathOps454() { + gBounds = path454.getBounds(); + allPaths.add(path454); +} + +void pathOps455() { + final Path path455 = Path(); + path455.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path455.getBounds(); + allPaths.add(path455); +} + +void pathOps456() { + final Path path456 = Path(); + path456.reset(); + path456.moveTo(331.66666666666663, 86); + path456.lineTo(81, 86); + path456.lineTo(81, 84); + path456.lineTo(331.66666666666663, 84); + gBounds = path456.getBounds(); + _runPathTest(path456); + gFillType = path456.fillType; + allPaths.add(path456); +} + +void pathOps457() { + gBounds = path457.getBounds(); + allPaths.add(path457); +} + +void pathOps458() { + final Path path458 = Path(); + path458.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path458.getBounds(); + allPaths.add(path458); +} + +void pathOps459() { + final Path path459 = Path(); + path459.reset(); + path459.moveTo(610.3333333333333, 86); + path459.lineTo(359.66666666666663, 86); + path459.lineTo(359.66666666666663, 84); + path459.lineTo(610.3333333333333, 84); + gBounds = path459.getBounds(); + _runPathTest(path459); + gFillType = path459.fillType; + allPaths.add(path459); +} + +void pathOps460() { + gBounds = path460.getBounds(); + allPaths.add(path460); +} + +void pathOps461() { + final Path path461 = Path(); + path461.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path461.getBounds(); + allPaths.add(path461); +} + +void pathOps462() { + final Path path462 = Path(); + path462.reset(); + path462.moveTo(889, 86); + path462.lineTo(638.3333333333333, 86); + path462.lineTo(638.3333333333333, 84); + path462.lineTo(889, 84); + gBounds = path462.getBounds(); + _runPathTest(path462); + gFillType = path462.fillType; + allPaths.add(path462); +} + +void pathOps463() { + gBounds = path463.getBounds(); + allPaths.add(path463); +} + +void pathOps464() { + gBounds = path464.getBounds(); + allPaths.add(path464); +} + +void pathOps465() { + gBounds = path465.getBounds(); + allPaths.add(path465); +} + +void pathOps466() { + gBounds = path466.getBounds(); + allPaths.add(path466); +} + +void pathOps467() { + gBounds = path467.getBounds(); + allPaths.add(path467); +} + +void pathOps468() { + final Path path468 = Path(); + path468.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path468.getBounds(); + allPaths.add(path468); +} + +void pathOps469() { + final Path path469 = Path(); + path469.reset(); + path469.moveTo(331.66666666666663, 86); + path469.lineTo(81, 86); + path469.lineTo(81, 84); + path469.lineTo(331.66666666666663, 84); + gBounds = path469.getBounds(); + _runPathTest(path469); + gFillType = path469.fillType; + allPaths.add(path469); +} + +void pathOps470() { + gBounds = path470.getBounds(); + allPaths.add(path470); +} + +void pathOps471() { + final Path path471 = Path(); + path471.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path471.getBounds(); + allPaths.add(path471); +} + +void pathOps472() { + final Path path472 = Path(); + path472.reset(); + path472.moveTo(610.3333333333333, 86); + path472.lineTo(359.66666666666663, 86); + path472.lineTo(359.66666666666663, 84); + path472.lineTo(610.3333333333333, 84); + gBounds = path472.getBounds(); + _runPathTest(path472); + gFillType = path472.fillType; + allPaths.add(path472); +} + +void pathOps473() { + gBounds = path473.getBounds(); + allPaths.add(path473); +} + +void pathOps474() { + final Path path474 = Path(); + path474.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path474.getBounds(); + allPaths.add(path474); +} + +void pathOps475() { + final Path path475 = Path(); + path475.reset(); + path475.moveTo(889, 86); + path475.lineTo(638.3333333333333, 86); + path475.lineTo(638.3333333333333, 84); + path475.lineTo(889, 84); + gBounds = path475.getBounds(); + _runPathTest(path475); + gFillType = path475.fillType; + allPaths.add(path475); +} + +void pathOps476() { + gBounds = path476.getBounds(); + allPaths.add(path476); +} + +void pathOps477() { + gBounds = path477.getBounds(); + allPaths.add(path477); +} + +void pathOps478() { + gBounds = path478.getBounds(); + allPaths.add(path478); +} + +void pathOps479() { + gBounds = path479.getBounds(); + allPaths.add(path479); +} + +void pathOps480() { + gBounds = path480.getBounds(); + allPaths.add(path480); +} + +void pathOps481() { + final Path path481 = Path(); + path481.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path481.getBounds(); + allPaths.add(path481); +} + +void pathOps482() { + final Path path482 = Path(); + path482.reset(); + path482.moveTo(331.66666666666663, 86); + path482.lineTo(81, 86); + path482.lineTo(81, 84); + path482.lineTo(331.66666666666663, 84); + gBounds = path482.getBounds(); + _runPathTest(path482); + gFillType = path482.fillType; + allPaths.add(path482); +} + +void pathOps483() { + gBounds = path483.getBounds(); + allPaths.add(path483); +} + +void pathOps484() { + final Path path484 = Path(); + path484.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path484.getBounds(); + allPaths.add(path484); +} + +void pathOps485() { + final Path path485 = Path(); + path485.reset(); + path485.moveTo(610.3333333333333, 86); + path485.lineTo(359.66666666666663, 86); + path485.lineTo(359.66666666666663, 84); + path485.lineTo(610.3333333333333, 84); + gBounds = path485.getBounds(); + _runPathTest(path485); + gFillType = path485.fillType; + allPaths.add(path485); +} + +void pathOps486() { + gBounds = path486.getBounds(); + allPaths.add(path486); +} + +void pathOps487() { + final Path path487 = Path(); + path487.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path487.getBounds(); + allPaths.add(path487); +} + +void pathOps488() { + final Path path488 = Path(); + path488.reset(); + path488.moveTo(889, 86); + path488.lineTo(638.3333333333333, 86); + path488.lineTo(638.3333333333333, 84); + path488.lineTo(889, 84); + gBounds = path488.getBounds(); + _runPathTest(path488); + gFillType = path488.fillType; + allPaths.add(path488); +} + +void pathOps489() { + gBounds = path489.getBounds(); + allPaths.add(path489); +} + +void pathOps490() { + gBounds = path490.getBounds(); + allPaths.add(path490); +} + +void pathOps491() { + gBounds = path491.getBounds(); + allPaths.add(path491); +} + +void pathOps492() { + gBounds = path492.getBounds(); + allPaths.add(path492); +} + +void pathOps493() { + gBounds = path493.getBounds(); + allPaths.add(path493); +} + +void pathOps494() { + final Path path494 = Path(); + path494.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path494.getBounds(); + allPaths.add(path494); +} + +void pathOps495() { + final Path path495 = Path(); + path495.reset(); + path495.moveTo(331.66666666666663, 86); + path495.lineTo(81, 86); + path495.lineTo(81, 84); + path495.lineTo(331.66666666666663, 84); + gBounds = path495.getBounds(); + _runPathTest(path495); + gFillType = path495.fillType; + allPaths.add(path495); +} + +void pathOps496() { + gBounds = path496.getBounds(); + allPaths.add(path496); +} + +void pathOps497() { + final Path path497 = Path(); + path497.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path497.getBounds(); + allPaths.add(path497); +} + +void pathOps498() { + final Path path498 = Path(); + path498.reset(); + path498.moveTo(610.3333333333333, 86); + path498.lineTo(359.66666666666663, 86); + path498.lineTo(359.66666666666663, 84); + path498.lineTo(610.3333333333333, 84); + gBounds = path498.getBounds(); + _runPathTest(path498); + gFillType = path498.fillType; + allPaths.add(path498); +} + +void pathOps499() { + gBounds = path499.getBounds(); + allPaths.add(path499); +} + +void pathOps500() { + final Path path500 = Path(); + path500.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path500.getBounds(); + allPaths.add(path500); +} + +void pathOps501() { + final Path path501 = Path(); + path501.reset(); + path501.moveTo(889, 86); + path501.lineTo(638.3333333333333, 86); + path501.lineTo(638.3333333333333, 84); + path501.lineTo(889, 84); + gBounds = path501.getBounds(); + _runPathTest(path501); + gFillType = path501.fillType; + allPaths.add(path501); +} + +void pathOps502() { + gBounds = path502.getBounds(); + allPaths.add(path502); +} + +void pathOps503() { + gBounds = path503.getBounds(); + allPaths.add(path503); +} + +void pathOps504() { + gBounds = path504.getBounds(); + allPaths.add(path504); +} + +void pathOps505() { + gBounds = path505.getBounds(); + allPaths.add(path505); +} + +void pathOps506() { + gBounds = path506.getBounds(); + allPaths.add(path506); +} + +void pathOps507() { + final Path path507 = Path(); + path507.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path507.getBounds(); + allPaths.add(path507); +} + +void pathOps508() { + final Path path508 = Path(); + path508.reset(); + path508.moveTo(331.66666666666663, 86); + path508.lineTo(81, 86); + path508.lineTo(81, 84); + path508.lineTo(331.66666666666663, 84); + gBounds = path508.getBounds(); + _runPathTest(path508); + gFillType = path508.fillType; + allPaths.add(path508); +} + +void pathOps509() { + gBounds = path509.getBounds(); + allPaths.add(path509); +} + +void pathOps510() { + final Path path510 = Path(); + path510.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path510.getBounds(); + allPaths.add(path510); +} + +void pathOps511() { + final Path path511 = Path(); + path511.reset(); + path511.moveTo(610.3333333333333, 86); + path511.lineTo(359.66666666666663, 86); + path511.lineTo(359.66666666666663, 84); + path511.lineTo(610.3333333333333, 84); + gBounds = path511.getBounds(); + _runPathTest(path511); + gFillType = path511.fillType; + allPaths.add(path511); +} + +void pathOps512() { + gBounds = path512.getBounds(); + allPaths.add(path512); +} + +void pathOps513() { + final Path path513 = Path(); + path513.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path513.getBounds(); + allPaths.add(path513); +} + +void pathOps514() { + final Path path514 = Path(); + path514.reset(); + path514.moveTo(889, 86); + path514.lineTo(638.3333333333333, 86); + path514.lineTo(638.3333333333333, 84); + path514.lineTo(889, 84); + gBounds = path514.getBounds(); + _runPathTest(path514); + gFillType = path514.fillType; + allPaths.add(path514); +} + +void pathOps515() { + gBounds = path515.getBounds(); + allPaths.add(path515); +} + +void pathOps516() { + gBounds = path516.getBounds(); + allPaths.add(path516); +} + +void pathOps517() { + gBounds = path517.getBounds(); + allPaths.add(path517); +} + +void pathOps518() { + gBounds = path518.getBounds(); + allPaths.add(path518); +} + +void pathOps519() { + gBounds = path519.getBounds(); + allPaths.add(path519); +} + +void pathOps520() { + final Path path520 = Path(); + path520.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path520.getBounds(); + allPaths.add(path520); +} + +void pathOps521() { + final Path path521 = Path(); + path521.reset(); + path521.moveTo(331.66666666666663, 86); + path521.lineTo(81, 86); + path521.lineTo(81, 84); + path521.lineTo(331.66666666666663, 84); + gBounds = path521.getBounds(); + _runPathTest(path521); + gFillType = path521.fillType; + allPaths.add(path521); +} + +void pathOps522() { + gBounds = path522.getBounds(); + allPaths.add(path522); +} + +void pathOps523() { + final Path path523 = Path(); + path523.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path523.getBounds(); + allPaths.add(path523); +} + +void pathOps524() { + final Path path524 = Path(); + path524.reset(); + path524.moveTo(610.3333333333333, 86); + path524.lineTo(359.66666666666663, 86); + path524.lineTo(359.66666666666663, 84); + path524.lineTo(610.3333333333333, 84); + gBounds = path524.getBounds(); + _runPathTest(path524); + gFillType = path524.fillType; + allPaths.add(path524); +} + +void pathOps525() { + gBounds = path525.getBounds(); + allPaths.add(path525); +} + +void pathOps526() { + final Path path526 = Path(); + path526.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path526.getBounds(); + allPaths.add(path526); +} + +void pathOps527() { + final Path path527 = Path(); + path527.reset(); + path527.moveTo(889, 86); + path527.lineTo(638.3333333333333, 86); + path527.lineTo(638.3333333333333, 84); + path527.lineTo(889, 84); + gBounds = path527.getBounds(); + _runPathTest(path527); + gFillType = path527.fillType; + allPaths.add(path527); +} + +void pathOps528() { + gBounds = path528.getBounds(); + allPaths.add(path528); +} + +void pathOps529() { + gBounds = path529.getBounds(); + allPaths.add(path529); +} + +void pathOps530() { + gBounds = path530.getBounds(); + allPaths.add(path530); +} + +void pathOps531() { + gBounds = path531.getBounds(); + allPaths.add(path531); +} + +void pathOps532() { + gBounds = path532.getBounds(); + allPaths.add(path532); +} + +void pathOps533() { + final Path path533 = Path(); + path533.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path533.getBounds(); + allPaths.add(path533); +} + +void pathOps534() { + final Path path534 = Path(); + path534.reset(); + path534.moveTo(331.66666666666663, 86); + path534.lineTo(81, 86); + path534.lineTo(81, 84); + path534.lineTo(331.66666666666663, 84); + gBounds = path534.getBounds(); + _runPathTest(path534); + gFillType = path534.fillType; + allPaths.add(path534); +} + +void pathOps535() { + gBounds = path535.getBounds(); + allPaths.add(path535); +} + +void pathOps536() { + final Path path536 = Path(); + path536.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path536.getBounds(); + allPaths.add(path536); +} + +void pathOps537() { + final Path path537 = Path(); + path537.reset(); + path537.moveTo(610.3333333333333, 86); + path537.lineTo(359.66666666666663, 86); + path537.lineTo(359.66666666666663, 84); + path537.lineTo(610.3333333333333, 84); + gBounds = path537.getBounds(); + _runPathTest(path537); + gFillType = path537.fillType; + allPaths.add(path537); +} + +void pathOps538() { + gBounds = path538.getBounds(); + allPaths.add(path538); +} + +void pathOps539() { + final Path path539 = Path(); + path539.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path539.getBounds(); + allPaths.add(path539); +} + +void pathOps540() { + final Path path540 = Path(); + path540.reset(); + path540.moveTo(889, 86); + path540.lineTo(638.3333333333333, 86); + path540.lineTo(638.3333333333333, 84); + path540.lineTo(889, 84); + gBounds = path540.getBounds(); + _runPathTest(path540); + gFillType = path540.fillType; + allPaths.add(path540); +} + +void pathOps541() { + gBounds = path541.getBounds(); + allPaths.add(path541); +} + +void pathOps542() { + gBounds = path542.getBounds(); + allPaths.add(path542); +} + +void pathOps543() { + gBounds = path543.getBounds(); + allPaths.add(path543); +} + +void pathOps544() { + gBounds = path544.getBounds(); + allPaths.add(path544); +} + +void pathOps545() { + gBounds = path545.getBounds(); + allPaths.add(path545); +} + +void pathOps546() { + final Path path546 = Path(); + path546.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path546.getBounds(); + allPaths.add(path546); +} + +void pathOps547() { + final Path path547 = Path(); + path547.reset(); + path547.moveTo(331.66666666666663, 86); + path547.lineTo(81, 86); + path547.lineTo(81, 84); + path547.lineTo(331.66666666666663, 84); + gBounds = path547.getBounds(); + _runPathTest(path547); + gFillType = path547.fillType; + allPaths.add(path547); +} + +void pathOps548() { + gBounds = path548.getBounds(); + allPaths.add(path548); +} + +void pathOps549() { + final Path path549 = Path(); + path549.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path549.getBounds(); + allPaths.add(path549); +} + +void pathOps550() { + final Path path550 = Path(); + path550.reset(); + path550.moveTo(610.3333333333333, 86); + path550.lineTo(359.66666666666663, 86); + path550.lineTo(359.66666666666663, 84); + path550.lineTo(610.3333333333333, 84); + gBounds = path550.getBounds(); + _runPathTest(path550); + gFillType = path550.fillType; + allPaths.add(path550); +} + +void pathOps551() { + gBounds = path551.getBounds(); + allPaths.add(path551); +} + +void pathOps552() { + final Path path552 = Path(); + path552.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path552.getBounds(); + allPaths.add(path552); +} + +void pathOps553() { + final Path path553 = Path(); + path553.reset(); + path553.moveTo(889, 86); + path553.lineTo(638.3333333333333, 86); + path553.lineTo(638.3333333333333, 84); + path553.lineTo(889, 84); + gBounds = path553.getBounds(); + _runPathTest(path553); + gFillType = path553.fillType; + allPaths.add(path553); +} + +void pathOps554() { + gBounds = path554.getBounds(); + allPaths.add(path554); +} + +void pathOps555() { + gBounds = path555.getBounds(); + allPaths.add(path555); +} + +void pathOps556() { + gBounds = path556.getBounds(); + allPaths.add(path556); +} + +void pathOps557() { + gBounds = path557.getBounds(); + allPaths.add(path557); +} + +void pathOps558() { + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + gBounds = path558.getBounds(); + allPaths.add(path558); +} + +void pathOps559() { + final Path path559 = Path(); + path559.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(81, 0, 331.66666666666663, 84), Radius.zero), + ); + gBounds = path559.getBounds(); + allPaths.add(path559); +} + +void pathOps560() { + final Path path560 = Path(); + path560.reset(); + path560.moveTo(331.66666666666663, 86); + path560.lineTo(81, 86); + path560.lineTo(81, 84); + path560.lineTo(331.66666666666663, 84); + gBounds = path560.getBounds(); + _runPathTest(path560); + gFillType = path560.fillType; + allPaths.add(path560); +} + +void pathOps561() { + gBounds = path561.getBounds(); + allPaths.add(path561); +} + +void pathOps562() { + final Path path562 = Path(); + path562.addRRect( + RRect.fromRectAndRadius( + const Rect.fromLTRB(359.66666666666663, 0, 610.3333333333333, 84), + Radius.zero, + ), + ); + gBounds = path562.getBounds(); + allPaths.add(path562); +} + +void pathOps563() { + final Path path563 = Path(); + path563.reset(); + path563.moveTo(610.3333333333333, 86); + path563.lineTo(359.66666666666663, 86); + path563.lineTo(359.66666666666663, 84); + path563.lineTo(610.3333333333333, 84); + gBounds = path563.getBounds(); + _runPathTest(path563); + gFillType = path563.fillType; + allPaths.add(path563); +} + +void pathOps564() { + gBounds = path564.getBounds(); + allPaths.add(path564); +} + +void pathOps565() { + final Path path565 = Path(); + path565.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(638.3333333333333, 0, 889, 84), Radius.zero), + ); + gBounds = path565.getBounds(); + allPaths.add(path565); +} + +void pathOps566() { + final Path path566 = Path(); + path566.reset(); + path566.moveTo(889, 86); + path566.lineTo(638.3333333333333, 86); + path566.lineTo(638.3333333333333, 84); + path566.lineTo(889, 84); + gBounds = path566.getBounds(); + _runPathTest(path566); + gFillType = path566.fillType; + allPaths.add(path566); +} + +void pathOps567() { + final Path path567 = Path(); + path567.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path567.getBounds(); + allPaths.add(path567); +} + +void pathOps568() { + final Path path568 = Path(); + path568.reset(); + path568.moveTo(234.66666666666666, 120); + path568.lineTo(96, 120); + path568.lineTo(96, 119); + path568.lineTo(234.66666666666666, 119); + gBounds = path568.getBounds(); + _runPathTest(path568); + gFillType = path568.fillType; + allPaths.add(path568); +} + +void pathOps569() { + final Path path569 = Path(); + path569.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path569.getBounds(); + allPaths.add(path569); +} + +void pathOps570() { + final Path path570 = Path(); + path570.reset(); + path570.moveTo(234.66666666666666, 120); + path570.lineTo(96, 120); + path570.lineTo(96, 119); + path570.lineTo(234.66666666666666, 119); + gBounds = path570.getBounds(); + _runPathTest(path570); + gFillType = path570.fillType; + allPaths.add(path570); +} + +void pathOps571() { + final Path path571 = Path(); + path571.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(116.39999999999999, 136, 853.6, 1144), Radius.zero), + ); + gBounds = path571.getBounds(); + allPaths.add(path571); +} + +void pathOps572() { + final Path path572 = Path(); + path572.addRRect( + RRect.fromRectAndCorners( + const Rect.fromLTRB(0, 0, 312.6, 880), + topLeft: const Radius.circular(10), + topRight: const Radius.circular(10), + bottomLeft: const Radius.circular(2), + bottomRight: const Radius.circular(2), + ), + ); + gFillType = path572.fillType; + path573 = path572.shift(const Offset(525, 248)); + allPaths.add(path572); +} + +void pathOps573() { + gBounds = path573.getBounds(); + allPaths.add(path573); +} + +void pathOps574() { + final Path path574 = Path(); + path574.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(525, 248, 837.6, 1128), Radius.zero), + ); + gBounds = path574.getBounds(); + allPaths.add(path574); +} + +void pathOps575() { + final Path path575 = Path(); + path575.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(525, 248, 837.6, 304), Radius.zero)); + gBounds = path575.getBounds(); + allPaths.add(path575); +} + +void pathOps576() { + final Path path576 = Path(); + path576.moveTo(0, 0); + path576.lineTo(220.60000000000002, 0); + path576.quadraticBezierTo(235.60000000000002, 0, 237.569696969697, 7.817946907441011); + path576.arcToPoint( + const Offset(299.630303030303, 7.817946907441011), + radius: const Radius.circular(32), + clockwise: false, + ); + path576.quadraticBezierTo(301.6, 0, 316.6, 0); + path576.lineTo(312.6, 0); + path576.lineTo(312.6, 48); + path576.lineTo(0, 48); + path576.close(); + gFillType = path576.fillType; + path577 = path576.shift(const Offset(525, 1080)); + allPaths.add(path576); +} + +void pathOps577() { + gBounds = path577.getBounds(); + allPaths.add(path577); +} + +void pathOps578() { + final Path path578 = Path(); + path578.addOval(const Rect.fromLTRB(0, 0, 56, 56)); + gFillType = path578.fillType; + path579 = path578.shift(const Offset(765.6, 1052)); + allPaths.add(path578); +} + +void pathOps579() { + gBounds = path579.getBounds(); + allPaths.add(path579); +} + +void pathOps580() { + final Path path580 = Path(); + path580.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(116.39999999999999, 136, 853.6, 192), Radius.zero), + ); + gBounds = path580.getBounds(); + allPaths.add(path580); +} + +void pathOps581() { + final Path path581 = Path(); + path581.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path581.getBounds(); + allPaths.add(path581); +} + +void pathOps582() { + final Path path582 = Path(); + path582.reset(); + path582.moveTo(234.66666666666666, 120); + path582.lineTo(96, 120); + path582.lineTo(96, 119); + path582.lineTo(234.66666666666666, 119); + gBounds = path582.getBounds(); + _runPathTest(path582); + gFillType = path582.fillType; + allPaths.add(path582); +} + +void pathOps583() { + final Path path583 = Path(); + path583.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path583.getBounds(); + allPaths.add(path583); +} + +void pathOps584() { + final Path path584 = Path(); + path584.reset(); + path584.moveTo(234.66666666666666, 120); + path584.lineTo(96, 120); + path584.lineTo(96, 119); + path584.lineTo(234.66666666666666, 119); + gBounds = path584.getBounds(); + _runPathTest(path584); + gFillType = path584.fillType; + allPaths.add(path584); +} + +void pathOps585() { + final Path path585 = Path(); + path585.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path585.getBounds(); + allPaths.add(path585); +} + +void pathOps586() { + final Path path586 = Path(); + path586.reset(); + path586.moveTo(234.66666666666666, 120); + path586.lineTo(96, 120); + path586.lineTo(96, 119); + path586.lineTo(234.66666666666666, 119); + gBounds = path586.getBounds(); + _runPathTest(path586); + gFillType = path586.fillType; + allPaths.add(path586); +} + +void pathOps587() { + final Path path587 = Path(); + path587.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path587.getBounds(); + allPaths.add(path587); +} + +void pathOps588() { + final Path path588 = Path(); + path588.reset(); + path588.moveTo(234.66666666666666, 120); + path588.lineTo(96, 120); + path588.lineTo(96, 119); + path588.lineTo(234.66666666666666, 119); + gBounds = path588.getBounds(); + _runPathTest(path588); + gFillType = path588.fillType; + allPaths.add(path588); +} + +void pathOps589() { + final Path path589 = Path(); + path589.addRRect( + RRect.fromRectAndRadius(const Rect.fromLTRB(0, 0, 250.66666666666666, 120), Radius.zero), + ); + gBounds = path589.getBounds(); + allPaths.add(path589); +} + +void pathOps590() { + final Path path590 = Path(); + path590.reset(); + path590.moveTo(234.66666666666666, 120); + path590.lineTo(96, 120); + path590.lineTo(96, 119); + path590.lineTo(234.66666666666666, 119); + gBounds = path590.getBounds(); + _runPathTest(path590); + gFillType = path590.fillType; + allPaths.add(path590); +} + +void pathOps596() { + final Path path596 = Path(); + path596.reset(); + path596.moveTo(56, 42); + path596.lineTo(56, 42); + path596.lineTo(58, 42); + path596.lineTo(58, 42); + gBounds = path596.getBounds(); + allPaths.add(path596); +} + +void pathOps598() { + final Path path598 = Path(); + path598.reset(); + path598.moveTo(56, 42); + path598.lineTo(56, 42); + path598.lineTo(58, 42); + path598.lineTo(58, 42); + gBounds = path598.getBounds(); + allPaths.add(path598); +} + +void pathOps600() { + final Path path600 = Path(); + path600.reset(); + path600.moveTo(56, 42); + path600.lineTo(56, 42); + path600.lineTo(58, 42); + path600.lineTo(58, 42); + gBounds = path600.getBounds(); + allPaths.add(path600); +} + +void pathOps602() { + final Path path602 = Path(); + path602.reset(); + path602.moveTo(56, 42); + path602.lineTo(56, 42); + path602.lineTo(58, 42); + path602.lineTo(58, 42); + gBounds = path602.getBounds(); + allPaths.add(path602); +} + +void pathOps604() { + final Path path604 = Path(); + path604.reset(); + path604.moveTo(56, 42); + path604.lineTo(56, 42); + path604.lineTo(58, 42); + path604.lineTo(58, 42); + gBounds = path604.getBounds(); + allPaths.add(path604); +} + +void pathOps605() { + final Path path605 = Path(); + path605.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(0, 136, 970, 1144), Radius.zero)); + allPaths.add(path605); +} + +void pathOps607() { + final Path path607 = Path(); + path607.addRRect(RRect.fromRectAndRadius(const Rect.fromLTRB(450, 136, 970, 696), Radius.zero)); + allPaths.add(path607); +} + +void _runPathTest(Path path) {} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart new file mode 100644 index 00000000..64d1bc37 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_picture_recording.dart @@ -0,0 +1,77 @@ +// Copyright 2014 The Flutter 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:ui'; + +import 'recorder.dart'; + +/// Measure the performance of paint bounds estimation by recording a picture +/// without actually rendering it. +/// +/// Bounds estimation is done in two phases: +/// +/// * As we call drawing methods on `Canvas` we grow bounds with every paint op. +/// * When we're done recording a picture we call `PictureRecorder.endRecording` +/// at which point we compute the overall picture bounds and cache the result. +/// +/// This benchmarks puts emphasis on paint operations that trigger expensive +/// math such as `transformLTRB`. To do that we push non-identity transforms +/// and rotations before calling drawing methods. +class BenchPictureRecording extends RawRecorder { + BenchPictureRecording() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_picture_recording'; + + /// Cached paint used for drawing. + /// + /// We want to avoid polluting the results with paint initialization logic. + late Paint paint; + + /// A prelaid out and cached paragraph. + /// + /// This is cached to remove text layout time from the benchmark time. + late Paragraph paragraph; + + @override + Future setUpAll() async { + paint = Paint(); + paragraph = + (ParagraphBuilder(ParagraphStyle())..addText('abcd edfh ijkl mnop qrst uvwx yz')).build() + ..layout(const ParagraphConstraints(width: 50)); + } + + @override + void body(Profile profile) { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + profile.record('recordPaintCommands', () { + for (int i = 1; i <= 100; i++) { + canvas.translate((10 + i).toDouble(), (10 + i).toDouble()); + + canvas.save(); + for (int j = 0; j < 10; j++) { + canvas.drawRect(const Rect.fromLTWH(10, 10, 10, 10), paint); + canvas.drawCircle(const Offset(50, 50), 50, paint); + canvas.rotate(1.0); + } + canvas.restore(); + + canvas.save(); + for (int j = 0; j < 10; j++) { + canvas.translate(1, 1); + canvas.clipRect(Rect.fromLTWH(20, 20, 40 / i, 40)); + canvas.drawRRect( + RRect.fromRectAndRadius(const Rect.fromLTWH(10, 10, 10, 10), const Radius.circular(2)), + paint, + ); + canvas.drawParagraph(paragraph, Offset.zero); + } + canvas.restore(); + } + }, reported: true); + profile.record('estimatePaintBounds', () { + recorder.endRecording(); + }, reported: true); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart new file mode 100644 index 00000000..f18e57ab --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart @@ -0,0 +1,110 @@ +// Copyright 2014 The Flutter 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:ui_web' as ui_web; + +import 'package:flutter/material.dart'; +import 'package:web/web.dart' as web; + +import 'recorder.dart'; + +const String benchmarkViewType = 'benchmark_element'; + +void _registerFactory() { + ui_web.platformViewRegistry.registerViewFactory(benchmarkViewType, (int viewId) { + final web.HTMLElement htmlElement = web.document.createElement('div') as web.HTMLDivElement; + htmlElement.id = '${benchmarkViewType}_$viewId'; + htmlElement.innerText = 'Google'; + htmlElement.style + ..setProperty('width', '100%') + ..setProperty('height', '100%') + ..setProperty('color', 'black') + ..setProperty('backgroundColor', 'rgba(0, 255, 0, .5)') + ..setProperty('textAlign', 'center') + ..setProperty('border', '1px solid black'); + return htmlElement; + }); +} + +/// Creates an infinite list of Link widgets and scrolls it. +class BenchPlatformViewInfiniteScroll extends WidgetRecorder { + BenchPlatformViewInfiniteScroll.forward() + : initialOffset = 0.0, + finalOffset = 30000.0, + super(name: benchmarkName) { + _registerFactory(); + } + + BenchPlatformViewInfiniteScroll.backward() + : initialOffset = 30000.0, + finalOffset = 0.0, + super(name: benchmarkNameBackward) { + _registerFactory(); + } + + static const String benchmarkName = 'bench_platform_view_infinite_scroll'; + static const String benchmarkNameBackward = 'bench_platform_view_infinite_scroll_backward'; + + final double initialOffset; + final double finalOffset; + + @override + Widget createWidget() => MaterialApp( + title: 'Infinite Platform View Scroll Benchmark', + home: _InfiniteScrollPlatformViews(initialOffset, finalOffset), + ); +} + +class _InfiniteScrollPlatformViews extends StatefulWidget { + const _InfiniteScrollPlatformViews(this.initialOffset, this.finalOffset); + + final double initialOffset; + final double finalOffset; + + @override + State<_InfiniteScrollPlatformViews> createState() => _InfiniteScrollPlatformViewsState(); +} + +class _InfiniteScrollPlatformViewsState extends State<_InfiniteScrollPlatformViews> { + static const Duration stepDuration = Duration(seconds: 20); + + late ScrollController scrollController; + late double offset; + + @override + void initState() { + super.initState(); + + offset = widget.initialOffset; + + scrollController = ScrollController(initialScrollOffset: offset); + + // Without the timer the animation doesn't begin. + Timer.run(() async { + await scrollController.animateTo( + widget.finalOffset, + curve: Curves.linear, + duration: stepDuration, + ); + }); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + controller: scrollController, + itemExtent: 100.0, + itemBuilder: (BuildContext context, int index) { + return const SizedBox(height: 100.0, child: HtmlElementView(viewType: benchmarkViewType)); + }, + ); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_simple_lazy_text_scroll.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_simple_lazy_text_scroll.dart new file mode 100644 index 00000000..9b68d67e --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_simple_lazy_text_scroll.dart @@ -0,0 +1,110 @@ +// Copyright 2014 The Flutter 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:flutter/widgets.dart'; + +import 'recorder.dart'; +import 'test_data.dart'; + +/// Creates several list views containing text items, then continuously scrolls +/// them up and down. +/// +/// Measures our ability to lazily render virtually infinitely big content. +class BenchSimpleLazyTextScroll extends WidgetRecorder { + BenchSimpleLazyTextScroll() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_simple_lazy_text_scroll'; + + @override + Widget createWidget() { + return const Directionality( + textDirection: TextDirection.ltr, + child: Row( + children: [ + Flexible( + child: _TestScrollingWidget( + initialScrollOffset: 0, + scrollDistance: 300, + scrollDuration: Duration(seconds: 1), + ), + ), + Flexible( + child: _TestScrollingWidget( + initialScrollOffset: 1000, + scrollDistance: 500, + scrollDuration: Duration(milliseconds: 1500), + ), + ), + Flexible( + child: _TestScrollingWidget( + initialScrollOffset: 2000, + scrollDistance: 700, + scrollDuration: Duration(milliseconds: 2000), + ), + ), + ], + ), + ); + } +} + +class _TestScrollingWidget extends StatefulWidget { + const _TestScrollingWidget({ + required this.initialScrollOffset, + required this.scrollDistance, + required this.scrollDuration, + }); + + final double initialScrollOffset; + final double scrollDistance; + final Duration scrollDuration; + + @override + State createState() { + return _TestScrollingWidgetState(); + } +} + +class _TestScrollingWidgetState extends State<_TestScrollingWidget> { + late ScrollController scrollController; + + @override + void initState() { + super.initState(); + + scrollController = ScrollController(initialScrollOffset: widget.initialScrollOffset); + + // Without the timer the animation doesn't begin. + Timer.run(() async { + bool forward = true; + while (true) { + await scrollController.animateTo( + forward ? widget.initialScrollOffset + widget.scrollDistance : widget.initialScrollOffset, + curve: Curves.linear, + duration: widget.scrollDuration, + ); + forward = !forward; + } + }); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + controller: scrollController, + itemCount: 10000, + itemBuilder: (BuildContext context, int index) { + return Text(lipsum[index % lipsum.length]); + }, + ); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart new file mode 100644 index 00000000..d48e4f19 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart @@ -0,0 +1,335 @@ +// Copyright 2014 The Flutter 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:math'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; + +import 'recorder.dart'; + +const String chars = + '1234567890' + 'abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '!@#%^&()[]{}<>,./?;:"`~-_=+|'; + +String _randomize(String text) { + return text.replaceAllMapped( + '*', + // Passing a seed so the results are reproducible. + (_) => chars[Random(0).nextInt(chars.length)], + ); +} + +class ParagraphGenerator { + int _counter = 0; + + /// Randomizes the given [text] and creates a paragraph with a unique + /// font-size so that the engine doesn't reuse a cached ruler. + ui.Paragraph generate(String text, {int? maxLines, bool hasEllipsis = false}) { + final ui.ParagraphBuilder builder = + ui.ParagraphBuilder( + ui.ParagraphStyle( + fontFamily: 'sans-serif', + maxLines: maxLines, + ellipsis: hasEllipsis ? '...' : null, + ), + ) + // Start from a font-size of 8.0 and go up by 0.01 each time. + ..pushStyle(ui.TextStyle(fontSize: 8.0 + _counter * 0.01)) + ..addText(_randomize(text)); + _counter++; + return builder.build(); + } +} + +/// Repeatedly lays out a paragraph. +/// +/// Creates a different paragraph each time in order to avoid hitting the cache. +class BenchTextLayout extends RawRecorder { + BenchTextLayout() : super(name: benchmarkName); + + static const String benchmarkName = 'text_canvaskit_layout'; + + final ParagraphGenerator generator = ParagraphGenerator(); + + static const String singleLineText = '*** ** ****'; + static const String multiLineText = + '*** ****** **** *** ******** * *** ' + '******* **** ********** *** ******* ' + '**** ***** *** ******** *** ********* ' + '** * *** ******* ***********'; + + @override + void body(Profile profile) { + recordParagraphOperations( + profile: profile, + paragraph: generator.generate(singleLineText), + text: singleLineText, + keyPrefix: 'single_line', + maxWidth: 800.0, + ); + + recordParagraphOperations( + profile: profile, + paragraph: generator.generate(multiLineText), + text: multiLineText, + keyPrefix: 'multi_line', + maxWidth: 200.0, + ); + + recordParagraphOperations( + profile: profile, + paragraph: generator.generate(multiLineText, maxLines: 2), + text: multiLineText, + keyPrefix: 'max_lines', + maxWidth: 200.0, + ); + + recordParagraphOperations( + profile: profile, + paragraph: generator.generate(multiLineText, hasEllipsis: true), + text: multiLineText, + keyPrefix: 'ellipsis', + maxWidth: 200.0, + ); + } + + void recordParagraphOperations({ + required Profile profile, + required ui.Paragraph paragraph, + required String text, + required String keyPrefix, + required double maxWidth, + }) { + profile.record('$keyPrefix.layout', () { + paragraph.layout(ui.ParagraphConstraints(width: maxWidth)); + }, reported: true); + profile.record('$keyPrefix.getBoxesForRange', () { + for (int start = 0; start < text.length; start += 3) { + for (int end = start + 1; end < text.length; end *= 2) { + paragraph.getBoxesForRange(start, end); + } + } + }, reported: true); + profile.record('$keyPrefix.getPositionForOffset', () { + for (double dx = 0.0; dx < paragraph.width; dx += 10.0) { + for (double dy = 0.0; dy < paragraph.height; dy += 10.0) { + paragraph.getPositionForOffset(Offset(dx, dy)); + } + } + }, reported: true); + } +} + +/// Repeatedly lays out the same paragraph. +/// +/// Uses the same paragraph content to make sure we hit the cache. It doesn't +/// use the same paragraph instance because the layout method will shortcircuit +/// in that case. +class BenchTextCachedLayout extends RawRecorder { + BenchTextCachedLayout() : super(name: benchmarkName); + + static const String benchmarkName = 'text_canvas_kit_cached_layout'; + + @override + void body(Profile profile) { + final ui.ParagraphBuilder builder = + ui.ParagraphBuilder(ui.ParagraphStyle(fontFamily: 'sans-serif')) + ..pushStyle(ui.TextStyle(fontSize: 12.0)) + ..addText( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, ' + 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + ); + final ui.Paragraph paragraph = builder.build(); + profile.record('layout', () { + paragraph.layout(const ui.ParagraphConstraints(width: double.infinity)); + }, reported: true); + } +} + +/// Global counter incremented every time the benchmark is asked to +/// [createWidget]. +/// +/// The purpose of this counter is to make sure the rendered paragraphs on each +/// build are unique. +int _counter = 0; + +/// Measures how expensive it is to construct a realistic text-heavy piece of UI. +/// +/// The benchmark constructs a tabbed view, where each tab displays a list of +/// colors. Each color's description is made of several [Text] nodes. +class BenchBuildColorsGrid extends WidgetBuildRecorder { + BenchBuildColorsGrid() : super(name: benchmarkName); + + /// Disables tracing for this benchmark. + /// + /// When tracing is enabled, DOM layout takes longer to complete. This has a + /// significant effect on the benchmark since we do a lot of text layout + /// operations that trigger synchronous DOM layout. + @override + bool get isTracingEnabled => false; + + static const String benchmarkName = 'text_canvas_kit_color_grid'; + + @override + Widget createWidget() { + _counter++; + return const MaterialApp(home: ColorsDemo()); + } +} + +// The code below was copied from `colors_demo.dart` in the `flutter_gallery` +// example. + +const double kColorItemHeight = 48.0; + +class Palette { + Palette({required this.name, required this.primary, this.accent, this.threshold = 900}); + + final String name; + final MaterialColor primary; + final MaterialAccentColor? accent; + final int threshold; // titles for indices > threshold are white, otherwise black +} + +final List allPalettes = [ + Palette(name: 'RED', primary: Colors.red, accent: Colors.redAccent, threshold: 300), + Palette(name: 'PINK', primary: Colors.pink, accent: Colors.pinkAccent, threshold: 200), + Palette(name: 'PURPLE', primary: Colors.purple, accent: Colors.purpleAccent, threshold: 200), + Palette( + name: 'DEEP PURPLE', + primary: Colors.deepPurple, + accent: Colors.deepPurpleAccent, + threshold: 200, + ), + Palette(name: 'INDIGO', primary: Colors.indigo, accent: Colors.indigoAccent, threshold: 200), + Palette(name: 'BLUE', primary: Colors.blue, accent: Colors.blueAccent, threshold: 400), + Palette( + name: 'LIGHT BLUE', + primary: Colors.lightBlue, + accent: Colors.lightBlueAccent, + threshold: 500, + ), + Palette(name: 'CYAN', primary: Colors.cyan, accent: Colors.cyanAccent, threshold: 600), + Palette(name: 'TEAL', primary: Colors.teal, accent: Colors.tealAccent, threshold: 400), + Palette(name: 'GREEN', primary: Colors.green, accent: Colors.greenAccent, threshold: 500), + Palette( + name: 'LIGHT GREEN', + primary: Colors.lightGreen, + accent: Colors.lightGreenAccent, + threshold: 600, + ), + Palette(name: 'LIME', primary: Colors.lime, accent: Colors.limeAccent, threshold: 800), + Palette(name: 'YELLOW', primary: Colors.yellow, accent: Colors.yellowAccent), + Palette(name: 'AMBER', primary: Colors.amber, accent: Colors.amberAccent), + Palette(name: 'ORANGE', primary: Colors.orange, accent: Colors.orangeAccent, threshold: 700), + Palette( + name: 'DEEP ORANGE', + primary: Colors.deepOrange, + accent: Colors.deepOrangeAccent, + threshold: 400, + ), + Palette(name: 'BROWN', primary: Colors.brown, threshold: 200), + Palette(name: 'GREY', primary: Colors.grey, threshold: 500), + Palette(name: 'BLUE GREY', primary: Colors.blueGrey, threshold: 500), +]; + +class ColorItem extends StatelessWidget { + const ColorItem({super.key, required this.index, required this.color, this.prefix = ''}); + + final int index; + final Color color; + final String prefix; + + String colorString() => + "$_counter:#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + @override + Widget build(BuildContext context) { + return Semantics( + container: true, + child: Container( + height: kColorItemHeight, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + color: color, + child: SafeArea( + top: false, + bottom: false, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('$_counter:$prefix$index'), Text(colorString())], + ), + ), + ), + ); + } +} + +class PaletteTabView extends StatelessWidget { + const PaletteTabView({super.key, required this.colors}); + + final Palette colors; + + static const List primaryKeys = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]; + static const List accentKeys = [100, 200, 400, 700]; + + @override + Widget build(BuildContext context) { + final TextTheme textTheme = Theme.of(context).textTheme; + final TextStyle whiteTextStyle = textTheme.bodyMedium!.copyWith(color: Colors.white); + final TextStyle blackTextStyle = textTheme.bodyMedium!.copyWith(color: Colors.black); + return Scrollbar( + child: ListView( + itemExtent: kColorItemHeight, + children: [ + ...primaryKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.primary[index]!), + ); + }), + if (colors.accent != null) + ...accentKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.accent![index]!, prefix: 'A'), + ); + }), + ], + ), + ); + } +} + +class ColorsDemo extends StatelessWidget { + const ColorsDemo({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: allPalettes.length, + child: Scaffold( + appBar: AppBar( + elevation: 0.0, + title: const Text('Colors'), + bottom: TabBar( + isScrollable: true, + tabs: + allPalettes + .map((Palette swatch) => Tab(text: '$_counter:${swatch.name}')) + .toList(), + ), + ), + body: TabBarView( + children: + allPalettes.map((Palette colors) { + return PaletteTabView(colors: colors); + }).toList(), + ), + ), + ); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_out_of_picture_bounds.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_out_of_picture_bounds.dart new file mode 100644 index 00000000..0b95fb17 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_out_of_picture_bounds.dart @@ -0,0 +1,120 @@ +// Copyright 2014 The Flutter 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:math' as math; +import 'dart:ui'; + +import 'recorder.dart'; +import 'test_data.dart'; + +/// Draws 9 screens worth of text in a 3x3 grid with only the middle cell +/// appearing on the visible screen: +/// +/// +-------------+-------------+-------------+ +/// | | | | +/// | invisible | invisible | invisible | +/// | | | | +/// +-----------------------------------------+ +/// | | | | +/// | invisible | visible | invisible | +/// | | | | +/// +-----------------------------------------+ +/// | | | | +/// | invisible | invisible | invisible | +/// | | | | +/// +-------------+-------------+-------------+ +/// +/// This reproduces the bug where we render more than visible causing +/// performance issues: https://github.com/flutter/flutter/issues/48516 +class BenchTextOutOfPictureBounds extends SceneBuilderRecorder { + BenchTextOutOfPictureBounds() : super(name: benchmarkName) { + const Color red = Color.fromARGB(255, 255, 0, 0); + const Color green = Color.fromARGB(255, 0, 255, 0); + + // We don't want paragraph generation and layout to pollute benchmark numbers. + singleLineParagraphs = generateLaidOutParagraphs( + paragraphCount: 500, + minWordCountPerParagraph: 2, + maxWordCountPerParagraph: 4, + widthConstraint: view.physicalSize.width / 2, + color: red, + ); + multiLineParagraphs = generateLaidOutParagraphs( + paragraphCount: 50, + minWordCountPerParagraph: 30, + maxWordCountPerParagraph: 49, + widthConstraint: view.physicalSize.width / 2, + color: green, + ); + } + + // Use hard-coded seed to make sure the data is stable across benchmark runs. + static final math.Random _random = math.Random(0); + + static const String benchmarkName = 'text_out_of_picture_bounds'; + + late List singleLineParagraphs; + late List multiLineParagraphs; + + @override + void onDrawFrame(SceneBuilder sceneBuilder) { + final PictureRecorder pictureRecorder = PictureRecorder(); + final Canvas canvas = Canvas(pictureRecorder); + final Size viewSize = view.physicalSize; + const double padding = 10.0; + + // Fills a single cell with random text. + void fillCellWithText(List textSource) { + canvas.save(); + double topOffset = 0; + while (topOffset < viewSize.height) { + final Paragraph paragraph = textSource[_random.nextInt(textSource.length)]; + + // Give it enough space to make sure it ends up being a single-line paragraph. + paragraph.layout(ParagraphConstraints(width: viewSize.width / 2)); + + canvas.drawParagraph(paragraph, Offset.zero); + canvas.translate(0, paragraph.height + padding); + topOffset += paragraph.height + padding; + } + canvas.restore(); + } + + // Starting with the top-left cell, fill every cell with text. + canvas.translate(-viewSize.width, -viewSize.height); + for (int row = 0; row < 3; row++) { + canvas.save(); + for (int col = 0; col < 3; col++) { + canvas.drawRect( + Offset.zero & viewSize, + Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0, + ); + // Fill single-line text. + fillCellWithText(singleLineParagraphs); + + // Fill multi-line text. + canvas.save(); + canvas.translate(viewSize.width / 2, 0); + fillCellWithText(multiLineParagraphs); + canvas.restore(); + + // Shift to next column. + canvas.translate(viewSize.width, 0); + } + + // Undo horizontal shift. + canvas.restore(); + + // Shift to next row. + canvas.translate(0, viewSize.height); + } + + final Picture picture = pictureRecorder.endRecording(); + sceneBuilder.pushOffset(0.0, 0.0); + sceneBuilder.addPicture(Offset.zero, picture); + sceneBuilder.pop(); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_wrapbox_scroll.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_wrapbox_scroll.dart new file mode 100644 index 00000000..b9dfdee7 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/bench_wrapbox_scroll.dart @@ -0,0 +1,144 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'recorder.dart'; + +/// Creates a [Wrap] inside a ListView. +/// +/// Tests large number of DOM nodes since image breaks up large canvas. +class BenchWrapBoxScroll extends WidgetRecorder { + BenchWrapBoxScroll() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_wrapbox_scroll'; + + @override + Widget createWidget() { + return MaterialApp( + theme: ThemeData(primarySwatch: Colors.blue), + title: 'WrapBox Scroll Benchmark', + home: const Scaffold(body: MyHomePage()), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + late ScrollController scrollController; + int block = 0; + static const Duration stepDuration = Duration(milliseconds: 500); + static const double stepDistance = 400; + + @override + void initState() { + super.initState(); + + scrollController = ScrollController(); + + // Without the timer the animation doesn't begin. + Timer.run(() async { + while (block < 25) { + await scrollController.animateTo( + (block % 5) * stepDistance, + duration: stepDuration, + curve: Curves.easeInOut, + ); + block++; + } + }); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView( + controller: scrollController, + children: [ + Wrap( + children: [ + for (int i = 0; i < 30; i++) + FractionallySizedBox(widthFactor: 0.2, child: ProductPreview(i)), //need case1 + for (int i = 0; i < 30; i++) ProductPreview(i), //need case2 + ], + ), + ], + ); + } +} + +class ProductPreview extends StatelessWidget { + const ProductPreview(this.previewIndex, {super.key}); + + final int previewIndex; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () => print('tap'), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: const EdgeInsets.all(23), + padding: const EdgeInsets.all(18), + decoration: const BoxDecoration(color: Color(0xfff9f9f9), shape: BoxShape.circle), + child: Image.network('assets/assets/Icon-192.png', width: 100, height: 100), + ), + const Text('title'), + const SizedBox(height: 14), + Wrap( + alignment: WrapAlignment.center, + children: [ + ProductOption(optionText: '$previewIndex: option1'), + ProductOption(optionText: '$previewIndex: option2'), + ProductOption(optionText: '$previewIndex: option3'), + ProductOption(optionText: '$previewIndex: option4'), + ProductOption(optionText: '$previewIndex: option5'), + ], + ), + ], + ), + ); + } +} + +class ProductOption extends StatelessWidget { + const ProductOption({super.key, required this.optionText}); + + final String optionText; + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints(minWidth: 56), + margin: const EdgeInsets.all(2), + padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 5), + decoration: BoxDecoration( + border: Border.all(color: const Color(0xffebebeb)), + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + child: Text( + optionText, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart new file mode 100644 index 00000000..2e516d18 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart @@ -0,0 +1,2199 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import 'package:flutter/services.dart'; + +const SizedBox rowDivider = SizedBox(width: 20); +const SizedBox colDivider = SizedBox(height: 10); +const double smallSpacing = 10.0; +const double cardWidth = 115; +const double widthConstraint = 450; +final GlobalKey scaffoldKey = GlobalKey(); + +class SingleColumnMaterial3Components extends StatelessWidget { + const SingleColumnMaterial3Components({super.key, this.scrollController}); + + final ScrollController? scrollController; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: ListView( + controller: scrollController, + children: [ + const Actions(), + colDivider, + const Communication(), + colDivider, + const Containment(), + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ), + ), + ); + } +} + +class TwoColumnMaterial3Components extends StatefulWidget { + const TwoColumnMaterial3Components({super.key}); + + @override + State createState() => _TwoColumnMaterial3ComponentsState(); +} + +class _TwoColumnMaterial3ComponentsState extends State { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: Row( + children: [ + Expanded( + child: FirstComponentList( + showNavBottomBar: true, + scaffoldKey: scaffoldKey, + showSecondList: true, + ), + ), + Expanded(child: SecondComponentList(scaffoldKey: scaffoldKey)), + ], + ), + ), + ); + } +} + +class FirstComponentList extends StatelessWidget { + const FirstComponentList({ + super.key, + required this.showNavBottomBar, + required this.scaffoldKey, + required this.showSecondList, + }); + + final bool showNavBottomBar; + final GlobalKey scaffoldKey; + final bool showSecondList; + + @override + Widget build(BuildContext context) { + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: ListView( + padding: + showSecondList ? const EdgeInsetsDirectional.only(end: smallSpacing) : EdgeInsets.zero, + children: [ + const Actions(), + colDivider, + const Communication(), + colDivider, + const Containment(), + if (!showSecondList) ...[ + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ], + ), + ); + } +} + +class SecondComponentList extends StatelessWidget { + const SecondComponentList({super.key, required this.scaffoldKey}); + + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: ListView( + padding: const EdgeInsetsDirectional.only(end: smallSpacing), + children: [ + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ), + ); + } +} + +class Actions extends StatelessWidget { + const Actions({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Actions', + children: [ + Buttons(), + FloatingActionButtons(), + IconToggleButtons(), + SegmentedButtons(), + ], + ); + } +} + +class Communication extends StatelessWidget { + const Communication({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Communication', + children: [ + NavigationBars(selectedIndex: 1, isExampleBar: true, isBadgeExample: true), + ProgressIndicators(), + SnackBarSection(), + ], + ); + } +} + +class Containment extends StatelessWidget { + const Containment({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Containment', + children: [BottomSheetSection(), Cards(), Dialogs(), Dividers()], + ); + } +} + +class Navigation extends StatelessWidget { + const Navigation({super.key, required this.scaffoldKey}); + + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentGroupDecoration( + label: 'Navigation', + children: [ + const BottomAppBars(), + const NavigationBars(selectedIndex: 0, isExampleBar: true), + NavigationDrawers(scaffoldKey: scaffoldKey), + const NavigationRails(), + const Tabs(), + const TopAppBars(), + ], + ); + } +} + +class Selection extends StatelessWidget { + const Selection({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Selection', + children: [Checkboxes(), Chips(), Menus(), Radios(), Sliders(), Switches()], + ); + } +} + +class TextInputs extends StatelessWidget { + const TextInputs({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration(label: 'Text inputs', children: [TextFields()]); + } +} + +class Buttons extends StatefulWidget { + const Buttons({super.key}); + + @override + State createState() => _ButtonsState(); +} + +class _ButtonsState extends State { + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Common buttons', + tooltipMessage: + 'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton', + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ButtonsWithoutIcon(isDisabled: false), + ButtonsWithIcon(), + ButtonsWithoutIcon(isDisabled: true), + ], + ), + ), + ); + } +} + +class ButtonsWithoutIcon extends StatelessWidget { + const ButtonsWithoutIcon({super.key, required this.isDisabled}); + + final bool isDisabled; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton(onPressed: isDisabled ? null : () {}, child: const Text('Elevated')), + colDivider, + FilledButton(onPressed: isDisabled ? null : () {}, child: const Text('Filled')), + colDivider, + FilledButton.tonal( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled tonal'), + ), + colDivider, + OutlinedButton(onPressed: isDisabled ? null : () {}, child: const Text('Outlined')), + colDivider, + TextButton(onPressed: isDisabled ? null : () {}, child: const Text('Text')), + ], + ), + ), + ); + } +} + +class ButtonsWithIcon extends StatelessWidget { + const ButtonsWithIcon({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + FilledButton.icon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + FilledButton.tonalIcon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + TextButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + ], + ), + ), + ); + } +} + +class FloatingActionButtons extends StatelessWidget { + const FloatingActionButtons({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Floating action buttons', + tooltipMessage: 'Use FloatingActionButton or FloatingActionButton.extended', + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: smallSpacing, + spacing: smallSpacing, + children: [ + FloatingActionButton.small( + onPressed: () {}, + tooltip: 'Small', + child: const Icon(Icons.add), + ), + FloatingActionButton.extended( + onPressed: () {}, + tooltip: 'Extended', + icon: const Icon(Icons.add), + label: const Text('Create'), + ), + FloatingActionButton(onPressed: () {}, tooltip: 'Standard', child: const Icon(Icons.add)), + FloatingActionButton.large( + onPressed: () {}, + tooltip: 'Large', + child: const Icon(Icons.add), + ), + ], + ), + ); + } +} + +class Cards extends StatelessWidget { + const Cards({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Cards', + tooltipMessage: 'Use Card', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + SizedBox( + width: cardWidth, + child: Card( + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ), + const SizedBox(height: 20), + const Align(alignment: Alignment.bottomLeft, child: Text('Elevated')), + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + elevation: 0, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ), + const SizedBox(height: 20), + const Align(alignment: Alignment.bottomLeft, child: Text('Filled')), + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).colorScheme.outline), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ), + const SizedBox(height: 20), + const Align(alignment: Alignment.bottomLeft, child: Text('Outlined')), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class _ClearButton extends StatelessWidget { + const _ClearButton({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) => + IconButton(icon: const Icon(Icons.clear), onPressed: () => controller.clear()); +} + +class TextFields extends StatefulWidget { + const TextFields({super.key}); + + @override + State createState() => _TextFieldsState(); +} + +class _TextFieldsState extends State { + final TextEditingController _controllerFilled = TextEditingController(); + final TextEditingController _controllerOutlined = TextEditingController(); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Text fields', + tooltipMessage: 'Use TextField with different InputDecoration', + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + maxLength: 10, + maxLengthEnforcement: MaxLengthEnforcement.none, + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + errorText: 'error text', + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerFilled, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerOutlined), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerOutlined), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + errorText: 'error text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerOutlined), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class Dialogs extends StatefulWidget { + const Dialogs({super.key}); + + @override + State createState() => _DialogsState(); +} + +class _DialogsState extends State { + void openDialog(BuildContext context) { + showDialog( + context: context, + builder: + (BuildContext context) => AlertDialog( + title: const Text('What is a dialog?'), + content: const Text( + 'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.', + ), + actions: [ + TextButton(child: const Text('Okay'), onPressed: () => Navigator.of(context).pop()), + FilledButton( + child: const Text('Dismiss'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + } + + void openFullscreenDialog(BuildContext context) { + showDialog( + context: context, + builder: + (BuildContext context) => Dialog.fullscreen( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Scaffold( + appBar: AppBar( + title: const Text('Full-screen dialog'), + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + actions: [ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Dialog', + tooltipMessage: 'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog', + child: Wrap( + alignment: WrapAlignment.spaceBetween, + children: [ + TextButton( + child: const Text('Show dialog', style: TextStyle(fontWeight: FontWeight.bold)), + onPressed: () => openDialog(context), + ), + TextButton( + child: const Text( + 'Show full-screen dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openFullscreenDialog(context), + ), + ], + ), + ); + } +} + +class Dividers extends StatelessWidget { + const Dividers({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Dividers', + tooltipMessage: 'Use Divider or VerticalDivider', + child: Column(children: [Divider(key: Key('divider'))]), + ); + } +} + +class Switches extends StatelessWidget { + const Switches({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Switches', + tooltipMessage: 'Use SwitchListTile or Switch', + child: Column(children: [SwitchRow(isEnabled: true), SwitchRow(isEnabled: false)]), + ); + } +} + +class SwitchRow extends StatefulWidget { + const SwitchRow({super.key, required this.isEnabled}); + + final bool isEnabled; + + @override + State createState() => _SwitchRowState(); +} + +class _SwitchRowState extends State { + bool value0 = false; + bool value1 = true; + + final MaterialStateProperty thumbIcon = MaterialStateProperty.resolveWith(( + Set states, + ) { + if (states.contains(MaterialState.selected)) { + return const Icon(Icons.check); + } + return const Icon(Icons.close); + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Switch( + value: value0, + onChanged: + widget.isEnabled + ? (bool value) { + setState(() { + value0 = value; + }); + } + : null, + ), + Switch( + thumbIcon: thumbIcon, + value: value1, + onChanged: + widget.isEnabled + ? (bool value) { + setState(() { + value1 = value; + }); + } + : null, + ), + ], + ); + } +} + +class Checkboxes extends StatefulWidget { + const Checkboxes({super.key}); + + @override + State createState() => _CheckboxesState(); +} + +class _CheckboxesState extends State { + bool? isChecked0 = true; + bool? isChecked1; + bool? isChecked2 = false; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Checkboxes', + tooltipMessage: 'Use CheckboxListTile or Checkbox', + child: Column( + children: [ + CheckboxListTile( + tristate: true, + value: isChecked0, + title: const Text('Option 1'), + onChanged: (bool? value) { + setState(() { + isChecked0 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked1, + title: const Text('Option 2'), + onChanged: (bool? value) { + setState(() { + isChecked1 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked2, + title: const Text('Option 3'), + onChanged: (bool? value) { + setState(() { + isChecked2 = value; + }); + }, + ), + const CheckboxListTile( + tristate: true, + title: Text('Option 4'), + value: true, + onChanged: null, + ), + ], + ), + ); + } +} + +enum Value { first, second } + +class Radios extends StatefulWidget { + const Radios({super.key}); + + @override + State createState() => _RadiosState(); +} + +enum Options { option1, option2, option3 } + +class _RadiosState extends State { + Options? _selectedOption = Options.option1; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Radio buttons', + tooltipMessage: 'Use RadioListTile or Radio', + child: Column( + children: [ + RadioListTile( + title: const Text('Option 1'), + value: Options.option1, + groupValue: _selectedOption, + onChanged: (Options? value) { + setState(() { + _selectedOption = value; + }); + }, + ), + RadioListTile( + title: const Text('Option 2'), + value: Options.option2, + groupValue: _selectedOption, + onChanged: (Options? value) { + setState(() { + _selectedOption = value; + }); + }, + ), + RadioListTile( + title: const Text('Option 3'), + value: Options.option3, + groupValue: _selectedOption, + onChanged: null, + ), + ], + ), + ); + } +} + +class ProgressIndicators extends StatefulWidget { + const ProgressIndicators({super.key}); + + @override + State createState() => _ProgressIndicatorsState(); +} + +class _ProgressIndicatorsState extends State { + bool playProgressIndicator = false; + + @override + Widget build(BuildContext context) { + final double? progressValue = playProgressIndicator ? null : 0.7; + + return ComponentDecoration( + label: 'Progress indicators', + tooltipMessage: 'Use CircularProgressIndicator or LinearProgressIndicator', + child: Column( + children: [ + Row( + children: [ + IconButton( + isSelected: playProgressIndicator, + selectedIcon: const Icon(Icons.pause), + icon: const Icon(Icons.play_arrow), + onPressed: () { + setState(() { + playProgressIndicator = !playProgressIndicator; + }); + }, + ), + Expanded( + child: Row( + children: [ + rowDivider, + CircularProgressIndicator(value: progressValue), + rowDivider, + Expanded(child: LinearProgressIndicator(value: progressValue)), + rowDivider, + ], + ), + ), + ], + ), + ], + ), + ); + } +} + +const List appBarDestinations = [ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.widgets_outlined), + label: 'Components', + selectedIcon: Icon(Icons.widgets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.format_paint_outlined), + label: 'Color', + selectedIcon: Icon(Icons.format_paint), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.text_snippet_outlined), + label: 'Typography', + selectedIcon: Icon(Icons.text_snippet), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.invert_colors_on_outlined), + label: 'Elevation', + selectedIcon: Icon(Icons.opacity), + ), +]; + +const List exampleBarDestinations = [ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.explore_outlined), + label: 'Explore', + selectedIcon: Icon(Icons.explore), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.pets_outlined), + label: 'Pets', + selectedIcon: Icon(Icons.pets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.account_box_outlined), + label: 'Account', + selectedIcon: Icon(Icons.account_box), + ), +]; + +List barWithBadgeDestinations = [ + NavigationDestination( + tooltip: '', + icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)), + label: 'Mail', + selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)), + label: 'Chat', + selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(child: Icon(Icons.group_outlined)), + label: 'Rooms', + selectedIcon: Badge(child: Icon(Icons.group_rounded)), + ), + NavigationDestination( + tooltip: '', + icon: Badge.count(count: 3, child: const Icon(Icons.videocam_outlined)), + label: 'Meet', + selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)), + ), +]; + +class NavigationBars extends StatefulWidget { + const NavigationBars({ + super.key, + this.onSelectItem, + required this.selectedIndex, + required this.isExampleBar, + this.isBadgeExample = false, + }); + + final void Function(int)? onSelectItem; + final int selectedIndex; + final bool isExampleBar; + final bool isBadgeExample; + + @override + State createState() => _NavigationBarsState(); +} + +class _NavigationBarsState extends State { + late int selectedIndex; + + @override + void initState() { + super.initState(); + selectedIndex = widget.selectedIndex; + } + + @override + void didUpdateWidget(covariant NavigationBars oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedIndex != oldWidget.selectedIndex) { + selectedIndex = widget.selectedIndex; + } + } + + @override + Widget build(BuildContext context) { + // App NavigationBar should get first focus. + Widget navigationBar = Focus( + autofocus: !(widget.isExampleBar || widget.isBadgeExample), + child: NavigationBar( + selectedIndex: selectedIndex, + onDestinationSelected: (int index) { + setState(() { + selectedIndex = index; + }); + if (!widget.isExampleBar) { + widget.onSelectItem!(index); + } + }, + destinations: + widget.isExampleBar && widget.isBadgeExample + ? barWithBadgeDestinations + : widget.isExampleBar + ? exampleBarDestinations + : appBarDestinations, + ), + ); + + if (widget.isExampleBar && widget.isBadgeExample) { + navigationBar = ComponentDecoration( + label: 'Badges', + tooltipMessage: 'Use Badge or Badge.count', + child: navigationBar, + ); + } else if (widget.isExampleBar) { + navigationBar = ComponentDecoration( + label: 'Navigation bar', + tooltipMessage: 'Use NavigationBar', + child: navigationBar, + ); + } + + return navigationBar; + } +} + +class IconToggleButtons extends StatefulWidget { + const IconToggleButtons({super.key}); + + @override + State createState() => _IconToggleButtonsState(); +} + +class _IconToggleButtonsState extends State { + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Icon buttons', + tooltipMessage: 'Use IconButton', + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + // Standard IconButton + children: [ + IconToggleButton(isEnabled: true, tooltip: 'Standard'), + colDivider, + IconToggleButton(isEnabled: false, tooltip: 'Standard (disabled)'), + ], + ), + Column( + children: [ + // Filled IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Filled', + getDefaultStyle: enabledFilledButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Filled (disabled)', + getDefaultStyle: disabledFilledButtonStyle, + ), + ], + ), + Column( + children: [ + // Filled Tonal IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Filled tonal', + getDefaultStyle: enabledFilledTonalButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Filled tonal (disabled)', + getDefaultStyle: disabledFilledTonalButtonStyle, + ), + ], + ), + Column( + children: [ + // Outlined IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Outlined', + getDefaultStyle: enabledOutlinedButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Outlined (disabled)', + getDefaultStyle: disabledOutlinedButtonStyle, + ), + ], + ), + ], + ), + ); + } +} + +class IconToggleButton extends StatefulWidget { + const IconToggleButton({ + required this.isEnabled, + required this.tooltip, + this.getDefaultStyle, + super.key, + }); + + final bool isEnabled; + final String tooltip; + final ButtonStyle? Function(bool, ColorScheme)? getDefaultStyle; + + @override + State createState() => _IconToggleButtonState(); +} + +class _IconToggleButtonState extends State { + bool selected = false; + + @override + Widget build(BuildContext context) { + final ColorScheme colors = Theme.of(context).colorScheme; + final VoidCallback? onPressed = + widget.isEnabled + ? () { + setState(() { + selected = !selected; + }); + } + : null; + final ButtonStyle? style = widget.getDefaultStyle?.call(selected, colors); + + return IconButton( + visualDensity: VisualDensity.standard, + isSelected: selected, + tooltip: widget.tooltip, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: onPressed, + style: style, + ); + } +} + +ButtonStyle enabledFilledButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + foregroundColor: selected ? colors.onPrimary : colors.primary, + backgroundColor: selected ? colors.primary : colors.surfaceContainerHighest, + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + hoverColor: selected ? colors.onPrimary.withOpacity(0.08) : colors.primary.withOpacity(0.08), + focusColor: selected ? colors.onPrimary.withOpacity(0.12) : colors.primary.withOpacity(0.12), + highlightColor: + selected ? colors.onPrimary.withOpacity(0.12) : colors.primary.withOpacity(0.12), + ); +} + +ButtonStyle disabledFilledButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + ); +} + +ButtonStyle enabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + foregroundColor: selected ? colors.onSecondaryContainer : colors.onSurfaceVariant, + backgroundColor: selected ? colors.secondaryContainer : colors.surfaceContainerHighest, + hoverColor: + selected + ? colors.onSecondaryContainer.withOpacity(0.08) + : colors.onSurfaceVariant.withOpacity(0.08), + focusColor: + selected + ? colors.onSecondaryContainer.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + highlightColor: + selected + ? colors.onSecondaryContainer.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + ); +} + +ButtonStyle disabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + ); +} + +ButtonStyle enabledOutlinedButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + backgroundColor: selected ? colors.inverseSurface : null, + hoverColor: + selected + ? colors.onInverseSurface.withOpacity(0.08) + : colors.onSurfaceVariant.withOpacity(0.08), + focusColor: + selected + ? colors.onInverseSurface.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + highlightColor: + selected ? colors.onInverseSurface.withOpacity(0.12) : colors.onSurface.withOpacity(0.12), + side: BorderSide(color: colors.outline), + ).copyWith( + foregroundColor: MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.selected)) { + return colors.onInverseSurface; + } + if (states.contains(MaterialState.pressed)) { + return colors.onSurface; + } + return null; + }), + ); +} + +ButtonStyle disabledOutlinedButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: selected ? colors.onSurface.withOpacity(0.12) : null, + side: selected ? null : BorderSide(color: colors.outline.withOpacity(0.12)), + ); +} + +class Chips extends StatefulWidget { + const Chips({super.key}); + + @override + State createState() => _ChipsState(); +} + +class _ChipsState extends State { + bool isFiltered = true; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Chips', + tooltipMessage: + 'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip', + child: Column( + children: [ + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + ActionChip( + label: const Text('Assist'), + avatar: const Icon(Icons.event), + onPressed: () {}, + ), + FilterChip( + label: const Text('Filter'), + selected: isFiltered, + onSelected: (bool selected) { + setState(() => isFiltered = selected); + }, + ), + InputChip(label: const Text('Input'), onPressed: () {}, onDeleted: () {}), + ActionChip(label: const Text('Suggestion'), onPressed: () {}), + ], + ), + colDivider, + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + const ActionChip(label: Text('Assist'), avatar: Icon(Icons.event)), + FilterChip(label: const Text('Filter'), selected: isFiltered, onSelected: null), + InputChip(label: const Text('Input'), onDeleted: () {}, isEnabled: false), + const ActionChip(label: Text('Suggestion')), + ], + ), + ], + ), + ); + } +} + +class SegmentedButtons extends StatelessWidget { + const SegmentedButtons({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Segmented buttons', + tooltipMessage: 'Use SegmentedButton', + child: Column(children: [SingleChoice(), colDivider, MultipleChoice()]), + ); + } +} + +enum Calendar { day, week, month, year } + +class SingleChoice extends StatefulWidget { + const SingleChoice({super.key}); + + @override + State createState() => _SingleChoiceState(); +} + +class _SingleChoiceState extends State { + Calendar calendarView = Calendar.day; + + @override + Widget build(BuildContext context) { + return SegmentedButton( + segments: const >[ + ButtonSegment( + value: Calendar.day, + label: Text('Day'), + icon: Icon(Icons.calendar_view_day), + ), + ButtonSegment( + value: Calendar.week, + label: Text('Week'), + icon: Icon(Icons.calendar_view_week), + ), + ButtonSegment( + value: Calendar.month, + label: Text('Month'), + icon: Icon(Icons.calendar_view_month), + ), + ButtonSegment( + value: Calendar.year, + label: Text('Year'), + icon: Icon(Icons.calendar_today), + ), + ], + selected: {calendarView}, + onSelectionChanged: (Set newSelection) { + setState(() { + // By default there is only a single segment that can be + // selected at one time, so its value is always the first + // item in the selected set. + calendarView = newSelection.first; + }); + }, + ); + } +} + +enum Sizes { extraSmall, small, medium, large, extraLarge } + +class MultipleChoice extends StatefulWidget { + const MultipleChoice({super.key}); + + @override + State createState() => _MultipleChoiceState(); +} + +class _MultipleChoiceState extends State { + Set selection = {Sizes.large, Sizes.extraLarge}; + + @override + Widget build(BuildContext context) { + return SegmentedButton( + segments: const >[ + ButtonSegment(value: Sizes.extraSmall, label: Text('XS')), + ButtonSegment(value: Sizes.small, label: Text('S')), + ButtonSegment(value: Sizes.medium, label: Text('M')), + ButtonSegment(value: Sizes.large, label: Text('L')), + ButtonSegment(value: Sizes.extraLarge, label: Text('XL')), + ], + selected: selection, + onSelectionChanged: (Set newSelection) { + setState(() { + selection = newSelection; + }); + }, + multiSelectionEnabled: true, + ); + } +} + +class SnackBarSection extends StatelessWidget { + const SnackBarSection({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Snackbar', + tooltipMessage: 'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar', + child: TextButton( + onPressed: () { + final SnackBar snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + width: 400.0, + content: const Text('This is a snackbar'), + action: SnackBarAction(label: 'Close', onPressed: () {}), + ); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + child: const Text('Show snackbar', style: TextStyle(fontWeight: FontWeight.bold)), + ), + ); + } +} + +class BottomSheetSection extends StatefulWidget { + const BottomSheetSection({super.key}); + + @override + State createState() => _BottomSheetSectionState(); +} + +class _BottomSheetSectionState extends State { + bool isNonModalBottomSheetOpen = false; + PersistentBottomSheetController? _nonModalBottomSheetController; + + @override + Widget build(BuildContext context) { + List buttonList = [ + IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.add)), + IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)), + IconButton(onPressed: () {}, icon: const Icon(Icons.archive_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.settings_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.favorite_border)), + ]; + const List labelList = [ + Text('Share'), + Text('Add to'), + Text('Trash'), + Text('Archive'), + Text('Settings'), + Text('Favorite'), + ]; + + buttonList = List.generate( + buttonList.length, + (int index) => Padding( + padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0), + child: Column(children: [buttonList[index], labelList[index]]), + ), + ); + + return ComponentDecoration( + label: 'Bottom sheet', + tooltipMessage: 'Use showModalBottomSheet or showBottomSheet', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: [ + TextButton( + child: const Text( + 'Show modal bottom sheet', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showModalBottomSheet( + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (BuildContext context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + TextButton( + child: Text( + isNonModalBottomSheetOpen ? 'Hide bottom sheet' : 'Show bottom sheet', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + if (isNonModalBottomSheetOpen) { + _nonModalBottomSheetController?.close(); + setState(() { + isNonModalBottomSheetOpen = false; + }); + return; + } else { + setState(() { + isNonModalBottomSheetOpen = true; + }); + } + + _nonModalBottomSheetController = showBottomSheet( + elevation: 8.0, + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (BuildContext context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + ], + ), + ); + } +} + +class BottomAppBars extends StatelessWidget { + const BottomAppBars({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Bottom app bar', + tooltipMessage: 'Use BottomAppBar', + child: Column( + children: [ + SizedBox( + height: 80, + child: Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () {}, + elevation: 0.0, + child: const Icon(Icons.add), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.endContained, + bottomNavigationBar: BottomAppBar( + child: Row( + children: [ + const IconButtonAnchorExample(), + IconButton(tooltip: 'Search', icon: const Icon(Icons.search), onPressed: () {}), + IconButton( + tooltip: 'Favorite', + icon: const Icon(Icons.favorite), + onPressed: () {}, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class IconButtonAnchorExample extends StatelessWidget { + const IconButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: [ + MenuItemButton(child: const Text('Menu 1'), onPressed: () {}), + MenuItemButton(child: const Text('Menu 2'), onPressed: () {}), + SubmenuButton( + menuChildren: [ + MenuItemButton(onPressed: () {}, child: const Text('Menu 3.1')), + MenuItemButton(onPressed: () {}, child: const Text('Menu 3.2')), + MenuItemButton(onPressed: () {}, child: const Text('Menu 3.3')), + ], + child: const Text('Menu 3'), + ), + ], + ); + } +} + +class ButtonAnchorExample extends StatelessWidget { + const ButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? child) { + return FilledButton.tonal( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Show menu'), + ); + }, + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(Icons.people_alt_outlined), + child: const Text('Item 1'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.remove_red_eye_outlined), + child: const Text('Item 2'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.refresh), + onPressed: () {}, + child: const Text('Item 3'), + ), + ], + ); + } +} + +class NavigationDrawers extends StatelessWidget { + const NavigationDrawers({super.key, required this.scaffoldKey}); + final GlobalKey scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Navigation drawer', + tooltipMessage: 'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer', + child: Column( + children: [ + const SizedBox(height: 520, child: NavigationDrawerSection()), + colDivider, + colDivider, + TextButton( + child: const Text( + 'Show modal navigation drawer', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + scaffoldKey.currentState!.openEndDrawer(); + }, + ), + ], + ), + ); + } +} + +class NavigationDrawerSection extends StatefulWidget { + const NavigationDrawerSection({super.key}); + + @override + State createState() => _NavigationDrawerSectionState(); +} + +class _NavigationDrawerSectionState extends State { + int navDrawerIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationDrawer( + onDestinationSelected: (int selectedIndex) { + setState(() { + navDrawerIndex = selectedIndex; + }); + }, + selectedIndex: navDrawerIndex, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text('Mail', style: Theme.of(context).textTheme.titleSmall), + ), + ...destinations.map((ExampleDestination destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + const Divider(indent: 28, endIndent: 28), + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text('Labels', style: Theme.of(context).textTheme.titleSmall), + ), + ...labelDestinations.map((ExampleDestination destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class ExampleDestination { + const ExampleDestination(this.label, this.icon, this.selectedIcon); + + final String label; + final Widget icon; + final Widget selectedIcon; +} + +const List destinations = [ + ExampleDestination('Inbox', Icon(Icons.inbox_outlined), Icon(Icons.inbox)), + ExampleDestination('Outbox', Icon(Icons.send_outlined), Icon(Icons.send)), + ExampleDestination('Favorites', Icon(Icons.favorite_outline), Icon(Icons.favorite)), + ExampleDestination('Trash', Icon(Icons.delete_outline), Icon(Icons.delete)), +]; + +const List labelDestinations = [ + ExampleDestination('Family', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), + ExampleDestination('School', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), + ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), +]; + +class NavigationRails extends StatelessWidget { + const NavigationRails({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Navigation rail', + tooltipMessage: 'Use NavigationRail', + child: IntrinsicWidth(child: SizedBox(height: 420, child: NavigationRailSection())), + ); + } +} + +class NavigationRailSection extends StatefulWidget { + const NavigationRailSection({super.key}); + + @override + State createState() => _NavigationRailSectionState(); +} + +class _NavigationRailSectionState extends State { + int navRailIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationRail( + onDestinationSelected: (int selectedIndex) { + setState(() { + navRailIndex = selectedIndex; + }); + }, + elevation: 4, + leading: FloatingActionButton(child: const Icon(Icons.create), onPressed: () {}), + groupAlignment: 0.0, + selectedIndex: navRailIndex, + labelType: NavigationRailLabelType.selected, + destinations: [ + ...destinations.map((ExampleDestination destination) { + return NavigationRailDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class Tabs extends StatefulWidget { + const Tabs({super.key}); + + @override + State createState() => _TabsState(); +} + +class _TabsState extends State with TickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Tabs', + tooltipMessage: 'Use TabBar', + child: SizedBox( + height: 80, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + controller: _tabController, + tabs: const [ + Tab( + icon: Icon(Icons.videocam_outlined), + text: 'Video', + iconMargin: EdgeInsets.zero, + ), + Tab(icon: Icon(Icons.photo_outlined), text: 'Photos', iconMargin: EdgeInsets.zero), + Tab(icon: Icon(Icons.audiotrack_sharp), text: 'Audio', iconMargin: EdgeInsets.zero), + ], + ), + ), + ), + ), + ); + } +} + +class TopAppBars extends StatelessWidget { + const TopAppBars({super.key}); + + static final List actions = [ + IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}), + IconButton(icon: const Icon(Icons.event), onPressed: () {}), + IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ]; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Top app bars', + tooltipMessage: 'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large', + child: Column( + children: [ + AppBar( + title: const Text('Center-aligned'), + leading: const BackButton(), + actions: [ + IconButton( + iconSize: 32, + icon: const Icon(Icons.account_circle_outlined), + onPressed: () {}, + ), + ], + centerTitle: true, + ), + colDivider, + AppBar( + title: const Text('Small'), + leading: const BackButton(), + actions: actions, + centerTitle: false, + ), + colDivider, + SizedBox( + height: 100, + child: CustomScrollView( + slivers: [ + SliverAppBar.medium( + title: const Text('Medium'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + colDivider, + SizedBox( + height: 130, + child: CustomScrollView( + slivers: [ + SliverAppBar.large( + title: const Text('Large'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + ], + ), + ); + } +} + +class Menus extends StatefulWidget { + const Menus({super.key}); + + @override + State createState() => _MenusState(); +} + +class _MenusState extends State { + final TextEditingController colorController = TextEditingController(); + final TextEditingController iconController = TextEditingController(); + IconLabel? selectedIcon = IconLabel.smile; + ColorLabel? selectedColor; + + @override + Widget build(BuildContext context) { + final List> colorEntries = >[]; + for (final ColorLabel color in ColorLabel.values) { + colorEntries.add( + DropdownMenuEntry( + value: color, + label: color.label, + enabled: color.label != 'Grey', + ), + ); + } + + final List> iconEntries = >[]; + for (final IconLabel icon in IconLabel.values) { + iconEntries.add(DropdownMenuEntry(value: icon, label: icon.label)); + } + + return ComponentDecoration( + label: 'Menus', + tooltipMessage: 'Use MenuAnchor or DropdownMenu', + child: Column( + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ButtonAnchorExample(), rowDivider, IconButtonAnchorExample()], + ), + colDivider, + Wrap( + alignment: WrapAlignment.spaceAround, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: smallSpacing, + runSpacing: smallSpacing, + children: [ + DropdownMenu( + controller: colorController, + label: const Text('Color'), + enableFilter: true, + dropdownMenuEntries: colorEntries, + inputDecorationTheme: const InputDecorationTheme(filled: true), + onSelected: (ColorLabel? color) { + setState(() { + selectedColor = color; + }); + }, + ), + DropdownMenu( + initialSelection: IconLabel.smile, + controller: iconController, + leadingIcon: const Icon(Icons.search), + label: const Text('Icon'), + dropdownMenuEntries: iconEntries, + onSelected: (IconLabel? icon) { + setState(() { + selectedIcon = icon; + }); + }, + ), + Icon(selectedIcon?.icon, color: selectedColor?.color ?? Colors.grey.withOpacity(0.5)), + ], + ), + ], + ), + ); + } +} + +enum ColorLabel { + blue('Blue', Colors.blue), + pink('Pink', Colors.pink), + green('Green', Colors.green), + yellow('Yellow', Colors.yellow), + grey('Grey', Colors.grey); + + const ColorLabel(this.label, this.color); + final String label; + final Color color; +} + +enum IconLabel { + smile('Smile', Icons.sentiment_satisfied_outlined), + cloud('Cloud', Icons.cloud_outlined), + brush('Brush', Icons.brush_outlined), + heart('Heart', Icons.favorite); + + const IconLabel(this.label, this.icon); + final String label; + final IconData icon; +} + +class Sliders extends StatefulWidget { + const Sliders({super.key}); + + @override + State createState() => _SlidersState(); +} + +class _SlidersState extends State { + double sliderValue0 = 30.0; + double sliderValue1 = 20.0; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Sliders', + tooltipMessage: 'Use Slider or RangeSlider', + child: Column( + children: [ + Slider( + max: 100, + value: sliderValue0, + onChanged: (double value) { + setState(() { + sliderValue0 = value; + }); + }, + ), + const SizedBox(height: 20), + Slider( + max: 100, + divisions: 5, + value: sliderValue1, + label: sliderValue1.round().toString(), + onChanged: (double value) { + setState(() { + sliderValue1 = value; + }); + }, + ), + ], + ), + ); + } +} + +class ComponentDecoration extends StatefulWidget { + const ComponentDecoration({ + super.key, + required this.label, + required this.child, + this.tooltipMessage = '', + }); + + final String label; + final Widget child; + final String? tooltipMessage; + + @override + State createState() => _ComponentDecorationState(); +} + +class _ComponentDecorationState extends State { + final FocusNode focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: smallSpacing), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(widget.label, style: Theme.of(context).textTheme.titleSmall), + Tooltip( + message: widget.tooltipMessage, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 5.0), + child: Icon(Icons.info_outline, size: 16), + ), + ), + ], + ), + ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: widthConstraint), + // Tapping within the a component card should request focus + // for that component's children. + child: Focus( + focusNode: focusNode, + canRequestFocus: true, + child: GestureDetector( + onTapDown: (_) { + focusNode.requestFocus(); + }, + behavior: HitTestBehavior.opaque, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide(color: Theme.of(context).colorScheme.outlineVariant), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 20.0), + child: Center(child: widget.child), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class ComponentGroupDecoration extends StatelessWidget { + const ComponentGroupDecoration({super.key, required this.label, required this.children}); + + final String label; + final List children; + + @override + Widget build(BuildContext context) { + // Fully traverse this component group before moving on + return FocusTraversalGroup( + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + color: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.3), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center( + child: Column( + children: [ + Text(label, style: Theme.of(context).textTheme.titleLarge), + colDivider, + ...children, + ], + ), + ), + ), + ), + ); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart new file mode 100644 index 00000000..06c5c838 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart @@ -0,0 +1,1361 @@ +// Copyright 2014 The Flutter 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:js_interop'; +import 'dart:math' as math; +import 'dart:ui'; +import 'dart:ui_web' as ui_web; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart'; +import 'package:web/web.dart' as web; + +/// The default number of samples from warm-up iterations. +/// +/// This value is used when [Profile.useCustomWarmUp] is set to false. +/// +/// The benchmark is warmed up prior to measuring to allow JIT and caches to settle. +const int _kDefaultWarmUpSampleCount = 200; + +/// The default number of samples collected to compute benchmark statistics. +/// +/// This value is used when [Profile.useCustomWarmUp] is set to false. +const int _kDefaultMeasuredSampleCount = 100; + +/// The default total number of samples collected by a benchmark. +/// +/// This value is used when [Profile.useCustomWarmUp] is set to false. +const int kDefaultTotalSampleCount = _kDefaultWarmUpSampleCount + _kDefaultMeasuredSampleCount; + +/// A benchmark metric that includes frame-related computations prior to +/// submitting layer and picture operations to the underlying renderer, such as +/// CanvasKit. During this phase we compute transforms, clips, and other +/// information needed for rendering. +const String kProfilePrerollFrame = 'preroll_frame'; + +/// A benchmark metric that includes submitting layer and picture information +/// to the renderer. +const String kProfileApplyFrame = 'apply_frame'; + +/// Measures the amount of time [action] takes. +/// +/// See also: +/// +/// * [timeAsyncAction], which measures the time of asynchronous work. +Duration timeAction(VoidCallback action) { + final Stopwatch stopwatch = Stopwatch()..start(); + action(); + stopwatch.stop(); + return stopwatch.elapsed; +} + +/// Measures the amount of time the future returned by [action] takes to complete. +/// +/// See also: +/// +/// * [timeAction], which measures the time of synchronous work. +Future timeAsyncAction(AsyncCallback action) async { + final Stopwatch stopwatch = Stopwatch()..start(); + await action(); + stopwatch.stop(); + return stopwatch.elapsed; +} + +/// A function that performs asynchronous work. +typedef AsyncVoidCallback = Future Function(); + +/// An [AsyncVoidCallback] that doesn't do anything. +/// +/// This is used just so we don't have to deal with null all over the place. +Future _dummyAsyncVoidCallback() async {} + +/// Runs the benchmark using the given [recorder]. +/// +/// Notifies about "set up" and "tear down" events via the [setUpAllDidRun] +/// and [tearDownAllWillRun] callbacks. +@sealed +class Runner { + /// Creates a runner for the [recorder]. + Runner({ + required this.recorder, + this.setUpAllDidRun = _dummyAsyncVoidCallback, + this.tearDownAllWillRun = _dummyAsyncVoidCallback, + }); + + /// The recorder that will run and record the benchmark. + final Recorder recorder; + + /// Called immediately after [Recorder.setUpAll] future is resolved. + /// + /// This is useful, for example, to kick off a profiler or a tracer such that + /// the "set up" computations are not included in the metrics. + final AsyncVoidCallback setUpAllDidRun; + + /// Called just before calling [Recorder.tearDownAll]. + /// + /// This is useful, for example, to stop a profiler or a tracer such that + /// the "tear down" computations are not included in the metrics. + final AsyncVoidCallback tearDownAllWillRun; + + /// Runs the benchmark and reports the results. + Future run() async { + await recorder.setUpAll(); + await setUpAllDidRun(); + final Profile profile = await recorder.run(); + await tearDownAllWillRun(); + await recorder.tearDownAll(); + return profile; + } +} + +/// Base class for benchmark recorders. +/// +/// Each benchmark recorder has a [name] and a [run] method at a minimum. +abstract class Recorder { + Recorder._(this.name, this.isTracingEnabled); + + /// Whether this recorder requires tracing using Chrome's DevTools Protocol's + /// "Tracing" API. + final bool isTracingEnabled; + + /// The name of the benchmark. + /// + /// The results displayed in the Flutter Dashboard will use this name as a + /// prefix. + final String name; + + /// Returns the recorded profile. + /// + /// This value is only available while the benchmark is running. + Profile? get profile; + + /// Whether the benchmark should continue running. + /// + /// Returns `false` if the benchmark collected enough data and it's time to + /// stop. + bool shouldContinue() => profile?.shouldContinue() ?? true; + + /// Called once before all runs of this benchmark recorder. + /// + /// This is useful for doing one-time setup work that's needed for the + /// benchmark. + Future setUpAll() async {} + + /// The implementation of the benchmark that will produce a [Profile]. + Future run(); + + /// Called once after all runs of this benchmark recorder. + /// + /// This is useful for doing one-time clean up work after the benchmark is + /// complete. + Future tearDownAll() async {} +} + +/// A recorder for benchmarking raw execution of Dart code. +/// +/// This is useful for benchmarks that don't need frames or widgets. +/// +/// Example: +/// +/// ```dart +/// class BenchForLoop extends RawRecorder { +/// BenchForLoop() : super(name: benchmarkName); +/// +/// static const String benchmarkName = 'for_loop'; +/// +/// @override +/// void body(Profile profile) { +/// profile.record('loop', () { +/// double x = 0; +/// for (int i = 0; i < 10000000; i++) { +/// x *= 1.5; +/// } +/// }); +/// } +/// } +/// ``` +abstract class RawRecorder extends Recorder { + RawRecorder({required String name, bool useCustomWarmUp = false}) + : _useCustomWarmUp = useCustomWarmUp, + super._(name, false); + + /// Whether to delimit warm-up frames in a custom way. + final bool _useCustomWarmUp; + + /// The body of the benchmark. + /// + /// This is the part that records measurements of the benchmark. + FutureOr body(Profile profile); + + @override + Profile? get profile => _profile; + Profile? _profile; + + @override + @nonVirtual + Future run() async { + _profile = Profile(name: name, useCustomWarmUp: _useCustomWarmUp); + do { + await Future.delayed(Duration.zero); + final FutureOr result = body(_profile!); + if (result is Future) { + await result; + } + } while (shouldContinue()); + return _profile!; + } +} + +/// A recorder for benchmarking interactions with the engine without the +/// framework by directly exercising [SceneBuilder]. +/// +/// To implement a benchmark, extend this class and implement [onDrawFrame]. +/// +/// Example: +/// +/// ```dart +/// class BenchDrawCircle extends SceneBuilderRecorder { +/// BenchDrawCircle() : super(name: benchmarkName); +/// +/// static const String benchmarkName = 'draw_circle'; +/// +/// @override +/// void onDrawFrame(SceneBuilder sceneBuilder) { +/// final PictureRecorder pictureRecorder = PictureRecorder(); +/// final Canvas canvas = Canvas(pictureRecorder); +/// final Paint paint = Paint()..color = const Color.fromARGB(255, 255, 0, 0); +/// final Size windowSize = window.physicalSize; +/// canvas.drawCircle(windowSize.center(Offset.zero), 50.0, paint); +/// final Picture picture = pictureRecorder.endRecording(); +/// sceneBuilder.addPicture(picture); +/// } +/// } +/// ``` +abstract class SceneBuilderRecorder extends Recorder { + SceneBuilderRecorder({required String name}) : super._(name, true); + + @override + Profile? get profile => _profile; + Profile? _profile; + + /// Called from [dart:ui.PlatformDispatcher.onBeginFrame]. + @mustCallSuper + void onBeginFrame() {} + + /// Called on every frame. + /// + /// An implementation should exercise the [sceneBuilder] to build a frame. + /// However, it must not call [SceneBuilder.build] or + /// [dart:ui.FlutterView.render]. Instead the benchmark harness will call them + /// and time them appropriately. + void onDrawFrame(SceneBuilder sceneBuilder); + + @override + Future run() { + final Completer profileCompleter = Completer(); + _profile = Profile(name: name); + + PlatformDispatcher.instance.onBeginFrame = (_) { + try { + startMeasureFrame(profile!); + onBeginFrame(); + } catch (error, stackTrace) { + profileCompleter.completeError(error, stackTrace); + rethrow; + } + }; + PlatformDispatcher.instance.onDrawFrame = () { + try { + _profile!.record('drawFrameDuration', () { + final SceneBuilder sceneBuilder = SceneBuilder(); + onDrawFrame(sceneBuilder); + _profile!.record('sceneBuildDuration', () { + final Scene scene = sceneBuilder.build(); + _profile!.record('windowRenderDuration', () { + view.render(scene); + }, reported: false); + }, reported: false); + }, reported: true); + endMeasureFrame(); + + if (shouldContinue()) { + PlatformDispatcher.instance.scheduleFrame(); + } else { + profileCompleter.complete(_profile!); + } + } catch (error, stackTrace) { + profileCompleter.completeError(error, stackTrace); + rethrow; + } + }; + PlatformDispatcher.instance.scheduleFrame(); + return profileCompleter.future; + } + + FlutterView get view { + assert( + PlatformDispatcher.instance.implicitView != null, + 'This benchmark requires the embedder to provide an implicit view.', + ); + return PlatformDispatcher.instance.implicitView!; + } +} + +/// A recorder for benchmarking interactions with the framework by creating +/// widgets. +/// +/// To implement a benchmark, extend this class and implement [createWidget]. +/// +/// Example: +/// +/// ```dart +/// class BenchListView extends WidgetRecorder { +/// BenchListView() : super(name: benchmarkName); +/// +/// static const String benchmarkName = 'bench_list_view'; +/// +/// @override +/// Widget createWidget() { +/// return Directionality( +/// textDirection: TextDirection.ltr, +/// child: _TestListViewWidget(), +/// ); +/// } +/// } +/// +/// class _TestListViewWidget extends StatefulWidget { +/// @override +/// State createState() { +/// return _TestListViewWidgetState(); +/// } +/// } +/// +/// class _TestListViewWidgetState extends State<_TestListViewWidget> { +/// ScrollController scrollController; +/// +/// @override +/// void initState() { +/// super.initState(); +/// scrollController = ScrollController(); +/// Timer.run(() async { +/// bool forward = true; +/// while (true) { +/// await scrollController.animateTo( +/// forward ? 300 : 0, +/// curve: Curves.linear, +/// duration: const Duration(seconds: 1), +/// ); +/// forward = !forward; +/// } +/// }); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return ListView.builder( +/// controller: scrollController, +/// itemCount: 10000, +/// itemBuilder: (BuildContext context, int index) { +/// return Text('Item #$index'); +/// }, +/// ); +/// } +/// } +/// ``` +abstract class WidgetRecorder extends Recorder implements FrameRecorder { + WidgetRecorder({required String name, this.useCustomWarmUp = false}) : super._(name, true); + + /// Creates a widget to be benchmarked. + /// + /// The widget must create its own animation to drive the benchmark. The + /// animation should continue indefinitely. The benchmark harness will stop + /// pumping frames automatically. + Widget createWidget(); + + final List _didStopCallbacks = []; + @override + void registerDidStop(VoidCallback cb) { + _didStopCallbacks.add(cb); + } + + @override + Profile? profile; + Completer? _runCompleter; + + /// Whether to delimit warm-up frames in a custom way. + final bool useCustomWarmUp; + + late Stopwatch _drawFrameStopwatch; + + @override + @mustCallSuper + void frameWillDraw() { + startMeasureFrame(profile!); + _drawFrameStopwatch = Stopwatch()..start(); + } + + @override + @mustCallSuper + void frameDidDraw() { + endMeasureFrame(); + profile!.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true); + + if (shouldContinue()) { + PlatformDispatcher.instance.scheduleFrame(); + } else { + for (final VoidCallback fn in _didStopCallbacks) { + fn(); + } + _runCompleter!.complete(); + } + } + + @override + void _onError(Object error, StackTrace? stackTrace) { + _runCompleter!.completeError(error, stackTrace); + } + + late final _RecordingWidgetsBinding _binding; + + @override + @mustCallSuper + Future setUpAll() async { + _binding = _RecordingWidgetsBinding.ensureInitialized(); + } + + @override + Future run() async { + _runCompleter = Completer(); + final Profile localProfile = profile = Profile(name: name, useCustomWarmUp: useCustomWarmUp); + final Widget widget = createWidget(); + + registerEngineBenchmarkValueListener(kProfilePrerollFrame, (num value) { + localProfile.addDataPoint( + kProfilePrerollFrame, + Duration(microseconds: value.toInt()), + reported: false, + ); + }); + registerEngineBenchmarkValueListener(kProfileApplyFrame, (num value) { + localProfile.addDataPoint( + kProfileApplyFrame, + Duration(microseconds: value.toInt()), + reported: false, + ); + }); + + _binding._beginRecording(this, widget); + + try { + await _runCompleter!.future; + return localProfile; + } finally { + stopListeningToEngineBenchmarkValues(kProfilePrerollFrame); + stopListeningToEngineBenchmarkValues(kProfileApplyFrame); + _runCompleter = null; + profile = null; + } + } +} + +/// A recorder for measuring the performance of building a widget from scratch +/// starting from an empty frame. +/// +/// The recorder will call [createWidget] and render it, then it will pump +/// another frame that clears the screen. It repeats this process, measuring the +/// performance of frames that render the widget and ignoring the frames that +/// clear the screen. +abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder { + WidgetBuildRecorder({required String name}) : super._(name, true); + + /// Creates a widget to be benchmarked. + /// + /// The widget is not expected to animate as we only care about construction + /// of the widget. If you are interested in benchmarking an animation, + /// consider using [WidgetRecorder]. + Widget createWidget(); + + final List _didStopCallbacks = []; + @override + void registerDidStop(VoidCallback cb) { + _didStopCallbacks.add(cb); + } + + @override + Profile? profile; + Completer? _runCompleter; + + late Stopwatch _drawFrameStopwatch; + + /// Whether in this frame we should call [createWidget] and render it. + /// + /// If false, then this frame will clear the screen. + bool showWidget = true; + + /// The state that hosts the widget under test. + late _WidgetBuildRecorderHostState _hostState; + + Widget? _getWidgetForFrame() { + if (showWidget) { + return createWidget(); + } else { + return null; + } + } + + late final _RecordingWidgetsBinding _binding; + + @override + @mustCallSuper + Future setUpAll() async { + _binding = _RecordingWidgetsBinding.ensureInitialized(); + } + + @override + @mustCallSuper + void frameWillDraw() { + if (showWidget) { + startMeasureFrame(profile!); + _drawFrameStopwatch = Stopwatch()..start(); + } + } + + @override + @mustCallSuper + void frameDidDraw() { + // Only record frames that show the widget. + if (showWidget) { + endMeasureFrame(); + profile!.addDataPoint('drawFrameDuration', _drawFrameStopwatch.elapsed, reported: true); + } + + if (shouldContinue()) { + showWidget = !showWidget; + _hostState._setStateTrampoline(); + } else { + for (final VoidCallback fn in _didStopCallbacks) { + fn(); + } + _runCompleter!.complete(); + } + } + + @override + void _onError(Object error, StackTrace? stackTrace) { + _runCompleter!.completeError(error, stackTrace); + } + + @override + Future run() async { + _runCompleter = Completer(); + final Profile localProfile = profile = Profile(name: name); + _binding._beginRecording(this, _WidgetBuildRecorderHost(this)); + + try { + await _runCompleter!.future; + return localProfile; + } finally { + _runCompleter = null; + profile = null; + } + } +} + +/// Hosts widgets created by [WidgetBuildRecorder]. +class _WidgetBuildRecorderHost extends StatefulWidget { + const _WidgetBuildRecorderHost(this.recorder); + + final WidgetBuildRecorder recorder; + + @override + State createState() => _WidgetBuildRecorderHostState(); +} + +class _WidgetBuildRecorderHostState extends State<_WidgetBuildRecorderHost> { + @override + void initState() { + super.initState(); + widget.recorder._hostState = this; + } + + // This is just to bypass the @protected on setState. + void _setStateTrampoline() { + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return SizedBox.expand(child: widget.recorder._getWidgetForFrame()); + } +} + +/// Series of time recordings indexed in time order. +/// +/// A timeseries is expected to contain at least one warm-up frame added by +/// calling [add] with `isWarmUpValue` set to true, followed by at least one +/// measured value added by calling [add] with `isWarmUpValue` set to false. +class Timeseries { + /// Creates an empty timeseries. + /// + /// The [name] is a unique name of this timeseries. If [isReported] is true + /// this timeseries is reported to the benchmark dashboard. + Timeseries(this.name, this.isReported); + + /// The label of this timeseries used for debugging and result inspection. + final String name; + + /// Whether this timeseries is reported to the benchmark dashboard. + /// + /// If `true` a new benchmark card is created for the timeseries and is + /// visible on the dashboard. + /// + /// If `false` the data is stored but it does not show up on the dashboard. + /// Use unreported metrics for metrics that are useful for manual inspection + /// but that are too fine-grained to be useful for tracking on the dashboard. + final bool isReported; + + /// The number of samples ignored as warm-up frames. + int _warmUpSampleCount = 0; + + /// List of all the values that have been recorded. + /// + /// This list has no limit. + final List _allValues = []; + + /// The total amount of data collected, including ones that were dropped + /// because of the sample size limit. + int get count => _allValues.length; + + /// Extracts useful statistics out of this timeseries. + /// + /// See [TimeseriesStats] for more details. + TimeseriesStats computeStats() { + // Assertions do not use the `assert` keyword because benchmarks run in + // profile mode, where asserts are tree-shaken out. + if (_warmUpSampleCount == 0) { + throw StateError( + 'The benchmark did not warm-up. Use at least one sample to warm-up ' + 'the benchmark to reduce noise.', + ); + } + if (_warmUpSampleCount >= count) { + throw StateError( + 'The benchmark did not report any measured samples. Add at least one ' + 'sample after warm-up is done. There were $_warmUpSampleCount warm-up ' + 'samples, and no measured samples in this timeseries.', + ); + } + + // The first few values we simply discard and never look at. They're from the warm-up phase. + final List warmUpValues = _allValues.sublist(0, _warmUpSampleCount); + + // Values we analyze. + final List candidateValues = _allValues.sublist(_warmUpSampleCount); + + // The average that includes outliers. + final double dirtyAverage = _computeAverage(name, candidateValues); + + // The standard deviation that includes outliers. + final double dirtyStandardDeviation = _computeStandardDeviationForPopulation( + name, + candidateValues, + ); + + // Any value that's higher than this is considered an outlier. + // Two standard deviations captures 95% of a normal distribution. + final double outlierCutOff = dirtyAverage + dirtyStandardDeviation * 2; + + // Candidates with outliers removed. + final Iterable cleanValues = candidateValues.where( + (double value) => value <= outlierCutOff, + ); + + // Outlier candidates. + final Iterable outliers = candidateValues.where( + (double value) => value > outlierCutOff, + ); + + // Final statistics. + final double cleanAverage = _computeAverage(name, cleanValues); + final double standardDeviation = _computeStandardDeviationForPopulation(name, cleanValues); + final double noise = cleanAverage > 0.0 ? standardDeviation / cleanAverage : 0.0; + + // Compute outlier average. If there are no outliers the outlier average is + // the same as clean value average. In other words, in a perfect benchmark + // with no noise the difference between average and outlier average is zero, + // which the best possible outcome. Noise produces a positive difference + // between the two. + final double outlierAverage = + outliers.isNotEmpty ? _computeAverage(name, outliers) : cleanAverage; + + final List annotatedValues = [ + for (final double warmUpValue in warmUpValues) + AnnotatedSample( + magnitude: warmUpValue, + isOutlier: warmUpValue > outlierCutOff, + isWarmUpValue: true, + ), + for (final double candidate in candidateValues) + AnnotatedSample( + magnitude: candidate, + isOutlier: candidate > outlierCutOff, + isWarmUpValue: false, + ), + ]; + + return TimeseriesStats( + name: name, + average: cleanAverage, + outlierCutOff: outlierCutOff, + outlierAverage: outlierAverage, + standardDeviation: standardDeviation, + noise: noise, + cleanSampleCount: cleanValues.length, + outlierSampleCount: outliers.length, + samples: annotatedValues, + ); + } + + // Whether the timeseries is in the warm-up phase. + bool _isWarmingUp = true; + + /// Adds a value to this timeseries. + void add(double value, {required bool isWarmUpValue}) { + if (value < 0.0) { + throw StateError('Timeseries $name: negative metric values are not supported. Got: $value'); + } + if (isWarmUpValue) { + if (!_isWarmingUp) { + throw StateError( + 'A warm-up value was added to the timeseries after the warm-up phase finished.', + ); + } + _warmUpSampleCount += 1; + } else if (_isWarmingUp) { + _isWarmingUp = false; + } + _allValues.add(value); + } +} + +/// Various statistics about a [Timeseries]. +/// +/// See the docs on the individual fields for more details. +@sealed +class TimeseriesStats { + const TimeseriesStats({ + required this.name, + required this.average, + required this.outlierCutOff, + required this.outlierAverage, + required this.standardDeviation, + required this.noise, + required this.cleanSampleCount, + required this.outlierSampleCount, + required this.samples, + }); + + /// The label used to refer to the corresponding timeseries. + final String name; + + /// The average value of the measured samples without outliers. + final double average; + + /// The standard deviation in the measured samples without outliers. + final double standardDeviation; + + /// The noise as a multiple of the [average] value takes from clean samples. + /// + /// This value can be multiplied by 100.0 to get noise as a percentage of + /// the average. + /// + /// If [average] is zero, treats the result as perfect score, returns zero. + final double noise; + + /// The maximum value a sample can have without being considered an outlier. + /// + /// See [Timeseries.computeStats] for details on how this value is computed. + final double outlierCutOff; + + /// The average of outlier samples. + /// + /// This value can be used to judge how badly we jank, when we jank. + /// + /// Another useful metrics is the difference between [outlierAverage] and + /// [average]. The smaller the value the more predictable is the performance + /// of the corresponding benchmark. + final double outlierAverage; + + /// The number of measured samples after outlier are removed. + final int cleanSampleCount; + + /// The number of outliers. + final int outlierSampleCount; + + /// All collected samples, annotated with statistical information. + /// + /// See [AnnotatedSample] for more details. + final List samples; + + /// Outlier average divided by clean average. + /// + /// This is a measure of performance consistency. The higher this number the + /// worse is jank when it happens. Smaller is better, with 1.0 being the + /// perfect score. If [average] is zero, this value defaults to 1.0. + double get outlierRatio => + average > 0.0 + ? outlierAverage / average + : 1.0; // this can only happen in perfect benchmark that reports only zeros + + @override + String toString() { + final StringBuffer buffer = StringBuffer(); + buffer.writeln( + '$name: (samples: $cleanSampleCount clean/$outlierSampleCount ' + 'outliers/${cleanSampleCount + outlierSampleCount} ' + 'measured/${samples.length} total)', + ); + buffer.writeln(' | average: $average μs'); + buffer.writeln(' | outlier average: $outlierAverage μs'); + buffer.writeln(' | outlier/clean ratio: ${outlierRatio}x'); + buffer.writeln(' | noise: ${_ratioToPercent(noise)}'); + return buffer.toString(); + } +} + +/// Annotates a single measurement with statistical information. +@sealed +class AnnotatedSample { + const AnnotatedSample({ + required this.magnitude, + required this.isOutlier, + required this.isWarmUpValue, + }); + + /// The non-negative raw result of the measurement. + final double magnitude; + + /// Whether this sample was considered an outlier. + final bool isOutlier; + + /// Whether this sample was taken during the warm-up phase. + /// + /// If this value is `true`, this sample does not participate in + /// statistical computations. However, the sample would still be + /// shown in the visualization of results so that the benchmark + /// can be inspected manually to make sure there's a predictable + /// warm-up regression slope. + final bool isWarmUpValue; +} + +/// Base class for a profile collected from running a benchmark. +class Profile { + /// Creates an empty profile that can be populated with benchmark samples + /// using [record], [recordAsync], and [addDataPoint] methods. + /// + /// The [name] is the unique name of this profile that distinguishes is from + /// other profiles. Typically, the name will describe the benchmark. + /// + /// If [useCustomWarmUp] is true the benchmark will continue running until + /// [stopBenchmark] is called. Otherwise, the benchmark collects the + /// [kDefaultTotalSampleCount] samples and stops automatically. + Profile({required this.name, this.useCustomWarmUp = false}); + + /// The name of the benchmark that produced this profile. + final String name; + + /// Whether to delimit warm-up frames in a custom way. + final bool useCustomWarmUp; + + /// True if the benchmark is currently measuring warm-up frames. + bool get isWarmingUp => _isWarmingUp; + bool _isWarmingUp = true; + + /// True if the benchmark is currently running. + bool get isRunning => _isRunning; + bool _isRunning = true; + + /// Stops the warm-up phase. + /// + /// After calling this method, subsequent calls to [record], [recordAsync], + /// and [addDataPoint] will record measured data samples. + /// + /// Call this method only once for each profile and only when [isWarmingUp] + /// is true. + void stopWarmingUp() { + if (!_isWarmingUp) { + throw StateError('Warm-up already stopped.'); + } else { + _isWarmingUp = false; + } + } + + /// Stops the benchmark. + /// + /// Call this method only once for each profile and only when [isWarmingUp] + /// is false (i.e. after calling [stopWarmingUp]). + void stopBenchmark() { + if (_isWarmingUp) { + throw StateError( + 'Warm-up has not finished yet. Benchmark should only be stopped after ' + 'it recorded at least one sample after the warm-up.', + ); + } else if (scoreData.isEmpty) { + throw StateError('The benchmark did not collect any data.'); + } else { + _isRunning = false; + } + } + + /// This data will be used to display cards in the Flutter Dashboard. + final Map scoreData = {}; + + /// This data isn't displayed anywhere. It's stored for completeness purposes. + final Map extraData = {}; + + /// Invokes [callback] and records the duration of its execution under [key]. + /// + /// See also: + /// + /// * [recordAsync], which records asynchronous work. + Duration record(String key, VoidCallback callback, {required bool reported}) { + final Duration duration = timeAction(callback); + addDataPoint(key, duration, reported: reported); + return duration; + } + + /// Invokes [callback] and records the amount of time the returned future takes. + /// + /// See also: + /// + /// * [record], which records synchronous work. + Future recordAsync(String key, AsyncCallback callback, {required bool reported}) async { + final Duration duration = await timeAsyncAction(callback); + addDataPoint(key, duration, reported: reported); + return duration; + } + + /// Adds a timed sample to the timeseries corresponding to [key]. + /// + /// Set [reported] to `true` to report the timeseries to the dashboard UI. + /// + /// Set [reported] to `false` to store the data, but not show it on the + /// dashboard UI. + void addDataPoint(String key, Duration duration, {required bool reported}) { + scoreData + .putIfAbsent(key, () => Timeseries(key, reported)) + .add(duration.inMicroseconds.toDouble(), isWarmUpValue: isWarmingUp); + + if (!useCustomWarmUp) { + // The stopWarmingUp and stopBenchmark will not be called. Use the + // auto-stopping logic. + _autoUpdateBenchmarkPhase(); + } + } + + /// A convenience wrapper over [addDataPoint] for adding [AggregatedTimedBlock] + /// to the profile. + /// + /// Uses [AggregatedTimedBlock.name] as the name of the data point, and + /// [AggregatedTimedBlock.duration] as the duration. + void addTimedBlock(AggregatedTimedBlock timedBlock, {required bool reported}) { + addDataPoint( + timedBlock.name, + Duration(microseconds: timedBlock.duration.toInt()), + reported: reported, + ); + } + + /// Checks the samples collected so far and sets the appropriate benchmark phase. + /// + /// If enough warm-up samples have been collected, stops the warm-up phase and + /// begins the measuring phase. + /// + /// If enough total samples have been collected, stops the benchmark. + void _autoUpdateBenchmarkPhase() { + if (useCustomWarmUp) { + StateError( + 'Must not call _autoUpdateBenchmarkPhase if custom warm-up is used. ' + 'Call `stopWarmingUp` and `stopBenchmark` instead.', + ); + } + + if (_isWarmingUp) { + final bool doesHaveEnoughWarmUpSamples = scoreData.keys.every( + (String key) => scoreData[key]!.count >= _kDefaultWarmUpSampleCount, + ); + if (doesHaveEnoughWarmUpSamples) { + stopWarmingUp(); + } + } else if (_isRunning) { + final bool doesHaveEnoughTotalSamples = scoreData.keys.every( + (String key) => scoreData[key]!.count >= kDefaultTotalSampleCount, + ); + if (doesHaveEnoughTotalSamples) { + stopBenchmark(); + } + } + } + + /// Decides whether the data collected so far is sufficient to stop, or + /// whether the benchmark should continue collecting more data. + /// + /// The signals used are sample size, noise, and duration. + /// + /// If any of the timeseries doesn't satisfy the noise requirements, this + /// method will return true (asking the benchmark to continue collecting + /// data). + bool shouldContinue() { + // If there are no `Timeseries` in the `scoreData`, then we haven't + // recorded anything yet. Don't stop. + if (scoreData.isEmpty) { + return true; + } + + return isRunning; + } + + /// Returns a JSON representation of the profile that will be sent to the + /// server. + Map toJson() { + final List scoreKeys = []; + final Map json = {'name': name, 'scoreKeys': scoreKeys}; + + for (final String key in scoreData.keys) { + final Timeseries timeseries = scoreData[key]!; + + if (timeseries.isReported) { + scoreKeys.add('$key.average'); + // Report `outlierRatio` rather than `outlierAverage`, because + // the absolute value of outliers is less interesting than the + // ratio. + scoreKeys.add('$key.outlierRatio'); + } + + final TimeseriesStats stats = timeseries.computeStats(); + json['$key.average'] = stats.average; + json['$key.outlierAverage'] = stats.outlierAverage; + json['$key.outlierRatio'] = stats.outlierRatio; + json['$key.noise'] = stats.noise; + } + + json.addAll(extraData); + + return json; + } + + @override + String toString() { + final StringBuffer buffer = StringBuffer(); + buffer.writeln('name: $name'); + for (final String key in scoreData.keys) { + final Timeseries timeseries = scoreData[key]!; + final TimeseriesStats stats = timeseries.computeStats(); + buffer.writeln(stats.toString()); + } + for (final String key in extraData.keys) { + final dynamic value = extraData[key]; + if (value is List) { + buffer.writeln('$key:'); + for (final dynamic item in value) { + buffer.writeln(' - $item'); + } + } else { + buffer.writeln('$key: $value'); + } + } + return buffer.toString(); + } +} + +/// Computes the arithmetic mean (or average) of given [values]. +double _computeAverage(String label, Iterable values) { + if (values.isEmpty) { + throw StateError('$label: attempted to compute an average of an empty value list.'); + } + + final double sum = values.reduce((double a, double b) => a + b); + return sum / values.length; +} + +/// Computes population standard deviation. +/// +/// Unlike sample standard deviation, which divides by N - 1, this divides by N. +/// +/// See also: +/// +/// * +double _computeStandardDeviationForPopulation(String label, Iterable population) { + if (population.isEmpty) { + throw StateError('$label: attempted to compute the standard deviation of empty population.'); + } + final double mean = _computeAverage(label, population); + final double sumOfSquaredDeltas = population.fold( + 0.0, + (double previous, double value) => previous += math.pow(value - mean, 2), + ); + return math.sqrt(sumOfSquaredDeltas / population.length); +} + +String _ratioToPercent(double value) { + return '${(value * 100).toStringAsFixed(2)}%'; +} + +/// Implemented by recorders that use [_RecordingWidgetsBinding] to receive +/// frame life-cycle calls. +abstract class FrameRecorder { + /// Add a callback that will be called by the recorder when it stops recording. + void registerDidStop(VoidCallback cb); + + /// Called just before calling [SchedulerBinding.handleDrawFrame]. + void frameWillDraw(); + + /// Called immediately after calling [SchedulerBinding.handleDrawFrame]. + void frameDidDraw(); + + /// Reports an error. + /// + /// The implementation is expected to halt benchmark execution as soon as possible. + void _onError(Object error, StackTrace? stackTrace); +} + +/// A variant of [WidgetsBinding] that collaborates with a [Recorder] to decide +/// when to stop pumping frames. +/// +/// A normal [WidgetsBinding] typically always pumps frames whenever a widget +/// instructs it to do so by calling [scheduleFrame] (transitively via +/// `setState`). This binding will stop pumping new frames as soon as benchmark +/// parameters are satisfactory (e.g. when the metric noise levels become low +/// enough). +class _RecordingWidgetsBinding extends BindingBase + with + GestureBinding, + SchedulerBinding, + ServicesBinding, + PaintingBinding, + SemanticsBinding, + RendererBinding, + WidgetsBinding { + @override + void initInstances() { + super.initInstances(); + _instance = this; + } + + /// The singleton instance of this object. + /// + /// Provides access to the features exposed by this class. The binding must + /// be initialized before using this getter; this is typically done by calling + /// [_RecordingWidgetsBinding.ensureInitialized]. + static _RecordingWidgetsBinding get instance => BindingBase.checkInstance(_instance); + static _RecordingWidgetsBinding? _instance; + + /// Returns an instance of the [_RecordingWidgetsBinding], creating and + /// initializing it if necessary. + /// + /// See also: + /// + /// * [WidgetsFlutterBinding.ensureInitialized], the equivalent in the widgets framework. + static _RecordingWidgetsBinding ensureInitialized() { + if (_instance == null) { + _RecordingWidgetsBinding(); + } + return instance; + } + + FrameRecorder? _recorder; + bool _hasErrored = false; + + /// To short-circuit all frame lifecycle methods when the benchmark has + /// stopped collecting data. + bool _benchmarkStopped = false; + + void _beginRecording(FrameRecorder recorder, Widget widget) { + if (_recorder != null) { + throw Exception('Cannot call _RecordingWidgetsBinding._beginRecording more than once'); + } + final FlutterExceptionHandler? originalOnError = FlutterError.onError; + + recorder.registerDidStop(() { + _benchmarkStopped = true; + }); + + // Fail hard and fast on errors. Benchmarks should not have any errors. + FlutterError.onError = (FlutterErrorDetails details) { + _haltBenchmarkWithError(details.exception, details.stack); + originalOnError?.call(details); + }; + _recorder = recorder; + runApp(widget); + } + + void _haltBenchmarkWithError(Object error, StackTrace? stackTrace) { + if (_hasErrored) { + return; + } + _recorder?._onError(error, stackTrace); + _hasErrored = true; + } + + @override + void handleBeginFrame(Duration? rawTimeStamp) { + // Don't keep on truckin' if there's an error or the benchmark has stopped. + if (_hasErrored || _benchmarkStopped) { + return; + } + try { + super.handleBeginFrame(rawTimeStamp); + } catch (error, stackTrace) { + _haltBenchmarkWithError(error, stackTrace); + rethrow; + } + } + + @override + void scheduleFrame() { + // Don't keep on truckin' if there's an error or the benchmark has stopped. + if (_hasErrored || _benchmarkStopped) { + return; + } + super.scheduleFrame(); + } + + @override + void handleDrawFrame() { + // Don't keep on truckin' if there's an error or the benchmark has stopped. + if (_hasErrored || _benchmarkStopped) { + return; + } + try { + _recorder?.frameWillDraw(); + super.handleDrawFrame(); + _recorder?.frameDidDraw(); + } catch (error, stackTrace) { + _haltBenchmarkWithError(error, stackTrace); + rethrow; + } + } +} + +int _currentFrameNumber = 1; + +/// If [_calledStartMeasureFrame] is true, we have called [startMeasureFrame] +/// but have not its pairing [endMeasureFrame] yet. +/// +/// This flag ensures that [startMeasureFrame] and [endMeasureFrame] are always +/// called in pairs, with [startMeasureFrame] followed by [endMeasureFrame]. +bool _calledStartMeasureFrame = false; + +/// Whether we are recording a measured frame. +/// +/// This flag ensures that we always stop measuring a frame if we +/// have started one. Because we want to skip warm-up frames, this flag +/// is necessary. +bool _isMeasuringFrame = false; + +/// Adds a marker indication the beginning of frame rendering. +/// +/// This adds an event to the performance trace used to find measured frames in +/// Chrome tracing data. The tracing data contains all frames, but some +/// benchmarks are only interested in a subset of frames. For example, +/// [WidgetBuildRecorder] only measures frames that build widgets, and ignores +/// frames that clear the screen. +/// +/// Warm-up frames are not measured. If [profile.isWarmingUp] is true, +/// this function does nothing. +void startMeasureFrame(Profile profile) { + if (_calledStartMeasureFrame) { + throw Exception('`startMeasureFrame` called twice in a row.'); + } + + _calledStartMeasureFrame = true; + + if (!profile.isWarmingUp) { + // Tell the browser to mark the beginning of the frame. + web.window.performance.mark('measured_frame_start#$_currentFrameNumber'); + _isMeasuringFrame = true; + } +} + +/// Signals the end of a measured frame. +/// +/// See [startMeasureFrame] for details on what this instrumentation is used +/// for. +/// +/// Warm-up frames are not measured. If [profile.isWarmingUp] was true +/// when the corresponding [startMeasureFrame] was called, +/// this function does nothing. +void endMeasureFrame() { + if (!_calledStartMeasureFrame) { + throw Exception('`startMeasureFrame` has not been called before calling `endMeasureFrame`'); + } + + _calledStartMeasureFrame = false; + + if (_isMeasuringFrame) { + // Tell the browser to mark the end of the frame, and measure the duration. + web.window.performance.mark('measured_frame_end#$_currentFrameNumber'); + web.window.performance.measure( + 'measured_frame', + 'measured_frame_start#$_currentFrameNumber'.toJS, + 'measured_frame_end#$_currentFrameNumber', + ); + + // Increment the current frame number. + _currentFrameNumber += 1; + + _isMeasuringFrame = false; + } +} + +/// A function that receives a benchmark value from the framework. +typedef EngineBenchmarkValueListener = void Function(num value); + +// Maps from a value label name to a listener. +final Map _engineBenchmarkListeners = + {}; + +/// Registers a [listener] for engine benchmark values labeled by [name]. +/// +/// If another listener is already registered, overrides it. +void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListener listener) { + if (_engineBenchmarkListeners.containsKey(name)) { + throw StateError( + 'A listener for "$name" is already registered.\n' + 'Call `stopListeningToEngineBenchmarkValues` to unregister the previous ' + 'listener before registering a new one.', + ); + } + + if (_engineBenchmarkListeners.isEmpty) { + // The first listener is being registered. Register the global listener. + ui_web.benchmarkValueCallback = _dispatchEngineBenchmarkValue; + } + _engineBenchmarkListeners[name] = listener; +} + +/// Stops listening to engine benchmark values under labeled by [name]. +void stopListeningToEngineBenchmarkValues(String name) { + _engineBenchmarkListeners.remove(name); + if (_engineBenchmarkListeners.isEmpty) { + // The last listener unregistered. Remove the global listener. + ui_web.benchmarkValueCallback = null; + } +} + +// Dispatches a benchmark value reported by the engine to the relevant listener. +// +// If there are no listeners registered for [name], ignores the value. +void _dispatchEngineBenchmarkValue(String name, double value) { + final EngineBenchmarkValueListener? listener = _engineBenchmarkListeners[name]; + if (listener != null) { + listener(value); + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/test_data.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/test_data.dart new file mode 100644 index 00000000..cec8c6a6 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/src/web/test_data.dart @@ -0,0 +1,66 @@ +// Copyright 2014 The Flutter 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:math' as math; +import 'dart:ui'; + +// Used to randomize data. +// +// Using constant seed for reproducibility. +final math.Random _random = math.Random(0); + +/// Random words used by benchmarks that contain text. +final List lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' + 'elit. Vivamus ut ligula a neque mattis posuere. Sed suscipit lobortis ' + 'sodales. Morbi sed neque molestie, hendrerit odio ac, aliquam velit. ' + 'Curabitur non quam sit amet nibh sollicitudin ultrices. Fusce ' + 'ullamcorper bibendum commodo. In et feugiat nisl. Aenean vulputate in ' + 'odio vestibulum ultricies. Nunc dolor libero, hendrerit eu urna sit ' + 'amet, pretium iaculis nulla. Ut porttitor nisl et leo iaculis, vel ' + 'fringilla odio pulvinar. Ut eget ligula id odio auctor egestas nec a ' + 'nisl. Aliquam luctus dolor et magna posuere mattis. ' + 'Suspendisse fringilla nisl et massa congue, eget ' + 'imperdiet lectus porta. Vestibulum sed dui sed dui porta imperdiet ut in risus. ' + 'Fusce diam purus, faucibus id accumsan sit amet, semper a sem. Sed aliquam ' + 'lacus eget libero ultricies, quis hendrerit tortor posuere. Pellentesque ' + 'sagittis eu est in maximus. Proin auctor fringilla dolor in hendrerit. Nam ' + 'pulvinar rhoncus tellus. Nullam vel mauris semper, volutpat tellus at, sagittis ' + 'lectus. Donec vitae nibh mauris. Morbi posuere sem id eros tristique tempus. ' + 'Vivamus lacinia sapien neque, eu semper purus gravida ut.' + .split(' '); + +/// Generates strings and builds pre-laid out paragraphs to be used by +/// benchmarks. +List generateLaidOutParagraphs({ + required int paragraphCount, + required int minWordCountPerParagraph, + required int maxWordCountPerParagraph, + required double widthConstraint, + required Color color, +}) { + final List strings = []; + int wordPointer = 0; // points to the next word in lipsum to extract + for (int i = 0; i < paragraphCount; i++) { + final int wordCount = + minWordCountPerParagraph + + _random.nextInt(maxWordCountPerParagraph - minWordCountPerParagraph + 1); + final List string = []; + for (int j = 0; j < wordCount; j++) { + string.add(lipsum[wordPointer]); + wordPointer = (wordPointer + 1) % lipsum.length; + } + + final ParagraphBuilder builder = + ParagraphBuilder(ParagraphStyle(fontFamily: 'sans-serif')) + ..pushStyle(TextStyle(color: color, fontSize: 18.0)) + ..addText(string.join(' ')) + ..pop(); + final Paragraph paragraph = builder.build(); + + // Fill half the screen. + paragraph.layout(ParagraphConstraints(width: widthConstraint)); + strings.add(paragraph); + } + return strings; +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/flutter/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart new file mode 100644 index 00000000..23f6e3d1 --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart @@ -0,0 +1,472 @@ +// Copyright 2014 The Flutter 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:convert' show json; +import 'dart:js_interop'; +import 'dart:math' as math; + +import 'package:web/web.dart' as web; + +import 'src/web/bench_build_image.dart'; +import 'src/web/bench_build_material_checkbox.dart'; +import 'src/web/bench_card_infinite_scroll.dart'; +import 'src/web/bench_child_layers.dart'; +import 'src/web/bench_clipped_out_pictures.dart'; +import 'src/web/bench_default_target_platform.dart'; +import 'src/web/bench_draw_rect.dart'; +import 'src/web/bench_dynamic_clip_on_static_picture.dart'; +import 'src/web/bench_harness.dart'; +import 'src/web/bench_image_decoding.dart'; +import 'src/web/bench_material_3.dart'; +import 'src/web/bench_material_3_semantics.dart'; +import 'src/web/bench_mouse_region_grid_hover.dart'; +import 'src/web/bench_mouse_region_grid_scroll.dart'; +import 'src/web/bench_mouse_region_mixed_grid_hover.dart'; +import 'src/web/bench_pageview_scroll_linethrough.dart'; +import 'src/web/bench_paths.dart'; +import 'src/web/bench_picture_recording.dart'; +import 'src/web/bench_platform_view_infinite_scroll.dart'; +import 'src/web/bench_simple_lazy_text_scroll.dart'; +import 'src/web/bench_text_layout.dart'; +import 'src/web/bench_text_out_of_picture_bounds.dart'; +import 'src/web/bench_wrapbox_scroll.dart'; +import 'src/web/recorder.dart'; + +typedef RecorderFactory = Recorder Function(); + +/// List of all benchmarks that run in the devicelab. +/// +/// When adding a new benchmark, add it to this map. Make sure that the name +/// of your benchmark is unique. +final Map benchmarks = { + // Benchmarks the overhead of the benchmark harness itself. + BenchRawRecorder.benchmarkName: () => BenchRawRecorder(), + BenchWidgetRecorder.benchmarkName: () => BenchWidgetRecorder(), + BenchWidgetBuildRecorder.benchmarkName: () => BenchWidgetBuildRecorder(), + BenchSceneBuilderRecorder.benchmarkName: () => BenchSceneBuilderRecorder(), + + // Benchmarks that run in all renderers. + BenchDefaultTargetPlatform.benchmarkName: () => BenchDefaultTargetPlatform(), + BenchBuildImage.benchmarkName: () => BenchBuildImage(), + BenchCardInfiniteScroll.benchmarkName: () => BenchCardInfiniteScroll.forward(), + BenchCardInfiniteScroll.benchmarkNameBackward: () => BenchCardInfiniteScroll.backward(), + BenchClippedOutPictures.benchmarkName: () => BenchClippedOutPictures(), + BenchDrawRect.benchmarkName: () => BenchDrawRect.staticPaint(), + BenchDrawRect.variablePaintBenchmarkName: () => BenchDrawRect.variablePaint(), + BenchPathRecording.benchmarkName: () => BenchPathRecording(), + BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(), + BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(), + BenchBuildMaterialCheckbox.benchmarkName: () => BenchBuildMaterialCheckbox(), + BenchDynamicClipOnStaticPicture.benchmarkName: () => BenchDynamicClipOnStaticPicture(), + BenchPageViewScrollLineThrough.benchmarkName: () => BenchPageViewScrollLineThrough(), + BenchPictureRecording.benchmarkName: () => BenchPictureRecording(), + BenchUpdateManyChildLayers.benchmarkName: () => BenchUpdateManyChildLayers(), + BenchMouseRegionGridScroll.benchmarkName: () => BenchMouseRegionGridScroll(), + BenchMouseRegionGridHover.benchmarkName: () => BenchMouseRegionGridHover(), + BenchMouseRegionMixedGridHover.benchmarkName: () => BenchMouseRegionMixedGridHover(), + BenchWrapBoxScroll.benchmarkName: () => BenchWrapBoxScroll(), + BenchPlatformViewInfiniteScroll.benchmarkName: () => BenchPlatformViewInfiniteScroll.forward(), + BenchPlatformViewInfiniteScroll.benchmarkNameBackward: + () => BenchPlatformViewInfiniteScroll.backward(), + BenchMaterial3Components.benchmarkName: () => BenchMaterial3Components(), + BenchMaterial3Semantics.benchmarkName: () => BenchMaterial3Semantics(), + BenchMaterial3ScrollSemantics.benchmarkName: () => BenchMaterial3ScrollSemantics(), + + BenchTextLayout.benchmarkName: () => BenchTextLayout(), + BenchBuildColorsGrid.benchmarkName: () => BenchBuildColorsGrid(), + BenchTextCachedLayout.benchmarkName: () => BenchTextCachedLayout(), + + BenchImageDecoding.benchmarkName: () => BenchImageDecoding(), +}; + +final LocalBenchmarkServerClient _client = LocalBenchmarkServerClient(); + +Future main() async { + // Check if the benchmark server wants us to run a specific benchmark. + final String nextBenchmark = await _client.requestNextBenchmark(); + + if (nextBenchmark == LocalBenchmarkServerClient.kManualFallback) { + _fallbackToManual('The server did not tell us which benchmark to run next.'); + return; + } + + await _runBenchmark(nextBenchmark); + web.window.location.reload(); +} + +Future _runBenchmark(String benchmarkName) async { + final RecorderFactory? recorderFactory = benchmarks[benchmarkName]; + + if (recorderFactory == null) { + _fallbackToManual('Benchmark $benchmarkName not found.'); + return; + } + + await runZoned>( + () async { + final Recorder recorder = recorderFactory(); + final Runner runner = + recorder.isTracingEnabled && !_client.isInManualMode + ? Runner( + recorder: recorder, + setUpAllDidRun: () => _client.startPerformanceTracing(benchmarkName), + tearDownAllWillRun: _client.stopPerformanceTracing, + ) + : Runner(recorder: recorder); + + final Profile profile = await runner.run(); + if (!_client.isInManualMode) { + await _client.sendProfileData(profile); + } else { + _printResultsToScreen(profile); + print(profile); + } + }, + zoneSpecification: ZoneSpecification( + print: (Zone self, ZoneDelegate parent, Zone zone, String line) async { + if (_client.isInManualMode) { + parent.print(zone, '[$benchmarkName] $line'); + } else { + await _client.printToConsole(line); + } + }, + handleUncaughtError: ( + Zone self, + ZoneDelegate parent, + Zone zone, + Object error, + StackTrace stackTrace, + ) async { + if (_client.isInManualMode) { + parent.print(zone, '[$benchmarkName] $error, $stackTrace'); + parent.handleUncaughtError(zone, error, stackTrace); + } else { + await _client.reportError(error, stackTrace); + } + }, + ), + ); +} + +extension WebHTMLElementExtension on web.HTMLElement { + void appendHtml(String html) { + final web.HTMLDivElement div = web.document.createElement('div') as web.HTMLDivElement; + div.innerHTML = html.toJS; + final web.DocumentFragment fragment = web.document.createDocumentFragment(); + fragment.append(div as JSAny); + web.document.adoptNode(fragment); + append(fragment as JSAny); + } +} + +void _fallbackToManual(String error) { + web.document.body!.appendHtml(''' +
+

$error

+ +

Choose one of the following benchmarks:

+ + +
    + ${benchmarks.keys.map((String name) => '
  • ').join('\n')} +
+
+ '''); + + for (final String benchmarkName in benchmarks.keys) { + final web.Element button = web.document.querySelector('#$benchmarkName')!; + button.addEventListener( + 'click', + (JSObject _) { + final web.Element? manualPanel = web.document.querySelector('#manual-panel'); + manualPanel?.remove(); + _runBenchmark(benchmarkName); + }.toJS, + ); + } +} + +/// Visualizes results on the Web page for manual inspection. +void _printResultsToScreen(Profile profile) { + web.document.body!.remove(); + web.document.body = web.document.createElement('body') as web.HTMLBodyElement; + web.document.body!.appendHtml('

${profile.name}

'); + + profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { + web.document.body!.appendHtml('

$scoreKey

'); + web.document.body!.appendHtml('
${timeseries.computeStats()}
'); + web.document.body!.append(TimeseriesVisualization(timeseries).render() as JSAny); + }); +} + +/// Draws timeseries data and statistics on a canvas. +class TimeseriesVisualization { + TimeseriesVisualization(this._timeseries) { + _stats = _timeseries.computeStats(); + _canvas = web.document.createElement('canvas') as web.HTMLCanvasElement; + _screenWidth = web.window.screen.width; + _canvas.width = _screenWidth; + _canvas.height = (_kCanvasHeight * web.window.devicePixelRatio).round(); + _canvas.style + ..setProperty('width', '100%') + ..setProperty('height', '${_kCanvasHeight}px') + ..setProperty('outline', '1px solid green'); + _ctx = _canvas.getContext('2d')! as web.CanvasRenderingContext2D; + + // The amount of vertical space available on the chart. Because some + // outliers can be huge they can dwarf all the useful values. So we + // limit it to 1.5 x the biggest non-outlier. + _maxValueChartRange = + 1.5 * + _stats.samples + .where((AnnotatedSample sample) => !sample.isOutlier) + .map((AnnotatedSample sample) => sample.magnitude) + .fold(0, math.max); + } + + static const double _kCanvasHeight = 200; + + final Timeseries _timeseries; + late TimeseriesStats _stats; + late web.HTMLCanvasElement _canvas; + late web.CanvasRenderingContext2D _ctx; + late int _screenWidth; + + // Used to normalize benchmark values to chart height. + late double _maxValueChartRange; + + /// Converts a sample value to vertical canvas coordinates. + /// + /// This does not work for horizontal coordinates. + double _normalized(double value) { + return _kCanvasHeight * value / _maxValueChartRange; + } + + /// A utility for drawing lines. + void drawLine(num x1, num y1, num x2, num y2) { + _ctx.beginPath(); + _ctx.moveTo(x1.toDouble(), y1.toDouble()); + _ctx.lineTo(x2.toDouble(), y2.toDouble()); + _ctx.stroke(); + } + + /// Renders the timeseries into a `` and returns the canvas element. + web.HTMLCanvasElement render() { + _ctx.translate(0, _kCanvasHeight * web.window.devicePixelRatio); + _ctx.scale(1, -web.window.devicePixelRatio); + + final double barWidth = _screenWidth / _stats.samples.length; + double xOffset = 0; + for (int i = 0; i < _stats.samples.length; i++) { + final AnnotatedSample sample = _stats.samples[i]; + + if (sample.isWarmUpValue) { + // Put gray background behind warm-up samples. + _ctx.fillStyle = 'rgba(200,200,200,1)'.toJS; + _ctx.fillRect(xOffset, 0, barWidth, _normalized(_maxValueChartRange)); + } + + if (sample.magnitude > _maxValueChartRange) { + // The sample value is so big it doesn't fit on the chart. Paint it purple. + _ctx.fillStyle = 'rgba(100,50,100,0.8)'.toJS; + } else if (sample.isOutlier) { + // The sample is an outlier, color it light red. + _ctx.fillStyle = 'rgba(255,50,50,0.6)'.toJS; + } else { + // A non-outlier sample, color it light blue. + _ctx.fillStyle = 'rgba(50,50,255,0.6)'.toJS; + } + + _ctx.fillRect(xOffset, 0, barWidth - 1, _normalized(sample.magnitude)); + xOffset += barWidth; + } + + // Draw a horizontal solid line corresponding to the average. + _ctx.lineWidth = 1; + drawLine(0, _normalized(_stats.average), _screenWidth, _normalized(_stats.average)); + + // Draw a horizontal dashed line corresponding to the outlier cut off. + _ctx.setLineDash([5.toJS, 5.toJS].toJS); + drawLine(0, _normalized(_stats.outlierCutOff), _screenWidth, _normalized(_stats.outlierCutOff)); + + // Draw a light red band that shows the noise (1 stddev in each direction). + _ctx.fillStyle = 'rgba(255,50,50,0.3)'.toJS; + _ctx.fillRect( + 0, + _normalized(_stats.average * (1 - _stats.noise)), + _screenWidth.toDouble(), + _normalized(2 * _stats.average * _stats.noise), + ); + + return _canvas; + } +} + +/// Implements the client REST API for the local benchmark server. +/// +/// The local server is optional. If it is not available the benchmark UI must +/// implement a manual fallback. This allows debugging benchmarks using plain +/// `flutter run`. +class LocalBenchmarkServerClient { + /// This value is returned by [requestNextBenchmark]. + static const String kManualFallback = '__manual_fallback__'; + + /// Whether we fell back to manual mode. + /// + /// This happens when you run benchmarks using plain `flutter run` rather than + /// devicelab test harness. The test harness spins up a special server that + /// provides API for automatically picking the next benchmark to run. + bool isInManualMode = false; + + /// Asks the local server for the name of the next benchmark to run. + /// + /// Returns [kManualFallback] if local server is not available (uses 404 as a + /// signal). + Future requestNextBenchmark() async { + final web.XMLHttpRequest request = await _requestXhr( + '/next-benchmark', + method: 'POST', + mimeType: 'application/json', + sendData: json.encode(benchmarks.keys.toList()), + ); + + // 404 is expected in the following cases: + // - The benchmark is ran using plain `flutter run`, which does not provide "next-benchmark" handler. + // - We ran all benchmarks and the benchmark is telling us there are no more benchmarks to run. + if (request.status != 200) { + isInManualMode = true; + return kManualFallback; + } + + isInManualMode = false; + return request.responseText; + } + + void _checkNotManualMode() { + if (isInManualMode) { + throw StateError('Operation not supported in manual fallback mode.'); + } + } + + /// Asks the local server to begin tracing performance. + /// + /// This uses the chrome://tracing tracer, which is not available from within + /// the page itself, and therefore must be controlled from outside using the + /// DevTools Protocol. + Future startPerformanceTracing(String benchmarkName) async { + _checkNotManualMode(); + await _requestXhr( + '/start-performance-tracing?label=$benchmarkName', + method: 'POST', + mimeType: 'application/json', + ); + } + + /// Stops the performance tracing session started by [startPerformanceTracing]. + Future stopPerformanceTracing() async { + _checkNotManualMode(); + await _requestXhr('/stop-performance-tracing', method: 'POST', mimeType: 'application/json'); + } + + /// Sends the profile data collected by the benchmark to the local benchmark + /// server. + Future sendProfileData(Profile profile) async { + _checkNotManualMode(); + final web.XMLHttpRequest request = await _requestXhr( + '/profile-data', + method: 'POST', + mimeType: 'application/json', + sendData: json.encode(profile.toJson()), + ); + if (request.status != 200) { + throw Exception( + 'Failed to report profile data to benchmark server. ' + 'The server responded with status code ${request.status}.', + ); + } + } + + /// Reports an error to the benchmark server. + /// + /// The server will halt the devicelab task and log the error. + Future reportError(dynamic error, StackTrace stackTrace) async { + _checkNotManualMode(); + await _requestXhr( + '/on-error', + method: 'POST', + mimeType: 'application/json', + sendData: json.encode({'error': '$error', 'stackTrace': '$stackTrace'}), + ); + } + + /// Reports a message about the demo to the benchmark server. + Future printToConsole(String report) async { + _checkNotManualMode(); + await _requestXhr( + '/print-to-console', + method: 'POST', + mimeType: 'text/plain', + sendData: report, + ); + } + + /// This is the same as calling [html.HttpRequest.request] but it doesn't + /// crash on 404, which we use to detect `flutter run`. + Future _requestXhr( + String url, { + String? method, + bool? withCredentials, + String? responseType, + String? mimeType, + Map? requestHeaders, + dynamic sendData, + }) { + final Completer completer = Completer(); + final web.XMLHttpRequest xhr = web.XMLHttpRequest(); + + method ??= 'GET'; + xhr.open(method, url, true); + + if (withCredentials != null) { + xhr.withCredentials = withCredentials; + } + + if (responseType != null) { + xhr.responseType = responseType; + } + + if (mimeType != null) { + xhr.overrideMimeType(mimeType); + } + + if (requestHeaders != null) { + requestHeaders.forEach((String header, String value) { + xhr.setRequestHeader(header, value); + }); + } + + xhr.addEventListener( + 'load', + (web.ProgressEvent e) { + completer.complete(xhr); + }.toJS, + ); + + xhr.addEventListener( + 'error', + (JSObject error) { + return completer.completeError(error); + }.toJS, + ); + + if (sendData != null) { + xhr.send((sendData as Object?).jsify()); + } else { + xhr.send(); + } + + return completer.future; + } +} diff --git a/flutter/dev/benchmarks/macrobenchmarks/pubspec.yaml b/flutter/dev/benchmarks/macrobenchmarks/pubspec.yaml new file mode 100644 index 00000000..19e8ea4f --- /dev/null +++ b/flutter/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -0,0 +1,217 @@ +name: macrobenchmarks +description: Performance benchmarks using flutter drive. + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + + # To change the version of the gallery assets, edit + # //packages/flutter_tools/lib/src/commands/update_packages.dart + # and run + # flutter update-packages --force-upgrade + flutter_gallery_assets: 1.0.2 + + web: 1.1.0 + + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker: 10.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webdriver: 3.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + test: 1.25.14 + integration_test: + sdk: flutter + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + assets: + - packages/flutter_gallery_assets/animated_images/animated_flutter_lgtm.gif + - packages/flutter_gallery_assets/food/butternut_squash_soup.png + - packages/flutter_gallery_assets/food/cherry_pie.png + - assets/999x1000.png + - assets/Icon-192.png + + # The following assets are required for running Flutter Gallery benchmarks. + - packages/flutter_gallery_assets/assets/icons/settings/settings_light.flr + - packages/flutter_gallery_assets/assets/icons/settings/settings_dark.flr + - packages/flutter_gallery_assets/assets/studies/shrine_card_dark.png + - packages/flutter_gallery_assets/assets/studies/starter_card.png + - packages/flutter_gallery_assets/assets/studies/starter_card_dark.png + - packages/flutter_gallery_assets/assets/studies/fortnightly_card_dark.png + - packages/flutter_gallery_assets/assets/studies/rally_card_dark.png + - packages/flutter_gallery_assets/assets/studies/fortnightly_card.png + - packages/flutter_gallery_assets/assets/studies/crane_card.png + - packages/flutter_gallery_assets/assets/studies/shrine_card.png + - packages/flutter_gallery_assets/assets/studies/crane_card_dark.png + - packages/flutter_gallery_assets/assets/studies/rally_card.png + - packages/flutter_gallery_assets/assets/logo/flutter_logo.png + - packages/flutter_gallery_assets/assets/logo/flutter_logo_color.png + - packages/flutter_gallery_assets/assets/icons/cupertino/cupertino.png + - packages/flutter_gallery_assets/assets/icons/material/material.png + - packages/flutter_gallery_assets/assets/icons/reference/reference.png + - packages/flutter_gallery_assets/assets/demos/bottom_navigation_background.png + - packages/flutter_gallery_assets/fonts/GalleryIcons.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Eczar-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-Light.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoCondensed-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoMono-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-ExtraBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Oswald-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Oswald-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Bold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Merriweather-BoldItalic.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Raleway-Light.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-SemiBold.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/RobotoCondensed-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/LibreFranklin-Medium.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Montserrat-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Rubik-Regular.ttf + - packages/flutter_gallery_assets/fonts/google_fonts/Eczar-SemiBold.ttf + - packages/flutter_gallery_assets/crane/destinations/eat_1.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_2.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_3.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_4.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_5.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_6.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_7.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_8.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_9.jpg + - packages/flutter_gallery_assets/crane/destinations/eat_10.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_0.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_1.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_2.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_3.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_4.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_5.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_6.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_7.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_8.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_9.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_10.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_11.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_12.jpg + - packages/flutter_gallery_assets/crane/destinations/fly_13.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_0.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_1.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_2.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_3.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_4.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_5.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_6.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_7.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_8.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_9.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_10.jpg + - packages/flutter_gallery_assets/crane/destinations/sleep_11.jpg + - packages/flutter_gallery_assets/crane/logo/logo.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_army.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_bees.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_chart.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_fabrics.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_feminists.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_gas.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_healthcare.jpg + - packages/flutter_gallery_assets/fortnightly/fortnightly_stocks.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_title.png + - packages/flutter_gallery_assets/fortnightly/fortnightly_war.png + - packages/flutter_gallery_assets/places/india_chennai_flower_market.png + - packages/flutter_gallery_assets/places/india_thanjavur_market.png + - packages/flutter_gallery_assets/places/india_tanjore_bronze_works.png + - packages/flutter_gallery_assets/places/india_tanjore_market_merchant.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple.png + - packages/flutter_gallery_assets/places/india_pondicherry_salt_farm.png + - packages/flutter_gallery_assets/places/india_chennai_highway.png + - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple_carvings.png + - packages/flutter_gallery_assets/places/india_chettinad_produce.png + - packages/flutter_gallery_assets/places/india_tanjore_market_technology.png + - packages/flutter_gallery_assets/places/india_pondicherry_beach.png + - packages/flutter_gallery_assets/places/india_pondicherry_fisherman.png + - packages/flutter_gallery_assets/splash_effects/splash_effect_1.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_2.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_3.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_4.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_5.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_6.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_7.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_8.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_9.gif + - packages/flutter_gallery_assets/splash_effects/splash_effect_10.gif + + # The following font is required for running Flutter Gallery benchmarks. + fonts: + - family: GalleryIcons + fonts: + - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf + +# PUBSPEC CHECKSUM: 79bc diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/build.gradle b/flutter/dev/benchmarks/test_apps/stocks/android/build.gradle new file mode 100644 index 00000000..bedac9d2 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/build.gradle @@ -0,0 +1,34 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the build.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' + +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') + dependencyLocking { + ignoredDependencies.add('io.flutter:*') + lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") + if (!project.hasProperty('local-engine-repo')) { + lockAllConfigurations() + } + } +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/buildscript-gradle.lockfile b/flutter/dev/benchmarks/test_apps/stocks/android/buildscript-gradle.lockfile new file mode 100644 index 00000000..291d301d --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/buildscript-gradle.lockfile @@ -0,0 +1,153 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.databinding:databinding-common:8.7.0=classpath +androidx.databinding:databinding-compiler-common:8.7.0=classpath +com.android.application:com.android.application.gradle.plugin:8.7.0=classpath +com.android.databinding:baseLibrary:8.7.0=classpath +com.android.tools.analytics-library:crash:31.7.0=classpath +com.android.tools.analytics-library:protos:31.7.0=classpath +com.android.tools.analytics-library:shared:31.7.0=classpath +com.android.tools.analytics-library:tracker:31.7.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:8.7.0-12006047=classpath +com.android.tools.build:aaptcompiler:8.7.0=classpath +com.android.tools.build:apksig:8.7.0=classpath +com.android.tools.build:apkzlib:8.7.0=classpath +com.android.tools.build:builder-model:8.7.0=classpath +com.android.tools.build:builder-test-api:8.7.0=classpath +com.android.tools.build:builder:8.7.0=classpath +com.android.tools.build:bundletool:1.17.1=classpath +com.android.tools.build:gradle-api:8.7.0=classpath +com.android.tools.build:gradle-settings-api:8.7.0=classpath +com.android.tools.build:gradle:8.7.0=classpath +com.android.tools.build:manifest-merger:31.7.0=classpath +com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath +com.android.tools.ddms:ddmlib:31.7.0=classpath +com.android.tools.layoutlib:layoutlib-api:31.7.0=classpath +com.android.tools.lint:lint-model:31.7.0=classpath +com.android.tools.lint:lint-typedef-remover:31.7.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=classpath +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=classpath +com.android.tools:annotations:31.7.0=classpath +com.android.tools:common:31.7.0=classpath +com.android.tools:dvlib:31.7.0=classpath +com.android.tools:repository:31.7.0=classpath +com.android.tools:sdk-common:31.7.0=classpath +com.android.tools:sdklib:31.7.0=classpath +com.android:signflinger:8.7.0=classpath +com.android:zipflinger:8.7.0=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.17.0=classpath +com.google.auto.value:auto-value-annotations:1.6.2=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.10.1=classpath +com.google.crypto.tink:tink:1.7.0=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.18.0=classpath +com.google.flatbuffers:flatbuffers-java:1.12.0=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:32.0.1-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:2.8=classpath +com.google.jimfs:jimfs:1.1=classpath +com.google.protobuf:protobuf-java-util:3.22.3=classpath +com.google.protobuf:protobuf-java:3.22.3=classpath +com.google.testing.platform:core-proto:0.0.9-alpha02=classpath +com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath +com.squareup:javapoet:1.10.0=classpath +com.squareup:javawriter:2.5.0=classpath +com.sun.activation:javax.activation:1.2.0=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath +commons-io:commons-io:2.13.0=classpath +commons-logging:commons-logging:1.2=classpath +io.grpc:grpc-api:1.57.0=classpath +io.grpc:grpc-context:1.57.0=classpath +io.grpc:grpc-core:1.57.0=classpath +io.grpc:grpc-netty:1.57.0=classpath +io.grpc:grpc-protobuf-lite:1.57.0=classpath +io.grpc:grpc-protobuf:1.57.0=classpath +io.grpc:grpc-stub:1.57.0=classpath +io.netty:netty-buffer:4.1.93.Final=classpath +io.netty:netty-codec-http2:4.1.93.Final=classpath +io.netty:netty-codec-http:4.1.93.Final=classpath +io.netty:netty-codec-socks:4.1.93.Final=classpath +io.netty:netty-codec:4.1.93.Final=classpath +io.netty:netty-common:4.1.93.Final=classpath +io.netty:netty-handler-proxy:4.1.93.Final=classpath +io.netty:netty-handler:4.1.93.Final=classpath +io.netty:netty-resolver:4.1.93.Final=classpath +io.netty:netty-transport-native-unix-common:4.1.93.Final=classpath +io.netty:netty-transport:4.1.93.Final=classpath +io.perfmark:perfmark-api:0.26.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath +javax.inject:javax.inject:1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath +net.sf.jopt-simple:jopt-simple:4.9=classpath +net.sf.kxml:kxml2:2.3.0=classpath +org.apache.commons:commons-compress:1.21=classpath +org.apache.httpcomponents:httpclient:4.5.14=classpath +org.apache.httpcomponents:httpcore:4.4.16=classpath +org.apache.httpcomponents:httpmime:4.5.6=classpath +org.bitbucket.b_c:jose4j:0.9.5=classpath +org.bouncycastle:bcpkix-jdk18on:1.77=classpath +org.bouncycastle:bcprov-jdk18on:1.77=classpath +org.bouncycastle:bcutil-jdk18on:1.77=classpath +org.checkerframework:checker-qual:3.33.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.23=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath +org.jdom:jdom2:2.0.6=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin.android:org.jetbrains.kotlin.android.gradle.plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.8.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.8.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.8.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.8.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.8.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.8.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.9.20=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.8.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.8.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.8.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.8.10=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:23.0.0=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.6=classpath +org.ow2.asm:asm-commons:9.6=classpath +org.ow2.asm:asm-tree:9.6=classpath +org.ow2.asm:asm-util:9.6=classpath +org.ow2.asm:asm:9.6=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +empty= diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/gradle.properties b/flutter/dev/benchmarks/test_apps/stocks/android/gradle.properties new file mode 100644 index 00000000..25971708 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/project-app.lockfile b/flutter/dev/benchmarks/test_apps/stocks/android/project-app.lockfile new file mode 100644 index 00000000..e914d37f --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/project-app.lockfile @@ -0,0 +1,206 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.8.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.4.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-jvm:1.8.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.8.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.13.1=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core-ktx:1.2.0=debugAndroidTestCompileClasspath +androidx.core:core:1.13.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.interpolator:interpolator:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-process:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.7.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.profileinstaller:profileinstaller:1.3.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.startup:startup-runtime:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window.extensions.core:core:1.0.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.emulator:proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools.utp:android-device-provider-ddmlib-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-ddmlib:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib +com.android.tools.utp:android-device-provider-gradle-proto:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-gradle:31.7.0=_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile-proto:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-device-provider-profile:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-additional-test-output:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output +com.android.tools.utp:android-test-plugin-host-apk-installer-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-apk-installer:31.7.0=_internal-unified-test-platform-android-test-plugin-host-apk-installer +com.android.tools.utp:android-test-plugin-host-coverage-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-coverage:31.7.0=_internal-unified-test-platform-android-test-plugin-host-coverage +com.android.tools.utp:android-test-plugin-host-device-info-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-device-info:31.7.0=_internal-unified-test-platform-android-test-plugin-host-device-info +com.android.tools.utp:android-test-plugin-host-emulator-control-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-emulator-control:31.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.android.tools.utp:android-test-plugin-host-logcat-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-logcat:31.7.0=_internal-unified-test-platform-android-test-plugin-host-logcat +com.android.tools.utp:android-test-plugin-host-retention-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-host-retention:31.7.0=_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:android-test-plugin-result-listener-gradle:31.7.0=_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools.utp:utp-common:31.7.0=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:annotations:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:common:31.7.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath +com.getkeepsafe.relinker:relinker:1.4.5=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.android:annotations:4.1.1.4=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.api.grpc:proto-google-common-protos:2.17.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.findbugs:jsr305:3.0.2=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +com.google.code.gson:gson:2.10.1=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.code.gson:gson:2.8.5=lintClassPath +com.google.crypto.tink:tink:1.7.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.errorprone:error_prone_annotations:2.18.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath +com.google.guava:failureaccess:1.0.1=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:guava:32.0.1-jre=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.guava:listenablefuture:1.0=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +com.google.j2objc:j2objc-annotations:1.3=lintClassPath +com.google.j2objc:j2objc-annotations:2.8=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.google.protobuf:protobuf-java:3.22.3=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-device-provider-local:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha02=_internal-unified-test-platform-android-driver-instrumentation,_internal-unified-test-platform-android-test-plugin-host-emulator-control +com.google.testing.platform:android-test-plugin:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin +com.google.testing.platform:core-proto:0.0.9-alpha02=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +com.google.testing.platform:core:0.0.9-alpha02=_internal-unified-test-platform-core +com.google.testing.platform:launcher:0.0.9-alpha02=_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-launcher +com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-io:commons-io:2.13.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention +commons-logging:commons-logging:1.2=lintClassPath +io.grpc:grpc-api:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-context:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-core:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-netty:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf-lite:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-protobuf:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.grpc:grpc-stub:1.57.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-buffer:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http2:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-http:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec-socks:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-codec:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler-proxy:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-handler:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-resolver:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport-native-unix-common:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.netty:netty-transport:4.1.93.Final=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +io.perfmark:perfmark-api:0.26.0=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath +javax.annotation:javax.annotation-api:1.3.2=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +javax.inject:javax.inject:1=lintClassPath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath +net.java.dev.jna:jna-platform:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.java.dev.jna:jna:5.6.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=_internal-unified-test-platform-android-device-provider-ddmlib,lintClassPath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.checkerframework:checker-qual:3.33.0=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.23=_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib:1.8.22=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.9.20=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=_internal-unified-test-platform-android-device-provider-ddmlib,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=_internal-unified-test-platform-android-device-provider-gradle,_internal-unified-test-platform-android-test-plugin-host-additional-test-output,_internal-unified-test-platform-android-test-plugin-host-apk-installer,_internal-unified-test-platform-android-test-plugin-host-coverage,_internal-unified-test-platform-android-test-plugin-host-device-info,_internal-unified-test-platform-android-test-plugin-host-emulator-control,_internal-unified-test-platform-android-test-plugin-host-logcat,_internal-unified-test-platform-android-test-plugin-host-retention,_internal-unified-test-platform-android-test-plugin-result-listener-gradle,lintClassPath +org.jetbrains:annotations:23.0.0=_internal-unified-test-platform-android-device-provider-ddmlib,debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=lintClassPath +org.ow2.asm:asm:7.0=lintClassPath +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/project-integration_test.lockfile b/flutter/dev/benchmarks/test_apps/stocks/android/project-integration_test.lockfile new file mode 100644 index 00000000..1326b877 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/project-integration_test.lockfile @@ -0,0 +1,67 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-compat-qual:2.5.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-core:5.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-inline:5.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.objenesis:objenesis:3.3=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath diff --git a/flutter/dev/benchmarks/test_apps/stocks/android/settings.gradle b/flutter/dev/benchmarks/test_apps/stocks/android/settings.gradle new file mode 100644 index 00000000..4a155b35 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/android/settings.gradle @@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter 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 file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +buildscript { + dependencyLocking { + lockFile = file("${rootProject.projectDir}/buildscript-gradle.lockfile") + lockAllConfigurations() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.7.0" apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false +} + +include ":app" diff --git a/flutter/dev/benchmarks/test_apps/stocks/l10n.yaml b/flutter/dev/benchmarks/test_apps/stocks/l10n.yaml new file mode 100644 index 00000000..414eb5f7 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/l10n.yaml @@ -0,0 +1,24 @@ +# Options used by the localizations tool +## `arb-dir` sets the input directory. The output directory will match +## the input directory if the output directory is not set. +arb-dir: lib/i18n +## `header-file` is the file that contains a custom +## header for each of the generated files. +header-file: header.txt +## `output-class` is the name of the localizations class your +## Flutter application will use. The file will need to be +## imported throughout your application. +output-class: StockStrings +## `output-localization-file` is the name of the generated file. +output-localization-file: stock_strings.dart +## `template-arb-file` describes the template arb file that the tool +## will use to check and validate the remaining arb files when +## generating Flutter's localization files. +synthetic-package: false +template-arb-file: stocks_en.arb +## setting `nullable-getter` to false generates a non-nullable +## StockStrings getter. This removes the need for adding null checks +## in the Flutter application itself. +nullable-getter: false +## Run the formatter on the generated localization files. +format: true diff --git a/flutter/dev/benchmarks/test_apps/stocks/pubspec.yaml b/flutter/dev/benchmarks/test_apps/stocks/pubspec.yaml new file mode 100644 index 00000000..61414bd7 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/pubspec.yaml @@ -0,0 +1,83 @@ +name: stocks + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + intl: 0.19.0 + http: 1.2.2 + isolate: 2.1.1 + + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + test: 1.25.14 + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker: 10.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webdriver: 3.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + +# PUBSPEC CHECKSUM: 88a9 diff --git a/flutter/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart b/flutter/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart new file mode 100644 index 00000000..21c8d3e5 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart @@ -0,0 +1,45 @@ +// Copyright 2014 The Flutter 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:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + group('scrolling performance test', () { + late FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + tearDownAll(() async { + driver.close(); + }); + + test('measure', () async { + final Timeline timeline = await driver.traceAction(() async { + // Find the scrollable stock list + final SerializableFinder stockList = find.byValueKey('stock-list'); + expect(stockList, isNotNull); + + // Scroll down + for (int i = 0; i < 5; i++) { + await driver.scroll(stockList, 0.0, -300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + + // Scroll up + for (int i = 0; i < 5; i++) { + await driver.scroll(stockList, 0.0, 300.0, const Duration(milliseconds: 300)); + await Future.delayed(const Duration(milliseconds: 500)); + } + }); + + final TimelineSummary summary = TimelineSummary.summarize(timeline); + await summary.writeTimelineToFile('stocks_scroll_perf', pretty: true); + }, timeout: Timeout.none); + }); +} diff --git a/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart b/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart new file mode 100644 index 00000000..332e06f4 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter 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:flutter_driver/driver_extension.dart'; +import 'package:stocks/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart b/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart new file mode 100644 index 00000000..17745812 --- /dev/null +++ b/flutter/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart @@ -0,0 +1,48 @@ +// Copyright 2014 The Flutter 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:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart' hide TypeMatcher, isInstanceOf; + +void main() { + group('basic stock view test', () { + late FlutterDriver driver; + + setUpAll(() async { + driver = await FlutterDriver.connect(); + }); + + tearDownAll(() async { + driver.close(); + }); + + test('Stock list is shown', () async { + final SerializableFinder stockList = find.byValueKey('stock-list'); + expect(stockList, isNotNull); + }, timeout: Timeout.none); + + test( + 'open AAPL stock', + () async { + final SerializableFinder stockList = find.byValueKey('stock-list'); + expect(stockList, isNotNull); + + final SerializableFinder aaplStockRow = find.byValueKey('AAPL'); + await driver.scrollUntilVisible(stockList, aaplStockRow); + + await driver.tap(aaplStockRow); + await Future.delayed(const Duration(milliseconds: 500)); + + final SerializableFinder stockOption = find.byValueKey('AAPL_symbol_name'); + final String symbol = await driver.getText(stockOption); + + expect(symbol, 'AAPL'); + }, + skip: 'Needs to be fixed on Fuchsia.', // https://github.com/flutter/flutter/issues/87069 + timeout: Timeout.none, + ); + }); +} diff --git a/flutter/dev/bots/test/analyze-test-input/.dartignore b/flutter/dev/bots/test/analyze-test-input/.dartignore new file mode 100644 index 00000000..4beb0434 --- /dev/null +++ b/flutter/dev/bots/test/analyze-test-input/.dartignore @@ -0,0 +1,6 @@ +This directory is excluded from analysis because its whole point is to +test the analysis (so it has issues). + +We have to have the actual test file system in a subdirectory (root) +because otherwise the .dartignore file in that directory would cause +the test itself to ignore the directory. \ No newline at end of file diff --git a/flutter/dev/bots/test/analyze-test-input/.editorconfig b/flutter/dev/bots/test/analyze-test-input/.editorconfig new file mode 100644 index 00000000..b0965f4e --- /dev/null +++ b/flutter/dev/bots/test/analyze-test-input/.editorconfig @@ -0,0 +1,4 @@ +[*.{kt,kts}] +# Disable trailing commas to allow compatibility with Kotlin versions less than 1.4. +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false diff --git a/flutter/dev/bots/test/analyze-test-input/ktlint-baseline.xml b/flutter/dev/bots/test/analyze-test-input/ktlint-baseline.xml new file mode 100644 index 00000000..edcb4a1c --- /dev/null +++ b/flutter/dev/bots/test/analyze-test-input/ktlint-baseline.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flutter/dev/bots/test/analyze_snippet_code_test.dart b/flutter/dev/bots/test/analyze_snippet_code_test.dart new file mode 100644 index 00000000..fe353862 --- /dev/null +++ b/flutter/dev/bots/test/analyze_snippet_code_test.dart @@ -0,0 +1,116 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// we ignore these so that the format of the strings below matches what package:test prints, to make maintenance easier +// ignore_for_file: use_raw_strings + +import 'dart:io'; + +import 'common.dart'; + +const List expectedMainErrors = [ + 'dev/bots/test/analyze-snippet-code-test-input/custom_imports_broken.dart:19:11: (statement) (undefined_identifier)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:30:5: (expression) (unnecessary_new)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:103:5: (statement) (always_specify_types)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:5: (top-level declaration) (prefer_const_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:111:19: (top-level declaration) (unnecessary_nullable_for_final_variable_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:5: (top-level declaration) (prefer_const_declarations)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:112:21: (top-level declaration) (invalid_assignment)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:134:14: (top-level declaration) (undefined_identifier)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:136:21: (top-level declaration) (read_potentially_unassigned_final)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:147:12: (self-contained program) (unused_import)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:148:11: (self-contained program) (undefined_class)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:148:22: (self-contained program) (undefined_function)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:153:10: (stateful widget) (annotate_overrides)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:153:10: (stateful widget) (must_call_super)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:161:7: (top-level declaration) (undefined_identifier)', + 'dev/bots/test/analyze-snippet-code-test-input/known_broken_documentation.dart:165: Found "```" in code but it did not match RegExp: pattern=^ */// *```dart\$ flags= so something is wrong. Line was: "/// ```none"', + 'dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart:9:12: (statement) (invalid_assignment)', + 'dev/bots/test/analyze-snippet-code-test-input/short_but_still_broken.dart:18:4: Empty ```dart block in snippet code.', +]; + +const List expectedUiErrors = [ + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:14:7: (top-level declaration) (prefer_typing_uninitialized_variables)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:14:7: (top-level declaration) (inference_failure_on_uninitialized_variable)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:14:7: (top-level declaration) (missing_const_final_var_or_type)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:16:20: (top-level declaration) (prefer_final_fields)', + 'dev/bots/test/analyze-snippet-code-test-dart-ui/ui.dart:16:20: (top-level declaration) (unused_field)', +]; + +final RegExp errorPrefixRE = RegExp(r'^([-a-z0-9/_.:]+): .*(\([-a-z_ ]+\) \([-a-z_ ]+\))$'); +String removeLintDescriptions(String error) { + final RegExpMatch? match = errorPrefixRE.firstMatch(error); + if (match != null) { + return '${match[1]}: ${match[2]}'; + } + return error; +} + +void main() { + // These tests don't run on Windows because the sample analyzer doesn't + // support Windows as a platform, since it is only run on Linux in the + // continuous integration tests. + if (Platform.isWindows) { + return; + } + + test( + 'analyze_snippet_code smoke test', + () { + final ProcessResult process = Process.runSync('../../bin/cache/dart-sdk/bin/dart', [ + '--enable-asserts', + 'analyze_snippet_code.dart', + '--no-include-dart-ui', + 'test/analyze-snippet-code-test-input', + ]); + expect(process.stdout, isEmpty); + final List stderrLines = process.stderr.toString().split('\n'); + expect( + stderrLines.length, + stderrLines.toSet().length, + reason: 'found duplicates in $stderrLines', + ); + final List stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList(); + expect(stderrNoDescriptions, [ + ...expectedMainErrors, + 'Found 18 snippet code errors.', + 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', + '', // because we end with a newline, split gives us an extra blank line + ]); + expect(process.exitCode, 1); + }, + // TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413 + skip: true, + ); + + test( + 'Analyzes dart:ui code', + () { + final ProcessResult process = Process.runSync('../../bin/cache/dart-sdk/bin/dart', [ + '--enable-asserts', + 'analyze_snippet_code.dart', + '--dart-ui-location=test/analyze-snippet-code-test-dart-ui', + 'test/analyze-snippet-code-test-input', + ]); + expect(process.stdout, isEmpty); + final List stderrLines = process.stderr.toString().split('\n'); + expect( + stderrLines.length, + stderrLines.toSet().length, + reason: 'found duplicates in $stderrLines', + ); + final List stderrNoDescriptions = stderrLines.map(removeLintDescriptions).toList(); + expect(stderrNoDescriptions, [ + ...expectedUiErrors, + ...expectedMainErrors, + 'Found 23 snippet code errors.', + 'See the documentation at the top of dev/bots/analyze_snippet_code.dart for details.', + '', // because we end with a newline, split gives us an extra blank line + ]); + expect(process.exitCode, 1); + }, + // TODO(scheglov): Restore after landing Dart SDK changes, https://github.com/flutter/flutter/issues/154413 + skip: true, + ); +} diff --git a/flutter/dev/bots/test/analyze_test.dart b/flutter/dev/bots/test/analyze_test.dart new file mode 100644 index 00000000..5ea05590 --- /dev/null +++ b/flutter/dev/bots/test/analyze_test.dart @@ -0,0 +1,408 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:path/path.dart' as path; + +import '../analyze.dart'; +import '../custom_rules/analyze.dart'; +import '../custom_rules/no_double_clamp.dart'; +import '../custom_rules/no_stop_watches.dart'; +import '../custom_rules/render_box_intrinsics.dart'; +import '../utils.dart'; +import 'common.dart'; + +typedef AsyncVoidCallback = Future Function(); + +Future capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async { + final StringBuffer buffer = StringBuffer(); + final PrintCallback oldPrint = print; + try { + print = (Object? line) { + buffer.writeln(line); + }; + await callback(); + expect( + hasError, + shouldHaveErrors, + reason: + buffer.isEmpty + ? '(No output to report.)' + : hasError + ? 'Unexpected errors:\n$buffer' + : 'Unexpected success:\n$buffer', + ); + } finally { + print = oldPrint; + resetErrorStatus(); + } + if (stdout.supportsAnsiEscapes) { + // Remove ANSI escapes when this test is running on a terminal. + return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), ''); + } else { + return buffer.toString(); + } +} + +void main() { + final String testRootPath = path.join('test', 'analyze-test-input', 'root'); + final String dartName = Platform.isWindows ? 'dart.exe' : 'dart'; + final String dartPath = path.canonicalize( + path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', dartName), + ); + final String testGenDefaultsPath = path.join('test', 'analyze-gen-defaults'); + + test('matchesErrorsInFile matcher basic test', () async { + final String result = await capture(() async { + foundError([ + 'meta.dart:5: error #1', + 'meta.dart:5: error #2', + 'meta.dart:6: error #3', + '', + 'Error summary', + ]); + }, shouldHaveErrors: true); + final File fixture = File(path.join(testRootPath, 'packages', 'foo', 'meta.dart')); + expect(result, matchesErrorsInFile(fixture, endsWith: ['', 'Error summary'])); + }); + + test('analyze.dart - verifyDeprecations', () async { + final String result = await capture( + () => verifyDeprecations(testRootPath, minimumMatches: 2), + shouldHaveErrors: true, + ); + final File fixture = File(path.join(testRootPath, 'packages', 'foo', 'deprecation.dart')); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + 'See: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes', + ], + ), + ); + }); + + test('analyze.dart - verifyGoldenTags', () async { + final List result = (await capture( + () => verifyGoldenTags(testRootPath, minimumMatches: 6), + shouldHaveErrors: true, + )).split('\n'); + const String noTag = + "Files containing golden tests must be tagged using @Tags(['reduced-test-set']) " + 'at the top of the file before import statements.'; + const String missingTag = + "Files containing golden tests must be tagged with 'reduced-test-set'."; + final List lines = + [ + '║ test/analyze-test-input/root/packages/foo/golden_missing_tag.dart: $missingTag', + '║ test/analyze-test-input/root/packages/foo/golden_no_tag.dart: $noTag', + ].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).toList(); + expect( + result.length, + 4 + lines.length, + reason: 'output had unexpected number of lines:\n${result.join('\n')}', + ); + expect( + result[0], + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + ); + expect(result.getRange(1, result.length - 3).toSet(), lines.toSet()); + expect( + result[result.length - 3], + '║ See: https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md', + ); + expect( + result[result.length - 2], + '╚═══════════════════════════════════════════════════════════════════════════════', + ); + expect(result[result.length - 1], ''); // trailing newline + }); + + test('analyze.dart - verifyNoMissingLicense', () async { + final String result = await capture( + () => verifyNoMissingLicense(testRootPath, checkMinimums: false), + shouldHaveErrors: true, + ); + final String file = 'test/analyze-test-input/root/packages/foo/foo.dart'.replaceAll( + '/', + Platform.isWindows ? r'\' : '/', + ); + expect( + result, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' + '║ The following file does not have the right license header for dart files:\n' + '║ $file\n' + '║ The expected license header is:\n' + '║ // Copyright 2014 The Flutter Authors. All rights reserved.\n' + '║ // Use of this source code is governed by a BSD-style license that can be\n' + '║ // found in the LICENSE file.\n' + '║ ...followed by a blank line.\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n', + ); + }); + + test('analyze.dart - verifyNoTrailingSpaces', () async { + final String result = await capture( + () => verifyNoTrailingSpaces(testRootPath, minimumMatches: 2), + shouldHaveErrors: true, + ); + final String lines = [ + '║ test/analyze-test-input/root/packages/foo/spaces.txt:5: trailing U+0020 space character', + '║ test/analyze-test-input/root/packages/foo/spaces.txt:9: trailing blank line', + ].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n'); + expect( + result, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' + '$lines\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n', + ); + }); + + test('analyze.dart - verifyRepositoryLinks', () async { + final String result = await capture( + () => verifyRepositoryLinks(testRootPath), + shouldHaveErrors: true, + ); + const String bannedBranch = 'master'; + final String file = + Platform.isWindows + ? r'test\analyze-test-input\root\packages\foo\bad_repository_links.dart' + : 'test/analyze-test-input/root/packages/foo/bad_repository_links.dart'; + final String lines = [ + '║ $file contains https://android.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://chromium.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://cs.opensource.google.com/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://dart.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://flutter.googlesource.com/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://source.chromium.org/+/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://github.com/flutter/flutter/tree/$bannedBranch/file1, which uses the banned "master" branch.', + '║ $file contains https://raw.githubusercontent.com/flutter/flutter/blob/$bannedBranch/file1, which uses the banned "master" branch.', + '║ Change the URLs above to the expected pattern by using the "main" branch if it exists, otherwise adding the repository to the list of exceptions in analyze.dart.', + ].join('\n'); + expect( + result, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' + '$lines\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n', + ); + }); + + test('analyze.dart - verifyNoBinaries - positive', () async { + final String result = await capture( + () => verifyNoBinaries( + testRootPath, + legacyBinaries: {const Hash256(0x39A050CD69434936, 0, 0, 0)}, + ), + shouldHaveErrors: !Platform.isWindows, + ); + if (!Platform.isWindows) { + expect( + result, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' + '║ test/analyze-test-input/root/packages/foo/serviceaccount.enc:0: file is not valid UTF-8\n' + '║ All files in this repository must be UTF-8. In particular, images and other binaries\n' + '║ must not be checked into this repository. This is because we are very sensitive to the\n' + '║ size of the repository as it is distributed to all our developers. If you have a binary\n' + '║ to which you need access, you should consider how to fetch it from another repository;\n' + '║ for example, the "assets-for-api-docs" repository is used for images in API docs.\n' + '║ To add assets to flutter_tools templates, see the instructions in the wiki:\n' + '║ https://github.com/flutter/flutter/blob/main/docs/tool/Managing-template-image-assets.md\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n', + ); + } + }); + + test('analyze.dart - verifyInternationalizations - comparison fails', () async { + final String result = await capture( + () => verifyInternationalizations(testRootPath, dartPath), + shouldHaveErrors: true, + ); + final String genLocalizationsScript = path.join( + 'dev', + 'tools', + 'localization', + 'bin', + 'gen_localizations.dart', + ); + expect(result, contains('$dartName $genLocalizationsScript --cupertino')); + expect(result, contains('$dartName $genLocalizationsScript --material')); + final String generatedFile = path.join( + testRootPath, + 'packages', + 'flutter_localizations', + 'lib', + 'src', + 'l10n', + 'generated_material_localizations.dart', + ); + expect( + result, + contains( + 'The contents of $generatedFile are different from that produced by gen_localizations.', + ), + ); + expect( + result, + contains(r'Did you forget to run gen_localizations.dart after updating a .arb file?'), + ); + }); + + test('analyze.dart - verifyNoBinaries - negative', () async { + await capture( + () => verifyNoBinaries( + testRootPath, + legacyBinaries: { + const Hash256( + 0xA8100AE6AA1940D0, + 0xB663BB31CD466142, + 0xEBBDBD5187131B92, + 0xD93818987832EB89, + ), // sha256("\xff") + const Hash256(0x155644D3F13D98BF, 0, 0, 0), + }, + ), + ); + }); + + test('analyze.dart - verifyNullInitializedDebugExpensiveFields', () async { + final String result = await capture( + () => verifyNullInitializedDebugExpensiveFields(testRootPath, minimumMatches: 1), + shouldHaveErrors: true, + ); + + final File fixture = File(path.join(testRootPath, 'packages', 'flutter', 'lib', 'bar.dart')); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + '', + 'Fields annotated with @_debugOnly must null initialize,', + 'to ensure both the field and initializer are removed from profile/release mode.', + 'These fields should be written as:', + 'field = kDebugMode ? : null;', + ], + ), + ); + }); + + test('analyze.dart - verifyTabooDocumentation', () async { + final String result = await capture( + () => verifyTabooDocumentation(testRootPath, minimumMatches: 1), + shouldHaveErrors: true, + ); + + final File fixture = File( + path.join(testRootPath, 'packages', 'flutter', 'lib', 'taboo_words.dart'), + ); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + '', + 'Avoid the word "simply" in documentation. See https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#use-the-passive-voice-recommend-do-not-require-never-say-things-are-simple for details.', + 'In many cases these words can be omitted without loss of generality; in other cases it may require a bit of rewording to avoid implying that the task is simple.', + 'Similarly, avoid using "note:" or the phrase "note that". See https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#avoid-empty-prose for details.', + ], + ), + ); + }); + + test('analyze.dart - clampDouble', () async { + final String result = await capture( + () => analyzeWithRules( + testRootPath, + [noDoubleClamp], + includePaths: ['packages/flutter/lib'], + ), + shouldHaveErrors: true, + ); + + final File fixture = File( + path.join(testRootPath, 'packages', 'flutter', 'lib', 'double_clamp.dart'), + ); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + '', // empty line before the last sentence. + 'For performance reasons, we use a custom "clampDouble" function instead of using "double.clamp".', + ], + ), + ); + }); + + test('analyze.dart - stopwatch', () async { + final String result = await capture( + () => analyzeWithRules( + testRootPath, + [noStopwatches], + includePaths: ['packages/flutter/lib'], + ), + shouldHaveErrors: true, + ); + + final File fixture = File( + path.join(testRootPath, 'packages', 'flutter', 'lib', 'stopwatch.dart'), + ); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + '', + 'Stopwatches introduce flakes by falling out of sync with the FakeAsync used in testing.', + 'A Stopwatch that stays in sync with FakeAsync is available through the Gesture or Test bindings, through samplingClock.', + ], + ), + ); + }); + + test('analyze.dart - RenderBox intrinsics', () async { + final String result = await capture( + () => analyzeWithRules( + testRootPath, + [renderBoxIntrinsicCalculation], + includePaths: ['packages/flutter/lib'], + ), + shouldHaveErrors: true, + ); + final File fixture = File( + path.join(testRootPath, 'packages', 'flutter', 'lib', 'renderbox_intrinsics.dart'), + ); + expect( + result, + matchesErrorsInFile( + fixture, + endsWith: [ + '', + 'Typically the get* methods should be used to obtain the intrinsics of a RenderBox.', + ], + ), + ); + }); + + test('analyze.dart - verifyMaterialFilesAreUpToDateWithTemplateFiles', () async { + String result = await capture( + () => verifyMaterialFilesAreUpToDateWithTemplateFiles(testGenDefaultsPath, dartPath), + shouldHaveErrors: true, + ); + final String lines = [ + '║ chip.dart is not up-to-date with the token template file.', + ].map((String line) => line.replaceAll('/', Platform.isWindows ? r'\' : '/')).join('\n'); + const String errorStart = '╔═'; + result = result.substring(result.indexOf(errorStart)); + expect( + result, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════\n' + '$lines\n' + '║ See: https://github.com/flutter/flutter/blob/main/dev/tools/gen_defaults to update the token template files.\n' + '╚═══════════════════════════════════════════════════════════════════════════════\n', + ); + }); +} diff --git a/flutter/dev/bots/test/check_code_samples_test.dart b/flutter/dev/bots/test/check_code_samples_test.dart new file mode 100644 index 00000000..d5556b7f --- /dev/null +++ b/flutter/dev/bots/test/check_code_samples_test.dart @@ -0,0 +1,248 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:path/path.dart' as path; + +import '../check_code_samples.dart'; +import '../utils.dart'; +import 'common.dart'; + +void main() { + late SampleChecker checker; + late FileSystem fs; + late Directory examples; + late Directory packages; + late Directory dartUIPath; + late Directory flutterRoot; + + String getRelativePath(File file, [Directory? from]) { + from ??= flutterRoot; + return path.relative(file.absolute.path, from: flutterRoot.absolute.path); + } + + void writeLink({required File source, required File example, String? alternateLink}) { + final String link = alternateLink ?? ' ** See code in ${getRelativePath(example)} **'; + source + ..createSync(recursive: true) + ..writeAsStringSync(''' +/// Class documentation +/// +/// {@tool dartpad} +/// Example description +/// +///$link +/// {@end-tool} +'''); + } + + void buildTestFiles({ + bool missingLinks = false, + bool missingTests = false, + bool malformedLinks = false, + }) { + final Directory examplesLib = examples.childDirectory('lib').childDirectory('layer') + ..createSync(recursive: true); + final File fooExample = + examplesLib.childFile('foo_example.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// Example for foo'); + final File barExample = + examplesLib.childFile('bar_example.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// Example for bar'); + if (missingLinks) { + examplesLib.childFile('missing_example.0.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// Example that is not linked'); + } + final Directory examplesTests = examples.childDirectory('test').childDirectory('layer') + ..createSync(recursive: true); + examplesTests.childFile('foo_example.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// test for foo example'); + if (!missingTests) { + examplesTests.childFile('bar_example.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// test for bar example'); + } + if (missingLinks) { + examplesTests.childFile('missing_example.0_test.dart') + ..createSync(recursive: true) + ..writeAsStringSync('// test for foo example'); + } + final Directory flutterPackage = packages + .childDirectory('flutter') + .childDirectory('lib') + .childDirectory('src')..createSync(recursive: true); + if (malformedLinks) { + writeLink( + source: flutterPackage.childDirectory('layer').childFile('foo.dart'), + example: fooExample, + alternateLink: '*See Code *', + ); + writeLink( + source: flutterPackage.childDirectory('layer').childFile('bar.dart'), + example: barExample, + alternateLink: ' ** See code examples/api/lib/layer/bar_example.0.dart **', + ); + } else { + writeLink( + source: flutterPackage.childDirectory('layer').childFile('foo.dart'), + example: fooExample, + ); + writeLink( + source: flutterPackage.childDirectory('layer').childFile('bar.dart'), + example: barExample, + ); + } + } + + setUp(() { + fs = MemoryFileSystem( + style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, + ); + // Get the root prefix of the current directory so that on Windows we get a + // correct root prefix. + flutterRoot = fs.directory( + path.join(path.rootPrefix(fs.currentDirectory.absolute.path), 'flutter sdk'), + )..createSync(recursive: true); + fs.currentDirectory = flutterRoot; + examples = flutterRoot.childDirectory('examples').childDirectory('api') + ..createSync(recursive: true); + packages = flutterRoot.childDirectory('packages')..createSync(recursive: true); + dartUIPath = flutterRoot + .childDirectory('bin') + .childDirectory('cache') + .childDirectory('pkg') + .childDirectory('sky_engine') + .childDirectory('lib')..createSync(recursive: true); + checker = SampleChecker( + examples: examples, + packages: packages, + dartUIPath: dartUIPath, + flutterRoot: flutterRoot, + filesystem: fs, + ); + }); + + test('check_code_samples.dart - checkCodeSamples catches missing links', () async { + buildTestFiles(missingLinks: true); + bool? success; + final String result = await capture(() async { + success = checker.checkCodeSamples(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following examples are not linked from any source file API doc comments:', + '║ examples/api/lib/layer/missing_example.0.dart', + '║ Either link them to a source file API doc comment, or remove them.', + '╚═══════════════════════════════════════════════════════════════════════════════', + ] + .map((String line) { + return line.replaceAll('/', Platform.isWindows ? r'\' : '/'); + }) + .join('\n'); + expect(result, equals('$lines\n')); + expect(success, equals(false)); + }); + + test('check_code_samples.dart - checkCodeSamples catches malformed links', () async { + buildTestFiles(malformedLinks: true); + bool? success; + final String result = await capture(() async { + success = checker.checkCodeSamples(); + }, shouldHaveErrors: true); + final bool isWindows = Platform.isWindows; + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following examples are not linked from any source file API doc comments:', + if (!isWindows) '║ examples/api/lib/layer/foo_example.0.dart', + if (!isWindows) '║ examples/api/lib/layer/bar_example.0.dart', + if (isWindows) r'║ examples\api\lib\layer\foo_example.0.dart', + if (isWindows) r'║ examples\api\lib\layer\bar_example.0.dart', + '║ Either link them to a source file API doc comment, or remove them.', + '╚═══════════════════════════════════════════════════════════════════════════════', + '╔═╡ERROR #2╞════════════════════════════════════════════════════════════════════', + '║ The following malformed links were found in API doc comments:', + if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/layer/foo.dart:6: ///*See Code *', + if (!isWindows) + '║ /flutter sdk/packages/flutter/lib/src/layer/bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **', + if (isWindows) + r'║ C:\flutter sdk\packages\flutter\lib\src\layer\foo.dart:6: ///*See Code *', + if (isWindows) + r'║ C:\flutter sdk\packages\flutter\lib\src\layer\bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **', + '║ Correct the formatting of these links so that they match the exact pattern:', + r"║ r'\*\* See code in (?.+) \*\*'", + '╚═══════════════════════════════════════════════════════════════════════════════', + ].join('\n'); + expect(result, equals('$lines\n')); + expect(success, equals(false)); + }); + + test('check_code_samples.dart - checkCodeSamples catches missing tests', () async { + buildTestFiles(missingTests: true); + bool? success; + final String result = await capture(() async { + success = checker.checkCodeSamples(); + }, shouldHaveErrors: true); + final String lines = [ + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + '║ The following example test files are missing:', + '║ examples/api/test/layer/bar_example.0_test.dart', + '╚═══════════════════════════════════════════════════════════════════════════════', + ] + .map((String line) { + return line.replaceAll('/', Platform.isWindows ? r'\' : '/'); + }) + .join('\n'); + expect(result, equals('$lines\n')); + expect(success, equals(false)); + }); + + test('check_code_samples.dart - checkCodeSamples succeeds', () async { + buildTestFiles(); + bool? success; + final String result = await capture(() async { + success = checker.checkCodeSamples(); + }); + expect(result, isEmpty); + expect(success, equals(true)); + }); +} + +typedef AsyncVoidCallback = Future Function(); + +Future capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async { + final StringBuffer buffer = StringBuffer(); + final PrintCallback oldPrint = print; + try { + print = (Object? line) { + buffer.writeln(line); + }; + await callback(); + expect( + hasError, + shouldHaveErrors, + reason: + buffer.isEmpty + ? '(No output to report.)' + : hasError + ? 'Unexpected errors:\n$buffer' + : 'Unexpected success:\n$buffer', + ); + } finally { + print = oldPrint; + resetErrorStatus(); + } + if (stdout.supportsAnsiEscapes) { + // Remove ANSI escapes when this test is running on a terminal. + return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), ''); + } else { + return buffer.toString(); + } +} diff --git a/flutter/dev/bots/test/ci_yaml_validation_test.dart b/flutter/dev/bots/test/ci_yaml_validation_test.dart new file mode 100644 index 00000000..fd514c13 --- /dev/null +++ b/flutter/dev/bots/test/ci_yaml_validation_test.dart @@ -0,0 +1,161 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('vm') +library; + +import 'dart:io' as io; + +import 'package:path/path.dart' as p; +import 'package:source_span/source_span.dart'; +import 'package:yaml/yaml.dart'; + +import './common.dart'; + +void main() { + final String flutterRoot = () { + io.Directory current = io.Directory.current; + while (!io.File(p.join(current.path, 'DEPS')).existsSync()) { + if (current.path == current.parent.path) { + fail( + 'Could not find flutter repository root (${io.Directory.current.path} -> ${current.path})', + ); + } + current = current.parent; + } + return current.path; + }(); + + group('framework', () { + final List<_CiYamlTarget> targets = _CiYamlTarget.parseAll(p.join(flutterRoot, '.ci.yaml')); + + for (final _CiYamlTarget target in targets) { + if (target.runIf == null || target.runIf!.isEmpty) { + continue; + } + + setUp(() { + printOnFailure(target.span.message('One or more errors occurred validating')); + }); + + group(target.name, () { + test('must include .ci.yaml', () { + expect( + target.runIf, + contains('.ci.yaml'), + reason: '.ci.yaml inclusion means changes to the runIfs will trigger presubmit tests.', + ); + }); + + test('must include DEPS', () { + expect( + target.runIf, + contains('DEPS'), + reason: 'DEPS updates (including the Dart SDK) mean presubmit tests must be run.', + ); + }); + + test('must include the engine sources', () { + expect( + target.runIf, + contains('engine/**'), + reason: 'Engine updates means framework presubmit tests must be run.', + ); + }); + }); + } + }); + + group('engine', () { + final List<_CiYamlTarget> targets = _CiYamlTarget.parseAll( + p.join(flutterRoot, 'engine', 'src', 'flutter', '.ci.yaml'), + ); + + for (final _CiYamlTarget target in targets) { + if (target.runIf == null || target.runIf!.isEmpty) { + continue; + } + + setUp(() { + printOnFailure(target.span.message('One or more errors occurred validating')); + }); + + group(target.name, () { + test('must include .ci.yaml', () { + expect( + target.runIf, + contains('engine/src/flutter/.ci.yaml'), + reason: '.ci.yaml inclusion means changes to the runIfs will trigger presubmit tests.', + ); + }); + + test('must include DEPS', () { + expect( + target.runIf, + contains('DEPS'), + reason: 'DEPS updates (including the Dart SDK) mean presubmit tests must be run.', + ); + }); + }); + } + }); +} + +/// A minimal representation of an ostensibly well-formatted `.ci.yaml` file. +/// +/// Due to the repository setup, it's not possible to reuse the existing +/// specifications of this file, and since the test case is only testing a +/// subset of the encoding, this class exposes only that subset. +/// +/// For a discussion leading to this design decision, see +/// . +/// +/// See also: +/// - [`scheduler.proto`][1], the schema definition of the file format. +/// - [`CI_YAML.md`][2], a human-authored description of the file format. +/// - [`ci_yaml.dart`][3], where validation is performed (in `flutter/cocoon`). +/// +/// [1]: https://github.com/flutter/cocoon/blob/main/app_dart/lib/src/model/proto/internal/scheduler.proto +/// [2]: https://github.com/flutter/cocoon/blob/main/CI_YAML.md +/// [3]: https://github.com/flutter/cocoon/blob/main/app_dart/lib/src/model/ci_yaml/ci_yaml.dart +final class _CiYamlTarget { + _CiYamlTarget({required this.name, required this.span, required this.runIf}); + + factory _CiYamlTarget.fromYamlMap(YamlMap map) { + return _CiYamlTarget( + name: map['name'] as String, + span: map.span, + runIf: () { + final YamlList? runIf = map['runIf'] as YamlList?; + if (runIf == null) { + return null; + } + return runIf.cast().toList(); + }(), + ); + } + + /// Parses a list of targets from the provided `.ci.yaml` file [path]. + static List<_CiYamlTarget> parseAll(String path) { + final YamlDocument yamlDoc = loadYamlDocument( + io.File(path).readAsStringSync(), + sourceUrl: Uri.parse(path), + ); + + final YamlMap root = yamlDoc.contents as YamlMap; + final YamlList targets = root['targets'] as YamlList; + return targets.nodes.map((YamlNode node) { + return _CiYamlTarget.fromYamlMap(node as YamlMap); + }).toList(); + } + + /// Name of the target. + final String name; + + /// Where the target was parsed at in the `.ci.yaml` file. + final SourceSpan span; + + /// Which lines were present in a `runIf` block, if any. + final List? runIf; +} diff --git a/flutter/dev/bots/test/codesign_test.dart b/flutter/dev/bots/test/codesign_test.dart new file mode 100644 index 00000000..7f00767a --- /dev/null +++ b/flutter/dev/bots/test/codesign_test.dart @@ -0,0 +1,285 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('mac-os') +library; + +import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; +import '../suite_runners/run_verify_binaries_codesigned_tests.dart'; +import './common.dart'; + +void main() async { + const String flutterRoot = '/a/b/c'; + final List allExpectedFiles = + binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot); + final String allFilesStdout = allExpectedFiles.join('\n'); + final List allExpectedXcframeworks = signedXcframeworks(flutterRoot); + final String allXcframeworksStdout = allExpectedXcframeworks.join('\n'); + final List withEntitlements = binariesWithEntitlements(flutterRoot); + + group('verifyExist', () { + test('Not all files found', () async { + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['find', '/a/b/c/bin/cache', '-type', 'f'], + stdout: '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot', + ), + const FakeCommand( + command: [ + 'file', + '--mime-type', + '-b', + '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot', + ], + stdout: 'application/x-mach-binary', + ), + ]); + expect( + () async => verifyExist(flutterRoot, processManager: processManager), + throwsExceptionWith('Did not find all expected binaries!'), + ); + }); + + test('All files found', () async { + final List commandList = []; + final FakeCommand findCmd = FakeCommand( + command: const ['find', '$flutterRoot/bin/cache', '-type', 'f'], + stdout: allFilesStdout, + ); + commandList.add(findCmd); + for (final String expectedFile in allExpectedFiles) { + commandList.add( + FakeCommand( + command: ['file', '--mime-type', '-b', expectedFile], + stdout: 'application/x-mach-binary', + ), + ); + } + final ProcessManager processManager = FakeProcessManager.list(commandList); + await expectLater(verifyExist('/a/b/c', processManager: processManager), completes); + }); + }); + + group('find paths', () { + test('All binary files found', () async { + final List commandList = []; + final FakeCommand findCmd = FakeCommand( + command: const ['find', '$flutterRoot/bin/cache', '-type', 'f'], + stdout: allFilesStdout, + ); + commandList.add(findCmd); + for (final String expectedFile in allExpectedFiles) { + commandList.add( + FakeCommand( + command: ['file', '--mime-type', '-b', expectedFile], + stdout: 'application/x-mach-binary', + ), + ); + } + final ProcessManager processManager = FakeProcessManager.list(commandList); + final List foundFiles = await findBinaryPaths( + '$flutterRoot/bin/cache', + processManager: processManager, + ); + expect(foundFiles, allExpectedFiles); + }); + + test('Empty file list', () async { + final List commandList = []; + const FakeCommand findCmd = FakeCommand( + command: ['find', '$flutterRoot/bin/cache', '-type', 'f'], + ); + commandList.add(findCmd); + final ProcessManager processManager = FakeProcessManager.list(commandList); + final List foundFiles = await findBinaryPaths( + '$flutterRoot/bin/cache', + processManager: processManager, + ); + expect(foundFiles, []); + }); + + test('All xcframeworks files found', () async { + final List commandList = [ + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ), + ]; + final ProcessManager processManager = FakeProcessManager.list(commandList); + final List foundFiles = await findXcframeworksPaths( + '$flutterRoot/bin/cache', + processManager: processManager, + ); + expect(foundFiles, allExpectedXcframeworks); + }); + + group('isBinary', () { + test('isTrue', () async { + final List commandList = []; + const String fileToCheck = '/a/b/c/one.zip'; + const FakeCommand findCmd = FakeCommand( + command: ['file', '--mime-type', '-b', fileToCheck], + stdout: 'application/x-mach-binary', + ); + commandList.add(findCmd); + final ProcessManager processManager = FakeProcessManager.list(commandList); + final bool result = await isBinary(fileToCheck, processManager: processManager); + expect(result, isTrue); + }); + + test('isFalse', () async { + final List commandList = []; + const String fileToCheck = '/a/b/c/one.zip'; + const FakeCommand findCmd = FakeCommand( + command: ['file', '--mime-type', '-b', fileToCheck], + stdout: 'text/xml', + ); + commandList.add(findCmd); + final ProcessManager processManager = FakeProcessManager.list(commandList); + final bool result = await isBinary(fileToCheck, processManager: processManager); + expect(result, isFalse); + }); + }); + + group('hasExpectedEntitlements', () { + test('expected entitlements', () async { + final List commandList = []; + const String fileToCheck = '/a/b/c/one.zip'; + const FakeCommand codesignCmd = FakeCommand( + command: ['codesign', '--display', '--entitlements', ':-', fileToCheck], + ); + commandList.add(codesignCmd); + final ProcessManager processManager = FakeProcessManager.list(commandList); + final bool result = await hasExpectedEntitlements( + fileToCheck, + flutterRoot, + processManager: processManager, + ); + expect(result, isTrue); + }); + + test('unexpected entitlements', () async { + final List commandList = []; + const String fileToCheck = '/a/b/c/one.zip'; + const FakeCommand codesignCmd = FakeCommand( + command: ['codesign', '--display', '--entitlements', ':-', fileToCheck], + exitCode: 1, + ); + commandList.add(codesignCmd); + final ProcessManager processManager = FakeProcessManager.list(commandList); + final bool result = await hasExpectedEntitlements( + fileToCheck, + flutterRoot, + processManager: processManager, + ); + expect(result, isFalse); + }); + }); + }); + + group('verifySignatures', () { + test('succeeds if every binary is codesigned and has correct entitlements', () async { + final List commandList = []; + final FakeCommand findCmd = FakeCommand( + command: const ['find', '$flutterRoot/bin/cache', '-type', 'f'], + stdout: allFilesStdout, + ); + commandList.add(findCmd); + for (final String expectedFile in allExpectedFiles) { + commandList.add( + FakeCommand( + command: ['file', '--mime-type', '-b', expectedFile], + stdout: 'application/x-mach-binary', + ), + ); + } + commandList.add( + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ), + ); + for (final String expectedFile in allExpectedFiles) { + commandList.add(FakeCommand(command: ['codesign', '-vvv', expectedFile])); + if (withEntitlements.contains(expectedFile)) { + commandList.add( + FakeCommand( + command: ['codesign', '--display', '--entitlements', ':-', expectedFile], + stdout: expectedEntitlements.join('\n'), + ), + ); + } + } + + for (final String expectedXcframework in allExpectedXcframeworks) { + commandList.add(FakeCommand(command: ['codesign', '-vvv', expectedXcframework])); + } + final ProcessManager processManager = FakeProcessManager.list(commandList); + await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes); + }); + + test('fails if binaries do not have the right entitlements', () async { + final List commandList = []; + final FakeCommand findCmd = FakeCommand( + command: const ['find', '$flutterRoot/bin/cache', '-type', 'f'], + stdout: allFilesStdout, + ); + commandList.add(findCmd); + for (final String expectedFile in allExpectedFiles) { + commandList.add( + FakeCommand( + command: ['file', '--mime-type', '-b', expectedFile], + stdout: 'application/x-mach-binary', + ), + ); + } + commandList.add( + FakeCommand( + command: const [ + 'find', + '$flutterRoot/bin/cache', + '-type', + 'd', + '-name', + '*xcframework', + ], + stdout: allXcframeworksStdout, + ), + ); + for (final String expectedFile in allExpectedFiles) { + commandList.add(FakeCommand(command: ['codesign', '-vvv', expectedFile])); + if (withEntitlements.contains(expectedFile)) { + commandList.add( + FakeCommand( + command: ['codesign', '--display', '--entitlements', ':-', expectedFile], + ), + ); + } + } + for (final String expectedXcframework in allExpectedXcframeworks) { + commandList.add(FakeCommand(command: ['codesign', '-vvv', expectedXcframework])); + } + final ProcessManager processManager = FakeProcessManager.list(commandList); + + expect( + () async => verifySignatures(flutterRoot, processManager: processManager), + throwsExceptionWith('Test failed because files found with the wrong entitlements'), + ); + }); + }); +} diff --git a/flutter/dev/bots/test/common.dart b/flutter/dev/bots/test/common.dart new file mode 100644 index 00000000..67088acf --- /dev/null +++ b/flutter/dev/bots/test/common.dart @@ -0,0 +1,222 @@ +// Copyright 2014 The Flutter 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:io'; +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:test/test.dart'; + +import '../utils.dart'; + +export 'package:test/test.dart' hide isInstanceOf; + +/// A matcher that compares the type of the actual value to the type argument T. +TypeMatcher isInstanceOf() => isA(); + +void tryToDelete(Directory directory) { + // This should not be necessary, but it turns out that + // on Windows it's common for deletions to fail due to + // bogus (we think) "access denied" errors. + try { + directory.deleteSync(recursive: true); + } on FileSystemException catch (error) { + print('Failed to delete ${directory.path}: $error'); + } +} + +Matcher throwsExceptionWith(String messageSubString) { + return throwsA( + isA().having( + (Exception e) => e.toString(), + 'description', + contains(messageSubString), + ), + ); +} + +/// A matcher that matches error messages specified in the given `fixture` [File]. +/// +/// This matcher allows analyzer tests to specify the expected error messages +/// in the test fixture file, eliminating the need to hard code line numbers in +/// the test. +/// +/// The error messages must be printed using the [foundError] function. Each +/// error must start with the path to the file where the error resides, line +/// number (1-based instead of 0-based) of the error, and a short description, +/// delimited by colons (`:`). In the test fixture one could add the following +/// comment on the line that would produce the error, to tell matcher what to +/// expect: +/// `// ERROR: `. +Matcher matchesErrorsInFile(File fixture, {List endsWith = const []}) => + _ErrorMatcher(fixture, endsWith); + +class _ErrorMatcher extends Matcher { + _ErrorMatcher(this.file, this.endsWith) : bodyMatcher = _ErrorsInFileMatcher(file); + + static const String mismatchDescriptionKey = 'mismatchDescription'; + static final int _errorBoxWidth = math.max(15, (hasColor ? stdout.terminalColumns : 80) - 1); + static const String _title = 'ERROR #1'; + static final String _firstLine = '╔═╡$_title╞═${"═" * (_errorBoxWidth - 4 - _title.length)}'; + + static final String _lastLine = '╚${"═" * _errorBoxWidth}'; + static const String _linePrefix = '║ '; + + static bool mismatch(String mismatchDescription, Map matchState) { + matchState[mismatchDescriptionKey] = mismatchDescription; + return false; + } + + final List endsWith; + final File file; + final _ErrorsInFileMatcher bodyMatcher; + + @override + bool matches(dynamic item, Map matchState) { + if (item is! String) { + return mismatch('expected a String, got $item', matchState); + } + final List lines = item.split('\n'); + if (lines.isEmpty) { + return mismatch('the actual error message is empty', matchState); + } + if (lines.first != _firstLine) { + return mismatch( + 'the first line of the error message must be $_firstLine, got ${lines.first}', + matchState, + ); + } + if (lines.last.isNotEmpty) { + return mismatch( + 'missing newline at the end of the error message, got ${lines.last}', + matchState, + ); + } + if (lines[lines.length - 2] != _lastLine) { + return mismatch( + 'the last line of the error message must be $_lastLine, got ${lines[lines.length - 2]}', + matchState, + ); + } + final List body = lines.sublist(1, lines.length - 2); + final String? noprefix = body.firstWhereOrNull((String line) => !line.startsWith(_linePrefix)); + if (noprefix != null) { + return mismatch( + 'Line "$noprefix" should start with a prefix $_linePrefix..\n$lines', + matchState, + ); + } + + final List bodyWithoutPrefix = body + .map((String s) => s.substring(_linePrefix.length)) + .toList(growable: false); + final bool hasTailMismatch = IterableZip(>[ + bodyWithoutPrefix.reversed, + endsWith.reversed, + ]).any((List ss) => ss[0] != ss[1]); + if (bodyWithoutPrefix.length < endsWith.length || hasTailMismatch) { + return mismatch( + 'The error message should end with $endsWith.\n' + 'Actual error(s): $item', + matchState, + ); + } + return bodyMatcher.matches( + bodyWithoutPrefix.sublist(0, bodyWithoutPrefix.length - endsWith.length), + matchState, + ); + } + + @override + Description describe(Description description) { + return description.add('file ${file.path} contains the expected analyze errors.'); + } + + @override + Description describeMismatch( + dynamic item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { + final String? description = matchState[mismatchDescriptionKey] as String?; + return description != null + ? mismatchDescription.add(description) + : mismatchDescription.add('$matchState'); + } +} + +class _ErrorsInFileMatcher extends Matcher { + _ErrorsInFileMatcher(this.file); + + final File file; + + static final RegExp expectationMatcher = RegExp(r'// ERROR: (?.+)$'); + + static bool mismatch(String mismatchDescription, Map matchState) { + return _ErrorMatcher.mismatch(mismatchDescription, matchState); + } + + List<(int, String)> _expectedErrorMessagesFromFile(Map matchState) { + final List<(int, String)> returnValue = <(int, String)>[]; + for (final (int index, String line) in file.readAsLinesSync().indexed) { + final List expectations = + expectationMatcher.firstMatch(line)?.namedGroup('expectations')?.split(' // ERROR: ') ?? + []; + for (final String expectation in expectations) { + returnValue.add((index + 1, expectation)); + } + } + return returnValue; + } + + @override + bool matches(dynamic item, Map matchState) { + final List actualErrors = item as List; + final List<(int, String)> expectedErrors = _expectedErrorMessagesFromFile(matchState); + if (expectedErrors.length != actualErrors.length) { + return mismatch( + 'expected ${expectedErrors.length} error(s), got ${actualErrors.length}.\n' + 'expected lines with errors: ${expectedErrors.map(((int, String) x) => x.$1).toList()}\n' + 'actual error(s): \n>${actualErrors.join('\n>')}', + matchState, + ); + } + for (int i = 0; i < actualErrors.length; ++i) { + final String actualError = actualErrors[i]; + final (int lineNumber, String expectedError) = expectedErrors[i]; + switch (actualError.split(':')) { + case [final String _]: + return mismatch('No colons (":") found in the error message "$actualError".', matchState); + case [final String path, final String line, ...final List rest]: + if (!path.endsWith(file.uri.pathSegments.last)) { + return mismatch('"$path" does not match the file name of the source file.', matchState); + } + if (lineNumber.toString() != line) { + return mismatch( + 'could not find the expected error "$expectedError" at line $lineNumber', + matchState, + ); + } + final String actualMessage = rest.join(':').trimLeft(); + if (actualMessage != expectedError) { + return mismatch( + 'expected \n"$expectedError"\n at line $lineNumber, got \n"$actualMessage"', + matchState, + ); + } + + case _: + return mismatch( + 'failed to recognize a valid path from the error message "$actualError".', + matchState, + ); + } + } + return true; + } + + @override + Description describe(Description description) => description; +} diff --git a/flutter/dev/bots/test/post_process_docs_test.dart b/flutter/dev/bots/test/post_process_docs_test.dart new file mode 100644 index 00000000..1a15c5c3 --- /dev/null +++ b/flutter/dev/bots/test/post_process_docs_test.dart @@ -0,0 +1,142 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:file/memory.dart'; +import 'package:platform/platform.dart'; + +import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; +import '../post_process_docs.dart'; +import 'common.dart'; + +void main() async { + group('getBranch', () { + const String branchName = 'stable'; + test('getBranchName does not call git if env LUCI_BRANCH provided', () async { + final Platform platform = FakePlatform( + environment: {'LUCI_BRANCH': branchName}, + ); + final ProcessManager processManager = FakeProcessManager.empty(); + final String calculatedBranchName = await getBranchName( + platform: platform, + processManager: processManager, + ); + expect(calculatedBranchName, branchName); + }); + + test('getBranchName calls git if env LUCI_BRANCH not provided', () async { + final Platform platform = FakePlatform(environment: {}); + + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['git', 'status', '-b', '--porcelain'], + stdout: '## $branchName', + ), + ]); + + final String calculatedBranchName = await getBranchName( + platform: platform, + processManager: processManager, + ); + expect(calculatedBranchName, branchName); + expect(processManager, hasNoRemainingExpectations); + }); + test('getBranchName calls git if env LUCI_BRANCH is empty', () async { + final Platform platform = FakePlatform(environment: {'LUCI_BRANCH': ''}); + + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand( + command: ['git', 'status', '-b', '--porcelain'], + stdout: '## $branchName', + ), + ]); + final String calculatedBranchName = await getBranchName( + platform: platform, + processManager: processManager, + ); + expect(calculatedBranchName, branchName); + expect(processManager, hasNoRemainingExpectations); + }); + }); + + group('gitRevision', () { + test('Return short format', () async { + const String commitHash = 'e65f01793938e13cac2d321b9fcdc7939f9b2ea6'; + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commitHash), + ]); + final String revision = await gitRevision(processManager: processManager); + expect(processManager, hasNoRemainingExpectations); + expect(revision, commitHash.substring(0, 10)); + }); + + test('Return full length', () async { + const String commitHash = 'e65f01793938e13cac2d321b9fcdc7939f9b2ea6'; + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: ['git', 'rev-parse', 'HEAD'], stdout: commitHash), + ]); + final String revision = await gitRevision(fullLength: true, processManager: processManager); + expect(processManager, hasNoRemainingExpectations); + expect(revision, commitHash); + }); + }); + + group('runProcessWithValidation', () { + test('With no error', () async { + const List command = ['git', 'rev-parse', 'HEAD']; + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: command), + ]); + await runProcessWithValidations(command, '', processManager: processManager, verbose: false); + expect(processManager, hasNoRemainingExpectations); + }); + + test('With error', () async { + const List command = ['git', 'rev-parse', 'HEAD']; + final ProcessManager processManager = FakeProcessManager.list([ + const FakeCommand(command: command, exitCode: 1), + ]); + try { + await runProcessWithValidations( + command, + '', + processManager: processManager, + verbose: false, + ); + throw Exception('Exception was not thrown'); + } on CommandException catch (e) { + expect(e, isA()); + } + }); + }); + + group('generateFooter', () { + test('generated correctly', () async { + const String expectedContent = ''' +(function() { + var span = document.querySelector('footer>span'); + if (span) { + span.innerText = 'Flutter 3.0.0 • 2022-09-22 14:09 • abcdef • stable'; + } + var sourceLink = document.querySelector('a.source-link'); + if (sourceLink) { + sourceLink.href = sourceLink.href.replace('/master/', '/abcdef/'); + } +})(); +'''; + final MemoryFileSystem fs = MemoryFileSystem(); + final File footerFile = fs.file('/a/b/c/footer.js')..createSync(recursive: true); + await createFooter( + footerFile, + '3.0.0', + timestampParam: '2022-09-22 14:09', + branchParam: 'stable', + revisionParam: 'abcdef', + ); + final String content = await footerFile.readAsString(); + expect(content, expectedContent); + }); + }); +} diff --git a/flutter/dev/bots/test/prepare_package_test.dart b/flutter/dev/bots/test/prepare_package_test.dart new file mode 100644 index 00000000..4a963143 --- /dev/null +++ b/flutter/dev/bots/test/prepare_package_test.dart @@ -0,0 +1,989 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io' show ProcessResult; +import 'dart:typed_data'; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:path/path.dart' as path; +import 'package:platform/platform.dart' show FakePlatform, Platform; + +import '../../../packages/flutter_tools/test/src/fake_process_manager.dart'; +import '../prepare_package/archive_creator.dart'; +import '../prepare_package/archive_publisher.dart'; +import '../prepare_package/common.dart'; +import '../prepare_package/process_runner.dart'; +import 'common.dart'; + +void main() { + const String testRef = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeef'; + test('Throws on missing executable', () async { + // Uses a *real* process manager, since we want to know what happens if + // it can't find an executable. + final ProcessRunner processRunner = ProcessRunner(subprocessOutput: false); + expect( + expectAsync1((List commandLine) async { + return processRunner.runProcess(commandLine); + })(['this_executable_better_not_exist_2857632534321']), + throwsA(isA()), + ); + + await expectLater( + () => processRunner.runProcess(['this_executable_better_not_exist_2857632534321']), + throwsA( + isA().having( + (PreparePackageException error) => error.message, + 'message', + contains( + 'ProcessException: Failed to find "this_executable_better_not_exist_2857632534321" in the search path', + ), + ), + ), + ); + }); + for (final String platformName in [Platform.macOS, Platform.linux, Platform.windows]) { + final FakePlatform platform = FakePlatform( + operatingSystem: platformName, + environment: { + 'DEPOT_TOOLS': + platformName == Platform.windows ? path.join('D:', 'depot_tools') : '/depot_tools', + }, + ); + group('ProcessRunner for $platform', () { + test('Returns stdout', () async { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand(command: ['echo', 'test'], stdout: 'output', stderr: 'error'), + ]); + final ProcessRunner processRunner = ProcessRunner( + subprocessOutput: false, + platform: platform, + processManager: fakeProcessManager, + ); + final String output = await processRunner.runProcess(['echo', 'test']); + expect(output, equals('output')); + }); + test('Throws on process failure', () async { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand( + command: ['echo', 'test'], + stdout: 'output', + stderr: 'error', + exitCode: -1, + ), + ]); + final ProcessRunner processRunner = ProcessRunner( + subprocessOutput: false, + platform: platform, + processManager: fakeProcessManager, + ); + expect( + expectAsync1((List commandLine) async { + return processRunner.runProcess(commandLine); + })(['echo', 'test']), + throwsA(isA()), + ); + }); + }); + + group('ArchiveCreator for $platformName', () { + late ArchiveCreator creator; + late Directory tempDir; + Directory flutterDir; + Directory cacheDir; + late FakeProcessManager processManager; + late FileSystem fs; + final List> args = >[]; + final List> namedArgs = >[]; + late String flutter; + late String dart; + + Future fakeHttpReader(Uri url, {Map? headers}) { + return Future.value(Uint8List(0)); + } + + setUp(() async { + processManager = FakeProcessManager.list([]); + args.clear(); + namedArgs.clear(); + fs = MemoryFileSystem.test(); + tempDir = fs.systemTempDirectory; + flutterDir = fs.directory(path.join(tempDir.path, 'flutter')); + flutterDir.createSync(recursive: true); + cacheDir = fs.directory(path.join(flutterDir.path, 'bin', 'cache')); + cacheDir.createSync(recursive: true); + creator = ArchiveCreator( + tempDir, + tempDir, + testRef, + Branch.beta, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + httpReader: fakeHttpReader, + ); + flutter = path.join(creator.flutterRoot.absolute.path, 'bin', 'flutter'); + dart = path.join( + creator.flutterRoot.absolute.path, + 'bin', + 'cache', + 'dart-sdk', + 'bin', + 'dart', + ); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + test('sets PUB_CACHE properly', () async { + final String createBase = path.join(tempDir.absolute.path, 'create_'); + final String archiveName = path.join( + tempDir.absolute.path, + 'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}', + ); + + processManager.addCommands( + convertResults(?>{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null, + 'git reset --hard $testRef': null, + 'git remote set-url origin https://github.com/flutter/flutter.git': null, + 'git gc --prune=now --aggressive': null, + 'git describe --tags --exact-match $testRef': [ + ProcessResult(0, 0, 'v1.2.3', ''), + ], + '$flutter --version --machine': [ + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ], + '$dart --version': [ + ProcessResult( + 0, + 0, + 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', + '', + ), + ], + if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null, + '$flutter doctor': null, + '$flutter update-packages': null, + '$flutter precache': null, + '$flutter ide-config': null, + '$flutter create --template=app ${createBase}app': null, + '$flutter create --template=package ${createBase}package': null, + '$flutter create --template=plugin ${createBase}plugin': null, + '$flutter pub cache list': [ProcessResult(0, 0, '{"packages":{}}', '')], + 'git clean -f -x -- **/.packages': null, + 'git clean -f -x -- **/.dart_tool/': null, + if (platform.isMacOS) + 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': + null, + if (platform.isWindows) 'attrib -h .git': null, + if (platform.isWindows) + '7za a -tzip -mx=9 $archiveName flutter': null + else if (platform.isMacOS) + 'zip -r -9 --symlinks $archiveName flutter': null + else if (platform.isLinux) + 'tar cJf $archiveName --verbose flutter': null, + }), + ); + await creator.initializeRepo(); + await creator.createArchive(); + }); + + test('calls the right commands for archive output', () async { + final String createBase = path.join(tempDir.absolute.path, 'create_'); + final String archiveName = path.join( + tempDir.absolute.path, + 'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}', + ); + final Map?> calls = ?>{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null, + 'git reset --hard $testRef': null, + 'git remote set-url origin https://github.com/flutter/flutter.git': null, + 'git gc --prune=now --aggressive': null, + 'git describe --tags --exact-match $testRef': [ + ProcessResult(0, 0, 'v1.2.3', ''), + ], + '$flutter --version --machine': [ + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ], + '$dart --version': [ + ProcessResult( + 0, + 0, + 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', + '', + ), + ], + if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null, + '$flutter doctor': null, + '$flutter update-packages': null, + '$flutter precache': null, + '$flutter ide-config': null, + '$flutter create --template=app ${createBase}app': null, + '$flutter create --template=package ${createBase}package': null, + '$flutter create --template=plugin ${createBase}plugin': null, + '$flutter pub cache list': [ProcessResult(0, 0, '{"packages":{}}', '')], + 'git clean -f -x -- **/.packages': null, + 'git clean -f -x -- **/.dart_tool/': null, + if (platform.isMacOS) + 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': + null, + if (platform.isWindows) 'attrib -h .git': null, + if (platform.isWindows) + '7za a -tzip -mx=9 $archiveName flutter': null + else if (platform.isMacOS) + 'zip -r -9 --symlinks $archiveName flutter': null + else if (platform.isLinux) + 'tar cJf $archiveName --verbose flutter': null, + }; + processManager.addCommands(convertResults(calls)); + creator = ArchiveCreator( + tempDir, + tempDir, + testRef, + Branch.beta, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + httpReader: fakeHttpReader, + ); + await creator.initializeRepo(); + await creator.createArchive(); + }); + + test('adds the arch name to the archive for non-x64', () async { + final String createBase = path.join(tempDir.absolute.path, 'create_'); + final String archiveName = path.join( + tempDir.absolute.path, + 'flutter_${platformName}_arm64_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}', + ); + final Map?> calls = ?>{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null, + 'git reset --hard $testRef': null, + 'git remote set-url origin https://github.com/flutter/flutter.git': null, + 'git gc --prune=now --aggressive': null, + 'git describe --tags --exact-match $testRef': [ + ProcessResult(0, 0, 'v1.2.3', ''), + ], + '$flutter --version --machine': [ + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ], + '$dart --version': [ + ProcessResult( + 0, + 0, + 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_arm64"', + '', + ), + ], + if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null, + '$flutter doctor': null, + '$flutter update-packages': null, + '$flutter precache': null, + '$flutter ide-config': null, + '$flutter create --template=app ${createBase}app': null, + '$flutter create --template=package ${createBase}package': null, + '$flutter create --template=plugin ${createBase}plugin': null, + '$flutter pub cache list': [ProcessResult(0, 0, '{"packages":{}}', '')], + 'git clean -f -x -- **/.packages': null, + 'git clean -f -x -- **/.dart_tool/': null, + if (platform.isMacOS) + 'codesign -vvvv --check-notarization ${path.join(tempDir.path, 'flutter', 'bin', 'cache', 'dart-sdk', 'bin', 'dart')}': + null, + if (platform.isWindows) 'attrib -h .git': null, + if (platform.isWindows) + '7za a -tzip -mx=9 $archiveName flutter': null + else if (platform.isMacOS) + 'zip -r -9 --symlinks $archiveName flutter': null + else if (platform.isLinux) + 'tar cJf $archiveName --verbose flutter': null, + }; + processManager.addCommands(convertResults(calls)); + creator = ArchiveCreator( + tempDir, + tempDir, + testRef, + Branch.beta, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + httpReader: fakeHttpReader, + ); + await creator.initializeRepo(); + await creator.createArchive(); + }); + + test('throws when a command errors out', () async { + final Map> calls = >{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': [ + ProcessResult(0, 0, 'output1', ''), + ], + 'git reset --hard $testRef': [ProcessResult(0, -1, 'output2', '')], + }; + processManager.addCommands(convertResults(calls)); + expect(expectAsync0(creator.initializeRepo), throwsA(isA())); + }); + + test('non-strict mode calls the right commands', () async { + final String createBase = path.join(tempDir.absolute.path, 'create_'); + final String archiveName = path.join( + tempDir.absolute.path, + 'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}', + ); + final Map?> calls = ?>{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null, + 'git reset --hard $testRef': null, + 'git remote set-url origin https://github.com/flutter/flutter.git': null, + 'git gc --prune=now --aggressive': null, + 'git describe --tags --abbrev=0 $testRef': [ + ProcessResult(0, 0, 'v1.2.3', ''), + ], + '$flutter --version --machine': [ + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ], + '$dart --version': [ + ProcessResult( + 0, + 0, + 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', + '', + ), + ], + if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null, + '$flutter doctor': null, + '$flutter update-packages': null, + '$flutter precache': null, + '$flutter ide-config': null, + '$flutter create --template=app ${createBase}app': null, + '$flutter create --template=package ${createBase}package': null, + '$flutter create --template=plugin ${createBase}plugin': null, + '$flutter pub cache list': [ProcessResult(0, 0, '{"packages":{}}', '')], + 'git clean -f -x -- **/.packages': null, + 'git clean -f -x -- **/.dart_tool/': null, + if (platform.isWindows) 'attrib -h .git': null, + if (platform.isWindows) + '7za a -tzip -mx=9 $archiveName flutter': null + else if (platform.isMacOS) + 'zip -r -9 --symlinks $archiveName flutter': null + else if (platform.isLinux) + 'tar cJf $archiveName --verbose flutter': null, + }; + processManager.addCommands(convertResults(calls)); + creator = ArchiveCreator( + tempDir, + tempDir, + testRef, + Branch.beta, + fs: fs, + strict: false, + processManager: processManager, + subprocessOutput: false, + platform: platform, + httpReader: fakeHttpReader, + ); + await creator.initializeRepo(); + await creator.createArchive(); + }); + + test('fails if binary is not codesigned', () async { + final String createBase = path.join(tempDir.absolute.path, 'create_'); + final String archiveName = path.join( + tempDir.absolute.path, + 'flutter_${platformName}_v1.2.3-beta${platform.isLinux ? '.tar.xz' : '.zip'}', + ); + final ProcessResult codesignFailure = ProcessResult( + 1, + 1, + '', + 'code object is not signed at all', + ); + final String binPath = path.join( + tempDir.path, + 'flutter', + 'bin', + 'cache', + 'dart-sdk', + 'bin', + 'dart', + ); + final Map?> calls = ?>{ + 'git clone -b beta https://flutter.googlesource.com/mirrors/flutter': null, + 'git reset --hard $testRef': null, + 'git remote set-url origin https://github.com/flutter/flutter.git': null, + 'git gc --prune=now --aggressive': null, + 'git describe --tags --exact-match $testRef': [ + ProcessResult(0, 0, 'v1.2.3', ''), + ], + '$flutter --version --machine': [ + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ProcessResult(0, 0, '{"dartSdkVersion": "3.2.1"}', ''), + ], + '$dart --version': [ + ProcessResult( + 0, + 0, + 'Dart SDK version: 2.17.0-63.0.beta (beta) (Wed Jan 26 03:48:52 2022 -0800) on "${platformName}_x64"', + '', + ), + ], + if (platform.isWindows) '7za x ${path.join(tempDir.path, 'mingit.zip')}': null, + '$flutter doctor': null, + '$flutter update-packages': null, + '$flutter precache': null, + '$flutter ide-config': null, + '$flutter create --template=app ${createBase}app': null, + '$flutter create --template=package ${createBase}package': null, + '$flutter create --template=plugin ${createBase}plugin': null, + '$flutter pub cache list': [ProcessResult(0, 0, '{"packages":{}}', '')], + 'git clean -f -x -- **/.packages': null, + 'git clean -f -x -- **/.dart_tool/': null, + if (platform.isMacOS) + 'codesign -vvvv --check-notarization $binPath': [codesignFailure], + if (platform.isWindows) 'attrib -h .git': null, + if (platform.isWindows) + '7za a -tzip -mx=9 $archiveName flutter': null + else if (platform.isMacOS) + 'zip -r -9 --symlinks $archiveName flutter': null + else if (platform.isLinux) + 'tar cJf $archiveName flutter': null, + }; + processManager.addCommands(convertResults(calls)); + creator = ArchiveCreator( + tempDir, + tempDir, + testRef, + Branch.beta, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + httpReader: fakeHttpReader, + ); + await creator.initializeRepo(); + + await expectLater( + () => creator.createArchive(), + throwsA( + isA().having( + (PreparePackageException exception) => exception.message, + 'message', + contains('The binary $binPath was not codesigned!'), + ), + ), + ); + }, skip: !platform.isMacOS); // [intended] codesign is only available on macOS + }); + + group('ArchivePublisher for $platformName', () { + late FakeProcessManager processManager; + late Directory tempDir; + late FileSystem fs; + final String gsutilCall = + platform.isWindows + ? 'python3 ${path.join("D:", "depot_tools", "gsutil.py")}' + : 'python3 ${path.join("/", "depot_tools", "gsutil.py")}'; + final String releasesName = 'releases_$platformName.json'; + final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip'; + final String archiveMime = platform.isLinux ? 'application/x-gtar' : 'application/zip'; + final String gsArchivePath = + 'gs://flutter_infra_release/releases/stable/$platformName/$archiveName'; + + setUp(() async { + fs = MemoryFileSystem.test( + style: platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix, + ); + processManager = FakeProcessManager.list([]); + tempDir = fs.systemTempDirectory.createTempSync('flutter_prepage_package_test.'); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + test('calls the right processes', () async { + final String archivePath = path.join(tempDir.absolute.path, archiveName); + final String jsonPath = path.join(tempDir.absolute.path, releasesName); + final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName'; + final String releasesJson = ''' +{ + "base_url": "https://storage.googleapis.com/flutter_infra_release/releases", + "current_release": { + "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2", + "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0" + }, + "releases": [ + { + "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0", + "channel": "beta", + "version": "v0.2.3", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip", + "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8", + "dart_sdk_arch": "x64" + }, + { + "hash": "b9bd51cc36b706215915711e580851901faebb40", + "channel": "beta", + "version": "v0.2.2", + "release_date": "2018-03-16T18:48:13.375013Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip", + "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b", + "dart_sdk_arch": "x64" + }, + { + "hash": "$testRef", + "channel": "stable", + "version": "v0.0.0", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip", + "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd", + "dart_sdk_arch": "x64" + } + ] +} +'''; + fs.file(jsonPath).writeAsStringSync(releasesJson); + fs.file(archivePath).writeAsStringSync('archive contents'); + final Map?> calls = ?>{ + // This process fails because the file does NOT already exist + '$gsutilCall -- cp $gsJsonPath $jsonPath': null, + '$gsutilCall -- stat $gsArchivePath': [ProcessResult(0, 1, '', '')], + '$gsutilCall -- rm $gsArchivePath': null, + '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, + '$gsutilCall -- rm $gsJsonPath': null, + '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': + null, + }; + processManager.addCommands(convertResults(calls)); + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + outputFile.createSync(); + assert(tempDir.existsSync()); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'x64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + assert(tempDir.existsSync()); + await publisher.generateLocalMetadata(); + await publisher.publishArchive(); + + final File releaseFile = fs.file(jsonPath); + expect(releaseFile.existsSync(), isTrue); + final String contents = releaseFile.readAsStringSync(); + // Make sure new data is added. + expect(contents, contains('"hash": "$testRef"')); + expect(contents, contains('"channel": "stable"')); + expect(contents, contains('"archive": "stable/$platformName/$archiveName"')); + expect( + contents, + contains('"sha256": "f69f4865f861193a91d1c5544a894167a7137b788d10bac8edbf5d095f45cb4d"'), + ); + // Make sure existing entries are preserved. + expect(contents, contains('"hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0"')); + expect(contents, contains('"hash": "b9bd51cc36b706215915711e580851901faebb40"')); + expect(contents, contains('"channel": "beta"')); + expect(contents, contains('"channel": "beta"')); + // Make sure old matching entries are removed. + expect(contents, isNot(contains('v0.0.0'))); + final Map jsonData = json.decode(contents) as Map; + final List releases = jsonData['releases'] as List; + expect(releases.length, equals(3)); + // Make sure the new entry is first (and hopefully it takes less than a + // minute to go from publishArchive above to this line!). + expect( + DateTime.now().difference( + DateTime.parse((releases[0] as Map)['release_date'] as String), + ), + lessThan(const Duration(minutes: 1)), + ); + const JsonEncoder encoder = JsonEncoder.withIndent(' '); + expect(contents, equals(encoder.convert(jsonData))); + }); + + test('contains Dart SDK version info', () async { + final String archivePath = path.join(tempDir.absolute.path, archiveName); + final String jsonPath = path.join(tempDir.absolute.path, releasesName); + final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName'; + final String releasesJson = ''' +{ + "base_url": "https://storage.googleapis.com/flutter_infra_release/releases", + "current_release": { + "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2", + "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0" + }, + "releases": [ + { + "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0", + "channel": "beta", + "version": "v0.2.3", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip", + "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8" + }, + { + "hash": "b9bd51cc36b706215915711e580851901faebb40", + "channel": "beta", + "version": "v0.2.2", + "release_date": "2018-03-16T18:48:13.375013Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip", + "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b" + }, + { + "hash": "$testRef", + "channel": "stable", + "version": "v0.0.0", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip", + "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd" + } + ] +} +'''; + fs.file(jsonPath).writeAsStringSync(releasesJson); + fs.file(archivePath).writeAsStringSync('archive contents'); + final Map?> calls = ?>{ + // This process fails because the file does NOT already exist + '$gsutilCall -- cp $gsJsonPath $jsonPath': null, + '$gsutilCall -- stat $gsArchivePath': [ProcessResult(0, 1, '', '')], + '$gsutilCall -- rm $gsArchivePath': null, + '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, + '$gsutilCall -- rm $gsJsonPath': null, + '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': + null, + }; + processManager.addCommands(convertResults(calls)); + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + outputFile.createSync(); + assert(tempDir.existsSync()); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'x64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + assert(tempDir.existsSync()); + await publisher.generateLocalMetadata(); + await publisher.publishArchive(); + + final File releaseFile = fs.file(jsonPath); + expect(releaseFile.existsSync(), isTrue); + final String contents = releaseFile.readAsStringSync(); + expect(contents, contains('"dart_sdk_version": "3.2.1"')); + expect(contents, contains('"dart_sdk_arch": "x64"')); + }); + + test('Supports multiple architectures', () async { + final String archivePath = path.join(tempDir.absolute.path, archiveName); + final String jsonPath = path.join(tempDir.absolute.path, releasesName); + final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName'; + final String releasesJson = ''' +{ + "base_url": "https://storage.googleapis.com/flutter_infra_release/releases", + "current_release": { + "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2", + "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0" + }, + "releases": [ + { + "hash": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "channel": "stable", + "version": "v1.2.3", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip", + "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8", + "dart_sdk_arch": "x64" + } + ] +} +'''; + fs.file(jsonPath).writeAsStringSync(releasesJson); + fs.file(archivePath).writeAsStringSync('archive contents'); + final Map?> calls = ?>{ + // This process fails because the file does NOT already exist + '$gsutilCall -- cp $gsJsonPath $jsonPath': null, + '$gsutilCall -- stat $gsArchivePath': [ProcessResult(0, 1, '', '')], + '$gsutilCall -- rm $gsArchivePath': null, + '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, + '$gsutilCall -- rm $gsJsonPath': null, + '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': + null, + }; + processManager.addCommands(convertResults(calls)); + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + outputFile.createSync(); + assert(tempDir.existsSync()); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'arm64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + assert(tempDir.existsSync()); + await publisher.generateLocalMetadata(); + await publisher.publishArchive(); + + final File releaseFile = fs.file(jsonPath); + expect(releaseFile.existsSync(), isTrue); + final String contents = releaseFile.readAsStringSync(); + final Map releases = jsonDecode(contents) as Map; + expect((releases['releases'] as List).length, equals(2)); + }); + + test('updates base_url from old bucket to new bucket', () async { + final String archivePath = path.join(tempDir.absolute.path, archiveName); + final String jsonPath = path.join(tempDir.absolute.path, releasesName); + final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName'; + final String releasesJson = ''' +{ + "base_url": "https://storage.googleapis.com/flutter_infra_release/releases", + "current_release": { + "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2", + "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0" + }, + "releases": [ + { + "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0", + "channel": "beta", + "version": "v0.2.3", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip", + "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8" + }, + { + "hash": "b9bd51cc36b706215915711e580851901faebb40", + "channel": "beta", + "version": "v0.2.2", + "release_date": "2018-03-16T18:48:13.375013Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip", + "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b" + }, + { + "hash": "$testRef", + "channel": "stable", + "version": "v0.0.0", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip", + "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd" + } + ] +} +'''; + fs.file(jsonPath).writeAsStringSync(releasesJson); + fs.file(archivePath).writeAsStringSync('archive contents'); + final Map?> calls = ?>{ + // This process fails because the file does NOT already exist + '$gsutilCall -- cp $gsJsonPath $jsonPath': null, + '$gsutilCall -- stat $gsArchivePath': [ProcessResult(0, 1, '', '')], + '$gsutilCall -- rm $gsArchivePath': null, + '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, + '$gsutilCall -- rm $gsJsonPath': null, + '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': + null, + }; + processManager.addCommands(convertResults(calls)); + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + outputFile.createSync(); + assert(tempDir.existsSync()); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'x64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + assert(tempDir.existsSync()); + await publisher.generateLocalMetadata(); + await publisher.publishArchive(); + + final File releaseFile = fs.file(jsonPath); + expect(releaseFile.existsSync(), isTrue); + final String contents = releaseFile.readAsStringSync(); + final Map jsonData = json.decode(contents) as Map; + expect( + jsonData['base_url'], + 'https://storage.googleapis.com/flutter_infra_release/releases', + ); + }); + + test( + 'publishArchive throws if forceUpload is false and artifact already exists on cloud storage', + () async { + final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip'; + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'x64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + final Map> calls = >{ + // This process returns 0 because file already exists + '$gsutilCall -- stat $gsArchivePath': [ProcessResult(0, 0, '', '')], + }; + processManager.addCommands(convertResults(calls)); + expect(() async => publisher.publishArchive(), throwsException); + }, + ); + + test( + 'publishArchive does not throw if forceUpload is true and artifact already exists on cloud storage', + () async { + final String archiveName = platform.isLinux ? 'archive.tar.xz' : 'archive.zip'; + final File outputFile = fs.file(path.join(tempDir.absolute.path, archiveName)); + final ArchivePublisher publisher = ArchivePublisher( + tempDir, + testRef, + Branch.stable, + { + 'frameworkVersionFromGit': 'v1.2.3', + 'dartSdkVersion': '3.2.1', + 'dartTargetArch': 'x64', + }, + outputFile, + false, + fs: fs, + processManager: processManager, + subprocessOutput: false, + platform: platform, + ); + final String archivePath = path.join(tempDir.absolute.path, archiveName); + final String jsonPath = path.join(tempDir.absolute.path, releasesName); + final String gsJsonPath = 'gs://flutter_infra_release/releases/$releasesName'; + final String releasesJson = ''' +{ + "base_url": "https://storage.googleapis.com/flutter_infra_release/releases", + "current_release": { + "beta": "3ea4d06340a97a1e9d7cae97567c64e0569dcaa2", + "beta": "5a58b36e36b8d7aace89d3950e6deb307956a6a0" + }, + "releases": [ + { + "hash": "5a58b36e36b8d7aace89d3950e6deb307956a6a0", + "channel": "beta", + "version": "v0.2.3", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.3-beta.zip", + "sha256": "4fe85a822093e81cb5a66c7fc263f68de39b5797b294191b6d75e7afcc86aff8" + }, + { + "hash": "b9bd51cc36b706215915711e580851901faebb40", + "channel": "beta", + "version": "v0.2.2", + "release_date": "2018-03-16T18:48:13.375013Z", + "archive": "beta/$platformName/flutter_${platformName}_v0.2.2-beta.zip", + "sha256": "6073331168cdb37a4637a5dc073d6a7ef4e466321effa2c529fa27d2253a4d4b" + }, + { + "hash": "$testRef", + "channel": "stable", + "version": "v0.0.0", + "release_date": "2018-03-20T01:47:02.851729Z", + "archive": "stable/$platformName/flutter_${platformName}_v0.0.0-beta.zip", + "sha256": "5dd34873b3a3e214a32fd30c2c319a0f46e608afb72f0d450b2d621a6d02aebd" + } + ] +} +'''; + fs.file(jsonPath).writeAsStringSync(releasesJson); + fs.file(archivePath).writeAsStringSync('archive contents'); + final Map?> calls = ?>{ + '$gsutilCall -- cp $gsJsonPath $jsonPath': null, + '$gsutilCall -- rm $gsArchivePath': null, + '$gsutilCall -- -h Content-Type:$archiveMime cp $archivePath $gsArchivePath': null, + '$gsutilCall -- rm $gsJsonPath': null, + '$gsutilCall -- -h Content-Type:application/json -h Cache-Control:max-age=60 cp $jsonPath $gsJsonPath': + null, + }; + processManager.addCommands(convertResults(calls)); + assert(tempDir.existsSync()); + await publisher.generateLocalMetadata(); + await publisher.publishArchive(true); + }, + ); + }); + } +} + +List convertResults(Map?> results) { + final List commands = []; + for (final String key in results.keys) { + final List? candidates = results[key]; + final List args = key.split(' '); + if (candidates == null) { + commands.add(FakeCommand(command: args)); + } else { + for (final ProcessResult result in candidates) { + commands.add( + FakeCommand( + command: args, + exitCode: result.exitCode, + stderr: result.stderr.toString(), + stdout: result.stdout.toString(), + ), + ); + } + } + } + return commands; +} diff --git a/flutter/dev/bots/test/run_command_test.dart b/flutter/dev/bots/test/run_command_test.dart new file mode 100644 index 00000000..dea57dfd --- /dev/null +++ b/flutter/dev/bots/test/run_command_test.dart @@ -0,0 +1,65 @@ +// Copyright 2014 The Flutter 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:io' as io; + +import '../run_command.dart'; +import '../utils.dart'; + +import 'common.dart'; + +void main() { + // These tests only run on Linux. They test platform-agnostic code that is + // triggered by platform-sensitive code. To avoid having to complicate our + // test harness by using a mockable process manager, the tests rely on one + // platform's conventions (Linux having `sh`). The logic being tested is not + // so critical that it matters that we're only testing it on one platform. + + test('short output on runCommand failure', () async { + final List log = []; + final PrintCallback oldPrint = print; + print = log.add; + try { + await runCommand('/usr/bin/sh', ['-c', 'echo test; false']); + expect(log, [ + startsWith('RUNNING:'), + 'workingDirectory: null, executable: /usr/bin/sh, arguments: [-c, echo test; false]', + 'test', + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + startsWith('║ Command: '), + '║ Command exited with exit code 1 but expected zero exit code.', + startsWith('║ Working directory: '), + '║ stdout and stderr output:', + '║ test', + '║ ', + '╚═══════════════════════════════════════════════════════════════════════════════', + ]); + } finally { + print = oldPrint; + resetErrorStatus(); + } + }, skip: !io.Platform.isLinux); // [intended] See comments above. + + test('long output on runCommand failure', () async { + final List log = []; + final PrintCallback oldPrint = print; + print = log.add; + try { + await runCommand('/usr/bin/sh', ['-c', 'echo ${"meow" * 1024}; false']); + expect(log, [ + startsWith('RUNNING:'), + 'workingDirectory: null, executable: /usr/bin/sh, arguments: [-c, echo ${"meow" * 1024}; false]', + 'meow' * 1024, + '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', + startsWith('║ Command: '), + '║ Command exited with exit code 1 but expected zero exit code.', + startsWith('║ Working directory: '), + '╚═══════════════════════════════════════════════════════════════════════════════', + ]); + } finally { + print = oldPrint; + resetErrorStatus(); + } + }, skip: !io.Platform.isLinux); // [intended] See comments above. +} diff --git a/flutter/dev/bots/test/sdk_directory_has_space_test.dart b/flutter/dev/bots/test/sdk_directory_has_space_test.dart new file mode 100644 index 00000000..2ffd3de0 --- /dev/null +++ b/flutter/dev/bots/test/sdk_directory_has_space_test.dart @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:path/path.dart' as path; + +import 'common.dart'; + +void main() { + test('We are in a directory with a space in it', () async { + // The Flutter SDK should be in a directory with a space in it, to make sure + // our tools support that. + final String? expectedName = Platform.environment['FLUTTER_SDK_PATH_WITH_SPACE']; + expect(expectedName, 'flutter sdk'); + expect(expectedName, contains(' ')); + final List parts = path.split(Directory.current.absolute.path); + expect(parts.reversed.take(3), ['bots', 'dev', expectedName]); + }, skip: true); // https://github.com/flutter/flutter/issues/87285 +} diff --git a/flutter/dev/bots/test/test_test.dart b/flutter/dev/bots/test/test_test.dart new file mode 100644 index 00000000..10eb774c --- /dev/null +++ b/flutter/dev/bots/test/test_test.dart @@ -0,0 +1,233 @@ +// Copyright 2014 The Flutter 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:io' hide Platform; + +import 'package:collection/collection.dart'; +import 'package:file/file.dart' as fs; +import 'package:file/memory.dart'; +import 'package:path/path.dart' as path; +import 'package:process/process.dart'; + +import '../suite_runners/run_flutter_packages_tests.dart'; +import '../utils.dart'; +import 'common.dart'; + +/// Fails a test if the exit code of `result` is not the expected value. This +/// is favored over `expect(result.exitCode, expectedExitCode)` because this +/// will include the process result's stdio in the failure message. +void expectExitCode(ProcessResult result, int expectedExitCode) { + if (result.exitCode != expectedExitCode) { + fail( + 'Process ${result.pid} exited with the wrong exit code.\n' + '\n' + 'EXPECTED: exit code $expectedExitCode\n' + 'ACTUAL: exit code ${result.exitCode}\n' + '\n' + 'STDOUT:\n' + '${result.stdout}\n' + 'STDERR:\n' + '${result.stderr}', + ); + } +} + +void main() { + group('verifyVersion()', () { + late MemoryFileSystem fileSystem; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + }); + + test('passes for valid version strings', () async { + const List valid_versions = [ + '1.2.3', + '12.34.56', + '1.2.3.pre.1', + '1.2.3-4.5.pre', + '1.2.3-5.0.pre.12', + ]; + for (final String version in valid_versions) { + final File file = fileSystem.file('version'); + file.writeAsStringSync(version); + + expect( + await verifyVersion(file), + isNull, + reason: '$version is valid but verifyVersionFile said it was bad', + ); + } + }); + + test('fails for invalid version strings', () async { + const List invalid_versions = [ + '1.2.3.4', + '1.2.3.', + '1.2.pre.1', + '1.2.3-pre.1', + '1.2.3-pre.1+hotfix.1', + ' 1.2.3', + '1.2.3-hotfix.1', + ]; + for (final String version in invalid_versions) { + final File file = fileSystem.file('version'); + file.writeAsStringSync(version); + + expect( + await verifyVersion(file), + 'The version logic generated an invalid version string: "$version".', + reason: '$version is invalid but verifyVersionFile said it was fine', + ); + } + }); + }); + + group('flutter/packages version', () { + final MemoryFileSystem memoryFileSystem = MemoryFileSystem(); + final fs.File packagesVersionFile = memoryFileSystem.file( + path.join('bin', 'internal', 'flutter_packages.version'), + ); + const String kSampleHash = '592b5b27431689336fa4c721a099eedf787aeb56'; + setUpAll(() { + packagesVersionFile.createSync(recursive: true); + }); + + test('commit hash', () async { + packagesVersionFile.writeAsStringSync(kSampleHash); + final String actualHash = await getFlutterPackagesVersion( + flutterRoot: flutterRoot, + fileSystem: memoryFileSystem, + packagesVersionFile: packagesVersionFile.path, + ); + expect(actualHash, kSampleHash); + }); + + test('commit hash with newlines', () async { + packagesVersionFile.writeAsStringSync('\n$kSampleHash\n'); + final String actualHash = await getFlutterPackagesVersion( + flutterRoot: flutterRoot, + fileSystem: memoryFileSystem, + packagesVersionFile: packagesVersionFile.path, + ); + expect(actualHash, kSampleHash); + }); + }); + + group('test.dart script', () { + const ProcessManager processManager = LocalProcessManager(); + + Future runScript([ + Map? environment, + List otherArgs = const [], + ]) async { + final String dart = path.absolute( + path.join('..', '..', 'bin', 'cache', 'dart-sdk', 'bin', 'dart'), + ); + final ProcessResult scriptProcess = processManager.runSync([ + dart, + 'test.dart', + ...otherArgs, + ], environment: environment); + return scriptProcess; + } + + test('subshards tests correctly', () async { + // When updating this test, try to pick shard numbers that ensure we're checking + // that unequal test distributions don't miss tests. + ProcessResult result = await runScript({ + 'SHARD': kTestHarnessShardName, + 'SUBSHARD': '1_3', + }); + expectExitCode(result, 0); + expect(result.stdout, contains('Selecting subshard 1 of 3 (tests 1-3 of 9)')); + + result = await runScript({'SHARD': kTestHarnessShardName, 'SUBSHARD': '3_3'}); + expectExitCode(result, 0); + expect(result.stdout, contains('Selecting subshard 3 of 3 (tests 7-9 of 9)')); + }); + + test('exits with code 1 when SUBSHARD index greater than total', () async { + final ProcessResult result = await runScript({ + 'SHARD': kTestHarnessShardName, + 'SUBSHARD': '100_99', + }); + expectExitCode(result, 1); + expect(result.stdout, contains('Invalid subshard name')); + }); + + test('exits with code 255 when invalid SUBSHARD name', () async { + final ProcessResult result = await runScript({ + 'SHARD': kTestHarnessShardName, + 'SUBSHARD': 'invalid_name', + }); + expectExitCode(result, 255); + expect(result.stdout, contains('Invalid subshard name')); + }); + + test('--dry-run prints every test that would run', () async { + final ProcessResult result = await runScript({}, ['--dry-run']); + expectExitCode(result, 0); + expect(result.stdout, contains('|> bin/flutter')); + }, testOn: 'posix'); + }); + + test('selectTestsForSubShard distributes tests amongst subshards correctly', () async { + List makeTests(int count) => List.generate(count, (int index) => index); + + void testSubsharding(int testCount, int subshardCount) { + String failureReason(String reason) { + return 'Subsharding test failed for testCount=$testCount, subshardCount=$subshardCount.\n' + '$reason'; + } + + final List tests = makeTests(testCount); + final List> subshards = List>.generate(subshardCount, (int index) { + final int subShardIndex = index + 1; + final (int start, int end) = selectTestsForSubShard( + testCount: tests.length, + subShardIndex: subShardIndex, + subShardCount: subshardCount, + ); + return tests.sublist(start, end); + }); + + final List testedTests = subshards.flattened.toList(); + final Set deduped = Set.from(subshards.flattened); + expect( + testedTests, + hasLength(deduped.length), + reason: failureReason('Subshards may have had duplicate tests.'), + ); + expect( + testedTests, + unorderedEquals(tests), + reason: failureReason('One or more tests were not assigned to a subshard.'), + ); + + final int minimumTestsPerShard = (testCount / subshardCount).floor(); + for (int i = 0; i < subshards.length; i++) { + final int extraTestsInThisShard = subshards[i].length - minimumTestsPerShard; + expect( + extraTestsInThisShard, + isNonNegative, + reason: failureReason( + 'Subsharding uneven. Subshard ${i + 1} had too few tests: ${subshards[i].length}', + ), + ); + expect( + extraTestsInThisShard, + lessThanOrEqualTo(1), + reason: failureReason( + 'Subsharding uneven. Subshard ${i + 1} had too many tests: ${subshards[i].length}', + ), + ); + } + } + + testSubsharding(9, 3); + testSubsharding(25, 8); + testSubsharding(30, 15); + }); +} diff --git a/flutter/dev/bots/test/tool_subsharding_test.dart b/flutter/dev/bots/test/tool_subsharding_test.dart new file mode 100644 index 00000000..1e409b0a --- /dev/null +++ b/flutter/dev/bots/test/tool_subsharding_test.dart @@ -0,0 +1,98 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:file/memory.dart'; + +import '../tool_subsharding.dart'; +import 'common.dart'; + +void main() { + group('generateMetrics', () { + late MemoryFileSystem fileSystem; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + }); + + test('empty metrics', () async { + final File file = fileSystem.file('success_file'); + const String output = ''' + {"missing": "entry"} + {"other": true}'''; + file.writeAsStringSync(output); + final TestFileReporterResults result = TestFileReporterResults.fromFile(file); + expect(result.allTestSpecs, isEmpty); + }); + + test('have metrics', () async { + final File file = fileSystem.file('success_file'); + const String output = ''' + {"protocolVersion":"0.1.1","runnerVersion":"1.21.6","pid":93376,"type":"start","time":0} + {"suite":{"id":0,"platform":"vm","path":"test/general.shard/project_validator_result_test.dart"},"type":"suite","time":0} + {"count":1,"time":12,"type":"allSuites"} + {"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":4798} + {"test":{"id":4,"name":"ProjectValidatorResult success status","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":159,"column":16,"url":"file:///file","root_line":50,"root_column":5,"root_url":"file:///file"},"type":"testStart","time":4803} + {"testID":4,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":4837} + {"suite":{"id":1,"platform":"vm","path":"other_path"},"type":"suite","time":1000} + {"test":{"id":5,"name":"ProjectValidatorResult success status with warning","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":159,"column":16,"url":"file:///file","root_line":60,"root_column":5,"root_url":"file:///file"},"type":"testStart","time":4837} + {"testID":5,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":4839} + {"test":{"id":6,"name":"ProjectValidatorResult error status","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":159,"column":16,"url":"file:///file","root_line":71,"root_column":5,"root_url":"file:///file"},"type":"testStart","time":4839} + {"testID":6,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":4841} + {"group":{"id":7,"suiteID":0,"parentID":2,"name":"ProjectValidatorTask","metadata":{"skip":false,"skipReason":null},"testCount":1,"line":82,"column":3,"url":"file:///file"},"type":"group","time":4841} + {"test":{"id":8,"name":"ProjectValidatorTask error status","suiteID":0,"groupIDs":[2,7],"metadata":{"skip":false,"skipReason":null},"line":159,"column":16,"url":"file:///file","root_line":89,"root_column":5,"root_url":"file:///file"},"type":"testStart","time":4842} + {"testID":8,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":4860} + {"group":{"id":7,"suiteID":1,"parentID":2,"name":"ProjectValidatorTask","metadata":{"skip":false,"skipReason":null},"testCount":1,"line":82,"column":3,"url":"file:///file"},"type":"group","time":5000} + {"success":true,"type":"done","time":4870}'''; + file.writeAsStringSync(output); + final Map result = TestFileReporterResults.fromFile(file).allTestSpecs; + expect(result, contains(0)); + expect(result, contains(1)); + expect(result[0]!.path, 'test/general.shard/project_validator_result_test.dart'); + expect(result[0]!.milliseconds, 4841); + expect(result[1]!.path, 'other_path'); + expect(result[1]!.milliseconds, 4000); + }); + + test('missing success entry', () async { + final File file = fileSystem.file('success_file'); + const String output = ''' + {"suite":{"id":1,"platform":"vm","path":"other_path"},"type":"suite","time":1000} + {"group":{"id":7,"suiteID":1,"parentID":2,"name":"name","metadata":{"skip":false,"skipReason":null},"testCount":1,"line":82,"column":3,"url":"file:///file"},"type":"group","time":5000}'''; + file.writeAsStringSync(output); + final TestFileReporterResults result = TestFileReporterResults.fromFile(file); + expect(result.hasFailedTests, true); + }); + + test('has failed stack traces', () async { + final File file = fileSystem.file('success_file'); + const String output = ''' + {"protocolVersion":"0.1.1","runnerVersion":"1.22.1","pid":47372,"type":"start","time":0} + {"suite":{"id":0,"platform":"vm","path":"test/tool_subsharding_test.dart"},"type":"suite","time":0} + {"test":{"id":1,"name":"loading test/tool_subsharding_test.dart","suiteID":0,"groupIDs":[],"metadata":{"skip":false,"skipReason":null},"line":null,"column":null,"url":null},"type":"testStart","time":2} + {"count":1,"time":11,"type":"allSuites"} + {"testID":1,"result":"success","skipped":false,"hidden":true,"type":"testDone","time":1021} + {"group":{"id":2,"suiteID":0,"parentID":null,"name":"","metadata":{"skip":false,"skipReason":null},"testCount":3,"line":null,"column":null,"url":null},"type":"group","time":1026} + {"group":{"id":3,"suiteID":0,"parentID":2,"name":"generateMetrics","metadata":{"skip":false,"skipReason":null},"testCount":3,"line":13,"column":3,"url":"file:///Users/user/Documents/flutter/dev/bots/test/tool_subsharding_test.dart"},"type":"group","time":1027} + {"test":{"id":4,"name":"generateMetrics empty metrics","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":20,"column":5,"url":"file:///Users/user/Documents/flutter/dev/bots/test/tool_subsharding_test.dart"},"type":"testStart","time":1027} + {"testID":4,"error":"Expected: Actual: ","stackTrace":"package:test_api expect test/tool_subsharding_test.dart 28:7 main.. ","isFailure":true,"type":"error","time":1095} + {"testID":4,"result":"failure","skipped":false,"hidden":false,"type":"testDone","time":1096} + {"test":{"id":5,"name":"generateMetrics have metrics","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":31,"column":5,"url":"file:///Users/user/Documents/flutter/dev/bots/test/tool_subsharding_test.dart"},"type":"testStart","time":1097} + {"testID":5,"result":"success","skipped":false,"hidden":false,"type":"testDone","time":1103} + {"test":{"id":6,"name":"generateMetrics missing success entry","suiteID":0,"groupIDs":[2,3],"metadata":{"skip":false,"skipReason":null},"line":60,"column":5,"url":"file:///Users/user/Documents/flutter/dev/bots/test/tool_subsharding_test.dart"},"type":"testStart","time":1103} + {"testID":6,"error":"Expected: Actual: ","stackTrace":"package:test_api expect test/tool_subsharding_test.dart 68:7 main.. ","isFailure":true,"type":"error","time":1107} + {"testID":6,"result":"failure","skipped":false,"hidden":false,"type":"testDone","time":1107} + {"testID":6,"error":"my error","isFailure":true,"type":"error","time":1107} + {"success":false,"type":"done","time":1120}'''; + file.writeAsStringSync(output); + final TestFileReporterResults result = TestFileReporterResults.fromFile(file); + expect(result.hasFailedTests, true); + expect(result.errors.length == 3, true); + expect(result.errors[0].contains('Expected: Actual: '), true); + expect(result.errors[1].contains('Expected: Actual: '), true); + expect(result.errors[2].contains('my error'), true); + }); + }); +} diff --git a/flutter/dev/bots/tool_subsharding.dart b/flutter/dev/bots/tool_subsharding.dart new file mode 100644 index 00000000..c4731324 --- /dev/null +++ b/flutter/dev/bots/tool_subsharding.dart @@ -0,0 +1,97 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io'; + +class TestSpecs { + TestSpecs({required this.path, required this.startTime}); + + final String path; + int startTime; + int? _endTime; + + int get milliseconds => endTime - startTime; + + set endTime(int value) { + _endTime = value; + } + + int get endTime => _endTime ?? 0; + + String toJson() { + return json.encode({'path': path, 'runtime': milliseconds.toString()}); + } +} + +class TestFileReporterResults { + TestFileReporterResults._({ + required this.allTestSpecs, + required this.hasFailedTests, + required this.errors, + }); + + /// Intended to parse the output file of `dart test --file-reporter json:file_name + factory TestFileReporterResults.fromFile(File metrics) { + if (!metrics.existsSync()) { + throw Exception('${metrics.path} does not exist'); + } + + final Map testSpecs = {}; + bool hasFailedTests = true; + final List errors = []; + + for (final String metric in metrics.readAsLinesSync()) { + /// Using print within a test adds the printed content to the json file report + /// as \u0000 making the file parsing step fail. The content of the json file + /// is expected to be a json dictionary per line and the following line removes + /// all the additional content at the beginning of the line until it finds the + /// first opening curly bracket. + // TODO(godofredoc): remove when https://github.com/flutter/flutter/issues/145553 is fixed. + final String sanitizedMetric = metric.replaceAll(RegExp(r'$.*{'), '{'); + final Map entry = json.decode(sanitizedMetric) as Map; + if (entry.containsKey('suite')) { + final Map suite = entry['suite']! as Map; + addTestSpec(suite, entry['time']! as int, testSpecs); + } else if (isMetricDone(entry, testSpecs)) { + final Map group = entry['group']! as Map; + final int suiteID = group['suiteID']! as int; + addMetricDone(suiteID, entry['time']! as int, testSpecs); + } else if (entry.containsKey('error')) { + final String stackTrace = + entry.containsKey('stackTrace') ? entry['stackTrace']! as String : ''; + errors.add('${entry['error']}\n $stackTrace'); + } else if (entry.containsKey('success') && entry['success'] == true) { + hasFailedTests = false; + } + } + + return TestFileReporterResults._( + allTestSpecs: testSpecs, + hasFailedTests: hasFailedTests, + errors: errors, + ); + } + + final Map allTestSpecs; + final bool hasFailedTests; + final List errors; + + static void addTestSpec(Map suite, int time, Map allTestSpecs) { + allTestSpecs[suite['id']! as int] = TestSpecs(path: suite['path']! as String, startTime: time); + } + + static void addMetricDone(int suiteID, int time, Map allTestSpecs) { + final TestSpecs testSpec = allTestSpecs[suiteID]!; + testSpec.endTime = time; + } + + static bool isMetricDone(Map entry, Map allTestSpecs) { + if (entry.containsKey('group') && entry['type']! as String == 'group') { + final Map group = entry['group']! as Map; + return allTestSpecs.containsKey(group['suiteID']! as int); + } + return false; + } +} diff --git a/flutter/dev/bots/unpublish_package.dart b/flutter/dev/bots/unpublish_package.dart new file mode 100644 index 00000000..9b5ffd42 --- /dev/null +++ b/flutter/dev/bots/unpublish_package.dart @@ -0,0 +1,526 @@ +// Copyright 2014 The Flutter 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 removes published archives from the cloud storage and the +/// corresponding JSON metadata file that the website uses to determine what +/// releases are available. +/// +/// If asked to remove a release that is currently the release on that channel, +/// it will replace that release with the next most recent release on that +/// channel. +library; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io' hide Platform; + +import 'package:args/args.dart'; +import 'package:path/path.dart' as path; +import 'package:platform/platform.dart' show LocalPlatform, Platform; +import 'package:process/process.dart'; + +const String gsBase = 'gs://flutter_infra_release'; +const String releaseFolder = '/releases'; +const String gsReleaseFolder = '$gsBase$releaseFolder'; +const String baseUrl = 'https://storage.googleapis.com/flutter_infra_release'; + +/// Exception class for when a process fails to run, so we can catch +/// it and provide something more readable than a stack trace. +class UnpublishException implements Exception { + UnpublishException(this.message, [this.result]); + + final String message; + final ProcessResult? result; + int get exitCode => result?.exitCode ?? -1; + + @override + String toString() { + String output = runtimeType.toString(); + output += ': $message'; + final String stderr = result?.stderr as String? ?? ''; + if (stderr.isNotEmpty) { + output += ':\n$stderr'; + } + return output; + } +} + +enum Channel { dev, beta, stable } + +String getChannelName(Channel channel) { + return switch (channel) { + Channel.beta => 'beta', + Channel.dev => 'dev', + Channel.stable => 'stable', + }; +} + +Channel fromChannelName(String? name) { + return switch (name) { + 'beta' => Channel.beta, + 'dev' => Channel.dev, + 'stable' => Channel.stable, + _ => throw ArgumentError('Invalid channel name.'), + }; +} + +enum PublishedPlatform { linux, macos, windows } + +String getPublishedPlatform(PublishedPlatform platform) { + return switch (platform) { + PublishedPlatform.linux => 'linux', + PublishedPlatform.macos => 'macos', + PublishedPlatform.windows => 'windows', + }; +} + +PublishedPlatform fromPublishedPlatform(String name) { + return switch (name) { + 'linux' => PublishedPlatform.linux, + 'macos' => PublishedPlatform.macos, + 'windows' => PublishedPlatform.windows, + _ => throw ArgumentError('Invalid published platform name.'), + }; +} + +/// A helper class for classes that want to run a process, optionally have the +/// stderr and stdout reported as the process runs, and capture the stdout +/// properly without dropping any. +class ProcessRunner { + /// Creates a [ProcessRunner]. + /// + /// The [processManager], [subprocessOutput], and [platform] arguments must + /// not be null. + ProcessRunner({ + this.processManager = const LocalProcessManager(), + this.subprocessOutput = true, + this.defaultWorkingDirectory, + this.platform = const LocalPlatform(), + }) { + environment = Map.from(platform.environment); + } + + /// The platform to use for a starting environment. + final Platform platform; + + /// Set [subprocessOutput] to show output as processes run. Stdout from the + /// process will be printed to stdout, and stderr printed to stderr. + final bool subprocessOutput; + + /// Set the [processManager] in order to inject a test instance to perform + /// testing. + final ProcessManager processManager; + + /// Sets the default directory used when `workingDirectory` is not specified + /// to [runProcess]. + final Directory? defaultWorkingDirectory; + + /// The environment to run processes with. + late Map environment; + + /// Run the command and arguments in `commandLine` as a sub-process from + /// `workingDirectory` if set, or the [defaultWorkingDirectory] if not. Uses + /// [Directory.current] if [defaultWorkingDirectory] is not set. + /// + /// Set `failOk` if [runProcess] should not throw an exception when the + /// command completes with a non-zero exit code. + Future runProcess( + List commandLine, { + Directory? workingDirectory, + bool failOk = false, + }) async { + workingDirectory ??= defaultWorkingDirectory ?? Directory.current; + if (subprocessOutput) { + stderr.write('Running "${commandLine.join(' ')}" in ${workingDirectory.path}.\n'); + } + final List output = []; + final Completer stdoutComplete = Completer(); + final Completer stderrComplete = Completer(); + late Process process; + Future allComplete() async { + await stderrComplete.future; + await stdoutComplete.future; + return process.exitCode; + } + + try { + process = await processManager.start( + commandLine, + workingDirectory: workingDirectory.absolute.path, + environment: environment, + ); + process.stdout.listen((List event) { + output.addAll(event); + if (subprocessOutput) { + stdout.add(event); + } + }, onDone: () async => stdoutComplete.complete()); + if (subprocessOutput) { + process.stderr.listen((List event) { + stderr.add(event); + }, onDone: () async => stderrComplete.complete()); + } else { + stderrComplete.complete(); + } + } on ProcessException catch (e) { + final String message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' + 'failed with:\n$e'; + throw UnpublishException(message); + } on ArgumentError catch (e) { + final String message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} ' + 'failed with:\n$e'; + throw UnpublishException(message); + } + + final int exitCode = await allComplete(); + if (exitCode != 0 && !failOk) { + final String message = + 'Running "${commandLine.join(' ')}" in ${workingDirectory.path} failed'; + throw UnpublishException(message, ProcessResult(0, exitCode, null, 'returned $exitCode')); + } + return utf8.decoder.convert(output).trim(); + } +} + +class ArchiveUnpublisher { + ArchiveUnpublisher( + this.tempDir, + this.revisionsBeingRemoved, + this.channels, + this.platform, { + this.confirmed = false, + ProcessManager? processManager, + bool subprocessOutput = true, + }) : assert(revisionsBeingRemoved.length == 40), + metadataGsPath = '$gsReleaseFolder/${getMetadataFilename(platform)}', + _processRunner = ProcessRunner( + processManager: processManager ?? const LocalProcessManager(), + subprocessOutput: subprocessOutput, + ); + + final PublishedPlatform platform; + final String metadataGsPath; + final Set channels; + final Set revisionsBeingRemoved; + final bool confirmed; + final Directory tempDir; + final ProcessRunner _processRunner; + static String getMetadataFilename(PublishedPlatform platform) => + 'releases_${getPublishedPlatform(platform)}.json'; + + /// Remove the archive from Google Storage. + Future unpublishArchive() async { + final Map jsonData = await _loadMetadata(); + final List> releases = + (jsonData['releases'] as List).map>((dynamic entry) { + final Map mapEntry = entry as Map; + return mapEntry.cast(); + }).toList(); + final Map> paths = await _getArchivePaths(releases); + releases.removeWhere( + (Map value) => + revisionsBeingRemoved.contains(value['hash']) && + channels.contains(fromChannelName(value['channel'])), + ); + releases.sort((Map a, Map b) { + final DateTime aDate = DateTime.parse(a['release_date']!); + final DateTime bDate = DateTime.parse(b['release_date']!); + return bDate.compareTo(aDate); + }); + jsonData['releases'] = releases; + for (final Channel channel in channels) { + if (!revisionsBeingRemoved.contains( + (jsonData['current_release'] as Map)[getChannelName(channel)], + )) { + // Don't replace the current release if it's not one of the revisions we're removing. + continue; + } + final Map replacementRelease = releases.firstWhere( + (Map value) => value['channel'] == getChannelName(channel), + ); + (jsonData['current_release'] as Map)[getChannelName(channel)] = + replacementRelease['hash']; + print( + '${confirmed ? 'Reverting' : 'Would revert'} current ${getChannelName(channel)} ' + '${getPublishedPlatform(platform)} release to ${replacementRelease['hash']} (version ${replacementRelease['version']}).', + ); + } + await _cloudRemoveArchive(paths); + await _updateMetadata(jsonData); + } + + Future>> _getArchivePaths( + List> releases, + ) async { + final Set hashes = {}; + final Map> paths = >{}; + for (final Map revision in releases) { + final String hash = revision['hash']!; + final Channel channel = fromChannelName(revision['channel']); + hashes.add(hash); + if (revisionsBeingRemoved.contains(hash) && channels.contains(channel)) { + paths[channel] ??= {}; + paths[channel]![hash] = revision['archive']!; + } + } + final Set missingRevisions = revisionsBeingRemoved.difference( + hashes.intersection(revisionsBeingRemoved), + ); + if (missingRevisions.isNotEmpty) { + final bool plural = missingRevisions.length > 1; + throw UnpublishException( + 'Revision${plural ? 's' : ''} $missingRevisions ${plural ? 'are' : 'is'} not present in the server metadata.', + ); + } + return paths; + } + + Future> _loadMetadata() async { + final File metadataFile = File(path.join(tempDir.absolute.path, getMetadataFilename(platform))); + // Always run this, even in dry runs. + await _runGsUtil(['cp', metadataGsPath, metadataFile.absolute.path], confirm: true); + final String currentMetadata = metadataFile.readAsStringSync(); + if (currentMetadata.isEmpty) { + throw UnpublishException('Empty metadata received from server'); + } + + Map jsonData; + try { + jsonData = json.decode(currentMetadata) as Map; + } on FormatException catch (e) { + throw UnpublishException('Unable to parse JSON metadata received from cloud: $e'); + } + + return jsonData; + } + + Future _updateMetadata(Map jsonData) async { + // We can't just cat the metadata from the server with 'gsutil cat', because + // Windows wants to echo the commands that execute in gsutil.bat to the + // stdout when we do that. So, we copy the file locally and then read it + // back in. + final File metadataFile = File(path.join(tempDir.absolute.path, getMetadataFilename(platform))); + const JsonEncoder encoder = JsonEncoder.withIndent(' '); + metadataFile.writeAsStringSync(encoder.convert(jsonData)); + print( + '${confirmed ? 'Overwriting' : 'Would overwrite'} $metadataGsPath with contents of ${metadataFile.absolute.path}', + ); + await _cloudReplaceDest(metadataFile.absolute.path, metadataGsPath); + } + + Future _runGsUtil( + List args, { + Directory? workingDirectory, + bool failOk = false, + bool confirm = false, + }) async { + final List command = ['gsutil', '--', ...args]; + if (confirm) { + return _processRunner.runProcess(command, workingDirectory: workingDirectory, failOk: failOk); + } else { + print('Would run: ${command.join(' ')}'); + return ''; + } + } + + Future _cloudRemoveArchive(Map> paths) async { + final List files = []; + print('${confirmed ? 'Removing' : 'Would remove'} the following release archives:'); + for (final Channel channel in paths.keys) { + final Map hashes = paths[channel]!; + for (final String hash in hashes.keys) { + final String file = '$gsReleaseFolder/${hashes[hash]}'; + files.add(file); + print(' $file'); + } + } + await _runGsUtil(['rm', ...files], failOk: true, confirm: confirmed); + } + + Future _cloudReplaceDest(String src, String dest) async { + assert(dest.startsWith('gs:'), '_cloudReplaceDest must have a destination in cloud storage.'); + assert(!src.startsWith('gs:'), '_cloudReplaceDest must have a local source file.'); + // We often don't have permission to overwrite, but + // we have permission to remove, so that's what we do first. + await _runGsUtil(['rm', dest], failOk: true, confirm: confirmed); + String? mimeType; + if (dest.endsWith('.tar.xz')) { + mimeType = 'application/x-gtar'; + } + if (dest.endsWith('.zip')) { + mimeType = 'application/zip'; + } + if (dest.endsWith('.json')) { + mimeType = 'application/json'; + } + final List args = [ + // Use our preferred MIME type for the files we care about + // and let gsutil figure it out for anything else. + if (mimeType != null) ...['-h', 'Content-Type:$mimeType'], + ...['cp', src, dest], + ]; + return _runGsUtil(args, confirm: confirmed); + } +} + +void _printBanner(String message) { + final String banner = '*** $message ***'; + print('\n'); + print('*' * banner.length); + print(banner); + print('*' * banner.length); + print('\n'); +} + +/// Prepares a flutter git repo to be removed from the published cloud storage. +Future main(List rawArguments) async { + final List allowedChannelValues = + Channel.values.map((Channel channel) => getChannelName(channel)).toList(); + final List allowedPlatformNames = + PublishedPlatform.values + .map((PublishedPlatform platform) => getPublishedPlatform(platform)) + .toList(); + final ArgParser argParser = ArgParser(); + argParser.addOption( + 'temp_dir', + help: + 'A location where temporary files may be written. Defaults to a ' + 'directory in the system temp folder. If a temp_dir is not ' + 'specified, then by default a generated temporary directory will be ' + 'created, used, and removed automatically when the script exits.', + ); + argParser.addMultiOption( + 'revision', + help: + 'The Flutter git repo revisions to remove from the published site. ' + 'Must be full 40-character hashes. More than one may be specified, ' + 'either by giving the option more than once, or by giving a comma ' + 'separated list. Required.', + ); + argParser.addMultiOption( + 'channel', + allowed: allowedChannelValues, + help: + 'The Flutter channels to remove the archives corresponding to the ' + 'revisions given with --revision. More than one may be specified, ' + 'either by giving the option more than once, or by giving a ' + 'comma separated list. If not specified, then the archives from all ' + 'channels that a revision appears in will be removed.', + ); + argParser.addMultiOption( + 'platform', + allowed: allowedPlatformNames, + help: + 'The Flutter platforms to remove the archive from. May specify more ' + 'than one, either by giving the option more than once, or by giving a ' + 'comma separated list. If not specified, then the archives from all ' + 'platforms that a revision appears in will be removed.', + ); + argParser.addFlag( + 'confirm', + help: + 'If set, will actually remove the archive from Google Cloud Storage ' + 'upon successful execution of this script. Published archives will be ' + 'removed from this directory: $baseUrl$releaseFolder. This option ' + 'must be set to perform any action on the server, otherwise only a dry ' + 'run is performed.', + ); + argParser.addFlag('help', negatable: false, help: 'Print help for this command.'); + + final ArgResults parsedArguments = argParser.parse(rawArguments); + + if (parsedArguments['help'] as bool) { + print(argParser.usage); + exit(0); + } + + void errorExit(String message, {int exitCode = -1}) { + stderr.write('Error: $message\n\n'); + stderr.write('${argParser.usage}\n'); + exit(exitCode); + } + + final List revisions = parsedArguments['revision'] as List; + if (revisions.isEmpty) { + errorExit('Invalid argument: at least one --revision must be specified.'); + } + for (final String revision in revisions) { + if (revision.length != 40) { + errorExit( + 'Invalid argument: --revision "$revision" must be the entire hash, not just a prefix.', + ); + } + if (revision.contains(RegExp(r'[^a-fA-F0-9]'))) { + errorExit('Invalid argument: --revision "$revision" contains non-hex characters.'); + } + } + + final String tempDirArg = parsedArguments['temp_dir'] as String; + Directory tempDir; + bool removeTempDir = false; + if (tempDirArg.isEmpty) { + tempDir = Directory.systemTemp.createTempSync('flutter_package.'); + removeTempDir = true; + } else { + tempDir = Directory(tempDirArg); + if (!tempDir.existsSync()) { + errorExit("Temporary directory $tempDirArg doesn't exist."); + } + } + + if (!(parsedArguments['confirm'] as bool)) { + _printBanner( + 'This will be just a dry run. To actually perform the changes below, re-run with --confirm argument.', + ); + } + + final List channelArg = parsedArguments['channel'] as List; + final List channelOptions = channelArg.isNotEmpty ? channelArg : allowedChannelValues; + final Set channels = + channelOptions.map((String value) => fromChannelName(value)).toSet(); + final List platformArg = parsedArguments['platform'] as List; + final List platformOptions = platformArg.isNotEmpty ? platformArg : allowedPlatformNames; + final List platforms = + platformOptions + .map((String value) => fromPublishedPlatform(value)) + .toList(); + int exitCode = 0; + late String message; + late String stack; + try { + for (final PublishedPlatform platform in platforms) { + final ArchiveUnpublisher publisher = ArchiveUnpublisher( + tempDir, + revisions.toSet(), + channels, + platform, + confirmed: parsedArguments['confirm'] as bool, + ); + await publisher.unpublishArchive(); + } + } on UnpublishException catch (e, s) { + exitCode = e.exitCode; + message = e.message; + stack = s.toString(); + } catch (e, s) { + exitCode = -1; + message = e.toString(); + stack = s.toString(); + } finally { + if (removeTempDir) { + tempDir.deleteSync(recursive: true); + } + if (exitCode != 0) { + errorExit('$message\n$stack', exitCode: exitCode); + } + if (!(parsedArguments['confirm'] as bool)) { + _printBanner( + 'This was just a dry run. To actually perform the above changes, re-run with --confirm argument.', + ); + } + exit(0); + } +} diff --git a/flutter/dev/bots/utils.dart b/flutter/dev/bots/utils.dart new file mode 100644 index 00000000..613a6810 --- /dev/null +++ b/flutter/dev/bots/utils.dart @@ -0,0 +1,745 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:core' hide print; +import 'dart:io' as system show exit; +import 'dart:io' hide exit; +import 'dart:math' as math; + +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:analyzer/source/line_info.dart'; +import 'package:collection/collection.dart'; +import 'package:file/file.dart' as fs; +import 'package:file/local.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; + +import 'run_command.dart'; +import 'tool_subsharding.dart'; + +typedef ShardRunner = Future Function(); + +/// A function used to validate the output of a test. +/// +/// If the output matches expectations, the function shall return null. +/// +/// If the output does not match expectations, the function shall return an +/// appropriate error message. +typedef OutputChecker = String? Function(CommandResult); + +const Duration _quietTimeout = Duration( + minutes: 10, +); // how long the output should be hidden between calls to printProgress before just being verbose + +// If running from LUCI set to False. +final bool isLuci = Platform.environment['LUCI_CI'] == 'True'; +final bool hasColor = stdout.supportsAnsiEscapes && !isLuci; +final bool _isRandomizationOff = + bool.tryParse(Platform.environment['TEST_RANDOMIZATION_OFF'] ?? '') ?? false; + +final String bold = hasColor ? '\x1B[1m' : ''; // shard titles +final String red = hasColor ? '\x1B[31m' : ''; // errors +final String green = hasColor ? '\x1B[32m' : ''; // section titles, commands +final String yellow = + hasColor + ? '\x1B[33m' + : ''; // indications that a test was skipped (usually renders orange or brown) +final String cyan = hasColor ? '\x1B[36m' : ''; // paths +final String reverse = hasColor ? '\x1B[7m' : ''; // clocks +final String gray = + hasColor ? '\x1B[30m' : ''; // subtle decorative items (usually renders as dark gray) +final String white = hasColor ? '\x1B[37m' : ''; // last log line (usually renders as light gray) +final String reset = hasColor ? '\x1B[0m' : ''; + +final String exe = Platform.isWindows ? '.exe' : ''; +final String bat = Platform.isWindows ? '.bat' : ''; +final String flutterRoot = path.dirname(path.dirname(path.dirname(path.fromUri(Platform.script)))); +final String flutter = path.join(flutterRoot, 'bin', 'flutter$bat'); +final String dart = path.join(flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart$exe'); +final String pubCache = path.join(flutterRoot, '.pub-cache'); +final String engineVersionFile = path.join(flutterRoot, 'bin', 'internal', 'engine.version'); +final String luciBotId = Platform.environment['SWARMING_BOT_ID'] ?? ''; +final bool runningInDartHHHBot = + luciBotId.startsWith('luci-dart-') || luciBotId.startsWith('dart-tests-'); + +const String kShardKey = 'SHARD'; +const String kSubshardKey = 'SUBSHARD'; +const String kTestHarnessShardName = 'test_harness_tests'; + +/// Environment variables to override the local engine when running `pub test`, +/// if such flags are provided to `test.dart`. +final Map localEngineEnv = {}; + +/// The arguments to pass to `flutter test` (typically the local engine +/// configuration) -- prefilled with the arguments passed to test.dart. +final List flutterTestArgs = []; + +/// Whether execution should be simulated for debugging purposes. +/// +/// When `true`, calls to [runCommand] print to [io.stdout] instead of running +/// the process. This is useful for determining what an invocation of `test.dart` +/// _might_ due if not invoked with `--dry-run`, or otherwise determine what the +/// different test shards and sub-shards are configured as. +bool get dryRun => _dryRun ?? false; + +/// Switches [dryRun] to `true`. +/// +/// Expected to be called at most once during execution of a process. +void enableDryRun() { + if (_dryRun != null) { + throw StateError('Should only be called at most once'); + } + _dryRun = true; +} + +bool? _dryRun; + +const int kESC = 0x1B; +const int kOpenSquareBracket = 0x5B; +const int kCSIParameterRangeStart = 0x30; +const int kCSIParameterRangeEnd = 0x3F; +const int kCSIIntermediateRangeStart = 0x20; +const int kCSIIntermediateRangeEnd = 0x2F; +const int kCSIFinalRangeStart = 0x40; +const int kCSIFinalRangeEnd = 0x7E; + +String get redLine { + if (hasColor) { + return '$red${'━' * stdout.terminalColumns}$reset'; + } + return '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; +} + +String get clock { + final DateTime now = DateTime.now(); + return '$reverse▌' + '${now.hour.toString().padLeft(2, "0")}:' + '${now.minute.toString().padLeft(2, "0")}:' + '${now.second.toString().padLeft(2, "0")}' + '▐$reset'; +} + +String prettyPrintDuration(Duration duration) { + String result = ''; + final int minutes = duration.inMinutes; + if (minutes > 0) { + result += '${minutes}min '; + } + final int seconds = duration.inSeconds - minutes * 60; + final int milliseconds = duration.inMilliseconds - (seconds * 1000 + minutes * 60 * 1000); + result += '$seconds.${milliseconds.toString().padLeft(3, "0")}s'; + return result; +} + +typedef PrintCallback = void Function(Object? line); +typedef VoidCallback = void Function(); + +// Allow print() to be overridden, for tests. +// +// Files that import this library should not import `print` from dart:core +// and should not use dart:io's `stdout` or `stderr`. +// +// By default this hides log lines between `printProgress` calls unless a +// timeout expires or anything calls `foundError`. +// +// Also used to implement `--verbose` in test.dart. +PrintCallback print = _printQuietly; + +// Called by foundError and used to implement `--abort-on-error` in test.dart. +VoidCallback? onError; + +bool get hasError => _hasError; +bool _hasError = false; + +List> _errorMessages = >[]; + +final List _pendingLogs = []; +Timer? _hideTimer; // When this is null, the output is verbose. + +void foundError(List messages) { + if (dryRun) { + printProgress(messages.join('\n')); + return; + } + assert(messages.isNotEmpty); + // Make the error message easy to notice in the logs by + // wrapping it in a red box. + final int width = math.max(15, (hasColor ? stdout.terminalColumns : 80) - 1); + final String title = 'ERROR #${_errorMessages.length + 1}'; + print('$red╔═╡$bold$title$reset$red╞═${"═" * (width - 4 - title.length)}'); + for (final String message in messages.expand((String line) => line.split('\n'))) { + print('$red║$reset $message'); + } + print('$red╚${"═" * width}'); + // Normally, "print" actually prints to the log. To make the errors visible, + // and to include useful context, print the entire log up to this point, and + // clear it. Subsequent messages will continue to not be logged until there is + // another error. + _pendingLogs.forEach(_printLoudly); + _pendingLogs.clear(); + _errorMessages.add(messages); + _hasError = true; + onError?.call(); +} + +@visibleForTesting +void resetErrorStatus() { + _hasError = false; + _errorMessages.clear(); + _pendingLogs.clear(); + _hideTimer?.cancel(); + _hideTimer = null; +} + +Never reportSuccessAndExit(String message) { + _hideTimer?.cancel(); + _hideTimer = null; + print('$clock $message$reset'); + system.exit(0); +} + +Never reportErrorsAndExit(String message) { + _hideTimer?.cancel(); + _hideTimer = null; + print('$clock $message$reset'); + print(redLine); + print('${red}The error messages reported above are repeated here:$reset'); + final bool printSeparators = _errorMessages.any((List messages) => messages.length > 1); + if (printSeparators) { + print(' -- This line intentionally left blank -- '); + } + for (int index = 0; index < _errorMessages.length * 2 - 1; index += 1) { + if (index.isEven) { + _errorMessages[index ~/ 2].forEach(print); + } else if (printSeparators) { + print(' -- This line intentionally left blank -- '); + } + } + print(redLine); + print('You may find the errors by searching for "╡ERROR #" in the logs.'); + system.exit(1); +} + +void printProgress(String message) { + _pendingLogs.clear(); + _hideTimer?.cancel(); + _hideTimer = null; + print('$clock $message$reset'); + if (hasColor) { + // This sets up a timer to switch to verbose mode when the tests take too long, + // so that if a test hangs we can see the logs. + // (This is only supported with a color terminal. When the terminal doesn't + // support colors, the scripts just print everything verbosely, that way in + // CI there's nothing hidden.) + _hideTimer = Timer(_quietTimeout, () { + _hideTimer = null; + _pendingLogs.forEach(_printLoudly); + _pendingLogs.clear(); + }); + } +} + +final Pattern _lineBreak = RegExp(r'[\r\n]'); + +void _printQuietly(Object? message) { + // The point of this function is to avoid printing its output unless the timer + // has gone off in which case the function assumes verbose mode is active and + // prints everything. To show that progress is still happening though, rather + // than showing nothing at all, it instead shows the last line of output and + // keeps overwriting it. To do this in color mode, carefully measures the line + // of text ignoring color codes, which is what the parser below does. + if (_hideTimer != null) { + _pendingLogs.add(message.toString()); + String line = '$message'.trimRight(); + final int start = line.lastIndexOf(_lineBreak) + 1; + int index = start; + int length = 0; + while (index < line.length && length < stdout.terminalColumns) { + if (line.codeUnitAt(index) == kESC) { + // 0x1B + index += 1; + if (index < line.length && line.codeUnitAt(index) == kOpenSquareBracket) { + // 0x5B, [ + // That was the start of a CSI sequence. + index += 1; + while (index < line.length && + line.codeUnitAt(index) >= kCSIParameterRangeStart && + line.codeUnitAt(index) <= kCSIParameterRangeEnd) { + // 0x30..0x3F + index += 1; // ...parameter bytes... + } + while (index < line.length && + line.codeUnitAt(index) >= kCSIIntermediateRangeStart && + line.codeUnitAt(index) <= kCSIIntermediateRangeEnd) { + // 0x20..0x2F + index += 1; // ...intermediate bytes... + } + if (index < line.length && + line.codeUnitAt(index) >= kCSIFinalRangeStart && + line.codeUnitAt(index) <= kCSIFinalRangeEnd) { + // 0x40..0x7E + index += 1; // ...final byte. + } + } + } else { + index += 1; + length += 1; + } + } + line = line.substring(start, index); + if (line.isNotEmpty) { + stdout.write('\r\x1B[2K$white$line$reset'); + } + } else { + _printLoudly('$message'); + } +} + +void _printLoudly(String message) { + if (hasColor) { + // Overwrite the last line written by _printQuietly. + stdout.writeln('\r\x1B[2K$reset${message.trimRight()}'); + } else { + stdout.writeln(message); + } +} + +// THE FOLLOWING CODE IS A VIOLATION OF OUR STYLE GUIDE +// BECAUSE IT INTRODUCES A VERY FLAKY RACE CONDITION +// https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#never-check-if-a-port-is-available-before-using-it-never-add-timeouts-and-other-race-conditions +// DO NOT USE THE FOLLOWING FUNCTIONS +// DO NOT WRITE CODE LIKE THE FOLLOWING FUNCTIONS +// https://github.com/flutter/flutter/issues/109474 + +int _portCounter = 8080; + +/// Finds the next available local port. +Future findAvailablePortAndPossiblyCauseFlakyTests() async { + while (!await _isPortAvailable(_portCounter)) { + _portCounter += 1; + } + return _portCounter++; +} + +Future _isPortAvailable(int port) async { + try { + final RawSocket socket = await RawSocket.connect('localhost', port); + socket.shutdown(SocketDirection.both); + await socket.close(); + return false; + } on SocketException { + return true; + } +} + +String locationInFile(ResolvedUnitResult unit, AstNode node, String workingDirectory) { + return '${path.relative(path.relative(unit.path, from: workingDirectory))}:${unit.lineInfo.getLocation(node.offset).lineNumber}'; +} + +/// Whether the given [AstNode] within the `compilationUnit` is under the effect +/// of an inline ignore directive described by `ignoreDirectivePattern`. +/// +/// The `compilationUnit` parameter is the parsed dart file containing the given +/// [AstNode]. The `ignoreDirectivePattern` is a [Pattern] that should precisely +/// match the ignore directive of interest (including the slashes, example: +/// `// flutter_ignore: deprecation_syntax`). +/// +/// The implementation assumes the `ignoreDirectivePattern` matches no more than +/// one line. It searches for the given `ignoreDirectivePattern` in the +/// `compilationUnit`, that either starts the line above the given `node`, or +/// appears after `node` but on the same line, such that the ignore directive +/// works the same way as dart's "ignore" comment: it can either be added above +/// or after the line that needs to be exemped. +bool hasInlineIgnore( + AstNode node, + ParseStringResult compilationUnit, + Pattern ignoreDirectivePattern, +) { + final LineInfo lineInfo = compilationUnit.lineInfo; + // In case the node has multiple lines, match from its start offset. + final String textAfterNode = compilationUnit.content.substring( + node.offset, + // This assumes every line ends with a newline character (including the last + // line) and the new line character is not included to match the given pattern. + lineInfo.getOffsetOfLineAfter(node.offset) - 1, + ); + if (textAfterNode.contains(ignoreDirectivePattern)) { + return true; + } + // The lineNumber getter uses one-based index while everything else uses zero-based index. + final int lineNumber = lineInfo.getLocation(node.offset).lineNumber - 1; + if (lineNumber <= 0) { + return false; + } + return compilationUnit.content + .substring(lineInfo.getOffsetOfLine(lineNumber - 1), lineInfo.getOffsetOfLine(lineNumber)) + .trimLeft() + .contains(ignoreDirectivePattern); +} + +// The seed used to shuffle tests. If not passed with +// --test-randomize-ordering-seed= on the command line, it will be set the +// first time it is accessed. Pass zero to turn off shuffling. +String? _shuffleSeed; + +set shuffleSeed(String? newSeed) { + _shuffleSeed = newSeed; +} + +String get shuffleSeed { + if (_shuffleSeed != null) { + return _shuffleSeed!; + } + // Attempt to load from the command-line argument + final String? seedArg = Platform.environment['--test-randomize-ordering-seed']; + if (seedArg != null) { + return seedArg; + } + // Fallback to the original time-based seed generation + final DateTime seedTime = DateTime.now().toUtc().subtract(const Duration(hours: 7)); + _shuffleSeed = '${seedTime.year * 10000 + seedTime.month * 100 + seedTime.day}'; + return _shuffleSeed!; +} + +// TODO(sigmund): includeLocalEngineEnv should default to true. Currently we +// only enable it on flutter-web test because some test suites do not work +// properly when overriding the local engine (for example, because some platform +// dependent targets are only built on some engines). +// See https://github.com/flutter/flutter/issues/72368 +Future runDartTest( + String workingDirectory, { + List? testPaths, + bool enableFlutterToolAsserts = true, + bool useBuildRunner = false, + String? coverage, + bool forceSingleCore = false, + Duration? perTestTimeout, + bool includeLocalEngineEnv = false, + bool ensurePrecompiledTool = true, + bool shuffleTests = true, + bool collectMetrics = false, + List? tags, + bool runSkipped = false, +}) async { + // TODO(matanlurey): Consider Platform.numberOfProcessors instead. + // See https://github.com/flutter/flutter/issues/161399. + int cpus = 2; + + // Integration tests that depend on external processes like chrome + // can get stuck if there are multiple instances running at once. + if (forceSingleCore) { + cpus = 1; + } + + const LocalFileSystem fileSystem = LocalFileSystem(); + final String suffix = DateTime.now().microsecondsSinceEpoch.toString(); + final File metricFile = fileSystem.systemTempDirectory.childFile('metrics_$suffix.json'); + final List args = [ + 'run', + 'test', + '--reporter=expanded', + '--file-reporter=json:${metricFile.path}', + if (shuffleTests) '--test-randomize-ordering-seed=$shuffleSeed', + '-j$cpus', + if (!hasColor) '--no-color', + if (coverage != null) '--coverage=$coverage', + if (perTestTimeout != null) '--timeout=${perTestTimeout.inMilliseconds}ms', + if (runSkipped) '--run-skipped', + if (tags != null) ...tags.map((String t) => '--tags=$t'), + if (testPaths != null) + for (final String testPath in testPaths) testPath, + ]; + final Map environment = { + 'FLUTTER_ROOT': flutterRoot, + if (includeLocalEngineEnv) ...localEngineEnv, + if (Directory(pubCache).existsSync()) 'PUB_CACHE': pubCache, + }; + if (enableFlutterToolAsserts) { + adjustEnvironmentToEnableFlutterAsserts(environment); + } + if (ensurePrecompiledTool) { + // We rerun the `flutter` tool here just to make sure that it is compiled + // before tests run, because the tests might time out if they have to rebuild + // the tool themselves. + await runCommand(flutter, ['--version'], environment: environment); + } + await runCommand( + dart, + args, + workingDirectory: workingDirectory, + environment: environment, + removeLine: useBuildRunner ? (String line) => line.startsWith('[INFO]') : null, + ); + + if (dryRun) { + return; + } + + final TestFileReporterResults test = TestFileReporterResults.fromFile( + metricFile, + ); // --file-reporter name + final File info = fileSystem.file(path.join(flutterRoot, 'error.log')); + info.writeAsStringSync(json.encode(test.errors)); + + if (collectMetrics) { + try { + final List testList = []; + final Map allTestSpecs = test.allTestSpecs; + for (final TestSpecs testSpecs in allTestSpecs.values) { + testList.add(testSpecs.toJson()); + } + if (testList.isNotEmpty) { + final String testJson = json.encode(testList); + final File testResults = fileSystem.file(path.join(flutterRoot, 'test_results.json')); + testResults.writeAsStringSync(testJson); + } + } on fs.FileSystemException catch (e) { + print('Failed to generate metrics: $e'); + } + } + + // metriciFile is a transitional file that needs to be deleted once it is parsed. + // TODO(godofredoc): Ensure metricFile is parsed and aggregated before deleting. + // https://github.com/flutter/flutter/issues/146003 + metricFile.deleteSync(); +} + +Future runFlutterTest( + String workingDirectory, { + String? script, + bool expectFailure = false, + bool printOutput = true, + OutputChecker? outputChecker, + List options = const [], + Map? environment, + List tests = const [], + bool shuffleTests = true, + bool fatalWarnings = true, +}) async { + assert( + !printOutput || outputChecker == null, + 'Output either can be printed or checked but not both', + ); + + final List tags = []; + // Recipe-configured reduced test shards will only execute tests with the + // appropriate tag. + if (Platform.environment['REDUCED_TEST_SET'] == 'True') { + tags.addAll(['-t', 'reduced-test-set']); + } + + const LocalFileSystem fileSystem = LocalFileSystem(); + final String suffix = DateTime.now().microsecondsSinceEpoch.toString(); + final File metricFile = fileSystem.systemTempDirectory.childFile('metrics_$suffix.json'); + final List args = [ + 'test', + '--reporter=expanded', + '--file-reporter=json:${metricFile.path}', + if (shuffleTests && !_isRandomizationOff) '--test-randomize-ordering-seed=$shuffleSeed', + if (fatalWarnings) '--fatal-warnings', + ...options, + ...tags, + ...flutterTestArgs, + ]; + + if (script != null) { + final String fullScriptPath = path.join(workingDirectory, script); + if (!FileSystemEntity.isFileSync(fullScriptPath)) { + foundError([ + '${red}Could not find test$reset: $green$fullScriptPath$reset', + 'Working directory: $cyan$workingDirectory$reset', + 'Script: $green$script$reset', + if (!printOutput) 'This is one of the tests that does not normally print output.', + ]); + return; + } + args.add(script); + } + + args.addAll(tests); + + final OutputMode outputMode = + outputChecker == null && printOutput ? OutputMode.print : OutputMode.capture; + + final CommandResult result = await runCommand( + flutter, + args, + workingDirectory: workingDirectory, + expectNonZeroExit: expectFailure, + outputMode: outputMode, + environment: environment, + ); + + // metriciFile is a transitional file that needs to be deleted once it is parsed. + // TODO(godofredoc): Ensure metricFile is parsed and aggregated before deleting. + // https://github.com/flutter/flutter/issues/146003 + if (!dryRun) { + metricFile.deleteSync(); + } + + if (outputChecker != null) { + final String? message = outputChecker(result); + if (message != null) { + foundError([message]); + } + } +} + +/// This will force the next run of the Flutter tool (if it uses the provided +/// environment) to have asserts enabled, by setting an environment variable. +void adjustEnvironmentToEnableFlutterAsserts(Map environment) { + // If an existing env variable exists append to it, but only if + // it doesn't appear to already include enable-asserts. + String toolsArgs = Platform.environment['FLUTTER_TOOL_ARGS'] ?? ''; + if (!toolsArgs.contains('--enable-asserts')) { + toolsArgs += ' --enable-asserts'; + } + environment['FLUTTER_TOOL_ARGS'] = toolsArgs.trim(); +} + +Future selectShard(Map shards) => + _runFromList(shards, kShardKey, 'shard', 0); +Future selectSubshard(Map subshards) => + _runFromList(subshards, kSubshardKey, 'subshard', 1); + +Future runShardRunnerIndexOfTotalSubshard(List tests) async { + final List sublist = selectIndexOfTotalSubshard(tests); + for (final ShardRunner test in sublist) { + await test(); + } +} + +/// Parse (one-)index/total-named subshards from environment variable SUBSHARD +/// and equally distribute [tests] between them. +/// The format of SUBSHARD is "{index}_{total number of shards}". +/// The scheduler can change the number of total shards without needing an additional +/// commit in this repository. +/// +/// Examples: +/// 1_3 +/// 2_3 +/// 3_3 +List selectIndexOfTotalSubshard(List tests, {String subshardKey = kSubshardKey}) { + // Example: "1_3" means the first (one-indexed) shard of three total shards. + final String? subshardName = Platform.environment[subshardKey]; + if (subshardName == null) { + print('$kSubshardKey environment variable is missing, skipping sharding'); + return tests; + } + printProgress('$bold$subshardKey=$subshardName$reset'); + + final RegExp pattern = RegExp(r'^(\d+)_(\d+)$'); + final Match? match = pattern.firstMatch(subshardName); + if (match == null || match.groupCount != 2) { + foundError([ + '${red}Invalid subshard name "$subshardName". Expected format "[int]_[int]" ex. "1_3"', + ]); + throw Exception('Invalid subshard name: $subshardName'); + } + // One-indexed. + final int index = int.parse(match.group(1)!); + final int total = int.parse(match.group(2)!); + if (index > total) { + foundError([ + '${red}Invalid subshard name "$subshardName". Index number must be greater or equal to total.', + ]); + return []; + } + + final (int start, int end) = selectTestsForSubShard( + testCount: tests.length, + subShardIndex: index, + subShardCount: total, + ); + print('Selecting subshard $index of $total (tests ${start + 1}-$end of ${tests.length})'); + return tests.sublist(start, end); +} + +/// Finds the interval of tests that a subshard is responsible for testing. +@visibleForTesting +(int start, int end) selectTestsForSubShard({ + required int testCount, + required int subShardIndex, + required int subShardCount, +}) { + // While there exists a closed formula figuring out the range of tests the + // subshard is responsible for, modeling this as a simulation of distributing + // items equally into buckets is more intuitive. + // + // A bucket represents how many tests a subshard should be allocated. + final List buckets = List.filled(subShardCount, 0); + // First, allocate an equal number of items to each bucket. + for (int i = 0; i < buckets.length; i++) { + buckets[i] = (testCount / subShardCount).floor(); + } + // For the N leftover items, put one into each of the first N buckets. + final int remainingItems = testCount % buckets.length; + for (int i = 0; i < remainingItems; i++) { + buckets[i] += 1; + } + + // Lastly, compute the indices of the items in buckets[index]. + // We derive this from the toal number items in previous buckets and the number + // of items in this bucket. + final int numberOfItemsInPreviousBuckets = + subShardIndex == 0 ? 0 : buckets.sublist(0, subShardIndex - 1).sum; + final int start = numberOfItemsInPreviousBuckets; + final int end = start + buckets[subShardIndex - 1]; + + return (start, end); +} + +Future _runFromList( + Map items, + String key, + String name, + int positionInTaskName, +) async { + try { + final String? item = Platform.environment[key]; + if (item == null) { + for (final String currentItem in items.keys) { + printProgress('$bold$key=$currentItem$reset'); + await items[currentItem]!(); + } + } else { + printProgress('$bold$key=$item$reset'); + if (!items.containsKey(item)) { + foundError([ + '${red}Invalid $name: $item$reset', + 'The available ${name}s are: ${items.keys.join(", ")}', + ]); + return; + } + await items[item]!(); + } + } catch (_) { + if (!dryRun) { + rethrow; + } + } +} + +/// Checks the given file's contents to determine if they match the allowed +/// pattern for version strings. +/// +/// Returns null if the contents are good. Returns a string if they are bad. +/// The string is an error message. +Future verifyVersion(File file) async { + final RegExp pattern = RegExp(r'^(\d+)\.(\d+)\.(\d+)((-\d+\.\d+)?\.pre(\.\d+)?)?$'); + if (!file.existsSync()) { + return 'The version logic failed to create the Flutter version file.'; + } + final String version = await file.readAsString(); + if (version == '0.0.0-unknown') { + return 'The version logic failed to determine the Flutter version.'; + } + if (!version.contains(pattern)) { + return 'The version logic generated an invalid version string: "$version".'; + } + return null; +} diff --git a/flutter/dev/conductor/core/lib/src/repository.dart b/flutter/dev/conductor/core/lib/src/repository.dart new file mode 100644 index 00000000..466d5c47 --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/repository.dart @@ -0,0 +1,853 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io' as io; + +import 'package:file/file.dart'; +import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; + +import './git.dart'; +import './globals.dart'; +import './stdio.dart'; +import './version.dart'; + +/// Allowed git remote names. +enum RemoteName { upstream, mirror } + +class Remote { + const Remote({required RemoteName name, required this.url}) : _name = name, assert(url != ''); + + const Remote.mirror(String url) : this(name: RemoteName.mirror, url: url); + const Remote.upstream(String url) : this(name: RemoteName.upstream, url: url); + + final RemoteName _name; + + /// The name of the remote. + String get name => switch (_name) { + RemoteName.upstream => 'upstream', + RemoteName.mirror => 'mirror', + }; + + /// The URL of the remote. + final String url; +} + +/// A source code repository. +/// +/// This class is an abstraction over a git +/// repository on the local disk. Ideally this abstraction would hide from +/// the outside libraries what git calls were needed to either read or update +/// data in the underlying repository. In practice, most of the bugs in the +/// conductor codebase are related to the git calls made from this and its +/// subclasses. +/// +/// Two factors that make this code more complicated than it would otherwise +/// need to be are: +/// 1. That any particular invocation of the conductor may or may not already +/// have the git checkout present on disk, depending on what commands were +/// previously run; and +/// 2. The need to provide overrides for integration tests (in particular +/// the ability to mark a [Repository] instance as a [localUpstream] made +/// integration tests more hermetic, at the cost of complexity in the +/// implementation). +/// +/// The only way to simplify the first factor would be to change the behavior of +/// the conductor tool to be a long-lived dart process that keeps all of its +/// state in memory and blocks on user input. This would add the constraint that +/// the user would need to keep the process running for the duration of a +/// release, which could potentially take multiple days and users could not +/// manually change the state of the release process (via editing the JSON +/// config file). However, these may be reasonable trade-offs to make the +/// codebase simpler and easier to reason about. +/// +/// The way to simplify the second factor would be to not put any special +/// handling in this library for integration tests. This would make integration +/// tests more difficult/less hermetic, but the production code more reliable. +/// This is probably the right trade-off to make, as the integration tests were +/// still not hermetic or reliable, and the main integration test was ultimately +/// deleted in #84354. +abstract class Repository { + Repository({ + required this.name, + required this.upstreamRemote, + required this.processManager, + required this.stdio, + required this.platform, + required this.fileSystem, + required this.parentDirectory, + required this.requiredLocalBranches, + this.initialRef, + this.localUpstream = false, + this.previousCheckoutLocation, + this.mirrorRemote, + }) : git = Git(processManager), + assert(upstreamRemote.url.isNotEmpty); + + final String name; + final Remote upstreamRemote; + + /// Branches that must exist locally in this [Repository]. + /// + /// If this [Repository] is used as a local upstream for another, the + /// downstream may try to fetch these branches, and git will fail if they do + /// not exist. + final List requiredLocalBranches; + + /// Remote for user's mirror. + /// + /// This value can be null, in which case attempting to access it will lead to + /// a [ConductorException]. + final Remote? mirrorRemote; + + /// The initial ref (branch or commit name) to check out. + final String? initialRef; + final Git git; + final ProcessManager processManager; + final Stdio stdio; + final Platform platform; + final FileSystem fileSystem; + final Directory parentDirectory; + + /// If the repository will be used as an upstream for a test repo. + final bool localUpstream; + + Directory? _checkoutDirectory; + String? previousCheckoutLocation; + + /// Directory for the repository checkout. + /// + /// Since cloning a repository takes a long time, we do not ensure it is + /// cloned on the filesystem until this getter is accessed. + Future get checkoutDirectory async { + if (_checkoutDirectory != null) { + return _checkoutDirectory!; + } + if (previousCheckoutLocation != null) { + _checkoutDirectory = fileSystem.directory(previousCheckoutLocation); + if (!_checkoutDirectory!.existsSync()) { + throw ConductorException( + 'Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!', + ); + } + if (initialRef != null) { + assert(initialRef != ''); + await git.run( + ['fetch', upstreamRemote.name], + 'Fetch ${upstreamRemote.name} to ensure we have latest refs', + workingDirectory: _checkoutDirectory!.path, + ); + // If [initialRef] is a remote ref, the checkout will be left in a detached HEAD state. + await git.run( + ['checkout', initialRef!], + 'Checking out initialRef $initialRef', + workingDirectory: _checkoutDirectory!.path, + ); + } + + return _checkoutDirectory!; + } + + _checkoutDirectory = parentDirectory.childDirectory(name); + await lazilyInitialize(_checkoutDirectory!); + + return _checkoutDirectory!; + } + + /// RegExp pattern to parse the output of git ls-remote. + /// + /// Git output looks like: + /// + /// 35185330c6af3a435f615ee8ac2fed8b8bb7d9d4 refs/heads/95159-squash + /// 6f60a1e7b2f3d2c2460c9dc20fe54d0e9654b131 refs/heads/add-debug-trace + /// c1436c42c0f3f98808ae767e390c3407787f1a67 refs/heads/add-recipe-field + /// 4d44dca340603e25d4918c6ef070821181202e69 refs/heads/add-release-channel + /// + /// We are interested in capturing what comes after 'refs/heads/'. + static final RegExp _lsRemotePattern = RegExp(r'.*\s+refs\/heads\/([^\s]+)$'); + + /// Parse git ls-remote --heads and return branch names. + Future> listRemoteBranches(String remote) async { + final String output = await git.getOutput( + ['ls-remote', '--heads', remote], + 'get remote branches', + workingDirectory: (await checkoutDirectory).path, + ); + + return [ + for (final String line in output.split('\n')) + if (_lsRemotePattern.firstMatch(line) case final RegExpMatch match) match.group(1)!, + ]; + } + + /// Ensure the repository is cloned to disk and initialized with proper state. + Future lazilyInitialize(Directory checkoutDirectory) async { + if (checkoutDirectory.existsSync()) { + stdio.printTrace('Deleting $name from ${checkoutDirectory.path}...'); + checkoutDirectory.deleteSync(recursive: true); + } + + stdio.printTrace('Cloning $name from ${upstreamRemote.url} to ${checkoutDirectory.path}...'); + await git.run( + [ + 'clone', + '--origin', + upstreamRemote.name, + '--', + upstreamRemote.url, + checkoutDirectory.path, + ], + 'Cloning $name repo', + workingDirectory: parentDirectory.path, + ); + if (mirrorRemote != null) { + await git.run( + ['remote', 'add', mirrorRemote!.name, mirrorRemote!.url], + 'Adding remote ${mirrorRemote!.url} as ${mirrorRemote!.name}', + workingDirectory: checkoutDirectory.path, + ); + await git.run( + ['fetch', mirrorRemote!.name], + 'Fetching git remote ${mirrorRemote!.name}', + workingDirectory: checkoutDirectory.path, + ); + } + if (localUpstream) { + // These branches must exist locally for the repo that depends on it + // to fetch and push to. + for (final String channel in requiredLocalBranches) { + await git.run( + ['checkout', channel, '--'], + 'check out branch $channel locally', + workingDirectory: checkoutDirectory.path, + ); + } + } + + if (initialRef != null) { + await git.run( + ['checkout', initialRef!], + 'Checking out initialRef $initialRef', + workingDirectory: checkoutDirectory.path, + ); + } + final String revision = await reverseParse('HEAD'); + stdio.printTrace('Repository $name is checked out at revision "$revision".'); + } + + /// The URL of the remote named [remoteName]. + Future remoteUrl(String remoteName) async { + return git.getOutput( + ['remote', 'get-url', remoteName], + 'verify the URL of the $remoteName remote', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Get the working tree status. + /// + /// Calls `git status --porcelain` which should output in a stable format + /// across git versions. + Future gitStatus() async { + return git.getOutput( + ['status', '--porcelain'], + 'check that the git checkout is clean', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Verify the repository's git checkout is clean. + Future gitCheckoutClean() async { + return (await gitStatus()).isEmpty; + } + + /// Return the revision for the branch point between two refs. + Future branchPoint(String firstRef, String secondRef) async { + return (await git.getOutput( + ['merge-base', firstRef, secondRef], + 'determine the merge base between $firstRef and $secondRef', + workingDirectory: (await checkoutDirectory).path, + )).trim(); + } + + /// Fetch all branches and associated commits and tags from [remoteName]. + Future fetch(String remoteName) async { + await git.run( + ['fetch', remoteName, '--tags'], + 'fetch $remoteName --tags', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Create (and checkout) a new branch based on the current HEAD. + /// + /// Runs `git checkout -b $branchName`. + Future newBranch(String branchName) async { + await git.run( + ['checkout', '-b', branchName], + 'create & checkout new branch $branchName', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Check out the given ref. + Future checkout(String ref) async { + await git.run( + ['checkout', ref], + 'checkout ref', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Obtain the version tag at the tip of a release branch. + Future getFullTag(String remoteName, String branchName, {bool exact = true}) async { + // includes both stable (e.g. 1.2.3) and dev tags (e.g. 1.2.3-4.5.pre) + const String glob = '*.*.*'; + // describe the latest dev release + final String ref = 'refs/remotes/$remoteName/$branchName'; + return git.getOutput( + ['describe', '--match', glob, if (exact) '--exact-match', '--tags', ref], + 'obtain last released version number', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Tag [commit] and push the tag to the remote. + Future tag(String commit, String tagName, String remote) async { + assert(commit.isNotEmpty); + assert(tagName.isNotEmpty); + assert(remote.isNotEmpty); + stdio.printStatus('About to tag commit $commit as $tagName...'); + await git.run( + ['tag', tagName, commit], + 'tag the commit with the version label', + workingDirectory: (await checkoutDirectory).path, + ); + stdio.printStatus('Tagging successful.'); + stdio.printStatus('About to push $tagName to remote $remote...'); + await git.run( + ['push', remote, tagName], + 'publish the tag to the repo', + workingDirectory: (await checkoutDirectory).path, + ); + stdio.printStatus('Tag push successful.'); + } + + /// List commits in reverse chronological order. + Future> revList(List args) async { + return (await git.getOutput( + ['rev-list', ...args], + 'rev-list with args ${args.join(' ')}', + workingDirectory: (await checkoutDirectory).path, + )).trim().split('\n'); + } + + /// Look up the commit for [ref]. + Future reverseParse(String ref) async { + final String revisionHash = await git.getOutput( + ['rev-parse', ref], + 'look up the commit for the ref $ref', + workingDirectory: (await checkoutDirectory).path, + ); + assert(revisionHash.isNotEmpty); + return revisionHash; + } + + /// Determines if one ref is an ancestor for another. + Future isAncestor(String possibleAncestor, String possibleDescendant) async { + final io.ProcessResult result = await git.run( + ['merge-base', '--is-ancestor', possibleDescendant, possibleAncestor], + 'verify $possibleAncestor is a direct ancestor of $possibleDescendant.', + allowNonZeroExitCode: true, + workingDirectory: (await checkoutDirectory).path, + ); + return result.exitCode == 0; + } + + /// Determines if a given commit has a tag. + Future isCommitTagged(String commit) async { + final io.ProcessResult result = await git.run( + ['describe', '--exact-match', '--tags', commit], + 'verify $commit is already tagged', + allowNonZeroExitCode: true, + workingDirectory: (await checkoutDirectory).path, + ); + return result.exitCode == 0; + } + + /// Resets repository HEAD to [ref]. + Future reset(String ref) async { + await git.run( + ['reset', ref, '--hard'], + 'reset to $ref', + workingDirectory: (await checkoutDirectory).path, + ); + } + + /// Push [commit] to the release channel [branch]. + Future pushRef({ + required String fromRef, + required String remote, + required String toRef, + bool force = false, + bool dryRun = false, + }) async { + final List args = ['push', if (force) '--force', remote, '$fromRef:$toRef']; + final String command = ['git', ...args].join(' '); + if (dryRun) { + stdio.printStatus('About to execute command: `$command`'); + } else { + await git.run( + args, + 'update the release branch with the commit', + workingDirectory: (await checkoutDirectory).path, + ); + stdio.printStatus('Executed command: `$command`'); + } + } + + Future commit(String message, {bool addFirst = false, String? author}) async { + final bool hasChanges = + (await git.getOutput( + ['status', '--porcelain'], + 'check for uncommitted changes', + workingDirectory: (await checkoutDirectory).path, + )).trim().isNotEmpty; + if (!hasChanges) { + throw ConductorException('Tried to commit with message $message but no changes were present'); + } + if (addFirst) { + await git.run( + ['add', '--all'], + 'add all changes to the index', + workingDirectory: (await checkoutDirectory).path, + ); + } + String? authorArg; + if (author != null) { + if (author.contains('"')) { + throw FormatException('Commit author cannot contain character \'"\', received $author'); + } + // verify [author] matches git author convention, e.g. "Jane Doe " + if (!RegExp(r'.+<.*>').hasMatch(author)) { + throw FormatException('Commit author appears malformed: "$author"'); + } + authorArg = '--author="$author"'; + } + final List commitCmd = [ + 'commit', + '--message', + message, + if (authorArg != null) authorArg, + ]; + stdio.printTrace('Executing git $commitCmd...'); + final io.ProcessResult commitResult = await git.run( + commitCmd, + 'commit changes', + workingDirectory: (await checkoutDirectory).path, + ); + final String stdout = commitResult.stdout as String; + if (stdout.isNotEmpty) { + stdio.printTrace(stdout); + } + final String stderr = commitResult.stderr as String; + if (stderr.isNotEmpty) { + stdio.printTrace(stderr); + } + + return reverseParse('HEAD'); + } + + /// Create an empty commit and return the revision. + @visibleForTesting + Future authorEmptyCommit([String message = 'An empty commit']) async { + await git.run( + [ + '-c', + 'user.name=Conductor', + '-c', + 'user.email=conductor@flutter.dev', + 'commit', + '--allow-empty', + '-m', + "'$message'", + ], + 'create an empty commit', + workingDirectory: (await checkoutDirectory).path, + ); + return reverseParse('HEAD'); + } + + /// Create a new clone of the current repository. + /// + /// The returned repository will inherit all properties from this one, except + /// for the upstream, which will be the path to this repository on disk. + /// + /// This method is for testing purposes. + @visibleForTesting + Future cloneRepository(String cloneName); +} + +class FrameworkRepository extends Repository { + FrameworkRepository( + this.checkouts, { + super.name = 'framework', + super.upstreamRemote = const Remote.upstream(FrameworkRepository.defaultUpstream), + super.localUpstream, + super.previousCheckoutLocation, + String super.initialRef = FrameworkRepository.defaultBranch, + super.mirrorRemote, + List? additionalRequiredLocalBranches, + }) : super( + fileSystem: checkouts.fileSystem, + parentDirectory: checkouts.directory, + platform: checkouts.platform, + processManager: checkouts.processManager, + stdio: checkouts.stdio, + requiredLocalBranches: [...?additionalRequiredLocalBranches, ...kReleaseChannels], + ); + + /// A [FrameworkRepository] with the host conductor's repo set as upstream. + /// + /// This is useful when testing a commit that has not been merged upstream + /// yet. + factory FrameworkRepository.localRepoAsUpstream( + Checkouts checkouts, { + String name = 'framework', + String? previousCheckoutLocation, + String initialRef = FrameworkRepository.defaultBranch, + required String upstreamPath, + }) { + return FrameworkRepository( + checkouts, + name: name, + upstreamRemote: Remote.upstream('file://$upstreamPath/'), + previousCheckoutLocation: previousCheckoutLocation, + initialRef: initialRef, + ); + } + + final Checkouts checkouts; + static const String defaultUpstream = 'git@github.com:flutter/flutter.git'; + static const String defaultBranch = 'master'; + + Future get cacheDirectory async { + return fileSystem.path.join((await checkoutDirectory).path, 'bin', 'cache'); + } + + @override + Future cloneRepository(String? cloneName) async { + assert(localUpstream); + cloneName ??= 'clone-of-$name'; + return FrameworkRepository( + checkouts, + name: cloneName, + upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), + ); + } + + Future _ensureToolReady() async { + final File toolsStamp = fileSystem + .directory(await cacheDirectory) + .childFile('flutter_tools.stamp'); + if (toolsStamp.existsSync()) { + final String toolsStampHash = toolsStamp.readAsStringSync().trim(); + final String repoHeadHash = await reverseParse('HEAD'); + if (toolsStampHash == repoHeadHash) { + return; + } + } + + stdio.printTrace('Building tool...'); + // Build tool + await processManager.run([ + fileSystem.path.join((await checkoutDirectory).path, 'bin', 'flutter'), + 'help', + ]); + } + + Future runFlutter(List args) async { + await _ensureToolReady(); + final String workingDirectory = (await checkoutDirectory).path; + return processManager.run([ + fileSystem.path.join(workingDirectory, 'bin', 'flutter'), + ...args, + ], workingDirectory: workingDirectory); + } + + Future streamDart(List args, {String? workingDirectory}) async { + final String repoWorkingDirectory = (await checkoutDirectory).path; + + await _streamProcess([ + fileSystem.path.join(repoWorkingDirectory, 'bin', 'dart'), + ...args, + ], workingDirectory: workingDirectory ?? repoWorkingDirectory); + } + + Future streamFlutter( + List args, { + void Function(String)? stdoutCallback, + void Function(String)? stderrCallback, + }) async { + final String workingDirectory = (await checkoutDirectory).path; + + return _streamProcess([ + fileSystem.path.join(workingDirectory, 'bin', 'flutter'), + ...args, + ], workingDirectory: workingDirectory); + } + + Future _streamProcess( + List cmd, { + void Function(String)? stdoutCallback, + void Function(String)? stderrCallback, + String? workingDirectory, + }) async { + stdio.printTrace('Executing $cmd...'); + final io.Process process = await processManager.start(cmd, workingDirectory: workingDirectory); + final StreamSubscription stdoutSub = process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(stdoutCallback ?? stdio.printTrace); + final StreamSubscription stderrSub = process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen(stderrCallback ?? stdio.printError); + await Future.wait(>[stdoutSub.asFuture(), stderrSub.asFuture()]); + unawaited(stdoutSub.cancel()); + unawaited(stderrSub.cancel()); + + final int exitCode = await process.exitCode; + if (exitCode != 0) { + throw io.ProcessException(cmd.first, cmd.sublist(1), 'Process failed', exitCode); + } + return process; + } + + @override + Future checkout(String ref) async { + await super.checkout(ref); + // The tool will overwrite old cached artifacts, but not delete unused + // artifacts from a previous version. Thus, delete the entire cache and + // re-populate. + final Directory cache = fileSystem.directory(await cacheDirectory); + if (cache.existsSync()) { + stdio.printTrace('Deleting cache...'); + cache.deleteSync(recursive: true); + } + await _ensureToolReady(); + } + + Future flutterVersion() async { + // Check version + final io.ProcessResult result = await runFlutter(['--version', '--machine']); + final Map versionJson = + jsonDecode(stdoutToString(result.stdout)) as Map; + return Version.fromString(versionJson['frameworkVersion'] as String); + } + + /// Create a release candidate branch version file. + /// + /// This file allows for easily traversing what candidate branch was used + /// from a release channel. + /// + /// Returns [true] if the version file was updated and a commit is needed. + Future updateCandidateBranchVersion( + String branch, { + @visibleForTesting File? versionFile, + }) async { + assert(branch.isNotEmpty); + versionFile ??= (await checkoutDirectory) + .childDirectory('bin') + .childDirectory('internal') + .childFile('release-candidate-branch.version'); + if (versionFile.existsSync()) { + final String oldCandidateBranch = versionFile.readAsStringSync(); + if (oldCandidateBranch.trim() == branch.trim()) { + stdio.printTrace( + 'Tried to update the candidate branch but version file is already up to date at: $branch', + ); + return false; + } + } + stdio.printStatus('Create ${versionFile.path} containing $branch'); + versionFile.writeAsStringSync( + // Version files have trailing newlines + '${branch.trim()}\n', + flush: true, + ); + return true; + } + + /// Update this framework's engine version file. + /// + /// Returns [true] if the version file was updated and a commit is needed. + Future updateEngineRevision( + String newEngine, { + @visibleForTesting File? engineVersionFile, + }) async { + assert(newEngine.isNotEmpty); + engineVersionFile ??= (await checkoutDirectory) + .childDirectory('bin') + .childDirectory('internal') + .childFile('engine.version'); + assert(engineVersionFile.existsSync()); + final String oldEngine = engineVersionFile.readAsStringSync(); + if (oldEngine.trim() == newEngine.trim()) { + stdio.printTrace( + 'Tried to update the engine revision but version file is already up to date at: $newEngine', + ); + return false; + } + stdio.printStatus('Updating engine revision from $oldEngine to $newEngine'); + engineVersionFile.writeAsStringSync( + // Version files have trailing newlines + '${newEngine.trim()}\n', + flush: true, + ); + return true; + } +} + +/// A wrapper around the host repository that is executing the conductor. +/// +/// [Repository] methods that mutate the underlying repository will throw a +/// [ConductorException]. +class HostFrameworkRepository extends FrameworkRepository { + HostFrameworkRepository({ + required Checkouts checkouts, + String name = 'host-framework', + required String upstreamPath, + }) : super( + checkouts, + name: name, + upstreamRemote: Remote.upstream('file://$upstreamPath/'), + localUpstream: false, + ) { + _checkoutDirectory = checkouts.fileSystem.directory(upstreamPath); + } + + @override + Future get checkoutDirectory async => _checkoutDirectory!; + + @override + Future newBranch(String branchName) async { + throw ConductorException('newBranch not implemented for the host repository'); + } + + @override + Future checkout(String ref) async { + throw ConductorException('checkout not implemented for the host repository'); + } + + @override + Future reset(String ref) async { + throw ConductorException('reset not implemented for the host repository'); + } + + @override + Future tag(String commit, String tagName, String remote) async { + throw ConductorException('tag not implemented for the host repository'); + } + + void updateChannel( + String commit, + String remote, + String branch, { + bool force = false, + bool dryRun = false, + }) { + throw ConductorException('updateChannel not implemented for the host repository'); + } + + @override + Future authorEmptyCommit([String message = 'An empty commit']) async { + throw ConductorException('authorEmptyCommit not implemented for the host repository'); + } +} + +class EngineRepository extends Repository { + EngineRepository( + this.checkouts, { + super.name = 'engine', + String super.initialRef = EngineRepository.defaultBranch, + super.upstreamRemote = const Remote.upstream(EngineRepository.defaultUpstream), + super.localUpstream, + super.previousCheckoutLocation, + super.mirrorRemote, + List? additionalRequiredLocalBranches, + }) : super( + fileSystem: checkouts.fileSystem, + parentDirectory: checkouts.directory, + platform: checkouts.platform, + processManager: checkouts.processManager, + stdio: checkouts.stdio, + requiredLocalBranches: additionalRequiredLocalBranches ?? const [], + ); + + final Checkouts checkouts; + + static const String defaultUpstream = 'git@github.com:flutter/engine.git'; + static const String defaultBranch = 'main'; + + /// Update the `dart_revision` entry in the DEPS file. + Future updateDartRevision(String newRevision, {@visibleForTesting File? depsFile}) async { + assert(newRevision.length == 40); + depsFile ??= (await checkoutDirectory).childFile('DEPS'); + final String fileContent = depsFile.readAsStringSync(); + final RegExp dartPattern = RegExp("[ ]+'dart_revision': '([a-z0-9]{40})',"); + final Iterable allMatches = dartPattern.allMatches(fileContent); + if (allMatches.length != 1) { + throw ConductorException( + 'Unexpected content in the DEPS file at ${depsFile.path}\n' + 'Expected to find pattern ${dartPattern.pattern} 1 times, but got ' + '${allMatches.length}.', + ); + } + final String updatedFileContent = fileContent.replaceFirst( + dartPattern, + " 'dart_revision': '$newRevision',", + ); + + depsFile.writeAsStringSync(updatedFileContent, flush: true); + } + + @override + Future cloneRepository(String? cloneName) async { + assert(localUpstream); + cloneName ??= 'clone-of-$name'; + return EngineRepository( + checkouts, + name: cloneName, + upstreamRemote: Remote.upstream('file://${(await checkoutDirectory).path}/'), + ); + } +} + +/// An enum of all the repositories that the Conductor supports. +enum RepositoryType { framework, engine } + +class Checkouts { + Checkouts({ + required this.fileSystem, + required this.platform, + required this.processManager, + required this.stdio, + required Directory parentDirectory, + String directoryName = 'flutter_conductor_checkouts', + }) : directory = parentDirectory.childDirectory(directoryName) { + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } + } + + final Directory directory; + final FileSystem fileSystem; + final Platform platform; + final ProcessManager processManager; + final Stdio stdio; +} diff --git a/flutter/dev/conductor/core/lib/src/start.dart b/flutter/dev/conductor/core/lib/src/start.dart new file mode 100644 index 00000000..ca4254e9 --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/start.dart @@ -0,0 +1,420 @@ +// Copyright 2014 The Flutter 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/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:file/file.dart'; +import 'package:fixnum/fixnum.dart'; +import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; + +import 'context.dart'; +import 'git.dart'; +import 'globals.dart'; +import 'proto/conductor_state.pb.dart' as pb; +import 'proto/conductor_state.pbenum.dart'; +import 'repository.dart'; +import 'state.dart' as state_import; +import 'stdio.dart'; +import 'version.dart'; + +const String kCandidateOption = 'candidate-branch'; +const String kDartRevisionOption = 'dart-revision'; +const String kEngineUpstreamOption = 'engine-upstream'; +const String kFrameworkMirrorOption = 'framework-mirror'; +const String kFrameworkUpstreamOption = 'framework-upstream'; +const String kEngineMirrorOption = 'engine-mirror'; +const String kReleaseOption = 'release-channel'; +const String kStateOption = 'state-file'; +const String kVersionOverrideOption = 'version-override'; +const String kGithubUsernameOption = 'github-username'; + +/// Command to print the status of the current Flutter release. +/// +/// This command has many required options which the user must provide +/// via command line arguments (or optionally environment variables). +/// +/// This command is the one with the worst user experience (as the user has to +/// carefully type out many different options into their terminal) and the one +/// that would benefit the most from a GUI frontend. This command will +/// optionally read its options from an environment variable to facilitate a workflow +/// in which configuration is provided by editing a bash script that sets environment +/// variables and then invokes the conductor tool. +class StartCommand extends Command { + StartCommand({required this.checkouts, required this.conductorVersion}) + : platform = checkouts.platform, + processManager = checkouts.processManager, + fileSystem = checkouts.fileSystem, + stdio = checkouts.stdio { + final String defaultPath = state_import.defaultStateFilePath(platform); + argParser.addOption( + kCandidateOption, + help: 'The candidate branch the release will be based on.', + ); + argParser.addOption( + kReleaseOption, + help: 'The target release channel for the release.', + allowed: kBaseReleaseChannels, + ); + argParser.addOption(kFrameworkMirrorOption, help: 'Configurable Framework repo mirror remote.'); + argParser.addOption( + kFrameworkUpstreamOption, + defaultsTo: FrameworkRepository.defaultUpstream, + help: 'Configurable Framework repo upstream remote. Primarily for testing.', + hide: true, + ); + argParser.addOption( + kEngineUpstreamOption, + defaultsTo: EngineRepository.defaultUpstream, + help: 'Configurable Engine repo upstream remote. Primarily for testing.', + hide: true, + ); + argParser.addOption( + kStateOption, + defaultsTo: defaultPath, + help: 'Path to persistent state file. Defaults to $defaultPath', + ); + argParser.addOption(kDartRevisionOption, help: 'New Dart revision to cherrypick.'); + argParser.addFlag( + kForceFlag, + abbr: 'f', + help: 'Override all validations of the command line inputs.', + ); + argParser.addOption( + kVersionOverrideOption, + help: + 'Explicitly set the desired version. This should only be used if ' + 'the version computed by the tool is not correct.', + ); + argParser.addOption(kGithubUsernameOption, help: 'Github username'); + } + + final Checkouts checkouts; + + final String conductorVersion; + final FileSystem fileSystem; + final Platform platform; + final ProcessManager processManager; + final Stdio stdio; + + @override + String get name => 'start'; + + @override + String get description => 'Initialize a new Flutter release.'; + + @visibleForTesting + StartContext createContext(ArgResults argumentResults) { + final String frameworkUpstream = + getValueFromEnvOrArgs(kFrameworkUpstreamOption, argumentResults, platform.environment)!; + final String githubUsername = + getValueFromEnvOrArgs(kGithubUsernameOption, argumentResults, platform.environment)!; + final String frameworkMirror = + getValueFromEnvOrArgs( + kFrameworkMirrorOption, + argumentResults, + platform.environment, + allowNull: true, + ) ?? + 'git@github.com:$githubUsername/flutter.git'; + final String engineUpstream = + getValueFromEnvOrArgs(kEngineUpstreamOption, argumentResults, platform.environment)!; + final String engineMirror = 'git@github.com:$githubUsername/engine.git'; + final String candidateBranch = + getValueFromEnvOrArgs(kCandidateOption, argumentResults, platform.environment)!; + final String releaseChannel = + getValueFromEnvOrArgs(kReleaseOption, argumentResults, platform.environment)!; + final String? dartRevision = getValueFromEnvOrArgs( + kDartRevisionOption, + argumentResults, + platform.environment, + allowNull: true, + ); + final bool force = getBoolFromEnvOrArgs(kForceFlag, argumentResults, platform.environment); + final File stateFile = checkouts.fileSystem.file( + getValueFromEnvOrArgs(kStateOption, argumentResults, platform.environment), + ); + final String? versionOverrideString = getValueFromEnvOrArgs( + kVersionOverrideOption, + argumentResults, + platform.environment, + allowNull: true, + ); + Version? versionOverride; + if (versionOverrideString != null) { + versionOverride = Version.fromString(versionOverrideString); + } + + return StartContext( + candidateBranch: candidateBranch, + checkouts: checkouts, + dartRevision: dartRevision, + engineMirror: engineMirror, + engineUpstream: engineUpstream, + conductorVersion: conductorVersion, + frameworkMirror: frameworkMirror, + frameworkUpstream: frameworkUpstream, + processManager: processManager, + releaseChannel: releaseChannel, + stateFile: stateFile, + force: force, + versionOverride: versionOverride, + githubUsername: githubUsername, + ); + } + + @override + Future run() async { + final ArgResults argumentResults = argResults!; + if (!platform.isMacOS && !platform.isLinux) { + throw ConductorException('Error! This tool is only supported on macOS and Linux'); + } + + return createContext(argumentResults).run(); + } +} + +/// Context for starting a new release. +/// +/// This is a frontend-agnostic implementation. +class StartContext extends Context { + StartContext({ + required this.candidateBranch, + required this.dartRevision, + required this.engineMirror, + required this.engineUpstream, + required this.frameworkMirror, + required this.frameworkUpstream, + required this.conductorVersion, + required this.processManager, + required this.releaseChannel, + required this.githubUsername, + required super.checkouts, + required super.stateFile, + this.force = false, + this.versionOverride, + }) : git = Git(processManager), + engine = EngineRepository( + checkouts, + initialRef: 'upstream/$candidateBranch', + upstreamRemote: Remote.upstream(engineUpstream), + mirrorRemote: Remote.mirror(engineMirror), + ), + framework = FrameworkRepository( + checkouts, + initialRef: 'upstream/$candidateBranch', + upstreamRemote: Remote.upstream(frameworkUpstream), + mirrorRemote: Remote.mirror(frameworkMirror), + ); + + final String candidateBranch; + final String? dartRevision; + final String engineMirror; + final String engineUpstream; + final String frameworkMirror; + final String frameworkUpstream; + final String conductorVersion; + final Git git; + final ProcessManager processManager; + final String releaseChannel; + final Version? versionOverride; + final String githubUsername; + + /// If validations should be overridden. + final bool force; + + final EngineRepository engine; + final FrameworkRepository framework; + + /// Determine which part of the version to increment in the next release. + /// + /// If [atBranchPoint] is true, then this is a [ReleaseType.BETA_INITIAL]. + @visibleForTesting + ReleaseType computeReleaseType(Version lastVersion, bool atBranchPoint) { + if (atBranchPoint) { + return ReleaseType.BETA_INITIAL; + } + if (releaseChannel != 'stable') { + return ReleaseType.BETA_HOTFIX; + } + + return switch (lastVersion.type) { + VersionType.stable => ReleaseType.STABLE_HOTFIX, + VersionType.development || + VersionType.gitDescribe || + VersionType.latest => ReleaseType.STABLE_INITIAL, + }; + } + + Future run() async { + if (stateFile.existsSync()) { + throw ConductorException( + 'Error! A persistent state file already found at ${stateFile.path}.\n\n' + 'Run `conductor clean` to cancel a previous release.', + ); + } + if (!releaseCandidateBranchRegex.hasMatch(candidateBranch)) { + throw ConductorException( + 'Invalid release candidate branch "$candidateBranch". Text should ' + 'match the regex pattern /${releaseCandidateBranchRegex.pattern}/.', + ); + } + + final Int64 unixDate = Int64(DateTime.now().millisecondsSinceEpoch); + final pb.ConductorState state = pb.ConductorState(); + + state.releaseChannel = releaseChannel; + state.createdDate = unixDate; + state.lastUpdatedDate = unixDate; + + // Create a new branch so that we don't accidentally push to upstream + // candidateBranch. + final String workingBranchName = 'cherrypicks-$candidateBranch'; + await engine.newBranch(workingBranchName); + + if (dartRevision != null && dartRevision!.isNotEmpty) { + await engine.updateDartRevision(dartRevision!); + await engine.commit('Update Dart SDK to $dartRevision', addFirst: true); + } + + final String engineHead = await engine.reverseParse('HEAD'); + state.engine = + (pb.Repository.create() + ..candidateBranch = candidateBranch + ..workingBranch = workingBranchName + ..startingGitHead = engineHead + ..currentGitHead = engineHead + ..checkoutPath = (await engine.checkoutDirectory).path + ..upstream = + (pb.Remote.create() + ..name = 'upstream' + ..url = engine.upstreamRemote.url) + ..mirror = + (pb.Remote.create() + ..name = 'mirror' + ..url = engine.mirrorRemote!.url)); + if (dartRevision != null && dartRevision!.isNotEmpty) { + state.engine.dartRevision = dartRevision!; + } + + await framework.newBranch(workingBranchName); + + // Get framework version + final Version lastVersion = Version.fromString( + await framework.getFullTag(framework.upstreamRemote.name, candidateBranch, exact: false), + ); + + final String frameworkHead = await framework.reverseParse('HEAD'); + final String branchPoint = await framework.branchPoint( + '${framework.upstreamRemote.name}/$candidateBranch', + '${framework.upstreamRemote.name}/${FrameworkRepository.defaultBranch}', + ); + final bool atBranchPoint = branchPoint == frameworkHead; + + final ReleaseType releaseType = computeReleaseType(lastVersion, atBranchPoint); + state.releaseType = releaseType; + + try { + lastVersion.ensureValid(candidateBranch, releaseType); + } on ConductorException catch (e) { + // Let the user know, but resume execution + stdio.printError(e.message); + } + + Version nextVersion; + if (versionOverride != null) { + nextVersion = versionOverride!; + } else { + nextVersion = calculateNextVersion(lastVersion, releaseType); + nextVersion = await ensureBranchPointTagged( + branchPoint: branchPoint, + requestedVersion: nextVersion, + framework: framework, + ); + } + + state.releaseVersion = nextVersion.toString(); + + state.framework = + (pb.Repository.create() + ..candidateBranch = candidateBranch + ..workingBranch = workingBranchName + ..startingGitHead = frameworkHead + ..currentGitHead = frameworkHead + ..checkoutPath = (await framework.checkoutDirectory).path + ..upstream = + (pb.Remote.create() + ..name = 'upstream' + ..url = framework.upstreamRemote.url) + ..mirror = + (pb.Remote.create() + ..name = 'mirror' + ..url = framework.mirrorRemote!.url)); + + state.currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS; + + state.conductorVersion = conductorVersion; + + stdio.printTrace('Writing state to file ${stateFile.path}...'); + + updateState(state, stdio.logs); + + stdio.printStatus(state_import.presentState(state)); + } + + /// Determine this release's version number from the [lastVersion] and the [incrementLetter]. + Version calculateNextVersion(Version lastVersion, ReleaseType releaseType) { + return switch (releaseType) { + ReleaseType.STABLE_INITIAL => Version( + x: lastVersion.x, + y: lastVersion.y, + z: 0, + type: VersionType.stable, + ), + ReleaseType.STABLE_HOTFIX => Version.increment(lastVersion, 'z'), + ReleaseType.BETA_INITIAL => Version.fromCandidateBranch(candidateBranch), + ReleaseType.BETA_HOTFIX || _ => Version.increment(lastVersion, 'n'), + }; + } + + /// Ensures the branch point [candidateBranch] and `master` has a version tag. + /// + /// This is necessary for version reporting for users on the `master` channel + /// to be correct. + Future ensureBranchPointTagged({ + required Version requestedVersion, + required String branchPoint, + required FrameworkRepository framework, + }) async { + if (await framework.isCommitTagged(branchPoint)) { + // The branch point is tagged, no work to be done + return requestedVersion; + } + if (requestedVersion.n != 0) { + stdio.printError( + 'Tried to tag the branch point, however the target version is ' + '$requestedVersion, which does not have n == 0!', + ); + return requestedVersion; + } + + final bool response = await prompt( + 'About to tag the release candidate branch branchpoint of $branchPoint ' + 'as $requestedVersion and push it to ${framework.upstreamRemote.url}. ' + 'Is this correct?', + ); + + if (!response) { + throw ConductorException('Aborting command.'); + } + + stdio.printStatus('Applying the tag $requestedVersion at the branch point $branchPoint'); + + await framework.tag(branchPoint, requestedVersion.toString(), frameworkUpstream); + final Version nextVersion = Version.increment(requestedVersion, 'n'); + stdio.printStatus('The actual release will be version $nextVersion.'); + return nextVersion; + } +} diff --git a/flutter/dev/conductor/core/lib/src/state.dart b/flutter/dev/conductor/core/lib/src/state.dart new file mode 100644 index 00000000..e823fb18 --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/state.dart @@ -0,0 +1,340 @@ +// Copyright 2014 The Flutter 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:convert' show JsonEncoder, jsonDecode; + +import 'package:file/file.dart' show File; +import 'package:platform/platform.dart'; + +import './globals.dart' as globals; +import './proto/conductor_state.pb.dart' as pb; +import './proto/conductor_state.pbenum.dart' show ReleasePhase; + +const String kStateFileName = '.flutter_conductor_state.json'; + +const String betaPostReleaseMsg = """ + 'Ensure the following post release steps are complete:', + '\t 1. Post announcement to discord and press the publish button', + '\t\t Discord: ${globals.discordReleaseChannel}', + '\t 2. Post announcement flutter release hotline chat room', + '\t\t Chatroom: ${globals.flutterReleaseHotline}', +"""; + +const String stablePostReleaseMsg = """ + 'Ensure the following post release steps are complete:', + '\t 1. Update hotfix to stable wiki following documentation best practices', + '\t\t Wiki link: ${globals.hotfixToStableWiki}', + '\t\t Best practices: ${globals.hotfixDocumentationBestPractices}', + '\t 2. Post announcement to flutter-announce group', + '\t\t Flutter Announce: ${globals.flutterAnnounceGroup}', + '\t 3. Post announcement to discord and press the publish button', + '\t\t Discord: ${globals.discordReleaseChannel}', + '\t 4. Post announcement flutter release hotline chat room', + '\t\t Chatroom: ${globals.flutterReleaseHotline}', +"""; +// The helper functions in `state.dart` wrap the code-generated dart files in +// `lib/src/proto/`. The most interesting of these functions is: + +// * `pb.ConductorState readStateFromFile(File)` - uses the code generated +// `.mergeFromProto3Json()` method to deserialize the JSON content from the +// config file into a Dart instance of the `ConductorState` class. +// * `void writeStateFromFile(File, pb.ConductorState, List)` +// - similarly calls the `.toProto3Json()` method to serialize a +// * `ConductorState` instance to a JSON string which is then written to disk. +// `String phaseInstructions(pb.ConductorState state)` - returns instructions +// for what the user is supposed to do next based on `state.currentPhase`. +// * `String presentState(pb.ConductorState state)` - pretty print the state file. +// This is a little easier to read than the raw JSON. + +String luciConsoleLink(String candidateBranch, String repoName) { + assert( + globals.releaseCandidateBranchRegex.hasMatch(candidateBranch), + 'Malformed candidateBranch argument passed: "$candidateBranch"', + ); + assert( + ['flutter', 'engine', 'packaging'].contains(repoName), + 'group named $repoName not recognized', + ); + if (repoName == 'packaging') { + return 'https://luci-milo.appspot.com/p/dart-internal/g/flutter_packaging/console'; + } + return 'https://flutter-dashboard.appspot.com/#/build?repo=$repoName&branch=$candidateBranch'; +} + +String defaultStateFilePath(Platform platform) { + final String? home = platform.environment['HOME']; + if (home == null) { + throw globals.ConductorException(r'Environment variable $HOME must be set!'); + } + return [home, kStateFileName].join(platform.pathSeparator); +} + +String presentState(pb.ConductorState state) { + final StringBuffer buffer = StringBuffer(); + buffer.writeln('Conductor version: ${state.conductorVersion}'); + buffer.writeln('Release channel: ${state.releaseChannel}'); + buffer.writeln('Release version: ${state.releaseVersion}'); + buffer.writeln(); + buffer.writeln( + 'Release started at: ${DateTime.fromMillisecondsSinceEpoch(state.createdDate.toInt())}', + ); + buffer.writeln( + 'Last updated at: ${DateTime.fromMillisecondsSinceEpoch(state.lastUpdatedDate.toInt())}', + ); + buffer.writeln(); + buffer.writeln('Engine Repo'); + buffer.writeln('\tCandidate branch: ${state.engine.candidateBranch}'); + buffer.writeln('\tStarting git HEAD: ${state.engine.startingGitHead}'); + buffer.writeln('\tCurrent git HEAD: ${state.engine.currentGitHead}'); + buffer.writeln('\tPath to checkout: ${state.engine.checkoutPath}'); + buffer.writeln( + '\tPost-submit LUCI dashboard: ${luciConsoleLink(state.engine.candidateBranch, 'engine')}', + ); + if (state.engine.cherrypicks.isNotEmpty) { + buffer.writeln('${state.engine.cherrypicks.length} Engine Cherrypicks:'); + for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) { + buffer.writeln('\t${cherrypick.trunkRevision} - ${cherrypick.state}'); + } + } else { + buffer.writeln('0 Engine cherrypicks.'); + } + if (state.engine.dartRevision.isNotEmpty) { + buffer.writeln('New Dart SDK revision: ${state.engine.dartRevision}'); + } + buffer.writeln('Framework Repo'); + buffer.writeln('\tCandidate branch: ${state.framework.candidateBranch}'); + buffer.writeln('\tStarting git HEAD: ${state.framework.startingGitHead}'); + buffer.writeln('\tCurrent git HEAD: ${state.framework.currentGitHead}'); + buffer.writeln('\tPath to checkout: ${state.framework.checkoutPath}'); + buffer.writeln( + '\tPost-submit LUCI dashboard: ${luciConsoleLink(state.framework.candidateBranch, 'flutter')}', + ); + if (state.framework.cherrypicks.isNotEmpty) { + buffer.writeln('${state.framework.cherrypicks.length} Framework Cherrypicks:'); + for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) { + buffer.writeln('\t${cherrypick.trunkRevision} - ${cherrypick.state}'); + } + } else { + buffer.writeln('0 Framework cherrypicks.'); + } + buffer.writeln(); + if (state.currentPhase == ReleasePhase.VERIFY_RELEASE) { + buffer.writeln( + '${state.releaseChannel} release ${state.releaseVersion} has been published and verified.\n', + ); + return buffer.toString(); + } + buffer.writeln('The current phase is:'); + buffer.writeln(presentPhases(state.currentPhase)); + + buffer.writeln(phaseInstructions(state)); + buffer.writeln(); + buffer.writeln('Issue `conductor next` when you are ready to proceed.'); + return buffer.toString(); +} + +String presentPhases(ReleasePhase currentPhase) { + final StringBuffer buffer = StringBuffer(); + bool phaseCompleted = true; + + for (final ReleasePhase phase in ReleasePhase.values) { + if (phase == currentPhase) { + // This phase will execute the next time `conductor next` is run. + buffer.writeln('> ${phase.name} (current)'); + phaseCompleted = false; + } else if (phaseCompleted) { + // This phase was already completed. + buffer.writeln('✓ ${phase.name}'); + } else { + // This phase has not been completed yet. + buffer.writeln(' ${phase.name}'); + } + } + return buffer.toString(); +} + +String phaseInstructions(pb.ConductorState state) { + switch (state.currentPhase) { + case ReleasePhase.APPLY_ENGINE_CHERRYPICKS: + if (state.engine.cherrypicks.isEmpty) { + return [ + 'There are no engine cherrypicks, so issue `conductor next` to continue', + 'to the next step.', + '\n', + '******************************************************', + '* Create a new entry in http://go/release-eng-retros *', + '******************************************************', + ].join('\n'); + } + return [ + 'You must now manually apply the following engine cherrypicks to the checkout', + 'at ${state.engine.checkoutPath} in order:', + for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) + '\t${cherrypick.trunkRevision}', + 'See ${globals.kReleaseDocumentationUrl} for more information.', + ].join('\n'); + case ReleasePhase.VERIFY_ENGINE_CI: + if (!requiresEnginePR(state)) { + return 'You must verify engine CI has passed: ' + '${luciConsoleLink(state.engine.candidateBranch, 'engine')}'; + } + // User's working branch was pushed to their mirror, but a PR needs to be + // opened on GitHub. + final String newPrLink = globals.getNewPrLink( + userName: githubAccount(state.engine.mirror.url), + repoName: 'engine', + state: state, + ); + final String consoleLink = luciConsoleLink(state.engine.candidateBranch, 'engine'); + return [ + 'Your working branch ${state.engine.workingBranch} was pushed to your mirror.', + 'You must now open a pull request at $newPrLink, verify pre-submit CI', + 'builds on your engine pull request are successful, merge your pull request,', + 'validate post-submit CI at $consoleLink.', + ].join('\n'); + case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: + final List outstandingCherrypicks = + state.framework.cherrypicks.where((pb.Cherrypick cp) { + return cp.state == pb.CherrypickState.PENDING || + cp.state == pb.CherrypickState.PENDING_WITH_CONFLICT; + }).toList(); + if (outstandingCherrypicks.isNotEmpty) { + return [ + 'You must now manually apply the following framework cherrypicks to the checkout', + 'at ${state.framework.checkoutPath} in order:', + for (final pb.Cherrypick cherrypick in outstandingCherrypicks) + '\t${cherrypick.trunkRevision}', + ].join('\n'); + } + return [ + 'Either all cherrypicks have been auto-applied or there were none.', + ].join('\n'); + case ReleasePhase.PUBLISH_VERSION: + if (!requiresFrameworkPR(state)) { + return 'Since there are no code changes in this release, no Framework ' + 'PR is necessary.'; + } + + final String newPrLink = globals.getNewPrLink( + userName: githubAccount(state.framework.mirror.url), + repoName: 'flutter', + state: state, + ); + return [ + 'Your working branch ${state.framework.workingBranch} was pushed to your mirror.', + 'You must now open a pull request at $newPrLink', + 'verify pre-submit CI builds on your pull request are successful, merge your ', + 'pull request, validate post-submit CI.', + ].join('\n'); + case ReleasePhase.VERIFY_RELEASE: + return 'Release archive packages must be verified on cloud storage: ${luciConsoleLink(state.framework.candidateBranch, 'packaging')}'; + case ReleasePhase.RELEASE_COMPLETED: + if (state.releaseChannel == 'beta') { + return [ + betaPostReleaseMsg, + '-----------------------------------------------------------------------', + 'This release has been completed.', + ].join('\n'); + } + return [ + stablePostReleaseMsg, + '-----------------------------------------------------------------------', + 'This release has been completed.', + ].join('\n'); + } + // For analyzer + throw globals.ConductorException('Unimplemented phase ${state.currentPhase}'); +} + +/// Regex pattern for git remote host URLs. +/// +/// First group = git host (currently must be github.com) +/// Second group = account name +/// Third group = repo name +final RegExp githubRemotePattern = RegExp( + r'^(git@github\.com:|https?:\/\/github\.com\/)([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_-]+)(\.git)?$', +); + +/// Parses a Git remote URL and returns the account name. +/// +/// Uses [githubRemotePattern]. +String githubAccount(String remoteUrl) { + final String engineUrl = remoteUrl; + final RegExpMatch? match = githubRemotePattern.firstMatch(engineUrl); + if (match == null) { + throw globals.ConductorException('Cannot determine the GitHub account from $engineUrl'); + } + final String? accountName = match.group(2); + if (accountName == null || accountName.isEmpty) { + throw globals.ConductorException('Cannot determine the GitHub account from $match'); + } + return accountName; +} + +/// Returns the next phase in the ReleasePhase enum. +/// +/// Will throw a [ConductorException] if [ReleasePhase.RELEASE_COMPLETED] is +/// passed as an argument, as there is no next phase. +ReleasePhase getNextPhase(ReleasePhase currentPhase) { + switch (currentPhase) { + case ReleasePhase.PUBLISH_VERSION: + return ReleasePhase.VERIFY_RELEASE; + case ReleasePhase.APPLY_ENGINE_CHERRYPICKS: + case ReleasePhase.VERIFY_ENGINE_CI: + case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS: + case ReleasePhase.VERIFY_RELEASE: + case ReleasePhase.RELEASE_COMPLETED: + final ReleasePhase? nextPhase = ReleasePhase.valueOf(currentPhase.value + 1); + if (nextPhase != null) { + return nextPhase; + } + } + throw globals.ConductorException('There is no next ReleasePhase!'); +} + +// Indent two spaces. +const JsonEncoder _encoder = JsonEncoder.withIndent(' '); + +void writeStateToFile(File file, pb.ConductorState state, List logs) { + state.logs.addAll(logs); + file.writeAsStringSync(_encoder.convert(state.toProto3Json()), flush: true); +} + +pb.ConductorState readStateFromFile(File file) { + final pb.ConductorState state = pb.ConductorState(); + final String stateAsString = file.readAsStringSync(); + state.mergeFromProto3Json(jsonDecode(stateAsString)); + return state; +} + +/// This release will require a new Engine PR. +/// +/// The logic is if there are engine cherrypicks that have not been abandoned OR +/// there is a new Dart revision, then return true, else false. +bool requiresEnginePR(pb.ConductorState state) { + final bool hasRequiredCherrypicks = state.engine.cherrypicks.any( + (pb.Cherrypick cp) => cp.state != pb.CherrypickState.ABANDONED, + ); + if (hasRequiredCherrypicks) { + return true; + } + return state.engine.dartRevision.isNotEmpty; +} + +/// This release will require a new Framework PR. +/// +/// The logic is if there was an Engine PR OR there are framework cherrypicks +/// that have not been abandoned. +bool requiresFrameworkPR(pb.ConductorState state) { + if (requiresEnginePR(state)) { + return true; + } + final bool hasRequiredCherrypicks = state.framework.cherrypicks.any( + (pb.Cherrypick cp) => cp.state != pb.CherrypickState.ABANDONED, + ); + if (hasRequiredCherrypicks) { + return true; + } + return false; +} diff --git a/flutter/dev/conductor/core/lib/src/status.dart b/flutter/dev/conductor/core/lib/src/status.dart new file mode 100644 index 00000000..58a1f02b --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/status.dart @@ -0,0 +1,58 @@ +// Copyright 2014 The Flutter 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:file/file.dart'; +import 'package:platform/platform.dart'; + +import './proto/conductor_state.pb.dart' as pb; +import './repository.dart'; +import './state.dart'; +import './stdio.dart'; + +const String kVerboseFlag = 'verbose'; +const String kStateOption = 'state-file'; + +/// Command to print the status of the current Flutter release. +class StatusCommand extends Command { + StatusCommand({required this.checkouts}) + : platform = checkouts.platform, + fileSystem = checkouts.fileSystem, + stdio = checkouts.stdio { + final String defaultPath = defaultStateFilePath(platform); + argParser.addOption( + kStateOption, + defaultsTo: defaultPath, + help: 'Path to persistent state file. Defaults to $defaultPath', + ); + argParser.addFlag(kVerboseFlag, abbr: 'v', help: 'Also print logs.'); + } + + final Checkouts checkouts; + final FileSystem fileSystem; + final Platform platform; + final Stdio stdio; + + @override + String get name => 'status'; + + @override + String get description => 'Print status of current release.'; + + @override + void run() { + final File stateFile = checkouts.fileSystem.file(argResults![kStateOption]); + if (!stateFile.existsSync()) { + stdio.printStatus('No persistent state file found at ${argResults![kStateOption]}.'); + return; + } + final pb.ConductorState state = readStateFromFile(stateFile); + + stdio.printStatus(presentState(state)); + if (argResults![kVerboseFlag] as bool) { + stdio.printStatus('\nLogs:'); + state.logs.forEach(stdio.printStatus); + } + } +} diff --git a/flutter/dev/conductor/core/lib/src/stdio.dart b/flutter/dev/conductor/core/lib/src/stdio.dart new file mode 100644 index 00000000..7899989c --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/stdio.dart @@ -0,0 +1,130 @@ +// Copyright 2014 The Flutter 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:io' as io; + +import 'package:meta/meta.dart'; + +/// An interface for presenting text output to the user. +/// +/// Although this could have been simplified by calling `print()` +/// from the tool, this abstraction allows unit tests to verify output +/// and allows a GUI frontend to provide an alternative implementation. +/// +/// User input probably should be part of this class–however it is currently +/// part of context.dart. +abstract class Stdio { + final List logs = []; + + /// Error messages printed to STDERR. + /// + /// Display an error `message` to the user on stderr. Print errors if the code + /// fails in some way. Errors are typically followed shortly by exiting the + /// app with a non-zero exit status. + @mustCallSuper + void printError(String message) { + logs.add('[error] $message'); + } + + /// Warning messages printed to STDERR. + /// + /// Display a warning `message` to the user on stderr. Print warnings if there + /// is important information to convey to the user that is not fatal. + @mustCallSuper + void printWarning(String message) { + logs.add('[warning] $message'); + } + + /// Ordinary STDOUT messages. + /// + /// Displays normal output on stdout. This should be used for things like + /// progress messages, success messages, or just normal command output. + @mustCallSuper + void printStatus(String message) { + logs.add('[status] $message'); + } + + /// Debug messages that are only printed in verbose mode. + /// + /// Use this for verbose tracing output. Users can turn this output on in order + /// to help diagnose issues. + @mustCallSuper + void printTrace(String message) { + logs.add('[trace] $message'); + } + + /// Write the `message` string to STDOUT without a trailing newline. + @mustCallSuper + void write(String message) { + logs.add('[write] $message'); + } + + /// Read a line of text from STDIN. + String readLineSync(); +} + +/// A logger that will print out trace messages. +class VerboseStdio extends Stdio { + VerboseStdio({required this.stdout, required this.stderr, required this.stdin, this.filter}); + + factory VerboseStdio.local() => + VerboseStdio(stdout: io.stdout, stderr: io.stderr, stdin: io.stdin); + + final io.Stdout stdout; + final io.Stdout stderr; + final io.Stdin stdin; + + /// If provided, all messages will be passed through this function before being logged. + final String Function(String)? filter; + + @override + void printError(String message) { + if (filter != null) { + message = filter!(message); + } + super.printError(message); + stderr.writeln(message); + } + + @override + void printWarning(String message) { + if (filter != null) { + message = filter!(message); + } + super.printWarning(message); + stderr.writeln(message); + } + + @override + void printStatus(String message) { + if (filter != null) { + message = filter!(message); + } + super.printStatus(message); + stdout.writeln(message); + } + + @override + void printTrace(String message) { + if (filter != null) { + message = filter!(message); + } + super.printTrace(message); + stdout.writeln(message); + } + + @override + void write(String message) { + if (filter != null) { + message = filter!(message); + } + super.write(message); + stdout.write(message); + } + + @override + String readLineSync() { + return stdin.readLineSync()!; + } +} diff --git a/flutter/dev/conductor/core/lib/src/validate_checkout_post_gradle_regeneration.dart b/flutter/dev/conductor/core/lib/src/validate_checkout_post_gradle_regeneration.dart new file mode 100644 index 00000000..e33d5afd --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/validate_checkout_post_gradle_regeneration.dart @@ -0,0 +1,78 @@ +// Copyright 2014 The Flutter 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:path/path.dart' show Context; + +/// Possible states of the Flutter repo checkout after Gradle lockfile +/// regeneration. +sealed class CheckoutStatePostGradleRegeneration { + factory CheckoutStatePostGradleRegeneration(String gitStatusOutput, Context context) { + gitStatusOutput = gitStatusOutput.trim(); + if (gitStatusOutput.isEmpty) { + return const NoDiff(); + } + + final List changes = gitStatusOutput.split('\n'); + final List changedPaths = []; + for (final String line in changes) { + final RegExpMatch? match = pattern.firstMatch(line); + if (match == null) { + return MalformedLine(line); + } + changedPaths.add(match.group(1)!); + } + + final List nonLockfileDiffs = + changedPaths.where((String path) { + final String extension = context.extension(path); + return extension != '.lockfile'; + }).toList(); + + if (nonLockfileDiffs.isNotEmpty) { + return NonLockfileChanges(nonLockfileDiffs); + } + + return const OnlyLockfileChanges(); + } + + /// Output format for `git status --porcelain` and `git status --short`. + /// + /// The first capture group is the path to the file or directory changed, + /// relative to the root of the repository. + /// + /// See `man git-status` for more reference. + static final RegExp pattern = RegExp(r'[ACDMRTU ]{1,2} (\S+)'); +} + +/// No files were changed, no commit needed. +final class NoDiff implements CheckoutStatePostGradleRegeneration { + const NoDiff(); +} + +/// Only files ending in *.lockfile were changed; changes can be committed. +final class OnlyLockfileChanges implements CheckoutStatePostGradleRegeneration { + const OnlyLockfileChanges(); +} + +/// There are changed files that do not end in *.lockfile; fail the script. +/// +/// Because the script to regenerate Gradle lockfiles triggers a Gradle build, +/// and because the packages_autoroller can have its PRs merged without a +/// human review, we are conservative about what changes we commit. +final class NonLockfileChanges implements CheckoutStatePostGradleRegeneration { + const NonLockfileChanges(this.changes); + + final List changes; +} + +/// A line in the output of `git status` does not match the expected pattern; +/// fail the script. +/// +/// This likely means there is a bug in the regular expression, and it needs +/// to be updated. +final class MalformedLine implements CheckoutStatePostGradleRegeneration { + const MalformedLine(this.line); + + final String line; +} diff --git a/flutter/dev/conductor/core/lib/src/version.dart b/flutter/dev/conductor/core/lib/src/version.dart new file mode 100644 index 00000000..8aa3f10c --- /dev/null +++ b/flutter/dev/conductor/core/lib/src/version.dart @@ -0,0 +1,278 @@ +// Copyright 2014 The Flutter 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 'globals.dart' show ConductorException, releaseCandidateBranchRegex; + +import 'proto/conductor_state.pbenum.dart'; + +/// Possible string formats that `flutter --version` can return. +enum VersionType { + /// A stable flutter release. + /// + /// Example: '1.2.3' + stable, + + /// A pre-stable flutter release. + /// + /// Example: '1.2.3-4.5.pre' + development, + + /// A master channel flutter version. + /// + /// Example: '1.2.3-4.0.pre.10' + /// + /// The last number is the number of commits past the last tagged version. + latest, + + /// A master channel flutter version from git describe. + /// + /// Example: '1.2.3-4.0.pre-10-gabc123'. + /// Example: '1.2.3-10-gabc123'. + gitDescribe, +} + +final Map versionPatterns = { + VersionType.stable: RegExp(r'^(\d+)\.(\d+)\.(\d+)$'), + VersionType.development: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre$'), + VersionType.latest: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre\.(\d+)$'), + VersionType.gitDescribe: RegExp(r'^(\d+)\.(\d+)\.(\d+)-((\d+)\.(\d+)\.pre-)?(\d+)-g[a-f0-9]+$'), +}; + +class Version { + Version({ + required this.x, + required this.y, + required this.z, + this.m, + this.n, + this.commits, + required this.type, + }) { + switch (type) { + case VersionType.stable: + assert(m == null); + assert(n == null); + assert(commits == null); + case VersionType.development: + assert(m != null); + assert(n != null); + assert(commits == null); + case VersionType.latest: + assert(m != null); + assert(n != null); + assert(commits != null); + case VersionType.gitDescribe: + assert(commits != null); + } + } + + /// Create a new [Version] from a version string. + /// + /// It is expected that [versionString] will be generated by + /// `flutter --version` and match one of `stablePattern`, `developmentPattern` + /// and `latestPattern`. + factory Version.fromString(String versionString) { + versionString = versionString.trim(); + // stable tag + Match? match = versionPatterns[VersionType.stable]!.firstMatch(versionString); + if (match != null) { + // parse stable + final List parts = + match.groups([1, 2, 3]).map((String? s) => int.parse(s!)).toList(); + return Version(x: parts[0], y: parts[1], z: parts[2], type: VersionType.stable); + } + // development tag + match = versionPatterns[VersionType.development]!.firstMatch(versionString); + if (match != null) { + // parse development + final List parts = + match.groups([1, 2, 3, 4, 5]).map((String? s) => int.parse(s!)).toList(); + return Version( + x: parts[0], + y: parts[1], + z: parts[2], + m: parts[3], + n: parts[4], + type: VersionType.development, + ); + } + // latest tag + match = versionPatterns[VersionType.latest]!.firstMatch(versionString); + if (match != null) { + // parse latest + final List parts = + match.groups([1, 2, 3, 4, 5, 6]).map((String? s) => int.parse(s!)).toList(); + return Version( + x: parts[0], + y: parts[1], + z: parts[2], + m: parts[3], + n: parts[4], + commits: parts[5], + type: VersionType.latest, + ); + } + match = versionPatterns[VersionType.gitDescribe]!.firstMatch(versionString); + if (match != null) { + // parse latest + final int x = int.parse(match.group(1)!); + final int y = int.parse(match.group(2)!); + final int z = int.parse(match.group(3)!); + final int? m = int.tryParse(match.group(5) ?? ''); + final int? n = int.tryParse(match.group(6) ?? ''); + final int commits = int.parse(match.group(7)!); + return Version(x: x, y: y, z: z, m: m, n: n, commits: commits, type: VersionType.gitDescribe); + } + throw Exception('${versionString.trim()} cannot be parsed'); + } + + // Returns a new version with the given [increment] part incremented. + // NOTE new version must be of same type as previousVersion. + factory Version.increment( + Version previousVersion, + String increment, { + VersionType? nextVersionType, + }) { + final int nextX = previousVersion.x; + int nextY = previousVersion.y; + int nextZ = previousVersion.z; + int? nextM = previousVersion.m; + int? nextN = previousVersion.n; + nextVersionType ??= switch (previousVersion.type) { + VersionType.stable => VersionType.stable, + VersionType.latest || + VersionType.gitDescribe || + VersionType.development => VersionType.development, + }; + + switch (increment) { + case 'x': + // This was probably a mistake. + throw Exception('Incrementing x is not supported by this tool.'); + case 'y': + // Dev release following a beta release. + nextY += 1; + nextZ = 0; + if (previousVersion.type != VersionType.stable) { + nextM = 0; + nextN = 0; + } + case 'z': + // Hotfix to stable release. + assert(previousVersion.type == VersionType.stable); + nextZ += 1; + case 'm': + assert( + false, + "Do not increment 'm' via Version.increment, use instead Version.fromCandidateBranch()", + ); + case 'n': + // Hotfix to internal roll. + nextN = nextN! + 1; + default: + throw Exception('Unknown increment level $increment.'); + } + return Version(x: nextX, y: nextY, z: nextZ, m: nextM, n: nextN, type: nextVersionType); + } + + factory Version.fromCandidateBranch(String branchName) { + // Regular dev release. + final RegExp pattern = RegExp(r'flutter-(\d+)\.(\d+)-candidate.(\d+)'); + final RegExpMatch? match = pattern.firstMatch(branchName); + late final int x; + late final int y; + late final int m; + try { + x = int.parse(match!.group(1)!); + y = int.parse(match.group(2)!); + m = int.parse(match.group(3)!); + } on Exception { + throw ConductorException( + 'branch named $branchName not recognized as a valid candidate branch', + ); + } + + return Version(type: VersionType.development, x: x, y: y, z: 0, m: m, n: 0); + } + + /// Major version. + final int x; + + /// Zero-indexed count of beta releases after a major release. + final int y; + + /// Number of hotfix releases after a stable release. + /// + /// For non-stable releases, this will be 0. + final int z; + + /// Zero-indexed count of dev releases after a beta release. + /// + /// For stable releases, this will be null. + final int? m; + + /// Number of hotfixes required to make a dev release. + /// + /// For stable releases, this will be null. + final int? n; + + /// Number of commits past last tagged dev release. + final int? commits; + + final VersionType type; + + /// Validate that the parsed version is valid. + /// + /// Will throw a [ConductorException] if the version is not possible given the + /// [candidateBranch] and [incrementLetter]. + void ensureValid(String candidateBranch, ReleaseType releaseType) { + final RegExpMatch? branchMatch = releaseCandidateBranchRegex.firstMatch(candidateBranch); + if (branchMatch == null) { + throw ConductorException( + 'Candidate branch $candidateBranch does not match the pattern ' + '${releaseCandidateBranchRegex.pattern}', + ); + } + + // These groups are required in the pattern, so these match groups should + // not be null + final String branchX = branchMatch.group(1)!; + if (x != int.tryParse(branchX)) { + throw ConductorException( + 'Parsed version $this has a different x value than candidate ' + 'branch $candidateBranch', + ); + } + final String branchY = branchMatch.group(2)!; + if (y != int.tryParse(branchY)) { + throw ConductorException( + 'Parsed version $this has a different y value than candidate ' + 'branch $candidateBranch', + ); + } + + // stable type versions don't have an m field set + if (type != VersionType.stable && + releaseType != ReleaseType.STABLE_HOTFIX && + releaseType != ReleaseType.STABLE_INITIAL) { + final String branchM = branchMatch.group(3)!; + if (m != int.tryParse(branchM)) { + throw ConductorException( + 'Parsed version $this has a different m value than candidate ' + 'branch $candidateBranch with type $type', + ); + } + } + } + + @override + String toString() { + return switch (type) { + VersionType.stable => '$x.$y.$z', + VersionType.development => '$x.$y.$z-$m.$n.pre', + VersionType.latest => '$x.$y.$z-$m.$n.pre.$commits', + VersionType.gitDescribe => '$x.$y.$z-$m.$n.pre.$commits', + }; + } +} diff --git a/flutter/dev/conductor/core/pubspec.yaml b/flutter/dev/conductor/core/pubspec.yaml new file mode 100644 index 00000000..5a9b6a9f --- /dev/null +++ b/flutter/dev/conductor/core/pubspec.yaml @@ -0,0 +1,70 @@ +name: conductor_core +description: Flutter Automated Release Tool + +publish_to: none + +environment: + sdk: ^3.7.0-0 + +dependencies: + archive: 3.6.1 + args: 2.6.0 + http: 1.2.2 + intl: 0.19.0 + meta: 1.16.0 + path: 1.9.1 + process: 5.0.3 + protobuf: 3.1.0 + + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fixnum: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 3.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + test: 1.25.14 + test_api: 0.7.4 + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: 0ea7 diff --git a/flutter/dev/customer_testing/lib/runner.dart b/flutter/dev/customer_testing/lib/runner.dart new file mode 100644 index 00000000..9da6a821 --- /dev/null +++ b/flutter/dev/customer_testing/lib/runner.dart @@ -0,0 +1,267 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'customer_test.dart'; + +Future runTests({ + int repeat = 1, + bool skipOnFetchFailure = false, + bool verbose = false, + int numberShards = 1, + int shardIndex = 0, + required List files, +}) async { + if (verbose) { + print('Starting run_tests.dart...'); + } + + // Best attempt at evenly splitting tests among the shards + final List shardedFiles = []; + for (int i = shardIndex; i < files.length; i += numberShards) { + shardedFiles.add(files[i]); + } + + int testCount = 0; + int failures = 0; + + if (verbose) { + final String s = files.length == 1 ? '' : 's'; + if (numberShards > 1) { + final String ss = shardedFiles.length == 1 ? '' : 's'; + print( + '${files.length} file$s specified. ${shardedFiles.length} test$ss in shard #$shardIndex ($numberShards shards total).', + ); + } else { + print('${files.length} file$s specified.'); + } + print(''); + } + + if (verbose) { + if (numberShards > 1) { + print('Tests in this shard:'); + } else { + print('Tests:'); + } + for (final File file in shardedFiles) { + print(file.path); + } + } + print(''); + + for (final File file in shardedFiles) { + // Always print name of running task for debugging individual customer test + // suites. + print('Processing ${file.path}...'); + + void failure(String message) { + print('ERROR: $message'); + failures += 1; + } + + CustomerTest instructions; + try { + instructions = CustomerTest(file); + } on FormatException catch (error) { + failure(error.message); + print(''); + continue; + } on FileSystemException catch (error) { + failure(error.message); + print(''); + continue; + } + + bool success = true; + + final Directory checkout = Directory.systemTemp.createTempSync( + 'flutter_customer_testing.${path.basenameWithoutExtension(file.path)}.', + ); + if (verbose) { + print('Created temporary directory: ${checkout.path}'); + } + try { + assert(instructions.fetch.isNotEmpty); + for (final String fetchCommand in instructions.fetch) { + success = await shell( + fetchCommand, + checkout, + verbose: verbose, + silentFailure: skipOnFetchFailure, + ); + if (!success) { + if (skipOnFetchFailure) { + if (verbose) { + print('Skipping (fetch failed).'); + } else { + print('Skipping ${file.path} (fetch failed).'); + } + } else { + failure('Failed to fetch repository.'); + } + break; + } + } + if (success) { + final Directory customerRepo = Directory(path.join(checkout.path, 'tests')); + for (final String setupCommand in instructions.setup) { + if (verbose) { + print('Running setup command: $setupCommand'); + } + success = await shell(setupCommand, customerRepo, verbose: verbose); + if (!success) { + failure('Setup command failed: $setupCommand'); + break; + } + } + for (final Directory updateDirectory in instructions.update) { + final Directory resolvedUpdateDirectory = Directory( + path.join(customerRepo.path, updateDirectory.path), + ); + if (verbose) { + print('Updating code in ${resolvedUpdateDirectory.path}...'); + } + if (!File(path.join(resolvedUpdateDirectory.path, 'pubspec.yaml')).existsSync()) { + failure( + 'The directory ${updateDirectory.path}, which was specified as an update directory, does not contain a "pubspec.yaml" file.', + ); + success = false; + break; + } + success = await shell('flutter packages get', resolvedUpdateDirectory, verbose: verbose); + if (!success) { + failure( + 'Could not run "flutter pub get" in ${updateDirectory.path}, which was specified as an update directory.', + ); + break; + } + success = await shell('dart fix --apply', resolvedUpdateDirectory, verbose: verbose); + if (!success) { + failure( + 'Could not run "dart fix" in ${updateDirectory.path}, which was specified as an update directory.', + ); + break; + } + } + if (success) { + if (verbose) { + print('Running tests...'); + } + if (instructions.iterations != null && instructions.iterations! < repeat) { + if (verbose) { + final String s = instructions.iterations == 1 ? '' : 's'; + print( + 'Limiting to ${instructions.iterations} round$s rather than $repeat rounds because of "iterations" directive.', + ); + } + repeat = instructions.iterations!; + } + final Stopwatch stopwatch = Stopwatch()..start(); + for (int iteration = 0; iteration < repeat; iteration += 1) { + if (verbose && repeat > 1) { + print('Round ${iteration + 1} of $repeat.'); + } + for (final String testCommand in instructions.tests) { + testCount += 1; + success = await shell(testCommand, customerRepo, verbose: verbose); + if (!success) { + failure( + 'One or more tests from ${path.basenameWithoutExtension(file.path)} failed.', + ); + break; + } + } + } + stopwatch.stop(); + // Always print test runtime for debugging. + print( + 'Tests finished in ${(stopwatch.elapsed.inSeconds / repeat).toStringAsFixed(2)} seconds per iteration.', + ); + } + } + } finally { + if (verbose) { + print('Deleting temporary directory...'); + } + try { + checkout.deleteSync(recursive: true); + } on FileSystemException { + print('Failed to delete "${checkout.path}".'); + } + } + if (!success) { + final String s = instructions.contacts.length == 1 ? '' : 's'; + print('Contact$s: ${instructions.contacts.join(", ")}'); + } + if (verbose || !success) { + print(''); + } + } + if (failures > 0) { + final String s = failures == 1 ? '' : 's'; + print('$failures failure$s.'); + return false; + } + print('$testCount tests all passed!'); + return true; +} + +final RegExp _spaces = RegExp(r' +'); + +Future shell( + String command, + Directory directory, { + bool verbose = false, + bool silentFailure = false, + void Function()? failedCallback, +}) async { + if (verbose) { + print('>> $command'); + } + Process process; + if (Platform.isWindows) { + process = await Process.start('CMD.EXE', [ + '/S', + '/C', + command, + ], workingDirectory: directory.path); + } else { + final List segments = command.trim().split(_spaces); + process = await Process.start( + segments.first, + segments.skip(1).toList(), + workingDirectory: directory.path, + ); + } + final List output = []; + utf8.decoder + .bind(process.stdout) + .transform(const LineSplitter()) + .listen(verbose ? printLog : output.add); + utf8.decoder + .bind(process.stderr) + .transform(const LineSplitter()) + .listen(verbose ? printLog : output.add); + final bool success = await process.exitCode == 0; + if (success || silentFailure) { + return success; + } + if (!verbose) { + if (failedCallback != null) { + failedCallback(); + } + print('>> $command'); + output.forEach(printLog); + } + return success; +} + +void printLog(String line) { + print('| $line'.trimRight()); +} diff --git a/flutter/dev/customer_testing/pubspec.yaml b/flutter/dev/customer_testing/pubspec.yaml new file mode 100644 index 00000000..b4cf3f67 --- /dev/null +++ b/flutter/dev/customer_testing/pubspec.yaml @@ -0,0 +1,60 @@ +name: customer_testing +description: Tool to run the tests listed in the flutter/tests repository. + +environment: + sdk: ^3.7.0-0 + +dependencies: + args: 2.6.0 + path: 1.9.1 + glob: 2.1.2 + meta: 1.16.0 + + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + test: 1.25.14 + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: 1221 diff --git a/flutter/dev/customer_testing/run_tests.dart b/flutter/dev/customer_testing/run_tests.dart new file mode 100644 index 00000000..1509324a --- /dev/null +++ b/flutter/dev/customer_testing/run_tests.dart @@ -0,0 +1,136 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:args/args.dart'; +import 'package:file/local.dart'; +import 'package:glob/glob.dart'; +import 'package:path/path.dart' as path; + +import 'lib/runner.dart'; + +Future main(List arguments) async { + exit(await run(arguments) ? 0 : 1); +} + +// Return true if successful, false if failed. +Future run(List arguments) async { + final ArgParser argParser = + ArgParser(allowTrailingOptions: false, usageLineLength: 72) + ..addOption( + 'repeat', + defaultsTo: '1', + help: + 'How many times to run each test. Set to a high value to look for flakes. If a test specifies a number of iterations, the lower of the two values is used.', + valueHelp: 'count', + ) + ..addOption( + 'shards', + defaultsTo: '1', + help: 'How many shards to split the tests into. Used in continuous integration.', + valueHelp: 'count', + ) + ..addOption( + 'shard-index', + defaultsTo: '0', + help: + 'The current shard to run the tests with the range [0 .. shards - 1]. Used in continuous integration.', + valueHelp: 'count', + ) + ..addFlag('skip-on-fetch-failure', help: 'Whether to skip tests that we fail to download.') + ..addFlag('skip-template', help: 'Whether to skip tests named "template.test".') + ..addFlag('verbose', help: 'Describe what is happening in detail.') + ..addFlag('help', negatable: false, help: 'Print this help message.'); + + void printHelp() { + print('run_tests.dart [options...] path/to/file1.test path/to/file2.test...'); + print('For details on the test registry format, see:'); + print(' https://github.com/flutter/tests/blob/main/registry/template.test'); + print(''); + print(argParser.usage); + print(''); + } + + ArgResults parsedArguments; + try { + parsedArguments = argParser.parse(arguments); + } on ArgParserException catch (error) { + printHelp(); + print('Error: ${error.message} Use --help for usage information.'); + exit(1); + } + + final int? repeat = int.tryParse(parsedArguments['repeat'] as String); + final bool skipOnFetchFailure = parsedArguments['skip-on-fetch-failure'] as bool; + final bool skipTemplate = parsedArguments['skip-template'] as bool; + final bool verbose = parsedArguments['verbose'] as bool; + final bool help = parsedArguments['help'] as bool; + final int? numberShards = int.tryParse(parsedArguments['shards'] as String); + final int? shardIndex = int.tryParse(parsedArguments['shard-index'] as String); + final List files = + parsedArguments.rest + .expand((String path) => Glob(path).listFileSystemSync(const LocalFileSystem())) + .whereType() + .where((File file) => !skipTemplate || path.basename(file.path) != 'template.test') + .toList(); + + if (help || + repeat == null || + files.isEmpty || + numberShards == null || + numberShards <= 0 || + shardIndex == null || + shardIndex < 0) { + printHelp(); + if (verbose) { + if (repeat == null) { + print('Error: Could not parse repeat count ("${parsedArguments['repeat']}")'); + } + if (numberShards == null) { + print('Error: Could not parse shards count ("${parsedArguments['shards']}")'); + } else if (numberShards < 1) { + print( + 'Error: The specified shards count ($numberShards) is less than 1. It must be greater than zero.', + ); + } + if (shardIndex == null) { + print('Error: Could not parse shard index ("${parsedArguments['shard-index']}")'); + } else if (shardIndex < 0) { + print( + 'Error: The specified shard index ($shardIndex) is negative. It must be in the range [0 .. shards - 1].', + ); + } + if (parsedArguments.rest.isEmpty) { + print('Error: No file arguments specified.'); + } else if (files.isEmpty) { + print( + 'Error: File arguments ("${parsedArguments.rest.join('", "')}") did not identify any real files.', + ); + } + } + return help; + } + + if (shardIndex > numberShards - 1) { + print( + 'Error: The specified shard index ($shardIndex) is more than the specified number of shards ($numberShards). ' + 'It must be in the range [0 .. shards - 1].', + ); + return false; + } + + if (files.length < numberShards) { + print('Warning: There are more shards than tests. Some shards will not run any tests.'); + } + + return runTests( + repeat: repeat, + skipOnFetchFailure: skipOnFetchFailure, + verbose: verbose, + numberShards: numberShards, + shardIndex: shardIndex, + files: files, + ); +} diff --git a/flutter/dev/customer_testing/test/common.dart b/flutter/dev/customer_testing/test/common.dart new file mode 100644 index 00000000..cd6fd458 --- /dev/null +++ b/flutter/dev/customer_testing/test/common.dart @@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter 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:test/test.dart' hide isInstanceOf; + +export 'package:test/test.dart' hide isInstanceOf; + +/// A matcher that compares the type of the actual value to the type argument T. +TypeMatcher isInstanceOf() => isA(); diff --git a/flutter/dev/customer_testing/test/customer_test_test.dart b/flutter/dev/customer_testing/test/customer_test_test.dart new file mode 100644 index 00000000..e2be085a --- /dev/null +++ b/flutter/dev/customer_testing/test/customer_test_test.dart @@ -0,0 +1,118 @@ +// Copyright 2014 The Flutter 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:io'; + +import 'package:customer_testing/customer_test.dart'; +import 'package:file/file.dart'; +import 'package:file/memory.dart'; + +import 'common.dart'; + +void main() { + test('constructs expected model', () async { + const String registryContent = ''' +contact=abc@gmail.com +fetch=git clone https://github.com/flutter/cocoon.git tests +fetch=git -C tests checkout abc123 +setup=flutter --version +setup.windows=flutter doctor +setup.posix=flutter -h +setup.linux=flutter analyze -h +setup.macos=flutter build -h +update=. +# Runs flutter analyze, flutter test, and builds web platform +test.posix=./test_utilities/bin/flutter_test_runner.sh app_flutter +test.posix=./test_utilities/bin/flutter_test_runner.sh repo_dashboard +test.windows=.\test_utilities\bin\flutter_test_runner.bat repo_dashboard + '''; + final File registryFile = MemoryFileSystem().file('flutter_cocoon.test') + ..writeAsStringSync(registryContent); + + final CustomerTest test = CustomerTest(registryFile); + expect(test.contacts, containsAll(['abc@gmail.com'])); + expect( + test.fetch, + containsAllInOrder([ + 'git clone https://github.com/flutter/cocoon.git tests', + 'git -C tests checkout abc123', + ]), + ); + expect(test.setup.first, 'flutter --version'); + if (Platform.isLinux || Platform.isMacOS) { + expect(test.setup.length, 3); + expect(test.setup[1], 'flutter -h'); + expect(test.setup[2], Platform.isLinux ? 'flutter analyze -h' : 'flutter build -h'); + expect( + test.tests, + containsAllInOrder([ + './test_utilities/bin/flutter_test_runner.sh app_flutter', + './test_utilities/bin/flutter_test_runner.sh repo_dashboard', + ]), + ); + } else if (Platform.isWindows) { + expect(test.setup.length, 2); + expect(test.setup[1], 'flutter doctor'); + expect( + test.tests, + containsAllInOrder(['.\test_utilities\bin\flutter_test_runner.bat repo_dashboard']), + ); + } + }); + + test('throws exception when unknown field is passed', () async { + const String registryContent = ''' +contact=abc@gmail.com +update=. +fetch=git clone https://github.com/flutter/cocoon.git tests +fetch=git -C tests checkout abc123 +test.posix=./test_utilities/bin/flutter_test_runner.sh app_flutter +test.windows=.\test_utilities\bin\flutter_test_runner.bat repo_dashboard +unknownfield=super not cool + '''; + final File registryFile = MemoryFileSystem().file('abc.test') + ..writeAsStringSync(registryContent); + + expect(() => CustomerTest(registryFile), throwsFormatException); + }); + + test('throws exception when no tests given', () async { + const String registryContent = ''' +contact=abc@gmail.com +update=. +fetch=git clone https://github.com/flutter/cocoon.git tests +'''; + final File registryFile = MemoryFileSystem().file('abc.test') + ..writeAsStringSync(registryContent); + + expect(() => CustomerTest(registryFile), throwsFormatException); + }); + + test('throws exception when only one fetch instruction given', () async { + const String registryContent = ''' +contact=abc@gmail.com +update=. +fetch=git clone https://github.com/flutter/cocoon.git tests +test.posix=./test_utilities/bin/flutter_test_runner.sh app_flutter +test.windows=.\test_utilities\bin\flutter_test_runner.bat repo_dashboard + '''; + final File registryFile = MemoryFileSystem().file('abc.test') + ..writeAsStringSync(registryContent); + + expect(() => CustomerTest(registryFile), throwsFormatException); + }); + + test('throws exception when no contacts given', () async { + const String registryContent = ''' +update=. +fetch=git clone https://github.com/flutter/cocoon.git tests +test.posix=./test_utilities/bin/flutter_test_runner.sh app_flutter +test.windows=.\test_utilities\bin\flutter_test_runner.bat repo_dashboard + '''; + final File registryFile = MemoryFileSystem().file('abc.test') + ..writeAsStringSync(registryContent); + + expect(() => CustomerTest(registryFile), throwsFormatException); + }); +} diff --git a/flutter/dev/devicelab/README.md b/flutter/dev/devicelab/README.md new file mode 100644 index 00000000..92e01c3b --- /dev/null +++ b/flutter/dev/devicelab/README.md @@ -0,0 +1,263 @@ +# Flutter DeviceLab + +DeviceLab is a physical lab that tests Flutter on real devices. + +This package contains the code for the test framework and tests. More generally +the tests are referred to as "tasks" in the API, but since we primarily use it +for testing, this document refers to them as "tests". + +Current statuses for the devicelab are available at +. See [dashboard user +guide](https://github.com/flutter/cocoon/blob/main/dashboard/USER_GUIDE.md) +for information on using the dashboards. + +## Table of Contents + +* [How the DeviceLab runs tests](#how-the-devicelab-runs-tests) +* [Running tests locally](#running-tests-locally) +* [Writing tests](#writing-tests) +* [Adding tests to continuous + integration](#adding-tests-to-continuous-integration) +* [Adding tests to presubmit](#adding-tests-to-presubmit) +* [Migrating to build and test model](#migrating-to-build-and-test-model) + +## How the DeviceLab runs tests + +DeviceLab tests are run against physical devices in Flutter's lab (the +"DeviceLab"). + +Tasks specify the type of device they are to run on (`linux_android`, `mac_ios`, +`mac_android`, `windows_android`, etc). When a device in the lab is free, it +will pick up tasks that need to be completed. + +1. If the task succeeds, the test runner reports the success and uploads its +performance metrics to Flutter's infrastructure. Not all tasks record +performance metrics. +2. If task fails, an auto rerun happens. Whenever the last run succeeds, the +task will be reported as a success. For this case, a flake will be flagged and +populated to the test result. +3. If the task fails in all reruns, the test runner reports the failure to + Flutter's infrastructure and no performance metrics are collected + +## Running tests locally + +Do make sure your tests pass locally before deploying to the CI environment. +Below is a handful of commands that run tests in a similar way to how the +CI environment runs them. These commands are also useful when you need to +reproduce a CI test failure locally. + +### Prerequisites + +You must set the `ANDROID_SDK_ROOT` environment variable to run +tests on Android. If you have a local build of the Flutter engine, then you have +a copy of the Android SDK at `.../engine/src/third_party/android_tools/sdk`. + +You can find where your Android SDK is using `flutter doctor -v`. + +### Warnings + +Running the devicelab will do things to your environment. + +Notably, it will start and stop Gradle, for instance. + +### Running tests in `test/...` + +`dart test test/{NAME_OF_TEST}` + +### Running specific tests + +To run a test, use option `-t` (`--task`): + +```sh +# from the .../flutter/dev/devicelab directory +../../bin/cache/dart-sdk/bin/dart bin/test_runner.dart test -t {NAME_OF_TEST} +``` + +Where `NAME_OR_PATH_OF_TEST` is the name of a task, which is a file's +basename in `bin/tasks`. Example: `complex_layout__start_up`. + +To run multiple tests, repeat option `-t` (`--task`) multiple times: + +```sh +../../bin/cache/dart-sdk/bin/dart bin/run.dart -t test1 -t test2 -t test3 +``` + +### Running tests against a local engine build + +To run device lab tests against a local engine build, pass the appropriate +flags to `bin/run.dart`: + +```sh +../../bin/cache/dart-sdk/bin/dart bin/run.dart --task=[some_task] \ + --local-engine-src-path=[path_to_local]/engine/src \ + --local-engine=[local_engine_architecture] \ + --local-engine-host=[local_engine_host_architecture] +``` + +An example of a local engine architecture is `android_debug_unopt_x86` and +an example of a local engine host architecture is `host_debug_unopt`. + +### Running an A/B test for engine changes + +You can run an A/B test that compares the performance of the default engine +against a local engine build. The test runs the same benchmark a specified +number of times against both engines, then outputs a tab-separated spreadsheet +with the results and stores them in a JSON file for future reference. The +results can be copied to a Google Spreadsheet for further inspection and the +JSON file can be reprocessed with the `summarize.dart` command for more detailed +output. + +Example: + +```sh +../../bin/cache/dart-sdk/bin/dart bin/run.dart --ab=10 \ + --local-engine=host_debug_unopt \ + --local-engine-host=host_debug_unopt \ + -t bin/tasks/web_benchmarks_canvaskit.dart +``` + +The `--ab=10` tells the runner to run an A/B test 10 times. + +`--local-engine=host_debug_unopt` tells the A/B test to use the +`host_debug_unopt` engine build. `--local-engine-host=host_debug_unopt` uses +the same engine build to run the `frontend_server` (in this example). +`--local-engine` is required for A/B test. + +`--ab-result-file=filename` can be used to provide an alternate location to +output the JSON results file (defaults to `ABresults#.json`). A single `#` +character can be used to indicate where to insert a serial number if a file with +that name already exists, otherwise, the file will be overwritten. + +A/B can run exactly one task. Multiple tasks are not supported. + +Example output: + +```text +Score Average A (noise) Average B (noise) Speed-up +bench_card_infinite_scroll.canvaskit.drawFrameDuration.average 2900.20 (8.44%) 2426.70 (8.94%) 1.20x +bench_card_infinite_scroll.canvaskit.totalUiFrame.average 4964.00 (6.29%) 4098.00 (8.03%) 1.21x +draw_rect.canvaskit.windowRenderDuration.average 1959.45 (16.56%) 2286.65 (0.61%) 0.86x +draw_rect.canvaskit.sceneBuildDuration.average 1969.45 (16.37%) 2294.90 (0.58%) 0.86x +draw_rect.canvaskit.drawFrameDuration.average 5335.20 (17.59%) 6437.60 (0.59%) 0.83x +draw_rect.canvaskit.totalUiFrame.average 6832.00 (13.16%) 7932.00 (0.34%) 0.86x +``` + +The output contains averages and noises for each score. More importantly, it +contains the speed-up value, i.e. how much _faster_ is the local engine than +the default engine. Values less than 1.0 indicate a slow-down. For example, +0.5x means the local engine is twice as slow as the default engine, and 2.0x +means it's twice as fast. Higher is better. + +Summarize tool example: + +```sh +../../bin/cache/dart-sdk/bin/dart bin/summarize.dart --[no-]tsv-table --[no-]raw-summary \ + ABresults.json ABresults1.json ABresults2.json ... +``` + +`--[no-]tsv-table` tells the tool to print the summary in a table with tabs for +easy spreadsheet entry. (defaults to on) + +`--[no-]raw-summary` tells the tool to print all per-run data collected by the +A/B test formatted with tabs for easy spreadsheet entry. (defaults to on) + +Multiple trailing filenames can be specified and each such results file will be +processed in turn. + +## Reproducing broken builds locally + +To reproduce the breakage locally `git checkout` the corresponding Flutter +revision. Note the name of the test that failed. In the example above the +failing test is `flutter_gallery__transition_perf`. This name can be passed to +the `run.dart` command. For example: + +```sh +../../bin/cache/dart-sdk/bin/dart bin/run.dart -t flutter_gallery__transition_perf +``` + +## Writing tests + +A test is a simple Dart program that lives under `bin/tasks` and uses +`package:flutter_devicelab/framework/framework.dart` to define and run a _task_. + +Example: + +```dart +import 'dart:async'; + +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + await task(() async { + ... do something interesting ... + + // Aggregate results into a JSONable Map structure. + Map testResults = ...; + + // Report success. + return new TaskResult.success(testResults); + + // Or you can also report a failure. + return new TaskResult.failure('Something went wrong!'); + }); +} +``` + +Only one `task` is permitted per program. However, that task can run any number +of tests internally. A task has a name. It succeeds and fails independently of +other tasks, and is reported to the dashboard independently of other tasks. + +A task runs in its own standalone Dart VM and reports results via Dart VM +service protocol. This ensures that tasks do not interfere with each other and +lets the CI system time out and clean up tasks that get stuck. + +## Adding tests to continuous integration + +Host only tests should be added to `flutter_tools`. + +There are several PRs needed to add a DeviceLab task to CI. + +_TASK_- the name of your test that also matches the name of the + file in `bin/tasks` without the `.dart` extension. + +1. Add target to + [.ci.yaml](https://github.com/flutter/flutter/blob/main/.ci.yaml) + * Mirror an existing one that has the recipe `devicelab_drone` + +If your test needs to run on multiple operating systems, create a separate +target for each operating system. + +## Adding tests to presubmit + +Flutter's DeviceLab has a limited capacity in presubmit. File an infra ticket +to investigate feasibility of adding a test to presubmit. + +## Migrating to build and test model + +To better utilize limited DeviceLab testbed resources and speed up commit validation +time, it is now supported to separate building artifacts (.apk/.app) from testing them. +The artifact will be built on a host only bot, a VM or physical bot without a device, +and the test will run based on the artifact against a testbed with a device. + +Steps: + +1. Update the task class to extend [`BuildTestTask`](https://github.com/flutter/flutter/blob/main/dev/devicelab/lib/tasks/build_test_task.dart) + - Override function `getBuildArgs` + - Override function `getTestArgs` + - Override function `parseTaskResult` + - Override function `getApplicationBinaryPath` +2. Update the `bin/tasks/{TEST}.dart` to point to the new task class +3. Validate the task locally + - build only: `dart bin/test_runner.dart test -t {NAME_OR_PATH_OF_TEST} --task-args build --task-args application-binary-path={PATH_TO_ARTIFACT}` + - test only: `dart bin/test_runner.dart test -t {NAME_OR_PATH_OF_TEST} --task-args test --task-args application-binary-path={PATH_TO_ARTIFACT}` +4. Add tasks to continuous integration + - Mirror a target with platform `Linux_build_test` or `Mac_build_test` + - The only difference from regular targets is the artifact property: if omitted, it will use the `task_name`. +5. Once validated in CI, enable the target in `PROD` by removing `bringup: true` and deleting the old target entry without build+test model. + +Take gallery tasks for example: + +1. Linux android + - Separating PR: https://github.com/flutter/flutter/pull/103550 + - Switching PR: https://github.com/flutter/flutter/pull/110533 +2. Mac iOS: https://github.com/flutter/flutter/pull/111164 diff --git a/flutter/dev/devicelab/analysis_options.yaml b/flutter/dev/devicelab/analysis_options.yaml new file mode 100644 index 00000000..2e46286d --- /dev/null +++ b/flutter/dev/devicelab/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../analysis_options.yaml + +linter: + rules: + - unawaited_futures diff --git a/flutter/dev/devicelab/lib/tasks/platform_channels_benchmarks.dart b/flutter/dev/devicelab/lib/tasks/platform_channels_benchmarks.dart new file mode 100644 index 00000000..ce36e9d4 --- /dev/null +++ b/flutter/dev/devicelab/lib/tasks/platform_channels_benchmarks.dart @@ -0,0 +1,49 @@ +// Copyright 2014 The Flutter 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:io' show Directory, Process; + +import 'package:path/path.dart' as path; + +import '../framework/devices.dart' as adb; +import '../framework/framework.dart' show TaskFunction; +import '../framework/task_result.dart' show TaskResult; +import '../framework/utils.dart' as utils; +import '../microbenchmarks.dart' as microbenchmarks; + +TaskFunction runTask(adb.DeviceOperatingSystem operatingSystem) { + return () async { + adb.deviceOperatingSystem = operatingSystem; + final adb.Device device = await adb.devices.workingDevice; + await device.unlock(); + + final Directory appDir = utils.dir( + path.join(utils.flutterDirectory.path, 'dev/benchmarks/platform_channels_benchmarks'), + ); + final Process flutterProcess = await utils.inDirectory(appDir, () async { + final List createArgs = [ + '--platforms', + 'ios,android', + '--no-overwrite', + '-v', + '.', + ]; + print('\nExecuting: flutter create $createArgs $appDir'); + await utils.flutter('create', options: createArgs); + + final List options = [ + '-v', + // --release doesn't work on iOS due to code signing issues + '--profile', + '--no-publish-port', + '-d', + device.deviceId, + ]; + return utils.startFlutter('run', options: options); + }); + + final Map results = await microbenchmarks.readJsonResults(flutterProcess); + return TaskResult.success(results, benchmarkScoreKeys: results.keys.toList()); + }; +} diff --git a/flutter/dev/devicelab/lib/tasks/plugin_tests.dart b/flutter/dev/devicelab/lib/tasks/plugin_tests.dart new file mode 100644 index 00000000..9d02c117 --- /dev/null +++ b/flutter/dev/devicelab/lib/tasks/plugin_tests.dart @@ -0,0 +1,637 @@ +// Copyright 2014 The Flutter 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:ffi'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../framework/framework.dart'; +import '../framework/ios.dart'; +import '../framework/task_result.dart'; +import '../framework/utils.dart'; + +/// Combines several TaskFunctions with trivial success value into one. +TaskFunction combine(List tasks) { + return () async { + for (final TaskFunction task in tasks) { + final TaskResult result = await task(); + if (result.failed) { + return result; + } + } + return TaskResult.success(null); + }; +} + +/// Defines task that creates new Flutter project, adds a local and remote +/// plugin, and then builds the specified [buildTarget]. +class PluginTest { + PluginTest( + this.buildTarget, + this.options, { + this.pluginCreateEnvironment, + this.appCreateEnvironment, + this.dartOnlyPlugin = false, + this.sharedDarwinSource = false, + this.template = 'plugin', + this.cocoapodsTransitiveFlutterDependency = false, + }); + + final String buildTarget; + final List options; + final Map? pluginCreateEnvironment; + final Map? appCreateEnvironment; + final bool dartOnlyPlugin; + final bool sharedDarwinSource; + final String template; + final bool cocoapodsTransitiveFlutterDependency; + + Future call() async { + final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_plugin_test.'); + // FFI plugins do not have support for `flutter test`. + // `flutter test` does not do a native build. + // Supporting `flutter test` would require invoking a native build. + final bool runFlutterTest = template != 'plugin_ffi'; + try { + section('Create plugin'); + final _FlutterProject plugin = await _FlutterProject.create( + tempDir, + options, + buildTarget, + name: 'plugintest', + template: template, + environment: pluginCreateEnvironment, + ); + if (dartOnlyPlugin) { + await plugin.convertDefaultPluginToDartPlugin(); + } + if (sharedDarwinSource) { + await plugin.convertDefaultPluginToSharedDarwinPlugin(); + } + section('Test plugin'); + if (runFlutterTest) { + await plugin.runFlutterTest(); + if (!dartOnlyPlugin) { + await plugin.example.runNativeTests(buildTarget); + } + } + section('Create Flutter app'); + final _FlutterProject app = await _FlutterProject.create( + tempDir, + options, + buildTarget, + name: 'plugintestapp', + template: 'app', + environment: appCreateEnvironment, + ); + try { + if (cocoapodsTransitiveFlutterDependency) { + section('Disable Swift Package Manager'); + await app.disableSwiftPackageManager(); + } + + section('Add plugins'); + await app.addPlugin('plugintest', pluginPath: path.join('..', 'plugintest')); + await app.addPlugin('path_provider'); + section('Build app'); + await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin); + if (cocoapodsTransitiveFlutterDependency) { + section('Test app with Flutter as a transitive CocoaPods dependency'); + await app.addCocoapodsTransitiveFlutterDependency(); + await app.build(buildTarget, validateNativeBuildProject: !dartOnlyPlugin); + } + if (runFlutterTest) { + section('Test app'); + await app.runFlutterTest(); + } + // Validate local engine handling. Currently only implemented for macOS. + if (!dartOnlyPlugin) { + section('Validate local engine configuration'); + final String fakeEngineSourcePath = path.join(tempDir.path, 'engine'); + await _testLocalEngineConfiguration(app, fakeEngineSourcePath); + } + } finally { + await plugin.delete(); + await app.delete(); + } + return TaskResult.success(null); + } catch (e) { + return TaskResult.failure(e.toString()); + } finally { + rmTree(tempDir); + } + } + + Future _testLocalEngineConfiguration( + _FlutterProject app, + String fakeEngineSourcePath, + ) async { + // The tool requires that a directory that looks like an engine build + // actually exists when passing --local-engine, so create a fake skeleton. + final Directory buildDir = Directory(path.join(fakeEngineSourcePath, 'out', 'foo')); + buildDir.createSync(recursive: true); + // Currently this test is only implemented for macOS; it can be extended to + // others as needed. + if (buildTarget == 'macos') { + // When using a local engine, podhelper.rb will search for a "macos-" + // directory within the FlutterMacOS.xcframework, so create a dummy one. + Directory( + path.join(buildDir.path, 'FlutterMacOS.xcframework/macos-arm64_x86_64'), + ).createSync(recursive: true); + + // Clean before regenerating the config to ensure that the pod steps run. + await inDirectory(Directory(app.rootPath), () async { + await evalFlutter('clean'); + }); + await app.build(buildTarget, configOnly: true, localEngine: buildDir); + } + } +} + +class _FlutterProject { + _FlutterProject(this.parent, this.name); + + final Directory parent; + final String name; + + String get rootPath => path.join(parent.path, name); + + File get pubspecFile => File(path.join(rootPath, 'pubspec.yaml')); + + _FlutterProject get example { + return _FlutterProject(Directory(path.join(rootPath)), 'example'); + } + + Future disableSwiftPackageManager() async { + final File pubspec = pubspecFile; + String content = await pubspec.readAsString(); + content = content.replaceFirst( + '# The following section is specific to Flutter packages.\n' + 'flutter:\n', + '# The following section is specific to Flutter packages.\n' + 'flutter:\n' + '\n' + ' disable-swift-package-manager: true\n', + ); + await pubspec.writeAsString(content, flush: true); + } + + Future addPlugin(String plugin, {String? pluginPath}) async { + final File pubspec = pubspecFile; + String content = await pubspec.readAsString(); + final String dependency = pluginPath != null ? '$plugin:\n path: $pluginPath' : '$plugin:'; + content = content.replaceFirst('\ndependencies:\n', '\ndependencies:\n $dependency\n'); + await pubspec.writeAsString(content, flush: true); + } + + /// Converts a plugin created from the standard template to a Dart-only + /// plugin. + Future convertDefaultPluginToDartPlugin() async { + final String dartPluginClass = 'DartClassFor$name'; + // Convert the metadata. + final File pubspec = pubspecFile; + String content = await pubspec.readAsString(); + content = content.replaceAll( + RegExp(r' pluginClass: .*?\n'), + ' dartPluginClass: $dartPluginClass\n', + ); + await pubspec.writeAsString(content, flush: true); + + // Add the Dart registration hook that the build will generate a call to. + final File dartCode = File(path.join(rootPath, 'lib', '$name.dart')); + content = await dartCode.readAsString(); + content = ''' +$content + +class $dartPluginClass { + static void registerWith() {} +} +'''; + await dartCode.writeAsString(content, flush: true); + + // Remove any native plugin code. + const List platforms = ['android', 'ios', 'linux', 'macos', 'windows']; + for (final String platform in platforms) { + final Directory platformDir = Directory(path.join(rootPath, platform)); + if (platformDir.existsSync()) { + await platformDir.delete(recursive: true); + } + } + } + + /// Converts an iOS/macOS plugin created from the standard template to a shared + /// darwin directory plugin. + Future convertDefaultPluginToSharedDarwinPlugin() async { + // Convert the metadata. + final File pubspec = pubspecFile; + String pubspecContent = await pubspec.readAsString(); + const String originalIOSKey = '\n ios:\n'; + const String originalMacOSKey = '\n macos:\n'; + if (!pubspecContent.contains(originalIOSKey) || !pubspecContent.contains(originalMacOSKey)) { + print(pubspecContent); + throw TaskResult.failure('Missing expected darwin platform plugin keys'); + } + pubspecContent = pubspecContent.replaceAll( + originalIOSKey, + '$originalIOSKey sharedDarwinSource: true\n', + ); + pubspecContent = pubspecContent.replaceAll( + originalMacOSKey, + '$originalMacOSKey sharedDarwinSource: true\n', + ); + await pubspec.writeAsString(pubspecContent, flush: true); + + // Copy ios to darwin, and delete macos. + final Directory iosDir = Directory(path.join(rootPath, 'ios')); + final Directory darwinDir = Directory(path.join(rootPath, 'darwin')); + recursiveCopy(iosDir, darwinDir); + + await iosDir.delete(recursive: true); + await Directory(path.join(rootPath, 'macos')).delete(recursive: true); + + final File podspec = File(path.join(darwinDir.path, '$name.podspec')); + String podspecContent = await podspec.readAsString(); + if (!podspecContent.contains('s.platform =')) { + print(podspecContent); + throw TaskResult.failure('Missing expected podspec platform'); + } + + // Remove "s.platform = :ios" to work on all platforms, including macOS. + podspecContent = podspecContent.replaceFirst(RegExp(r'.*s\.platform.*'), ''); + podspecContent = podspecContent.replaceFirst( + "s.dependency 'Flutter'", + "s.ios.dependency 'Flutter'\ns.osx.dependency 'FlutterMacOS'", + ); + + await podspec.writeAsString(podspecContent, flush: true); + + // Make PlugintestPlugin.swift compile on iOS and macOS with target conditionals. + // If SwiftPM is disabled, the file will be in `darwin/Classes/`. + // Otherwise, the file will be in `darwin//Sources//`. + final String pluginClass = '${name[0].toUpperCase()}${name.substring(1)}Plugin'; + print('pluginClass: $pluginClass'); + File pluginRegister = File(path.join(darwinDir.path, 'Classes', '$pluginClass.swift')); + if (!pluginRegister.existsSync()) { + pluginRegister = File(path.join(darwinDir.path, name, 'Sources', name, '$pluginClass.swift')); + } + final String pluginRegisterContent = ''' +#if os(macOS) +import FlutterMacOS +#elseif os(iOS) +import Flutter +#endif + +public class $pluginClass: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { +#if os(macOS) + let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger) +#elseif os(iOS) + let channel = FlutterMethodChannel(name: "$name", binaryMessenger: registrar.messenger()) +#endif + let instance = $pluginClass() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { +#if os(macOS) + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) +#elseif os(iOS) + result("iOS " + UIDevice.current.systemVersion) +#endif + } +} +'''; + await pluginRegister.writeAsString(pluginRegisterContent, flush: true); + } + + Future runFlutterTest() async { + await inDirectory(Directory(rootPath), () async { + await flutter('test'); + }); + } + + Future runNativeTests(String buildTarget) async { + // Native unit tests rely on building the app first to generate necessary + // build files. + await build(buildTarget, validateNativeBuildProject: false); + + switch (buildTarget) { + case 'apk': + if (await exec( + path.join('.', 'gradlew'), + ['testDebugUnitTest'], + workingDirectory: path.join(rootPath, 'android'), + canFail: true, + ) != + 0) { + throw TaskResult.failure('Platform unit tests failed'); + } + case 'ios': + String? simulatorDeviceId; + try { + await testWithNewIOSSimulator('TestNativeUnitTests', (String deviceId) async { + simulatorDeviceId = deviceId; + if (!await runXcodeTests( + platformDirectory: path.join(rootPath, 'ios'), + destination: 'id=$deviceId', + configuration: 'Debug', + testName: 'native_plugin_unit_tests_ios', + skipCodesign: true, + )) { + throw TaskResult.failure('Platform unit tests failed'); + } + }); + } finally { + await removeIOSSimulator(simulatorDeviceId); + } + case 'linux': + if (await exec( + path.join( + rootPath, + 'build', + 'linux', + 'x64', + 'release', + 'plugins', + 'plugintest', + 'plugintest_test', + ), + [], + canFail: true, + ) != + 0) { + throw TaskResult.failure('Platform unit tests failed'); + } + case 'macos': + if (!await runXcodeTests( + platformDirectory: path.join(rootPath, 'macos'), + destination: 'platform=macOS', + configuration: 'Debug', + testName: 'native_plugin_unit_tests_macos', + skipCodesign: true, + )) { + throw TaskResult.failure('Platform unit tests failed'); + } + case 'windows': + final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64'; + if (await exec( + path.join( + rootPath, + 'build', + 'windows', + arch, + 'plugins', + 'plugintest', + 'Release', + 'plugintest_test.exe', + ), + [], + canFail: true, + ) != + 0) { + throw TaskResult.failure('Platform unit tests failed'); + } + } + } + + static Future<_FlutterProject> create( + Directory directory, + List options, + String target, { + required String name, + required String template, + Map? environment, + }) async { + await inDirectory(directory, () async { + await flutter( + 'create', + options: [ + '--template=$template', + '--org', + 'io.flutter.devicelab', + ...options, + name, + ], + environment: environment, + ); + }); + + final _FlutterProject project = _FlutterProject(directory, name); + if (template == 'plugin' && (target == 'ios' || target == 'macos')) { + project._reduceDarwinPluginMinimumVersion(name, target); + } + return project; + } + + /// Creates a Pod that uses a Flutter plugin as a dependency and therefore + /// Flutter as a transitive dependency. + Future addCocoapodsTransitiveFlutterDependency() async { + final String iosDirectoryPath = path.join(rootPath, 'ios'); + + final File nativePod = File(path.join(iosDirectoryPath, 'NativePod', 'NativePod.podspec')); + nativePod.createSync(recursive: true); + nativePod.writeAsStringSync(''' +Pod::Spec.new do |s| + s.name = 'NativePod' + s.version = '1.0.0' + s.summary = 'A pod to test Flutter as a transitive dependency.' + s.homepage = 'https://flutter.dev' + s.license = { :type => 'BSD' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = "Classes", "Classes/**/*.{h,m}" + s.dependency 'plugintest' +end +'''); + + final File nativePodClass = File( + path.join(iosDirectoryPath, 'NativePod', 'Classes', 'NativePodTest.m'), + ); + nativePodClass.createSync(recursive: true); + nativePodClass.writeAsStringSync(''' +#import + +@interface NativePodTest : NSObject + +@end + +@implementation NativePodTest + +@end +'''); + + final File podfileFile = File(path.join(iosDirectoryPath, 'Podfile')); + final List podfileContents = podfileFile.readAsLinesSync(); + final int index = podfileContents.indexWhere( + (String line) => line.contains('flutter_install_all_ios_pods'), + ); + podfileContents.insert(index, "pod 'NativePod', :path => 'NativePod'"); + podfileFile.writeAsStringSync(podfileContents.join('\n')); + } + + // Make the platform version artificially low to test that the "deployment + // version too low" warning is never emitted. + void _reduceDarwinPluginMinimumVersion(String plugin, String target) { + final File podspec = File(path.join(rootPath, target, '$plugin.podspec')); + if (!podspec.existsSync()) { + throw TaskResult.failure('podspec file missing at ${podspec.path}'); + } + final String versionString = + target == 'ios' ? "s.platform = :ios, '12.0'" : "s.platform = :osx, '10.11'"; + String podspecContent = podspec.readAsStringSync(); + if (!podspecContent.contains(versionString)) { + throw TaskResult.failure( + 'Update this test to match plugin minimum $target deployment version', + ); + } + // Add transitive dependency on AppAuth 1.6 targeting iOS 8 and macOS 10.9, which no longer builds in Xcode + // to test the version is forced higher and builds. + const String iosContent = ''' +s.platform = :ios, '10.0' +s.dependency 'AppAuth', '1.6.0' +'''; + + const String macosContent = ''' +s.platform = :osx, '10.8' +s.dependency 'AppAuth', '1.6.0' +'''; + + podspecContent = podspecContent.replaceFirst( + versionString, + target == 'ios' ? iosContent : macosContent, + ); + podspec.writeAsStringSync(podspecContent, flush: true); + } + + Future build( + String target, { + bool validateNativeBuildProject = true, + bool configOnly = false, + Directory? localEngine, + }) async { + await inDirectory(Directory(rootPath), () async { + final String buildOutput = await evalFlutter( + 'build', + options: [ + target, + '-v', + if (target == 'ios') '--no-codesign', + if (configOnly) '--config-only', + if (localEngine != null) + // The engine directory is of the form /out/, + // which has to be broken up into the component flags. + ...[ + '--local-engine-src-path=${localEngine.parent.parent.path}', + '--local-engine=${path.basename(localEngine.path)}', + '--local-engine-host=${path.basename(localEngine.path)}', + ], + ], + ); + + if (target == 'ios' || target == 'macos') { + // This warning is confusing and shouldn't be emitted. Plugins often support lower versions than the + // Flutter app, but as long as they support the minimum it will work. + // warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, + // but the range of supported deployment target versions is 9.0 to 14.0.99. + // + // (or "The macOS deployment target 'MACOSX_DEPLOYMENT_TARGET'"...) + if (buildOutput.contains( + 'is set to 10.0, but the range of supported deployment target versions', + ) || + buildOutput.contains( + 'is set to 10.8, but the range of supported deployment target versions', + )) { + throw TaskResult.failure('Minimum plugin version warning present'); + } + + if (validateNativeBuildProject) { + final File generatedSwiftManifest = File( + path.join( + rootPath, + target, + 'Flutter', + 'ephemeral', + 'Packages', + 'FlutterGeneratedPluginSwiftPackage', + 'Package.swift', + ), + ); + final bool swiftPackageManagerEnabled = generatedSwiftManifest.existsSync(); + + if (!swiftPackageManagerEnabled) { + final File podsProject = File( + path.join(rootPath, target, 'Pods', 'Pods.xcodeproj', 'project.pbxproj'), + ); + if (!podsProject.existsSync()) { + throw TaskResult.failure('Xcode Pods project file missing at ${podsProject.path}'); + } + + final String podsProjectContent = podsProject.readAsStringSync(); + if (target == 'ios') { + // Plugins with versions lower than the app version should not have IPHONEOS_DEPLOYMENT_TARGET set. + // The plugintest plugin target should not have IPHONEOS_DEPLOYMENT_TARGET set since it has been lowered + // in _reduceDarwinPluginMinimumVersion to 10, which is below the target version of 11. + if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 10')) { + throw TaskResult.failure( + 'Plugin build setting IPHONEOS_DEPLOYMENT_TARGET not removed', + ); + } + // Transitive dependency AppAuth targeting too-low 8.0 was not fixed. + if (podsProjectContent.contains('IPHONEOS_DEPLOYMENT_TARGET = 8')) { + throw TaskResult.failure( + 'Transitive dependency build setting IPHONEOS_DEPLOYMENT_TARGET=8 not removed', + ); + } + if (!podsProjectContent.contains( + r'"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "$(inherited) i386";', + )) { + throw TaskResult.failure(r'EXCLUDED_ARCHS is not "$(inherited) i386"'); + } + } else if (target == 'macos') { + // Same for macOS deployment target, but 10.8. + // The plugintest target should not have MACOSX_DEPLOYMENT_TARGET set. + if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.8')) { + throw TaskResult.failure( + 'Plugin build setting MACOSX_DEPLOYMENT_TARGET not removed', + ); + } + // Transitive dependency AppAuth targeting too-low 10.9 was not fixed. + if (podsProjectContent.contains('MACOSX_DEPLOYMENT_TARGET = 10.9')) { + throw TaskResult.failure( + 'Transitive dependency build setting MACOSX_DEPLOYMENT_TARGET=10.9 not removed', + ); + } + } + + if (localEngine != null) { + final RegExp localEngineSearchPath = RegExp( + 'FRAMEWORK_SEARCH_PATHS\\s*=[^;]*${localEngine.path}', + ); + if (!localEngineSearchPath.hasMatch(podsProjectContent)) { + throw TaskResult.failure( + 'FRAMEWORK_SEARCH_PATHS does not contain the --local-engine path', + ); + } + } + } + } + } + }); + } + + Future delete() async { + if (Platform.isWindows) { + // A running Gradle daemon might prevent us from deleting the project + // folder on Windows. + final String wrapperPath = path.absolute(path.join(rootPath, 'android', 'gradlew.bat')); + if (File(wrapperPath).existsSync()) { + await exec(wrapperPath, ['--stop'], canFail: true); + } + // TODO(ianh): Investigating if flakiness is timing dependent. + await Future.delayed(const Duration(seconds: 10)); + } + rmTree(parent); + } +} diff --git a/flutter/dev/devicelab/lib/tasks/run_tests.dart b/flutter/dev/devicelab/lib/tasks/run_tests.dart new file mode 100644 index 00000000..c8366440 --- /dev/null +++ b/flutter/dev/devicelab/lib/tasks/run_tests.dart @@ -0,0 +1,354 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:ffi'; +import 'dart:io'; + +import '../framework/devices.dart'; +import '../framework/framework.dart'; +import '../framework/task_result.dart'; +import '../framework/utils.dart'; + +TaskFunction createAndroidRunDebugTest() { + return AndroidRunOutputTest(release: false).call; +} + +TaskFunction createAndroidRunReleaseTest() { + return AndroidRunOutputTest(release: true).call; +} + +TaskFunction createLinuxRunDebugTest() { + return DesktopRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/empty.dart', + release: false, + ).call; +} + +TaskFunction createLinuxRunReleaseTest() { + return DesktopRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/empty.dart', + release: true, + ).call; +} + +TaskFunction createMacOSRunDebugTest() { + return DesktopRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/main.dart', + release: false, + allowStderr: true, + ).call; +} + +TaskFunction createMacOSRunReleaseTest() { + return DesktopRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/main.dart', + release: true, + allowStderr: true, + ).call; +} + +TaskFunction createWindowsRunDebugTest() { + return WindowsRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/empty.dart', + release: false, + ).call; +} + +TaskFunction createWindowsRunReleaseTest() { + return WindowsRunOutputTest( + '${flutterDirectory.path}/dev/integration_tests/ui', + 'lib/empty.dart', + release: true, + ).call; +} + +class AndroidRunOutputTest extends RunOutputTask { + AndroidRunOutputTest({required super.release}) + : super('${flutterDirectory.path}/dev/integration_tests/ui', 'lib/main.dart'); + + @override + Future prepare(String deviceId) async { + // Uninstall if the app is already installed on the device to get to a clean state. + final List stderr = []; + print('uninstalling...'); + final Process uninstall = await startFlutter( + 'install', + // TODO(andrewkolos): consider removing -v after + // https://github.com/flutter/flutter/issues/153367 is troubleshot. + options: ['--suppress-analytics', '--uninstall-only', '-d', deviceId, '-v'], + isBot: false, + ); + uninstall.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen( + (String line) { + print('uninstall:stdout: $line'); + }, + ); + uninstall.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen( + (String line) { + print('uninstall:stderr: $line'); + stderr.add(line); + }, + ); + if (await uninstall.exitCode != 0) { + throw 'flutter install --uninstall-only failed.'; + } + if (stderr.isNotEmpty) { + throw 'flutter install --uninstall-only had output on standard error.'; + } + } + + @override + bool isExpectedStderr(String line) { + // TODO(egarciad): Remove once https://github.com/flutter/flutter/issues/95131 is fixed. + return line.contains('Mapping new ns'); + } + + @override + TaskResult verify(List stdout, List stderr) { + final String gradleTask = release ? 'assembleRelease' : 'assembleDebug'; + final String apk = release ? 'app-release.apk' : 'app-debug.apk'; + + _findNextMatcherInList( + stdout, + (String line) => + line.startsWith('Launching lib/main.dart on ') && + line.endsWith(' in ${release ? 'release' : 'debug'} mode...'), + 'Launching lib/main.dart on', + ); + + _findNextMatcherInList( + stdout, + (String line) => line.startsWith("Running Gradle task '$gradleTask'..."), + "Running Gradle task '$gradleTask'...", + ); + + // Size information is only included in release builds. + _findNextMatcherInList( + stdout, + (String line) => + line.contains('Built build/app/outputs/flutter-apk/$apk') && + (!release || line.contains('MB)')), + 'Built build/app/outputs/flutter-apk/$apk', + ); + + _findNextMatcherInList( + stdout, + (String line) => line.startsWith('Installing build/app/outputs/flutter-apk/$apk...'), + 'Installing build/app/outputs/flutter-apk/$apk...', + ); + + _findNextMatcherInList( + stdout, + (String line) => line.contains('Quit (terminate the application on the device).'), + 'q Quit (terminate the application on the device)', + ); + + _findNextMatcherInList( + stdout, + (String line) => line == 'Application finished.', + 'Application finished.', + ); + + return TaskResult.success(null); + } +} + +class WindowsRunOutputTest extends DesktopRunOutputTest { + WindowsRunOutputTest( + super.testDirectory, + super.testTarget, { + required super.release, + super.allowStderr = false, + }); + + final String arch = Abi.current() == Abi.windowsX64 ? 'x64' : 'arm64'; + + static final RegExp _buildOutput = RegExp( + r'Building Windows application\.\.\.\s*\d+(\.\d+)?(ms|s)', + multiLine: true, + ); + static final RegExp _builtOutput = RegExp( + r'Built build\\windows\\(x64|arm64)\\runner\\(Debug|Release)\\\w+\.exe( \(\d+(\.\d+)?MB\))?', + ); + + @override + void verifyBuildOutput(List stdout) { + _findNextMatcherInList(stdout, _buildOutput.hasMatch, 'Building Windows application...'); + + final String buildMode = release ? 'Release' : 'Debug'; + _findNextMatcherInList(stdout, (String line) { + if (!_builtOutput.hasMatch(line) || !line.contains(buildMode)) { + return false; + } + + return true; + }, '√ Built build\\windows\\$arch\\runner\\$buildMode\\ui.exe'); + } +} + +class DesktopRunOutputTest extends RunOutputTask { + DesktopRunOutputTest( + super.testDirectory, + super.testTarget, { + required super.release, + this.allowStderr = false, + }); + + /// Whether `flutter run` is expected to produce output on stderr. + final bool allowStderr; + + @override + bool isExpectedStderr(String line) => allowStderr; + + @override + TaskResult verify(List stdout, List stderr) { + _findNextMatcherInList( + stdout, + (String line) => + line.startsWith('Launching $testTarget on ') && + line.endsWith(' in ${release ? 'release' : 'debug'} mode...'), + 'Launching $testTarget on', + ); + + verifyBuildOutput(stdout); + + _findNextMatcherInList( + stdout, + (String line) => line.contains('Quit (terminate the application on the device).'), + 'q Quit (terminate the application on the device)', + ); + + _findNextMatcherInList( + stdout, + (String line) => line == 'Application finished.', + 'Application finished.', + ); + + return TaskResult.success(null); + } + + /// Verify the output from `flutter run`'s build step. + void verifyBuildOutput(List stdout) {} +} + +/// Test that the output of `flutter run` is expected. +abstract class RunOutputTask { + RunOutputTask(this.testDirectory, this.testTarget, {required this.release}); + + static final RegExp _engineLogRegex = RegExp(r'\[(VERBOSE|INFO|WARNING|ERROR|FATAL):.+\(\d+\)\]'); + + /// The directory where the app under test is defined. + final String testDirectory; + + /// The main entry-point file of the application, as run on the device. + final String testTarget; + + /// Whether to run the app in release mode. + final bool release; + + Future call() { + return inDirectory(testDirectory, () async { + final Device device = await devices.workingDevice; + await device.unlock(); + final String deviceId = device.deviceId; + + final Completer ready = Completer(); + final List stdout = []; + final List stderr = []; + + await prepare(deviceId); + + final List options = [testTarget, '-d', deviceId, if (release) '--release']; + + final Process run = await startFlutter('run', options: options, isBot: false); + + int? runExitCode; + run.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(( + String line, + ) { + print('run:stdout: $line'); + stdout.add(line); + if (line.contains('Quit (terminate the application on the device).')) { + ready.complete(); + } + }); + final Stream runStderr = + run.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .asBroadcastStream(); + runStderr.listen((String line) => print('run:stderr: $line')); + runStderr.skipWhile(isExpectedStderr).listen((String line) => stderr.add(line)); + unawaited( + run.exitCode.then((int exitCode) { + runExitCode = exitCode; + }), + ); + await Future.any(>[ready.future, run.exitCode]); + if (runExitCode != null) { + throw 'Failed to run test app; runner unexpected exited, with exit code $runExitCode.'; + } + run.stdin.write('q'); + + await run.exitCode; + + if (stderr.isNotEmpty) { + throw 'flutter run ${release ? '--release' : ''} had unexpected output on standard error.'; + } + + final List engineLogs = List.from(stdout.where(_engineLogRegex.hasMatch)); + if (engineLogs.isNotEmpty) { + throw 'flutter run had unexpected Flutter engine logs $engineLogs'; + } + + return verify(stdout, stderr); + }); + } + + /// Prepare the device for running the test app. + Future prepare(String deviceId) => Future.value(); + + /// Returns true if this stderr output line is expected. + bool isExpectedStderr(String line) => false; + + /// Verify the output of `flutter run`. + TaskResult verify(List stdout, List stderr) => + throw UnimplementedError('verify is not implemented'); + + /// Helper that verifies a line in [list] matches [matcher]. + /// The [list] is updated to contain the lines remaining after the match. + void _findNextMatcherInList( + List list, + bool Function(String testLine) matcher, + String errorMessageExpectedLine, + ) { + final List copyOfListForErrorMessage = List.from(list); + + while (list.isNotEmpty) { + final String nextLine = list.first; + list.removeAt(0); + + if (matcher(nextLine)) { + return; + } + } + + throw ''' +Did not find expected line + +$errorMessageExpectedLine + +in flutter run ${release ? '--release' : ''} stdout + +$copyOfListForErrorMessage +'''; + } +} diff --git a/flutter/dev/devicelab/lib/tasks/web_benchmarks.dart b/flutter/dev/devicelab/lib/tasks/web_benchmarks.dart new file mode 100644 index 00000000..c50e8254 --- /dev/null +++ b/flutter/dev/devicelab/lib/tasks/web_benchmarks.dart @@ -0,0 +1,240 @@ +// Copyright 2014 The Flutter 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:convert' show json; +import 'dart:io' as io; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; +import 'package:shelf/shelf.dart'; +import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_static/shelf_static.dart'; + +import '../framework/browser.dart'; +import '../framework/task_result.dart'; +import '../framework/utils.dart'; + +/// The port number used by the local benchmark server. +const int benchmarkServerPort = 9999; +const int chromeDebugPort = 10000; + +typedef WebBenchmarkOptions = ({bool useWasm, bool forceSingleThreadedSkwasm}); + +Future runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async { + // Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy. + Logger.root.level = Level.INFO; + final String macrobenchmarksDirectory = path.join( + flutterDirectory.path, + 'dev', + 'benchmarks', + 'macrobenchmarks', + ); + return inDirectory(macrobenchmarksDirectory, () async { + await flutter('clean'); + await evalFlutter( + 'build', + options: [ + 'web', + '--no-tree-shake-icons', // local engine builds are frequently out of sync with the Dart Kernel version + if (benchmarkOptions.useWasm) ...['-O4', '--wasm', '--no-strip-wasm'], + '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true', + '--profile', + '--no-web-resources-cdn', + '-t', + 'lib/web_benchmarks.dart', + ], + ); + final Completer>> profileData = + Completer>>(); + final List> collectedProfiles = >[]; + List? benchmarks; + late Iterator benchmarkIterator; + + // This future fixes a race condition between the web-page loading and + // asking to run a benchmark, and us connecting to Chrome's DevTools port. + // Sometime one wins. Other times, the other wins. + Future? whenChromeIsReady; + Chrome? chrome; + late io.HttpServer server; + Cascade cascade = Cascade(); + List>? latestPerformanceTrace; + cascade = cascade + .add((Request request) async { + try { + chrome ??= await whenChromeIsReady; + if (request.requestedUri.path.endsWith('/profile-data')) { + final Map profile = + json.decode(await request.readAsString()) as Map; + final String benchmarkName = profile['name'] as String; + if (benchmarkName != benchmarkIterator.current) { + profileData.completeError( + Exception( + 'Browser returned benchmark results from a wrong benchmark.\n' + 'Requested to run benchmark ${benchmarkIterator.current}, but ' + 'got results for $benchmarkName.', + ), + ); + unawaited(server.close()); + } + + // Trace data is null when the benchmark is not frame-based, such as RawRecorder. + if (latestPerformanceTrace != null) { + final BlinkTraceSummary traceSummary = + BlinkTraceSummary.fromJson(latestPerformanceTrace!)!; + profile['totalUiFrame.average'] = + traceSummary.averageTotalUIFrameTime.inMicroseconds; + profile['scoreKeys'] ??= []; // using dynamic for consistency with JSON + (profile['scoreKeys'] as List).add('totalUiFrame.average'); + latestPerformanceTrace = null; + } + collectedProfiles.add(profile); + return Response.ok('Profile received'); + } else if (request.requestedUri.path.endsWith('/start-performance-tracing')) { + latestPerformanceTrace = null; + await chrome!.beginRecordingPerformance( + request.requestedUri.queryParameters['label']!, + ); + return Response.ok('Started performance tracing'); + } else if (request.requestedUri.path.endsWith('/stop-performance-tracing')) { + latestPerformanceTrace = await chrome!.endRecordingPerformance(); + return Response.ok('Stopped performance tracing'); + } else if (request.requestedUri.path.endsWith('/on-error')) { + final Map errorDetails = + json.decode(await request.readAsString()) as Map; + unawaited(server.close()); + // Keep the stack trace as a string. It's thrown in the browser, not this Dart VM. + profileData.completeError('${errorDetails['error']}\n${errorDetails['stackTrace']}'); + return Response.ok(''); + } else if (request.requestedUri.path.endsWith('/next-benchmark')) { + if (benchmarks == null) { + benchmarks = + (json.decode(await request.readAsString()) as List).cast(); + benchmarkIterator = benchmarks!.iterator; + } + if (benchmarkIterator.moveNext()) { + final String nextBenchmark = benchmarkIterator.current; + print('Launching benchmark "$nextBenchmark"'); + return Response.ok(nextBenchmark); + } else { + profileData.complete(collectedProfiles); + return Response.notFound('Finished running benchmarks.'); + } + } else if (request.requestedUri.path.endsWith('/print-to-console')) { + // A passthrough used by + // `dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart` + // to print information. + final String message = await request.readAsString(); + print('[APP] $message'); + return Response.ok('Reported.'); + } else { + return Response.notFound('This request is not handled by the profile-data handler.'); + } + } catch (error, stackTrace) { + profileData.completeError(error, stackTrace); + return Response.internalServerError(body: '$error'); + } + }) + .add(createBuildDirectoryHandler(path.join(macrobenchmarksDirectory, 'build', 'web'))); + + server = await io.HttpServer.bind('localhost', benchmarkServerPort); + try { + shelf_io.serveRequests(server, cascade.handler); + + final String dartToolDirectory = path.join('$macrobenchmarksDirectory/.dart_tool'); + final String userDataDir = + io.Directory(dartToolDirectory).createTempSync('flutter_chrome_user_data.').path; + + // TODO(yjbanov): temporarily disables headful Chrome until we get + // devicelab hardware that is able to run it. Our current + // GCE VMs can only run in headless mode. + // See: https://github.com/flutter/flutter/issues/50164 + final bool isUncalibratedSmokeTest = io.Platform.environment['CALIBRATED'] != 'true'; + // final bool isUncalibratedSmokeTest = + // io.Platform.environment['UNCALIBRATED_SMOKE_TEST'] == 'true'; + final String urlParams = benchmarkOptions.forceSingleThreadedSkwasm ? '?force_st=true' : ''; + final ChromeOptions options = ChromeOptions( + url: 'http://localhost:$benchmarkServerPort/index.html$urlParams', + userDataDirectory: userDataDir, + headless: isUncalibratedSmokeTest, + debugPort: chromeDebugPort, + enableWasmGC: benchmarkOptions.useWasm, + ); + + print('Launching Chrome.'); + whenChromeIsReady = Chrome.launch( + options, + onError: (String error) { + profileData.completeError(Exception(error)); + }, + workingDirectory: cwd, + ); + + print('Waiting for the benchmark to report benchmark profile.'); + final Map taskResult = {}; + final List benchmarkScoreKeys = []; + final List> profiles = await profileData.future; + + print('Received profile data'); + for (final Map profile in profiles) { + final String benchmarkName = profile['name'] as String; + if (benchmarkName.isEmpty) { + throw 'Benchmark name is empty'; + } + + final String webRendererName; + if (benchmarkOptions.useWasm) { + webRendererName = benchmarkOptions.forceSingleThreadedSkwasm ? 'skwasm_st' : 'skwasm'; + } else { + webRendererName = 'canvaskit'; + } + final String namespace = '$benchmarkName.$webRendererName'; + final List scoreKeys = List.from(profile['scoreKeys'] as List); + if (scoreKeys.isEmpty) { + throw 'No score keys in benchmark "$benchmarkName"'; + } + for (final String scoreKey in scoreKeys) { + if (scoreKey.isEmpty) { + throw 'Score key is empty in benchmark "$benchmarkName". ' + 'Received [${scoreKeys.join(', ')}]'; + } + benchmarkScoreKeys.add('$namespace.$scoreKey'); + } + + for (final String key in profile.keys) { + if (key == 'name' || key == 'scoreKeys') { + continue; + } + taskResult['$namespace.$key'] = profile[key]; + } + } + return TaskResult.success(taskResult, benchmarkScoreKeys: benchmarkScoreKeys); + } finally { + unawaited(server.close()); + chrome?.stop(); + } + }); +} + +Handler createBuildDirectoryHandler(String buildDirectoryPath) { + final Handler childHandler = createStaticHandler(buildDirectoryPath); + return (Request request) async { + final Response response = await childHandler(request); + final String? mimeType = response.mimeType; + + // Provide COOP/COEP headers so that the browser loads the page as + // crossOriginIsolated. This will make sure that we get high-resolution + // timers for our benchmark measurements. + if (mimeType == 'text/html' || mimeType == 'text/javascript') { + return response.change( + headers: { + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }, + ); + } else { + return response; + } + }; +} diff --git a/flutter/dev/devicelab/lib/tasks/web_dev_mode_tests.dart b/flutter/dev/devicelab/lib/tasks/web_dev_mode_tests.dart new file mode 100644 index 00000000..9a8e45e7 --- /dev/null +++ b/flutter/dev/devicelab/lib/tasks/web_dev_mode_tests.dart @@ -0,0 +1,201 @@ +// Copyright 2014 The Flutter 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:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import '../framework/framework.dart'; +import '../framework/task_result.dart'; +import '../framework/utils.dart'; + +final Directory _editedFlutterGalleryDir = dir( + path.join(Directory.systemTemp.path, 'edited_flutter_gallery'), +); +final Directory flutterGalleryDir = dir( + path.join(flutterDirectory.path, 'dev/integration_tests/flutter_gallery'), +); + +const String kInitialStartupTime = 'InitialStartupTime'; +const String kFirstRestartTime = 'FistRestartTime'; +const String kFirstRecompileTime = 'FirstRecompileTime'; +const String kSecondStartupTime = 'SecondStartupTime'; +const String kSecondRestartTime = 'SecondRestartTime'; + +abstract class WebDevice { + static const String chrome = 'chrome'; + static const String webServer = 'web-server'; +} + +TaskFunction createWebDevModeTest(String webDevice, bool enableIncrementalCompiler) { + return () async { + final List options = [ + '--hot', + '-d', + webDevice, + '--verbose', + '--resident', + '--target=lib/main.dart', + ]; + int hotRestartCount = 0; + final String expectedMessage = + webDevice == WebDevice.webServer ? 'Recompile complete' : 'Reloaded application'; + final Map measurements = {}; + await inDirectory(flutterDirectory, () async { + rmTree(_editedFlutterGalleryDir); + mkdirs(_editedFlutterGalleryDir); + recursiveCopy(flutterGalleryDir, _editedFlutterGalleryDir); + await inDirectory(_editedFlutterGalleryDir, () async { + { + await flutter('packages', options: ['get']); + final Process process = await startFlutter('run', options: options); + + final Completer stdoutDone = Completer(); + final Completer stderrDone = Completer(); + final Stopwatch sw = Stopwatch()..start(); + bool restarted = false; + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + // non-dwds builds do not know when the browser is loaded so keep trying + // until this succeeds. + if (line.contains('Ignoring terminal input')) { + Future.delayed(const Duration(seconds: 1)).then((void _) { + process.stdin.write(restarted ? 'q' : 'r'); + }); + return; + } + if (line.contains('To hot restart')) { + // measure clean start-up time. + sw.stop(); + measurements[kInitialStartupTime] = sw.elapsedMilliseconds; + sw + ..reset() + ..start(); + process.stdin.write('r'); + return; + } + if (line.contains(expectedMessage)) { + if (hotRestartCount == 0) { + measurements[kFirstRestartTime] = sw.elapsedMilliseconds; + // Update the file and reload again. + final File appDartSource = file( + path.join(_editedFlutterGalleryDir.path, 'lib/gallery/app.dart'), + ); + appDartSource.writeAsStringSync( + appDartSource.readAsStringSync().replaceFirst( + "'Flutter Gallery'", + "'Updated Flutter Gallery'", + ), + ); + sw + ..reset() + ..start(); + process.stdin.writeln('r'); + ++hotRestartCount; + } else { + restarted = true; + measurements[kFirstRecompileTime] = sw.elapsedMilliseconds; + // Quit after second hot restart. + process.stdin.writeln('q'); + } + } + print('stdout: $line'); + }, + onDone: () { + stdoutDone.complete(); + }, + ); + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + print('stderr: $line'); + }, + onDone: () { + stderrDone.complete(); + }, + ); + + await Future.wait(>[stdoutDone.future, stderrDone.future]); + await process.exitCode; + } + + // Start `flutter run` again to make sure it loads from the previous + // state. dev compilers loads up from previously compiled JavaScript. + { + final Stopwatch sw = Stopwatch()..start(); + final Process process = await startFlutter('run', options: options); + final Completer stdoutDone = Completer(); + final Completer stderrDone = Completer(); + bool restarted = false; + process.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + // non-dwds builds do not know when the browser is loaded so keep trying + // until this succeeds. + if (line.contains('Ignoring terminal input')) { + Future.delayed(const Duration(seconds: 1)).then((void _) { + process.stdin.write(restarted ? 'q' : 'r'); + }); + return; + } + if (line.contains('To hot restart')) { + measurements[kSecondStartupTime] = sw.elapsedMilliseconds; + sw + ..reset() + ..start(); + process.stdin.write('r'); + return; + } + if (line.contains(expectedMessage)) { + restarted = true; + measurements[kSecondRestartTime] = sw.elapsedMilliseconds; + process.stdin.writeln('q'); + } + print('stdout: $line'); + }, + onDone: () { + stdoutDone.complete(); + }, + ); + process.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen( + (String line) { + print('stderr: $line'); + }, + onDone: () { + stderrDone.complete(); + }, + ); + + await Future.wait(>[stdoutDone.future, stderrDone.future]); + await process.exitCode; + } + }); + }); + if (hotRestartCount != 1) { + return TaskResult.failure(null); + } + return TaskResult.success( + measurements, + benchmarkScoreKeys: [ + kInitialStartupTime, + kFirstRestartTime, + kFirstRecompileTime, + kSecondStartupTime, + kSecondRestartTime, + ], + ); + }; +} diff --git a/flutter/dev/devicelab/pubspec.yaml b/flutter/dev/devicelab/pubspec.yaml new file mode 100644 index 00000000..006af707 --- /dev/null +++ b/flutter/dev/devicelab/pubspec.yaml @@ -0,0 +1,78 @@ +name: flutter_devicelab +description: Flutter continuous integration performance and correctness tests. +homepage: https://github.com/flutter/flutter + +environment: + sdk: ^3.7.0-0 + +dependencies: + archive: 3.6.1 + args: 2.6.0 + file: 7.0.1 + http: 1.2.2 + logging: 1.3.0 + meta: 1.16.0 + metrics_center: 1.0.13 + path: 1.9.1 + platform: 3.1.6 + process: 5.0.3 + pubspec_parse: 1.5.0 + shelf: 1.4.2 + shelf_static: 1.1.3 + stack_trace: 1.12.1 + vm_service: 14.3.1 + web: 1.1.0 + webkit_inspection_protocol: 1.2.1 + xml: 6.5.0 + standard_message_codec: 0.0.1+4 + + _discoveryapis_commons: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + checked_yaml: 2.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + gcloud: 0.8.18 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + google_identity_services_web: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + googleapis: 12.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + googleapis_auth: 1.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + json_annotation: 4.9.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + petitparser: 6.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + retry: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + test: 1.25.14 + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: f70c diff --git a/flutter/dev/forbidden_from_release_tests/pubspec.yaml b/flutter/dev/forbidden_from_release_tests/pubspec.yaml new file mode 100644 index 00000000..c6280b42 --- /dev/null +++ b/flutter/dev/forbidden_from_release_tests/pubspec.yaml @@ -0,0 +1,19 @@ +name: forbidden_from_release_tests +publish_to: 'none' + +environment: + sdk: ^3.7.0-0 + +dependencies: + args: 2.6.0 + file: 7.0.1 + package_config: 2.1.1 + path: 1.9.1 + process: 5.0.3 + vm_snapshot_analysis: 0.7.6 + + collection: 1.19.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 3.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: f99a diff --git a/flutter/dev/integration_tests/android_engine_test/README.md b/flutter/dev/integration_tests/android_engine_test/README.md new file mode 100644 index 00000000..70793738 --- /dev/null +++ b/flutter/dev/integration_tests/android_engine_test/README.md @@ -0,0 +1,146 @@ +# android_engine_test + +This directory contains a sample app and tests that demonstrate how to use the +(experimental) _native_ Flutter Driver API to drive Flutter apps that run on +Android devices or emulators, interact with and capture screenshots of the app, +and compare the screenshots against golden images. + +> [!CAUTION] +> This test suite is a _very_ end-to-end suite that is testing a combination of +> the graphics backend, the Android embedder, the Flutter framework, and Flutter +> tools, and only useful when the documentation and naming stays up to date and +> is clearly actionable. +> +> Please take extra care when updating the test suite to also update the REAMDE. + +## How it runs on CI (LUCI) + +See [`dev/bots/suite_runners/run_android_engine_tests.dart`](../../bots/suite_runners/run_android_engine_tests.dart), but tl;dr: + +```sh +# TIP: If golden-files do not exist locally, this command will fail locally. +SHARD=android_engine_tests bin/cache/dart-sdk/bin/dart dev/bots/test.dart +``` + +## Running the apps and tests + +Each `lib/{prefix}_main.dart` file is a standalone Flutter app that you can run +on an Android device or emulator. + +- [`flutter_rendered_blue_rectangle`](#flutter_rendered_blue_rectangle) +- [`external_texture/surface_producer_smiley_face`](#external_texturesurface_producer_smiley_face) +- [`external_texture/surface_texture_smiley_face`](#external_texturesurface_texture_smiley_face) +- [`platform_view/hybrid_composition_platform_view`](#platform_viewhybrid_composition_platform_view) +- [`platform_view/texture_layer_hybrid_composition_platform_view`](#platform_viewtexture_layer_hybrid_composition_platform_view) +- [`platform_view/virtual_display_platform_view`](#platform_viewvirtual_display_platform_view) +- [`platform_view_tap_color_change`](#platform_view_tap_color_change) + +### `flutter_rendered_blue_rectangle` + +This app displays a full screen blue rectangle. It mostly serves as a test that +Flutter can run at all on the target device, and that the Flutter (native) +driver can take a screenshot and compare it to a golden image. If this app or +test fails, it's likely none of the other apps or tests will work either. + +```sh +# Run the app +$ flutter run lib/flutter_rendered_blue_rectangle_main.dart + +# Run the test +$ flutter drive lib/flutter_rendered_blue_rectangle_main.dart +``` + +### `external_texture/surface_producer_smiley_face` + +This app displays a full screen rectangular deformed smiley face with a yellow +background. It tests the [`SurfaceProducer`](https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.SurfaceProducer.html) API end-to-end, including historic regression cases around +backgrounding the app, trimming memory, and resuming the app. + +```sh +# Run the app +$ flutter run lib/external_texture/surface_producer_smiley_face_main.dart + +# Run the test +$ flutter drive lib/external_texture/surface_producer_smiley_face_main.dart +``` + +### `external_texture/surface_texture_smiley_face` + +This app displays a full screen rectangular deformed smiley face with a yellow +background. It tests the [`SurfaceTexture`](https://api.flutter.dev/javadoc/io/flutter/view/TextureRegistry.SurfaceTexture.html) API end-to-end. + +```sh +# Run the app +$ flutter run lib/external_texture/surface_texture_smiley_face_main.dart + +# Run the test +$ flutter drive lib/external_texture/surface_texture_smiley_face_main.dart +``` + +### `platform_view/hybrid_composition_platform_view` + +This app displays a blue orange gradient, the app is backgrounded, and then +resumed. It tests the [Hybrid Composition](../../../docs/platforms/android/Android-Platform-Views.md#hybrid-composition) implementation. + +```sh +# Run the app +$ flutter run lib/platform_view/hybrid_composition_platform_view_main.dart + +# Run the test +$ flutter drive lib/platform_view/hybrid_composition_platform_view_main.dart +``` + +### `platform_view/texture_layer_hybrid_composition_platform_view` + +This app displays a blue orange gradient, the app is backgrounded, and then +resumed. It tests the [Texture Layer Hybrid Composition](../../../docs/platforms/android/Android-Platform-Views.md#texture-layer-hybrid-composition) implementation. + +```sh +# Run the app +$ flutter run lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart + +# Run the test +$ flutter drive lib/platform_view/texture_layer_hybrid_composition_platform_view_main.dart +``` + +### `platform_view/virtual_display_platform_view` + +This app displays a blue orange gradient, the app is backgrounded, and then +resumed. It tests the [Virtual Display](../../../docs/platforms/android/Android-Platform-Views.md#virtual-display) implementation. + +```sh +# Run the app +$ flutter run lib/platform_view/virtual_display_platform_view_main.dart + +# Run the test +$ flutter drive lib/platform_view/virtual_display_platform_view_main.dart +``` + +### `platform_view_tap_color_change` + +This app displays a blue rectangle, using platform views, which upon +being tapped (natively, not by Flutter), changes from blue to red. + +```sh +# Run the app +$ flutter run lib/platform_view_tap_color_change_main.dart + +# Run the test +$ flutter drive lib/platform_view_tap_color_change_main_test.dart +``` + +## Deflaking + +Use `tool/deflake.dart ` to, in 1-command: + +- Build an APK. +- Establish a baseline set of golden-files locally. +- Run N tests (by default, 10) in the same state, asserting the same output. + +For example: + +```sh +dart tool/deflake.dart lib/flutter_rendered_blue_rectangle_main.dart +``` + +For more options, see `dart tool/deflake.dart --help`. diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation/widgets.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation/widgets.dart new file mode 100644 index 00000000..b1e918ea --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation/widgets.dart @@ -0,0 +1,136 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'sections.dart'; + +const double kSectionIndicatorWidth = 32.0; + +// The card for a single section. Displays the section's gradient and background image. +class SectionCard extends StatelessWidget { + const SectionCard({super.key, required this.section}); + + final Section section; + + @override + Widget build(BuildContext context) { + return Semantics( + label: section.title, + button: true, + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient(colors: [section.leftColor!, section.rightColor!]), + ), + child: Image.asset( + section.backgroundAsset!, + package: section.backgroundAssetPackage, + color: const Color.fromRGBO(255, 255, 255, 0.075), + colorBlendMode: BlendMode.modulate, + fit: BoxFit.cover, + ), + ), + ); + } +} + +// The title is rendered with two overlapping text widgets that are vertically +// offset a little. It's supposed to look sort-of 3D. +class SectionTitle extends StatelessWidget { + const SectionTitle({super.key, required this.section, required this.scale, required this.opacity}) + : assert(opacity >= 0.0 && opacity <= 1.0); + + final Section section; + final double scale; + final double opacity; + + static const TextStyle sectionTitleStyle = TextStyle( + fontFamily: 'Raleway', + inherit: false, + fontSize: 24.0, + fontWeight: FontWeight.w500, + color: Colors.white, + textBaseline: TextBaseline.alphabetic, + ); + + static final TextStyle sectionTitleShadowStyle = sectionTitleStyle.copyWith( + color: const Color(0x19000000), + ); + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: Opacity( + opacity: opacity, + child: Transform( + transform: Matrix4.identity()..scale(scale), + alignment: Alignment.center, + child: Stack( + children: [ + Positioned(top: 4.0, child: Text(section.title!, style: sectionTitleShadowStyle)), + Text(section.title!, style: sectionTitleStyle), + ], + ), + ), + ), + ); + } +} + +// Small horizontal bar that indicates the selected section. +class SectionIndicator extends StatelessWidget { + const SectionIndicator({super.key, this.opacity = 1.0}); + + final double opacity; + + @override + Widget build(BuildContext context) { + return IgnorePointer( + child: Container( + width: kSectionIndicatorWidth, + height: 3.0, + color: Colors.white.withOpacity(opacity), + ), + ); + } +} + +// Display a single SectionDetail. +class SectionDetailView extends StatelessWidget { + SectionDetailView({super.key, required this.detail}) + : assert(detail.imageAsset != null), + assert((detail.imageAsset ?? detail.title) != null); + + final SectionDetail detail; + + @override + Widget build(BuildContext context) { + final Widget image = DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + image: DecorationImage( + image: AssetImage(detail.imageAsset!, package: detail.imageAssetPackage), + fit: BoxFit.cover, + ), + ), + ); + + Widget item; + if (detail.title == null && detail.subtitle == null) { + item = Container( + height: 240.0, + padding: const EdgeInsets.all(16.0), + child: SafeArea(top: false, bottom: false, child: image), + ); + } else { + item = ListTile( + title: Text(detail.title!), + subtitle: Text(detail.subtitle!), + leading: SizedBox(width: 32.0, height: 32.0, child: image), + ); + } + + return DecoratedBox(decoration: BoxDecoration(color: Colors.grey.shade200), child: item); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation_demo.dart new file mode 100644 index 00000000..a31b2e7f --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/animation_demo.dart @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'animation/home.dart'; + +class AnimationDemo extends StatelessWidget { + const AnimationDemo({super.key}); + + static const String routeName = '/animation'; + + @override + Widget build(BuildContext context) => const AnimationDemoHome(); +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/calculator_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/calculator_demo.dart new file mode 100644 index 00000000..f3f00aa8 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/calculator_demo.dart @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import 'calculator/home.dart'; + +class CalculatorDemo extends StatelessWidget { + const CalculatorDemo({super.key}); + + static const String routeName = '/calculator'; + + @override + Widget build(BuildContext context) => const Calculator(); +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/colors_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/colors_demo.dart new file mode 100644 index 00000000..edfd0372 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/colors_demo.dart @@ -0,0 +1,156 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +const double kColorItemHeight = 48.0; + +class Palette { + Palette({this.name, this.primary, this.accent, this.threshold = 900}); + + final String? name; + final MaterialColor? primary; + final MaterialAccentColor? accent; + final int threshold; // titles for indices > threshold are white, otherwise black + + bool get isValid => name != null && primary != null; +} + +final List allPalettes = [ + Palette(name: 'RED', primary: Colors.red, accent: Colors.redAccent, threshold: 300), + Palette(name: 'PINK', primary: Colors.pink, accent: Colors.pinkAccent, threshold: 200), + Palette(name: 'PURPLE', primary: Colors.purple, accent: Colors.purpleAccent, threshold: 200), + Palette( + name: 'DEEP PURPLE', + primary: Colors.deepPurple, + accent: Colors.deepPurpleAccent, + threshold: 200, + ), + Palette(name: 'INDIGO', primary: Colors.indigo, accent: Colors.indigoAccent, threshold: 200), + Palette(name: 'BLUE', primary: Colors.blue, accent: Colors.blueAccent, threshold: 400), + Palette( + name: 'LIGHT BLUE', + primary: Colors.lightBlue, + accent: Colors.lightBlueAccent, + threshold: 500, + ), + Palette(name: 'CYAN', primary: Colors.cyan, accent: Colors.cyanAccent, threshold: 600), + Palette(name: 'TEAL', primary: Colors.teal, accent: Colors.tealAccent, threshold: 400), + Palette(name: 'GREEN', primary: Colors.green, accent: Colors.greenAccent, threshold: 500), + Palette( + name: 'LIGHT GREEN', + primary: Colors.lightGreen, + accent: Colors.lightGreenAccent, + threshold: 600, + ), + Palette(name: 'LIME', primary: Colors.lime, accent: Colors.limeAccent, threshold: 800), + Palette(name: 'YELLOW', primary: Colors.yellow, accent: Colors.yellowAccent), + Palette(name: 'AMBER', primary: Colors.amber, accent: Colors.amberAccent), + Palette(name: 'ORANGE', primary: Colors.orange, accent: Colors.orangeAccent, threshold: 700), + Palette( + name: 'DEEP ORANGE', + primary: Colors.deepOrange, + accent: Colors.deepOrangeAccent, + threshold: 400, + ), + Palette(name: 'BROWN', primary: Colors.brown, threshold: 200), + Palette(name: 'GREY', primary: Colors.grey, threshold: 500), + Palette(name: 'BLUE GREY', primary: Colors.blueGrey, threshold: 500), +]; + +class ColorItem extends StatelessWidget { + const ColorItem({super.key, required this.index, required this.color, this.prefix = ''}); + + final int index; + final Color color; + final String prefix; + + String colorString() => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; + + @override + Widget build(BuildContext context) { + return Semantics( + container: true, + child: Container( + height: kColorItemHeight, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + color: color, + child: SafeArea( + top: false, + bottom: false, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text('$prefix$index'), Text(colorString())], + ), + ), + ), + ); + } +} + +class PaletteTabView extends StatelessWidget { + PaletteTabView({super.key, required this.colors}) : assert(colors.isValid); + + final Palette colors; + + static const List primaryKeys = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]; + static const List accentKeys = [100, 200, 400, 700]; + + @override + Widget build(BuildContext context) { + final TextTheme textTheme = Theme.of(context).textTheme; + final TextStyle whiteTextStyle = textTheme.bodyMedium!.copyWith(color: Colors.white); + final TextStyle blackTextStyle = textTheme.bodyMedium!.copyWith(color: Colors.black); + return Scrollbar( + child: ListView( + primary: true, + itemExtent: kColorItemHeight, + children: [ + ...primaryKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.primary![index]!), + ); + }), + if (colors.accent != null) + ...accentKeys.map((int index) { + return DefaultTextStyle( + style: index > colors.threshold ? whiteTextStyle : blackTextStyle, + child: ColorItem(index: index, color: colors.accent![index]!, prefix: 'A'), + ); + }), + ], + ), + ); + } +} + +class ColorsDemo extends StatelessWidget { + const ColorsDemo({super.key}); + + static const String routeName = '/colors'; + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: allPalettes.length, + child: Scaffold( + appBar: AppBar( + elevation: 0.0, + title: const Text('Colors'), + bottom: TabBar( + isScrollable: true, + tabs: allPalettes.map((Palette swatch) => Tab(text: swatch.name)).toList(), + ), + ), + body: TabBarView( + children: + allPalettes.map((Palette colors) { + return PaletteTabView(colors: colors); + }).toList(), + ), + ), + ); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart new file mode 100644 index 00000000..a18b9e24 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart @@ -0,0 +1,315 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class _ContactCategory extends StatelessWidget { + const _ContactCategory({this.icon, this.children}); + + final IconData? icon; + final List? children; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + return Container( + padding: const EdgeInsets.symmetric(vertical: 16.0), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: themeData.dividerColor))), + child: DefaultTextStyle( + style: Theme.of(context).textTheme.titleMedium!, + child: SafeArea( + top: false, + bottom: false, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.symmetric(vertical: 24.0), + width: 72.0, + child: Icon(icon, color: themeData.primaryColor), + ), + Expanded(child: Column(children: children!)), + ], + ), + ), + ), + ); + } +} + +class _ContactItem extends StatelessWidget { + const _ContactItem({this.icon, required this.lines, this.tooltip, this.onPressed}) + : assert(lines.length > 1); + + final IconData? icon; + final List lines; + final String? tooltip; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + return MergeSemantics( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...lines.sublist(0, lines.length - 1).map((String line) => Text(line)), + Text(lines.last, style: themeData.textTheme.bodySmall), + ], + ), + ), + if (icon != null) + SizedBox( + width: 72.0, + child: IconButton( + icon: Icon(icon), + color: themeData.primaryColor, + onPressed: onPressed, + ), + ), + ], + ), + ), + ); + } +} + +class ContactsDemo extends StatefulWidget { + const ContactsDemo({super.key}); + + static const String routeName = '/contacts'; + + @override + ContactsDemoState createState() => ContactsDemoState(); +} + +enum AppBarBehavior { normal, pinned, floating, snapping } + +class ContactsDemoState extends State { + final double _appBarHeight = 256.0; + + AppBarBehavior _appBarBehavior = AppBarBehavior.pinned; + + @override + Widget build(BuildContext context) { + return Theme( + data: ThemeData( + useMaterial3: false, + brightness: Brightness.light, + primarySwatch: Colors.indigo, + platform: Theme.of(context).platform, + ), + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: _appBarHeight, + pinned: _appBarBehavior == AppBarBehavior.pinned, + floating: + _appBarBehavior == AppBarBehavior.floating || + _appBarBehavior == AppBarBehavior.snapping, + snap: _appBarBehavior == AppBarBehavior.snapping, + actions: [ + IconButton( + icon: const Icon(Icons.create), + tooltip: 'Edit', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text("Editing isn't supported in this screen.")), + ); + }, + ), + PopupMenuButton( + onSelected: (AppBarBehavior value) { + setState(() { + _appBarBehavior = value; + }); + }, + itemBuilder: + (BuildContext context) => >[ + const PopupMenuItem( + value: AppBarBehavior.normal, + child: Text('App bar scrolls away'), + ), + const PopupMenuItem( + value: AppBarBehavior.pinned, + child: Text('App bar stays put'), + ), + const PopupMenuItem( + value: AppBarBehavior.floating, + child: Text('App bar floats'), + ), + const PopupMenuItem( + value: AppBarBehavior.snapping, + child: Text('App bar snaps'), + ), + ], + ), + ], + flexibleSpace: FlexibleSpaceBar( + title: const Text('Ali Connors'), + background: Stack( + fit: StackFit.expand, + children: [ + Image.asset( + 'people/ali_landscape.png', + package: 'flutter_gallery_assets', + fit: BoxFit.cover, + height: _appBarHeight, + ), + // This gradient ensures that the toolbar icons are distinct + // against the background image. + const DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment(0, .35), + colors: [Color(0xC0000000), Color(0x00000000)], + ), + ), + ), + ], + ), + ), + ), + SliverList( + delegate: SliverChildListDelegate([ + AnnotatedRegion( + value: SystemUiOverlayStyle.dark, + child: _ContactCategory( + icon: Icons.call, + children: [ + _ContactItem( + icon: Icons.message, + tooltip: 'Send message', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Pretend that this opened your SMS application.'), + ), + ); + }, + lines: const ['(650) 555-1234', 'Mobile'], + ), + _ContactItem( + icon: Icons.message, + tooltip: 'Send message', + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('A messaging app appears.'))); + }, + lines: const ['(323) 555-6789', 'Work'], + ), + _ContactItem( + icon: Icons.message, + tooltip: 'Send message', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Imagine if you will, a messaging application.'), + ), + ); + }, + lines: const ['(650) 555-6789', 'Home'], + ), + ], + ), + ), + _ContactCategory( + icon: Icons.contact_mail, + children: [ + _ContactItem( + icon: Icons.email, + tooltip: 'Send personal e-mail', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Here, your e-mail application would open.'), + ), + ); + }, + lines: const ['ali_connors@example.com', 'Personal'], + ), + _ContactItem( + icon: Icons.email, + tooltip: 'Send work e-mail', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Summon your favorite e-mail application here.'), + ), + ); + }, + lines: const ['aliconnors@example.com', 'Work'], + ), + ], + ), + _ContactCategory( + icon: Icons.location_on, + children: [ + _ContactItem( + icon: Icons.map, + tooltip: 'Open map', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('This would show a map of San Francisco.')), + ); + }, + lines: const ['2000 Main Street', 'San Francisco, CA', 'Home'], + ), + _ContactItem( + icon: Icons.map, + tooltip: 'Open map', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('This would show a map of Mountain View.')), + ); + }, + lines: const [ + '1600 Amphitheater Parkway', + 'Mountain View, CA', + 'Work', + ], + ), + _ContactItem( + icon: Icons.map, + tooltip: 'Open map', + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('This would also show a map, if this was not a demo.'), + ), + ); + }, + lines: const ['126 Severyns Ave', 'Mountain View, CA', 'Jet Travel'], + ), + ], + ), + _ContactCategory( + icon: Icons.today, + children: [ + _ContactItem(lines: const ['Birthday', 'January 9th, 1989']), + _ContactItem(lines: const ['Wedding anniversary', 'June 21st, 2014']), + _ContactItem( + lines: const ['First day in office', 'January 20th, 2015'], + ), + _ContactItem(lines: const ['Last day in office', 'August 9th, 2018']), + ], + ), + ]), + ), + ], + ), + ), + ); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart new file mode 100644 index 00000000..2cd95fc2 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/images_demo.dart @@ -0,0 +1,46 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +import '../gallery/demo.dart'; + +class ImagesDemo extends StatelessWidget { + const ImagesDemo({super.key}); + + static const String routeName = '/images'; + + @override + Widget build(BuildContext context) { + return TabbedComponentDemoScaffold( + title: 'Animated images', + demos: [ + ComponentDemoTabData( + tabName: 'WEBP', + description: '', + exampleCodeTag: 'animated_image', + demoWidget: Semantics( + label: 'Example of animated WEBP', + child: Image.asset( + 'animated_images/animated_flutter_stickers.webp', + package: 'flutter_gallery_assets', + ), + ), + ), + ComponentDemoTabData( + tabName: 'GIF', + description: '', + exampleCodeTag: 'animated_image', + demoWidget: Semantics( + label: 'Example of animated GIF', + child: Image.asset( + 'animated_images/animated_flutter_lgtm.gif', + package: 'flutter_gallery_assets', + ), + ), + ), + ], + ); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart new file mode 100644 index 00000000..debae7d6 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart @@ -0,0 +1,736 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +class PestoDemo extends StatelessWidget { + const PestoDemo({super.key}); + + static const String routeName = '/pesto'; + + @override + Widget build(BuildContext context) => const PestoHome(); +} + +const String _kSmallLogoImage = 'logos/pesto/logo_small.png'; +const String _kGalleryAssetsPackage = 'flutter_gallery_assets'; +const double _kAppBarHeight = 128.0; +const double _kFabHalfSize = 28.0; // TODO(mpcomplete): needs to adapt to screen size +const double _kRecipePageMaxWidth = 500.0; + +final Set _favoriteRecipes = {}; + +final ThemeData _kTheme = ThemeData( + appBarTheme: const AppBarTheme(foregroundColor: Colors.white, backgroundColor: Colors.teal), + brightness: Brightness.light, + floatingActionButtonTheme: const FloatingActionButtonThemeData(foregroundColor: Colors.white), +); + +class PestoHome extends StatelessWidget { + const PestoHome({super.key}); + + @override + Widget build(BuildContext context) { + return const RecipeGridPage(recipes: kPestoRecipes); + } +} + +class PestoFavorites extends StatelessWidget { + const PestoFavorites({super.key}); + + @override + Widget build(BuildContext context) { + return RecipeGridPage(recipes: _favoriteRecipes.toList()); + } +} + +class PestoStyle extends TextStyle { + const PestoStyle({ + double super.fontSize = 12.0, + super.fontWeight, + Color super.color = Colors.black87, + super.letterSpacing, + super.height, + }) : super(inherit: false, fontFamily: 'Raleway', textBaseline: TextBaseline.alphabetic); +} + +// Displays a grid of recipe cards. +class RecipeGridPage extends StatefulWidget { + const RecipeGridPage({super.key, this.recipes}); + + final List? recipes; + + @override + State createState() => _RecipeGridPageState(); +} + +class _RecipeGridPageState extends State { + @override + Widget build(BuildContext context) { + final double statusBarHeight = MediaQuery.of(context).padding.top; + return Theme( + data: _kTheme.copyWith(platform: Theme.of(context).platform), + child: Scaffold( + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.redAccent, + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Not supported.'))); + }, + child: const Icon(Icons.edit), + ), + body: CustomScrollView( + semanticChildCount: widget.recipes!.length, + slivers: [ + _buildAppBar(context, statusBarHeight), + _buildBody(context, statusBarHeight), + ], + ), + ), + ); + } + + Widget _buildAppBar(BuildContext context, double statusBarHeight) { + return SliverAppBar( + pinned: true, + expandedHeight: _kAppBarHeight, + actions: [ + IconButton( + icon: const Icon(Icons.search), + tooltip: 'Search', + onPressed: () { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Not supported.'))); + }, + ), + ], + flexibleSpace: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = constraints.biggest; + final double appBarHeight = size.height - statusBarHeight; + final double t = (appBarHeight - kToolbarHeight) / (_kAppBarHeight - kToolbarHeight); + final double extraPadding = Tween(begin: 10.0, end: 24.0).transform(t); + final double logoHeight = appBarHeight - 1.5 * extraPadding; + return Padding( + padding: EdgeInsets.only( + top: statusBarHeight + 0.5 * extraPadding, + bottom: extraPadding, + ), + child: Center(child: PestoLogo(height: logoHeight, t: t.clamp(0.0, 1.0))), + ); + }, + ), + ); + } + + Widget _buildBody(BuildContext context, double statusBarHeight) { + final EdgeInsets mediaPadding = MediaQuery.of(context).padding; + final EdgeInsets padding = EdgeInsets.only( + top: 8.0, + left: 8.0 + mediaPadding.left, + right: 8.0 + mediaPadding.right, + bottom: 8.0, + ); + return SliverPadding( + padding: padding, + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: _kRecipePageMaxWidth, + crossAxisSpacing: 8.0, + mainAxisSpacing: 8.0, + ), + delegate: SliverChildBuilderDelegate((BuildContext context, int index) { + final Recipe? recipe = widget.recipes![index]; + return RecipeCard( + recipe: recipe, + onTap: () { + showRecipePage(context, recipe); + }, + ); + }, childCount: widget.recipes!.length), + ), + ); + } + + void showFavoritesPage(BuildContext context) { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: '/pesto/favorites'), + builder: (BuildContext context) => const PestoFavorites(), + ), + ); + } + + void showRecipePage(BuildContext context, Recipe? recipe) { + Navigator.push( + context, + MaterialPageRoute( + settings: const RouteSettings(name: '/pesto/recipe'), + builder: (BuildContext context) { + return Theme( + data: _kTheme.copyWith(platform: Theme.of(context).platform), + child: RecipePage(recipe: recipe), + ); + }, + ), + ); + } +} + +class PestoLogo extends StatefulWidget { + const PestoLogo({super.key, this.height, this.t}); + + final double? height; + final double? t; + + @override + State createState() => _PestoLogoState(); +} + +class _PestoLogoState extends State { + // Native sizes for logo and its image/text components. + static const double kLogoHeight = 162.0; + static const double kLogoWidth = 220.0; + static const double kImageHeight = 108.0; + static const double kTextHeight = 48.0; + final TextStyle titleStyle = const PestoStyle( + fontSize: kTextHeight, + fontWeight: FontWeight.w900, + color: Colors.white, + letterSpacing: 3.0, + ); + final RectTween _textRectTween = RectTween( + begin: const Rect.fromLTWH(0.0, kLogoHeight, kLogoWidth, kTextHeight), + end: const Rect.fromLTWH(0.0, kImageHeight, kLogoWidth, kTextHeight), + ); + final Curve _textOpacity = const Interval(0.4, 1.0, curve: Curves.easeInOut); + final RectTween _imageRectTween = RectTween( + begin: const Rect.fromLTWH(0.0, 0.0, kLogoWidth, kLogoHeight), + end: const Rect.fromLTWH(0.0, 0.0, kLogoWidth, kImageHeight), + ); + + @override + Widget build(BuildContext context) { + return Semantics( + namesRoute: true, + child: Transform( + transform: Matrix4.identity()..scale(widget.height! / kLogoHeight), + alignment: Alignment.topCenter, + child: SizedBox( + width: kLogoWidth, + child: Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fromRect( + rect: _imageRectTween.lerp(widget.t!)!, + child: Image.asset( + _kSmallLogoImage, + package: _kGalleryAssetsPackage, + fit: BoxFit.contain, + ), + ), + Positioned.fromRect( + rect: _textRectTween.lerp(widget.t!)!, + child: Opacity( + opacity: _textOpacity.transform(widget.t!), + child: Text('PESTO', style: titleStyle, textAlign: TextAlign.center), + ), + ), + ], + ), + ), + ), + ); + } +} + +// A card with the recipe's image, author, and title. +class RecipeCard extends StatelessWidget { + const RecipeCard({super.key, this.recipe, this.onTap}); + + final Recipe? recipe; + final VoidCallback? onTap; + + TextStyle get titleStyle => const PestoStyle(fontSize: 24.0, fontWeight: FontWeight.w600); + TextStyle get authorStyle => const PestoStyle(fontWeight: FontWeight.w500, color: Colors.black54); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( + tag: 'packages/$_kGalleryAssetsPackage/${recipe!.imagePath}', + child: AspectRatio( + aspectRatio: 4.0 / 3.0, + child: Image.asset( + recipe!.imagePath!, + package: recipe!.imagePackage, + fit: BoxFit.cover, + semanticLabel: recipe!.name, + ), + ), + ), + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Image.asset( + recipe!.ingredientsImagePath!, + package: recipe!.ingredientsImagePackage, + width: 48.0, + height: 48.0, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + recipe!.name!, + style: titleStyle, + softWrap: false, + overflow: TextOverflow.ellipsis, + ), + Text(recipe!.author!, style: authorStyle), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +// Displays one recipe. Includes the recipe sheet with a background image. +class RecipePage extends StatefulWidget { + const RecipePage({super.key, this.recipe}); + + final Recipe? recipe; + + @override + State createState() => _RecipePageState(); +} + +class _RecipePageState extends State { + final TextStyle menuItemStyle = const PestoStyle( + fontSize: 15.0, + color: Colors.black54, + height: 24.0 / 15.0, + ); + + double _getAppBarHeight(BuildContext context) => MediaQuery.of(context).size.height * 0.3; + + @override + Widget build(BuildContext context) { + // The full page content with the recipe's image behind it. This + // adjusts based on the size of the screen. If the recipe sheet touches + // the edge of the screen, use a slightly different layout. + final double appBarHeight = _getAppBarHeight(context); + final Size screenSize = MediaQuery.of(context).size; + final bool fullWidth = screenSize.width < _kRecipePageMaxWidth; + final bool isFavorite = _favoriteRecipes.contains(widget.recipe); + return Scaffold( + body: Stack( + children: [ + Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + height: appBarHeight + _kFabHalfSize, + child: Hero( + tag: 'packages/$_kGalleryAssetsPackage/${widget.recipe!.imagePath}', + child: Image.asset( + widget.recipe!.imagePath!, + package: widget.recipe!.imagePackage, + fit: fullWidth ? BoxFit.fitWidth : BoxFit.cover, + ), + ), + ), + CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: appBarHeight - _kFabHalfSize, + backgroundColor: Colors.transparent, + actions: [ + PopupMenuButton( + onSelected: (String item) {}, + itemBuilder: + (BuildContext context) => >[ + _buildMenuItem(Icons.share, 'Tweet recipe'), + _buildMenuItem(Icons.email, 'Email recipe'), + _buildMenuItem(Icons.message, 'Message recipe'), + _buildMenuItem(Icons.people, 'Share on Facebook'), + ], + ), + ], + flexibleSpace: const FlexibleSpaceBar( + background: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment(0.0, -0.2), + colors: [Color(0x60000000), Color(0x00000000)], + ), + ), + ), + ), + ), + SliverToBoxAdapter( + child: Stack( + children: [ + Container( + padding: const EdgeInsets.only(top: _kFabHalfSize), + width: fullWidth ? null : _kRecipePageMaxWidth, + child: RecipeSheet(recipe: widget.recipe), + ), + Positioned( + right: 16.0, + child: FloatingActionButton( + backgroundColor: Colors.redAccent, + onPressed: _toggleFavorite, + child: Icon(isFavorite ? Icons.favorite : Icons.favorite_border), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ); + } + + PopupMenuItem _buildMenuItem(IconData icon, String label) { + return PopupMenuItem( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 24.0), + child: Icon(icon, color: Colors.black54), + ), + Text(label, style: menuItemStyle), + ], + ), + ); + } + + void _toggleFavorite() { + setState(() { + if (_favoriteRecipes.contains(widget.recipe)) { + _favoriteRecipes.remove(widget.recipe); + } else { + _favoriteRecipes.add(widget.recipe); + } + }); + } +} + +/// Displays the recipe's name and instructions. +class RecipeSheet extends StatelessWidget { + RecipeSheet({super.key, this.recipe}); + + final TextStyle titleStyle = const PestoStyle(fontSize: 34.0); + final TextStyle descriptionStyle = const PestoStyle( + fontSize: 15.0, + color: Colors.black54, + height: 24.0 / 15.0, + ); + final TextStyle itemStyle = const PestoStyle(fontSize: 15.0, height: 24.0 / 15.0); + final TextStyle itemAmountStyle = PestoStyle( + fontSize: 15.0, + color: _kTheme.primaryColor, + height: 24.0 / 15.0, + ); + final TextStyle headingStyle = const PestoStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + height: 24.0 / 15.0, + ); + + final Recipe? recipe; + + @override + Widget build(BuildContext context) { + return Material( + child: SafeArea( + top: false, + bottom: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 40.0), + child: Table( + columnWidths: const {0: FixedColumnWidth(64.0)}, + children: [ + TableRow( + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Image.asset( + recipe!.ingredientsImagePath!, + package: recipe!.ingredientsImagePackage, + width: 32.0, + height: 32.0, + alignment: Alignment.centerLeft, + fit: BoxFit.scaleDown, + ), + ), + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Text(recipe!.name!, style: titleStyle), + ), + ], + ), + TableRow( + children: [ + const SizedBox(), + Padding( + padding: const EdgeInsets.only(top: 8.0, bottom: 4.0), + child: Text(recipe!.description!, style: descriptionStyle), + ), + ], + ), + TableRow( + children: [ + const SizedBox(), + Padding( + padding: const EdgeInsets.only(top: 24.0, bottom: 4.0), + child: Text('Ingredients', style: headingStyle), + ), + ], + ), + ...recipe!.ingredients!.map((RecipeIngredient ingredient) { + return _buildItemRow(ingredient.amount!, ingredient.description!); + }), + TableRow( + children: [ + const SizedBox(), + Padding( + padding: const EdgeInsets.only(top: 24.0, bottom: 4.0), + child: Text('Steps', style: headingStyle), + ), + ], + ), + ...recipe!.steps!.map((RecipeStep step) { + return _buildItemRow(step.duration ?? '', step.description!); + }), + ], + ), + ), + ), + ); + } + + TableRow _buildItemRow(String left, String right) { + return TableRow( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Text(left, style: itemAmountStyle), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Text(right, style: itemStyle), + ), + ], + ); + } +} + +class Recipe { + const Recipe({ + this.name, + this.author, + this.description, + this.imagePath, + this.imagePackage, + this.ingredientsImagePath, + this.ingredientsImagePackage, + this.ingredients, + this.steps, + }); + + final String? name; + final String? author; + final String? description; + final String? imagePath; + final String? imagePackage; + final String? ingredientsImagePath; + final String? ingredientsImagePackage; + final List? ingredients; + final List? steps; +} + +class RecipeIngredient { + const RecipeIngredient({this.amount, this.description}); + + final String? amount; + final String? description; +} + +class RecipeStep { + const RecipeStep({this.duration, this.description}); + + final String? duration; + final String? description; +} + +const List kPestoRecipes = [ + Recipe( + name: 'Roasted Chicken', + author: 'Peter Carlsson', + ingredientsImagePath: 'food/icons/main.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + 'The perfect dish to welcome your family and friends with on a crisp autumn night. Pair with roasted veggies to truly impress them.', + imagePath: 'food/roasted_chicken.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '1 whole', description: 'Chicken'), + RecipeIngredient(amount: '1/2 cup', description: 'Butter'), + RecipeIngredient(amount: '1 tbsp', description: 'Onion powder'), + RecipeIngredient(amount: '1 tbsp', description: 'Freshly ground pepper'), + RecipeIngredient(amount: '1 tsp', description: 'Salt'), + ], + steps: [ + RecipeStep(duration: '1 min', description: 'Put in oven'), + RecipeStep(duration: '1hr 45 min', description: 'Cook'), + ], + ), + Recipe( + name: 'Chopped Beet Leaves', + author: 'Trevor Hansen', + ingredientsImagePath: 'food/icons/veggie.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + 'This vegetable has more to offer than just its root. Beet greens can be tossed into a salad to add some variety or sauteed on its own with some oil and garlic.', + imagePath: 'food/chopped_beet_leaves.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [RecipeIngredient(amount: '3 cups', description: 'Beet greens')], + steps: [RecipeStep(duration: '5 min', description: 'Chop')], + ), + Recipe( + name: 'Pesto Pasta', + author: 'Ali Connors', + ingredientsImagePath: 'food/icons/main.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + "With this pesto recipe, you can quickly whip up a meal to satisfy your savory needs. And if you're feeling festive, you can add bacon to taste.", + imagePath: 'food/pesto_pasta.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '1/4 cup ', description: 'Pasta'), + RecipeIngredient(amount: '2 cups', description: 'Fresh basil leaves'), + RecipeIngredient(amount: '1/2 cup', description: 'Parmesan cheese'), + RecipeIngredient(amount: '1/2 cup', description: 'Extra virgin olive oil'), + RecipeIngredient(amount: '1/3 cup', description: 'Pine nuts'), + RecipeIngredient(amount: '1/4 cup', description: 'Lemon juice'), + RecipeIngredient(amount: '3 cloves', description: 'Garlic'), + RecipeIngredient(amount: '1/4 tsp', description: 'Salt'), + RecipeIngredient(amount: '1/8 tsp', description: 'Pepper'), + RecipeIngredient(amount: '3 lbs', description: 'Bacon'), + ], + steps: [RecipeStep(duration: '15 min', description: 'Blend')], + ), + Recipe( + name: 'Cherry Pie', + author: 'Sandra Adams', + ingredientsImagePath: 'food/icons/main.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + "Sometimes when you're craving some cheer in your life you can jumpstart your day with some cherry pie. Dessert for breakfast is perfectly acceptable.", + imagePath: 'food/cherry_pie.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '1', description: 'Pie crust'), + RecipeIngredient(amount: '4 cups', description: 'Fresh or frozen cherries'), + RecipeIngredient(amount: '1 cup', description: 'Granulated sugar'), + RecipeIngredient(amount: '4 tbsp', description: 'Cornstarch'), + RecipeIngredient(amount: '1½ tbsp', description: 'Butter'), + ], + steps: [ + RecipeStep(duration: '15 min', description: 'Mix'), + RecipeStep(duration: '1hr 30 min', description: 'Bake'), + ], + ), + Recipe( + name: 'Spinach Salad', + author: 'Peter Carlsson', + ingredientsImagePath: 'food/icons/spicy.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + "Everyone's favorite leafy green is back. Paired with fresh sliced onion, it's ready to tackle any dish, whether it be a salad or an egg scramble.", + imagePath: 'food/spinach_onion_salad.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '4 cups', description: 'Spinach'), + RecipeIngredient(amount: '1 cup', description: 'Sliced onion'), + ], + steps: [RecipeStep(duration: '5 min', description: 'Mix')], + ), + Recipe( + name: 'Butternut Squash Soup', + author: 'Ali Connors', + ingredientsImagePath: 'food/icons/healthy.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + 'This creamy butternut squash soup will warm you on the chilliest of winter nights and bring a delightful pop of orange to the dinner table.', + imagePath: 'food/butternut_squash_soup.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '1', description: 'Butternut squash'), + RecipeIngredient(amount: '4 cups', description: 'Chicken stock'), + RecipeIngredient(amount: '2', description: 'Potatoes'), + RecipeIngredient(amount: '1', description: 'Onion'), + RecipeIngredient(amount: '1', description: 'Carrot'), + RecipeIngredient(amount: '1', description: 'Celery'), + RecipeIngredient(amount: '1 tsp', description: 'Salt'), + RecipeIngredient(amount: '1 tsp', description: 'Pepper'), + ], + steps: [ + RecipeStep(duration: '10 min', description: 'Prep vegetables'), + RecipeStep(duration: '5 min', description: 'Stir'), + RecipeStep(duration: '1 hr 10 min', description: 'Cook'), + ], + ), + Recipe( + name: 'Spanakopita', + author: 'Trevor Hansen', + ingredientsImagePath: 'food/icons/quick.png', + ingredientsImagePackage: _kGalleryAssetsPackage, + description: + "You 'feta' believe this is a crowd-pleaser! Flaky phyllo pastry surrounds a delicious mixture of spinach and cheeses to create the perfect appetizer.", + imagePath: 'food/spanakopita.png', + imagePackage: _kGalleryAssetsPackage, + ingredients: [ + RecipeIngredient(amount: '1 lb', description: 'Spinach'), + RecipeIngredient(amount: '½ cup', description: 'Feta cheese'), + RecipeIngredient(amount: '½ cup', description: 'Cottage cheese'), + RecipeIngredient(amount: '2', description: 'Eggs'), + RecipeIngredient(amount: '1', description: 'Onion'), + RecipeIngredient(amount: '½ lb', description: 'Phyllo dough'), + ], + steps: [ + RecipeStep(duration: '5 min', description: 'Sauté vegetables'), + RecipeStep(duration: '3 min', description: 'Stir vegetables and other filling ingredients'), + RecipeStep( + duration: '10 min', + description: 'Fill phyllo squares half-full with filling and fold.', + ), + RecipeStep(duration: '40 min', description: 'Bake'), + ], + ), +]; diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/shrine_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/shrine_demo.dart new file mode 100644 index 00000000..847ff773 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/shrine_demo.dart @@ -0,0 +1,15 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; +import 'shrine/app.dart'; + +class ShrineDemo extends StatelessWidget { + const ShrineDemo({super.key}); + + static const String routeName = '/shrine'; // Used by the Gallery app. + + @override + Widget build(BuildContext context) => const ShrineApp(); +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/typography_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/typography_demo.dart new file mode 100644 index 00000000..1367e02d --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/typography_demo.dart @@ -0,0 +1,96 @@ +// Copyright 2014 The Flutter 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:flutter/material.dart'; + +class TextStyleItem extends StatelessWidget { + const TextStyleItem({super.key, required this.name, required this.style, required this.text}); + + final String name; + final TextStyle style; + final String text; + + @override + Widget build(BuildContext context) { + final ThemeData theme = Theme.of(context); + final TextStyle nameStyle = theme.textTheme.bodySmall!.copyWith( + color: theme.textTheme.bodySmall!.color, + ); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 72.0, child: Text(name, style: nameStyle)), + Expanded(child: Text(text, style: style.copyWith(height: 1.0))), + ], + ), + ); + } +} + +class TypographyDemo extends StatelessWidget { + const TypographyDemo({super.key}); + + static const String routeName = '/typography'; + + @override + Widget build(BuildContext context) { + final TextTheme textTheme = Theme.of(context).textTheme; + final List styleItems = [ + TextStyleItem( + name: 'Display Large', + style: textTheme.displayLarge!, + text: 'Regular 57/64 +0', + ), + TextStyleItem( + name: 'Display Medium', + style: textTheme.displayMedium!, + text: 'Regular 45/52 +0', + ), + TextStyleItem( + name: 'Display Small', + style: textTheme.displaySmall!, + text: 'Regular 36/44 +0', + ), + TextStyleItem( + name: 'Headline Large', + style: textTheme.headlineLarge!, + text: 'Regular 32/40 +0', + ), + TextStyleItem( + name: 'Headline Medium', + style: textTheme.headlineMedium!, + text: 'Regular 28/36 +0', + ), + TextStyleItem( + name: 'Headline Small', + style: textTheme.headlineSmall!, + text: 'Regular 24/32 +0', + ), + TextStyleItem(name: 'Title Large', style: textTheme.titleLarge!, text: 'Medium 22/28 +0'), + TextStyleItem( + name: 'Title Medium', + style: textTheme.titleMedium!, + text: 'Medium 16/24 +0.15', + ), + TextStyleItem(name: 'Title Small', style: textTheme.titleSmall!, text: 'Medium 14/20 +0.1'), + TextStyleItem(name: 'Body Large', style: textTheme.bodyLarge!, text: 'Regular 16/24 +0.5'), + TextStyleItem(name: 'Body Medium', style: textTheme.bodyMedium!, text: 'Regular 14/20 +0.25'), + TextStyleItem(name: 'Body Small', style: textTheme.bodySmall!, text: 'Regular 12/16 +0.4'), + TextStyleItem(name: 'Label Large', style: textTheme.labelLarge!, text: 'Medium 14/20 +0.1'), + TextStyleItem(name: 'Label Medium', style: textTheme.labelMedium!, text: 'Medium 12/16 +0.5'), + TextStyleItem(name: 'Label Small', style: textTheme.labelSmall!, text: 'Medium 11/16 +0.5'), + ]; + + return Scaffold( + appBar: AppBar(title: const Text('Typography')), + body: SafeArea( + top: false, + bottom: false, + child: Scrollbar(child: ListView(primary: true, children: styleItems)), + ), + ); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo/video_demo.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo/video_demo.dart new file mode 100644 index 00000000..27da7c8e --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo/video_demo.dart @@ -0,0 +1,322 @@ +// Copyright 2014 The Flutter 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/material.dart'; +import 'package:video_player/video_player.dart'; + +class VideoCard extends StatelessWidget { + const VideoCard({super.key, this.controller, this.title, this.subtitle}); + + final VideoPlayerController? controller; + final String? title; + final String? subtitle; + + Widget _buildInlineVideo() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0), + child: Center( + child: AspectRatio( + aspectRatio: 3 / 2, + child: Hero(tag: controller!, child: VideoPlayerLoading(controller)), + ), + ), + ); + } + + Widget _buildFullScreenVideo() { + return Scaffold( + appBar: AppBar(title: Text(title!)), + body: Center( + child: AspectRatio( + aspectRatio: 3 / 2, + child: Hero(tag: controller!, child: VideoPlayPause(controller)), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + Widget fullScreenRoutePageBuilder( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return _buildFullScreenVideo(); + } + + void pushFullScreenWidget() { + final TransitionRoute route = PageRouteBuilder( + settings: RouteSettings(name: title), + pageBuilder: fullScreenRoutePageBuilder, + ); + + route.completed.then((void value) { + controller!.setVolume(0.0); + }); + + controller!.setVolume(1.0); + Navigator.of(context).push(route); + } + + return SafeArea( + top: false, + bottom: false, + child: Card( + child: Column( + children: [ + ListTile(title: Text(title!), subtitle: Text(subtitle!)), + GestureDetector(onTap: pushFullScreenWidget, child: _buildInlineVideo()), + ], + ), + ), + ); + } +} + +class VideoPlayerLoading extends StatefulWidget { + const VideoPlayerLoading(this.controller, {super.key}); + + final VideoPlayerController? controller; + + @override + State createState() => _VideoPlayerLoadingState(); +} + +class _VideoPlayerLoadingState extends State { + bool? _initialized; + + @override + void initState() { + super.initState(); + _initialized = widget.controller!.value.isInitialized; + widget.controller!.addListener(() { + if (!mounted) { + return; + } + final bool controllerInitialized = widget.controller!.value.isInitialized; + if (_initialized != controllerInitialized) { + setState(() { + _initialized = controllerInitialized; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + if (_initialized!) { + return VideoPlayer(widget.controller!); + } + return Stack( + fit: StackFit.expand, + children: [ + VideoPlayer(widget.controller!), + const Center(child: CircularProgressIndicator()), + ], + ); + } +} + +class VideoPlayPause extends StatefulWidget { + const VideoPlayPause(this.controller, {super.key}); + + final VideoPlayerController? controller; + + @override + State createState() => _VideoPlayPauseState(); +} + +class _VideoPlayPauseState extends State { + _VideoPlayPauseState() { + listener = () { + if (mounted) { + setState(() {}); + } + }; + } + + FadeAnimation? imageFadeAnimation; + late VoidCallback listener; + + VideoPlayerController? get controller => widget.controller; + + @override + void initState() { + super.initState(); + controller!.addListener(listener); + } + + @override + void deactivate() { + controller!.removeListener(listener); + super.deactivate(); + } + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.bottomCenter, + fit: StackFit.expand, + children: [ + GestureDetector( + child: VideoPlayerLoading(controller), + onTap: () { + if (!controller!.value.isInitialized) { + return; + } + if (controller!.value.isPlaying) { + imageFadeAnimation = const FadeAnimation(child: Icon(Icons.pause, size: 100.0)); + controller!.pause(); + } else { + imageFadeAnimation = const FadeAnimation(child: Icon(Icons.play_arrow, size: 100.0)); + controller!.play(); + } + }, + ), + Center(child: imageFadeAnimation), + ], + ); + } +} + +class FadeAnimation extends StatefulWidget { + const FadeAnimation({super.key, this.child, this.duration = const Duration(milliseconds: 500)}); + + final Widget? child; + final Duration duration; + + @override + State createState() => _FadeAnimationState(); +} + +class _FadeAnimationState extends State with SingleTickerProviderStateMixin { + late AnimationController animationController; + + @override + void initState() { + super.initState(); + animationController = AnimationController(duration: widget.duration, vsync: this); + animationController.addListener(() { + if (mounted) { + setState(() {}); + } + }); + animationController.forward(from: 0.0); + } + + @override + void deactivate() { + animationController.stop(); + super.deactivate(); + } + + @override + void didUpdateWidget(FadeAnimation oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.child != widget.child) { + animationController.forward(from: 0.0); + } + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return animationController.isAnimating + ? Opacity(opacity: 1.0 - animationController.value, child: widget.child) + : Container(); + } +} + +class VideoDemo extends StatefulWidget { + const VideoDemo({super.key}); + + static const String routeName = '/video'; + + @override + State createState() => _VideoDemoState(); +} + +class _VideoDemoState extends State with SingleTickerProviderStateMixin { + final VideoPlayerController butterflyController = VideoPlayerController.asset( + 'videos/butterfly.mp4', + package: 'flutter_gallery_assets', + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + final VideoPlayerController beeController = VideoPlayerController.asset( + 'videos/bee.mp4', + package: 'flutter_gallery_assets', + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + bool isDisposed = false; + + // Only non-test mobile environments are supported for this demo. + bool isSupported = Platform.isAndroid || Platform.isIOS; + + @override + void initState() { + super.initState(); + if (!isSupported) { + return; + } + + Future initController(VideoPlayerController controller, String name) async { + controller.setLooping(true); + controller.setVolume(0.0); + controller.play(); + await controller.initialize(); + if (mounted) { + setState(() {}); + } + } + + initController(butterflyController, 'butterfly'); + initController(beeController, 'bee'); + } + + @override + void dispose() { + isDisposed = true; + butterflyController.dispose(); + beeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Videos')), + body: + isSupported + ? Scrollbar( + child: ListView( + primary: true, + children: [ + VideoCard( + title: 'Butterfly', + subtitle: '… flutters by', + controller: butterflyController, + ), + VideoCard( + title: 'Bee', + subtitle: '… gently buzzing', + controller: beeController, + ), + ], + ), + ) + : const Placeholder(), + ); + } +} diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/demo_lists.dart b/flutter/dev/integration_tests/flutter_gallery/lib/demo_lists.dart new file mode 100644 index 00000000..271d0569 --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/demo_lists.dart @@ -0,0 +1,43 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Demos for which timeline data will be collected using +// FlutterDriver.traceAction(). +// +// Warning: The number of tests executed with timeline collection enabled +// significantly impacts heap size of the running app. When run with +// --trace-startup, as we do in this test, the VM stores trace events in an +// endless buffer instead of a ring buffer. +// +// These names must match GalleryItem titles from kAllGalleryDemos +// in dev/integration_tests/flutter_gallery/lib/gallery/demos.dart +const List kProfiledDemos = [ + 'Shrine@Studies', + 'Contact profile@Studies', + 'Animation@Studies', + 'Bottom navigation@Material', + 'Buttons@Material', + 'Cards@Material', + 'Chips@Material', + 'Dialogs@Material', + 'Pickers@Material', +]; + +// There are 3 places where the Gallery demos are traversed. +// 1- In widget tests such as dev/integration_tests/flutter_gallery/test/smoke_test.dart +// 2- In driver tests such as dev/integration_tests/flutter_gallery/test_driver/transitions_perf_test.dart +// 3- In on-device instrumentation tests such as dev/integration_tests/flutter_gallery/test/live_smoketest.dart +// +// If you change navigation behavior in the Gallery or in the framework, make +// sure all 3 are covered. + +// Demos that will be backed out of within FlutterDriver.runUnsynchronized(); +// +// These names must match GalleryItem titles from kAllGalleryDemos +// in dev/integration_tests/flutter_gallery/lib/gallery/demos.dart +const List kUnsynchronizedDemos = [ + 'Progress indicators@Material', + 'Activity Indicator@Cupertino', + 'Video@Media', +]; diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/main.dart b/flutter/dev/integration_tests/flutter_gallery/lib/main.dart new file mode 100644 index 00000000..ca4d8adf --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/main.dart @@ -0,0 +1,12 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Thanks for checking out Flutter! +// Like what you see? Tweet us @FlutterDev + +import 'package:flutter/material.dart'; + +import 'gallery/app.dart'; + +void main() => runApp(const GalleryApp()); diff --git a/flutter/dev/integration_tests/flutter_gallery/lib/main_publish.dart b/flutter/dev/integration_tests/flutter_gallery/lib/main_publish.dart new file mode 100644 index 00000000..faf2bd8d --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/lib/main_publish.dart @@ -0,0 +1,13 @@ +// Copyright 2014 The Flutter 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 'gallery/home.dart'; +import 'main.dart' as other_main; + +// This main chain-calls main.dart's main. This file is used for publishing +// the gallery and removes the 'PREVIEW' banner. +void main() { + GalleryHome.showPreviewBanner = false; + other_main.main(); +} diff --git a/flutter/dev/integration_tests/flutter_gallery/pubspec.yaml b/flutter/dev/integration_tests/flutter_gallery/pubspec.yaml new file mode 100644 index 00000000..0e72f12d --- /dev/null +++ b/flutter/dev/integration_tests/flutter_gallery/pubspec.yaml @@ -0,0 +1,275 @@ +name: flutter_gallery + +environment: + sdk: ^3.7.0-0 + +dependencies: + flutter: + sdk: flutter + collection: 1.19.1 + intl: 0.19.0 + string_scanner: 1.4.1 + url_launcher: 6.3.1 + cupertino_icons: 1.0.8 + video_player: 2.9.2 + scoped_model: 2.0.0 + shrine_images: 2.0.2 + + # Also update dev/benchmarks/complex_layout/pubspec.yaml + # and dev/benchmarks/macrobenchmarks/pubspec.yaml + flutter_gallery_assets: 1.0.2 + + characters: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.15.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.16.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + plugin_platform_interface: 2.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_android: 6.3.14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_ios: 6.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_linux: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_macos: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_platform_interface: 2.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_web: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_windows: 3.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + video_player_android: 2.7.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + video_player_avfoundation: 2.6.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + video_player_platform_interface: 6.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + video_player_web: 2.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + flutter_goldens: + sdk: flutter + test: 1.25.14 + integration_test: + sdk: flutter + + _fe_analyzer_shared: 76.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 6.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + async: 2.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.11.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 7.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker: 10.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_flutter_testing: 3.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + leak_tracker_testing: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 3.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + process: 5.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.12.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.7.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 14.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket: 0.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webdriver: 3.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + assets: + - lib/gallery/example_code.dart + - packages/flutter_gallery_assets/people/ali_landscape.png + - packages/flutter_gallery_assets/monochrome/red-square-1024x1024.png + - packages/flutter_gallery_assets/logos/flutter_white/logo.png + - packages/flutter_gallery_assets/logos/fortnightly/fortnightly_logo.png + - packages/flutter_gallery_assets/videos/bee.mp4 + - packages/flutter_gallery_assets/videos/butterfly.mp4 + - packages/flutter_gallery_assets/animated_images/animated_flutter_lgtm.gif + - packages/flutter_gallery_assets/animated_images/animated_flutter_stickers.webp + - packages/flutter_gallery_assets/food/butternut_squash_soup.png + - packages/flutter_gallery_assets/food/cherry_pie.png + - packages/flutter_gallery_assets/food/chopped_beet_leaves.png + - packages/flutter_gallery_assets/food/fruits.png + - packages/flutter_gallery_assets/food/pesto_pasta.png + - packages/flutter_gallery_assets/food/roasted_chicken.png + - packages/flutter_gallery_assets/food/spanakopita.png + - packages/flutter_gallery_assets/food/spinach_onion_salad.png + - packages/flutter_gallery_assets/food/icons/fish.png + - packages/flutter_gallery_assets/food/icons/healthy.png + - packages/flutter_gallery_assets/food/icons/main.png + - packages/flutter_gallery_assets/food/icons/meat.png + - packages/flutter_gallery_assets/food/icons/quick.png + - packages/flutter_gallery_assets/food/icons/spicy.png + - packages/flutter_gallery_assets/food/icons/veggie.png + - packages/flutter_gallery_assets/logos/pesto/logo_small.png + - packages/flutter_gallery_assets/places/india_chennai_flower_market.png + - packages/flutter_gallery_assets/places/india_thanjavur_market.png + - packages/flutter_gallery_assets/places/india_tanjore_bronze_works.png + - packages/flutter_gallery_assets/places/india_tanjore_market_merchant.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple.png + - packages/flutter_gallery_assets/places/india_pondicherry_salt_farm.png + - packages/flutter_gallery_assets/places/india_chennai_highway.png + - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png + - packages/flutter_gallery_assets/places/india_tanjore_thanjavur_temple_carvings.png + - packages/flutter_gallery_assets/places/india_chettinad_produce.png + - packages/flutter_gallery_assets/places/india_tanjore_market_technology.png + - packages/flutter_gallery_assets/places/india_pondicherry_beach.png + - packages/flutter_gallery_assets/places/india_pondicherry_fisherman.png + - packages/flutter_gallery_assets/products/backpack.png + - packages/flutter_gallery_assets/products/belt.png + - packages/flutter_gallery_assets/products/cup.png + - packages/flutter_gallery_assets/products/deskset.png + - packages/flutter_gallery_assets/products/dress.png + - packages/flutter_gallery_assets/products/earrings.png + - packages/flutter_gallery_assets/products/flatwear.png + - packages/flutter_gallery_assets/products/hat.png + - packages/flutter_gallery_assets/products/jacket.png + - packages/flutter_gallery_assets/products/jumper.png + - packages/flutter_gallery_assets/products/kitchen_quattro.png + - packages/flutter_gallery_assets/products/napkins.png + - packages/flutter_gallery_assets/products/planters.png + - packages/flutter_gallery_assets/products/platter.png + - packages/flutter_gallery_assets/products/scarf.png + - packages/flutter_gallery_assets/products/shirt.png + - packages/flutter_gallery_assets/products/sunnies.png + - packages/flutter_gallery_assets/products/sweater.png + - packages/flutter_gallery_assets/products/sweats.png + - packages/flutter_gallery_assets/products/table.png + - packages/flutter_gallery_assets/products/teaset.png + - packages/flutter_gallery_assets/products/top.png + - packages/flutter_gallery_assets/people/square/ali.png + - packages/flutter_gallery_assets/people/square/peter.png + - packages/flutter_gallery_assets/people/square/sandra.png + - packages/flutter_gallery_assets/people/square/stella.png + - packages/flutter_gallery_assets/people/square/trevor.png + - packages/shrine_images/diamond.png + - packages/shrine_images/slanted_menu.png + - packages/shrine_images/0-0.jpg + - packages/shrine_images/1-0.jpg + - packages/shrine_images/2-0.jpg + - packages/shrine_images/3-0.jpg + - packages/shrine_images/4-0.jpg + - packages/shrine_images/5-0.jpg + - packages/shrine_images/6-0.jpg + - packages/shrine_images/7-0.jpg + - packages/shrine_images/8-0.jpg + - packages/shrine_images/9-0.jpg + - packages/shrine_images/10-0.jpg + - packages/shrine_images/11-0.jpg + - packages/shrine_images/12-0.jpg + - packages/shrine_images/13-0.jpg + - packages/shrine_images/14-0.jpg + - packages/shrine_images/15-0.jpg + - packages/shrine_images/16-0.jpg + - packages/shrine_images/17-0.jpg + - packages/shrine_images/18-0.jpg + - packages/shrine_images/19-0.jpg + - packages/shrine_images/20-0.jpg + - packages/shrine_images/21-0.jpg + - packages/shrine_images/22-0.jpg + - packages/shrine_images/23-0.jpg + - packages/shrine_images/24-0.jpg + - packages/shrine_images/25-0.jpg + - packages/shrine_images/26-0.jpg + - packages/shrine_images/27-0.jpg + - packages/shrine_images/28-0.jpg + - packages/shrine_images/29-0.jpg + - packages/shrine_images/30-0.jpg + - packages/shrine_images/31-0.jpg + - packages/shrine_images/32-0.jpg + - packages/shrine_images/33-0.jpg + - packages/shrine_images/34-0.jpg + - packages/shrine_images/35-0.jpg + - packages/shrine_images/36-0.jpg + - packages/shrine_images/37-0.jpg + + fonts: + - family: Raleway + fonts: + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-Regular.ttf + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/raleway/Raleway-SemiBold.ttf + weight: 600 + - family: AbrilFatface + fonts: + - asset: packages/flutter_gallery_assets/fonts/abrilfatface/AbrilFatface-Regular.ttf + - family: GalleryIcons + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/gallery_icons/GalleryIcons.ttf + - family: GoogleSans + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-BoldItalic.ttf + weight: 700 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Bold.ttf + weight: 700 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Italic.ttf + weight: 400 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-MediumItalic.ttf + weight: 500 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSans-Regular.ttf + weight: 400 + - family: GoogleSansDisplay + fonts: + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-BoldItalic.ttf + weight: 700 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Bold.ttf + weight: 700 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Italic.ttf + weight: 400 + style: italic + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-MediumItalic.ttf + style: italic + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Medium.ttf + weight: 500 + - asset: packages/flutter_gallery_assets/fonts/private/googlesans/GoogleSansDisplay-Regular.ttf + weight: 400 + - family: LibreFranklin + fonts: + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Bold.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Light.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Medium.ttf + - asset: packages/flutter_gallery_assets/fonts/librefranklin/LibreFranklin-Regular.ttf + - family: Merriweather + fonts: + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-BlackItalic.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Italic.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf + - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf + +# PUBSPEC CHECKSUM: 0e5e diff --git a/flutter/flutter_console.bat b/flutter/flutter_console.bat new file mode 100644 index 00000000..58307645 --- /dev/null +++ b/flutter/flutter_console.bat @@ -0,0 +1,38 @@ +@ECHO off +REM Copyright 2014 The Flutter Authors. All rights reserved. +REM Use of this source code is governed by a BSD-style license that can be +REM found in the LICENSE file. + +TITLE Flutter Console + +echo. +echo ######## ## ## ## ######## ######## ######## ######## +echo ## ## ## ## ## ## ## ## ## +echo ## ## ## ## ## ## ## ## ## +echo ###### ## ## ## ## ## ###### ######## +echo ## ## ## ## ## ## ## ## ## +echo ## ## ## ## ## ## ## ## ## +echo ## ######## ####### ## ## ######## ## ## +echo. +echo. + +echo WELCOME to the Flutter Console. +echo =============================================================================== +echo. +echo Use the console below this message to interact with the "flutter" command. +echo Run "flutter doctor" to check if your system is ready to run Flutter apps. +echo Run "flutter create " to create a new Flutter project. +echo. +echo Run "flutter help" to see all available commands. +echo. +echo Want to use an IDE to interact with Flutter? https://flutter.dev/ide-setup/ +echo. +echo Want to run the "flutter" command from any Command Prompt or PowerShell window? +echo Add Flutter to your PATH: https://flutter.dev/setup-windows/#update-your-path +echo. +echo =============================================================================== + +REM "%~dp0" is the directory of this file including trailing backslash +SET PATH=%~dp0bin;%PATH% + +CALL cmd /K "@echo off & cd %USERPROFILE% & echo on" diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 4cf37b85..56855470 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -143,7 +143,20 @@ class PlayerView extends HookConsumerWidget { }); }, ), - ) + ), + Tooltip( + tooltip: TooltipContainer( + child: Text(context.l10n.lyrics), + ), + child: IconButton.ghost( + icon: const Icon(SpotubeIcons.lyrics, size: 18), + onPressed: currentTrack == null + ? null + : () { + context.pushRoute(const PlayerLyricsRoute()); + }, + ), + ), ], ), ), diff --git a/lib/modules/player/player_controls.dart b/lib/modules/player/player_controls.dart index 4d5d6deb..082d0467 100644 --- a/lib/modules/player/player_controls.dart +++ b/lib/modules/player/player_controls.dart @@ -14,6 +14,7 @@ import 'package:spotube/modules/player/use_progress.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/provider/volume_provider.dart'; class PlayerControls extends HookConsumerWidget { final PaletteGenerator? palette; @@ -35,11 +36,20 @@ class PlayerControls extends HookConsumerWidget { SeekIntent(ref, true), const SingleActivator(LogicalKeyboardKey.arrowLeft): SeekIntent(ref, false), + const SingleActivator(LogicalKeyboardKey.space): + PlayPauseIntent(ref), + const SingleActivator(LogicalKeyboardKey.arrowUp): + VolumeUpIntent(ref), + const SingleActivator(LogicalKeyboardKey.arrowDown): + VolumeDownIntent(ref), }, [ref]); final actions = useMemoized( () => { SeekIntent: SeekAction(), + PlayPauseIntent: PlayPauseAction(), + VolumeUpIntent: VolumeUpAction(), + VolumeDownIntent: VolumeDownAction(), }, []); final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); @@ -256,7 +266,16 @@ class PlayerControls extends HookConsumerWidget { }), ], ), - const SizedBox(height: 5) + const SizedBox(height: 5), + Consumer(builder: (context, ref, _) { + final volume = ref.watch(volumeProvider); + return VolumeSlider( + value: volume, + onChanged: (value) { + ref.read(volumeProvider.notifier).setVolume(value); + }, + ); + }), ], ), ), @@ -264,3 +283,33 @@ class PlayerControls extends HookConsumerWidget { ); } } + +class VolumeUpIntent extends Intent { + final WidgetRef ref; + const VolumeUpIntent(this.ref); +} + +class VolumeUpAction extends Action { + @override + invoke(intent) async { + final volume = intent.ref.read(volumeProvider); + final newVolume = (volume + 0.1).clamp(0.0, 1.0); + await intent.ref.read(volumeProvider.notifier).setVolume(newVolume); + return null; + } +} + +class VolumeDownIntent extends Intent { + final WidgetRef ref; + const VolumeDownIntent(this.ref); +} + +class VolumeDownAction extends Action { + @override + invoke(intent) async { + final volume = intent.ref.read(volumeProvider); + final newVolume = (volume - 0.1).clamp(0.0, 1.0); + await intent.ref.read(volumeProvider.notifier).setVolume(newVolume); + return null; + } +} diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 0948bdeb..07fd81ab 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -63,6 +63,17 @@ class SettingsPage extends HookConsumerWidget { ), ), const SizedBox(height: 200), + ListTile( + title: Text('Dark Mode'), + trailing: Switch( + value: ref.watch(userPreferencesProvider).themeMode == ThemeMode.dark, + onChanged: (value) { + ref.read(userPreferencesProvider.notifier).setThemeMode( + value ? ThemeMode.dark : ThemeMode.light, + ); + }, + ), + ), ], ), ),